fix: re-matching automatique des métadonnées externes après scan/import
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>
This commit is contained in:
@@ -643,6 +643,10 @@ pub(crate) async fn refresh_link(
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// Re-match any external books that couldn't be matched during insert
|
||||
// (e.g., metadata was fetched before books were imported)
|
||||
let _ = rematch_unlinked_books(pool, library_id).await;
|
||||
|
||||
let has_changes = !series_changes.is_empty() || !book_changes.is_empty();
|
||||
|
||||
Ok(SeriesRefreshResult {
|
||||
@@ -947,3 +951,40 @@ async fn sync_book_with_diff(
|
||||
|
||||
Ok(diffs)
|
||||
}
|
||||
|
||||
/// Re-match external_book_metadata rows that have book_id IS NULL
|
||||
/// by joining on volume number with local books in the same series.
|
||||
/// Called after scans/imports to fix metadata that was fetched before books existed.
|
||||
pub async fn rematch_unlinked_books(pool: &PgPool, library_id: Uuid) -> Result<i64, String> {
|
||||
let result = sqlx::query(
|
||||
r#"
|
||||
UPDATE external_book_metadata ebm
|
||||
SET book_id = matched.book_id
|
||||
FROM (
|
||||
SELECT DISTINCT ON (ebm2.id)
|
||||
ebm2.id AS ebm_id,
|
||||
b.id AS book_id
|
||||
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.volume = ebm2.volume_number
|
||||
WHERE eml.library_id = $1
|
||||
AND ebm2.book_id IS NULL
|
||||
AND ebm2.volume_number IS NOT NULL
|
||||
AND eml.status = 'approved'
|
||||
) matched
|
||||
WHERE ebm.id = matched.ebm_id
|
||||
"#,
|
||||
)
|
||||
.bind(library_id)
|
||||
.execute(pool)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let count = result.rows_affected() as i64;
|
||||
if count > 0 {
|
||||
info!("[METADATA] Re-matched {count} unlinked external books for library {library_id}");
|
||||
}
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
@@ -1096,7 +1096,7 @@ pub async fn update_series(
|
||||
)]
|
||||
pub async fn delete_series(
|
||||
State(state): State<AppState>,
|
||||
Extension(user): Extension<AuthUser>,
|
||||
Extension(_user): Extension<AuthUser>,
|
||||
Path((library_id, name)): Path<(Uuid, String)>,
|
||||
) -> Result<Json<crate::responses::DeletedResponse>, ApiError> {
|
||||
use stripstream_core::paths::remap_libraries_path;
|
||||
|
||||
Reference in New Issue
Block a user