diff --git a/app/api/songs/route.ts b/app/api/songs/route.ts index 8604f72..831b35a 100644 --- a/app/api/songs/route.ts +++ b/app/api/songs/route.ts @@ -469,51 +469,55 @@ export async function PUT(request: Request) { }; } - // Handle SpecialSong relations separately - if (effectiveSpecialIds !== undefined) { - // First, get current special assignments - const currentSpecials = await prisma.specialSong.findMany({ - where: { songId: Number(id) } - }); - - const currentSpecialIds = currentSpecials.map(ss => ss.specialId); - const newSpecialIds = effectiveSpecialIds as number[]; - - // Delete removed specials - const toDelete = currentSpecialIds.filter(sid => !newSpecialIds.includes(sid)); - if (toDelete.length > 0) { - await prisma.specialSong.deleteMany({ - where: { - songId: Number(id), - specialId: { in: toDelete } - } + // Execute all database write operations in a transaction to ensure consistency + const updatedSong = await prisma.$transaction(async (tx) => { + // Handle SpecialSong relations separately + if (effectiveSpecialIds !== undefined) { + // First, get current special assignments (within transaction) + const currentSpecials = await tx.specialSong.findMany({ + where: { songId: Number(id) } }); - } - // Add new specials - const toAdd = newSpecialIds.filter(sid => !currentSpecialIds.includes(sid)); - if (toAdd.length > 0) { - await prisma.specialSong.createMany({ - data: toAdd.map(specialId => ({ - songId: Number(id), - specialId, - startTime: 0 - })) - }); - } - } + const currentSpecialIds = currentSpecials.map(ss => ss.specialId); + const newSpecialIds = effectiveSpecialIds as number[]; - const updatedSong = await prisma.song.update({ - where: { id: Number(id) }, - data, - include: { - genres: true, - specials: { - include: { - special: true - } + // Delete removed specials + const toDelete = currentSpecialIds.filter(sid => !newSpecialIds.includes(sid)); + if (toDelete.length > 0) { + await tx.specialSong.deleteMany({ + where: { + songId: Number(id), + specialId: { in: toDelete } + } + }); + } + + // Add new specials + const toAdd = newSpecialIds.filter(sid => !currentSpecialIds.includes(sid)); + if (toAdd.length > 0) { + await tx.specialSong.createMany({ + data: toAdd.map(specialId => ({ + songId: Number(id), + specialId, + startTime: 0 + })) + }); } } + + // Update song (this also handles genre relations via Prisma's set operation) + return await tx.song.update({ + where: { id: Number(id) }, + data, + include: { + genres: true, + specials: { + include: { + special: true + } + } + } + }); }); return NextResponse.json(updatedSong); @@ -559,7 +563,7 @@ export async function DELETE(request: Request) { } } - // Delete file + // Delete files first (outside transaction, as file system operations can't be rolled back) const filePath = path.join(process.cwd(), 'public/uploads', song.filename); try { await unlink(filePath); @@ -578,9 +582,11 @@ export async function DELETE(request: Request) { } } - // Delete from database (will cascade delete related puzzles) - await prisma.song.delete({ - where: { id: Number(id) }, + // Delete from database in transaction (will cascade delete related puzzles, SpecialSong, etc.) + await prisma.$transaction(async (tx) => { + await tx.song.delete({ + where: { id: Number(id) }, + }); }); return NextResponse.json({ success: true });