import { PrismaClient } from '@prisma/client'; import fs from 'fs'; import path from 'path'; /** * Seeds the database with the curriculum loaded from JSON files. * Safe to call multiple times — checks for existing data before inserting/updating. */ export async function seedDatabase(prisma: PrismaClient): Promise<{ seeded: boolean; message: string }> { const tracksDir = path.resolve(__dirname, '../content/tracks'); const modulesDir = path.resolve(__dirname, '../content/modules'); let totalSynced = 0; // 1. Sync regular tracks if (fs.existsSync(tracksDir)) { const trackFiles = fs.readdirSync(tracksDir).filter(f => f.endsWith('.json')); console.log(`[SEED] Found ${trackFiles.length} track files.`); for (const file of trackFiles) { await syncTrackFile(prisma, path.join(tracksDir, file)); totalSynced++; } } // 2. Sync modular adaptive content if (fs.existsSync(modulesDir)) { const moduleFiles = fs.readdirSync(modulesDir).filter(f => f.endsWith('.json')); console.log(`[SEED] Found ${moduleFiles.length} modular files.`); for (const file of moduleFiles) { await syncModuleFile(prisma, path.join(modulesDir, file)); totalSynced++; } } return { seeded: true, message: `✅ ${totalSynced} files synchronized successfully.` }; } async function syncTrackFile(prisma: PrismaClient, filePath: string) { const content = JSON.parse(fs.readFileSync(filePath, 'utf-8')); if (!content.trackId || !Array.isArray(content.days)) return; const track = await prisma.track.upsert({ where: { id: content.trackId }, update: { title: content.title, description: content.description || '', duration: Math.ceil(content.days.reduce((max: number, d: any) => Math.max(max, d.dayNumber), 0)), language: content.language, }, create: { id: content.trackId, title: content.title, description: content.description || '', duration: Math.ceil(content.days.reduce((max: number, d: any) => Math.max(max, d.dayNumber), 0)), language: content.language, } }); for (const day of content.days) { await upsertTrackDay(prisma, track.id, day.dayNumber, { title: day.title || `Jour ${day.dayNumber}`, lessonText: day.lessonText, exercisePrompt: day.exercisePrompt, exerciseType: day.exerciseType || 'TEXT', exerciseCriteria: day.exerciseCriteria || null, badges: day.badges || null, audioUrl: day.audioUrl || null, imageUrl: day.imageUrl || null, videoUrl: day.videoUrl || null, videoCaption: day.videoCaption || null, buttonsJson: day.buttonsJson || null, }); } } async function syncModuleFile(prisma: PrismaClient, filePath: string) { try { const content = JSON.parse(fs.readFileSync(filePath, 'utf-8')); if (!content.meta?.id) return; const trackTitle = `Module: ${content.meta.secteur || content.meta.id}`; const trackDescription = `Adaptive module for ${content.meta.secteur || 'general purpose'}`; let langue = (content.meta.langue || 'FR').toUpperCase(); if (langue === 'WO') langue = 'WOLOF'; if (langue !== 'FR' && langue !== 'WOLOF') langue = 'FR'; const track = await prisma.track.upsert({ where: { id: content.meta.id }, update: { title: trackTitle, description: trackDescription, duration: Array.isArray(content.modules) ? content.modules.length : 1, language: langue as any, }, create: { id: content.meta.id, title: trackTitle, description: trackDescription, duration: Array.isArray(content.modules) ? content.modules.length : 1, language: langue as any, } }); if (Array.isArray(content.modules)) { // Multi-day module for (let i = 0; i < content.modules.length; i++) { const mod = content.modules[i]; const dayNumber = i + 1; await upsertTrackDay(prisma, track.id, dayNumber, { title: mod.id || `M${dayNumber}`, lessonText: mod.scenario?.lessonText || mod.content?.FR?.lessonText || '', exercisePrompt: mod.exercice?.question || mod.content?.FR?.exercisePrompt || '', exerciseType: mod.exercice?.type || 'TEXT', exerciseCriteria: mod.exercice?.criteria || mod.exercice || null, buttonsJson: mod.content ? { content: mod.content } : null, badges: null, audioUrl: null, }); } } else { // Single-day module (the root is the module) await upsertTrackDay(prisma, track.id, 1, { title: content.meta.id, lessonText: content.content?.FR?.lessonText || '', exercisePrompt: content.content?.FR?.exercisePrompt || '', exerciseType: 'TEXT', exerciseCriteria: { rules_engine: content.rules_engine, scoring: content.scoring }, buttonsJson: content.content ? { content: content.content } : null, badges: null, audioUrl: null, }); } } catch (err: any) { console.error(`[SEED] Failed to sync module file ${filePath}:`, err.message); } } async function upsertTrackDay(prisma: PrismaClient, trackId: string, dayNumber: number, data: any) { const existing = await prisma.trackDay.findFirst({ where: { trackId, dayNumber } }); if (existing) { await prisma.trackDay.update({ where: { id: existing.id }, data: { ...data, videoUrl: data.videoUrl || null, videoCaption: data.videoCaption || null, } }); } else { await prisma.trackDay.create({ data: { ...data, trackId, dayNumber, videoUrl: data.videoUrl || null, videoCaption: data.videoCaption || null, } }); } }