CognxSafeTrack
fix(ai): add imageUrl field to Prisma schema and correctly route language selection inside whatsapp.ts
f0f0df4 | 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, | |
| } | |
| }); | |
| } | |
| } | |