Skip to main content

headless_lms_models/
suspected_cheaters.rs

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}