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>
This commit is contained in:
@@ -95,8 +95,8 @@ pub async fn start_refresh(
|
||||
let link_count: i64 = sqlx::query_scalar(
|
||||
r#"
|
||||
SELECT COUNT(*) FROM external_metadata_links eml
|
||||
LEFT JOIN series_metadata sm
|
||||
ON sm.library_id = eml.library_id AND sm.name = eml.series_name
|
||||
LEFT JOIN series sm
|
||||
ON sm.library_id = eml.library_id AND sm.id = eml.series_id
|
||||
WHERE eml.library_id = $1
|
||||
AND eml.status = 'approved'
|
||||
AND COALESCE(sm.status, 'ongoing') NOT IN ('ended', 'cancelled')
|
||||
@@ -188,8 +188,8 @@ pub async fn start_refresh(
|
||||
let link_count: i64 = sqlx::query_scalar(
|
||||
r#"
|
||||
SELECT COUNT(*) FROM external_metadata_links eml
|
||||
LEFT JOIN series_metadata sm
|
||||
ON sm.library_id = eml.library_id AND sm.name = eml.series_name
|
||||
LEFT JOIN series sm
|
||||
ON sm.library_id = eml.library_id AND sm.id = eml.series_id
|
||||
WHERE eml.library_id = $1
|
||||
AND eml.status = 'approved'
|
||||
AND COALESCE(sm.status, 'ongoing') NOT IN ('ended', 'cancelled')
|
||||
@@ -357,14 +357,14 @@ pub(crate) async fn process_metadata_refresh(
|
||||
// Get approved links for this library, only for ongoing series (not ended/cancelled)
|
||||
let links: Vec<(Uuid, String, String, String)> = sqlx::query_as(
|
||||
r#"
|
||||
SELECT eml.id, eml.series_name, eml.provider, eml.external_id
|
||||
SELECT eml.id, sm.name AS series_name, eml.provider, eml.external_id
|
||||
FROM external_metadata_links eml
|
||||
LEFT JOIN series_metadata sm
|
||||
ON sm.library_id = eml.library_id AND sm.name = eml.series_name
|
||||
JOIN series sm
|
||||
ON sm.id = eml.series_id
|
||||
WHERE eml.library_id = $1
|
||||
AND eml.status = 'approved'
|
||||
AND COALESCE(sm.status, 'ongoing') NOT IN ('ended', 'cancelled')
|
||||
ORDER BY eml.series_name
|
||||
ORDER BY sm.name
|
||||
"#,
|
||||
)
|
||||
.bind(library_id)
|
||||
@@ -541,13 +541,14 @@ pub(crate) async fn refresh_link(
|
||||
// Pre-fetch local books
|
||||
let local_books: Vec<(Uuid, Option<i32>, String)> = sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, volume, title FROM books
|
||||
WHERE library_id = $1
|
||||
AND COALESCE(NULLIF(series, ''), 'unclassified') = $2
|
||||
ORDER BY volume NULLS LAST,
|
||||
REGEXP_REPLACE(LOWER(title), '[0-9].*$', ''),
|
||||
COALESCE((REGEXP_MATCH(LOWER(title), '\d+'))[1]::int, 0),
|
||||
title ASC
|
||||
SELECT b.id, b.volume, b.title FROM books b
|
||||
LEFT JOIN series s ON s.id = b.series_id
|
||||
WHERE b.library_id = $1
|
||||
AND COALESCE(s.name, 'unclassified') = $2
|
||||
ORDER BY b.volume NULLS LAST,
|
||||
REGEXP_REPLACE(LOWER(b.title), '[0-9].*$', ''),
|
||||
COALESCE((REGEXP_MATCH(LOWER(b.title), '\d+'))[1]::int, 0),
|
||||
b.title ASC
|
||||
"#,
|
||||
)
|
||||
.bind(library_id)
|
||||
@@ -741,7 +742,7 @@ async fn sync_series_with_diff(
|
||||
// Fetch existing series metadata for diffing
|
||||
let existing = sqlx::query(
|
||||
r#"SELECT description, publishers, start_year, total_volumes, status, authors, locked_fields
|
||||
FROM series_metadata WHERE library_id = $1 AND name = $2"#,
|
||||
FROM series WHERE library_id = $1 AND name = $2"#,
|
||||
)
|
||||
.bind(library_id)
|
||||
.bind(series_name)
|
||||
@@ -800,35 +801,35 @@ async fn sync_series_with_diff(
|
||||
// Now do the actual upsert
|
||||
sqlx::query(
|
||||
r#"
|
||||
INSERT INTO series_metadata (library_id, name, description, publishers, start_year, total_volumes, status, authors, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW(), NOW())
|
||||
INSERT INTO series (id, library_id, name, description, publishers, start_year, total_volumes, status, authors, created_at, updated_at)
|
||||
VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, NOW(), NOW())
|
||||
ON CONFLICT (library_id, name)
|
||||
DO UPDATE SET
|
||||
description = CASE
|
||||
WHEN (series_metadata.locked_fields->>'description')::boolean IS TRUE THEN series_metadata.description
|
||||
ELSE COALESCE(NULLIF(EXCLUDED.description, ''), series_metadata.description)
|
||||
WHEN (series.locked_fields->>'description')::boolean IS TRUE THEN series.description
|
||||
ELSE COALESCE(NULLIF(EXCLUDED.description, ''), series.description)
|
||||
END,
|
||||
publishers = CASE
|
||||
WHEN (series_metadata.locked_fields->>'publishers')::boolean IS TRUE THEN series_metadata.publishers
|
||||
WHEN (series.locked_fields->>'publishers')::boolean IS TRUE THEN series.publishers
|
||||
WHEN array_length(EXCLUDED.publishers, 1) > 0 THEN EXCLUDED.publishers
|
||||
ELSE series_metadata.publishers
|
||||
ELSE series.publishers
|
||||
END,
|
||||
start_year = CASE
|
||||
WHEN (series_metadata.locked_fields->>'start_year')::boolean IS TRUE THEN series_metadata.start_year
|
||||
ELSE COALESCE(EXCLUDED.start_year, series_metadata.start_year)
|
||||
WHEN (series.locked_fields->>'start_year')::boolean IS TRUE THEN series.start_year
|
||||
ELSE COALESCE(EXCLUDED.start_year, series.start_year)
|
||||
END,
|
||||
total_volumes = CASE
|
||||
WHEN (series_metadata.locked_fields->>'total_volumes')::boolean IS TRUE THEN series_metadata.total_volumes
|
||||
ELSE COALESCE(EXCLUDED.total_volumes, series_metadata.total_volumes)
|
||||
WHEN (series.locked_fields->>'total_volumes')::boolean IS TRUE THEN series.total_volumes
|
||||
ELSE COALESCE(EXCLUDED.total_volumes, series.total_volumes)
|
||||
END,
|
||||
status = CASE
|
||||
WHEN (series_metadata.locked_fields->>'status')::boolean IS TRUE THEN series_metadata.status
|
||||
ELSE COALESCE(EXCLUDED.status, series_metadata.status)
|
||||
WHEN (series.locked_fields->>'status')::boolean IS TRUE THEN series.status
|
||||
ELSE COALESCE(EXCLUDED.status, series.status)
|
||||
END,
|
||||
authors = CASE
|
||||
WHEN (series_metadata.locked_fields->>'authors')::boolean IS TRUE THEN series_metadata.authors
|
||||
WHEN (series.locked_fields->>'authors')::boolean IS TRUE THEN series.authors
|
||||
WHEN array_length(EXCLUDED.authors, 1) > 0 THEN EXCLUDED.authors
|
||||
ELSE series_metadata.authors
|
||||
ELSE series.authors
|
||||
END,
|
||||
updated_at = NOW()
|
||||
"#,
|
||||
@@ -967,7 +968,7 @@ pub async fn rematch_unlinked_books(pool: &PgPool, library_id: Uuid) -> Result<i
|
||||
FROM external_book_metadata ebm2
|
||||
JOIN external_metadata_links eml ON eml.id = ebm2.link_id
|
||||
JOIN books b ON b.library_id = eml.library_id
|
||||
AND LOWER(COALESCE(NULLIF(b.series, ''), 'unclassified')) = LOWER(eml.series_name)
|
||||
AND b.series_id = eml.series_id
|
||||
AND b.volume = ebm2.volume_number
|
||||
WHERE eml.library_id = $1
|
||||
AND ebm2.book_id IS NULL
|
||||
|
||||
Reference in New Issue
Block a user