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