feat(analysis): enqueue page-analysis on upload/crawl + admin backfill endpoints

Wires the analyze_page job kind into the page-create paths and adds admin
controls, all gated on a new ANALYSIS_ENABLED flag (AppState.analysis_enabled,
config::AnalysisConfig):

- Chapter upload (api::chapters) enqueues one analyze_page job per page
  after the tx commits (best-effort; failures logged, never fatal).
- Crawler content sync (persist_pages → RETURNING id) enqueues per
  persisted page when the daemon dispatcher's analysis_enabled flag is set;
  threaded through sync_chapter_content (CLI/resync pass false).
- repo::page_analysis::enqueue_all_pages: bulk backfill via INSERT..SELECT,
  skipping done pages (only_unanalyzed) and existing pending jobs.
- New admin endpoints (RequireAdmin, 503 when disabled, audited):
  POST /admin/analysis/reenqueue and POST /admin/pages/:id/analyze (force).

Tests: upload enqueues N jobs / no-op when disabled; crawler persist_pages
enqueue gate; admin reenqueue (backfill, idempotent, only-unanalyzed, 503,
non-admin 403) and force-analyze (force flag, 404).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-13 18:39:03 +02:00
parent 63e1aa5484
commit 2bbf1595ff
15 changed files with 592 additions and 11 deletions

View File

@@ -137,6 +137,7 @@ async fn create(
)
.await?;
let mut page_ids: Vec<Uuid> = Vec::with_capacity(pages.len());
for (idx, page) in pages.iter().enumerate() {
let page_number = (idx + 1) as i32;
let nnnn = format!("{:04}", page_number);
@@ -145,7 +146,9 @@ async fn create(
manga_id, chapter.id, nnnn, page.ext
);
state.storage.put(&key, &page.bytes).await?;
repo::page::create(&mut *tx, chapter.id, page_number, &key, page.mime).await?;
let created =
repo::page::create(&mut *tx, chapter.id, page_number, &key, page.mime).await?;
page_ids.push(created.id);
}
let page_count = pages.len() as i32;
@@ -154,6 +157,20 @@ async fn create(
tx.commit().await?;
// Enqueue AI content-analysis for each new page. Done after commit so a
// rolled-back upload never leaves jobs pointing at nonexistent pages; a
// failed enqueue is logged but doesn't fail the upload (the admin
// re-enqueue endpoint can backfill).
if state.analysis_enabled {
for page_id in page_ids {
if let Err(e) =
repo::page_analysis::enqueue_for_page(&state.db, page_id, false).await
{
tracing::warn!(%page_id, error = %e, "failed to enqueue page analysis");
}
}
}
Ok((StatusCode::CREATED, Json(chapter)))
}