feat(analysis): admin coverage + per-page inspection API

Read-only admin endpoints (admin-gated, not analysis-enabled-gated) for
the dashboard's coverage overview and page-detail view:

- GET /v1/admin/analysis/mangas?search= — paginated per-manga coverage
  (analyzed/total pages; only mangas with pages).
- GET /v1/admin/analysis/mangas/:id/chapters — per-chapter coverage.
- GET /v1/admin/analysis/chapters/:id/pages — per-page status grid
  (done | failed | queued | none).
- GET /v1/admin/analysis/pages/:id — full result (status, is_nsfw, scene,
  model, analyzed_at, error, OCR lines with kind, tags, content warnings);
  "none" for an existing-but-unanalyzed page, 404 for a missing page.

repo::page_analysis gains manga_coverage / chapter_coverage /
chapter_page_status / page_detail; domain adds the matching row types.

Tests: coverage counts (manga + chapter), per-page status, full detail +
unanalyzed "none" + 404, non-admin 403.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-13 20:34:05 +02:00
parent 268e8cc6c2
commit 824f5acf22
7 changed files with 469 additions and 6 deletions

View File

@@ -128,6 +128,61 @@ pub struct PageSearchItem {
pub rank: f32,
}
/// Analysis coverage for one manga (admin overview): how many of its pages
/// have a completed analysis vs. how many exist.
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct MangaCoverage {
pub manga_id: Uuid,
pub title: String,
pub total_pages: i64,
pub analyzed_pages: i64,
}
/// Analysis coverage for one chapter.
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct ChapterCoverage {
pub chapter_id: Uuid,
pub number: i32,
pub title: Option<String>,
pub total_pages: i64,
pub analyzed_pages: i64,
}
/// Per-page analysis status for a chapter's page grid. `status` is one of
/// `done` | `failed` | `queued` (a pending/running job) | `none`.
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct PageStatusItem {
pub page_id: Uuid,
pub page_number: i32,
pub status: String,
}
/// One OCR line in the page-detail view.
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct OcrLine {
pub kind: OcrKind,
pub text: String,
}
/// Full analysis result for one page, for the admin detail modal. `status`
/// is `done` | `failed` | `none` (no analysis row yet).
#[derive(Debug, Clone, Serialize)]
pub struct PageAnalysisDetail {
pub page_id: Uuid,
pub page_number: i32,
pub chapter_id: Uuid,
pub manga_id: Uuid,
pub status: String,
pub is_nsfw: bool,
pub scene_description: Option<String>,
pub model: Option<String>,
pub error: Option<String>,
pub analyzed_at: Option<DateTime<Utc>>,
pub ocr: Vec<OcrLine>,
pub tags: Vec<String>,
pub content_warnings: Vec<ContentWarning>,
}
/// One persisted `page_analysis` row.
#[derive(Debug, Clone, Serialize, FromRow)]
pub struct PageAnalysis {