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_suspected_cheaters_by_id(
155    conn: &mut PgConnection,
156    id: Uuid,
157) -> ModelResult<SuspectedCheaters> {
158    let cheaters = sqlx::query_as!(
159        SuspectedCheaters,
160        "
161      SELECT *
162      FROM suspected_cheaters
163      WHERE user_id = $1
164      AND deleted_at IS NULL;
165    ",
166        id
167    )
168    .fetch_one(conn)
169    .await?;
170    Ok(cheaters)
171}
172
173pub async fn get_all_suspected_cheaters_in_course(
174    conn: &mut PgConnection,
175    course_id: Uuid,
176    archive: bool,
177) -> ModelResult<Vec<SuspectedCheaters>> {
178    let cheaters = sqlx::query_as!(
179        SuspectedCheaters,
180        "
181SELECT *
182FROM suspected_cheaters
183WHERE course_id = $1
184    AND is_archived = $2
185    AND deleted_at IS NULL;
186    ",
187        course_id,
188        archive
189    )
190    .fetch_all(conn)
191    .await?;
192    Ok(cheaters)
193}
194
195pub async fn insert_thresholds_by_module_id(
196    conn: &mut PgConnection,
197    course_module_id: Uuid,
198    duration_seconds: i32,
199) -> ModelResult<Threshold> {
200    let threshold = sqlx::query_as!(
201        Threshold,
202        "
203        INSERT INTO cheater_thresholds (
204            course_module_id,
205            duration_seconds
206        )
207        VALUES ($1, $2)
208        ON CONFLICT (course_module_id)
209        DO UPDATE SET
210            duration_seconds = EXCLUDED.duration_seconds,
211            deleted_at = NULL
212        RETURNING *
213        ",
214        course_module_id,
215        duration_seconds,
216    )
217    .fetch_one(conn)
218    .await?;
219
220    Ok(threshold)
221}
222
223pub async fn get_thresholds_by_module_id(
224    conn: &mut PgConnection,
225    course_module_id: Uuid,
226) -> ModelResult<Option<Threshold>> {
227    let threshold = sqlx::query_as!(
228        Threshold,
229        "
230      SELECT id,
231      course_module_id,
232      duration_seconds,
233      created_at,
234      updated_at,
235      deleted_at
236      FROM cheater_thresholds
237      WHERE course_module_id = $1
238      AND deleted_at IS NULL;
239    ",
240        course_module_id
241    )
242    .fetch_optional(conn)
243    .await?;
244    Ok(threshold)
245}
246
247pub async fn get_all_thresholds_for_course(
248    conn: &mut PgConnection,
249    course_id: Uuid,
250) -> ModelResult<Vec<Threshold>> {
251    let thresholds = sqlx::query_as!(
252        Threshold,
253        "
254      SELECT ct.id,
255      ct.course_module_id,
256      ct.duration_seconds,
257      ct.created_at,
258      ct.updated_at,
259      ct.deleted_at
260      FROM cheater_thresholds ct
261      JOIN course_modules cm ON ct.course_module_id = cm.id
262      WHERE cm.course_id = $1
263      AND ct.deleted_at IS NULL
264      AND cm.deleted_at IS NULL;
265    ",
266        course_id
267    )
268    .fetch_all(conn)
269    .await?;
270    Ok(thresholds)
271}
272
273pub async fn delete_threshold_for_module(
274    conn: &mut PgConnection,
275    course_module_id: Uuid,
276) -> ModelResult<()> {
277    sqlx::query!(
278        "
279        UPDATE cheater_thresholds
280        SET deleted_at = NOW()
281        WHERE course_module_id = $1
282        AND deleted_at IS NULL
283        ",
284        course_module_id
285    )
286    .execute(conn)
287    .await?;
288    Ok(())
289}