← snapshot
4792 bytes
//! Route table and shared handler helpers.
use std::collections::BTreeMap;
use axum::Router;
use axum::routing::{get, post};
use serde_json::json;
use crate::error::ApiError;
use crate::state::{AppState, RepoEntry};
pub(crate) mod commits;
pub(crate) mod decisions;
pub(crate) mod import;
pub(crate) mod promote;
pub(crate) mod remarks;
pub(crate) mod rendezvous;
pub(crate) mod repos;
pub(crate) mod snapshots;
pub(crate) mod telemetry;
pub(crate) mod transcribe;
/// Assembles every route under `/api`.
pub(crate) fn router() -> Router<crate::SharedState> {
Router::new()
.route("/api/health", get(health))
.route("/api/repos", post(repos::init).get(repos::list))
.route("/api/repos/{slug}", get(repos::detail))
.route("/api/repos/{slug}/identities", post(repos::add_identity))
.route(
"/api/repos/{slug}/refs",
get(repos::list_refs).post(repos::branch),
)
.route("/api/repos/{slug}/log", get(snapshots::log))
.route("/api/repos/{slug}/snapshots/{hex}", get(snapshots::detail))
.route(
"/api/repos/{slug}/snapshots/{hex}/tree",
get(snapshots::tree),
)
.route("/api/repos/{slug}/blobs/{hex}", get(snapshots::blob))
.route("/api/repos/{slug}/diff", get(snapshots::diff))
.route("/api/import/github", post(import::github))
.route("/api/repos/{slug}/commits", post(commits::create))
.route(
"/api/repos/{slug}/decisions",
get(decisions::list).post(decisions::propose),
)
.route("/api/repos/{slug}/decisions/{hex}", get(decisions::detail))
.route(
"/api/repos/{slug}/decisions/{hex}/attest",
post(decisions::attest),
)
.route(
"/api/repos/{slug}/remarks",
get(remarks::list).post(remarks::raise),
)
.route(
"/api/repos/{slug}/remarks/{id}/settle",
post(remarks::settle),
)
.route(
"/api/transcribe",
get(transcribe::available)
.post(transcribe::run)
// Audio is far larger than any JSON this surface accepts, so
// this one route opts out of the default 2 MiB body cap.
.layer(axum::extract::DefaultBodyLimit::max(
transcribe::MAX_AUDIO_BYTES,
)),
)
.route("/api/repos/{slug}/promote/preview", get(promote::preview))
.route("/api/repos/{slug}/promote", post(promote::run))
.route(
"/api/repos/{slug}/sketches/snapshot/{hex}",
get(rendezvous::sketch_snapshot),
)
.route(
"/api/repos/{slug}/sketches/decision/{hex}",
get(rendezvous::sketch_decision),
)
.route(
"/api/repos/{slug}/rendezvous/publish",
post(rendezvous::publish),
)
.route(
"/api/repos/{slug}/rendezvous/find-similar",
post(rendezvous::find_similar),
)
.route(
"/api/rendezvous/publications",
get(rendezvous::publications),
)
.route(
"/api/rendezvous/introductions",
get(rendezvous::introductions).post(rendezvous::broker),
)
.route("/api/demo", post(crate::demo::seed_route))
.route("/api/telemetry/web-vitals", post(telemetry::web_vitals))
}
async fn health() -> axum::Json<serde_json::Value> {
axum::Json(json!({ "ok": true }))
}
/// Runs `f` with read access to the named repo.
pub(crate) fn with_repo<T>(
state: &AppState,
slug: &str,
f: impl FnOnce(&RepoEntry) -> Result<T, ApiError>,
) -> Result<T, ApiError> {
let repos = read_repos(state)?;
let entry = repos
.get(slug)
.ok_or_else(|| ApiError::not_found(format!("no repo named `{slug}`")))?;
f(entry)
}
/// Runs `f` with write access to the named repo.
pub(crate) fn with_repo_mut<T>(
state: &AppState,
slug: &str,
f: impl FnOnce(&mut RepoEntry) -> Result<T, ApiError>,
) -> Result<T, ApiError> {
let mut repos = write_repos(state)?;
let entry = repos
.get_mut(slug)
.ok_or_else(|| ApiError::not_found(format!("no repo named `{slug}`")))?;
f(entry)
}
pub(crate) fn read_repos(
state: &AppState,
) -> Result<std::sync::RwLockReadGuard<'_, BTreeMap<String, RepoEntry>>, ApiError> {
state
.repos
.read()
.map_err(|_| ApiError::internal("state lock poisoned"))
}
pub(crate) fn write_repos(
state: &AppState,
) -> Result<std::sync::RwLockWriteGuard<'_, BTreeMap<String, RepoEntry>>, ApiError> {
state
.repos
.write()
.map_err(|_| ApiError::internal("state lock poisoned"))
}