import { v4 as uuid } from 'uuid'; import { db } from '../../src/db/client.js'; import { categories, questions, knowledgeCards, skillTree, achievements, adminUsers } from '../../src/db/schema.js'; import { eq } from 'drizzle-orm'; import * as adminAuthService from '../../src/services/admin/admin-auth.js'; import categoriesData from '../../content/categories.json' with { type: 'json' }; import historyData from '../../content/history.json' with { type: 'json' }; import dramaData from '../../content/drama.json' with { type: 'json' }; import crosstalkData from '../../content/crosstalk.json' with { type: 'json' }; import skillTreeData from '../../content/skill-tree.json' with { type: 'json' }; import achievementsData from '../../content/achievements.json' with { type: 'json' }; interface QuestionInput { categoryId: string; contentType: string; difficulty: number; stem: { text: string }; correctAnswer: string; distractors: string[]; knowledgeCard: { summary: string; deepDive?: string; sourceRef?: string; }; } interface SkillTreeNodeInput { categoryId: string; title: string; sortOrder: number; questionsRequired: number; passThreshold: number; parentId: string | null; } async function seedCategories() { let inserted = 0; let skipped = 0; for (const cat of categoriesData) { const [existing] = await db.select().from(categories).where(eq(categories.id, cat.id)).limit(1); if (existing) { skipped++; continue; } await db.insert(categories).values({ id: cat.id, name: cat.name, slug: cat.slug, sortOrder: cat.sortOrder, status: cat.status, }); inserted++; } console.log(`Categories: ${inserted} inserted, ${skipped} skipped`); } async function seedSkillTree() { let inserted = 0; let skipped = 0; const chapterIdMap = new Map(); // sortOrder → id (per category) for (const node of skillTreeData as SkillTreeNodeInput[]) { // Check if chapter with same category + title already exists const existing = await db .select() .from(skillTree) .where(eq(skillTree.title, node.title)) .limit(1); if (existing.length > 0) { chapterIdMap.set(node.sortOrder, existing[0]!.id); skipped++; continue; } const id = uuid(); let parentId: string | null = null; if (node.parentId === 'DEPENDS_ON_PREVIOUS') { // Find the previous chapter in the same category const previousSortOrder = node.sortOrder - 1; parentId = chapterIdMap.get(previousSortOrder) ?? null; } await db.insert(skillTree).values({ id, categoryId: node.categoryId, title: node.title, sortOrder: node.sortOrder, questionsRequired: node.questionsRequired, passThreshold: node.passThreshold, parentId, }); chapterIdMap.set(node.sortOrder, id); inserted++; } console.log(`Skill tree: ${inserted} inserted, ${skipped} skipped`); } async function seedQuestions(allQuestions: QuestionInput[]) { let inserted = 0; let skipped = 0; for (const q of allQuestions) { // Check by unique combination: categoryId + correctAnswer + stem text const stemText = q.stem.text; const existing = await db .select() .from(questions) .where(eq(questions.correctAnswer, q.correctAnswer)) .limit(1); const alreadyExists = existing.some( (row) => (row.stem as { text: string }).text === stemText && row.categoryId === q.categoryId, ); if (alreadyExists) { skipped++; continue; } const questionId = uuid(); await db.insert(questions).values({ id: questionId, stem: q.stem, contentType: q.contentType as 'text', correctAnswer: q.correctAnswer, distractors: q.distractors, categoryId: q.categoryId, difficulty: q.difficulty, status: 'published', }); if (q.knowledgeCard) { await db.insert(knowledgeCards).values({ id: uuid(), questionId, summary: q.knowledgeCard.summary, deepDive: q.knowledgeCard.deepDive ?? null, sourceRef: q.knowledgeCard.sourceRef ?? null, }); } inserted++; } console.log(`Questions: ${inserted} inserted, ${skipped} skipped`); } async function seedAchievements() { let inserted = 0; let skipped = 0; for (const a of achievementsData as Array<{ type: string; name: string; description: string; iconUrl: string | null; condition: Record; }>) { const existing = await db .select() .from(achievements) .where(eq(achievements.name, a.name)) .limit(1); if (existing.length > 0) { skipped++; continue; } await db.insert(achievements).values({ id: uuid(), type: a.type as 'knowledge' | 'behavior', name: a.name, description: a.description, iconUrl: a.iconUrl ?? null, condition: a.condition, }); inserted++; } console.log(`Achievements: ${inserted} inserted, ${skipped} skipped`); } async function main() { console.log('Starting seed data import...\n'); // Step 0: Admin users (no dependencies) await seedAdminUsers(); // Step 1: Categories (no dependencies) await seedCategories(); // Step 2: Skill tree (depends on categories) await seedSkillTree(); // Step 3: Questions + Knowledge cards (depends on categories) const allQuestions: QuestionInput[] = [ ...(historyData as QuestionInput[]), ...(dramaData as QuestionInput[]), ...(crosstalkData as QuestionInput[]), ]; await seedQuestions(allQuestions); // Step 4: Achievements await seedAchievements(); console.log('\nSeed data import complete!'); process.exit(0); } async function seedAdminUsers() { // Create default admin user (username: admin, password: admin123) // Note: In production, change the password immediately! await adminAuthService.createAdminUser('admin', 'admin123', 'super_admin'); console.log('Admin user seeded: username=admin, password=admin123 (CHANGE IN PRODUCTION!)'); } main().catch((err) => { console.error('Seed failed:', err); process.exit(1); });