//! Who administers the app. //! //! Users themselves are not managed here: they come from the authentication //! provider. The one decision that belongs to this app is `admin`, and this is //! where it is taken. use aide::{ axum::{ ApiRouter, routing::{delete_with, get_with, post_with}, }, transform::TransformOperation, }; use axum::{Json, extract::Path, http::StatusCode}; use schemars::JsonSchema; use serde::Deserialize; use crate::{ api::helpers::{IdPath, admin, admin_desc, desc, unexpected_error}, core::{ controller::{AppController, ControllerError, users::UsersControllerError}, models::user::Administrator, }, }; pub fn routes() -> ApiRouter { ApiRouter::new() .api_route("/admins", get_with(get_admins, get_admins_docs)) .api_route("/admins", post_with(grant_admin, grant_admin_docs)) .api_route("/admins/{id}", delete_with(revoke_admin, revoke_admin_docs)) } #[derive(Debug, Deserialize, JsonSchema)] struct GrantAdminForm { /// The address of the person to promote, whether or not they have ever /// logged in email: String, } #[axum::debug_handler] async fn get_admins(ac: AppController) -> Result>, (StatusCode, String)> { match admin(ac)?.get_admins().await { Ok(admins) => Ok(Json(admins)), Err(err) => unexpected_error("get_admins", err), } } fn get_admins_docs(op: TransformOperation) -> TransformOperation { op.tag("Users") .summary("List the administrators") .response_with::<403, (), _>(admin_desc) } #[axum::debug_handler] async fn grant_admin( ac: AppController, Json(GrantAdminForm { email }): Json, ) -> Result, (StatusCode, String)> { match admin(ac)?.grant_admin(&email).await { Ok(administrator) => Ok(Json(administrator)), Err(ControllerError::User(err @ UsersControllerError::EmailInvalid)) => { Err((StatusCode::BAD_REQUEST, err.to_string())) } Err(err) => unexpected_error("grant_admin", err), } } fn grant_admin_docs(op: TransformOperation) -> TransformOperation { op.tag("Users") .summary("Make somebody an administrator, by email") .description( "The person need not have logged in yet: the row created is adopted \ at their first login. Granting to somebody who already is one \ changes nothing.", ) .response_with::<403, (), _>(admin_desc) .response_with::<400, (), _>(desc("Not an email address")) } #[axum::debug_handler] async fn revoke_admin( ac: AppController, Path(IdPath { id }): Path, ) -> Result<(), (StatusCode, String)> { match admin(ac)?.revoke_admin(id).await { Ok(()) => Ok(()), Err(ControllerError::User(err @ UsersControllerError::CannotDemoteSelf)) => { Err((StatusCode::CONFLICT, err.to_string())) } Err(err) if err.is_not_found() => Err((StatusCode::NOT_FOUND, "No such user".to_owned())), Err(err) => unexpected_error("revoke_admin", err), } } fn revoke_admin_docs(op: TransformOperation) -> TransformOperation { op.tag("Users") .summary("Take the administrator rights away") .response_with::<403, (), _>(admin_desc) .response_with::<409, (), _>(desc("An administrator cannot demote themselves")) .response::<404, ()>() }