headless_lms_models/
pending_roles.rs

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