import { NextRequest, NextResponse } from 'next/server' import prisma from '@/lib/prisma' import { auth } from '@/auth' import { parseNote } from '@/lib/utils' import { createNoteHistorySnapshot, getNoteHistoryMode, isNoteHistoryEnabledForUser, shouldCaptureHistorySnapshot, shouldCreateAutoSnapshot, } from '@/lib/note-history' // GET /api/notes/[id] - Get a single note export async function GET( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { const session = await auth() if (!session?.user?.id) { return NextResponse.json( { success: false, error: 'Unauthorized' }, { status: 401 } ) } try { const { id } = await params const note = await prisma.note.findUnique({ where: { id } }) if (!note) { return NextResponse.json( { success: false, error: 'Note not found' }, { status: 404 } ) } if (note.userId !== session.user.id) { const share = await prisma.noteShare.findUnique({ where: { noteId_userId: { noteId: note.id, userId: session.user.id } } }) if (!share || share.status !== 'accepted') { return NextResponse.json( { success: false, error: 'Forbidden' }, { status: 403 } ) } } return NextResponse.json({ success: true, data: parseNote(note) }) } catch (error) { console.error('Error fetching note:', error) return NextResponse.json( { success: false, error: 'Failed to fetch note' }, { status: 500 } ) } } // PUT /api/notes/[id] - Update a note export async function PUT( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { const session = await auth() if (!session?.user?.id) { return NextResponse.json( { success: false, error: 'Unauthorized' }, { status: 401 } ) } try { const { id } = await params const existingNote = await prisma.note.findUnique({ where: { id } }) if (!existingNote) { return NextResponse.json( { success: false, error: 'Note not found' }, { status: 404 } ) } if (existingNote.userId !== session.user.id) { return NextResponse.json( { success: false, error: 'Forbidden' }, { status: 403 } ) } const body = await request.json() // Whitelist allowed fields to prevent mass assignment const allowedFields = ['title', 'content', 'color', 'isPinned', 'isArchived', 'type', 'isMarkdown', 'size', 'notebookId'] const updateData: Record = {} for (const key of allowedFields) { if (key in body) { updateData[key] = body[key] } } if ('checkItems' in body) { updateData.checkItems = body.checkItems ?? null } if ('labels' in body) { updateData.labels = body.labels ?? null } // Only update if data actually changed const hasChanges = Object.keys(updateData).some((key) => { const newValue = updateData[key] const oldValue = (existingNote as any)[key] // Handle arrays/objects by comparing JSON if (typeof newValue === 'object' && newValue !== null) { return JSON.stringify(newValue) !== JSON.stringify(oldValue) } return newValue !== oldValue }) // If no changes, return existing note without updating timestamp if (!hasChanges) { return NextResponse.json({ success: true, data: parseNote(existingNote), }) } const note = await prisma.note.update({ where: { id }, data: updateData, }) try { const historyEnabled = existingNote.historyEnabled === true if (historyEnabled && shouldCaptureHistorySnapshot(updateData)) { const mode = await getNoteHistoryMode(session.user.id) if (mode === 'auto') { const shouldAuto = await shouldCreateAutoSnapshot({ noteId: id, userId: session.user.id, updateData, existingContent: existingNote.content ?? '', existingTitle: existingNote.title ?? null, }) if (shouldAuto) { await createNoteHistorySnapshot({ noteId: id, userId: session.user.id, reason: 'api:update', }) } } // manual mode: no auto-snapshot } } catch (snapshotError) { console.error('[HISTORY] Failed to create snapshot from /api/notes/[id] PUT:', snapshotError) } return NextResponse.json({ success: true, data: parseNote(note) }) } catch (error) { console.error('Error updating note:', error) return NextResponse.json( { success: false, error: 'Failed to update note' }, { status: 500 } ) } } // DELETE /api/notes/[id] - Delete a note export async function DELETE( request: NextRequest, { params }: { params: Promise<{ id: string }> } ) { const session = await auth() if (!session?.user?.id) { return NextResponse.json( { success: false, error: 'Unauthorized' }, { status: 401 } ) } try { const { id } = await params const existingNote = await prisma.note.findUnique({ where: { id } }) if (!existingNote) { return NextResponse.json( { success: false, error: 'Note not found' }, { status: 404 } ) } if (existingNote.userId !== session.user.id) { return NextResponse.json( { success: false, error: 'Forbidden' }, { status: 403 } ) } await prisma.note.update({ where: { id }, data: { trashedAt: new Date() } }) return NextResponse.json({ success: true, message: 'Note moved to trash' }) } catch (error) { console.error('Error deleting note:', error) return NextResponse.json( { success: false, error: 'Failed to delete note' }, { status: 500 } ) } }