diff --git a/app/api/curator-comments/[id]/archive/route.ts b/app/api/curator-comments/[id]/archive/route.ts new file mode 100644 index 0000000..87c70ec --- /dev/null +++ b/app/api/curator-comments/[id]/archive/route.ts @@ -0,0 +1,66 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { PrismaClient } from '@prisma/client'; +import { requireStaffAuth } from '@/lib/auth'; + +const prisma = new PrismaClient(); + +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + // Require curator authentication + const { error, context } = await requireStaffAuth(request); + if (error || !context) { + return error!; + } + + // Only curators can archive comments + if (context.role !== 'curator') { + return NextResponse.json( + { error: 'Only curators can archive comments' }, + { status: 403 } + ); + } + + try { + const { id } = await params; + const commentId = Number(id); + const curatorId = context.curator.id; + + // Verify that this comment belongs to this curator + const recipient = await prisma.curatorCommentRecipient.findUnique({ + where: { + commentId_curatorId: { + commentId: commentId, + curatorId: curatorId + } + } + }); + + if (!recipient) { + return NextResponse.json( + { error: 'Comment not found or access denied' }, + { status: 404 } + ); + } + + // Update archived flag + await prisma.curatorCommentRecipient.update({ + where: { + id: recipient.id + }, + data: { + archived: true + } + }); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('Error archiving comment:', error); + return NextResponse.json( + { error: 'Internal Server Error' }, + { status: 500 } + ); + } +} + diff --git a/app/api/curator-comments/route.ts b/app/api/curator-comments/route.ts index b6cfe34..e64ac4a 100644 --- a/app/api/curator-comments/route.ts +++ b/app/api/curator-comments/route.ts @@ -22,10 +22,11 @@ export async function GET(request: NextRequest) { try { const curatorId = context.curator.id; - // Get all comments for this curator, ordered by creation date (newest first) + // Get all non-archived comments for this curator, ordered by creation date (newest first) const comments = await prisma.curatorCommentRecipient.findMany({ where: { - curatorId: curatorId + curatorId: curatorId, + archived: false }, include: { comment: { diff --git a/app/curator/CuratorPageClient.tsx b/app/curator/CuratorPageClient.tsx index 0a8c490..34d78f3 100644 --- a/app/curator/CuratorPageClient.tsx +++ b/app/curator/CuratorPageClient.tsx @@ -133,16 +133,11 @@ export default function CuratorPageClient() { } }, []); - useEffect(() => { - if (showComments && isAuthenticated) { - fetchComments(); - } - }, [showComments, isAuthenticated]); const bootstrapCuratorData = async () => { try { setLoading(true); - await Promise.all([fetchCuratorInfo(), fetchSongs(), fetchGenres(), fetchSpecials()]); + await Promise.all([fetchCuratorInfo(), fetchSongs(), fetchGenres(), fetchSpecials(), fetchComments()]); } finally { setLoading(false); } @@ -184,6 +179,24 @@ export default function CuratorPageClient() { } }; + const archiveComment = async (commentId: number) => { + try { + const res = await fetch(`/api/curator-comments/${commentId}/archive`, { + method: 'POST', + headers: getCuratorAuthHeaders(), + }); + if (res.ok) { + // Remove comment from local state (archived comments are not shown) + setComments(comments.filter(c => c.id !== commentId)); + } else { + setMessage(t('archiveCommentError')); + } + } catch (error) { + console.error('Error archiving comment:', error); + setMessage(t('archiveCommentError')); + } + }; + const fetchCuratorInfo = async () => { const res = await fetch('/api/curator/me', { headers: getCuratorAuthHeaders(), @@ -708,9 +721,6 @@ export default function CuratorPageClient() { + ); })} diff --git a/messages/de.json b/messages/de.json index 1a82eba..4b987d0 100644 --- a/messages/de.json +++ b/messages/de.json @@ -248,7 +248,10 @@ "loadCommentsError": "Fehler beim Laden der Kommentare.", "commentFromPuzzle": "Kommentar zu Puzzle", "commentGenre": "Genre", - "unreadComment": "Ungelesen" + "unreadComment": "Ungelesen", + "archiveComment": "Archivieren", + "archiveCommentConfirm": "Möchtest du diesen Kommentar wirklich archivieren?", + "archiveCommentError": "Fehler beim Archivieren des Kommentars." }, "About": { "title": "Über Hördle & Impressum", diff --git a/messages/en.json b/messages/en.json index e107677..7796b3e 100644 --- a/messages/en.json +++ b/messages/en.json @@ -248,7 +248,10 @@ "loadCommentsError": "Error loading comments.", "commentFromPuzzle": "Comment from puzzle", "commentGenre": "Genre", - "unreadComment": "Unread" + "unreadComment": "Unread", + "archiveComment": "Archive", + "archiveCommentConfirm": "Do you really want to archive this comment?", + "archiveCommentError": "Error archiving comment." }, "About": { "title": "About Hördle & Imprint", diff --git a/prisma/migrations/20251203225222_add_archived_to_curator_comment_recipient/migration.sql b/prisma/migrations/20251203225222_add_archived_to_curator_comment_recipient/migration.sql new file mode 100644 index 0000000..6aedab2 --- /dev/null +++ b/prisma/migrations/20251203225222_add_archived_to_curator_comment_recipient/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "CuratorCommentRecipient" ADD COLUMN "archived" BOOLEAN NOT NULL DEFAULT false; + diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7f38825..5da755e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -176,6 +176,7 @@ model CuratorCommentRecipient { curatorId Int curator Curator @relation(fields: [curatorId], references: [id], onDelete: Cascade) readAt DateTime? + archived Boolean @default(false) @@unique([commentId, curatorId]) @@index([curatorId])