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
use crate::{
    prelude::*,
    roles::{RoleDomain, RoleInfo, UserRole},
};

#[derive(Debug, Serialize)]
#[cfg_attr(feature = "ts_rs", derive(TS))]
pub struct PendingRole {
    pub id: Uuid,
    pub user_email: String,
    pub role: UserRole,
    pub expires_at: DateTime<Utc>,
}

pub async fn insert(
    conn: &mut PgConnection,
    pkey_policy: PKeyPolicy<Uuid>,
    role_info: RoleInfo,
) -> ModelResult<Uuid> {
    match role_info.domain {
        crate::roles::RoleDomain::Global
        | crate::roles::RoleDomain::Organization(_)
        | crate::roles::RoleDomain::Exam(_) => {
            return Err(ModelError::new(
                ModelErrorType::InvalidRequest,
                "Cannot use a pending role for a role this broad".to_string(),
                None,
            ));
        }

        crate::roles::RoleDomain::Course(_) | crate::roles::RoleDomain::CourseInstance(_) => (),
    };

    match role_info.role {
        UserRole::Admin | UserRole::Teacher => {
            return Err(ModelError::new(
                ModelErrorType::InvalidRequest,
                "Cannot use a pending role with this much power".to_string(),
                None,
            ))
        }
        _ => (),
    }

    let course_id = match role_info.domain {
        crate::roles::RoleDomain::Course(id) => Some(id),
        _ => None,
    };

    let course_instance_id = match role_info.domain {
        crate::roles::RoleDomain::CourseInstance(id) => Some(id),
        _ => None,
    };

    let id = sqlx::query!(
        r#"
INSERT INTO pending_roles (
    id,
    user_email,
    role,
    course_id,
    course_instance_id
  )
VALUES ($1, $2, $3, $4, $5)
RETURNING id;
        "#,
        pkey_policy.into_uuid(),
        role_info.email,
        role_info.role as UserRole,
        course_id,
        course_instance_id
    )
    .fetch_one(conn)
    .await?
    .id;
    Ok(id)
}

pub async fn get_all(conn: &mut PgConnection, domain: RoleDomain) -> ModelResult<Vec<PendingRole>> {
    let res = match domain {
        RoleDomain::Global | RoleDomain::Organization(_) | RoleDomain::Exam(_) => {
            return Ok(Vec::new())
        }
        RoleDomain::Course(course_id) => {
            sqlx::query_as!(
                PendingRole,
                r#"
SELECT id, user_email, expires_at, role AS "role!: UserRole" FROM pending_roles
WHERE course_id = $1
AND deleted_at IS NULL
AND expires_at > NOW()
          "#,
                course_id
            )
            .fetch_all(&mut *conn)
            .await?
        }
        RoleDomain::CourseInstance(course_instance_id) => {
            sqlx::query_as!(
                PendingRole,
                r#"
SELECT id, user_email, expires_at, role AS "role!: UserRole" FROM pending_roles
WHERE course_instance_id = $1
AND deleted_at IS NULL
AND expires_at > NOW()
        "#,
                course_instance_id
            )
            .fetch_all(&mut *conn)
            .await?
        }
    };
    Ok(res)
}