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>
- 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>
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>
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>
- 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>
- 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>
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>
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>
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>
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>
- 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>
- 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>
- 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>
- 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>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
Supprime les entrées available_downloads dont le series_name ne
correspond plus à aucune série existante dans books, au début de
chaque job de détection de téléchargements.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Le tri par date d'ajout utilisait updated_at qui est écrasé à chaque
rescan de l'indexer, empêchant les livres/séries récemment ajoutés
de remonter en premier. Utilise maintenant created_at qui reflète
la vraie date d'ajout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Modale Prowlarr (page série) : remplacé le bouton qBittorrent brut
par QbittorrentDownloadButton avec suivi managé (libraryId,
seriesName, expectedVolumes) et bouton "télécharger et remplacer".
- Ajout de alwaysShowReplace pour la modale Prowlarr (toujours montrer
le bouton remplacer) vs la page downloads (seulement si allVolumes >
expectedVolumes).
- Fix parseur : les tags de version entre crochets [V2], [V3] ne sont
plus extraits comme volumes (le préfixe "v" est ignoré après "[").
- Progression qBittorrent : utilise directement le champ progress
(completed et amount_left sont non-fiables sur qBittorrent 4.3.2).
- Référence import : ne plus exclure les volumes attendus de la
recherche de référence (corrige le mauvais dossier/nommage quand
tous les volumes sont dans expected_volumes).
- allVolumes ajouté à ProwlarrRelease (backend + frontend).
- flex-wrap sur les pastilles volumes dans la modale Prowlarr.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>