headless_lms_models/
suspected_cheaters.rs

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