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