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}