feat: cover retry backfill + admin force-resync for manga & chapter (0.50.0)

Adds a per-tick cover-backfill pass to the crawler daemon so mangas whose
cover download failed on first attempt get retried — the metadata pass's
early-stop optimisation otherwise prevents the walk from revisiting them.

Adds admin-only POST /admin/mangas/:id/resync and POST /admin/chapters/:id/resync
that refetch metadata + cover (or chapter content with force_refetch) from the
crawler source synchronously and return the refreshed row. Surfaced in the
UI as "Force resync" buttons on the manga detail and reader pages,
admin-only via session.user.is_admin.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-01 22:00:09 +02:00
parent 5c22dfdb41
commit c134bdbbde
19 changed files with 1505 additions and 17 deletions

View File

@@ -21,6 +21,11 @@ pub enum AppError {
PayloadTooLarge(String),
#[error("unsupported media type: {0}")]
UnsupportedMediaType(String),
/// 503 — a feature is currently unavailable, distinct from a 5xx
/// internal error. Used when admin actions require the crawler
/// daemon but it's been disabled (`CRAWLER_DAEMON=false`).
#[error("service unavailable: {0}")]
ServiceUnavailable(String),
/// 429 with an optional `Retry-After` header value (in seconds).
#[error("too many requests")]
TooManyRequests {
@@ -56,6 +61,7 @@ impl AppError {
AppError::Conflict(_) => "conflict",
AppError::PayloadTooLarge(_) => "payload_too_large",
AppError::UnsupportedMediaType(_) => "unsupported_media_type",
AppError::ServiceUnavailable(_) => "service_unavailable",
AppError::TooManyRequests { .. } => "too_many_requests",
AppError::ValidationFailed { .. } => "validation_failed",
AppError::Database(sqlx::Error::RowNotFound) => "not_found",
@@ -85,6 +91,9 @@ impl IntoResponse for AppError {
AppError::UnsupportedMediaType(msg) => {
(StatusCode::UNSUPPORTED_MEDIA_TYPE, msg.clone(), None)
}
AppError::ServiceUnavailable(msg) => {
(StatusCode::SERVICE_UNAVAILABLE, msg.clone(), None)
}
AppError::TooManyRequests { retry_after_secs } => {
// Emit `Retry-After: N` (RFC 6585 §4) so a well-behaved
// client can back off correctly. Done by building the