Module Update
This commit is contained in:
35
docs/scripts/reset-db.ts
Normal file
35
docs/scripts/reset-db.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import "dotenv/config"
|
||||
import { db } from "@/shared/db"
|
||||
import { sql } from "drizzle-orm"
|
||||
|
||||
async function reset() {
|
||||
console.log("🔥 Resetting database...")
|
||||
|
||||
// Disable foreign key checks
|
||||
await db.execute(sql`SET FOREIGN_KEY_CHECKS = 0;`)
|
||||
|
||||
// Get all table names
|
||||
const tables = await db.execute(sql`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE();
|
||||
`)
|
||||
|
||||
// Drop each table
|
||||
for (const row of (tables[0] as unknown as any[])) {
|
||||
const tableName = row.TABLE_NAME || row.table_name
|
||||
console.log(`Dropping table: ${tableName}`)
|
||||
await db.execute(sql.raw(`DROP TABLE IF EXISTS \`${tableName}\`;`))
|
||||
}
|
||||
|
||||
// Re-enable foreign key checks
|
||||
await db.execute(sql`SET FOREIGN_KEY_CHECKS = 1;`)
|
||||
|
||||
console.log("✅ Database reset complete.")
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
reset().catch((err) => {
|
||||
console.error("❌ Reset failed:", err)
|
||||
process.exit(1)
|
||||
})
|
||||
346
docs/scripts/seed-exams.ts
Normal file
346
docs/scripts/seed-exams.ts
Normal 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)
|
||||
})
|
||||
Reference in New Issue
Block a user