Files
hoerdle/app/api/curator/specials/[id]/route.ts
Hördle Bot c83dc7a5e5 Fix: Cache-Control-Header für Waveform-Editor API-Route hinzugefügt
- API-Route sendet jetzt explizite No-Cache-Header
- Frontend-Fetch verwendet cache: 'no-store'
- Behebt Problem, dass neu hochgeladene Dateien erst nach Container-Neustart bearbeitbar waren
2025-12-07 13:03:43 +01:00

98 lines
3.1 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';
import { requireStaffAuth } from '@/lib/auth';
import { access } from 'fs/promises';
import path from 'path';
const prisma = new PrismaClient();
// Mark route as dynamic to prevent caching
export const dynamic = 'force-dynamic';
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const { error, context } = await requireStaffAuth(request);
if (error || !context) return error!;
if (context.role !== 'curator') {
return NextResponse.json(
{ error: 'Only curators can access this endpoint' },
{ status: 403 }
);
}
const { id } = await params;
const specialId = Number(id);
if (!specialId || Number.isNaN(specialId)) {
return NextResponse.json({ error: 'Invalid special id' }, { status: 400 });
}
// Prüfen, ob dieses Special dem Kurator zugeordnet ist
const assignment = await prisma.curatorSpecial.findFirst({
where: { curatorId: context.curator.id, specialId },
});
if (!assignment) {
return NextResponse.json(
{ error: 'Forbidden: You are not allowed to access this special' },
{ status: 403 }
);
}
const special = await prisma.special.findUnique({
where: { id: specialId },
include: {
songs: {
include: {
song: true,
},
orderBy: { order: 'asc' },
},
},
});
if (!special) {
return NextResponse.json({ error: 'Special not found' }, { status: 404 });
}
// Filtere Songs ohne vollständige Song-Daten und prüfe Datei-Existenz
// Dies verhindert Fehler im Frontend, wenn Songs gelöscht wurden, Daten fehlen
// oder Dateien noch nicht im Container verfügbar sind (Volume Mount Delay)
const uploadsDir = path.join(process.cwd(), 'public/uploads');
const filteredSongs = await Promise.all(
special.songs
.filter(ss => ss.song && ss.song.filename)
.map(async (ss) => {
const filePath = path.join(uploadsDir, ss.song.filename);
try {
// Prüfe ob Datei existiert und zugänglich ist
await access(filePath);
return ss;
} catch (error) {
// Datei existiert nicht oder ist nicht zugänglich
console.warn(`[API] Song file not available: ${ss.song.filename} (may be syncing)`);
return null;
}
})
);
// Entferne null-Werte (Songs ohne verfügbare Dateien)
const availableSongs = filteredSongs.filter((ss): ss is typeof special.songs[0] => ss !== null);
return NextResponse.json({
...special,
songs: availableSongs,
}, {
headers: {
'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
},
});
}