import { NextRequest, NextResponse } from 'next/server' import { auth } from '@/auth' import prisma from '@/lib/prisma' function escapeCSV(value: string): string { if (!value) return '' if (value.includes(',') || value.includes('"') || value.includes('\n')) { return `"${value.replace(/"/g, '""')}"` } return value } export async function GET(request: NextRequest) { try { const session = await auth() if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const notebookId = request.nextUrl.searchParams.get('notebookId') if (!notebookId) { return NextResponse.json({ error: 'notebookId required' }, { status: 400 }) } const notebook = await prisma.notebook.findFirst({ where: { id: notebookId, userId: session.user.id }, select: { name: true }, }) if (!notebook) { return NextResponse.json({ error: 'Notebook not found' }, { status: 404 }) } const schema = await prisma.notebookSchema.findUnique({ where: { notebookId }, include: { properties: { orderBy: { position: 'asc' } } }, }) const notes = await prisma.note.findMany({ where: { notebookId, trashedAt: null, userId: session.user.id }, include: { properties: true }, orderBy: { order: 'asc' }, }) const propMap = new Map() if (schema?.properties) { schema.properties.forEach(p => propMap.set(p.id, p.name)) } const headers = ['Titre', ...Array.from(propMap.values()), 'Créé le', 'Modifié le'] const rows: string[] = [headers.map(escapeCSV).join(',')] for (const note of notes) { const noteProps = new Map() for (const np of note.properties) { let val = np.value || '' try { val = JSON.parse(val); if (Array.isArray(val)) val = val.join(', '); if (typeof val === 'object') val = String(val) } catch {} noteProps.set(np.propertyId, String(val)) } const row = [ escapeCSV(note.title || ''), ...Array.from(propMap.keys()).map(propId => escapeCSV(noteProps.get(propId) || '')), escapeCSV(note.createdAt.toISOString().slice(0, 10)), escapeCSV(note.updatedAt.toISOString().slice(0, 10)), ] rows.push(row.join(',')) } const csv = '\uFEFF' + rows.join('\n') const filename = `${notebook.name.replace(/[^a-z0-9]/gi, '_')}.csv` return new NextResponse(csv, { headers: { 'Content-Type': 'text/csv; charset=utf-8', 'Content-Disposition': `attachment; filename="${filename}"`, }, }) } catch (error: any) { console.error('[CSV Export] Error:', error) return NextResponse.json({ error: error.message }, { status: 500 }) } } export async function POST(request: NextRequest) { try { const session = await auth() if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const { notebookId, csvData } = await request.json() if (!notebookId || !csvData) { return NextResponse.json({ error: 'notebookId and csvData required' }, { status: 400 }) } // Parse CSV const lines = csvData.trim().split('\n') if (lines.length < 2) { return NextResponse.json({ error: 'CSV must have at least a header and one row' }, { status: 400 }) } const parseCSVLine = (line: string): string[] => { const result: string[] = [] let current = '' let inQuotes = false for (let i = 0; i < line.length; i++) { const char = line[i] if (char === '"') { if (inQuotes && line[i + 1] === '"') { current += '"'; i++ } else inQuotes = !inQuotes } else if (char === ',' && !inQuotes) { result.push(current.trim()) current = '' } else { current += char } } result.push(current.trim()) return result } const headers = parseCSVLine(lines[0].replace(/^\uFEFF/, '')) const titleIdx = headers.findIndex(h => h.toLowerCase().includes('titre') || h.toLowerCase() === 'title') const dataRows = lines.slice(1).filter((l: string) => l.trim()) // Get or create schema let schema = await prisma.notebookSchema.findUnique({ where: { notebookId } }) if (!schema) { schema = await prisma.notebookSchema.create({ data: { notebookId } }) } // Map headers to property IDs (create properties for non-title columns) const headerToPropId = new Map() for (let i = 0; i < headers.length; i++) { if (i === titleIdx) continue const headerName = headers[i] if (headerName.toLowerCase().includes('créé') || headerName.toLowerCase().includes('modifié') || headerName.toLowerCase().includes('created') || headerName.toLowerCase().includes('modified')) continue const existing = await prisma.notebookProperty.findFirst({ where: { schemaId: schema.id, name: headerName }, }) if (existing) { headerToPropId.set(i, existing.id) } else { const prop = await prisma.notebookProperty.create({ data: { schemaId: schema.id, name: headerName, type: 'text', position: await prisma.notebookProperty.count({ where: { schemaId: schema.id } }), }, }) headerToPropId.set(i, prop.id) } } // Get max order const maxOrderNote = await prisma.note.findFirst({ where: { notebookId }, orderBy: { order: 'desc' }, select: { order: true }, }) let nextOrder = (maxOrderNote?.order ?? 0) + 1 let created = 0 for (const line of dataRows) { const cells = parseCSVLine(line) const title = titleIdx >= 0 ? cells[titleIdx] : cells[0] || 'Sans titre' const note = await prisma.note.create({ data: { title, content: '

', userId: session.user.id, notebookId, order: nextOrder++, }, }) for (const [colIdx, propId] of headerToPropId) { const value = cells[colIdx] if (value) { await prisma.noteProperty.create({ data: { noteId: note.id, propertyId: propId, value: JSON.stringify(value), }, }).catch(() => {}) } } created++ } return NextResponse.json({ success: true, created }) } catch (error: any) { console.error('[CSV Import] Error:', error) return NextResponse.json({ error: error.message }, { status: 500 }) } }