Files
Momento/memento-note/app/api/flashcards/save/route.ts
Antigravity 0784c94242
Some checks failed
CI / Lint, Test & Build (push) Failing after 57s
CI / Deploy production (on server) (push) Has been skipped
feat(notes): vues structurées tableau/kanban, flashcards et MCP robuste
Ajoute la base organisable par carnet (schéma, champs partagés, valeurs par note)
avec activation guidée, tableau éditable, kanban et suppression de colonnes.
Corrige le multiselect en vue tableau et enrichit sidebar, grille et i18n FR/EN.
Inclut aussi les améliorations flashcards SM-2, l'audit consentement IA et la
robustesse du serveur MCP (config, validation, rate-limit, métriques).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 23:03:16 +00:00

126 lines
3.9 KiB
TypeScript

import { NextRequest, NextResponse } from 'next/server'
import { Prisma } from '@prisma/client'
import { auth } from '@/auth'
import { prisma } from '@/lib/prisma'
import { getOrCreateDeckForNotebook } from '@/lib/flashcards/deck-utils'
import type { FlashcardStyle } from '@/lib/flashcards/generate-flashcards'
interface CardInput {
front: string
back: string
type?: string
}
export async function POST(request: NextRequest) {
try {
if (!prisma.flashcard || !prisma.flashcardDeck) {
return NextResponse.json(
{ error: 'Flashcards client outdated. Restart the app after prisma generate.', errorKey: 'flashcards.schemaMissing' },
{ status: 503 },
)
}
const session = await auth()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const body = await request.json()
const noteId = typeof body.noteId === 'string' ? body.noteId : null
const deckIdInput = typeof body.deckId === 'string' ? body.deckId : null
const cards = Array.isArray(body.cards) ? body.cards as CardInput[] : []
if (cards.length === 0) {
return NextResponse.json({ error: 'No cards to save' }, { status: 400 })
}
let notebookId: string | null = null
let fallbackDeckName: string | undefined
if (noteId) {
const note = await prisma.note.findFirst({
where: { id: noteId, userId: session.user.id },
select: { notebookId: true, title: true },
})
if (!note) {
return NextResponse.json({ error: 'Note not found' }, { status: 404 })
}
notebookId = note.notebookId
if (!notebookId) {
fallbackDeckName = note.title?.trim() || 'General'
}
}
let deckId = deckIdInput
if (!deckId) {
if (noteId) {
const existingFromNote = await prisma.flashcard.findFirst({
where: { noteId, deck: { userId: session.user.id } },
select: { deckId: true },
})
if (existingFromNote) {
deckId = existingFromNote.deckId
}
}
if (!deckId) {
const deck = await getOrCreateDeckForNotebook({
userId: session.user.id,
notebookId,
manualName: fallbackDeckName,
})
deckId = deck.id
}
} else {
const deck = await prisma.flashcardDeck.findFirst({
where: { id: deckId, userId: session.user.id },
})
if (!deck) {
return NextResponse.json({ error: 'Deck not found' }, { status: 404 })
}
}
const sanitized = cards
.map((c) => ({
front: typeof c.front === 'string' ? c.front.trim().slice(0, 500) : '',
back: typeof c.back === 'string' ? c.back.trim().slice(0, 800) : '',
type: (['qa', 'cloze', 'concept'].includes(c.type || '') ? c.type : 'qa') as FlashcardStyle,
}))
.filter((c) => c.front && c.back)
if (sanitized.length === 0) {
return NextResponse.json({ error: 'No valid cards' }, { status: 400 })
}
await prisma.flashcard.createMany({
data: sanitized.map((c) => ({
deckId: deckId!,
noteId,
front: c.front,
back: c.back,
type: c.type,
})),
})
await prisma.flashcardDeck.update({
where: { id: deckId! },
data: { updatedAt: new Date() },
})
return NextResponse.json({
deckId,
savedCount: sanitized.length,
})
} catch (error) {
console.error('[flashcards/save]', error)
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2021' || error.code === 'P2022') {
return NextResponse.json(
{ error: 'Flashcards schema not migrated. Run prisma migrate deploy.', errorKey: 'flashcards.schemaMissing' },
{ status: 503 },
)
}
}
const message = error instanceof Error ? error.message : 'Failed to save flashcards'
return NextResponse.json({ error: message }, { status: 500 })
}
}