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