1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
use crate::{course_instance_enrollments::CourseInstanceEnrollment, prelude::*};

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
#[cfg_attr(feature = "ts_rs", derive(TS))]
pub struct UserCourseSettings {
    pub user_id: Uuid,
    pub course_language_group_id: Uuid,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub deleted_at: Option<DateTime<Utc>>,
    pub current_course_id: Uuid,
    pub current_course_instance_id: Uuid,
}

/// Creates new user course settings based on the enrollment or updates an existing one.
pub async fn upsert_user_course_settings_for_enrollment(
    conn: &mut PgConnection,
    course_instance_enrollment: &CourseInstanceEnrollment,
) -> ModelResult<UserCourseSettings> {
    let user_course_settings = sqlx::query_as!(
        UserCourseSettings,
        "
INSERT INTO user_course_settings (
    user_id,
    course_language_group_id,
    current_course_id,
    current_course_instance_id
  )
SELECT $1,
  course_language_group_id,
  $2,
  $3
FROM courses
WHERE id = $2
  AND deleted_at IS NULL ON CONFLICT (user_id, course_language_group_id) DO
UPDATE
SET current_course_id = $2,
  current_course_instance_id = $3,
  deleted_at = NULL
RETURNING *;
        ",
        course_instance_enrollment.user_id,
        course_instance_enrollment.course_id,
        course_instance_enrollment.course_instance_id
    )
    .fetch_one(conn)
    .await?;
    Ok(user_course_settings)
}

pub async fn get_user_course_settings(
    conn: &mut PgConnection,
    user_id: Uuid,
    course_language_group_id: Uuid,
) -> ModelResult<UserCourseSettings> {
    let user_course_settings = sqlx::query_as!(
        UserCourseSettings,
        "
SELECT *
FROM user_course_settings
WHERE user_id = $1
  AND course_language_group_id = $2
  AND deleted_at IS NULL;
        ",
        user_id,
        course_language_group_id
    )
    .fetch_one(conn)
    .await?;
    Ok(user_course_settings)
}

pub async fn get_user_course_settings_by_course_id(
    conn: &mut PgConnection,
    user_id: Uuid,
    course_id: Uuid,
) -> ModelResult<Option<UserCourseSettings>> {
    let user_course_settings = sqlx::query_as!(
        UserCourseSettings,
        "
SELECT ucs.*
FROM courses c
  JOIN user_course_settings ucs ON (
    ucs.course_language_group_id = c.course_language_group_id
  )
WHERE c.id = $1
  AND ucs.user_id = $2
  AND c.deleted_at IS NULL
  AND ucs.deleted_at IS NULL;
        ",
        course_id,
        user_id,
    )
    .fetch_optional(conn)
    .await?;
    Ok(user_course_settings)
}

/// Gets all of the user's course settings that have their current course id included in the provided
/// list.
///
/// The distinction for current courses is stated, because multiple courses can share the same
/// course settings if they are different language versions of each other. Course settings that may
/// exist for inactive courses will be omited. This behavior can be desireable in some cases, and
/// should not be changed.
///
/// Note that this function doesn't create any settings that are missing for the user, so the amount
/// of results may be less than the amount of courses provided.
pub async fn get_all_by_user_and_multiple_current_courses(
    conn: &mut PgConnection,
    course_ids: &[Uuid],
    user_id: Uuid,
) -> ModelResult<Vec<UserCourseSettings>> {
    let res = sqlx::query_as!(
        UserCourseSettings,
        "
SELECT *
FROM user_course_settings
WHERE current_course_id = ANY($1)
  AND user_id = $2
  AND deleted_at IS NULL
        ",
        course_ids,
        user_id,
    )
    .fetch_all(conn)
    .await?;
    Ok(res)
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::{
        course_instance_enrollments::{self, NewCourseInstanceEnrollment},
        course_instances::{self, NewCourseInstance},
        test_helper::*,
    };

    #[tokio::test]
    async fn upserts_user_course_settings() {
        insert_data!(:tx, :user, :org, :course, :instance);

        let enrollment = course_instance_enrollments::insert_enrollment_if_it_doesnt_exist(
            tx.as_mut(),
            NewCourseInstanceEnrollment {
                course_id: course,
                course_instance_id: instance.id,
                user_id: user,
            },
        )
        .await
        .unwrap();
        let settings = upsert_user_course_settings_for_enrollment(tx.as_mut(), &enrollment)
            .await
            .unwrap();
        assert_eq!(settings.current_course_id, enrollment.course_id);
        assert_eq!(
            settings.current_course_instance_id,
            enrollment.course_instance_id
        );

        let instance_2 = course_instances::insert(
            tx.as_mut(),
            PKeyPolicy::Generate,
            NewCourseInstance {
                course_id: course,
                name: Some("instance-2"),
                description: None,
                teacher_in_charge_name: "teacher",
                teacher_in_charge_email: "teacher@example.com",
                support_email: None,
                opening_time: None,
                closing_time: None,
            },
        )
        .await
        .unwrap()
        .id;
        let enrollment_2 = course_instance_enrollments::insert_enrollment_if_it_doesnt_exist(
            tx.as_mut(),
            NewCourseInstanceEnrollment {
                course_id: course,
                course_instance_id: instance_2,
                user_id: user,
            },
        )
        .await
        .unwrap();
        let settings_2 = upsert_user_course_settings_for_enrollment(tx.as_mut(), &enrollment_2)
            .await
            .unwrap();
        assert_eq!(
            settings_2.current_course_instance_id,
            enrollment_2.course_instance_id
        );
    }
}