Skip to main content

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 confirm_pending_action;
23mod dismiss_pending_action;
24mod flag_reporter;
25mod get_audit_log;
26mod get_pending_action;
27mod get_report;
28mod get_subject_history;
29mod get_subject_strikes;
30mod get_trust_chain;
31mod list_audit_log;
32mod list_labels;
33mod list_pending_actions;
34mod list_reports;
35mod negate_label;
36mod pending_action_view;
37mod record_action;
38mod report_view;
39mod resolve_report;
40mod retention_sweep;
41mod revoke_action;
42mod subject_action_view;
43
44/// Operator configuration for admin endpoints. Kept separate from the
45/// subscribe/query configs so operators can tune label-value policy
46/// without touching read-side knobs.
47#[derive(Debug, Clone, Default)]
48pub struct AdminConfig {
49    /// Operational allowlist for `applyLabel` (§F12 `InvalidLabelValue`).
50    /// When `Some`, `applyLabel` rejects values not in this set.
51    /// When `None`, any val ≤128 bytes is accepted — the §F11
52    /// anti-leak principle applies: the error message on reject
53    /// does NOT enumerate the allowed values.
54    ///
55    /// Distinct from [`Self::declared_label_values`] (the trust-chain
56    /// surface for the labeler's *declared* taxonomy). They typically
57    /// match in production but conceptually differ — the allowlist
58    /// gates incoming writes; the declared list documents what the
59    /// labeler publishes.
60    ///
61    /// Future: a #9 service-record update may derive this from the
62    /// published `app.bsky.labeler.service` record so the lexicon set
63    /// and the runtime policy stay in lockstep.
64    pub label_values: Option<Vec<String>>,
65
66    /// Service DID surfaced in `tools.cairn.admin.getTrustChain` (#36).
67    /// Mirrors `Config::service_did`; populated at admin_router
68    /// construction in `serve::run`. Default empty for tests that
69    /// don't exercise the trust-chain endpoint.
70    pub service_did: String,
71
72    /// Service endpoint URL surfaced in
73    /// `tools.cairn.admin.getTrustChain` (#36). Mirrors
74    /// `Config::service_endpoint`. Default empty for tests that
75    /// don't exercise the trust-chain endpoint.
76    pub service_endpoint: String,
77
78    /// Labeler-declared label values from the `[labeler]` config
79    /// block — surfaced by `tools.cairn.admin.getTrustChain` as the
80    /// trust-chain "taxonomy" snapshot. `None` when the deployment
81    /// runs without `[labeler]` (§F19 labeler-absent path); the
82    /// trust-chain endpoint then reports `serviceRecord: null`.
83    /// Distinct from [`Self::label_values`] above — see that field's
84    /// doc comment.
85    pub declared_label_values: Option<Vec<String>>,
86}
87
88/// Build a Router exposing the tools.cairn.admin.* endpoints
89/// registered so far. Compose with subscribe/query/createReport
90/// routers via `Router::merge`.
91///
92/// `strike_policy` is the resolved v1.4 `[strike_policy]` (#48); the
93/// strikes read endpoint consults the threshold + decay window when
94/// projecting the wire envelope. Pass the same instance the writer
95/// task holds — `serve::run` resolves once at startup and clones
96/// here.
97pub fn admin_router(
98    pool: Pool<Sqlite>,
99    writer: WriterHandle,
100    auth: Arc<AuthContext>,
101    config: AdminConfig,
102    strike_policy: crate::moderation::policy::StrikePolicy,
103) -> Router {
104    let state = common::AdminState {
105        pool,
106        writer,
107        auth,
108        config: Arc::new(config),
109        strike_policy: Arc::new(strike_policy),
110    };
111    Router::new()
112        .route(
113            "/xrpc/tools.cairn.admin.applyLabel",
114            post(apply_label::handler),
115        )
116        .route(
117            "/xrpc/tools.cairn.admin.negateLabel",
118            post(negate_label::handler),
119        )
120        .route(
121            "/xrpc/tools.cairn.admin.listLabels",
122            get(list_labels::handler),
123        )
124        .route(
125            "/xrpc/tools.cairn.admin.listReports",
126            get(list_reports::handler),
127        )
128        .route(
129            "/xrpc/tools.cairn.admin.getReport",
130            get(get_report::handler),
131        )
132        .route(
133            "/xrpc/tools.cairn.admin.resolveReport",
134            post(resolve_report::handler),
135        )
136        .route(
137            "/xrpc/tools.cairn.admin.flagReporter",
138            post(flag_reporter::handler),
139        )
140        .route(
141            "/xrpc/tools.cairn.admin.listAuditLog",
142            get(list_audit_log::handler),
143        )
144        .route(
145            "/xrpc/tools.cairn.admin.getAuditLog",
146            get(get_audit_log::handler),
147        )
148        .route(
149            "/xrpc/tools.cairn.admin.retentionSweep",
150            post(retention_sweep::handler),
151        )
152        .route(
153            "/xrpc/tools.cairn.admin.getTrustChain",
154            get(get_trust_chain::handler),
155        )
156        .route(
157            "/xrpc/tools.cairn.admin.recordAction",
158            post(record_action::handler),
159        )
160        .route(
161            "/xrpc/tools.cairn.admin.revokeAction",
162            post(revoke_action::handler),
163        )
164        .route(
165            "/xrpc/tools.cairn.admin.getSubjectHistory",
166            get(get_subject_history::handler),
167        )
168        .route(
169            "/xrpc/tools.cairn.admin.getSubjectStrikes",
170            get(get_subject_strikes::handler),
171        )
172        .route(
173            "/xrpc/tools.cairn.admin.confirmPendingAction",
174            post(confirm_pending_action::handler),
175        )
176        .route(
177            "/xrpc/tools.cairn.admin.dismissPendingAction",
178            post(dismiss_pending_action::handler),
179        )
180        .route(
181            "/xrpc/tools.cairn.admin.listPendingActions",
182            get(list_pending_actions::handler),
183        )
184        .route(
185            "/xrpc/tools.cairn.admin.getPendingAction",
186            get(get_pending_action::handler),
187        )
188        .layer(Extension(state))
189}