From 8972679e2588b6e5cd3e9fd53b0223e3d1a6e56c Mon Sep 17 00:00:00 2001 From: MasutaYunikon Date: Mon, 11 May 2026 19:03:26 +0200 Subject: [PATCH] =?UTF-8?q?fix(import):=20les=20episodes=20avec=20une=20as?= =?UTF-8?q?sociation=20chang=C3=A9=20n'apparait=20plus=20sur=20la=20page?= =?UTF-8?q?=20serie?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/routes/import.ts | 13 +++++++++++++ src/components/ManualImportModal.vue | 26 ++++---------------------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/server/routes/import.ts b/server/routes/import.ts index e138619..29fe589 100644 --- a/server/routes/import.ts +++ b/server/routes/import.ts @@ -72,6 +72,19 @@ router.post('/manual-import', requireAuth, async (req, res) => { const destPath = path.join(destDir, destFilename) try { if (!fs.existsSync(file_path)) throw new Error('Fichier source introuvable') + + // Nettoyer toute entrée organized qui référence ce même fichier physique + // sous un épisode différent (réassignation → l'ancien enregistrement devient invalide) + for (const [h, eps] of Object.entries(organized)) { + for (const [epId, entry] of Object.entries(eps)) { + if (entry?.dest_path === file_path && Number(epId) !== Number(episode_id)) { + delete (organized[h] as any)[epId] + logger.info('api', `Import manuel : suppression entrée stale ep ${epId} (fichier réassigné)`) + } + } + if (Object.keys(organized[h]).length === 0) delete organized[h] + } + fs.mkdirSync(destDir, { recursive: true }) if (file_path !== destPath) { const serieRootPath = path.join(mediaPath, serieTitle) diff --git a/src/components/ManualImportModal.vue b/src/components/ManualImportModal.vue index 3556085..a9c762c 100644 --- a/src/components/ManualImportModal.vue +++ b/src/components/ManualImportModal.vue @@ -187,7 +187,7 @@ const serieFolder = computed(() => `${props.initialPath}/${serieTitle.value}`) interface FileItem { file : { name: string; path: string; size: number } episode_id : number | null - previousEpisodeId : number | null // ep_id original si alreadyImported et qu'on le réassigne + originalEpisodeId : number | null // ep_id au moment du scan (immuable) — sert au serveur pour cleanup hash : string | null alreadyImported : boolean willRename : boolean @@ -268,12 +268,7 @@ function formatSize(bytes: number): string { function onEpisodeChange(i: number) { const item = items.value[i] - const newEpId = item.episode_id - - // Tracker l'ancien épisode pour pouvoir nettoyer l'entrée organized au moment de l'import - if (item.alreadyImported && item.episode_id !== newEpId) { - item.previousEpisodeId = item.episode_id - } + const newEpId = item.episode_id // v-model a déjà mis à jour la valeur item.hash = getHashForEpisode(Number(newEpId)) item.alreadyImported = false @@ -310,11 +305,11 @@ async function scan() { // Vérifier si déjà importé via dest_path const existingEpId = organizedByPath.get(f.path) ?? null if (existingEpId) { - return { file: f, episode_id: existingEpId, previousEpisodeId: null, hash: getHashForEpisode(existingEpId), alreadyImported: true, willRename: false } + return { file: f, episode_id: existingEpId, originalEpisodeId: existingEpId, hash: getHashForEpisode(existingEpId), alreadyImported: true, willRename: false } } const match = autoMatch(f.name) - return { file: f, ...match, previousEpisodeId: null, alreadyImported: false, willRename: match.episode_id !== null } + return { file: f, ...match, originalEpisodeId: null, alreadyImported: false, willRename: match.episode_id !== null } }) } catch { scanError.value = 'Impossible de contacter le serveur' @@ -337,19 +332,6 @@ async function doImport() { return } - // Nettoyer les anciens épisodes réassignés (fichier était "alreadyImported" ep A, - // l'utilisateur l'a changé en ep B → supprimer l'entrée organized de ep A sans toucher au fichier) - const toUnimport = items.value - .filter(i => i.previousEpisodeId !== null && i.episode_id !== i.previousEpisodeId) - .map(i => i.previousEpisodeId as number) - for (const oldEpId of toUnimport) { - try { - await fetch(`/api/organized/${props.serieId}/${oldEpId}?deleteFile=false`, { - method: 'DELETE', credentials: 'include', - }) - } catch {} - } - try { const res = await fetch('/api/manual-import', { method : 'POST',