Files
CICD/scripts/seed.ts
SpecialX e7c902e8e1
Some checks failed
CI / build-and-test (push) Failing after 1m31s
CI / deploy (push) Has been skipped
Module Update
2025-12-30 14:42:30 +08:00

237 lines
7.0 KiB
TypeScript

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);
});