headless_lms_models/
exercise_slides.rs

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}