headless_lms_models/
pending_roles.rs

1use crate::{
2    prelude::*,
3    roles::{RoleDomain, RoleInfo, UserRole},
4};
5
6#[derive(Debug, Serialize)]
7#[cfg_attr(feature = "ts_rs", derive(TS))]
8pub struct PendingRole {
9    pub id: Uuid,
10    pub user_email: String,
11    pub role: UserRole,
12    pub expires_at: DateTime<Utc>,
13}
14
15pub async fn insert(
16    conn: &mut PgConnection,
17    pkey_policy: PKeyPolicy<Uuid>,
18    role_info: RoleInfo,
19) -> ModelResult<Uuid> {
20    match role_info.domain {
21        crate::roles::RoleDomain::Global
22        | crate::roles::RoleDomain::Organization(_)
23        | crate::roles::RoleDomain::Exam(_) => {
24            return Err(ModelError::new(
25                ModelErrorType::InvalidRequest,
26                "Cannot use a pending role for a role this broad".to_string(),
27                None,
28            ));
29        }
30
31        crate::roles::RoleDomain::Course(_) | crate::roles::RoleDomain::CourseInstance(_) => (),
32    };
33
34    match role_info.role {
35        UserRole::Admin | UserRole::Teacher => {
36            return Err(ModelError::new(
37                ModelErrorType::InvalidRequest,
38                "Cannot use a pending role with this much power".to_string(),
39                None,
40            ));
41        }
42        _ => (),
43    }
44
45    let course_id = match role_info.domain {
46        crate::roles::RoleDomain::Course(id) => Some(id),
47        _ => None,
48    };
49
50    let course_instance_id = match role_info.domain {
51        crate::roles::RoleDomain::CourseInstance(id) => Some(id),
52        _ => None,
53    };
54
55    let id = sqlx::query!(
56        r#"
57INSERT INTO pending_roles (
58    id,
59    user_email,
60    role,
61    course_id,
62    course_instance_id
63  )
64VALUES ($1, $2, $3, $4, $5)
65RETURNING id;
66        "#,
67        pkey_policy.into_uuid(),
68        role_info.email,
69        role_info.role as UserRole,
70        course_id,
71        course_instance_id
72    )
73    .fetch_one(conn)
74    .await?
75    .id;
76    Ok(id)
77}
78
79pub async fn get_all(conn: &mut PgConnection, domain: RoleDomain) -> ModelResult<Vec<PendingRole>> {
80    let res = match domain {
81        RoleDomain::Global | RoleDomain::Organization(_) | RoleDomain::Exam(_) => {
82            return Ok(Vec::new());
83        }
84        RoleDomain::Course(course_id) => {
85            sqlx::query_as!(
86                PendingRole,
87                r#"
88SELECT id, user_email, expires_at, role AS "role!: UserRole" FROM pending_roles
89WHERE course_id = $1
90AND deleted_at IS NULL
91AND expires_at > NOW()
92          "#,
93                course_id
94            )
95            .fetch_all(&mut *conn)
96            .await?
97        }
98        RoleDomain::CourseInstance(course_instance_id) => {
99            sqlx::query_as!(
100                PendingRole,
101                r#"
102SELECT id, user_email, expires_at, role AS "role!: UserRole" FROM pending_roles
103WHERE course_instance_id = $1
104AND deleted_at IS NULL
105AND expires_at > NOW()
106        "#,
107                course_instance_id
108            )
109            .fetch_all(&mut *conn)
110            .await?
111        }
112    };
113    Ok(res)
114}