headless_lms_models/
email_templates.rs

1use crate::prelude::*;
2
3#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, sqlx::Type)]
4#[cfg_attr(feature = "ts_rs", derive(TS))]
5#[sqlx(type_name = "email_template_type", rename_all = "snake_case")]
6#[serde(rename_all = "snake_case")]
7pub enum EmailTemplateType {
8    ResetPasswordEmail,
9    DeleteUserEmail,
10    ConfirmEmailCode,
11    Generic,
12}
13
14#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
15#[cfg_attr(feature = "ts_rs", derive(TS))]
16pub struct EmailTemplate {
17    pub id: Uuid,
18    pub created_at: DateTime<Utc>,
19    pub updated_at: DateTime<Utc>,
20    pub deleted_at: Option<DateTime<Utc>>,
21    pub content: Option<serde_json::Value>,
22    pub template_type: EmailTemplateType,
23    pub subject: Option<String>,
24    pub exercise_completions_threshold: Option<i32>,
25    pub points_threshold: Option<i32>,
26    pub course_id: Option<Uuid>,
27    pub language: Option<String>,
28}
29
30#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
31#[cfg_attr(feature = "ts_rs", derive(TS))]
32pub struct EmailTemplateNew {
33    pub template_type: EmailTemplateType,
34    pub language: Option<String>,
35    pub content: Option<serde_json::Value>,
36    pub subject: Option<String>,
37}
38
39#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
40#[cfg_attr(feature = "ts_rs", derive(TS))]
41pub struct EmailTemplateUpdate {
42    pub template_type: EmailTemplateType,
43    pub subject: String,
44    pub content: serde_json::Value,
45    pub exercise_completions_threshold: Option<i32>,
46    pub points_threshold: Option<i32>,
47}
48
49pub async fn get_email_templates(
50    conn: &mut PgConnection,
51    course_id: Uuid,
52) -> ModelResult<Vec<EmailTemplate>> {
53    let res = sqlx::query_as!(
54        EmailTemplate,
55        r#"
56SELECT id,
57  created_at,
58  updated_at,
59  deleted_at,
60  content,
61  email_template_type AS "template_type: EmailTemplateType",
62  subject,
63  exercise_completions_threshold,
64  points_threshold,
65  course_id,
66  language
67FROM email_templates
68WHERE course_id = $1
69  AND deleted_at IS NULL
70        "#,
71        course_id
72    )
73    .fetch_all(conn)
74    .await?;
75    Ok(res)
76}
77
78pub async fn get_all_email_templates(conn: &mut PgConnection) -> ModelResult<Vec<EmailTemplate>> {
79    let res = sqlx::query_as!(
80        EmailTemplate,
81        r#"
82SELECT id,
83  created_at,
84  updated_at,
85  deleted_at,
86  content,
87  email_template_type AS "template_type: EmailTemplateType",
88  subject,
89  exercise_completions_threshold,
90  points_threshold,
91  course_id,
92  language
93FROM email_templates
94WHERE deleted_at IS NULL
95        "#,
96    )
97    .fetch_all(conn)
98    .await?;
99    Ok(res)
100}
101
102pub async fn get_generic_email_template_by_type_and_language(
103    conn: &mut PgConnection,
104    template_type: EmailTemplateType,
105    language: &str,
106) -> ModelResult<EmailTemplate> {
107    let res = sqlx::query_as!(
108        EmailTemplate,
109        r#"
110SELECT id,
111  created_at,
112  updated_at,
113  deleted_at,
114  content,
115  email_template_type AS "template_type: EmailTemplateType",
116  subject,
117  exercise_completions_threshold,
118  points_threshold,
119  course_id,
120  language
121FROM email_templates
122WHERE email_template_type = $1
123  AND course_id IS NULL
124  AND deleted_at IS NULL
125  AND (
126    language = $2
127    OR language = 'en'
128    OR language IS NULL
129  )
130ORDER BY CASE
131    WHEN language = $2 THEN 0
132    WHEN language = 'en' THEN 1
133    WHEN language IS NULL THEN 2
134    ELSE 3
135  END
136LIMIT 1
137        "#,
138        template_type as EmailTemplateType,
139        language
140    )
141    .fetch_one(conn)
142    .await?;
143    Ok(res)
144}
145
146pub async fn insert_email_template(
147    conn: &mut PgConnection,
148    course_id: Option<Uuid>,
149    email_template: EmailTemplateNew,
150    subject: Option<&'_ str>,
151) -> ModelResult<EmailTemplate> {
152    let subject_to_use = email_template.subject.as_deref().or(subject);
153    let res = sqlx::query_as!(
154        EmailTemplate,
155        r#"
156INSERT INTO email_templates (
157    email_template_type,
158    course_id,
159    subject,
160    language,
161    content
162  )
163VALUES ($1, $2, $3, $4, $5) ON CONFLICT (email_template_type, language, deleted_at)
164WHERE course_id IS NULL
165  AND deleted_at IS NULL DO
166UPDATE
167SET subject = COALESCE(EXCLUDED.subject, email_templates.subject),
168  content = COALESCE(EXCLUDED.content, email_templates.content),
169  updated_at = NOW()
170RETURNING id,
171  created_at,
172  updated_at,
173  deleted_at,
174  content,
175  email_template_type AS "template_type: EmailTemplateType",
176  subject,
177  exercise_completions_threshold,
178  points_threshold,
179  course_id,
180  language
181        "#,
182        email_template.template_type as EmailTemplateType,
183        course_id,
184        subject_to_use,
185        email_template.language,
186        email_template.content,
187    )
188    .fetch_one(conn)
189    .await?;
190    Ok(res)
191}
192
193pub async fn get_email_template(
194    conn: &mut PgConnection,
195    email_template_id: Uuid,
196) -> ModelResult<EmailTemplate> {
197    let res = sqlx::query_as!(
198        EmailTemplate,
199        r#"
200SELECT id,
201  created_at,
202  updated_at,
203  deleted_at,
204  content,
205  email_template_type AS "template_type: EmailTemplateType",
206  subject,
207  exercise_completions_threshold,
208  points_threshold,
209  course_id,
210  language
211FROM email_templates
212WHERE id = $1
213  AND deleted_at IS NULL
214        "#,
215        email_template_id
216    )
217    .fetch_one(conn)
218    .await?;
219    Ok(res)
220}
221
222pub async fn update_email_template(
223    conn: &mut PgConnection,
224    email_template_id: Uuid,
225    email_template_update: EmailTemplateUpdate,
226) -> ModelResult<EmailTemplate> {
227    let res = sqlx::query_as!(
228        EmailTemplate,
229        r#"
230UPDATE email_templates
231SET email_template_type = $1,
232  subject = $2,
233  content = $3,
234  exercise_completions_threshold = $4,
235  points_threshold = $5
236WHERE id = $6
237  AND deleted_at IS NULL
238RETURNING id,
239  created_at,
240  updated_at,
241  deleted_at,
242  content,
243  email_template_type AS "template_type: EmailTemplateType",
244  subject,
245  exercise_completions_threshold,
246  points_threshold,
247  course_id,
248  language
249  "#,
250        email_template_update.template_type as EmailTemplateType,
251        email_template_update.subject,
252        email_template_update.content,
253        email_template_update.exercise_completions_threshold,
254        email_template_update.points_threshold,
255        email_template_id
256    )
257    .fetch_one(conn)
258    .await?;
259    Ok(res)
260}
261
262pub async fn delete_email_template(
263    conn: &mut PgConnection,
264    email_template_id: Uuid,
265) -> ModelResult<EmailTemplate> {
266    let deleted = sqlx::query_as!(
267        EmailTemplate,
268        r#"
269UPDATE email_templates
270SET deleted_at = NOW()
271WHERE id = $1
272  AND deleted_at IS NULL
273RETURNING id,
274  created_at,
275  updated_at,
276  deleted_at,
277  content,
278  email_template_type AS "template_type: EmailTemplateType",
279  subject,
280  exercise_completions_threshold,
281  points_threshold,
282  course_id,
283  language
284  "#,
285        email_template_id
286    )
287    .fetch_one(conn)
288    .await?;
289    Ok(deleted)
290}