cargagep-v2/src/api/users.rs
Antoine Pelletier 6c8715fee3 wip
2026-08-25 12:29:30 +02:00

101 lines
3.4 KiB
Rust

//! 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<Json<Vec<Administrator>>, (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<GrantAdminForm>,
) -> Result<Json<Administrator>, (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<IdPath>,
) -> 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, ()>()
}