headless_lms_models/
exercise_slides.rs

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}