Skip to main content

headless_lms_server/domain/credit_registration/
linking_mail_resend.rs

1//! What the teacher- and admin-facing linking mail resend endpoints share: the gate before either
2//! may try, and the wire shape both answer with.
3
4use headless_lms_credit_registration::account_linking;
5use utoipa::ToSchema;
6
7use crate::prelude::*;
8
9/// Refuses a resend while account linking is switched off, or for a course with no module that
10/// registers credits.
11pub async fn ensure_resend_possible(
12    conn: &mut PgConnection,
13    app_conf: &ApplicationConfiguration,
14    course_id: Uuid,
15) -> Result<(), ControllerError> {
16    if !app_conf.suotar_configuration.is_account_linking_enabled() {
17        return Err(controller_err!(
18            BadRequest,
19            "Account linking is switched off.".to_string()
20        ));
21    }
22    let registers_credits = models::course_modules::get_by_course_id(conn, course_id)
23        .await?
24        .iter()
25        .any(|module| {
26            models::course_modules::registers_credits(
27                module.uh_course_code.as_deref(),
28                module.completions_can_be_registered_to_sisu,
29            )
30        });
31    if !registers_credits {
32        return Err(controller_err!(
33            BadRequest,
34            "This course has no module with a course code.".to_string()
35        ));
36    }
37    Ok(())
38}
39
40/// What a resend endpoint tells its caller. Shared wire shape for the teacher- and admin-facing
41/// resend endpoints; `NoStudentNumberKnown` is only ever emitted by the teacher endpoint, whose
42/// target may be an account that has never held a number.
43#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, ToSchema)]
44#[serde(rename_all = "snake_case")]
45pub enum ResendOutcome {
46    /// A mail is owed and will be handed to the sender on the next run.
47    Queued,
48    AlreadyMailedToEveryKnownAddress,
49    /// A cap refused it.
50    RefusedByRateCap,
51    /// A linking email went to the person moments ago, which even an override leaves in place.
52    JustSent,
53    NoAddressInStudyRegistry,
54    NotOnTheCourseRoster,
55    /// The number is already linked to an account, so no linking mail is owed.
56    AlreadyLinked,
57    StudyRegistryUnavailable,
58    /// This account has never had a student number, so there is nothing to look up.
59    NoStudentNumberKnown,
60}
61
62impl From<account_linking::ResendOutcome> for ResendOutcome {
63    fn from(outcome: account_linking::ResendOutcome) -> Self {
64        use account_linking::ResendOutcome as Attempted;
65        match outcome {
66            Attempted::Queued => Self::Queued,
67            Attempted::AlreadyMailedToEveryKnownAddress => Self::AlreadyMailedToEveryKnownAddress,
68            Attempted::RefusedByRateCap => Self::RefusedByRateCap,
69            Attempted::JustSent => Self::JustSent,
70            Attempted::NoAddressInStudyRegistry => Self::NoAddressInStudyRegistry,
71            Attempted::NotOnTheCourseRoster => Self::NotOnTheCourseRoster,
72            Attempted::AlreadyLinked => Self::AlreadyLinked,
73            Attempted::StudyRegistryUnavailable => Self::StudyRegistryUnavailable,
74        }
75    }
76}