import { NextRequest, NextResponse } from 'next/server' import { auth } from '@/auth' import prisma from '@/lib/prisma' import { embeddingService } from '@/lib/ai/services/embedding.service' import { upsertNoteEmbedding } from '@/lib/embeddings' type DemoNote = { title: string; content: string } const DEMO_NOTES: Record = { fr: [ { title: 'Réunion Q3 — Stratégie produit', content: `

Réunion Q3 — Stratégie produit

Points clés abordés lors de la réunion trimestrielle :

Prochaine étape : finaliser l'onboarding wizard pour atteindre le taux d'activation cible.

`, }, { title: 'Idées de projets secondaires', content: `

Idées de projets secondaires

Liste de projets à explorer dans les prochains mois :

Critères de sélection : impact fort, temps minimal, apprentissage technique.

`, }, { title: 'Livres à lire — Recommandations', content: `

Livres à lire — Recommandations

Productivité & Pensée

Technique

Prochaine lecture : Building a Second Brain (lien avec Momento évident).

`, }, { title: 'Notes de formation React — Hooks avancés', content: `

Notes de formation React — Hooks avancés

useCallback vs useMemo

useReducer

Préférer useReducer à useState quand :

Pattern : Context + useReducer

Combine Context API avec useReducer pour un state management léger sans Redux.

`, }, { title: 'Objectifs personnels 2025', content: `

Objectifs personnels 2025

🎯 Professionnel

📚 Apprentissage

🌱 Personnel

Revue mensuelle le 1er de chaque mois pour ajuster les priorités.

`, }, ], en: [ { title: 'Q3 Meeting — Product Strategy', content: `

Q3 Meeting — Product Strategy

Key points from the quarterly review:

Next step: finalize onboarding wizard to reach activation target.

`, }, { title: 'Side Project Ideas', content: `

Side Project Ideas

Projects to explore in the coming months:

Selection criteria: high impact, minimal time, technical learning.

`, }, { title: 'Books to Read — Recommendations', content: `

Books to Read — Recommendations

Productivity & Thinking

Technical

Next read: Building a Second Brain (obvious connection to Momento).

`, }, { title: 'React Training Notes — Advanced Hooks', content: `

React Training Notes — Advanced Hooks

useCallback vs useMemo

useReducer

Prefer useReducer over useState when:

`, }, { title: 'Personal Goals 2025', content: `

Personal Goals 2025

🎯 Professional

📚 Learning

🌱 Personal

`, }, ], fa: [ { title: 'جلسه Q3 — استراتژی محصول', content: `

جلسه Q3 — استراتژی محصول

نکات کلیدی جلسه فصلی:

قدم بعدی: نهایی کردن ویزارد آنبوردینگ برای رسیدن به هدف فعال‌سازی.

`, }, { title: 'ایده‌های پروژه‌های جانبی', content: `

ایده‌های پروژه‌های جانبی

`, }, { title: 'کتاب‌های پیشنهادی', content: `

کتاب‌های پیشنهادی

`, }, { title: 'یادداشت‌های آموزش React', content: `

یادداشت‌های آموزش React

useCallback و useMemo

`, }, { title: 'اهداف شخصی ۲۰۲۵', content: `

اهداف شخصی ۲۰۲۵

🎯 حرفه‌ای

📚 یادگیری

`, }, ], } function getNotesForLocale(locale: string): DemoNote[] { const lang = locale.split('-')[0].toLowerCase() return DEMO_NOTES[lang] ?? DEMO_NOTES.en } /** Wraps a promise with a timeout — rejects after `ms` milliseconds. */ function withTimeout(promise: Promise, ms: number): Promise { const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error(`Operation timed out after ${ms}ms`)), ms) ) return Promise.race([promise, timeout]) } export async function POST(req: NextRequest) { const session = await auth() if (!session?.user?.id) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const userId = session.user.id let locale = 'en' try { const body = await req.json().catch(() => ({})) if (body?.locale) locale = body.locale } catch { /* ignore */ } // Idempotency check — if any demo notes already exist, return them (handles partial creation too) const existing = await prisma.note.findMany({ where: { userId, isDemo: true, trashedAt: null }, select: { id: true, title: true }, }) if (existing.length > 0) { return NextResponse.json({ created: false, notes: existing, message: 'Demo notes already exist' }) } const demoNotes = getNotesForLocale(locale) const created: { id: string; title: string | null }[] = [] for (const demo of demoNotes) { const note = await prisma.note.create({ data: { userId, title: demo.title, content: demo.content, isMarkdown: true, isDemo: true, type: 'richtext', color: 'default', }, }) created.push({ id: note.id, title: note.title }) // Synchronous embedding generation so semantic search works immediately (6s timeout per note) try { const { embedding } = await withTimeout( embeddingService.generateNoteEmbedding(demo.title, demo.content), 6000 ) if (embedding) { await withTimeout(upsertNoteEmbedding(note.id, embedding), 3000) } } catch (e) { console.error('[ONBOARDING] Embedding failed for demo note:', note.id, e) } } return NextResponse.json({ created: true, notes: created }) }