Module Update
Some checks failed
CI / build-and-test (push) Failing after 1m31s
CI / deploy (push) Has been skipped

This commit is contained in:
SpecialX
2025-12-30 14:42:30 +08:00
parent f1797265b2
commit e7c902e8e1
148 changed files with 19317 additions and 113 deletions

346
docs/scripts/seed-exams.ts Normal file
View File

@@ -0,0 +1,346 @@
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"])
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: type as any,
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"])
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: status as any,
})
// 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)
})