Skip to main content

headless_lms_server/programs/seed/
seed_generic_emails.rs

1use headless_lms_models::{
2    email_templates::{EmailTemplateNew, EmailTemplateType, insert_email_template},
3    user_passwords::insert_password_reset_token,
4};
5use serde_json::json;
6use sqlx::{Pool, Postgres};
7use uuid::Uuid;
8
9use super::seed_users::SeedUsersResult;
10
11pub async fn seed_generic_emails(
12    db_pool: Pool<Postgres>,
13    seed_users_result: SeedUsersResult,
14) -> anyhow::Result<()> {
15    info!("inserting password reset emails");
16
17    let mut conn = db_pool.acquire().await?;
18
19    let english_subject = Some("Reset password request");
20    let english_body = json!([
21        {
22            "type": "core/paragraph",
23            "isValid": true,
24            "clientId": "95acea49-1c92-4d54-9854-707e1bfee010",
25            "attributes": {
26                "content": "Hello, it seems you requested a password reset.",
27                "drop_cap": false
28            },
29            "innerBlocks": []
30        },
31        {
32            "type": "core/paragraph",
33            "isValid": true,
34            "clientId": "ceac591d-b291-40b2-8da7-6f437a6a8fce",
35            "attributes": {
36                "content": "You can reset your password here: {{RESET_LINK}}",
37                "drop_cap": false
38            },
39            "innerBlocks": []
40        },
41        {
42            "type": "core/paragraph",
43            "isValid": true,
44            "clientId": "6a4165a1-38e1-4a17-b364-5f265afc7d23",
45            "attributes": {
46                "content": "If you did not request a password reset, please ignore this message.",
47                "drop_cap": false
48            },
49            "innerBlocks": []
50        }
51    ]);
52
53    let english_template = EmailTemplateNew {
54        template_type: EmailTemplateType::ResetPasswordEmail,
55        language: Some("en".to_string()),
56        content: Some(english_body),
57        subject: english_subject.map(|s| s.to_string()),
58    };
59
60    insert_email_template(&mut conn, None, english_template, english_subject).await?;
61
62    let finnish_subject = Some("Salasanan palautuspyyntö");
63    let finnish_body = json!([
64        {
65            "type": "core/paragraph",
66            "isValid": true,
67            "clientId": "a9de49ff-919f-44b4-a085-9210ce0da94b",
68            "attributes": {
69                "content": "Hei, olet pyytänyt salasanan palautusta.",
70                "drop_cap": false
71            },
72            "innerBlocks": []
73        },
74        {
75            "type": "core/paragraph",
76            "isValid": true,
77            "clientId": "c145bae4-9ff5-4194-8913-50f8900c20c8",
78            "attributes": {
79                "content": "Voit palauttaa salasanasi tästä: {{RESET_LINK}}",
80                "drop_cap": false
81            },
82            "innerBlocks": []
83        },
84        {
85            "type": "core/paragraph",
86            "isValid": true,
87            "clientId": "ebd0d430-0ae3-4b85-9e8e-96481660ded4",
88            "attributes": {
89                "content": "Jos et pyytänyt salasanan palautusta, voit jättää tämän viestin huomiotta.",
90                "drop_cap": false
91            },
92            "innerBlocks": []
93        }
94    ]);
95
96    let finnish_template = EmailTemplateNew {
97        template_type: EmailTemplateType::ResetPasswordEmail,
98        language: Some("fi".to_string()),
99        content: Some(finnish_body),
100        subject: finnish_subject.map(|s| s.to_string()),
101    };
102
103    insert_email_template(&mut conn, None, finnish_template, finnish_subject).await?;
104
105    info!("inserting password reset token for user");
106    let SeedUsersResult { sign_up_user, .. } = seed_users_result;
107    insert_password_reset_token(
108        &mut conn,
109        sign_up_user,
110        Uuid::parse_str("5a831370-6b7e-4ece-b962-6bc31c28fe53")?,
111    )
112    .await?;
113
114    info!("inserting delete account email");
115
116    let delete_subject = Some("Account deletion code");
117    let delete_body = json!([
118        {
119            "type": "core/paragraph",
120            "isValid": true,
121            "clientId": "11111111-1111-1111-1111-111111111111",
122            "attributes": {
123                "content": "Hello, it seems you requested a code for deleting your account",
124                "drop_cap": false
125            },
126            "innerBlocks": []
127        },
128        {
129            "type": "core/paragraph",
130            "isValid": true,
131            "clientId": "22222222-2222-2222-2222-222222222222",
132            "attributes": {
133                "content": "Use this verification code to delete your account: {{CODE}}",
134                "drop_cap": false
135            },
136            "innerBlocks": []
137        },
138        {
139            "type": "core/paragraph",
140            "isValid": true,
141            "clientId": "33333333-3333-3333-3333-333333333333",
142            "attributes": {
143                "content": "If you did not request a code, please ignore this message.",
144                "drop_cap": false
145            },
146            "innerBlocks": []
147        }
148    ]);
149
150    let delete_template = EmailTemplateNew {
151        template_type: EmailTemplateType::DeleteUserEmail,
152        language: Some("en".to_string()),
153        content: Some(delete_body),
154        subject: delete_subject.map(|s| s.to_string()),
155    };
156
157    insert_email_template(&mut conn, None, delete_template, delete_subject).await?;
158
159    info!("inserting confirm email code email");
160
161    let confirm_subject = Some("Email verification code");
162    let confirm_body = json!([
163        {
164            "type": "core/paragraph",
165            "isValid": true,
166            "clientId": "44444444-4444-4444-4444-444444444444",
167            "attributes": {
168                "content": "Hello, please use this code to verify your email address",
169                "drop_cap": false
170            },
171            "innerBlocks": []
172        },
173        {
174            "type": "core/paragraph",
175            "isValid": true,
176            "clientId": "55555555-5555-5555-5555-555555555555",
177            "attributes": {
178                "content": "Your verification code is: {{CODE}}",
179                "drop_cap": false
180            },
181            "innerBlocks": []
182        },
183        {
184            "type": "core/paragraph",
185            "isValid": true,
186            "clientId": "66666666-6666-6666-6666-666666666666",
187            "attributes": {
188                "content": "If you did not request this code, please ignore this message.",
189                "drop_cap": false
190            },
191            "innerBlocks": []
192        }
193    ]);
194
195    let confirm_template = EmailTemplateNew {
196        template_type: EmailTemplateType::ConfirmEmailCode,
197        language: Some("en".to_string()),
198        content: Some(confirm_body),
199        subject: confirm_subject.map(|s| s.to_string()),
200    };
201
202    insert_email_template(&mut conn, None, confirm_template, confirm_subject).await?;
203
204    seed_email_ownership_verification_templates(&mut conn).await?;
205    seed_account_linking_templates(&mut conn).await?;
206    seed_credit_registration_status_templates(&mut conn).await?;
207
208    Ok(())
209}
210
211/// The mail that carries a student-number linking link. Every placeholder comes from the delivery
212/// row because the recipient may have no account here, and the "you received this because" line has
213/// to stay because the message is unsolicited. A migration cannot insert a row using an enum value
214/// it adds itself, so in dev and tests these templates come only from here.
215async fn seed_account_linking_templates(conn: &mut sqlx::PgConnection) -> anyhow::Result<()> {
216    info!("inserting credit registration account linking emails");
217
218    let english_subject = Some("Link your student number to register your credits");
219    let english_body = json!([
220        {
221            "type": "core/paragraph",
222            "isValid": true,
223            "clientId": "d1000000-0000-0000-0000-000000000001",
224            "attributes": {
225                "content": "Hello {{NAME}}, we can see your enrolment on {{COURSE_NAME}}. To register your credits in Sisu, we need to link your student number to your account on courses.mooc.fi.",
226                "drop_cap": false
227            },
228            "innerBlocks": []
229        },
230        {
231            "type": "core/paragraph",
232            "isValid": true,
233            "clientId": "d1000000-0000-0000-0000-000000000002",
234            "attributes": {
235                "content": "Log in to courses.mooc.fi with the account you use for the course. Then open this confirmation link to link student number {{STUDENT_NUMBER}} to it: {{LINK}}",
236                "drop_cap": false
237            },
238            "innerBlocks": []
239        },
240        {
241            "type": "core/paragraph",
242            "isValid": true,
243            "clientId": "d1000000-0000-0000-0000-000000000003",
244            "attributes": {
245                "content": "The link works for 14 days and only once. You got this email because you are enrolled on {{COURSE_NAME}} at the University of Helsinki. If this was not you, you can ignore this email.",
246                "drop_cap": false
247            },
248            "innerBlocks": []
249        }
250    ]);
251
252    insert_email_template(
253        conn,
254        None,
255        EmailTemplateNew {
256            template_type: EmailTemplateType::CreditRegistrationAccountLinking,
257            language: Some("en".to_string()),
258            content: Some(english_body),
259            subject: english_subject.map(|s| s.to_string()),
260        },
261        english_subject,
262    )
263    .await?;
264
265    let finnish_subject = Some("Liitä opiskelijanumerosi, jotta voimme kirjata opintopisteesi");
266    let finnish_body = json!([
267        {
268            "type": "core/paragraph",
269            "isValid": true,
270            "clientId": "d2000000-0000-0000-0000-000000000001",
271            "attributes": {
272                "content": "Hei {{NAME}}, näemme ilmoittautumisesi kurssille {{COURSE_NAME}}. Jotta voimme kirjata opintopisteesi Sisuun, meidän pitää liittää opiskelijanumerosi courses.mooc.fi-tiliisi.",
273                "drop_cap": false
274            },
275            "innerBlocks": []
276        },
277        {
278            "type": "core/paragraph",
279            "isValid": true,
280            "clientId": "d2000000-0000-0000-0000-000000000002",
281            "attributes": {
282                "content": "Kirjaudu courses.mooc.fi-palveluun sillä tilillä, jolla teet kurssia. Avaa sitten tämä vahvistuslinkki, niin opiskelijanumero {{STUDENT_NUMBER}} liitetään tiliisi: {{LINK}}",
283                "drop_cap": false
284            },
285            "innerBlocks": []
286        },
287        {
288            "type": "core/paragraph",
289            "isValid": true,
290            "clientId": "d2000000-0000-0000-0000-000000000003",
291            "attributes": {
292                "content": "Linkki toimii 14 päivää ja vain kerran. Sait tämän viestin, koska olet ilmoittautunut Helsingin yliopiston kurssille {{COURSE_NAME}}. Jos et ole ilmoittautunut, voit jättää viestin huomiotta.",
293                "drop_cap": false
294            },
295            "innerBlocks": []
296        }
297    ]);
298
299    insert_email_template(
300        conn,
301        None,
302        EmailTemplateNew {
303            template_type: EmailTemplateType::CreditRegistrationAccountLinking,
304            language: Some("fi".to_string()),
305            content: Some(finnish_body),
306            subject: finnish_subject.map(|s| s.to_string()),
307        },
308        finnish_subject,
309    )
310    .await?;
311
312    Ok(())
313}
314
315/// The sender looks the account's pending code up at send time and substitutes `{{CODE}}`, as it does
316/// for the login and account deletion codes.
317///
318/// A migration cannot insert a template row using an enum value it adds itself, so in dev and tests
319/// the `verify_email_address` templates come only from here.
320async fn seed_email_ownership_verification_templates(
321    conn: &mut sqlx::PgConnection,
322) -> anyhow::Result<()> {
323    info!("inserting email address verification emails");
324
325    let english_subject = Some("Confirm your email address");
326    let english_body = json!([
327        {
328            "type": "core/paragraph",
329            "isValid": true,
330            "clientId": "77777777-7777-7777-7777-777777777777",
331            "attributes": {
332                "content": "Hello, please use this code to confirm the email address on your account.",
333                "drop_cap": false
334            },
335            "innerBlocks": []
336        },
337        {
338            "type": "core/paragraph",
339            "isValid": true,
340            "clientId": "88888888-8888-8888-8888-888888888888",
341            "attributes": {
342                "content": "Your confirmation code is: {{CODE}}",
343                "drop_cap": false
344            },
345            "innerBlocks": []
346        },
347        {
348            "type": "core/paragraph",
349            "isValid": true,
350            "clientId": "99999999-9999-9999-9999-999999999999",
351            "attributes": {
352                "content": "If you did not request this, you can ignore this message. Nothing changes until the code is entered.",
353                "drop_cap": false
354            },
355            "innerBlocks": []
356        }
357    ]);
358
359    insert_email_template(
360        conn,
361        None,
362        EmailTemplateNew {
363            template_type: EmailTemplateType::VerifyEmailAddress,
364            language: Some("en".to_string()),
365            content: Some(english_body),
366            subject: english_subject.map(|s| s.to_string()),
367        },
368        english_subject,
369    )
370    .await?;
371
372    let finnish_subject = Some("Vahvista sähköpostiosoitteesi");
373    let finnish_body = json!([
374        {
375            "type": "core/paragraph",
376            "isValid": true,
377            "clientId": "aaaaaaaa-7777-7777-7777-777777777777",
378            "attributes": {
379                "content": "Hei, vahvista tilisi sähköpostiosoite tällä koodilla.",
380                "drop_cap": false
381            },
382            "innerBlocks": []
383        },
384        {
385            "type": "core/paragraph",
386            "isValid": true,
387            "clientId": "bbbbbbbb-8888-8888-8888-888888888888",
388            "attributes": {
389                "content": "Vahvistuskoodisi on: {{CODE}}",
390                "drop_cap": false
391            },
392            "innerBlocks": []
393        },
394        {
395            "type": "core/paragraph",
396            "isValid": true,
397            "clientId": "cccccccc-9999-9999-9999-999999999999",
398            "attributes": {
399                "content": "Jos et pyytänyt tätä, voit jättää viestin huomiotta. Mikään ei muutu ennen kuin koodi syötetään.",
400                "drop_cap": false
401            },
402            "innerBlocks": []
403        }
404    ]);
405
406    insert_email_template(
407        conn,
408        None,
409        EmailTemplateNew {
410            template_type: EmailTemplateType::VerifyEmailAddress,
411            language: Some("fi".to_string()),
412            content: Some(finnish_body),
413            subject: finnish_subject.map(|s| s.to_string()),
414        },
415        finnish_subject,
416    )
417    .await?;
418
419    Ok(())
420}
421
422/// The credit registration student mails. Their only link is the status page, since students enrol
423/// in different ways.
424async fn seed_credit_registration_status_templates(
425    conn: &mut sqlx::PgConnection,
426) -> anyhow::Result<()> {
427    info!("inserting credit registration status emails");
428
429    let templates: [(EmailTemplateType, &str, &str, &[&str]); 4] = [
430        (
431            EmailTemplateType::CreditRegistrationActionNeeded,
432            "en",
433            "One more step to get your credits registered",
434            &[
435                "Hello, congratulations on completing {{COURSE_NAME}}! You have earned {{CREDITS}} credits.",
436                "To get the credits registered in Sisu, you need to be enrolled on the course. You can see how to enrol here: {{STATUS_LINK}}",
437                "After that you do not need to do anything else: we check your enrolment regularly and register your credits automatically. If you have already enrolled, you can ignore this message.",
438                "Best regards,<br>MOOC.fi",
439            ],
440        ),
441        (
442            EmailTemplateType::CreditRegistrationActionNeeded,
443            "fi",
444            "Vielä yksi vaihe opintopisteiden kirjaamiseen",
445            &[
446                "Hei, onnittelut kurssin {{COURSE_NAME}} suorittamisesta! Olet ansainnut {{CREDITS}} op.",
447                "Jotta opintopisteet voidaan kirjata Sisuun, sinun pitää olla ilmoittautunut kurssille. Näet ilmoittautumisohjeet täältä: {{STATUS_LINK}}",
448                "Sen jälkeen sinun ei tarvitse tehdä muuta: tarkistamme ilmoittautumisesi säännöllisesti ja kirjaamme opintopisteesi automaattisesti. Jos olet jo ilmoittautunut, voit jättää tämän viestin huomiotta.",
449                "Terveisin,<br>MOOC.fi",
450            ],
451        ),
452        (
453            EmailTemplateType::CreditRegistrationRegistered,
454            "en",
455            "Your credits have been registered",
456            &[
457                "Hello, your {{CREDITS}} credits for {{COURSE_NAME}} are now registered in Sisu.",
458                "You do not need to do anything else. You can see the details here: {{STATUS_LINK}}",
459                "Best regards,<br>MOOC.fi",
460            ],
461        ),
462        (
463            EmailTemplateType::CreditRegistrationRegistered,
464            "fi",
465            "Opintopisteesi on kirjattu",
466            &[
467                "Hei, kurssin {{COURSE_NAME}} opintopisteesi ({{CREDITS}} op) on nyt kirjattu Sisuun.",
468                "Sinun ei tarvitse tehdä muuta. Näet tiedot täältä: {{STATUS_LINK}}",
469                "Terveisin,<br>MOOC.fi",
470            ],
471        ),
472    ];
473
474    for (template_index, (template_type, language, subject, paragraphs)) in
475        templates.into_iter().enumerate()
476    {
477        let blocks: Vec<_> = paragraphs
478            .iter()
479            .enumerate()
480            .map(|(paragraph_index, content)| {
481                json!({
482                    "type": "core/paragraph",
483                    "isValid": true,
484                    "clientId": format!(
485                        "d{}000000-0000-0000-0000-{:012}",
486                        template_index + 3,
487                        paragraph_index + 1
488                    ),
489                    "attributes": {
490                        "content": content,
491                        "drop_cap": false
492                    },
493                    "innerBlocks": []
494                })
495            })
496            .collect();
497
498        insert_email_template(
499            conn,
500            None,
501            EmailTemplateNew {
502                template_type,
503                language: Some(language.to_string()),
504                content: Some(json!(blocks)),
505                subject: Some(subject.to_string()),
506            },
507            Some(subject),
508        )
509        .await?;
510    }
511
512    Ok(())
513}