feat(api,web): modélisation série + volume et parcours de série

- Table series + colonnes seriesId/volumeNumber/volumeLabel sur les livres,
  extraction souple des numéros (T03, 003, etc.) via extract-series-volume
- Contrôleur et page de série, badge volume sur les cartes de livre,
  styles associés (fond/clarté série, ajustements globaux)
- Statuts de scan/enrichissement et provenance des métadonnées en base

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Git Agent
2026-08-24 09:11:10 +02:00
parent 34159e5e9b
commit 93bb40a8cd
24 changed files with 1481 additions and 76 deletions

View File

@ -84,9 +84,21 @@ export const UpdateLibrarySchema = z.object({
});
export type UpdateLibraryDto = z.infer<typeof UpdateLibrarySchema>;
export const SeriesSchema = z.object({
id: z.number().int().positive(),
title: z.string(),
normalizedTitle: z.string(),
description: z.string().nullable(),
publisher: z.string().nullable(),
createdAt: z.string(),
updatedAt: z.string()
});
export type SeriesDto = z.infer<typeof SeriesSchema>;
export const BookSchema = z.object({
id: z.number().int().positive(),
libraryId: z.number().int().positive(),
seriesId: z.number().int().positive().nullable().optional(),
title: z.string(),
author: z.string().nullable(),
description: z.string().nullable(),
@ -95,9 +107,16 @@ export const BookSchema = z.object({
language: z.string().nullable(),
publisher: z.string().nullable(),
publishedDate: z.string().nullable(),
volumeNumber: z.number().int().nullable().optional(),
volumeLabel: z.string().nullable().optional(),
format: z.enum(["epub", "pdf", "cbz", "cbr"]),
filePath: z.string(),
coverPath: z.string().nullable(),
metadataStatus: z.enum(["enriched", "partial", "none"]).default("none"),
metadataProvenance: z.record(z.string(), z.string()).default({}),
series: SeriesSchema.nullable().optional(),
scanStatus: z.enum(["idle", "running", "succeeded", "failed"]).default("idle"),
enrichmentStatus: z.enum(["idle", "running", "succeeded", "failed"]).default("idle"),
fileSize: z.number().int().nonnegative(),
fileMtime: z.string(),
createdAt: z.string(),
@ -152,7 +171,7 @@ export const JobSchema = z.object({
});
export type JobDto = z.infer<typeof JobSchema>;
export const MetadataProviderIdSchema = z.enum(["local", "openlibrary", "googlebooks", "bnf"]);
export const MetadataProviderIdSchema = z.enum(["local", "openlibrary", "googlebooks", "bnf", "mangadex", "comicvine"]);
export type MetadataProviderId = z.infer<typeof MetadataProviderIdSchema>;
export const MetadataSourceConfigSchema = z.object({