import { NextRequest, NextResponse } from 'next/server' import { auth } from '@/auth' import { prisma } from '@/lib/prisma' import { revalidatePath } from 'next/cache' function parseDate(v: unknown): Date | null { if (!v) return null const d = new Date(v as string) return isNaN(d.getTime()) ? null : d } export async function POST(req: NextRequest) { try { const session = await auth() if (!session?.user?.id) { return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 }) } const userId = session.user.id const formData = await req.formData() const file = formData.get('file') as File if (!file) { return NextResponse.json({ success: false, error: 'No file provided' }, { status: 400 }) } const text = await file.text() let importData: any try { importData = JSON.parse(text) } catch { return NextResponse.json({ success: false, error: 'Invalid JSON file' }, { status: 400 }) } if (!importData.data || !importData.data.notes) { return NextResponse.json({ success: false, error: 'Invalid import format' }, { status: 400 }) } const stats = { notes: 0, labels: 0, notebooks: 0, skipped: 0 } // 1. Notebooks — parents first, preserve original IDs if (importData.data.notebooks?.length) { const sorted = [...importData.data.notebooks].sort((a: any, b: any) => { if (!a.parentId && b.parentId) return -1 if (a.parentId && !b.parentId) return 1 return 0 }) for (const nb of sorted) { try { await prisma.notebook.upsert({ where: { id: nb.id }, update: { name: nb.name, icon: nb.icon ?? null, color: nb.color ?? null, order: nb.order ?? 0, parentId: nb.parentId ?? null, trashedAt: parseDate(nb.trashedAt), updatedAt: new Date(), }, create: { id: nb.id, name: nb.name, icon: nb.icon ?? null, color: nb.color ?? null, order: nb.order ?? 0, parentId: nb.parentId ?? null, trashedAt: parseDate(nb.trashedAt), userId, createdAt: parseDate(nb.createdAt) ?? new Date(), updatedAt: parseDate(nb.updatedAt) ?? new Date(), }, }) stats.notebooks++ } catch (e: any) { if (e.code === 'P2002') { // unique constraint — try with new ID const parentId = nb.parentId ?? null await prisma.notebook.create({ data: { name: nb.name, icon: nb.icon ?? null, color: nb.color ?? null, order: nb.order ?? 0, parentId, trashedAt: parseDate(nb.trashedAt), userId, createdAt: parseDate(nb.createdAt) ?? new Date(), updatedAt: parseDate(nb.updatedAt) ?? new Date(), }, }) stats.notebooks++ } else { stats.skipped++ } } } } // 2. Labels — preserve original IDs if (importData.data.labels?.length) { for (const lb of importData.data.labels) { try { await prisma.label.upsert({ where: { id: lb.id }, update: { name: lb.name, color: lb.color ?? null, notebookId: lb.notebookId ?? null, type: lb.type ?? 'user', }, create: { id: lb.id, name: lb.name, color: lb.color ?? null, notebookId: lb.notebookId ?? null, type: lb.type ?? 'user', userId, createdAt: parseDate(lb.createdAt) ?? new Date(), updatedAt: parseDate(lb.updatedAt) ?? new Date(), }, }) stats.labels++ } catch { stats.skipped++ } } } // 3. Notes — preserve original IDs if (importData.data.notes?.length) { for (const n of importData.data.notes) { try { const labelIds: string[] = n.labelIds || [] const validLabelIds = labelIds.length ? await prisma.label.findMany({ where: { id: { in: labelIds }, userId }, select: { id: true }, }).then(ls => ls.map(l => l.id)) : [] await prisma.note.upsert({ where: { id: n.id }, update: { title: n.title || 'Untitled', content: n.content || '', color: n.color || 'default', isPinned: n.isPinned ?? false, isArchived: n.isArchived ?? false, type: n.type || 'richtext', order: n.order ?? 0, isMarkdown: n.isMarkdown ?? false, size: n.size || 'small', autoGenerated: n.autoGenerated ?? false, historyEnabled: n.historyEnabled ?? true, checkItems: n.checkItems ?? undefined, images: n.images ?? undefined, links: n.links ?? undefined, trashedAt: parseDate(n.trashedAt), notebookId: n.notebookId ?? null, contentUpdatedAt: parseDate(n.contentUpdatedAt), updatedAt: new Date(), labelRelations: { set: validLabelIds.map(id => ({ id })), }, }, create: { id: n.id, title: n.title || 'Untitled', content: n.content || '', color: n.color || 'default', isPinned: n.isPinned ?? false, isArchived: n.isArchived ?? false, type: n.type || 'richtext', order: n.order ?? 0, isMarkdown: n.isMarkdown ?? false, size: n.size || 'small', autoGenerated: n.autoGenerated ?? false, historyEnabled: n.historyEnabled ?? true, checkItems: n.checkItems ?? undefined, images: n.images ?? undefined, links: n.links ?? undefined, trashedAt: parseDate(n.trashedAt), notebookId: n.notebookId ?? null, userId, createdAt: parseDate(n.createdAt) ?? new Date(), updatedAt: parseDate(n.updatedAt) ?? new Date(), contentUpdatedAt: parseDate(n.contentUpdatedAt), labelRelations: { connect: validLabelIds.map(id => ({ id })), }, }, }) stats.notes++ } catch (e: any) { if (e.code === 'P2002') { await prisma.note.create({ data: { title: n.title || 'Untitled', content: n.content || '', color: n.color || 'default', isPinned: n.isPinned ?? false, isArchived: n.isArchived ?? false, type: n.type || 'richtext', order: n.order ?? 0, isMarkdown: n.isMarkdown ?? false, size: n.size || 'small', notebookId: n.notebookId ?? null, userId, createdAt: parseDate(n.createdAt) ?? new Date(), updatedAt: parseDate(n.updatedAt) ?? new Date(), contentUpdatedAt: parseDate(n.contentUpdatedAt), }, }) stats.notes++ } else { stats.skipped++ } } } } revalidatePath('/home') revalidatePath('/settings/data') return NextResponse.json({ success: true, ...stats }) } catch (error) { console.error('Import error:', error) return NextResponse.json({ success: false, error: 'Failed to import notes' }, { status: 500 }) } }