From 7c808e91672019fe8bd2e40ced73d3e90cae1041 Mon Sep 17 00:00:00 2001 From: Jos Vooges | STH Date: Tue, 1 Sep 2026 14:56:37 +0200 Subject: [PATCH] Fix library repair failing on playback session foreign keys. Delete stale playback sessions before removing unavailable or duplicate media file rows. Co-authored-by: Cursor --- apps/master-api/src/metadata/service.ts | 31 +++++++++++++++++++------ 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/apps/master-api/src/metadata/service.ts b/apps/master-api/src/metadata/service.ts index 39e7cbf..db34895 100644 --- a/apps/master-api/src/metadata/service.ts +++ b/apps/master-api/src/metadata/service.ts @@ -910,11 +910,7 @@ export class MetadataService { const seriesMerged = await this.mergeAllDuplicateSeries(); // Unavailable rows left after the /media/movies → /mnt/user migration - const staleFilesRemoved = ( - await prisma.mediaFile.deleteMany({ - where: { available: false }, - }) - ).count; + const staleFilesRemoved = await this.deleteUnavailableMediaFiles(); const duplicateFilesRemoved = await this.removeDuplicateMediaFiles(); @@ -1135,8 +1131,29 @@ export class MetadataService { } if (toDelete.length === 0) return 0; - const result = await prisma.mediaFile.deleteMany({ where: { id: { in: toDelete } } }); - return result.count; + return this.deleteMediaFilesSafely(toDelete); + } + + /** Remove playback sessions first — FK blocks direct media_file deletes. */ + private async deleteMediaFilesSafely(ids: string[]): Promise { + if (ids.length === 0) return 0; + const chunkSize = 500; + let deleted = 0; + for (let i = 0; i < ids.length; i += chunkSize) { + const chunk = ids.slice(i, i + chunkSize); + await prisma.playbackSession.deleteMany({ where: { mediaFileId: { in: chunk } } }); + const result = await prisma.mediaFile.deleteMany({ where: { id: { in: chunk } } }); + deleted += result.count; + } + return deleted; + } + + private async deleteUnavailableMediaFiles(): Promise { + const stale = await prisma.mediaFile.findMany({ + where: { available: false }, + select: { id: true }, + }); + return this.deleteMediaFilesSafely(stale.map((f) => f.id)); } /** Admin: search TMDB by title (or resolve a single IMDb id). */