Commit Graph

138 Commits

Author SHA1 Message Date
a99bfb5a91 perf(docker): optimize Rust build times with dependency layer caching
Replace sccache with a two-stage build strategy: dummy source files cache
dependency compilation in a separate layer, and --mount=type=cache for
Cargo registry/git/target directories. Source-only changes now skip
full dependency recompilation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 11:06:11 +01:00
389d71b42f refactor: replace Meilisearch with PostgreSQL full-text search
Remove Meilisearch dependency entirely. Search is now handled by
PostgreSQL ILIKE with pg_trgm indexes, joining series_metadata for
series-level authors. No external search engine needed.

- Replace search.rs Meilisearch HTTP calls with PostgreSQL queries
- Remove meili.rs from indexer, sync_meili call from job pipeline
- Remove MEILI_URL/MEILI_MASTER_KEY from config, state, env files
- Remove meilisearch service from docker-compose.yml
- Add migration 0027: drop sync_metadata, enable pg_trgm, add indexes
- Remove search resync button/endpoint (no longer needed)
- Update all documentation (CLAUDE.md, README.md, AGENTS.md, PLAN.md)

API contract unchanged — same SearchResponse shape returned.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 10:59:25 +01:00
2985ef5561 chore: bump version to 1.4.0 2026-03-18 10:59:12 +01:00
4be8177683 feat: fix author search, add edit modals, settings tabs & search resync
- Fix Meilisearch indexing to use authors[] array instead of scalar author field
- Join series_metadata to include series-level authors in search documents
- Configure searchable attributes (title, authors, series) in Meilisearch
- Convert EditSeriesForm and EditBookForm from inline forms to modals
- Add tabbed navigation (General / Integrations) to Settings page
- Add Force Search Resync button (POST /settings/search/resync)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 10:45:36 +01:00
a675dcd2a4 chore: bump version to 1.3.0 2026-03-18 10:45:16 +01:00
127cd8a42c feat(komga): add Komga read-status sync with reports and history
Adds Komga sync feature to import read status from a Komga server.
Books are matched by title (case-insensitive) with series+title primary
match and title-only fallback. Sync reports are persisted with matched,
newly marked, and unmatched book lists. UI shows check icon for newly
marked books, sorted to top. Credentials (URL+username) are saved
between sessions. Uses HashSet for O(1) lookups to handle large libraries.

Closes #2

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 22:04:19 +01:00
1b9f2d3915 chore: bump version to 1.2.2 2026-03-16 22:04:04 +01:00
f095bf050b chore: bump version to 1.2.1 2026-03-16 21:12:25 +01:00
b17718df9b fix(stats): count authors from both author and authors fields
The total_authors stat now combines distinct values from the legacy
author column and the new authors array column.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 21:11:58 +01:00
5c3ddf7819 chore: bump version to 1.2.0 2026-03-16 19:29:43 +01:00
c56d02a895 chore: bump version to 1.1.0 2026-03-16 19:29:24 +01:00
bc98067871 feat(books): édition des métadonnées livres et séries + champ authors multi-valeurs
- Nouveaux endpoints PATCH /books/:id et PATCH /libraries/:id/series/:name pour éditer les métadonnées
- GET /libraries/:id/series/:name/metadata pour récupérer les métadonnées de série
- Ajout du champ `authors` (Vec<String>) sur les structs Book/BookDetails
- 3 migrations : table series_metadata, colonne authors sur series_metadata et books
- Composants EditBookForm et EditSeriesForm dans le backoffice
- Routes API Next.js correspondantes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 17:21:55 +01:00
a085924f8a fix(books): correction du tri naturel des titres avec sous-titres variables
Remplace REGEXP_REPLACE(title, '[0-9]+', '', 'g') par
REGEXP_REPLACE(title, '[0-9].*$', '') pour n'extraire que le préfixe
avant le premier chiffre.

L'ancienne regex supprimait TOUS les chiffres du titre entier, y compris
dans les sous-titres. Quand chaque volume a un sous-titre différent
(ex: "Deadpool marvel deluxe 3 - Je suis ton homme"), la partie texte
restante variait → l'ordre alphabétique des sous-titres prenait le dessus.

Avec le nouveau préfixe "deadpool marvel deluxe " identique pour tous,
le numéro de volume (2ème clé) dicte correctement l'ordre.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:16:00 +01:00
9fbdf793d0 chore: bump version to 1.0.1 2026-03-16 12:08:15 +01:00
b14accbbe0 fix(books): tri des séries par volume + suppression de l'ancienne extract_page
- Ajout de `b.volume NULLS LAST` comme première clé de tri dans list_books
  et dans tous les ROW_NUMBER() OVER (...) des CTEs series, pour corriger
  l'ordre des volumes dont les titres varient en format (ex: "Round" vs "R")
- Suppression de l'ancienne extract_page publique et de ses 4 helpers
  (extract_cbz_page_n, extract_cbz_page_n_streaming, extract_cbr_page_n,
  extract_pdf_page_n) remplacés par la nouvelle implémentation avec cache
- Suppression de archive_index_cache dans AppState (remplacé par le cache
  statique CBZ_INDEX_CACHE dans parsers), import StdMutex nettoyé

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 12:08:03 +01:00
330239d2c3 feat(api): log info par requête HTTP (méthode, path, status, durée)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 08:09:32 +01:00
bf5a20882b perf(pages): cache de l'index d'archive en mémoire (-73% CBZ, -76% CBR cold)
Chaque cold render ré-énumérait toutes les entrées ZIP/RAR pour construire
la liste triée des images. Maintenant la liste est mise en cache dans l'AppState
(LruCache<String, Arc<Vec<String>>>, std::sync::Mutex pour accès spawn_blocking).

Nouvelles fonctions dans parsers :
- list_archive_images(path, format) -> Vec<String>
- extract_image_by_name(path, format, name) -> Vec<u8>

Mesures avant/après (cache disque froid, n=20) :
- CBZ cold : 43ms → 11.9ms (-73%)
- CBR cold : 46ms → 11.0ms (-76%)
- Warm/concurrent : identique

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 08:09:32 +01:00
44c6dd626a feat(backoffice): afficher le format (cbz/cbr/pdf) au lieu du kind sur les cards
- Ajoute `format: string | null` dans BookDto
- BookCard et page détail utilisent `book.format ?? book.kind` avec les couleurs
  success=CBZ, warning=CBR, destructive=PDF

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 08:09:26 +01:00
9153b0c750 refactor(pages): déléguer l'extraction de pages au crate parsers
- Expose `extract_page(path, format, page_number, render_width)` dans parsers
- Rend `is_image_name` publique, ajoute gif/bmp/tif/tiff
- Supprime ~250 lignes dupliquées dans pages.rs (CBZ/CBR/PDF extract)
- Retire zip/unrar/pdfium-render/natord de api, remplacé par parsers

Perf avant/après : stable (±5%, dans le bruit de mesure).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-16 08:09:26 +01:00
e18bbba4ce feat: add sort parameter (title/latest) to books and series endpoints
Add sort=latest option to GET /books and GET /series API endpoints,
and expose a Sort select in the backoffice books and series pages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 21:46:37 +01:00
2870dd9dbc chore: bump version to 1.0.0 2026-03-15 18:38:01 +01:00
cf2e7a0be7 feat(backoffice): add dashboard statistics with charts
Add GET /stats API endpoint with collection overview, reading status,
format/library breakdowns, top series, and monthly additions.
Replace static home page with interactive dashboard featuring donut
charts, bar charts, and progress bars. Use distinct colors for series
(warning/yellow) across nav, page titles, and quick links.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 18:37:53 +01:00
82444cda02 chore: bump version to 0.3.0 2026-03-15 18:17:27 +01:00
1d25c8869f feat(backoffice): add reading progress management, series page, and live search
- API: add POST /series/mark-read to batch mark all books in a series
- API: add GET /series cross-library endpoint with search, library and status filters
- API: add library_id to SeriesItem response
- Backoffice: mark book as read/unread button on book detail page
- Backoffice: mark series as read/unread button on series cards
- Backoffice: new /series top-level page with search and filters
- Backoffice: new /libraries/[id]/series/[name] series detail page
- Backoffice: opacity on fully read books and series cards
- Backoffice: live search with debounce on books and series pages
- Backoffice: reading status filter on books and series pages
- Fix $2 -> $1 parameter binding in mark-series-read SQL

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 18:17:16 +01:00
fd277602c9 feat(api): add GET /series/ongoing and GET /books/ongoing endpoints
Two new read routes for the home screen:
- /series/ongoing: partially read series sorted by last activity
- /books/ongoing: next unread book per ongoing series

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 16:24:05 +01:00
673777bc8d chore: bump version to 0.2.0 2026-03-15 16:23:09 +01:00
03af82d065 feat(tokens): allow permanent deletion of revoked tokens
Add POST /admin/tokens/{id}/delete endpoint that permanently removes
a token from the database (only if already revoked). Add delete button
in backoffice UI for revoked tokens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 15:19:44 +01:00
78e28a269d chore: bump version to 0.1.5 2026-03-15 15:17:16 +01:00
ee05df26c4 fix(indexer): corriger OOM lors du full rebuild (batching + limite threads)
- Extraction par batches de 200 livres (libère mémoire entre chaque batch)
- Limiter tokio spawn_blocking à 8 threads (défaut 512, chaque thread ~8MB stack)
- Réduire concurrence extraction de 8 à 2 max
- Supprimer raw_bytes.clone() inutile (passage par ownership)
- Ajouter log RSS entre chaque batch pour diagnostic mémoire

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 13:34:14 +01:00
96d9efdeed chore: bump version to 0.1.4 2026-03-15 13:20:41 +01:00
9f5183848b chore: bump version to 0.1.3 2026-03-15 13:09:53 +01:00
6f9dd108ef chore: bump version to 0.1.2 2026-03-15 13:06:36 +01:00
61bc307715 perf(parsers): optimiser listing CBZ avec file_names(), ajouter magic bytes check RAR
- Remplacer by_index() par file_names() pour lister les pages ZIP (zero I/O)
- Ajouter vérification magic bytes avant fallback RAR
- Ajouter tracing debug logs dans parsers
- Script docker-push avec version bump interactif

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 13:01:04 +01:00
c7f3ad981d chore: bump version to 0.1.1 2026-03-15 12:51:54 +01:00
0d60d46cae feat(indexer,backoffice): logs par domaine, réduction fd, UI mobile
- Ajout de targets de log par domaine (scan, extraction, thumbnail, watcher)
  contrôlables via RUST_LOG pour activer/désactiver les logs granulaires
- Ajout de logs détaillés dans extracting_pages (per-book timing en debug,
  progression toutes les 25 books en info)
- Réduction de la consommation de fd: walkdir max_open(20/10), comptage
  séquentiel au lieu de par_iter parallèle, suppression de rayon
- Détection ENFILE dans le scanner: abort après 10 erreurs IO consécutives
- Backoffice: settings dans le burger mobile, masquer "backoffice" et
  icône settings en mobile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 11:57:49 +01:00
6947af10fe perf(api,indexer): optimiser pages, thumbnails, watcher et robustesse fd
- Pages: mode Original (zero-transcoding), ETag/304, cache index CBZ,
  préfetch next 2 pages, filtre Triangle par défaut
- Thumbnails: DCT scaling JPEG via jpeg-decoder (decode 7x plus rapide),
  img.thumbnail() pour resize, support format Original, fix JPEG RGBA8
- API fallback thumbnail: OutputFormat::Original + DCT scaling au lieu
  de WebP full-decode, retour (bytes, content_type) dynamique
- Watcher: remplacement notify par poll léger sans inotify/fd,
  skip poll quand job actif, snapshots en mémoire
- Jobs: mutex exclusif corrigé (tous statuts actifs, tous types exclusifs)
- Robustesse: suppression fs::canonicalize (problèmes fd Docker),
  list_folders avec erreurs explicites, has_children default true
- Backoffice: FormRow items-start pour alignement inputs avec helper text,
  labels settings clarifiés

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 23:07:42 +01:00
fe54f55f47 feat(indexer,backoffice): ajouter warnings dans les stats de job, skip fichiers inaccessibles
- Indexer: ajout du champ `warnings` dans JobStats pour les erreurs
  non-fatales (fichiers inaccessibles, permissions)
- Indexer: skip les fichiers dont le stat échoue au lieu de faire
  crasher tout le scan de la library
- Backoffice: affichage des warnings dans le détail job (summary,
  timeline, Index Statistics) et dans la popin jobs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 13:44:48 +01:00
f71ca92e85 chore: corriger whitespace et paths dans .env.example
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 13:26:42 +01:00
7cca7e40c2 fix(parsers,api,indexer,backoffice): corriger CBZ Unicode extra fields, centraliser extraction, nettoyer Meili, fixer header
- Parsers: raw ZIP reader (flate2) contournant la validation CRC32 des
  Unicode extra fields (0x7075) qui bloquait certains CBZ
- Parsers: nouvelle API publique extract_page() pour extraire une page
  par index depuis CBZ/CBR/PDF avec fallbacks automatiques
- API: suppression du code d'extraction dupliqué, délégation à parsers::extract_page()
- API: retrait des dépendances directes zip/unrar/pdfium-render/natord
- Indexer: nettoyage Meili systématique à chaque sync (au lieu de ~10%)
  avec pagination pour supporter les grosses collections — corrige les
  doublons dans la recherche
- Indexer: retrait de la dépendance rand (plus utilisée)
- Backoffice: popin jobs rendue via createPortal avec positionnement
  dynamique — corrige le débordement desktop et le header cassé en mobile

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 13:26:14 +01:00
5db2a7501b feat(books): ajouter le champ format en base et l'exposer dans l'API
- Migration 0020 : colonne format sur books, backfill depuis book_files
- batch.rs / scanner.rs : l'indexer écrit le format dans books
- books.rs : format dans BookItem + filtre ?format= dans list_books
- perf_pages.sh : benchmarks par format CBZ/CBR/PDF

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 08:55:18 +01:00
85e0945c9d fix(parsers,api): skipper les entrées ZIP corrompues au lieu d'échouer
Une seule entrée illisible dans le central directory ne doit pas bloquer
l'analyse de tout le livre. Le count et la première page lisible sont
retournés même si certaines entrées sont endommagées.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 08:38:38 +01:00
efc2773199 chore(deps): mettre à jour zip 2.4→8.2, notify 6.1→8.2, lopdf 0.35→0.39
- zip 8.x résout nativement les extra fields NTFS (source du bug EOCD)
- notify 8.x améliore le support inotify Linux
- lopdf 0.39 contient des correctifs de parsing PDF

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 23:30:14 +01:00
1d9a1c76d2 fix(parsers,api): fallback streaming ZIP pour archives avec extra fields NTFS
Les ZIP créés par des outils Windows (version 6.3) contiennent des extra
fields NTFS (tag 0x000A) qui font échouer ZipArchive::new() avec "Could
not find EOCD". Ajout d'un fallback via read_zipfile_from_stream qui lit
les local file headers sans dépendre du central directory.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 23:24:36 +01:00
3e3e0154fa fix(parsers): corriger récursion infinie CBZ↔CBR causant un stack overflow
analyze_cbz et analyze_cbr se rappelaient mutuellement sans garde quand
un fichier échouait les deux formats → stack overflow à l'analyse.
Ajout d'un paramètre allow_fallback=false pour briser la boucle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 23:15:35 +01:00
e73498cc60 fix(docker): retirer sysctls inotify non supportés par ce kernel
Les sysctls fs.inotify.* ne sont pas namespacés sur ce kernel.
La configuration doit se faire sur l'hôte via /etc/sysctl.conf.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 23:10:14 +01:00
0f4025369c fix(docker): retirer fs.inotify.max_user_instances non namespacé
Ce sysctl n'est pas dans un namespace kernel séparé et provoque une
erreur OCI au démarrage du container. Seul max_user_watches est conservé.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 23:09:31 +01:00
7d3670e951 fix(api/pages): fallback CBR→ZIP et CBZ→RAR pour archives mal extensionnées
Même correctif que dans le parsers/indexer : un .cbr qui est en réalité
un ZIP (et vice-versa) retourne maintenant la bonne page au lieu d'un 500.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 23:06:40 +01:00
09682f5836 fix(docker): augmenter les limites inotify pour éviter "Too many open files"
Ajoute les sysctls fs.inotify.max_user_watches=524288 et
fs.inotify.max_user_instances=512 sur le service indexer pour
prévenir l'erreur watcher sur les grosses bibliothèques.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 23:04:27 +01:00
db11c62d2f fix(analyzer): timeout sur analyze_book pour éviter les blocages indefinis
Un fichier corrompu (RAR/ZIP/PDF qui ne répond plus) occupait un slot
de concurrence indéfiniment, bloquant le pipeline à ex. 1517/1521.

- Ajoute tokio::time::timeout autour de spawn_blocking(analyze_book)
- Timeout lu depuis limits.timeout_seconds en DB (défaut 120s)
- Le livre est marqué parse_status='error' en cas de timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:44:48 +01:00
7346f1d5b7 fix(parsers): fallback CBR pour les .cbz qui sont en réalité des archives RAR
Symétrique au fallback CBZ→RAR déjà existant dans analyze_cbr.
Détecte les fichiers .cbz avec magic bytes RAR et les traite via le parser unrar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 22:29:47 +01:00