Skip to main content

headless_lms_models/
course_credit_registration_consents.rs

1use utoipa::ToSchema;
2
3use crate::prelude::*;
4
5#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, ToSchema)]
6pub struct CourseCreditRegistrationConsent {
7    pub id: Uuid,
8    pub created_at: DateTime<Utc>,
9    pub updated_at: DateTime<Utc>,
10    pub deleted_at: Option<DateTime<Utc>>,
11    pub user_id: Uuid,
12    pub course_id: Uuid,
13    pub consent_given: bool,
14    pub consent_given_at: Option<DateTime<Utc>>,
15    pub consent_withdrawn_at: Option<DateTime<Utc>>,
16    pub asked_at: DateTime<Utc>,
17}
18
19/// Records the student's answer, keeping both timestamps so gave-then-withdrew history survives.
20pub async fn upsert(
21    conn: &mut PgConnection,
22    user_id: Uuid,
23    course_id: Uuid,
24    consent_given: bool,
25) -> ModelResult<CourseCreditRegistrationConsent> {
26    let res = sqlx::query_as!(
27        CourseCreditRegistrationConsent,
28        r#"
29INSERT INTO course_credit_registration_consents (
30    user_id,
31    course_id,
32    consent_given,
33    consent_given_at,
34    consent_withdrawn_at
35  )
36VALUES (
37    $1,
38    $2,
39    $3,
40    CASE
41      WHEN $3 THEN now()
42    END,
43    CASE
44      WHEN NOT $3 THEN now()
45    END
46  ) ON CONFLICT (user_id, course_id, deleted_at) DO
47UPDATE
48SET consent_given = $3,
49  consent_given_at = CASE
50    WHEN $3 THEN now()
51    ELSE course_credit_registration_consents.consent_given_at
52  END,
53  consent_withdrawn_at = CASE
54    WHEN NOT $3 THEN now()
55    ELSE course_credit_registration_consents.consent_withdrawn_at
56  END
57RETURNING *
58        "#,
59        user_id,
60        course_id,
61        consent_given,
62    )
63    .fetch_one(conn)
64    .await?;
65    Ok(res)
66}
67
68/// `None` means never asked; `consent_given = false` means asked and declined. Only the former
69/// re-opens the course-start dialog.
70pub async fn get_by_user_and_course(
71    conn: &mut PgConnection,
72    user_id: Uuid,
73    course_id: Uuid,
74) -> ModelResult<Option<CourseCreditRegistrationConsent>> {
75    let res = sqlx::query_as!(
76        CourseCreditRegistrationConsent,
77        r#"
78SELECT *
79FROM course_credit_registration_consents
80WHERE user_id = $1
81  AND course_id = $2
82  AND deleted_at IS NULL
83        "#,
84        user_id,
85        course_id,
86    )
87    .fetch_optional(conn)
88    .await?;
89    Ok(res)
90}
91
92pub async fn get_by_user_id(
93    conn: &mut PgConnection,
94    user_id: Uuid,
95) -> ModelResult<Vec<CourseCreditRegistrationConsent>> {
96    let res = sqlx::query_as!(
97        CourseCreditRegistrationConsent,
98        r#"
99SELECT *
100FROM course_credit_registration_consents
101WHERE user_id = $1
102  AND deleted_at IS NULL
103ORDER BY created_at DESC
104        "#,
105        user_id,
106    )
107    .fetch_all(conn)
108    .await?;
109    Ok(res)
110}
111
112pub async fn get_consenting_user_ids_for_course(
113    conn: &mut PgConnection,
114    course_id: Uuid,
115) -> ModelResult<Vec<Uuid>> {
116    let res = sqlx::query_scalar!(
117        r#"
118SELECT user_id
119FROM course_credit_registration_consents
120WHERE course_id = $1
121  AND consent_given
122  AND deleted_at IS NULL
123        "#,
124        course_id,
125    )
126    .fetch_all(conn)
127    .await?;
128    Ok(res)
129}