Module Update
This commit is contained in:
236
scripts/seed.ts
Normal file
236
scripts/seed.ts
Normal file
@@ -0,0 +1,236 @@
|
||||
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 any // Bypass strict typing for seed
|
||||
});
|
||||
|
||||
// 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);
|
||||
});
|
||||
Reference in New Issue
Block a user