Files
stripstream-librarian/openspec/changes/archive/2026-03-09-cbr-to-cbz-conversion/tasks.md
Froidefond Julien e0b80cae38 feat: conversion CBR → CBZ via job asynchrone
Ajoute la possibilité de convertir un livre CBR en CBZ depuis le backoffice.
La conversion est sécurisée : le CBR original n'est supprimé qu'après vérification
du CBZ généré et mise à jour de la base de données.

- parsers: nouvelle fn `convert_cbr_to_cbz` (unar extract → zip pack → vérification → rename atomique)
- api: `POST /books/:id/convert` crée un job `cbr_to_cbz` (vérifie format CBR, détecte collision)
- indexer: nouveau `converter.rs` dispatché depuis `job.rs`
- backoffice: bouton "Convert to CBZ" sur la page détail (visible si CBR), label dans JobRow
- migrations: colonne `book_id` sur `index_jobs` + type `cbr_to_cbz` dans le check constraint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 23:02:08 +01:00

3.2 KiB

1. Migration DB

  • 1.1 Créer infra/migrations/0013_add_book_id_to_index_jobs.sql : ajouter colonne book_id UUID NULL REFERENCES books(id) ON DELETE SET NULL à index_jobs
  • 1.2 Mettre à jour sqlx-data.json / préparer les queries sqlx si nécessaire

2. Parsers — Fonction de conversion

  • 2.1 Ajouter pub fn convert_cbr_to_cbz(cbr_path: &Path) -> Result<PathBuf> dans crates/parsers/src/lib.rs
    • Extraire le CBR vers tmp_dir avec unar
    • Lister et trier les images extraites
    • Créer {stem}.cbz.tmp dans le dossier parent du CBR avec la zip crate
    • Vérifier que le CBZ contient le même nombre d'images que le CBR
    • Renommer .cbz.tmp.cbz
    • Nettoyer tmp_dir
    • Retourner le chemin du CBZ créé
  • 2.2 Ajouter la gestion d'erreur : collision de fichier existant, échec unar, échec zip, décompte incorrect

3. API — Endpoint de conversion

  • 3.1 Dans apps/api/src/books.rs, ajouter le handler POST /books/:id/convert
    • Vérifier que le livre existe
    • Vérifier que file_format == 'cbr'
    • Résoudre le chemin physique avec LIBRARIES_ROOT_PATH
    • Vérifier qu'aucun fichier {stem}.cbz n'existe déjà
    • Insérer un job cbr_to_cbz avec book_id en DB
    • Retourner le job créé
  • 3.2 Enregistrer la route dans apps/api/src/main.rs
  • 3.3 Ajouter les annotations #[utoipa::path] pour OpenAPI
  • 3.4 Mettre à jour IndexJobResponse / map_row dans index_jobs.rs pour inclure book_id (le champ est nullable)

4. Indexer — Worker de conversion

  • 4.1 Créer apps/indexer/src/converter.rs avec la fonction convert_book(job_id, book_id, pool, config)
    • Lire le livre en DB (file_path, file_format)
    • Résoudre le chemin physique
    • Appeler parsers::convert_cbr_to_cbz()
    • Mettre à jour books.file_path, books.file_format, books.updated_at en DB
    • Supprimer le CBR (log warning si la suppression échoue, ne pas faire échouer le job)
    • Mettre à jour le statut du job (success ou failed)
  • 4.2 Dans apps/indexer/src/worker.rs, ajouter le dispatch pour le type cbr_to_cbz
  • 4.3 Ajouter converter dans apps/indexer/src/lib.rs

5. Backoffice — Bouton de conversion

  • 5.1 Créer apps/backoffice/app/components/ConvertButton.tsx (composant client)
    • Appelle POST /books/:id/convert via apiFetch
    • Affiche un état de chargement pendant l'appel
    • En succès : affiche un message avec lien vers le job créé
    • En erreur : affiche le message d'erreur de l'API
  • 5.2 Intégrer ConvertButton dans apps/backoffice/app/books/[id]/page.tsx
    • Visible uniquement si book.file_format === 'cbr'
    • Placer dans la section des métadonnées ou comme action en haut de page
  • 5.3 Mettre à jour JobRow.tsx pour afficher le type cbr_to_cbz correctement (label lisible)

6. Vérification

  • 6.1 cargo build passe sans erreurs ni warnings clippy
  • 6.2 Tester manuellement la conversion d'un livre CBR depuis la page détail
  • 6.3 Vérifier que le CBR est supprimé et que file_format en DB est bien 'cbz'
  • 6.4 Vérifier le cas d'erreur : tenter de convertir un livre CBZ → 409
  • 6.5 Vérifier le cas d'erreur : CBZ déjà présent → 409