hive-c0re: annotate remaining dashboard routes with utoipa

This commit is contained in:
damocles 2026-07-31 23:00:12 +02:00 committed by mara
commit 582ebe5eee
21 changed files with 738 additions and 45 deletions

View file

@ -14,12 +14,25 @@ use axum::{
use problem_details::ProblemDetails;
use crate::scheduled_prompts::ScheduleNotFoundOrCancelled;
use crate::scheduled_prompts_worker::FireNowReport;
use super::{AppState, error_problem, error_response};
/// `GET /api/schedules` — snapshot of every schedule for the
/// scheduled-prompts tab. Returns the wire shape directly
/// so the frontend can render without an extra translation layer.
// `hive_sh4re::WireSchedule` (the actual body) has no `ToSchema` — adding
// one would pull `utoipa` into the wire-types crate for a single dashboard
// endpoint. `serde_json::Value` placeholder; see the batch report.
#[utoipa::path(
get,
path = "/api/schedules",
responses(
(status = 200, description = "every schedule, wire shape", body = Vec<serde_json::Value>),
(status = 500, description = "sqlite read failed"),
),
tag = "schedules"
)]
pub(super) async fn api_schedules(State(state): State<AppState>) -> Response {
match state.coord.scheduled_prompts.list() {
Ok(rows) => {
@ -50,6 +63,20 @@ pub(super) async fn api_schedules(State(state): State<AppState>) -> Response {
/// skips the approval gate — the operator click *is* the
/// approval. The schedule lands directly with
/// `source = Operator` and the worker picks it up at fire time.
#[utoipa::path(
post,
path = "/api/schedules",
// `hive_sh4re::SchedulePromptPayload` (the actual body) has no `ToSchema` —
// same reasoning as the `Vec<serde_json::Value>` placeholder on
// `api_schedules` above.
request_body(content = serde_json::Value, description = "SchedulePromptPayload wire shape"),
responses(
(status = 200, description = "created; body carries the new row id", body = serde_json::Value),
(status = 400, description = "no targets, empty body, or interval_seconds == 0"),
(status = 500, description = "submit failed"),
),
tag = "schedules"
)]
pub(super) async fn post_schedule_new(
State(state): State<AppState>,
axum::Json(payload): axum::Json<hive_sh4re::SchedulePromptPayload>,
@ -86,7 +113,7 @@ pub(super) async fn post_schedule_new(
/// Optional JSON body for `fire-now`. Absent / empty body ⇒
/// `reset_timer = false` (back-compat: cadence stays intact).
#[derive(serde::Deserialize, Default)]
#[derive(serde::Deserialize, Default, utoipa::ToSchema)]
pub(super) struct FireNowBody {
#[serde(default)]
reset_timer: bool,
@ -100,6 +127,17 @@ pub(super) struct FireNowBody {
/// wrong." For recurring schedules the cadence stays intact unless
/// the body carries `{"reset_timer": true}`, in which case the
/// countdown is re-armed from now (`next_fire_at = now + interval`).
#[utoipa::path(
post,
path = "/api/schedules/{id}/fire-now",
params(("id" = i64, Path, description = "schedule row id")),
request_body(content = FireNowBody, description = "optional; absent body means reset_timer = false"),
responses(
(status = 200, description = "fired; per-target outcome counts", body = FireNowReport),
(status = 500, description = "schedule missing, cancelled, or fully drained"),
),
tag = "schedules"
)]
pub(super) async fn post_schedule_fire_now(
State(state): State<AppState>,
AxumPath(id): AxumPath<i64>,
@ -123,6 +161,13 @@ pub(super) async fn post_schedule_fire_now(
/// flip to Cancelled, `{"cancelled": false}` when the DAG was
/// Running / terminal / gone. On success a fresh `RebuildQueueChanged`
/// snapshot fires so the state flip surfaces live.
#[utoipa::path(
post,
path = "/api/rebuild-queue/{id}/cancel",
params(("id" = u64, Path, description = "job-queue DAG id")),
responses((status = 200, description = "whether the DAG was cancelled", body = serde_json::Value)),
tag = "schedules"
)]
pub(super) async fn post_rebuild_queue_cancel(
State(state): State<AppState>,
AxumPath(id): AxumPath<u64>,
@ -137,14 +182,14 @@ pub(super) async fn post_rebuild_queue_cancel(
}
}
#[derive(serde::Deserialize, Default)]
#[derive(serde::Deserialize, Default, utoipa::ToSchema)]
pub(super) struct CancelScheduleForm {
/// `None` / absent / empty array → cancel whole schedule.
#[serde(default)]
targets: Option<Vec<String>>,
}
#[derive(serde::Deserialize, Default)]
#[derive(serde::Deserialize, Default, utoipa::ToSchema)]
#[allow(
clippy::option_option,
reason = "double-Option carries three-state PATCH semantics on the wire \
@ -203,6 +248,16 @@ where
/// `interval_seconds`. Cancelled schedules are refused — submit
/// a new one instead. Returns the updated `WireSchedule` so the
/// caller's post-edit refresh has the new state inline.
#[utoipa::path(
patch,
path = "/api/schedules/{id}",
params(("id" = i64, Path, description = "schedule row id")),
responses(
(status = 200, description = "updated, wire shape", body = serde_json::Value),
(status = 500, description = "update failed (cancelled, not found, ...)"),
),
tag = "schedules"
)]
pub(super) async fn patch_schedule(
State(state): State<AppState>,
AxumPath(id): AxumPath<i64>,
@ -233,6 +288,17 @@ pub(super) async fn patch_schedule(
/// `POST /api/schedules/{id}/pause` — pause a schedule so the worker
/// skips it until explicitly resumed. Idempotent; no-op on an already-
/// paused row. Returns 404 when the schedule is cancelled or not found.
#[utoipa::path(
post,
path = "/api/schedules/{id}/pause",
params(("id" = i64, Path, description = "schedule row id")),
responses(
(status = 200, description = "paused", body = String),
(status = 404, description = "schedule cancelled or not found"),
(status = 500, description = "pause failed"),
),
tag = "schedules"
)]
pub(super) async fn post_schedule_pause(
State(state): State<AppState>,
AxumPath(id): AxumPath<i64>,
@ -255,6 +321,17 @@ pub(super) async fn post_schedule_pause(
/// `POST /api/schedules/{id}/resume` — resume a paused schedule.
/// Idempotent; no-op on an already-active row. Returns 404 when the
/// schedule is cancelled or not found.
#[utoipa::path(
post,
path = "/api/schedules/{id}/resume",
params(("id" = i64, Path, description = "schedule row id")),
responses(
(status = 200, description = "resumed", body = String),
(status = 404, description = "schedule cancelled or not found"),
(status = 500, description = "resume failed"),
),
tag = "schedules"
)]
pub(super) async fn post_schedule_resume(
State(state): State<AppState>,
AxumPath(id): AxumPath<i64>,
@ -278,6 +355,16 @@ pub(super) async fn post_schedule_resume(
/// (whole schedule when no `targets` field, partial when one is
/// provided). Operator bypasses the topology check; the manager
/// surface enforces it for agent callers.
#[utoipa::path(
post,
path = "/api/schedules/{id}/cancel",
params(("id" = i64, Path, description = "schedule row id")),
responses(
(status = 200, description = "cancelled (whole or partial)", body = String),
(status = 500, description = "cancel failed"),
),
tag = "schedules"
)]
pub(super) async fn post_schedule_cancel(
State(state): State<AppState>,
AxumPath(id): AxumPath<i64>,