mas_handlers/admin/v1/users/
set_admin.rs1use aide::{OperationIo, transform::TransformOperation};
8use axum::{Json, response::IntoResponse};
9use hyper::StatusCode;
10use schemars::JsonSchema;
11use serde::Deserialize;
12use ulid::Ulid;
13
14use crate::{
15 admin::{
16 call_context::CallContext,
17 model::{Resource, User},
18 params::UlidPathParam,
19 response::{ErrorResponse, SingleResponse},
20 },
21 impl_from_error_for_route,
22};
23
24#[derive(Debug, thiserror::Error, OperationIo)]
25#[aide(output_with = "Json<ErrorResponse>")]
26pub enum RouteError {
27 #[error(transparent)]
28 Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
29
30 #[error("User ID {0} not found")]
31 NotFound(Ulid),
32}
33
34impl_from_error_for_route!(mas_storage::RepositoryError);
35
36impl IntoResponse for RouteError {
37 fn into_response(self) -> axum::response::Response {
38 let error = ErrorResponse::from_error(&self);
39 let status = match self {
40 Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
41 Self::NotFound(_) => StatusCode::NOT_FOUND,
42 };
43 (status, Json(error)).into_response()
44 }
45}
46
47#[derive(Deserialize, JsonSchema)]
49#[serde(rename = "UserSetAdminRequest")]
50pub struct Request {
51 admin: bool,
53}
54
55pub fn doc(operation: TransformOperation) -> TransformOperation {
56 operation
57 .id("userSetAdmin")
58 .summary("Set whether a user can request admin")
59 .description("Calling this endpoint will not have any effect on existing sessions, meaning that their existing sessions will keep admin access if they were granted it.")
60 .tag("user")
61 .response_with::<200, Json<SingleResponse<User>>, _>(|t| {
62 let [_alice, bob, ..] = User::samples();
64 let id = bob.id();
65 let response = SingleResponse::new(bob, format!("/api/admin/v1/users/{id}/set-admin"));
66 t.description("User had admin privileges set").example(response)
67 })
68 .response_with::<404, RouteError, _>(|t| {
69 let response = ErrorResponse::from_error(&RouteError::NotFound(Ulid::nil()));
70 t.description("User ID not found").example(response)
71 })
72}
73
74#[tracing::instrument(name = "handler.admin.v1.users.set_admin", skip_all, err)]
75pub async fn handler(
76 CallContext { mut repo, .. }: CallContext,
77 id: UlidPathParam,
78 Json(params): Json<Request>,
79) -> Result<Json<SingleResponse<User>>, RouteError> {
80 let id = *id;
81 let user = repo
82 .user()
83 .lookup(id)
84 .await?
85 .ok_or(RouteError::NotFound(id))?;
86
87 let user = repo
88 .user()
89 .set_can_request_admin(user, params.admin)
90 .await?;
91
92 repo.save().await?;
93
94 Ok(Json(SingleResponse::new(
95 User::from(user),
96 format!("/api/admin/v1/users/{id}/set-admin"),
97 )))
98}
99
100#[cfg(test)]
101mod tests {
102 use hyper::{Request, StatusCode};
103 use mas_storage::{RepositoryAccess, user::UserRepository};
104 use sqlx::PgPool;
105
106 use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup};
107
108 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
109 async fn test_change_can_request_admin(pool: PgPool) {
110 setup();
111 let mut state = TestState::from_pool(pool).await.unwrap();
112 let token = state.token_with_scope("urn:mas:admin").await;
113
114 let mut repo = state.repository().await.unwrap();
115 let user = repo
116 .user()
117 .add(&mut state.rng(), &state.clock, "alice".to_owned())
118 .await
119 .unwrap();
120 repo.save().await.unwrap();
121
122 let request = Request::post(format!("/api/admin/v1/users/{}/set-admin", user.id))
123 .bearer(&token)
124 .json(serde_json::json!({
125 "admin": true,
126 }));
127
128 let response = state.request(request).await;
129 response.assert_status(StatusCode::OK);
130 let body: serde_json::Value = response.json();
131
132 assert_eq!(body["data"]["attributes"]["admin"], true);
133
134 let mut repo = state.repository().await.unwrap();
136 let user = repo.user().lookup(user.id).await.unwrap().unwrap();
137 assert!(user.can_request_admin);
138 repo.save().await.unwrap();
139
140 let request = Request::post(format!("/api/admin/v1/users/{}/set-admin", user.id))
142 .bearer(&token)
143 .json(serde_json::json!({
144 "admin": false,
145 }));
146
147 let response = state.request(request).await;
148 response.assert_status(StatusCode::OK);
149 let body: serde_json::Value = response.json();
150
151 assert_eq!(body["data"]["attributes"]["admin"], false);
152
153 let mut repo = state.repository().await.unwrap();
155 let user = repo.user().lookup(user.id).await.unwrap().unwrap();
156 assert!(!user.can_request_admin);
157 repo.save().await.unwrap();
158 }
159}