headless_lms_models/
email_layouts.rs1use headless_lms_utils::email_processor::{self, DEFAULT_EMAIL_THEME, EmailTheme};
4
5use crate::prelude::*;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct EmailLayout {
10 pub id: Uuid,
11 pub updated_at: DateTime<Utc>,
12 pub language: Option<String>,
14 pub html: String,
15 pub button_background_color: String,
17 pub button_text_color: String,
19}
20
21pub async fn get_all_live(conn: &mut PgConnection) -> ModelResult<Vec<EmailLayout>> {
23 let res = sqlx::query_as!(
24 EmailLayout,
25 r#"
26SELECT id,
27 updated_at,
28 language,
29 html,
30 button_background_color,
31 button_text_color
32FROM email_layouts
33WHERE deleted_at IS NULL
34 "#
35 )
36 .fetch_all(conn)
37 .await?;
38 Ok(res)
39}
40
41pub fn find_for_language<'a>(
45 layouts: &'a [EmailLayout],
46 language: Option<&str>,
47) -> Option<&'a EmailLayout> {
48 let with_language = |wanted: &str| {
49 layouts.iter().find(|layout| {
50 layout
51 .language
52 .as_deref()
53 .is_some_and(|language| language.eq_ignore_ascii_case(wanted))
54 })
55 };
56 let language = language
57 .map(str::trim)
58 .filter(|language| !language.is_empty());
59 language
60 .and_then(|language| {
61 with_language(language).or_else(|| {
62 let (primary, _) = language.split_once(['-', '_'])?;
63 with_language(primary)
64 })
65 })
66 .or_else(|| layouts.iter().find(|layout| layout.language.is_none()))
67}
68
69#[derive(Debug, Clone, Copy)]
72pub struct ResolvedLayout<'a> {
73 pub html: &'a str,
74 pub theme: EmailTheme<'a>,
75}
76
77impl<'a> ResolvedLayout<'a> {
78 pub fn for_language(layouts: &'a [EmailLayout], language: Option<&str>) -> Self {
80 match find_for_language(layouts, language) {
81 Some(layout) => Self {
82 html: &layout.html,
83 theme: EmailTheme {
84 button_background_color: &layout.button_background_color,
85 button_text_color: &layout.button_text_color,
86 },
87 },
88 None => Self {
89 html: email_processor::DEFAULT_EMAIL_LAYOUT,
90 theme: DEFAULT_EMAIL_THEME,
91 },
92 }
93 }
94}