headless_lms_server/controllers/main_frontend/
roles.rs1use crate::prelude::*;
2use models::{
3 pending_roles::{self, PendingRole},
4 roles::{self, RoleDomain, RoleInfo, RoleUser},
5 users,
6};
7use utoipa::{OpenApi, ToSchema};
8
9use crate::domain::authorization::skip_authorize;
10
11#[derive(OpenApi)]
12#[openapi(paths(set, unset, fetch, fetch_pending))]
13pub(crate) struct MainFrontendRolesApiDoc;
14
15async fn authorize_role_management(
16 conn: &mut PgConnection,
17 domain: RoleDomain,
18 action: Act,
19 user_id: Uuid,
20) -> ControllerResult<()> {
21 let token = match domain {
22 RoleDomain::Global => {
23 authorize(conn, action, Some(user_id), Res::GlobalPermissions).await?
24 }
25 RoleDomain::Organization(id) => {
26 authorize(conn, action, Some(user_id), Res::Organization(id)).await?
27 }
28 RoleDomain::Course(id) => authorize(conn, action, Some(user_id), Res::Course(id)).await?,
29 RoleDomain::CourseInstance(id) => {
30 authorize(conn, action, Some(user_id), Res::CourseInstance(id)).await?
31 }
32 RoleDomain::Exam(id) => authorize(conn, Act::Edit, Some(user_id), Res::Exam(id)).await?,
33 };
34
35 token.authorized_ok(())
36}
37
38#[utoipa::path(
42 post,
43 path = "/add",
44 operation_id = "addRole",
45 tag = "roles",
46 request_body = RoleInfo,
47 responses(
48 (status = 200, description = "Role added")
49 )
50)]
51#[instrument(skip(pool))]
52pub async fn set(
53 pool: web::Data<PgPool>,
54 role_info: web::Json<RoleInfo>,
55 user: AuthUser,
56) -> ControllerResult<HttpResponse> {
57 let mut conn = pool.acquire().await?;
58 authorize_role_management(
59 &mut conn,
60 role_info.domain,
61 Act::EditRole(role_info.role),
62 user.id,
63 )
64 .await?;
65
66 let target_user = users::try_get_by_email(&mut conn, &role_info.email).await?;
67 if let Some(target_user) = target_user {
68 roles::insert(&mut conn, target_user.id, role_info.role, role_info.domain).await?;
69 let token = skip_authorize();
70 return token.authorized_ok(HttpResponse::Ok().finish());
71 }
72 Err(ControllerError::new(
73 ControllerErrorType::NotFound,
74 "The user either does not exist or has not logged in to this website previously."
75 .to_string(),
76 None,
77 ))
78}
79
80#[utoipa::path(
84 post,
85 path = "/remove",
86 operation_id = "removeRole",
87 tag = "roles",
88 request_body = RoleInfo,
89 responses(
90 (status = 200, description = "Role removed")
91 )
92)]
93#[instrument(skip(pool))]
94pub async fn unset(
95 pool: web::Data<PgPool>,
96 role_info: web::Json<RoleInfo>,
97 user: AuthUser,
98) -> ControllerResult<HttpResponse> {
99 let mut conn = pool.acquire().await?;
100 authorize_role_management(
101 &mut conn,
102 role_info.domain,
103 Act::EditRole(role_info.role),
104 user.id,
105 )
106 .await?;
107 let target_user = users::get_by_email(&mut conn, &role_info.email).await?;
108 roles::remove(&mut conn, target_user.id, role_info.role, role_info.domain).await?;
109
110 let token = skip_authorize();
111 token.authorized_ok(HttpResponse::Ok().finish())
112}
113
114#[derive(Debug, Deserialize, ToSchema)]
115
116pub struct RoleQuery {
117 #[serde(skip_serializing_if = "Option::is_none")]
118 global: Option<bool>,
119 #[serde(skip_serializing_if = "Option::is_none")]
120 organization_id: Option<Uuid>,
121 #[serde(skip_serializing_if = "Option::is_none")]
122 course_id: Option<Uuid>,
123 #[serde(skip_serializing_if = "Option::is_none")]
124 course_instance_id: Option<Uuid>,
125 #[serde(skip_serializing_if = "Option::is_none")]
126 exam_id: Option<Uuid>,
127}
128
129impl TryFrom<RoleQuery> for RoleDomain {
130 type Error = ControllerError;
131
132 fn try_from(
133 RoleQuery {
134 global,
135 organization_id,
136 course_id,
137 course_instance_id,
138 exam_id,
139 }: RoleQuery,
140 ) -> Result<Self, Self::Error> {
141 let domain = if global.unwrap_or_default() {
142 RoleDomain::Global
143 } else if let Some(id) = organization_id {
144 RoleDomain::Organization(id)
145 } else if let Some(id) = course_id {
146 RoleDomain::Course(id)
147 } else if let Some(id) = course_instance_id {
148 RoleDomain::CourseInstance(id)
149 } else if let Some(id) = exam_id {
150 RoleDomain::Exam(id)
151 } else {
152 return Err(ControllerError::new(
153 ControllerErrorType::BadRequest,
154 "Invalid query".to_string(),
155 None,
156 ));
157 };
158 Ok(domain)
159 }
160}
161
162#[instrument(skip(pool))]
166#[utoipa::path(
167 get,
168 path = "",
169 operation_id = "getRoles",
170 tag = "roles",
171 params(
172 ("global" = Option<bool>, Query, description = "Global domain"),
173 ("organization_id" = Option<Uuid>, Query, description = "Organization id"),
174 ("course_id" = Option<Uuid>, Query, description = "Course id"),
175 ("course_instance_id" = Option<Uuid>, Query, description = "Course instance id"),
176 ("exam_id" = Option<Uuid>, Query, description = "Exam id")
177 ),
178 responses(
179 (status = 200, description = "Roles", body = [RoleUser])
180 )
181)]
182pub async fn fetch(
183 pool: web::Data<PgPool>,
184 query: web::Query<RoleQuery>,
185 user: AuthUser,
186) -> ControllerResult<web::Json<Vec<RoleUser>>> {
187 let mut conn = pool.acquire().await?;
188 let domain = query.into_inner().try_into()?;
189 authorize_role_management(&mut conn, domain, Act::Edit, user.id).await?;
190
191 let roles = roles::get(&mut conn, domain).await?;
192
193 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::AnyCourse).await?;
194 token.authorized_ok(web::Json(roles))
195}
196
197#[instrument(skip(pool))]
201#[utoipa::path(
202 get,
203 path = "/pending",
204 operation_id = "getPendingRoles",
205 tag = "roles",
206 params(
207 ("global" = Option<bool>, Query, description = "Global domain"),
208 ("organization_id" = Option<Uuid>, Query, description = "Organization id"),
209 ("course_id" = Option<Uuid>, Query, description = "Course id"),
210 ("course_instance_id" = Option<Uuid>, Query, description = "Course instance id"),
211 ("exam_id" = Option<Uuid>, Query, description = "Exam id")
212 ),
213 responses(
214 (status = 200, description = "Pending roles", body = [PendingRole])
215 )
216)]
217pub async fn fetch_pending(
218 pool: web::Data<PgPool>,
219 query: web::Query<RoleQuery>,
220 user: AuthUser,
221) -> ControllerResult<web::Json<Vec<PendingRole>>> {
222 let mut conn = pool.acquire().await?;
223 let domain = query.into_inner().try_into()?;
224 authorize_role_management(&mut conn, domain, Act::Edit, user.id).await?;
225
226 let roles = pending_roles::get_all(&mut conn, domain).await?;
227 let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::AnyCourse).await?;
228 token.authorized_ok(web::Json(roles))
229}
230
231pub fn _add_routes(cfg: &mut ServiceConfig) {
239 cfg.route("/add", web::post().to(set))
240 .route("/remove", web::post().to(unset))
241 .route("", web::get().to(fetch))
242 .route("/pending", web::get().to(fetch_pending));
243}