feat(api): admin manga/chapter overview with derived sync state (0.39.0)

Adds GET /api/v1/admin/mangas and /admin/mangas/:id/chapters guarded by
RequireAdmin. Sync state is computed at query time from the existing
crawler signals (manga_sources / chapter_sources / crawler_jobs) — no
new state column is persisted, so the crawler stays the single writer
of these signals.

Per-manga priority: InProgress (in-flight sync_manga or
sync_chapter_list job) > Dropped (all source rows soft-dropped) >
Synced (default; covers user-uploaded mangas with zero source rows).

Per-chapter priority: Downloading (in-flight sync_chapter_content) >
Dropped (all source rows soft-dropped) > Failed (most-recent terminal
job is dead) > NotDownloaded (page_count = 0) > Synced. The Failed
check sits ABOVE NotDownloaded so the more informative "we tried and
it died" state wins over "we never got around to it" — see the
priority comment in repo/admin_view.rs.

Migration 0020 adds a partial index on
crawler_jobs((payload->>'source_manga_key')) for the one job kind
(sync_manga) whose payload doesn't carry manga_id directly — without it
the in-flight detection for a manga falls back to a seqscan over the
job table.
This commit is contained in:
MechaCat02
2026-05-30 21:41:09 +02:00
parent 0b2018ceca
commit bf7c9b5c2a
11 changed files with 790 additions and 4 deletions

View File

@@ -4,6 +4,7 @@
//! bot/API tokens cannot reach admin routes (see
//! `crate::auth::extractor::RequireAdmin`).
pub mod mangas;
pub mod users;
use axum::Router;
@@ -11,5 +12,5 @@ use axum::Router;
use crate::app::AppState;
pub fn routes() -> Router<AppState> {
Router::new().merge(users::routes())
Router::new().merge(users::routes()).merge(mangas::routes())
}