1use crate::prelude::*;
2use utoipa::ToSchema;
3
4#[derive(Debug, Serialize, Deserialize, PartialEq, 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 insert_email_template(
138 conn: &mut PgConnection,
139 course_id: Option<Uuid>,
140 email_template: EmailTemplateNew,
141 subject: Option<&'_ str>,
142) -> ModelResult<EmailTemplate> {
143 let subject_to_use = email_template.subject.as_deref().or(subject);
144 let res = sqlx::query_as!(
145 EmailTemplate,
146 r#"
147INSERT INTO email_templates (
148 email_template_type,
149 course_id,
150 subject,
151 language,
152 content
153 )
154VALUES ($1, $2, $3, $4, $5) ON CONFLICT (email_template_type, language, deleted_at)
155WHERE course_id IS NULL
156 AND deleted_at IS NULL DO
157UPDATE
158SET subject = COALESCE(EXCLUDED.subject, email_templates.subject),
159 content = COALESCE(EXCLUDED.content, email_templates.content),
160 updated_at = NOW()
161RETURNING *
162 "#,
163 email_template.template_type as EmailTemplateType,
164 course_id,
165 subject_to_use,
166 email_template.language,
167 email_template.content,
168 )
169 .fetch_one(conn)
170 .await?;
171 Ok(res)
172}
173
174pub async fn get_email_template(
175 conn: &mut PgConnection,
176 email_template_id: Uuid,
177) -> ModelResult<EmailTemplate> {
178 let res = sqlx::query_as!(
179 EmailTemplate,
180 r#"
181SELECT *
182FROM email_templates
183WHERE id = $1
184 AND deleted_at IS NULL
185 "#,
186 email_template_id
187 )
188 .fetch_one(conn)
189 .await?;
190 Ok(res)
191}
192
193pub async fn update_email_template(
194 conn: &mut PgConnection,
195 email_template_id: Uuid,
196 email_template_update: EmailTemplateUpdate,
197) -> ModelResult<EmailTemplate> {
198 let res = sqlx::query_as!(
199 EmailTemplate,
200 r#"
201UPDATE email_templates
202SET email_template_type = $1,
203 subject = $2,
204 content = $3,
205 exercise_completions_threshold = $4,
206 points_threshold = $5
207WHERE id = $6
208 AND deleted_at IS NULL
209RETURNING *
210 "#,
211 email_template_update.template_type as EmailTemplateType,
212 email_template_update.subject,
213 email_template_update.content,
214 email_template_update.exercise_completions_threshold,
215 email_template_update.points_threshold,
216 email_template_id
217 )
218 .fetch_one(conn)
219 .await?;
220 Ok(res)
221}
222
223pub async fn delete_email_template(
224 conn: &mut PgConnection,
225 email_template_id: Uuid,
226) -> ModelResult<EmailTemplate> {
227 let deleted = sqlx::query_as!(
228 EmailTemplate,
229 r#"
230UPDATE email_templates
231SET deleted_at = NOW()
232WHERE id = $1
233 AND deleted_at IS NULL
234RETURNING *
235 "#,
236 email_template_id
237 )
238 .fetch_one(conn)
239 .await?;
240 Ok(deleted)
241}