headless_lms_credit_registration/use_cases/
student_notifications.rs1use headless_lms_models::email_deliveries::insert_email_delivery_with_placeholders;
9use headless_lms_models::library::credit_registration::student_notifications::{
10 STUDENT_NOTIFICATION_LIMIT, StudentNotificationToQueue, claim_unnotified, set_email_delivery_id,
11};
12use serde_json::json;
13use sqlx::{Connection, PgPool};
14use uuid::Uuid;
15
16use crate::error::CreditRegistrationResult;
17use crate::use_cases::mail_queue::{MailQueueSummary, TemplateCache, template_language};
18use crate::workflow::Counts;
19use headless_lms_models::credit_registrations::RegistrationScope;
20
21pub(crate) async fn run(
23 pool: &PgPool,
24 scope: &RegistrationScope,
25 base_url: &str,
26) -> CreditRegistrationResult<Counts> {
27 let mut conn = pool.acquire().await?;
28 let mut tx = conn.begin().await?;
29 let claimed = claim_unnotified(&mut tx, scope, STUDENT_NOTIFICATION_LIMIT).await?;
30 let mut templates = TemplateCache::default();
31 let mut summary = MailQueueSummary::new(claimed.len());
32 for notification in &claimed {
33 let template_type = notification.kind.email_template_type();
34 let language = template_language(¬ification.course_language_code);
35 let Some(template_id) = templates.id_for(&mut tx, template_type, &language).await? else {
36 summary.skip_missing_template(
37 template_type,
38 &language,
39 format!("{template_type:?} in {language}"),
40 );
41 continue;
42 };
43 let delivery = insert_email_delivery_with_placeholders(
44 &mut tx,
45 notification.user_id,
46 template_id,
47 &placeholders(base_url, notification, &language),
48 )
49 .await?;
50 set_email_delivery_id(
51 &mut tx,
52 notification.credit_registration_id,
53 notification.kind,
54 delivery,
55 )
56 .await?;
57 }
58 tx.commit().await?;
59 Ok(summary.finish("No student notification email template for:"))
60}
61
62fn placeholders(
64 base_url: &str,
65 notification: &StudentNotificationToQueue,
66 language: &str,
67) -> serde_json::Value {
68 json!({
69 "NAME": notification.first_name.as_deref().unwrap_or_default(),
70 "COURSE_NAME": notification.course_name,
71 "MODULE_NAME": notification.course_module_name.as_deref().unwrap_or_default(),
72 "CREDITS": notification
73 .credits
74 .map(|credits| format_credits(credits, language))
75 .unwrap_or_default(),
76 "STATUS_LINK": status_page_url(base_url, notification.course_module_id),
77 })
78}
79
80fn format_credits(credits: f32, language: &str) -> String {
83 let formatted = format!("{credits:.2}");
84 let formatted = formatted.trim_end_matches('0').trim_end_matches('.');
85 match language {
86 "fi" | "sv" => formatted.replace('.', ","),
87 _ => formatted.to_string(),
88 }
89}
90
91fn status_page_url(base_url: &str, course_module_id: Uuid) -> String {
93 format!(
94 "{}/completion-registration/{course_module_id}",
95 base_url.trim_end_matches('/')
96 )
97}