headless_lms_models/
course_instance_enrollments.rs

1use crate::{
2    course_instances::CourseInstance, course_module_completions::CourseModuleCompletion,
3    courses::Course, prelude::*, user_course_settings::UserCourseSettings,
4};
5
6#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
7#[cfg_attr(feature = "ts_rs", derive(TS))]
8pub struct CourseInstanceEnrollment {
9    pub user_id: Uuid,
10    pub course_id: Uuid,
11    pub course_instance_id: Uuid,
12    pub created_at: DateTime<Utc>,
13    pub updated_at: DateTime<Utc>,
14    pub deleted_at: Option<DateTime<Utc>>,
15}
16
17#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
18#[cfg_attr(feature = "ts_rs", derive(TS))]
19pub struct CourseInstanceEnrollmentsInfo {
20    pub course_instance_enrollments: Vec<CourseInstanceEnrollment>,
21    pub course_instances: Vec<CourseInstance>,
22    pub courses: Vec<Course>,
23    pub user_course_settings: Vec<UserCourseSettings>,
24    pub course_module_completions: Vec<CourseModuleCompletion>,
25}
26
27pub async fn insert(
28    conn: &mut PgConnection,
29    user_id: Uuid,
30    course_id: Uuid,
31    course_instance_id: Uuid,
32) -> ModelResult<()> {
33    sqlx::query!(
34        "
35INSERT INTO course_instance_enrollments (user_id, course_id, course_instance_id)
36VALUES ($1, $2, $3)
37",
38        user_id,
39        course_id,
40        course_instance_id,
41    )
42    .execute(conn)
43    .await?;
44    Ok(())
45}
46
47#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
48pub struct NewCourseInstanceEnrollment {
49    pub user_id: Uuid,
50    pub course_id: Uuid,
51    pub course_instance_id: Uuid,
52}
53
54/**
55Inserts enrollment if it doesn't exist yet.
56
57If the enrollment exists, this just makes sure that the record is not deleted. This is useful because the user might accidentally request entrolling to the same course instance twice for example with two differet browser tabs.
58*/
59pub async fn insert_enrollment_if_it_doesnt_exist(
60    conn: &mut PgConnection,
61    enrollment: NewCourseInstanceEnrollment,
62) -> ModelResult<CourseInstanceEnrollment> {
63    let enrollment = sqlx::query_as!(
64        CourseInstanceEnrollment,
65        "
66INSERT INTO course_instance_enrollments (user_id, course_id, course_instance_id)
67VALUES ($1, $2, $3)
68ON CONFLICT (user_id, course_instance_id)
69DO UPDATE SET deleted_at = NULL
70RETURNING *;
71",
72        enrollment.user_id,
73        enrollment.course_id,
74        enrollment.course_instance_id,
75    )
76    .fetch_one(conn)
77    .await?;
78    Ok(enrollment)
79}
80
81pub async fn insert_enrollment_and_set_as_current(
82    conn: &mut PgConnection,
83    new_enrollment: NewCourseInstanceEnrollment,
84) -> ModelResult<CourseInstanceEnrollment> {
85    let mut tx = conn.begin().await?;
86
87    let enrollment = insert_enrollment_if_it_doesnt_exist(&mut tx, new_enrollment).await?;
88    crate::user_course_settings::upsert_user_course_settings_for_enrollment(&mut tx, &enrollment)
89        .await?;
90    tx.commit().await?;
91
92    Ok(enrollment)
93}
94
95pub async fn get_by_user_and_course_instance_id(
96    conn: &mut PgConnection,
97    user_id: Uuid,
98    course_instance_id: Uuid,
99) -> ModelResult<CourseInstanceEnrollment> {
100    let res = sqlx::query_as!(
101        CourseInstanceEnrollment,
102        "
103SELECT *
104FROM course_instance_enrollments
105WHERE user_id = $1
106  AND course_instance_id = $2
107  AND deleted_at IS NULL
108        ",
109        user_id,
110        course_instance_id
111    )
112    .fetch_one(conn)
113    .await?;
114    Ok(res)
115}
116
117pub async fn get_by_user_id(
118    conn: &mut PgConnection,
119    user_id: Uuid,
120) -> ModelResult<Vec<CourseInstanceEnrollment>> {
121    let res = sqlx::query_as!(
122        CourseInstanceEnrollment,
123        "
124SELECT *
125FROM course_instance_enrollments
126WHERE user_id = $1
127  AND deleted_at IS NULL
128        ",
129        user_id
130    )
131    .fetch_all(conn)
132    .await?;
133    Ok(res)
134}
135
136pub async fn get_course_instance_enrollments_info_for_user(
137    conn: &mut PgConnection,
138    user_id: Uuid,
139) -> ModelResult<CourseInstanceEnrollmentsInfo> {
140    let course_instance_enrollments = get_by_user_id(conn, user_id).await?;
141
142    let course_instance_ids: Vec<Uuid> = course_instance_enrollments
143        .iter()
144        .map(|e| e.course_instance_id)
145        .collect();
146
147    let course_instances = crate::course_instances::get_by_ids(conn, &course_instance_ids).await?;
148
149    let course_ids: Vec<Uuid> = course_instances.iter().map(|e| e.course_id).collect();
150
151    let courses = crate::courses::get_by_ids(conn, &course_ids).await?;
152
153    let course_module_completions =
154        crate::course_module_completions::get_all_by_user_id(conn, user_id).await?;
155
156    // Returns all user course settings because there is always an enrollment for a current course instance (enforced by a database constraint), and all of those are in the course_ids list
157    let user_course_settings =
158        crate::user_course_settings::get_all_by_user_and_multiple_current_courses(
159            conn,
160            &course_ids,
161            user_id,
162        )
163        .await?;
164
165    Ok(CourseInstanceEnrollmentsInfo {
166        course_instance_enrollments,
167        course_instances,
168        courses,
169        user_course_settings,
170        course_module_completions,
171    })
172}