1use futures::future::BoxFuture;
2use url::Url;
3use utoipa::ToSchema;
4
5use crate::{
6 exercise_service_info::ExerciseServiceInfoApi,
7 exercise_tasks::{self, CourseMaterialExerciseTask},
8 prelude::*,
9};
10
11pub struct NewExerciseSlide {
12 exercise_id: Uuid,
13 order_number: i32,
14}
15
16#[derive(Debug, Serialize, Deserialize, FromRow, PartialEq, Clone)]
17
18pub struct ExerciseSlide {
19 pub id: Uuid,
20 pub created_at: DateTime<Utc>,
21 pub updated_at: DateTime<Utc>,
22 pub deleted_at: Option<DateTime<Utc>>,
23 pub exercise_id: Uuid,
24 pub order_number: i32,
25}
26
27#[derive(Debug, Serialize, Deserialize, ToSchema)]
28
29pub struct CourseMaterialExerciseSlide {
30 pub id: Uuid,
31 pub exercise_tasks: Vec<CourseMaterialExerciseTask>,
32}
33
34pub async fn insert(
35 conn: &mut PgConnection,
36 pkey_policy: PKeyPolicy<Uuid>,
37 exercise_id: Uuid,
38 order_number: i32,
39) -> ModelResult<Uuid> {
40 let res = sqlx::query!(
41 "
42INSERT INTO exercise_slides (id, exercise_id, order_number)
43VALUES ($1, $2, $3)
44RETURNING id
45 ",
46 pkey_policy.into_uuid(),
47 exercise_id,
48 order_number
49 )
50 .fetch_one(conn)
51 .await?;
52 Ok(res.id)
53}
54
55pub async fn insert_exercise_slide(
56 conn: &mut PgConnection,
57 new_slide: NewExerciseSlide,
58) -> ModelResult<ExerciseSlide> {
59 let res = sqlx::query_as!(
60 ExerciseSlide,
61 "
62INSERT INTO exercise_slides (exercise_id, order_number)
63VALUES ($1, $2)
64RETURNING *;
65 ",
66 new_slide.exercise_id,
67 new_slide.order_number
68 )
69 .fetch_one(conn)
70 .await?;
71 Ok(res)
72}
73
74pub async fn upsert(
75 conn: &mut PgConnection,
76 id: Uuid,
77 exercise_id: Uuid,
78 order_number: i32,
79) -> ModelResult<Uuid> {
80 let res = sqlx::query!(
81 "
82INSERT INTO exercise_slides (id, exercise_id, order_number)
83VALUES ($1, $2, $3) ON CONFLICT (id) DO
84UPDATE
85SET exercise_id = $2,
86 order_number = $3,
87 deleted_at = NULL
88RETURNING id;
89 ",
90 id,
91 exercise_id,
92 order_number
93 )
94 .fetch_one(conn)
95 .await?;
96 Ok(res.id)
97}
98
99pub async fn get_exercise_slides(conn: &mut PgConnection) -> ModelResult<Vec<ExerciseSlide>> {
100 let res = sqlx::query_as!(
101 ExerciseSlide,
102 "
103SELECT *
104FROM exercise_slides
105WHERE deleted_at IS NULL;
106 "
107 )
108 .fetch_all(conn)
109 .await?;
110 Ok(res)
111}
112
113pub async fn get_exercise_slides_by_exercise_ids(
114 conn: &mut PgConnection,
115 exercise_ids: &[Uuid],
116) -> ModelResult<Vec<ExerciseSlide>> {
117 let res = sqlx::query_as!(
118 ExerciseSlide,
119 "
120SELECT *
121FROM exercise_slides
122WHERE exercise_id = ANY($1)
123 AND deleted_at IS NULL;
124 ",
125 exercise_ids,
126 )
127 .fetch_all(conn)
128 .await?;
129 Ok(res)
130}
131
132pub async fn get_exercise_slide(conn: &mut PgConnection, id: Uuid) -> ModelResult<ExerciseSlide> {
133 let res = sqlx::query_as!(
134 ExerciseSlide,
135 "
136SELECT *
137FROM exercise_slides
138WHERE id = $1
139 AND deleted_at IS NULL;
140 ",
141 id
142 )
143 .fetch_one(conn)
144 .await?;
145 Ok(res)
146}
147
148pub async fn get_random_exercise_slide_for_exercise(
149 conn: &mut PgConnection,
150 exercise_id: Uuid,
151) -> ModelResult<ExerciseSlide> {
152 let res = sqlx::query_as!(
153 ExerciseSlide,
154 "
155SELECT *
156FROM exercise_slides
157WHERE exercise_id = $1
158 AND deleted_at IS NULL
159ORDER BY random()
160LIMIT 1;
161 ",
162 exercise_id
163 )
164 .fetch_one(conn)
165 .await?;
166 Ok(res)
167}
168
169pub async fn get_exercise_slide_by_exercise_task_id(
170 conn: &mut PgConnection,
171 exercise_task_id: Uuid,
172) -> ModelResult<Option<ExerciseSlide>> {
173 let res = sqlx::query_as!(
174 ExerciseSlide,
175 "
176SELECT s.*
177FROM exercise_slides s
178 JOIN exercise_tasks t ON (s.id = t.exercise_slide_id)
179WHERE t.id = $1
180 AND t.deleted_at IS NULL
181 AND s.deleted_at IS NULL;
182 ",
183 exercise_task_id,
184 )
185 .fetch_optional(conn)
186 .await?;
187 Ok(res)
188}
189
190pub async fn get_exercise_slides_by_exercise_id(
191 conn: &mut PgConnection,
192 exercise_id: Uuid,
193) -> ModelResult<Vec<ExerciseSlide>> {
194 let res = sqlx::query_as!(
195 ExerciseSlide,
196 "
197SELECT *
198FROM exercise_slides
199WHERE exercise_id = $1
200 AND deleted_at IS NULL;
201 ",
202 exercise_id
203 )
204 .fetch_all(conn)
205 .await?;
206 Ok(res)
207}
208
209pub async fn get_course_material_exercise_slide_by_id(
210 conn: &mut PgConnection,
211 id: Uuid,
212 user_id: Option<Uuid>,
213 fetch_service_info: impl Fn(Url) -> BoxFuture<'static, ModelResult<ExerciseServiceInfoApi>>,
214) -> ModelResult<CourseMaterialExerciseSlide> {
215 let exercise_tasks =
216 exercise_tasks::get_course_material_exercise_tasks(conn, id, user_id, fetch_service_info)
217 .await?;
218 Ok(CourseMaterialExerciseSlide { id, exercise_tasks })
219}
220
221pub async fn delete_exercise_slides_by_exercise_ids(
222 conn: &mut PgConnection,
223 exercise_ids: &[Uuid],
224) -> ModelResult<Vec<Uuid>> {
225 let deleted_ids = sqlx::query!(
226 "
227UPDATE exercise_slides
228SET deleted_at = now()
229WHERE exercise_id = ANY($1)
230AND deleted_at IS NULL
231RETURNING id;
232 ",
233 exercise_ids,
234 )
235 .fetch_all(conn)
236 .await?
237 .into_iter()
238 .map(|x| x.id)
239 .collect();
240 Ok(deleted_ids)
241}