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}
39pub 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 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 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 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}