import "dotenv/config"; import { db } from "../src/shared/db"; import { users, roles, usersToRoles, questions, knowledgePoints, questionsToKnowledgePoints, exams, examQuestions, examSubmissions, submissionAnswers, textbooks, chapters } from "../src/shared/db/schema"; import { createId } from "@paralleldrive/cuid2"; import { faker } from "@faker-js/faker"; import { sql } from "drizzle-orm"; /** * Enterprise-Grade Seed Script for Next_Edu * * Scenarios Covered: * 1. IAM: RBAC with multiple roles (Teacher & Grade Head). * 2. Knowledge Graph: Nested Knowledge Points (Math -> Algebra -> Linear Equations). * 3. Question Bank: Rich Text Content & Nested Questions (Reading Comprehension). * 4. Exams: JSON Structure for Sectioning. */ async function seed() { console.log("๐ŸŒฑ Starting Database Seed..."); const start = performance.now(); // --- 0. Cleanup (Optional: Truncate tables for fresh start) --- // Note: Order matters due to foreign keys if checks are enabled. // Ideally, use: SET FOREIGN_KEY_CHECKS = 0; try { await db.execute(sql`SET FOREIGN_KEY_CHECKS = 0;`); const tables = [ "submission_answers", "exam_submissions", "exam_questions", "exams", "questions_to_knowledge_points", "questions", "knowledge_points", "chapters", "textbooks", "users_to_roles", "roles", "users", "accounts", "sessions" ]; for (const table of tables) { await db.execute(sql.raw(`TRUNCATE TABLE \`${table}\`;`)); } await db.execute(sql`SET FOREIGN_KEY_CHECKS = 1;`); console.log("๐Ÿงน Cleaned up existing data."); } catch (e) { console.warn("โš ๏ธ Cleanup warning (might be fresh DB):", e); } // --- 1. IAM & Roles --- console.log("๐Ÿ‘ค Seeding IAM..."); // Roles const roleMap = { admin: "role_admin", teacher: "role_teacher", student: "role_student", grade_head: "role_grade_head" }; await db.insert(roles).values([ { id: roleMap.admin, name: "admin", description: "System Administrator" }, { id: roleMap.teacher, name: "teacher", description: "Academic Instructor" }, { id: roleMap.student, name: "student", description: "Learner" }, { id: roleMap.grade_head, name: "grade_head", description: "Head of Grade Year" } ]); // Users const usersData = [ { id: "user_admin", name: "Admin User", email: "admin@next-edu.com", role: "admin", // Legacy field image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Admin" }, { id: "user_teacher_math", name: "Mr. Math", email: "math@next-edu.com", role: "teacher", image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Math" }, { id: "user_student_1", name: "Alice Student", email: "alice@next-edu.com", role: "student", image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Alice" } ]; await db.insert(users).values(usersData); // Assign Roles (RBAC) await db.insert(usersToRoles).values([ { userId: "user_admin", roleId: roleMap.admin }, { userId: "user_teacher_math", roleId: roleMap.teacher }, // Math teacher is also a Grade Head { userId: "user_teacher_math", roleId: roleMap.grade_head }, { userId: "user_student_1", roleId: roleMap.student }, ]); // --- 2. Knowledge Graph (Tree) --- console.log("๐Ÿง  Seeding Knowledge Graph..."); const kpMathId = createId(); const kpAlgebraId = createId(); const kpLinearId = createId(); await db.insert(knowledgePoints).values([ { id: kpMathId, name: "Mathematics", level: 0 }, { id: kpAlgebraId, name: "Algebra", parentId: kpMathId, level: 1 }, { id: kpLinearId, name: "Linear Equations", parentId: kpAlgebraId, level: 2 }, ]); // --- 3. Question Bank (Rich Content) --- console.log("๐Ÿ“š Seeding Question Bank..."); // 3.1 Simple Single Choice const qSimpleId = createId(); await db.insert(questions).values({ id: qSimpleId, authorId: "user_teacher_math", type: "single_choice", difficulty: 1, content: { text: "What is 2 + 2?", options: [ { id: "A", text: "3", isCorrect: false }, { id: "B", text: "4", isCorrect: true }, { id: "C", text: "5", isCorrect: false } ] } }); // Link to KP await db.insert(questionsToKnowledgePoints).values({ questionId: qSimpleId, knowledgePointId: kpLinearId // Just for demo }); // 3.2 Composite Question (Reading Comprehension) const qParentId = createId(); const qChild1Id = createId(); const qChild2Id = createId(); // Parent (Passage) await db.insert(questions).values({ id: qParentId, authorId: "user_teacher_math", type: "composite", difficulty: 3, content: { text: "Read the following passage about Algebra...\n(Long text here)...", assets: [] } }); // Children await db.insert(questions).values([ { id: qChild1Id, authorId: "user_teacher_math", parentId: qParentId, // <--- Key: Nested type: "single_choice", difficulty: 2, content: { text: "What is the main topic?", options: [ { id: "A", text: "Geometry", isCorrect: false }, { id: "B", text: "Algebra", isCorrect: true } ] } }, { id: qChild2Id, authorId: "user_teacher_math", parentId: qParentId, type: "text", difficulty: 4, content: { text: "Explain the concept of variables.", } } ]); // --- 4. Exams (New Structure) --- console.log("๐Ÿ“ Seeding Exams..."); const examId = createId(); const examStructure = [ { type: "group", title: "Part 1: Basics", children: [ { type: "question", questionId: qSimpleId, score: 10 } ] }, { type: "group", title: "Part 2: Reading", children: [ // For composite questions, we usually add the parent, and the system fetches children { type: "question", questionId: qParentId, score: 20 } ] } ]; await db.insert(exams).values({ id: examId, title: "Algebra Mid-Term 2025", description: "Comprehensive assessment", creatorId: "user_teacher_math", status: "published", startTime: new Date(), structure: examStructure as unknown }); // Link questions physically (Source of Truth) await db.insert(examQuestions).values([ { examId, questionId: qSimpleId, score: 10, order: 0 }, { examId, questionId: qParentId, score: 20, order: 1 }, // Note: Child questions are often implicitly included or explicitly added depending on logic. // For this seed, we assume linking Parent is enough for the relation, // but let's link children too for completeness if the query strategy requires it. { examId, questionId: qChild1Id, score: 0, order: 2 }, { examId, questionId: qChild2Id, score: 0, order: 3 }, ]); const end = performance.now(); console.log(`โœ… Seed completed in ${(end - start).toFixed(2)}ms`); process.exit(0); } seed().catch((err) => { console.error("โŒ Seed failed:", err); process.exit(1); });