feat: add reading_status_push job — differential push to AniList

Push reading statuses (PLANNING/CURRENT/COMPLETED) to AniList for all
linked series that changed since last sync, or have new books/no sync yet.

- Migration 0057: adds reading_status_push to index_jobs type constraint
- Migration 0058: creates reading_status_push_results table (pushed/skipped/no_books/error)
- API: new reading_status_push module with start_push, get_push_report, get_push_results
- Differential detection: synced_at IS NULL OR reading progress updated OR new books added
- Same 429 retry logic as reading_status_match (wait 10s, retry once, abort on 2nd 429)
- Notifications: ReadingStatusPushCompleted/Failed events
- Backoffice: push button in reading status group, job detail report with per-series list
- Replay support, badge label, i18n (FR + EN)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-25 10:30:04 +01:00
parent d977b6b27a
commit 10cc69e53f
13 changed files with 939 additions and 7 deletions

View File

@@ -0,0 +1,4 @@
ALTER TABLE index_jobs
DROP CONSTRAINT IF EXISTS index_jobs_type_check,
ADD CONSTRAINT index_jobs_type_check
CHECK (type IN ('scan', 'rebuild', 'full_rebuild', 'rescan', 'thumbnail_rebuild', 'thumbnail_regenerate', 'cbr_to_cbz', 'metadata_batch', 'metadata_refresh', 'reading_status_match', 'reading_status_push'));

View File

@@ -0,0 +1,19 @@
CREATE TABLE reading_status_push_results (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
job_id UUID NOT NULL REFERENCES index_jobs(id) ON DELETE CASCADE,
library_id UUID NOT NULL REFERENCES libraries(id) ON DELETE CASCADE,
series_name TEXT NOT NULL,
-- 'pushed' | 'skipped' | 'no_books' | 'error'
status TEXT NOT NULL,
anilist_id INTEGER,
anilist_title TEXT,
anilist_url TEXT,
-- what was actually sent to AniList (NULL when skipped/error)
anilist_status TEXT,
progress_volumes INTEGER,
error_message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_rspr_job_id ON reading_status_push_results(job_id);
CREATE INDEX idx_rspr_status ON reading_status_push_results(status);