cairn_mod/server/admin/mod.rs
1//! `tools.cairn.admin.*` handlers (§F12).
2//!
3//! Each endpoint lives in its own file; [`admin_router`] wires them
4//! together under one `Extension<AdminState>` so sub-modules don't
5//! need to duplicate state plumbing. Per-request auth + role + CORS
6//! gating is centralized in the shared `common::verify_and_authorize`
7//! helper inside this module.
8
9use std::sync::Arc;
10
11use axum::Extension;
12use axum::Router;
13use axum::routing::{get, post};
14use sqlx::{Pool, Sqlite};
15
16use crate::auth::AuthContext;
17use crate::writer::WriterHandle;
18
19mod apply_label;
20mod audit_view;
21mod common;
22mod flag_reporter;
23mod get_report;
24mod list_audit_log;
25mod list_labels;
26mod list_reports;
27mod negate_label;
28mod report_view;
29mod resolve_report;
30
31/// Operator configuration for admin endpoints. Kept separate from the
32/// subscribe/query configs so operators can tune label-value policy
33/// without touching read-side knobs.
34#[derive(Debug, Clone, Default)]
35pub struct AdminConfig {
36 /// Operator-declared label values (§F12 `InvalidLabelValue`).
37 /// When `Some`, `applyLabel` rejects values not in this set.
38 /// When `None`, any val ≤128 bytes is accepted — the §F11
39 /// anti-leak principle applies: the error message on reject
40 /// does NOT enumerate the allowed values.
41 ///
42 /// Future: a #9 service-record update may derive this from the
43 /// published `app.bsky.labeler.service` record so the lexicon set
44 /// and the runtime policy stay in lockstep.
45 pub label_values: Option<Vec<String>>,
46}
47
48/// Build a Router exposing the tools.cairn.admin.* endpoints
49/// registered so far. Compose with subscribe/query/createReport
50/// routers via `Router::merge`.
51pub fn admin_router(
52 pool: Pool<Sqlite>,
53 writer: WriterHandle,
54 auth: Arc<AuthContext>,
55 config: AdminConfig,
56) -> Router {
57 let state = common::AdminState {
58 pool,
59 writer,
60 auth,
61 config: Arc::new(config),
62 };
63 Router::new()
64 .route(
65 "/xrpc/tools.cairn.admin.applyLabel",
66 post(apply_label::handler),
67 )
68 .route(
69 "/xrpc/tools.cairn.admin.negateLabel",
70 post(negate_label::handler),
71 )
72 .route(
73 "/xrpc/tools.cairn.admin.listLabels",
74 get(list_labels::handler),
75 )
76 .route(
77 "/xrpc/tools.cairn.admin.listReports",
78 get(list_reports::handler),
79 )
80 .route(
81 "/xrpc/tools.cairn.admin.getReport",
82 get(get_report::handler),
83 )
84 .route(
85 "/xrpc/tools.cairn.admin.resolveReport",
86 post(resolve_report::handler),
87 )
88 .route(
89 "/xrpc/tools.cairn.admin.flagReporter",
90 post(flag_reporter::handler),
91 )
92 .route(
93 "/xrpc/tools.cairn.admin.listAuditLog",
94 get(list_audit_log::handler),
95 )
96 .layer(Extension(state))
97}