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