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}