feat: table series avec UUID PK — migration complète backend + frontend

Migration DB (0070 + 0071):
- Backup automatique de book_reading_progress avant migration
- Crée table series (fusion de series_metadata) avec UUID PK
- Ajoute series_id FK à books, external_metadata_links, anilist_series_links,
  available_downloads, download_detection_results
- Supprime les colonnes TEXT legacy et la table series_metadata

Backend API + Indexer:
- Toutes les queries SQL migrées vers series_id FK + JOIN series
- Routes /series/:name → /series/:series_id (UUID)
- Nouvel endpoint GET /series/by-name/:name pour lookup par nom
- match_title_volumes() factorisé entre prowlarr.rs et download_detection.rs
- Fix scheduler.rs: settings → app_settings
- OpenAPI mis à jour avec les nouveaux endpoints

Frontend:
- Routes /libraries/[id]/series/[name] → /series/[seriesId]
- Tous les composants (Edit, Delete, MarkRead, Prowlarr, Metadata,
  ReadingStatus) utilisent seriesId
- compressVolumes() pour afficher T1→3 au lieu de T1 T2 T3
- Titre release en entier (plus de truncate) dans available downloads

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-29 22:51:00 +02:00
parent 292e9bc77f
commit ccc7f375f6
38 changed files with 463 additions and 286 deletions

View File

@@ -17,14 +17,12 @@ async fn rematch_unlinked_books(pool: &PgPool, library_id: Uuid) {
b.id AS book_id
FROM external_book_metadata ebm2
JOIN external_metadata_links eml ON eml.id = ebm2.link_id
JOIN books b ON b.library_id = eml.library_id
JOIN books b ON b.series_id = eml.series_id
AND b.volume = ebm2.volume_number
LEFT JOIN series s ON s.id = b.series_id
WHERE eml.library_id = $1
AND ebm2.book_id IS NULL
AND ebm2.volume_number IS NOT NULL
AND eml.status = 'approved'
AND LOWER(COALESCE(s.name, 'unclassified')) = LOWER(eml.series_name)
) matched
WHERE ebm.id = matched.ebm_id
"#,

View File

@@ -122,7 +122,7 @@ pub async fn check_and_schedule_reading_status_push(pool: &PgPool) -> Result<()>
pub async fn check_and_schedule_download_detection(pool: &PgPool) -> Result<()> {
// Only schedule if Prowlarr is configured
let prowlarr_configured: bool = sqlx::query_scalar(
"SELECT EXISTS(SELECT 1 FROM settings WHERE key = 'prowlarr' AND value->>'base_url' IS NOT NULL AND value->>'base_url' != '')"
"SELECT EXISTS(SELECT 1 FROM app_settings WHERE key = 'prowlarr' AND value->>'base_url' IS NOT NULL AND value->>'base_url' != '')"
)
.fetch_one(pool)
.await