import { NextRequest, NextResponse } from 'next/server' import { auth } from '@/auth' import { prisma } from '@/lib/prisma' import { MAX_PROPERTIES_PER_NOTEBOOK } from '@/lib/structured-views/types' import { isValidPropertyType } from '@/lib/structured-views/property-utils' import { buildNoteValuesMap, parseViewSettings, serializeSchema } from '@/lib/structured-views/schema-serialize' async function assertNotebookAccess(notebookId: string, userId: string) { return prisma.notebook.findFirst({ where: { id: notebookId, userId, trashedAt: null }, select: { id: true }, }) } const schemaInclude = { properties: { orderBy: { position: 'asc' as const } }, } as const 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: notebookId } = await params const notebook = await assertNotebookAccess(notebookId, session.user.id) if (!notebook) { return NextResponse.json({ success: false, error: 'Notebook not found' }, { status: 404 }) } const raw = await prisma.notebookSchema.findUnique({ where: { notebookId }, include: schemaInclude, }) const schema = serializeSchema(raw) if (!schema) { return NextResponse.json({ success: true, data: { schema: null, noteValues: {} } }) } const notes = await prisma.note.findMany({ where: { notebookId, userId: session.user.id, trashedAt: null }, select: { id: true }, }) const noteIds = notes.map((n) => n.id) const rows = noteIds.length ? await prisma.noteProperty.findMany({ where: { noteId: { in: noteIds } }, include: { property: { select: { type: true } } }, }) : [] const noteValues = buildNoteValuesMap(noteIds, rows) return NextResponse.json({ success: true, data: { schema, noteValues } }) } catch (error) { console.error('GET notebook schema:', error) return NextResponse.json({ success: false, error: 'Failed to load schema' }, { status: 500 }) } } export async function POST( _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: notebookId } = await params const notebook = await assertNotebookAccess(notebookId, session.user.id) if (!notebook) { return NextResponse.json({ success: false, error: 'Notebook not found' }, { status: 404 }) } const existing = await prisma.notebookSchema.findUnique({ where: { notebookId } }) if (existing) { const raw = await prisma.notebookSchema.findUnique({ where: { notebookId }, include: schemaInclude, }) return NextResponse.json({ success: true, data: { schema: serializeSchema(raw) } }) } const created = await prisma.notebookSchema.create({ data: { notebookId }, include: schemaInclude, }) return NextResponse.json({ success: true, data: { schema: serializeSchema(created) } }) } catch (error) { console.error('POST notebook schema:', error) return NextResponse.json({ success: false, error: 'Failed to create schema' }, { status: 500 }) } } export async function PATCH( 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: notebookId } = await params const notebook = await assertNotebookAccess(notebookId, session.user.id) if (!notebook) { return NextResponse.json({ success: false, error: 'Notebook not found' }, { status: 404 }) } const body = await request.json() const action = typeof body.action === 'string' ? body.action : '' let schema = await prisma.notebookSchema.findUnique({ where: { notebookId } }) if (!schema) { schema = await prisma.notebookSchema.create({ data: { notebookId } }) } if (action === 'addProperty') { const name = typeof body.name === 'string' ? body.name.trim() : '' const type = typeof body.type === 'string' ? body.type : '' if (!name || !isValidPropertyType(type)) { return NextResponse.json({ success: false, error: 'Invalid property' }, { status: 400 }) } const count = await prisma.notebookProperty.count({ where: { schemaId: schema.id } }) if (count >= MAX_PROPERTIES_PER_NOTEBOOK) { return NextResponse.json({ success: false, error: 'Max properties reached' }, { status: 400 }) } let options: string[] = [] if (type === 'select' || type === 'multiselect') { options = Array.isArray(body.options) ? body.options .filter((o: unknown): o is string => typeof o === 'string' && o.trim().length > 0) .map((o: string) => o.trim()) : [] if (options.length === 0) { return NextResponse.json({ success: false, error: 'Options required' }, { status: 400 }) } } await prisma.notebookProperty.create({ data: { schemaId: schema.id, name, type, options: options.length ? JSON.stringify(options) : null, position: count, }, }) } else if (action === 'deleteProperty') { const propertyId = typeof body.propertyId === 'string' ? body.propertyId : '' if (!propertyId) { return NextResponse.json({ success: false, error: 'propertyId required' }, { status: 400 }) } const currentSettings = parseViewSettings(schema.viewSettings) await prisma.notebookProperty.deleteMany({ where: { id: propertyId, schemaId: schema.id }, }) if (currentSettings.kanbanGroupPropertyId === propertyId) { await prisma.notebookSchema.update({ where: { id: schema.id }, data: { viewSettings: JSON.stringify({ ...currentSettings, kanbanGroupPropertyId: null }) }, }) } } else if (action === 'updateViewSettings') { const current = parseViewSettings(schema.viewSettings) const next: Record = { ...current } if ('kanbanGroupPropertyId' in body) { next.kanbanGroupPropertyId = typeof body.kanbanGroupPropertyId === 'string' ? body.kanbanGroupPropertyId : null } await prisma.notebookSchema.update({ where: { id: schema.id }, data: { viewSettings: JSON.stringify(next) }, }) } else if (action === 'disable') { await prisma.notebookSchema.delete({ where: { id: schema.id } }) return NextResponse.json({ success: true, data: { schema: null } }) } else { return NextResponse.json({ success: false, error: 'Unknown action' }, { status: 400 }) } const raw = await prisma.notebookSchema.findUnique({ where: { notebookId }, include: schemaInclude, }) return NextResponse.json({ success: true, data: { schema: serializeSchema(raw) } }) } catch (error) { console.error('PATCH notebook schema:', error) return NextResponse.json({ success: false, error: 'Failed to update schema' }, { status: 500 }) } }