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>
This commit is contained in:
2026-03-29 22:51:00 +02:00
parent 292e9bc77f
commit ccc7f375f6
38 changed files with 463 additions and 286 deletions

View File

@@ -1,13 +1,13 @@
import { NextResponse, NextRequest } from "next/server";
import { apiFetch } from "@/lib/api";
type Params = Promise<{ libraryId: string; seriesName: string }>;
type Params = Promise<{ libraryId: string; seriesId: string }>;
export async function GET(request: NextRequest, { params }: { params: Params }) {
try {
const { libraryId, seriesName } = await params;
const { libraryId, seriesId } = await params;
const data = await apiFetch(
`/anilist/series/${libraryId}/${encodeURIComponent(seriesName)}`,
`/anilist/series/${libraryId}/${seriesId}`,
);
return NextResponse.json(data);
} catch (error) {
@@ -18,10 +18,10 @@ export async function GET(request: NextRequest, { params }: { params: Params })
export async function POST(request: NextRequest, { params }: { params: Params }) {
try {
const { libraryId, seriesName } = await params;
const { libraryId, seriesId } = await params;
const body = await request.json();
const data = await apiFetch(
`/anilist/series/${libraryId}/${encodeURIComponent(seriesName)}/link`,
`/anilist/series/${libraryId}/${seriesId}/link`,
{ method: "POST", body: JSON.stringify(body) },
);
return NextResponse.json(data);
@@ -33,9 +33,9 @@ export async function POST(request: NextRequest, { params }: { params: Params })
export async function DELETE(request: NextRequest, { params }: { params: Params }) {
try {
const { libraryId, seriesName } = await params;
const { libraryId, seriesId } = await params;
const data = await apiFetch(
`/anilist/series/${libraryId}/${encodeURIComponent(seriesName)}/unlink`,
`/anilist/series/${libraryId}/${seriesId}/unlink`,
{ method: "DELETE" },
);
return NextResponse.json(data);

View File

@@ -1,17 +0,0 @@
import { NextRequest, NextResponse } from "next/server";
import { updateSeries } from "@/lib/api";
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string; name: string }> }
) {
const { id, name } = await params;
try {
const body = await request.json();
const data = await updateSeries(id, name, body);
return NextResponse.json(data);
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to update series";
return NextResponse.json({ error: message }, { status: 500 });
}
}

View File

@@ -3,11 +3,11 @@ import { fetchSeriesMetadata } from "@/lib/api";
export async function GET(
_request: NextRequest,
{ params }: { params: Promise<{ id: string; name: string }> }
{ params }: { params: Promise<{ id: string; seriesId: string }> }
) {
const { id, name } = await params;
const { id, seriesId } = await params;
try {
const data = await fetchSeriesMetadata(id, name);
const data = await fetchSeriesMetadata(id, seriesId);
return NextResponse.json(data);
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to fetch series metadata";

View File

@@ -0,0 +1,31 @@
import { NextRequest, NextResponse } from "next/server";
import { updateSeries, deleteSeries } from "@/lib/api";
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string; seriesId: string }> }
) {
const { id, seriesId } = await params;
try {
const body = await request.json();
const data = await updateSeries(id, seriesId, body);
return NextResponse.json(data);
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to update series";
return NextResponse.json({ error: message }, { status: 500 });
}
}
export async function DELETE(
_request: NextRequest,
{ params }: { params: Promise<{ id: string; seriesId: string }> }
) {
const { id, seriesId } = await params;
try {
await deleteSeries(id, seriesId);
return NextResponse.json({ deleted: true });
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to delete series";
return NextResponse.json({ error: message }, { status: 500 });
}
}