1use crate::course_modules;
2use crate::prelude::*;
3use utoipa::ToSchema;
4
5#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
6
7pub struct SuspectedCheaters {
8 pub id: Uuid,
9 pub user_id: Uuid,
10 pub course_id: Uuid,
11 pub created_at: DateTime<Utc>,
12 pub deleted_at: Option<DateTime<Utc>>,
13 pub updated_at: Option<DateTime<Utc>>,
14 pub total_duration_seconds: Option<i32>,
15 pub total_points: i32,
16 pub is_archived: Option<bool>,
17}
18
19#[derive(Debug, Serialize, Deserialize, ToSchema)]
20
21pub struct ThresholdData {
22 pub duration_seconds: i32,
23}
24
25#[derive(Debug, Serialize, Deserialize)]
26
27pub struct DeletedSuspectedCheater {
28 pub id: i32,
29 pub count: i32,
30}
31
32#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
33
34pub struct Threshold {
35 pub id: Uuid,
36 pub course_module_id: Uuid,
37 pub created_at: DateTime<Utc>,
38 pub updated_at: DateTime<Utc>,
39 pub deleted_at: Option<DateTime<Utc>>,
40 pub duration_seconds: i32,
41}
42
43pub async fn insert(
44 conn: &mut PgConnection,
45 user_id: Uuid,
46 course_id: Uuid,
47 total_duration_seconds: Option<i32>,
48 total_points: i32,
49) -> ModelResult<()> {
50 sqlx::query!(
51 "
52 INSERT INTO suspected_cheaters (
53 user_id,
54 total_duration_seconds,
55 total_points,
56 course_id
57 )
58 VALUES ($1, $2, $3, $4)
59 ",
60 user_id,
61 total_duration_seconds,
62 total_points,
63 course_id
64 )
65 .execute(conn)
66 .await?;
67 Ok(())
68}
69
70pub async fn insert_thresholds(
71 conn: &mut PgConnection,
72 course_id: Uuid,
73 duration_seconds: i32,
74) -> ModelResult<Threshold> {
75 let default_module = course_modules::get_default_by_course_id(conn, course_id).await?;
76
77 let threshold = sqlx::query_as!(
78 Threshold,
79 "
80 INSERT INTO cheater_thresholds (
81 course_module_id,
82 duration_seconds
83 )
84 VALUES ($1, $2)
85 ON CONFLICT (course_module_id)
86 DO UPDATE SET
87 duration_seconds = EXCLUDED.duration_seconds,
88 deleted_at = NULL
89 RETURNING *
90 ",
91 default_module.id,
92 duration_seconds,
93 )
94 .fetch_one(conn)
95 .await?;
96
97 Ok(threshold)
98}
99
100pub async fn get_thresholds_by_id(
101 conn: &mut PgConnection,
102 course_id: Uuid,
103) -> ModelResult<Threshold> {
104 let default_module = course_modules::get_default_by_course_id(conn, course_id).await?;
105
106 let thresholds = sqlx::query_as!(
107 Threshold,
108 "
109 SELECT id,
110 course_module_id,
111 duration_seconds,
112 created_at,
113 updated_at,
114 deleted_at
115 FROM cheater_thresholds
116 WHERE course_module_id = $1
117 AND deleted_at IS NULL;
118 ",
119 default_module.id
120 )
121 .fetch_one(conn)
122 .await?;
123 Ok(thresholds)
124}
125
126pub async fn archive_suspected_cheater(conn: &mut PgConnection, id: Uuid) -> ModelResult<()> {
127 sqlx::query!(
128 "
129 UPDATE suspected_cheaters
130 SET is_archived = TRUE
131 WHERE user_id = $1
132 ",
133 id
134 )
135 .execute(conn)
136 .await?;
137 Ok(())
138}
139
140pub async fn approve_suspected_cheater(conn: &mut PgConnection, id: Uuid) -> ModelResult<()> {
141 sqlx::query!(
142 "
143 UPDATE suspected_cheaters
144 SET is_archived = FALSE
145 WHERE user_id = $1
146 ",
147 id
148 )
149 .execute(conn)
150 .await?;
151 Ok(())
152}
153
154pub async fn get_by_user_id_and_course_id(
155 conn: &mut PgConnection,
156 user_id: Uuid,
157 course_id: Uuid,
158) -> ModelResult<SuspectedCheaters> {
159 let cheater = sqlx::query_as!(
160 SuspectedCheaters,
161 "
162SELECT *
163FROM suspected_cheaters
164WHERE user_id = $1
165 AND course_id = $2
166 AND deleted_at IS NULL;
167 ",
168 user_id,
169 course_id
170 )
171 .fetch_one(conn)
172 .await?;
173 Ok(cheater)
174}
175
176pub async fn archive_by_user_id_and_course_id(
177 conn: &mut PgConnection,
178 user_id: Uuid,
179 course_id: Uuid,
180) -> ModelResult<SuspectedCheaters> {
181 let cheater = sqlx::query_as!(
182 SuspectedCheaters,
183 "
184UPDATE suspected_cheaters
185SET is_archived = TRUE
186WHERE user_id = $1
187 AND course_id = $2
188 AND deleted_at IS NULL
189RETURNING *
190 ",
191 user_id,
192 course_id
193 )
194 .fetch_one(conn)
195 .await?;
196 Ok(cheater)
197}
198
199pub async fn approve_by_user_id_and_course_id(
200 conn: &mut PgConnection,
201 user_id: Uuid,
202 course_id: Uuid,
203) -> ModelResult<SuspectedCheaters> {
204 let cheater = sqlx::query_as!(
205 SuspectedCheaters,
206 "
207UPDATE suspected_cheaters
208SET is_archived = FALSE
209WHERE user_id = $1
210 AND course_id = $2
211 AND deleted_at IS NULL
212RETURNING *
213 ",
214 user_id,
215 course_id
216 )
217 .fetch_one(conn)
218 .await?;
219 Ok(cheater)
220}
221
222pub async fn get_suspected_cheaters_by_id(
223 conn: &mut PgConnection,
224 id: Uuid,
225) -> ModelResult<SuspectedCheaters> {
226 let cheaters = sqlx::query_as!(
227 SuspectedCheaters,
228 "
229 SELECT *
230 FROM suspected_cheaters
231 WHERE user_id = $1
232 AND deleted_at IS NULL;
233 ",
234 id
235 )
236 .fetch_one(conn)
237 .await?;
238 Ok(cheaters)
239}
240
241pub async fn get_all_suspected_cheaters_in_course(
242 conn: &mut PgConnection,
243 course_id: Uuid,
244 archive: bool,
245) -> ModelResult<Vec<SuspectedCheaters>> {
246 let cheaters = sqlx::query_as!(
247 SuspectedCheaters,
248 "
249SELECT *
250FROM suspected_cheaters
251WHERE course_id = $1
252 AND is_archived = $2
253 AND deleted_at IS NULL;
254 ",
255 course_id,
256 archive
257 )
258 .fetch_all(conn)
259 .await?;
260 Ok(cheaters)
261}
262
263pub async fn insert_thresholds_by_module_id(
264 conn: &mut PgConnection,
265 course_module_id: Uuid,
266 duration_seconds: i32,
267) -> ModelResult<Threshold> {
268 let threshold = sqlx::query_as!(
269 Threshold,
270 "
271 INSERT INTO cheater_thresholds (
272 course_module_id,
273 duration_seconds
274 )
275 VALUES ($1, $2)
276 ON CONFLICT (course_module_id)
277 DO UPDATE SET
278 duration_seconds = EXCLUDED.duration_seconds,
279 deleted_at = NULL
280 RETURNING *
281 ",
282 course_module_id,
283 duration_seconds,
284 )
285 .fetch_one(conn)
286 .await?;
287
288 Ok(threshold)
289}
290
291pub async fn get_thresholds_by_module_id(
292 conn: &mut PgConnection,
293 course_module_id: Uuid,
294) -> ModelResult<Option<Threshold>> {
295 let threshold = sqlx::query_as!(
296 Threshold,
297 "
298 SELECT id,
299 course_module_id,
300 duration_seconds,
301 created_at,
302 updated_at,
303 deleted_at
304 FROM cheater_thresholds
305 WHERE course_module_id = $1
306 AND deleted_at IS NULL;
307 ",
308 course_module_id
309 )
310 .fetch_optional(conn)
311 .await?;
312 Ok(threshold)
313}
314
315pub async fn get_all_thresholds_for_course(
316 conn: &mut PgConnection,
317 course_id: Uuid,
318) -> ModelResult<Vec<Threshold>> {
319 let thresholds = sqlx::query_as!(
320 Threshold,
321 "
322 SELECT ct.id,
323 ct.course_module_id,
324 ct.duration_seconds,
325 ct.created_at,
326 ct.updated_at,
327 ct.deleted_at
328 FROM cheater_thresholds ct
329 JOIN course_modules cm ON ct.course_module_id = cm.id
330 WHERE cm.course_id = $1
331 AND ct.deleted_at IS NULL
332 AND cm.deleted_at IS NULL;
333 ",
334 course_id
335 )
336 .fetch_all(conn)
337 .await?;
338 Ok(thresholds)
339}
340
341pub async fn delete_threshold_for_module(
342 conn: &mut PgConnection,
343 course_module_id: Uuid,
344) -> ModelResult<()> {
345 sqlx::query!(
346 "
347 UPDATE cheater_thresholds
348 SET deleted_at = NOW()
349 WHERE course_module_id = $1
350 AND deleted_at IS NULL
351 ",
352 course_module_id
353 )
354 .execute(conn)
355 .await?;
356 Ok(())
357}