import "dotenv/config" import { db } from "@/shared/db" import { users, exams, questions, knowledgePoints, examSubmissions, examQuestions, submissionAnswers } from "@/shared/db/schema" import { createId } from "@paralleldrive/cuid2" import { faker } from "@faker-js/faker" import { eq } from "drizzle-orm" /** * Seed Script for Next_Edu * * Usage: * 1. Ensure DATABASE_URL is set in .env * 2. Run with tsx: npx tsx docs/scripts/seed-exams.ts */ const SUBJECTS = ["Mathematics", "Physics", "English", "Chemistry", "Biology"] const GRADES = ["Grade 10", "Grade 11", "Grade 12"] const DIFFICULTY = [1, 2, 3, 4, 5] async function seed() { console.log("🌱 Starting seed process...") // 1. Create a Teacher User if not exists const teacherEmail = "teacher@example.com" let teacherId = "user_teacher_123" const existingTeacher = await db.query.users.findFirst({ where: eq(users.email, teacherEmail) }) if (!existingTeacher) { console.log("Creating teacher user...") await db.insert(users).values({ id: teacherId, name: "Senior Teacher", email: teacherEmail, role: "teacher", image: "https://api.dicebear.com/7.x/avataaars/svg?seed=Teacher", }) } else { teacherId = existingTeacher.id console.log("Teacher user exists:", teacherId) } // 1b. Create Students console.log("Creating students...") const studentIds: string[] = [] for (let i = 0; i < 5; i++) { const sId = createId() studentIds.push(sId) await db.insert(users).values({ id: sId, name: faker.person.fullName(), email: faker.internet.email(), role: "student", image: `https://api.dicebear.com/7.x/avataaars/svg?seed=${sId}`, }) } // 2. Create Knowledge Points console.log("Creating knowledge points...") const kpIds: string[] = [] for (const subject of SUBJECTS) { for (let i = 0; i < 3; i++) { const kpId = createId() kpIds.push(kpId) await db.insert(knowledgePoints).values({ id: kpId, name: `${subject} - ${faker.science.unit()}`, description: faker.lorem.sentence(), level: 1, }) } } // 3. Create Questions console.log("Creating questions...") const questionIds: string[] = [] for (let i = 0; i < 50; i++) { const qId = createId() questionIds.push(qId) const type = faker.helpers.arrayElement([ "single_choice", "multiple_choice", "text", "judgment", ] as const) await db.insert(questions).values({ id: qId, content: { text: faker.lorem.paragraph(), options: type.includes("choice") ? [ { id: "A", text: faker.lorem.sentence(), isCorrect: true }, { id: "B", text: faker.lorem.sentence(), isCorrect: false }, { id: "C", text: faker.lorem.sentence(), isCorrect: false }, { id: "D", text: faker.lorem.sentence(), isCorrect: false }, ] : undefined }, type, difficulty: faker.helpers.arrayElement(DIFFICULTY), authorId: teacherId, }) } // 4. Create Exams & Submissions console.log("Creating exams and submissions...") for (let i = 0; i < 15; i++) { const examId = createId() const subject = faker.helpers.arrayElement(SUBJECTS) const grade = faker.helpers.arrayElement(GRADES) const status = faker.helpers.arrayElement(["draft", "published", "archived"] as const) const scheduledAt = faker.date.soon({ days: 30 }) const meta = { subject, grade, difficulty: faker.helpers.arrayElement(DIFFICULTY), totalScore: 100, durationMin: faker.helpers.arrayElement([45, 60, 90, 120]), questionCount: faker.number.int({ min: 10, max: 30 }), tags: [faker.word.sample(), faker.word.sample()], scheduledAt: scheduledAt.toISOString() } await db.insert(exams).values({ id: examId, title: `${subject} ${faker.helpers.arrayElement(["Midterm", "Final", "Quiz", "Unit Test"])}`, description: JSON.stringify(meta), creatorId: teacherId, startTime: scheduledAt, status, }) // Link some questions to this exam (random 5 questions) const selectedQuestions = faker.helpers.arrayElements(questionIds, 5) await db.insert(examQuestions).values( selectedQuestions.map((qId, idx) => ({ examId, questionId: qId, score: 20, // 5 * 20 = 100 order: idx })) ) // Create submissions for published exams if (status === "published") { const submittingStudents = faker.helpers.arrayElements(studentIds, faker.number.int({ min: 1, max: 3 })) for (const studentId of submittingStudents) { const submissionId = createId() const submissionStatus = faker.helpers.arrayElement(["submitted", "graded"]) await db.insert(examSubmissions).values({ id: submissionId, examId, studentId, score: submissionStatus === "graded" ? faker.number.int({ min: 60, max: 100 }) : null, status: submissionStatus, submittedAt: faker.date.recent(), }) // Generate answers for this submission for (const qId of selectedQuestions) { await db.insert(submissionAnswers).values({ id: createId(), submissionId: submissionId, questionId: qId, answerContent: { answer: faker.lorem.sentence() }, // Mock answer score: submissionStatus === "graded" ? faker.number.int({ min: 0, max: 20 }) : null, feedback: submissionStatus === "graded" ? faker.lorem.sentence() : null, }) } } } } // 5. Create a specific Primary School Chinese Exam (小学语文) console.log("Creating Primary School Chinese Exam...") const chineseExamId = createId() const chineseQuestions = [] // 5a. Pinyin Questions const pinyinQ1 = createId() const pinyinQ2 = createId() chineseQuestions.push({ id: pinyinQ1, score: 5 }, { id: pinyinQ2, score: 5 }) await db.insert(questions).values([ { id: pinyinQ1, content: { text: "看拼音写词语:chūn tiān ( )" }, type: "text", difficulty: 1, authorId: teacherId, }, { id: pinyinQ2, content: { text: "看拼音写词语:huā duǒ ( )" }, type: "text", difficulty: 1, authorId: teacherId, } ]) // 5b. Vocabulary Questions const vocabQ1 = createId() const vocabQ2 = createId() chineseQuestions.push({ id: vocabQ1, score: 5 }, { id: vocabQ2, score: 5 }) await db.insert(questions).values([ { id: vocabQ1, content: { text: "选词填空:今天天气真( )。", options: [ { id: "A", text: "美好", isCorrect: false }, { id: "B", text: "晴朗", isCorrect: true }, { id: "C", text: "快乐", isCorrect: false } ] }, type: "single_choice", difficulty: 2, authorId: teacherId, }, { id: vocabQ2, content: { text: "下列词语中,书写正确的是( )。", options: [ { id: "A", text: "漂扬", isCorrect: false }, { id: "B", text: "飘扬", isCorrect: true }, { id: "C", text: "票扬", isCorrect: false } ] }, type: "single_choice", difficulty: 2, authorId: teacherId, } ]) // 5c. Reading Comprehension Questions const readingQ1 = createId() const readingQ2 = createId() chineseQuestions.push({ id: readingQ1, score: 10 }, { id: readingQ2, score: 10 }) await db.insert(questions).values([ { id: readingQ1, content: { text: "阅读短文《小兔子乖乖》,回答问题:\n\n小兔子乖乖,把门儿开开...\n\n文中提到的动物是?", options: [ { id: "A", text: "大灰狼", isCorrect: false }, { id: "B", text: "小兔子", isCorrect: true }, { id: "C", text: "小花猫", isCorrect: false } ] }, type: "single_choice", difficulty: 3, authorId: teacherId, }, { id: readingQ2, content: { text: "请用一句话形容小兔子。" }, type: "text", difficulty: 3, authorId: teacherId, } ]) // 5d. Construct Exam Structure const chineseExamStructure = [ { id: createId(), type: "group", title: "第一部分:基础知识", children: [ { id: createId(), type: "group", title: "一、看拼音写词语", children: [ { id: createId(), type: "question", questionId: pinyinQ1, score: 5 }, { id: createId(), type: "question", questionId: pinyinQ2, score: 5 } ] }, { id: createId(), type: "group", title: "二、词语积累", children: [ { id: createId(), type: "question", questionId: vocabQ1, score: 5 }, { id: createId(), type: "question", questionId: vocabQ2, score: 5 } ] } ] }, { id: createId(), type: "group", title: "第二部分:阅读理解", children: [ { id: createId(), type: "group", title: "三、短文阅读", children: [ { id: createId(), type: "question", questionId: readingQ1, score: 10 }, { id: createId(), type: "question", questionId: readingQ2, score: 10 } ] } ] } ] await db.insert(exams).values({ id: chineseExamId, title: "小学语文三年级上册期末考试", description: JSON.stringify({ subject: "Chinese", grade: "Grade 3", difficulty: 3, totalScore: 40, durationMin: 90, questionCount: 6, tags: ["期末", "语文", "三年级"] }), structure: chineseExamStructure, creatorId: teacherId, status: "published", startTime: new Date(), }) // Link questions to exam await db.insert(examQuestions).values( chineseQuestions.map((q, idx) => ({ examId: chineseExamId, questionId: q.id, score: q.score, order: idx })) ) console.log("✅ Seed completed successfully!") process.exit(0) } seed().catch((err) => { console.error("❌ Seed failed:", err) process.exit(1) })