Commit Graph

428 Commits

Author SHA1 Message Date
c5ada110ab fix: résoudre series_id dans torrent_downloads via JOIN
La table torrent_downloads ne stockait pas series_id. Le DTO l'exposait
comme optional mais était toujours null, causant des liens KO en page
downloads (fallback sur series_name alors que la route attend un UUID).

Ajout d'un LEFT JOIN series sur (library_id, LOWER(name)) dans la query
list_torrent_downloads pour résoudre le series_id dynamiquement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 09:27:41 +02:00
6511ed02cb feat: déplacer le bouton logout dans le menu hamburger mobile
Sur mobile, le bouton logout est désormais dans le drawer MobileNav
(en bas, après Settings) et masqué dans le header.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 09:24:44 +02:00
ab5b4becd3 chore: bump version to 3.0.0
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 52s
2026-03-29 22:51:20 +02:00
ccc7f375f6 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>
2026-03-29 22:51:00 +02:00
292e9bc77f refactor: migrer tout le code Rust vers series_id (table series)
API (15 fichiers):
- series.rs: helpers resolve_series_id/get_or_create_series, toutes les
  queries migrent de books.series TEXT vers series_id FK + JOIN series
- Routes /series/:name → /series/:series_id (UUID)
- books.rs: filtres série par series_id, SELECT s.name AS series via JOIN
- metadata.rs: sync écrit dans series au lieu de series_metadata
- metadata_refresh.rs: refresh_link et rematch via series_id
- metadata_batch.rs: sync via series table
- anilist.rs: liens par series_id au lieu de series_name
- download_detection.rs: available_downloads via series_id
- reading_progress.rs: mark_series_read par series_id
- torrent_import.rs: import via series JOIN
- search.rs, stats.rs, libraries.rs: JOINs series pour les noms
- reading_status_match.rs, reading_status_push.rs: séries via JOIN

Indexer (3 fichiers):
- scanner.rs: get_or_create_series_id() avec cache HashMap
- batch.rs: BookInsert/BookUpdate.series_id UUID au lieu de series String
- job.rs: rematch_unlinked_books via series JOIN

4 nouveaux tests (SeriesItem, SeriesMetadata, UpdateSeriesResponse,
BatchStructs avec series_id)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 21:13:11 +02:00
f2a7db939f feat: migration DB — table series avec UUID PK (fusionne series_metadata)
Migration 0070:
- Crée table series (id UUID PK, library_id FK, name, description, authors,
  publishers, status, locked_fields, original_name, etc.)
- Peuple depuis books + series_metadata existants
- Ajoute series_id FK à: books, external_metadata_links, anilist_series_links,
  available_downloads, download_detection_results
- Backfill tous les series_id par matching nom

Migration 0071:
- Supprime les colonnes TEXT legacy (books.series, *.series_name)
- Drop table series_metadata (fusionnée dans series)
- Recrée les contraintes UNIQUE sur series_id au lieu de series_name
- Nettoie les rows orphelines (series_id NULL)
- Ajoute index sur series_id dans toutes les tables

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:57:23 +02:00
9da70021f9 fix: LiveSearchForm — clear remet tout à zéro + focus préservé
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 47s
- Clear supprime le cookie ET force un remount du form (defaultValues à vide)
- Navigation propre via useEffect au lieu de mutations pendant le render
- Le focus est préservé lors de nos propres navigations (recherche, filtres)
- Remount uniquement sur navigation externe (back/forward du navigateur)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:47:45 +02:00
c005d408bf chore: bump version to 2.18.2 2026-03-29 20:44:54 +02:00
93399c3a3d feat: docker-push ne build que les services modifiés depuis le dernier tag
Détecte les fichiers changés depuis le dernier tag v* :
- apps/api/ ou crates/ changés → rebuild api
- apps/indexer/ ou crates/ changés → rebuild indexer
- apps/backoffice/ changé → rebuild backoffice
- Cargo.toml/Cargo.lock changés → rebuild tout

Affiche les services à build/skip, demande confirmation.
Option de forcer le build de tout si rien n'a changé.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:44:32 +02:00
991f468d19 chore: bump version to 2.18.1
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 44s
2026-03-29 18:08:31 +02:00
caeb9f989f fix: conserver le focus du champ de recherche après navigation
Le form ne remount plus lors de nos propres navigations (recherche,
filtres, clear) — le focus est préservé. Le remount ne se fait que
lors de navigations externes (back/forward du navigateur) pour syncer
les valeurs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:08:25 +02:00
69de2ae237 feat: loader discret dans la barre de recherche pendant la navigation
Utilise useTransition pour wrapper les router.replace dans LiveSearchForm.
Affiche un petit spinner à droite du champ de recherche pendant que les
résultats se chargent (books, series, authors).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:06:12 +02:00
3998d65694 fix: aligner la page livre sur le layout de la page série
- Cover w-48 → w-40 (cohérent avec la page série)
- Titre séparé, auteur + série + statut de lecture regroupés en badges
- Métadonnées (format, pages, langue, ISBN, date) en ligne texte
  au lieu de pills (style série)
- Toolbar d'actions groupée en bas (Edit, MarkRead, Convert, Delete)
- Tous les boutons d'action (MarkBookRead, Convert, Delete) alignés en
  py-1.5 au lieu de Button size=sm (h-9)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:03:38 +02:00
5757517d84 fix: harmoniser la hauteur des boutons dans la toolbar série
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 45s
- ProwlarrSearchModal: py-2 → py-1.5, gap-2 → gap-1.5 (aligné avec les autres)
- DeleteSeriesButton: remplace Button size=sm (h-9) par bouton inline
  avec py-1.5 cohérent avec le reste de la toolbar

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:58:13 +02:00
bedb588f50 fix: envoyer expected_volumes lors d'un replace pour tracker le download
Quand on fait un replace sans volumes manquants, expected_volumes n'était
pas envoyé → is_managed=false → pas d'insertion dans torrent_downloads
→ le torrent n'apparaissait pas dans la page downloads.

Maintenant un replace avec libraryId envoie toujours expected_volumes
(même vide) pour garantir le tracking.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:52:16 +02:00
ad0e9e5fa4 chore: bump version to 2.18.0 2026-03-29 17:52:07 +02:00
0c7685215b fix: re-matching automatique des métadonnées externes après scan/import
Quand les métadonnées externes sont récupérées avant que les livres
n'existent localement, le book_id reste NULL et les livres apparaissent
comme "manquants" alors qu'ils sont présents.

- Ajoute rematch_unlinked_books() qui associe les external_book_metadata
  non liées aux livres locaux par correspondance de volume
- Appelé automatiquement à la fin de refresh_link() (API)
- Appelé après chaque scan terminé dans l'indexer (job.rs)
- Testé sur Dragon Ball : 47/85 external books rematched, 43 restants
  correspondent aux tomes Dragon Ball Z non présents localement

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:48:58 +02:00
0cce4e50a7 fix: toujours afficher le bouton replace dans la modale Prowlarr
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 42s
alwaysShowReplace suffit maintenant à montrer le bouton replace,
même quand allVolumes est vide (série complète sans volumes manquants).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:36:37 +02:00
25355d6827 chore: bump version to 2.17.0 2026-03-29 17:33:41 +02:00
2d44885826 feat: suppression de série entière (dossier + livres + métadonnées)
Backend:
- Ajoute DELETE /libraries/:id/series/:name dans series.rs
- Supprime tous les fichiers physiques des livres de la série
- Supprime le dossier de la série (remove_dir_all)
- Nettoie en DB : books, series_metadata, external_metadata_links,
  anilist_series_links, available_downloads
- Queue un scan job pour cohérence

Frontend:
- Crée DeleteSeriesButton.tsx avec modale de confirmation
- Ajouté dans la toolbar de la page détail série
- i18n fr/en pour les textes de confirmation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:33:40 +02:00
3e4c9e888a chore: bump version to 2.16.0
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 49s
2026-03-29 17:25:41 +02:00
e4df93456d fix: retirer le terme "manquant" des badges de volumes dans les résultats Prowlarr
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:23:28 +02:00
3d2c8c78bf feat: regroupement des releases par titre identique dans la UI
- Prowlarr search modal : les releases avec le même titre sont groupées
  avec rowSpan sur la colonne titre, sources listées en sous-lignes
- Downloads page : même logique, titre + volumes affichés une seule fois,
  sources (indexer, seeders, taille, boutons) en lignes compactes
- Fonction utilitaire groupReleasesByTitle() dans les deux composants

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:17:24 +02:00
f8f2e9fe71 chore: bump version to 2.15.0 2026-03-29 17:08:29 +02:00
3a105bbac8 feat: détection des releases intégrales/complètes dans Prowlarr
- Ajoute is_integral_release() pour détecter "intégrale", "complet",
  "complete", "integral" dans les titres de torrents
- Les releases intégrales matchent tous les volumes manquants d'une série
- 4 tests unitaires (français, anglais, casse, faux positifs)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:05:38 +02:00
e34d7a671a refactor: Phase E — types de réponses API standardisés + SVGs inline → Icon
E1 - API responses:
- Crée responses.rs avec OkResponse, DeletedResponse, UpdatedResponse,
  RevokedResponse, UnlinkedResponse, StatusResponse (6 tests de sérialisation)
- Remplace ~15 json!() inline par des types structurés dans books, libraries,
  tokens, users, handlers, anilist, metadata, download_detection, torrent_import
- Signatures de retour des handlers typées (plus de serde_json::Value)

E2 - SVGs → Icon component:
- Ajoute icon "lock" au composant Icon
- Remplace ~30 SVGs inline par <Icon> dans 9 composants
  (FolderPicker, FolderBrowser, LiveSearchForm, JobRow, LibraryActions,
  ReadingStatusModal, EditBookForm, EditSeriesForm, UserSwitcher)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:02:39 +02:00
2670969d7e refactor: Phase D — composant Modal réutilisable + utilitaire searchParams
- Crée Modal.tsx dans components/ui (backdrop, container, header sticky, close button)
- Remplace le scaffolding modal dupliqué dans EditBookForm, EditSeriesForm,
  DeleteBookButton, MetadataSearchModal (4 composants)
- Crée lib/searchParams.ts avec paramString, paramStringOr, paramInt, paramBool
- Simplifie le parsing des query params dans books, series, authors pages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 12:23:50 +02:00
13b1e1768e refactor: Phase C — découpe scanner.rs et analyzer.rs en sous-fonctions
scanner.rs:
- Extrait should_skip_deletions() — logique pure de sécurité anti-suppression (testable)
- Extrait handle_stale_deletions() — gestion des fichiers disparus du disque
- Extrait upsert_directory_mtimes() — sauvegarde des mtimes pour scan incrémental
- 6 tests unitaires pour should_skip_deletions (volume démonté, DB vide, cas normal, etc.)

analyzer.rs:
- Extrait spawn_cancellation_poller() — polling d'annulation de job réutilisable

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 12:17:39 +02:00
4133d406e1 refactor: Phase B — fallback CBR factorisé, erreurs DB typées, logging silent errors
- Extrait is_not_rar_error() + open_cbr_listing() dans parsers, simplifie 4 fonctions CBR
- Harmonise extract_cbr_page pour vérifier l'erreur comme les 3 autres (était incohérent)
- Améliore From<sqlx::Error>: RowNotFound→404, PoolTimedOut→503, contraintes→400
- Remplace 5 let _ = par if let Err(e) avec warn! dans analyzer.rs (status/progress updates)
- 15 nouveaux tests (parsers: is_not_rar, detect_format, is_image_name; API: sqlx error mapping)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 12:08:10 +02:00
38a0f56328 refactor: Phase A — extraction des helpers partagés et micro-fixes
- Centralise remap_libraries_path/unmap_libraries_path dans crates/core/paths.rs
  (supprime 4 copies dupliquées dans API + indexer)
- Centralise mode_to_interval_minutes/validate_schedule_mode dans crates/core/schedule.rs
  (remplace 8 match blocks + 4 validations inline)
- Ajoute helpers env_or<T>/env_string_or dans config.rs, utilise ThumbnailConfig::default()
  comme base dans from_env() (élimine la duplication des valeurs par défaut)
- Supprime std::mem::take inutile dans books.rs
- Cible #[allow(dead_code)] sur le champ plutôt que le struct (metadata.rs)
- Remplace eprintln! par tracing::warn! dans parsers
- Fix clippy boolean logic bug dans prowlarr.rs

10 nouveaux tests unitaires (paths + schedule)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:54:03 +02:00
776ef679c2 fix: import torrent — reconnaître les volumes nus (- 07 -, 06.) et gérer les torrents stalledDL
- Ajout Pass 3 dans extract_volumes_from_title pour les patterns "Nom - 07 - Titre.cbz"
  et "06. nom.cbz" (nombre nu entre tirets ou en début de nom)
- Gestion des états qBittorrent stalledDL/pausedDL/error/missingFiles → marqués en erreur
  en DB et supprimés de qBittorrent
- Ajout de logs de debug pour l'import (fichiers trouvés, volumes extraits, dedup)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 11:33:03 +02:00
b8ed77f3f2 chore: bump version to 2.14.21
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 40s
2026-03-29 07:59:36 +02:00
42c4cdfdbd chore: bump version to 2.14.20
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 21s
2026-03-29 07:51:00 +02:00
9f3b48ed62 chore: bump version to 2.14.19
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 12s
2026-03-29 07:45:52 +02:00
10a508e610 feat: suppression de livres + import insensible aux accents
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 40s
- Ajout DELETE /books/:id : supprime le fichier physique, la thumbnail,
  le book en DB et queue un scan de la lib. Bouton avec confirmation
  sur la page de détail du livre.
- L'import torrent utilise unaccent() en SQL pour matcher les séries
  indépendamment des accents (ex: "les géants" = "les geants").
- Fallback filesystem avec strip_accents pour les séries sans livre en DB.
- Migration 0069: activation de l'extension PostgreSQL unaccent.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 19:10:06 +01:00
02f85a5d7b chore: bump version to 2.14.18 2026-03-28 19:09:37 +01:00
d8a7f381f9 fix: prioriser cbz > cbr > pdf > epub lors de l'import torrent
Quand un download contient le même volume en plusieurs formats,
on ne garde que le meilleur (cbz prioritaire, puis cbr, pdf, epub).
Les packs multi-volumes sont toujours conservés.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 18:44:06 +01:00
9b04a79330 feat: suppression individuelle de releases dans les available downloads
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 39s
Ajoute DELETE /available-downloads/:id?release=N pour supprimer une
release spécifique du JSON array (supprime l'entrée série si c'est
la dernière). Bouton trash sur chaque release dans la page downloads.

Corrige aussi le parsing des ranges de volumes sans préfixe sur le
second nombre (T17-23 détecte maintenant T17 à T23).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 17:51:09 +01:00
5e6217cc30 chore: bump version to 2.14.17 2026-03-28 17:50:41 +01:00
67d4ebade9 chore: bump version to 2.14.16
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 41s
2026-03-28 17:44:15 +01:00
002bcc0acf fix: désactiver le Router Cache client pour les pages dynamiques
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 42s
La navigation client-side (via Link) servait des pages cachées,
empêchant les recherches de fonctionner sans Ctrl+R.
staleTimes.dynamic: 0 force Next.js à toujours re-fetcher les pages
dynamiques lors de la navigation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 14:09:56 +01:00
5b3e4c6f8b chore: bump version to 2.14.15 2026-03-28 14:08:47 +01:00
354d24a7f6 feat: notification Telegram à la fin d'un import torrent
Ajoute les événements TorrentImportCompleted et TorrentImportFailed
au système de notifications. Une notif Telegram est envoyée après
l'import des fichiers dans la bibliothèque (succès ou erreur),
avec le nom de la série, la bibliothèque et le nombre de fichiers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 14:07:02 +01:00
da96e1ea0a fix: résoudre les URLs Prowlarr avant envoi à qBittorrent
qBittorrent ne suit pas les redirections 301 des URLs proxy Prowlarr.
L'API résout maintenant les redirections elle-même : si l'URL mène à un
magnet on le passe directement, si c'est un .torrent on l'uploade en
multipart. Ajoute aussi des logs sur tous les points d'échec du endpoint
/qbittorrent/add et un User-Agent "Stripstream-Librarian" sur les
appels Prowlarr.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 13:42:23 +01:00
9a3ab27886 chore: bump version to 2.14.14
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 40s
2026-03-28 13:33:08 +01:00
622fc98490 chore: bump version to 2.14.13 2026-03-28 13:32:51 +01:00
dc3b2467e1 chore: bump version to 2.14.12
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 43s
2026-03-28 13:26:45 +01:00
03e4fce5f9 feat: unifier la recherche livres via le endpoint /books avec paramètre q
La recherche utilise désormais le endpoint paginé /books avec un filtre
ILIKE sur title/series/author, ce qui permet la pagination des résultats.
Les series_hits sont toujours récupérés en parallèle via searchBooks.
Corrige aussi le remount du LiveSearchForm lors de la navigation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 13:23:51 +01:00
aa1a501adf chore: bump version to 2.14.11 2026-03-28 09:30:50 +01:00
ce10c3cbe4 chore: bump version to 2.14.10
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 42s
2026-03-28 08:53:16 +01:00