Adds /api/v1/admin/users list / DELETE / PATCH guarded by RequireAdmin, plus the audit-log substrate every future destructive admin endpoint will reuse. Safety properties: - Cannot self-delete or self-demote (409 conflict, message calls out "yourself" so the UI can render an explanation). - Cannot remove the last admin via either DELETE or demote. The check takes pg_advisory_xact_lock(ADMIN_INVARIANT_LOCK_KEY) and re-counts admins inside the same tx, closing the parallel-demote race that a bare "if count > 1" check would let through. The HTTP-serial path to this guard is structurally unreachable (the actor would have to be the lone admin demoting themselves, which the self-guard fires on first); the parallel race test exercises it via repo calls. Audit log (admin_audit table) records the action inside the same tx as the action itself, so a rolled-back action never leaves an orphan audit row. actor_user_id is ON DELETE SET NULL so the log outlives a later-deleted admin. target_id is not a FK because future audit kinds will target non-user rows.
34 lines
725 B
Rust
34 lines
725 B
Rust
pub mod admin;
|
|
pub mod auth;
|
|
pub mod authors;
|
|
pub mod bookmarks;
|
|
pub mod chapters;
|
|
pub mod collections;
|
|
pub mod files;
|
|
pub mod genres;
|
|
pub mod health;
|
|
pub mod history;
|
|
pub mod mangas;
|
|
pub mod pagination;
|
|
pub mod tags;
|
|
|
|
use axum::Router;
|
|
|
|
use crate::app::AppState;
|
|
|
|
pub fn routes() -> Router<AppState> {
|
|
Router::new()
|
|
.merge(health::routes())
|
|
.merge(mangas::routes())
|
|
.merge(chapters::routes())
|
|
.merge(files::routes())
|
|
.merge(auth::routes())
|
|
.merge(bookmarks::routes())
|
|
.merge(genres::routes())
|
|
.merge(tags::routes())
|
|
.merge(authors::routes())
|
|
.merge(collections::routes())
|
|
.merge(history::routes())
|
|
.merge(admin::routes())
|
|
}
|