Skip to main content

headless_lms_models/
certificate_configuration_to_requirements.rs

1use utoipa::ToSchema;
2
3use crate::prelude::*;
4
5#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
6
7pub struct CertificateConfigurationToRequirement {
8    pub id: Uuid,
9    pub created_at: DateTime<Utc>,
10    pub updated_at: DateTime<Utc>,
11    pub deleted_at: Option<DateTime<Utc>>,
12    pub certificate_configuration_id: Uuid,
13    pub course_module_id: Option<Uuid>,
14}
15
16#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
17
18pub struct CertificateAllRequirements {
19    pub certificate_configuration_id: Uuid,
20    pub course_module_ids: Vec<Uuid>,
21}
22
23impl CertificateAllRequirements {
24    /** A certificate configuration is a default configuration if the requirement is only for one course module. These types of configurations are regarded as the default because they are the most commonly used ones. */
25    pub fn is_default_certificate_configuration(&self) -> bool {
26        self.course_module_ids.len() == 1
27    }
28
29    /** Checks if the user has completed all requirements to be eligible for a certificate. */
30    pub async fn has_user_completed_all_requirements(
31        &self,
32        conn: &mut PgConnection,
33        user_id: Uuid,
34    ) -> ModelResult<bool> {
35        let all_users_completions =
36            crate::course_module_completions::get_all_by_user_id(conn, user_id).await?;
37
38        let all_completed_course_module_ids = all_users_completions
39            .iter()
40            // A completion still awaiting suspected-cheater review is withheld until a teacher
41            // dismisses (restores it) or confirms (fails the student), so it must not count
42            // toward certificate eligibility.
43            .filter(|o| !o.needs_to_be_reviewed)
44            .map(|o| o.course_module_id)
45            .collect::<Vec<_>>();
46        // Compare the vecs of completed stuff to the requirements
47        let all_required_course_modules_completed = self
48            .course_module_ids
49            .iter()
50            .all(|id| all_completed_course_module_ids.contains(id));
51        let result = all_required_course_modules_completed;
52        if !result {
53            let missing_course_module_ids = self
54                .course_module_ids
55                .iter()
56                .filter(|id| !all_completed_course_module_ids.contains(id))
57                .collect::<Vec<_>>();
58            warn!(
59                "User {} has not completed all requirements for certificate configuration {}. Missing course module ids: {:?}.",
60                user_id, self.certificate_configuration_id, missing_course_module_ids
61            )
62        }
63        Ok(result)
64    }
65}
66
67pub async fn get_all_requirements_for_certificate_configuration(
68    conn: &mut PgConnection,
69    certificate_configuration_id: Uuid,
70) -> ModelResult<CertificateAllRequirements> {
71    let requirements = sqlx::query_as!(
72        CertificateConfigurationToRequirement,
73        r#"
74SELECT *
75FROM certificate_configuration_to_requirements
76WHERE certificate_configuration_id = $1
77AND deleted_at IS NULL
78        "#,
79        certificate_configuration_id
80    )
81    .fetch_all(conn)
82    .await?;
83    let course_module_ids = requirements
84        .iter()
85        .filter_map(|r| r.course_module_id)
86        .collect();
87
88    Ok(CertificateAllRequirements {
89        certificate_configuration_id,
90        course_module_ids,
91    })
92}
93
94pub async fn insert(
95    conn: &mut PgConnection,
96    certificate_configuration_id: Uuid,
97    course_module_id: Option<Uuid>,
98) -> ModelResult<CertificateConfigurationToRequirement> {
99    let row = sqlx::query_as!(
100        CertificateConfigurationToRequirement,
101        r#"
102INSERT INTO certificate_configuration_to_requirements (
103    certificate_configuration_id,
104    course_module_id
105  )
106VALUES ($1, $2)
107RETURNING *
108        "#,
109        certificate_configuration_id,
110        course_module_id,
111    )
112    .fetch_one(conn)
113    .await?;
114    Ok(row)
115}
116
117pub async fn link_configuration_to_module_if_missing(
118    conn: &mut PgConnection,
119    certificate_configuration_id: Uuid,
120    course_module_id: Uuid,
121) -> ModelResult<()> {
122    sqlx::query!(
123        r#"
124        INSERT INTO certificate_configuration_to_requirements (
125            certificate_configuration_id,
126            course_module_id
127        )
128        SELECT $1, $2
129        WHERE NOT EXISTS (
130            SELECT 1
131            FROM certificate_configuration_to_requirements
132            WHERE certificate_configuration_id = $1
133              AND course_module_id = $2
134              AND deleted_at IS NULL
135        )
136        "#,
137        certificate_configuration_id,
138        course_module_id
139    )
140    .execute(conn)
141    .await?;
142
143    Ok(())
144}