Skip to main content

headless_lms_models/
generated_certificates.rs

1use crate::prelude::*;
2use headless_lms_utils as utils;
3use utoipa::ToSchema;
4
5#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, ToSchema)]
6
7pub struct GeneratedCertificate {
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 user_id: Uuid,
13    pub name_on_certificate: String,
14    pub verification_id: String,
15    pub certificate_configuration_id: Uuid,
16}
17
18pub async fn get_certificate_for_user(
19    conn: &mut PgConnection,
20    user_id: Uuid,
21    certificate_configuration_id: Uuid,
22) -> ModelResult<GeneratedCertificate> {
23    let res = sqlx::query_as!(
24        GeneratedCertificate,
25        "
26SELECT *
27FROM generated_certificates
28WHERE user_id = $1
29  AND certificate_configuration_id = $2
30  AND deleted_at IS NULL
31",
32        user_id,
33        certificate_configuration_id
34    )
35    .fetch_one(conn)
36    .await?;
37    Ok(res)
38}
39/// Verifies that the user has completed the given module and creates the certificate in the database.
40pub async fn generate_and_insert(
41    conn: &mut PgConnection,
42    user_id: Uuid,
43    name_on_certificate: &str,
44    certificate_configuration_id: Uuid,
45) -> ModelResult<GeneratedCertificate> {
46    let requirements = crate::certificate_configuration_to_requirements::get_all_requirements_for_certificate_configuration(conn, certificate_configuration_id).await?;
47    // Verify that the user has completed the module in the course instance
48    if !requirements
49        .has_user_completed_all_requirements(conn, user_id)
50        .await?
51    {
52        return Err(ModelError::new(
53            ModelErrorType::PreconditionFailed,
54            "User has not completed all the requirements to be eligible for this certificate."
55                .to_string(),
56            None,
57        ));
58    }
59
60    // Verify that a certificate doesn't already exist
61    if sqlx::query!(
62        "
63SELECT id
64FROM generated_certificates
65WHERE user_id = $1
66    AND certificate_configuration_id = $2
67    AND deleted_at IS NULL
68",
69        user_id,
70        certificate_configuration_id,
71    )
72    .fetch_optional(&mut *conn)
73    .await?
74    .is_some()
75    {
76        // Certificate already exists
77        return Err(ModelError::new(
78            ModelErrorType::PreconditionFailed,
79            "User already has a certificate for the given module and course instance".to_string(),
80            None,
81        ));
82    }
83
84    let verification_id = generate_verification_id();
85    let res = sqlx::query_as!(
86        GeneratedCertificate,
87        "
88INSERT INTO generated_certificates (
89    user_id,
90    certificate_configuration_id,
91    name_on_certificate,
92    verification_id
93  )
94VALUES ($1, $2, $3, $4)
95RETURNING *
96",
97        user_id,
98        certificate_configuration_id,
99        name_on_certificate,
100        verification_id,
101    )
102    .fetch_one(conn)
103    .await?;
104    Ok(res)
105}
106
107pub async fn get_certificate_by_verification_id(
108    conn: &mut PgConnection,
109    certificate_verification_id: &str,
110) -> ModelResult<GeneratedCertificate> {
111    let res = sqlx::query_as!(
112        GeneratedCertificate,
113        "
114SELECT *
115FROM generated_certificates
116WHERE verification_id = $1
117  AND deleted_at IS NULL
118",
119        certificate_verification_id
120    )
121    .fetch_one(conn)
122    .await?;
123    Ok(res)
124}
125
126fn generate_verification_id() -> String {
127    utils::strings::generate_easily_writable_random_string(15)
128}
129
130#[derive(Debug, Deserialize, Serialize, ToSchema)]
131
132pub struct CertificateUpdateRequest {
133    pub date_issued: DateTime<Utc>,
134    pub name_on_certificate: Option<String>,
135}
136
137pub async fn update_certificate(
138    conn: &mut PgConnection,
139    certificate_id: Uuid,
140    date_issued: DateTime<Utc>,
141    name_on_certificate: Option<String>,
142) -> ModelResult<GeneratedCertificate> {
143    let updated = if let Some(name) = name_on_certificate {
144        sqlx::query_as!(
145            GeneratedCertificate,
146            r#"
147            UPDATE generated_certificates
148            SET created_at = $1,
149                name_on_certificate = $2,
150                updated_at = NOW()
151            WHERE id = $3
152              AND deleted_at IS NULL
153            RETURNING *
154            "#,
155            date_issued,
156            name,
157            certificate_id
158        )
159        .fetch_one(conn)
160        .await?
161    } else {
162        sqlx::query_as!(
163            GeneratedCertificate,
164            r#"
165            UPDATE generated_certificates
166            SET created_at = $1,
167                updated_at = NOW()
168            WHERE id = $2
169              AND deleted_at IS NULL
170            RETURNING *
171            "#,
172            date_issued,
173            certificate_id
174        )
175        .fetch_one(conn)
176        .await?
177    };
178
179    Ok(updated)
180}
181
182pub async fn get_by_id(
183    conn: &mut PgConnection,
184    certificate_id: Uuid,
185) -> ModelResult<GeneratedCertificate> {
186    let res = sqlx::query_as!(
187        GeneratedCertificate,
188        r#"
189        SELECT *
190        FROM generated_certificates
191        WHERE id = $1
192          AND deleted_at IS NULL
193        "#,
194        certificate_id
195    )
196    .fetch_one(conn)
197    .await?;
198
199    Ok(res)
200}
201
202pub async fn find_existing(
203    conn: &mut PgConnection,
204    user_id: Uuid,
205    config_id: Uuid,
206) -> ModelResult<Option<Uuid>> {
207    let row = sqlx::query!(
208        r#"
209        SELECT id
210        FROM generated_certificates
211        WHERE user_id = $1
212          AND certificate_configuration_id = $2
213          AND deleted_at IS NULL
214        "#,
215        user_id,
216        config_id
217    )
218    .fetch_optional(conn)
219    .await?;
220
221    Ok(row.map(|r| r.id))
222}
223
224pub async fn insert_raw(
225    conn: &mut PgConnection,
226    user_id: Uuid,
227    config_id: Uuid,
228    name: &str,
229    verification_id: &str,
230) -> ModelResult<Uuid> {
231    let row = sqlx::query!(
232        r#"
233        INSERT INTO generated_certificates (
234            user_id,
235            certificate_configuration_id,
236            name_on_certificate,
237            verification_id
238        )
239        VALUES ($1, $2, $3, $4)
240        RETURNING id
241        "#,
242        user_id,
243        config_id,
244        name,
245        verification_id
246    )
247    .fetch_one(conn)
248    .await?;
249
250    Ok(row.id)
251}