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            "name": "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                "dropCap": false
28            },
29            "innerBlocks": []
30        },
31        {
32            "name": "core/paragraph",
33            "isValid": true,
34            "clientId": "ceac591d-b291-40b2-8da7-6f437a6a8fce",
35            "attributes": {
36                "content": "Click the button below to choose a new password.",
37                "dropCap": false
38            },
39            "innerBlocks": []
40        },
41        {
42            "name": "core/buttons",
43            "isValid": true,
44            "clientId": "e1000000-0000-0000-0000-000000000001",
45            "attributes": {},
46            "innerBlocks": [
47                {
48                    "name": "core/button",
49                    "isValid": true,
50                    "clientId": "e1000000-0000-0000-0000-000000000002",
51                    "attributes": {
52                        "text": "Reset password",
53                        "url": "{{RESET_LINK}}"
54                    },
55                    "innerBlocks": []
56                }
57            ]
58        },
59        {
60            "name": "core/paragraph",
61            "isValid": true,
62            "clientId": "6a4165a1-38e1-4a17-b364-5f265afc7d23",
63            "attributes": {
64                "content": "If you did not request a password reset, please ignore this message.",
65                "dropCap": false
66            },
67            "innerBlocks": []
68        }
69    ]);
70
71    let english_template = EmailTemplateNew {
72        template_type: EmailTemplateType::ResetPasswordEmail,
73        language: Some("en".to_string()),
74        content: Some(english_body),
75        subject: english_subject.map(|s| s.to_string()),
76    };
77
78    insert_email_template(&mut conn, None, english_template, english_subject).await?;
79
80    let finnish_subject = Some("Salasanan palautuspyyntö");
81    let finnish_body = json!([
82        {
83            "name": "core/paragraph",
84            "isValid": true,
85            "clientId": "a9de49ff-919f-44b4-a085-9210ce0da94b",
86            "attributes": {
87                "content": "Hei, olet pyytänyt salasanan palautusta.",
88                "dropCap": false
89            },
90            "innerBlocks": []
91        },
92        {
93            "name": "core/paragraph",
94            "isValid": true,
95            "clientId": "c145bae4-9ff5-4194-8913-50f8900c20c8",
96            "attributes": {
97                "content": "Valitse uusi salasana alla olevasta painikkeesta.",
98                "dropCap": false
99            },
100            "innerBlocks": []
101        },
102        {
103            "name": "core/buttons",
104            "isValid": true,
105            "clientId": "e2000000-0000-0000-0000-000000000001",
106            "attributes": {},
107            "innerBlocks": [
108                {
109                    "name": "core/button",
110                    "isValid": true,
111                    "clientId": "e2000000-0000-0000-0000-000000000002",
112                    "attributes": {
113                        "text": "Palauta salasana",
114                        "url": "{{RESET_LINK}}"
115                    },
116                    "innerBlocks": []
117                }
118            ]
119        },
120        {
121            "name": "core/paragraph",
122            "isValid": true,
123            "clientId": "ebd0d430-0ae3-4b85-9e8e-96481660ded4",
124            "attributes": {
125                "content": "Jos et pyytänyt salasanan palautusta, voit jättää tämän viestin huomiotta.",
126                "dropCap": false
127            },
128            "innerBlocks": []
129        }
130    ]);
131
132    let finnish_template = EmailTemplateNew {
133        template_type: EmailTemplateType::ResetPasswordEmail,
134        language: Some("fi".to_string()),
135        content: Some(finnish_body),
136        subject: finnish_subject.map(|s| s.to_string()),
137    };
138
139    insert_email_template(&mut conn, None, finnish_template, finnish_subject).await?;
140
141    info!("inserting password reset token for user");
142    let SeedUsersResult { sign_up_user, .. } = seed_users_result;
143    insert_password_reset_token(
144        &mut conn,
145        sign_up_user,
146        Uuid::parse_str("5a831370-6b7e-4ece-b962-6bc31c28fe53")?,
147    )
148    .await?;
149
150    info!("inserting delete account email");
151
152    let delete_subject = Some("Account deletion code");
153    let delete_body = json!([
154        {
155            "name": "core/paragraph",
156            "isValid": true,
157            "clientId": "11111111-1111-1111-1111-111111111111",
158            "attributes": {
159                "content": "Hello, it seems you requested a code for deleting your account",
160                "dropCap": false
161            },
162            "innerBlocks": []
163        },
164        {
165            "name": "core/paragraph",
166            "isValid": true,
167            "clientId": "22222222-2222-2222-2222-222222222222",
168            "attributes": {
169                "content": "Use this verification code to delete your account:",
170                "dropCap": false
171            },
172            "innerBlocks": []
173        },
174        {
175            "name": "moocfi/email-one-time-code",
176            "isValid": true,
177            "clientId": "e3000000-0000-0000-0000-000000000001",
178            "attributes": {
179                "code": "{{CODE}}"
180            },
181            "innerBlocks": []
182        },
183        {
184            "name": "core/paragraph",
185            "isValid": true,
186            "clientId": "33333333-3333-3333-3333-333333333333",
187            "attributes": {
188                "content": "If you did not request a code, please ignore this message.",
189                "dropCap": false
190            },
191            "innerBlocks": []
192        }
193    ]);
194
195    let delete_template = EmailTemplateNew {
196        template_type: EmailTemplateType::DeleteUserEmail,
197        language: Some("en".to_string()),
198        content: Some(delete_body),
199        subject: delete_subject.map(|s| s.to_string()),
200    };
201
202    insert_email_template(&mut conn, None, delete_template, delete_subject).await?;
203
204    info!("inserting confirm email code email");
205
206    let confirm_subject = Some("Email verification code");
207    let confirm_body = json!([
208        {
209            "name": "core/paragraph",
210            "isValid": true,
211            "clientId": "44444444-4444-4444-4444-444444444444",
212            "attributes": {
213                "content": "Hello, please use this code to verify your email address",
214                "dropCap": false
215            },
216            "innerBlocks": []
217        },
218        {
219            "name": "core/paragraph",
220            "isValid": true,
221            "clientId": "55555555-5555-5555-5555-555555555555",
222            "attributes": {
223                "content": "Your verification code is:",
224                "dropCap": false
225            },
226            "innerBlocks": []
227        },
228        {
229            "name": "moocfi/email-one-time-code",
230            "isValid": true,
231            "clientId": "e3000000-0000-0000-0000-000000000002",
232            "attributes": {
233                "code": "{{CODE}}"
234            },
235            "innerBlocks": []
236        },
237        {
238            "name": "core/paragraph",
239            "isValid": true,
240            "clientId": "66666666-6666-6666-6666-666666666666",
241            "attributes": {
242                "content": "If you did not request this code, please ignore this message.",
243                "dropCap": false
244            },
245            "innerBlocks": []
246        }
247    ]);
248
249    let confirm_template = EmailTemplateNew {
250        template_type: EmailTemplateType::ConfirmEmailCode,
251        language: Some("en".to_string()),
252        content: Some(confirm_body),
253        subject: confirm_subject.map(|s| s.to_string()),
254    };
255
256    insert_email_template(&mut conn, None, confirm_template, confirm_subject).await?;
257
258    seed_email_ownership_verification_templates(&mut conn).await?;
259    seed_account_linking_templates(&mut conn).await?;
260    seed_credit_registration_status_templates(&mut conn).await?;
261
262    Ok(())
263}
264
265/// The mail that carries a student-number linking link. Every placeholder comes from the delivery
266/// row because the recipient may have no account here, and the "you received this because" line has
267/// to stay because the message is unsolicited. A migration cannot insert a row using an enum value
268/// it adds itself, so in dev and tests these templates come only from here.
269async fn seed_account_linking_templates(conn: &mut sqlx::PgConnection) -> anyhow::Result<()> {
270    info!("inserting credit registration account linking emails");
271
272    let english_subject =
273        Some("Link your University of Helsinki student number to register your credits");
274    let english_body = json!([
275        {
276            "name": "core/paragraph",
277            "isValid": true,
278            "clientId": "d1000000-0000-0000-0000-000000000001",
279            "attributes": {
280                "content": "Hello, we can see your enrolment on {{COURSE_NAME}}. To register your completion in Sisu, we need to link your University of Helsinki student number {{STUDENT_NUMBER}} to your account on courses.mooc.fi.",
281                "dropCap": false
282            },
283            "innerBlocks": []
284        },
285        {
286            "name": "core/paragraph",
287            "isValid": true,
288            "clientId": "d1000000-0000-0000-0000-000000000002",
289            "attributes": {
290                "content": "Log in to courses.mooc.fi with the account you use for the course, then press the button below.",
291                "dropCap": false
292            },
293            "innerBlocks": []
294        },
295        {
296            "name": "core/buttons",
297            "isValid": true,
298            "clientId": "e4000000-0000-0000-0000-000000000001",
299            "attributes": {},
300            "innerBlocks": [
301                {
302                    "name": "core/button",
303                    "isValid": true,
304                    "clientId": "e4000000-0000-0000-0000-000000000002",
305                    "attributes": {
306                        "text": "Link student number",
307                        "url": "{{LINK}}"
308                    },
309                    "innerBlocks": []
310                }
311            ]
312        },
313        {
314            "name": "core/paragraph",
315            "isValid": true,
316            "clientId": "d1000000-0000-0000-0000-000000000003",
317            "attributes": {
318                "content": "The link works once, for 14 days. It is fine if this email address is different from the one you use on courses.mooc.fi.",
319                "dropCap": false
320            },
321            "innerBlocks": []
322        },
323        {
324            "name": "core/paragraph",
325            "isValid": true,
326            "clientId": "d1000000-0000-0000-0000-000000000004",
327            "attributes": {
328                "content": "You got this email because you are enrolled on {{COURSE_NAME}} at the University of Helsinki. If that is not you, ignore this email.",
329                "dropCap": false
330            },
331            "innerBlocks": []
332        },
333        {
334            "name": "core/paragraph",
335            "isValid": true,
336            "clientId": "d1000000-0000-0000-0000-000000000005",
337            "attributes": {
338                "content": "Best regards,<br>MOOC.fi",
339                "dropCap": false
340            },
341            "innerBlocks": []
342        }
343    ]);
344
345    insert_email_template(
346        conn,
347        None,
348        EmailTemplateNew {
349            template_type: EmailTemplateType::CreditRegistrationAccountLinking,
350            language: Some("en".to_string()),
351            content: Some(english_body),
352            subject: english_subject.map(|s| s.to_string()),
353        },
354        english_subject,
355    )
356    .await?;
357
358    let finnish_subject =
359        Some("Liitä Helsingin yliopiston opiskelijanumerosi, jotta voimme kirjata opintopisteesi");
360    let finnish_body = json!([
361        {
362            "name": "core/paragraph",
363            "isValid": true,
364            "clientId": "d2000000-0000-0000-0000-000000000001",
365            "attributes": {
366                "content": "Hei, näemme ilmoittautumisesi kurssille {{COURSE_NAME}}. Jotta voimme kirjata suorituksesi Sisuun, meidän pitää liittää Helsingin yliopiston opiskelijanumerosi {{STUDENT_NUMBER}} courses.mooc.fi-tiliisi.",
367                "dropCap": false
368            },
369            "innerBlocks": []
370        },
371        {
372            "name": "core/paragraph",
373            "isValid": true,
374            "clientId": "d2000000-0000-0000-0000-000000000002",
375            "attributes": {
376                "content": "Kirjaudu courses.mooc.fi-palveluun sillä tilillä, jolla teet kurssia, ja paina sitten alla olevaa painiketta.",
377                "dropCap": false
378            },
379            "innerBlocks": []
380        },
381        {
382            "name": "core/buttons",
383            "isValid": true,
384            "clientId": "e5000000-0000-0000-0000-000000000001",
385            "attributes": {},
386            "innerBlocks": [
387                {
388                    "name": "core/button",
389                    "isValid": true,
390                    "clientId": "e5000000-0000-0000-0000-000000000002",
391                    "attributes": {
392                        "text": "Liitä opiskelijanumero",
393                        "url": "{{LINK}}"
394                    },
395                    "innerBlocks": []
396                }
397            ]
398        },
399        {
400            "name": "core/paragraph",
401            "isValid": true,
402            "clientId": "d2000000-0000-0000-0000-000000000003",
403            "attributes": {
404                "content": "Linkki toimii kerran, 14 päivän ajan. Ei haittaa, jos tämä sähköpostiosoite on eri kuin courses.mooc.fi:ssä käyttämäsi.",
405                "dropCap": false
406            },
407            "innerBlocks": []
408        },
409        {
410            "name": "core/paragraph",
411            "isValid": true,
412            "clientId": "d2000000-0000-0000-0000-000000000004",
413            "attributes": {
414                "content": "Sait tämän viestin, koska olet ilmoittautunut Helsingin yliopiston kurssille {{COURSE_NAME}}. Jos viesti ei ole sinulle, voit jättää sen huomiotta.",
415                "dropCap": false
416            },
417            "innerBlocks": []
418        },
419        {
420            "name": "core/paragraph",
421            "isValid": true,
422            "clientId": "d2000000-0000-0000-0000-000000000005",
423            "attributes": {
424                "content": "Terveisin,<br>MOOC.fi",
425                "dropCap": false
426            },
427            "innerBlocks": []
428        }
429    ]);
430
431    insert_email_template(
432        conn,
433        None,
434        EmailTemplateNew {
435            template_type: EmailTemplateType::CreditRegistrationAccountLinking,
436            language: Some("fi".to_string()),
437            content: Some(finnish_body),
438            subject: finnish_subject.map(|s| s.to_string()),
439        },
440        finnish_subject,
441    )
442    .await?;
443
444    Ok(())
445}
446
447/// The sender looks the account's pending code up at send time and substitutes `{{CODE}}`, as it does
448/// for the login and account deletion codes.
449///
450/// A migration cannot insert a template row using an enum value it adds itself, so in dev and tests
451/// the `verify_email_address` templates come only from here.
452async fn seed_email_ownership_verification_templates(
453    conn: &mut sqlx::PgConnection,
454) -> anyhow::Result<()> {
455    info!("inserting email address verification emails");
456
457    let english_subject = Some("Confirm your email address");
458    let english_body = json!([
459        {
460            "name": "core/paragraph",
461            "isValid": true,
462            "clientId": "77777777-7777-7777-7777-777777777777",
463            "attributes": {
464                "content": "Hello, please use this code to confirm the email address on your account.",
465                "dropCap": false
466            },
467            "innerBlocks": []
468        },
469        {
470            "name": "core/paragraph",
471            "isValid": true,
472            "clientId": "88888888-8888-8888-8888-888888888888",
473            "attributes": {
474                "content": "Your confirmation code is:",
475                "dropCap": false
476            },
477            "innerBlocks": []
478        },
479        {
480            "name": "moocfi/email-one-time-code",
481            "isValid": true,
482            "clientId": "e3000000-0000-0000-0000-000000000003",
483            "attributes": {
484                "code": "{{CODE}}"
485            },
486            "innerBlocks": []
487        },
488        {
489            "name": "core/paragraph",
490            "isValid": true,
491            "clientId": "99999999-9999-9999-9999-999999999999",
492            "attributes": {
493                "content": "If you did not request this, you can ignore this message. Nothing changes until the code is entered.",
494                "dropCap": false
495            },
496            "innerBlocks": []
497        }
498    ]);
499
500    insert_email_template(
501        conn,
502        None,
503        EmailTemplateNew {
504            template_type: EmailTemplateType::VerifyEmailAddress,
505            language: Some("en".to_string()),
506            content: Some(english_body),
507            subject: english_subject.map(|s| s.to_string()),
508        },
509        english_subject,
510    )
511    .await?;
512
513    let finnish_subject = Some("Vahvista sähköpostiosoitteesi");
514    let finnish_body = json!([
515        {
516            "name": "core/paragraph",
517            "isValid": true,
518            "clientId": "aaaaaaaa-7777-7777-7777-777777777777",
519            "attributes": {
520                "content": "Hei, vahvista tilisi sähköpostiosoite tällä koodilla.",
521                "dropCap": false
522            },
523            "innerBlocks": []
524        },
525        {
526            "name": "core/paragraph",
527            "isValid": true,
528            "clientId": "bbbbbbbb-8888-8888-8888-888888888888",
529            "attributes": {
530                "content": "Vahvistuskoodisi on:",
531                "dropCap": false
532            },
533            "innerBlocks": []
534        },
535        {
536            "name": "moocfi/email-one-time-code",
537            "isValid": true,
538            "clientId": "e3000000-0000-0000-0000-000000000004",
539            "attributes": {
540                "code": "{{CODE}}"
541            },
542            "innerBlocks": []
543        },
544        {
545            "name": "core/paragraph",
546            "isValid": true,
547            "clientId": "cccccccc-9999-9999-9999-999999999999",
548            "attributes": {
549                "content": "Jos et pyytänyt tätä, voit jättää viestin huomiotta. Mikään ei muutu ennen kuin koodi syötetään.",
550                "dropCap": false
551            },
552            "innerBlocks": []
553        }
554    ]);
555
556    insert_email_template(
557        conn,
558        None,
559        EmailTemplateNew {
560            template_type: EmailTemplateType::VerifyEmailAddress,
561            language: Some("fi".to_string()),
562            content: Some(finnish_body),
563            subject: finnish_subject.map(|s| s.to_string()),
564        },
565        finnish_subject,
566    )
567    .await?;
568
569    Ok(())
570}
571
572/// The credit registration student mails. Their only link is the status page, since students enrol
573/// in different ways.
574async fn seed_credit_registration_status_templates(
575    conn: &mut sqlx::PgConnection,
576) -> anyhow::Result<()> {
577    info!("inserting credit registration status emails");
578
579    const BUTTON_AFTER_PARAGRAPH: usize = 2;
580
581    let templates: [(EmailTemplateType, &str, &str, &str, &[&str]); 4] = [
582        (
583            EmailTemplateType::CreditRegistrationActionNeeded,
584            "en",
585            "One more step to get your credits registered",
586            "See how to enrol",
587            &[
588                "Hello, congratulations on completing {{COURSE_NAME}}! You have earned {{CREDITS}} credits.",
589                "To get your completion registered in Sisu, enrol on the course. The button below shows how. If you have already enrolled, you do not need to enrol again.",
590                "After you enrol, we register your completion automatically. If your University of Helsinki student number is not linked yet, we first send a linking email to your email address in Sisu.",
591                "Best regards,<br>MOOC.fi",
592            ],
593        ),
594        (
595            EmailTemplateType::CreditRegistrationActionNeeded,
596            "fi",
597            "Vielä yksi vaihe opintopisteiden kirjaamiseen",
598            "Katso ilmoittautumisohjeet",
599            &[
600                "Hei, onnittelut kurssin {{COURSE_NAME}} suorittamisesta! Olet ansainnut {{CREDITS}} op.",
601                "Jotta suorituksesi voidaan kirjata Sisuun, ilmoittaudu kurssille. Alla olevasta painikkeesta näet, miten. Jos olet jo ilmoittautunut, sinun ei tarvitse ilmoittautua uudelleen.",
602                "Ilmoittautumisen jälkeen kirjaamme suorituksesi automaattisesti. Jos Helsingin yliopiston opiskelijanumeroasi ei ole vielä liitetty, lähetämme ensin liitosviestin Sisussa olevaan sähköpostiosoitteeseesi.",
603                "Terveisin,<br>MOOC.fi",
604            ],
605        ),
606        (
607            EmailTemplateType::CreditRegistrationRegistered,
608            "en",
609            "Your credits have been registered",
610            "See the details",
611            &[
612                "Hello, your completion of {{COURSE_NAME}} ({{CREDITS}} credits) is now registered in Sisu.",
613                "You do not need to do anything else.",
614                "Best regards,<br>MOOC.fi",
615            ],
616        ),
617        (
618            EmailTemplateType::CreditRegistrationRegistered,
619            "fi",
620            "Opintopisteesi on kirjattu",
621            "Katso tiedot",
622            &[
623                "Hei, suorituksesi kurssilta {{COURSE_NAME}} ({{CREDITS}} op) on nyt kirjattu Sisuun.",
624                "Sinun ei tarvitse tehdä muuta.",
625                "Terveisin,<br>MOOC.fi",
626            ],
627        ),
628    ];
629
630    for (template_index, (template_type, language, subject, button_label, paragraphs)) in
631        templates.into_iter().enumerate()
632    {
633        let mut blocks: Vec<_> = paragraphs
634            .iter()
635            .enumerate()
636            .map(|(paragraph_index, content)| {
637                json!({
638                    "name": "core/paragraph",
639                    "isValid": true,
640                    "clientId": format!(
641                        "d{}000000-0000-0000-0000-{:012}",
642                        template_index + 3,
643                        paragraph_index + 1
644                    ),
645                    "attributes": {
646                        "content": content,
647                        "dropCap": false
648                    },
649                    "innerBlocks": []
650                })
651            })
652            .collect();
653        blocks.insert(
654            BUTTON_AFTER_PARAGRAPH,
655            json!({
656                "name": "core/buttons",
657                "isValid": true,
658                "clientId": format!("d{}000000-0000-0000-0000-{:012}", template_index + 3, 100),
659                "attributes": {},
660                "innerBlocks": [{
661                    "name": "core/button",
662                    "isValid": true,
663                    "clientId": format!("d{}000000-0000-0000-0000-{:012}", template_index + 3, 101),
664                    "attributes": { "text": button_label, "url": "{{STATUS_LINK}}" },
665                    "innerBlocks": []
666                }]
667            }),
668        );
669
670        insert_email_template(
671            conn,
672            None,
673            EmailTemplateNew {
674                template_type,
675                language: Some(language.to_string()),
676                content: Some(json!(blocks)),
677                subject: Some(subject.to_string()),
678            },
679            Some(subject),
680        )
681        .await?;
682    }
683
684    Ok(())
685}