headless_lms_credit_registration/use_cases/
mail_queue.rs1use headless_lms_base::error::backend_error::BackendError;
5use headless_lms_models::email_templates::{
6 EmailTemplateType, get_generic_email_template_by_type_and_language,
7};
8use sqlx::PgConnection;
9use std::collections::{BTreeSet, HashMap};
10use uuid::Uuid;
11
12use crate::error::CreditRegistrationResult;
13use crate::workflow::Counts;
14
15#[derive(Default)]
18pub(super) struct TemplateCache(HashMap<(EmailTemplateType, String), Option<Uuid>>);
19
20impl TemplateCache {
21 pub(super) async fn id_for(
22 &mut self,
23 conn: &mut PgConnection,
24 template_type: EmailTemplateType,
25 language: &str,
26 ) -> CreditRegistrationResult<Option<Uuid>> {
27 let key = (template_type, language.to_string());
28 if let Some(id) = self.0.get(&key) {
29 return Ok(*id);
30 }
31 let found =
32 match get_generic_email_template_by_type_and_language(conn, template_type, language)
33 .await
34 {
35 Ok(template) => Some(template.id),
36 Err(error)
37 if matches!(
38 error.error_type(),
39 headless_lms_models::ModelErrorType::RecordNotFound
40 ) =>
41 {
42 None
43 }
44 Err(error) => return Err(error.into()),
45 };
46 self.0.insert(key, found);
47 Ok(found)
48 }
49}
50
51pub(super) struct MailQueueSummary {
55 claimed: usize,
56 skipped: usize,
57 missing_templates: BTreeSet<String>,
58}
59
60impl MailQueueSummary {
61 pub(super) fn new(claimed: usize) -> Self {
62 Self {
63 claimed,
64 skipped: 0,
65 missing_templates: BTreeSet::new(),
66 }
67 }
68
69 pub(super) fn skip_missing_template(
71 &mut self,
72 template_type: EmailTemplateType,
73 language: &str,
74 label: impl Into<String>,
75 ) {
76 debug!(
77 ?template_type,
78 language = %language,
79 "No email template for mail; skipping"
80 );
81 self.missing_templates.insert(label.into());
82 self.skipped += 1;
83 }
84
85 pub(super) fn finish(self, missing_templates_prefix: &str) -> Counts {
88 let claimed = self.claimed;
89 let skipped = self.skipped;
90 if claimed > 0 {
91 let queued = claimed - skipped;
92 info!(
93 claimed,
94 queued, skipped, "queued {queued} mails, skipped {skipped}"
95 );
96 }
97 let finding = (!self.missing_templates.is_empty()).then(|| {
98 format!(
99 "{missing_templates_prefix} {}.",
100 self.missing_templates
101 .into_iter()
102 .collect::<Vec<_>>()
103 .join(", ")
104 )
105 });
106 Counts::processed_with_failures(
107 i32::try_from(claimed).unwrap_or(i32::MAX),
108 i32::try_from(skipped).unwrap_or(i32::MAX),
109 )
110 .with_finding(finding)
111 }
112}
113
114pub(super) fn template_language(course_language_code: &str) -> String {
118 course_language_code
119 .split(['-', '_'])
120 .next()
121 .unwrap_or(course_language_code)
122 .to_lowercase()
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn a_locale_narrows_to_the_language_the_templates_are_stored_under() {
131 assert_eq!(template_language("fi-FI"), "fi");
132 assert_eq!(template_language("en_US"), "en");
133 assert_eq!(template_language("en"), "en");
134 }
135}