575 lines
23 KiB
TypeScript
575 lines
23 KiB
TypeScript
import {
|
|
mysqlTable,
|
|
varchar,
|
|
text,
|
|
timestamp,
|
|
int,
|
|
primaryKey,
|
|
index,
|
|
json,
|
|
mysqlEnum,
|
|
boolean,
|
|
foreignKey
|
|
} from "drizzle-orm/mysql-core";
|
|
import { createId } from "@paralleldrive/cuid2";
|
|
import type { AdapterAccountType } from "next-auth/adapters";
|
|
|
|
// --- Helper for ID generation (CUID2) ---
|
|
const id = (name: string) => varchar(name, { length: 128 }).notNull().$defaultFn(() => createId());
|
|
|
|
// --- 1. Users & Auth (Auth.js v5 Standard + RBAC) ---
|
|
|
|
export const users = mysqlTable("users", {
|
|
id: id("id").primaryKey(),
|
|
name: varchar("name", { length: 255 }),
|
|
email: varchar("email", { length: 255 }).notNull().unique(),
|
|
emailVerified: timestamp("emailVerified", { mode: "date" }),
|
|
image: varchar("image", { length: 255 }),
|
|
|
|
// Custom Role Field for RBAC (Default Role)
|
|
role: varchar("role", { length: 50 }).default("student"),
|
|
|
|
// Credentials Auth (Optional)
|
|
password: varchar("password", { length: 255 }),
|
|
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
emailIdx: index("email_idx").on(table.email),
|
|
}));
|
|
|
|
// Auth.js: Accounts (OAuth providers)
|
|
export const accounts = mysqlTable("accounts", {
|
|
userId: varchar("userId", { length: 128 })
|
|
.notNull()
|
|
.references(() => users.id, { onDelete: "cascade" }),
|
|
type: varchar("type", { length: 255 }).$type<AdapterAccountType>().notNull(),
|
|
provider: varchar("provider", { length: 255 }).notNull(),
|
|
providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
|
|
refresh_token: text("refresh_token"),
|
|
access_token: text("access_token"),
|
|
expires_at: int("expires_at"),
|
|
token_type: varchar("token_type", { length: 255 }),
|
|
scope: varchar("scope", { length: 255 }),
|
|
id_token: text("id_token"),
|
|
session_state: varchar("session_state", { length: 255 }),
|
|
},
|
|
(account) => ({
|
|
compoundKey: primaryKey({
|
|
columns: [account.provider, account.providerAccountId],
|
|
}),
|
|
userIdIdx: index("account_userId_idx").on(account.userId),
|
|
})
|
|
);
|
|
|
|
// Auth.js: Sessions
|
|
export const sessions = mysqlTable("sessions", {
|
|
sessionToken: varchar("sessionToken", { length: 255 }).primaryKey(),
|
|
userId: varchar("userId", { length: 128 })
|
|
.notNull()
|
|
.references(() => users.id, { onDelete: "cascade" }),
|
|
expires: timestamp("expires", { mode: "date" }).notNull(),
|
|
}, (table) => ({
|
|
userIdIdx: index("session_userId_idx").on(table.userId),
|
|
}));
|
|
|
|
// Auth.js: Verification Tokens
|
|
export const verificationTokens = mysqlTable("verificationTokens", {
|
|
identifier: varchar("identifier", { length: 255 }).notNull(),
|
|
token: varchar("token", { length: 255 }).notNull(),
|
|
expires: timestamp("expires", { mode: "date" }).notNull(),
|
|
}, (vt) => ({
|
|
compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }),
|
|
}));
|
|
|
|
// --- Custom RBAC Extensions ---
|
|
|
|
export const roles = mysqlTable("roles", {
|
|
id: id("id").primaryKey(),
|
|
name: varchar("name", { length: 50 }).notNull().unique(), // e.g., 'admin', 'teacher', 'student', 'grade_head'
|
|
description: varchar("description", { length: 255 }),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
});
|
|
|
|
// Many-to-Many: Users <-> Roles
|
|
// Solves: "A teacher can also be a Grade Head"
|
|
export const usersToRoles = mysqlTable("users_to_roles", {
|
|
userId: varchar("user_id", { length: 128 }).notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
roleId: varchar("role_id", { length: 128 }).notNull().references(() => roles.id, { onDelete: "cascade" }),
|
|
}, (table) => ({
|
|
pk: primaryKey({ columns: [table.userId, table.roleId] }),
|
|
userIdIdx: index("user_id_idx").on(table.userId),
|
|
}));
|
|
|
|
// --- 2. Knowledge Points (Tree Structure) ---
|
|
|
|
export const knowledgePoints = mysqlTable("knowledge_points", {
|
|
id: id("id").primaryKey(),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
description: text("description"),
|
|
|
|
// Tree Structure: Parent KP
|
|
parentId: varchar("parent_id", { length: 128 }), // Self-reference defined in relations
|
|
|
|
chapterId: varchar("chapter_id", { length: 128 }),
|
|
|
|
// Metadata for ordering or level
|
|
level: int("level").default(0),
|
|
order: int("order").default(0),
|
|
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
parentIdIdx: index("parent_id_idx").on(table.parentId),
|
|
chapterIdIdx: index("kp_chapter_id_idx").on(table.chapterId),
|
|
}));
|
|
|
|
// --- 3. Question Bank (Core) ---
|
|
|
|
export const questionTypeEnum = mysqlEnum("type", ["single_choice", "multiple_choice", "text", "judgment", "composite"]);
|
|
|
|
export const questions = mysqlTable("questions", {
|
|
id: id("id").primaryKey(),
|
|
|
|
// Content can be JSON to store rich text, images, etc. or just text.
|
|
// Using JSON for flexibility in a modern ed-tech app (e.g. SlateJS nodes).
|
|
content: json("content").notNull(),
|
|
|
|
type: questionTypeEnum.notNull(),
|
|
difficulty: int("difficulty").default(1), // 1-5
|
|
|
|
// Self-reference for "Infinite Nesting" (Parent Question)
|
|
// e.g., A reading comprehension passage (Parent) -> 5 sub-questions (Children)
|
|
parentId: varchar("parent_id", { length: 128 }),
|
|
|
|
authorId: varchar("author_id", { length: 128 }).notNull().references(() => users.id),
|
|
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
parentIdIdx: index("parent_id_idx").on(table.parentId), // Critical for querying children
|
|
authorIdIdx: index("author_id_idx").on(table.authorId),
|
|
// In a real large-scale system, we might add Full-Text Search index on content
|
|
}));
|
|
|
|
// Many-to-Many: Questions <-> Knowledge Points
|
|
export const questionsToKnowledgePoints = mysqlTable("questions_to_knowledge_points", {
|
|
questionId: varchar("question_id", { length: 128 }).notNull(),
|
|
knowledgePointId: varchar("knowledge_point_id", { length: 128 }).notNull(),
|
|
}, (table) => ({
|
|
pk: primaryKey({ columns: [table.questionId, table.knowledgePointId] }),
|
|
kpIdx: index("kp_idx").on(table.knowledgePointId), // For querying "All questions under this KP"
|
|
qFk: foreignKey({
|
|
columns: [table.questionId],
|
|
foreignColumns: [questions.id],
|
|
name: "q_kp_qid_fk"
|
|
}).onDelete("cascade"),
|
|
kpFk: foreignKey({
|
|
columns: [table.knowledgePointId],
|
|
foreignColumns: [knowledgePoints.id],
|
|
name: "q_kp_kpid_fk"
|
|
}).onDelete("cascade"),
|
|
}));
|
|
|
|
// --- 4. Academic / Teaching Flow ---
|
|
|
|
export const textbooks = mysqlTable("textbooks", {
|
|
id: id("id").primaryKey(),
|
|
title: varchar("title", { length: 255 }).notNull(),
|
|
subject: varchar("subject", { length: 100 }).notNull(),
|
|
grade: varchar("grade", { length: 50 }),
|
|
publisher: varchar("publisher", { length: 100 }),
|
|
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
});
|
|
|
|
export const chapters = mysqlTable("chapters", {
|
|
id: id("id").primaryKey(),
|
|
textbookId: varchar("textbook_id", { length: 128 }).notNull().references(() => textbooks.id, { onDelete: "cascade" }),
|
|
title: varchar("title", { length: 255 }).notNull(),
|
|
order: int("order").default(0),
|
|
|
|
// Chapters can also be nested
|
|
parentId: varchar("parent_id", { length: 128 }),
|
|
|
|
content: text("content"),
|
|
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
textbookIdx: index("textbook_idx").on(table.textbookId),
|
|
parentIdIdx: index("parent_id_idx").on(table.parentId),
|
|
}));
|
|
|
|
// --- 5. School Management ---
|
|
|
|
export const departments = mysqlTable("departments", {
|
|
id: id("id").primaryKey(),
|
|
name: varchar("name", { length: 255 }).notNull().unique(),
|
|
description: text("description"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
nameIdx: index("departments_name_idx").on(table.name),
|
|
}))
|
|
|
|
export const classrooms = mysqlTable("classrooms", {
|
|
id: id("id").primaryKey(),
|
|
name: varchar("name", { length: 255 }).notNull().unique(),
|
|
building: varchar("building", { length: 100 }),
|
|
floor: int("floor"),
|
|
capacity: int("capacity"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
nameIdx: index("classrooms_name_idx").on(table.name),
|
|
}))
|
|
|
|
export const academicYears = mysqlTable("academic_years", {
|
|
id: id("id").primaryKey(),
|
|
name: varchar("name", { length: 100 }).notNull().unique(),
|
|
startDate: timestamp("start_date", { mode: "date" }).notNull(),
|
|
endDate: timestamp("end_date", { mode: "date" }).notNull(),
|
|
isActive: boolean("is_active").default(false).notNull(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
nameIdx: index("academic_years_name_idx").on(table.name),
|
|
activeIdx: index("academic_years_active_idx").on(table.isActive),
|
|
}))
|
|
|
|
export const schools = mysqlTable("schools", {
|
|
id: id("id").primaryKey(),
|
|
name: varchar("name", { length: 255 }).notNull().unique(),
|
|
code: varchar("code", { length: 50 }).unique(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
nameIdx: index("schools_name_idx").on(table.name),
|
|
codeIdx: index("schools_code_idx").on(table.code),
|
|
}))
|
|
|
|
export const grades = mysqlTable("grades", {
|
|
id: id("id").primaryKey(),
|
|
schoolId: varchar("school_id", { length: 128 }).notNull(),
|
|
name: varchar("name", { length: 100 }).notNull(),
|
|
order: int("order").default(0).notNull(),
|
|
gradeHeadId: varchar("grade_head_id", { length: 128 }),
|
|
teachingHeadId: varchar("teaching_head_id", { length: 128 }),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
schoolIdx: index("grades_school_idx").on(table.schoolId),
|
|
schoolNameUnique: index("grades_school_name_uniq").on(table.schoolId, table.name),
|
|
gradeHeadIdx: index("grades_grade_head_idx").on(table.gradeHeadId),
|
|
teachingHeadIdx: index("grades_teaching_head_idx").on(table.teachingHeadId),
|
|
schoolFk: foreignKey({
|
|
columns: [table.schoolId],
|
|
foreignColumns: [schools.id],
|
|
name: "g_s_fk",
|
|
}).onDelete("cascade"),
|
|
gradeHeadFk: foreignKey({
|
|
columns: [table.gradeHeadId],
|
|
foreignColumns: [users.id],
|
|
name: "g_gh_fk",
|
|
}).onDelete("set null"),
|
|
teachingHeadFk: foreignKey({
|
|
columns: [table.teachingHeadId],
|
|
foreignColumns: [users.id],
|
|
name: "g_th_fk",
|
|
}).onDelete("set null"),
|
|
}))
|
|
|
|
// --- 6. Classes / Enrollment / Schedule ---
|
|
|
|
export const classEnrollmentStatusEnum = mysqlEnum("class_enrollment_status", ["active", "inactive"]);
|
|
|
|
export const classes = mysqlTable("classes", {
|
|
id: id("id").primaryKey(),
|
|
schoolName: varchar("school_name", { length: 255 }),
|
|
schoolId: varchar("school_id", { length: 128 }),
|
|
name: varchar("name", { length: 255 }).notNull(),
|
|
grade: varchar("grade", { length: 50 }).notNull(),
|
|
gradeId: varchar("grade_id", { length: 128 }),
|
|
homeroom: varchar("homeroom", { length: 50 }),
|
|
room: varchar("room", { length: 50 }),
|
|
invitationCode: varchar("invitation_code", { length: 6 }).unique(),
|
|
teacherId: varchar("teacher_id", { length: 128 }).notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
teacherIdx: index("classes_teacher_idx").on(table.teacherId),
|
|
gradeIdx: index("classes_grade_idx").on(table.grade),
|
|
schoolIdx: index("classes_school_idx").on(table.schoolId),
|
|
gradeIdIdx: index("classes_grade_id_idx").on(table.gradeId),
|
|
schoolFk: foreignKey({
|
|
columns: [table.schoolId],
|
|
foreignColumns: [schools.id],
|
|
name: "c_s_fk",
|
|
}).onDelete("set null"),
|
|
gradeFk: foreignKey({
|
|
columns: [table.gradeId],
|
|
foreignColumns: [grades.id],
|
|
name: "c_g_fk",
|
|
}).onDelete("set null"),
|
|
}));
|
|
|
|
export const classSubjectEnum = mysqlEnum("subject", ["语文", "数学", "英语", "美术", "体育", "科学", "社会", "音乐"]);
|
|
|
|
export const classSubjectTeachers = mysqlTable("class_subject_teachers", {
|
|
classId: varchar("class_id", { length: 128 }).notNull(),
|
|
subject: classSubjectEnum.notNull(),
|
|
teacherId: varchar("teacher_id", { length: 128 }).references(() => users.id, { onDelete: "set null" }),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
pk: primaryKey({ columns: [table.classId, table.subject] }),
|
|
classIdx: index("class_subject_teachers_class_idx").on(table.classId),
|
|
teacherIdx: index("class_subject_teachers_teacher_idx").on(table.teacherId),
|
|
classFk: foreignKey({
|
|
columns: [table.classId],
|
|
foreignColumns: [classes.id],
|
|
name: "cst_c_fk",
|
|
}).onDelete("cascade"),
|
|
}));
|
|
|
|
export const classEnrollments = mysqlTable("class_enrollments", {
|
|
classId: varchar("class_id", { length: 128 }).notNull(),
|
|
studentId: varchar("student_id", { length: 128 }).notNull(),
|
|
status: classEnrollmentStatusEnum.default("active").notNull(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
}, (table) => ({
|
|
pk: primaryKey({ columns: [table.classId, table.studentId] }),
|
|
classIdx: index("class_enrollments_class_idx").on(table.classId),
|
|
studentIdx: index("class_enrollments_student_idx").on(table.studentId),
|
|
classFk: foreignKey({
|
|
columns: [table.classId],
|
|
foreignColumns: [classes.id],
|
|
name: "ce_c_fk",
|
|
}).onDelete("cascade"),
|
|
studentFk: foreignKey({
|
|
columns: [table.studentId],
|
|
foreignColumns: [users.id],
|
|
name: "ce_s_fk",
|
|
}).onDelete("cascade"),
|
|
}));
|
|
|
|
export const classSchedule = mysqlTable("class_schedule", {
|
|
id: id("id").primaryKey(),
|
|
classId: varchar("class_id", { length: 128 }).notNull(),
|
|
weekday: int("weekday").notNull(),
|
|
startTime: varchar("start_time", { length: 5 }).notNull(),
|
|
endTime: varchar("end_time", { length: 5 }).notNull(),
|
|
course: varchar("course", { length: 255 }).notNull(),
|
|
location: varchar("location", { length: 100 }),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
classIdx: index("class_schedule_class_idx").on(table.classId),
|
|
classDayIdx: index("class_schedule_class_day_idx").on(table.classId, table.weekday),
|
|
classFk: foreignKey({
|
|
columns: [table.classId],
|
|
foreignColumns: [classes.id],
|
|
name: "cs_c_fk",
|
|
}).onDelete("cascade"),
|
|
}));
|
|
|
|
export const exams = mysqlTable("exams", {
|
|
id: id("id").primaryKey(),
|
|
title: varchar("title", { length: 255 }).notNull(),
|
|
description: text("description"),
|
|
|
|
/**
|
|
* Stores the hierarchical structure of the exam.
|
|
* Expected JSON Schema:
|
|
* type ExamStructure = Array<
|
|
* | { type: 'group', title: string, children: ExamStructure }
|
|
* | { type: 'question', questionId: string, score: number }
|
|
* >
|
|
*
|
|
* Note: This is for UI presentation/ordering.
|
|
* Real relational integrity is maintained in 'exam_questions' table.
|
|
*/
|
|
structure: json("structure"),
|
|
|
|
creatorId: varchar("creator_id", { length: 128 }).notNull().references(() => users.id),
|
|
|
|
startTime: timestamp("start_time"),
|
|
endTime: timestamp("end_time"),
|
|
|
|
// Status: draft, published, ongoing, finished
|
|
status: varchar("status", { length: 50 }).default("draft"),
|
|
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
});
|
|
|
|
// Linking Exams to Questions (Many-to-Many often, or One-to-Many if specific to exam)
|
|
// Usually questions are reused, so Many-to-Many
|
|
export const examQuestions = mysqlTable("exam_questions", {
|
|
examId: varchar("exam_id", { length: 128 }).notNull().references(() => exams.id, { onDelete: "cascade" }),
|
|
questionId: varchar("question_id", { length: 128 }).notNull().references(() => questions.id, { onDelete: "cascade" }),
|
|
score: int("score").default(0),
|
|
order: int("order").default(0),
|
|
}, (table) => ({
|
|
pk: primaryKey({ columns: [table.examId, table.questionId] }),
|
|
}));
|
|
|
|
export const examSubmissions = mysqlTable("exam_submissions", {
|
|
id: id("id").primaryKey(),
|
|
examId: varchar("exam_id", { length: 128 }).notNull().references(() => exams.id, { onDelete: "cascade" }),
|
|
studentId: varchar("student_id", { length: 128 }).notNull().references(() => users.id, { onDelete: "cascade" }),
|
|
|
|
score: int("score"), // Total score
|
|
status: varchar("status", { length: 50 }).default("started"), // started, submitted, graded
|
|
|
|
submittedAt: timestamp("submitted_at"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
examStudentIdx: index("exam_student_idx").on(table.examId, table.studentId),
|
|
}));
|
|
|
|
export const submissionAnswers = mysqlTable("submission_answers", {
|
|
id: id("id").primaryKey(),
|
|
submissionId: varchar("submission_id", { length: 128 }).notNull().references(() => examSubmissions.id, { onDelete: "cascade" }),
|
|
questionId: varchar("question_id", { length: 128 }).notNull().references(() => questions.id),
|
|
|
|
answerContent: json("answer_content"), // Student's answer
|
|
score: int("score"), // Score for this specific question
|
|
feedback: text("feedback"), // Teacher's feedback
|
|
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
submissionIdx: index("submission_idx").on(table.submissionId),
|
|
}));
|
|
|
|
export const homeworkAssignments = mysqlTable("homework_assignments", {
|
|
id: id("id").primaryKey(),
|
|
sourceExamId: varchar("source_exam_id", { length: 128 }).notNull(),
|
|
title: varchar("title", { length: 255 }).notNull(),
|
|
description: text("description"),
|
|
structure: json("structure"),
|
|
status: varchar("status", { length: 50 }).default("draft"),
|
|
creatorId: varchar("creator_id", { length: 128 }).notNull(),
|
|
availableAt: timestamp("available_at"),
|
|
dueAt: timestamp("due_at"),
|
|
allowLate: boolean("allow_late").default(false).notNull(),
|
|
lateDueAt: timestamp("late_due_at"),
|
|
maxAttempts: int("max_attempts").default(1).notNull(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
creatorIdx: index("hw_assignment_creator_idx").on(table.creatorId),
|
|
sourceExamIdx: index("hw_assignment_source_exam_idx").on(table.sourceExamId),
|
|
statusIdx: index("hw_assignment_status_idx").on(table.status),
|
|
sourceExamFk: foreignKey({
|
|
columns: [table.sourceExamId],
|
|
foreignColumns: [exams.id],
|
|
name: "hw_asg_exam_fk",
|
|
}).onDelete("cascade"),
|
|
creatorFk: foreignKey({
|
|
columns: [table.creatorId],
|
|
foreignColumns: [users.id],
|
|
name: "hw_asg_creator_fk",
|
|
}).onDelete("cascade"),
|
|
}));
|
|
|
|
export const homeworkAssignmentQuestions = mysqlTable("homework_assignment_questions", {
|
|
assignmentId: varchar("assignment_id", { length: 128 }).notNull(),
|
|
questionId: varchar("question_id", { length: 128 }).notNull(),
|
|
score: int("score").default(0),
|
|
order: int("order").default(0),
|
|
}, (table) => ({
|
|
pk: primaryKey({ columns: [table.assignmentId, table.questionId] }),
|
|
assignmentIdx: index("hw_assignment_questions_assignment_idx").on(table.assignmentId),
|
|
assignmentFk: foreignKey({
|
|
columns: [table.assignmentId],
|
|
foreignColumns: [homeworkAssignments.id],
|
|
name: "hw_aq_a_fk",
|
|
}).onDelete("cascade"),
|
|
questionFk: foreignKey({
|
|
columns: [table.questionId],
|
|
foreignColumns: [questions.id],
|
|
name: "hw_aq_q_fk",
|
|
}).onDelete("cascade"),
|
|
}));
|
|
|
|
export const homeworkAssignmentTargets = mysqlTable("homework_assignment_targets", {
|
|
assignmentId: varchar("assignment_id", { length: 128 }).notNull(),
|
|
studentId: varchar("student_id", { length: 128 }).notNull(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
}, (table) => ({
|
|
pk: primaryKey({ columns: [table.assignmentId, table.studentId] }),
|
|
assignmentIdx: index("hw_assignment_targets_assignment_idx").on(table.assignmentId),
|
|
studentIdx: index("hw_assignment_targets_student_idx").on(table.studentId),
|
|
assignmentFk: foreignKey({
|
|
columns: [table.assignmentId],
|
|
foreignColumns: [homeworkAssignments.id],
|
|
name: "hw_at_a_fk",
|
|
}).onDelete("cascade"),
|
|
studentFk: foreignKey({
|
|
columns: [table.studentId],
|
|
foreignColumns: [users.id],
|
|
name: "hw_at_s_fk",
|
|
}).onDelete("cascade"),
|
|
}));
|
|
|
|
export const homeworkSubmissions = mysqlTable("homework_submissions", {
|
|
id: id("id").primaryKey(),
|
|
assignmentId: varchar("assignment_id", { length: 128 }).notNull(),
|
|
studentId: varchar("student_id", { length: 128 }).notNull(),
|
|
attemptNo: int("attempt_no").default(1).notNull(),
|
|
score: int("score"),
|
|
status: varchar("status", { length: 50 }).default("started"),
|
|
startedAt: timestamp("started_at").defaultNow().notNull(),
|
|
submittedAt: timestamp("submitted_at"),
|
|
isLate: boolean("is_late").default(false).notNull(),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
assignmentStudentIdx: index("hw_assignment_student_idx").on(table.assignmentId, table.studentId),
|
|
assignmentFk: foreignKey({
|
|
columns: [table.assignmentId],
|
|
foreignColumns: [homeworkAssignments.id],
|
|
name: "hw_sub_a_fk",
|
|
}).onDelete("cascade"),
|
|
studentFk: foreignKey({
|
|
columns: [table.studentId],
|
|
foreignColumns: [users.id],
|
|
name: "hw_sub_student_fk",
|
|
}).onDelete("cascade"),
|
|
}));
|
|
|
|
export const homeworkAnswers = mysqlTable("homework_answers", {
|
|
id: id("id").primaryKey(),
|
|
submissionId: varchar("submission_id", { length: 128 }).notNull(),
|
|
questionId: varchar("question_id", { length: 128 }).notNull(),
|
|
answerContent: json("answer_content"),
|
|
score: int("score"),
|
|
feedback: text("feedback"),
|
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
|
}, (table) => ({
|
|
submissionIdx: index("hw_answer_submission_idx").on(table.submissionId),
|
|
submissionQuestionIdx: index("hw_answer_submission_question_idx").on(table.submissionId, table.questionId),
|
|
submissionFk: foreignKey({
|
|
columns: [table.submissionId],
|
|
foreignColumns: [homeworkSubmissions.id],
|
|
name: "hw_ans_sub_fk",
|
|
}).onDelete("cascade"),
|
|
questionFk: foreignKey({
|
|
columns: [table.questionId],
|
|
foreignColumns: [questions.id],
|
|
name: "hw_ans_q_fk",
|
|
}),
|
|
}));
|
|
|
|
// Re-export old courses table if needed or deprecate it.
|
|
// Assuming we are replacing the old simple schema with this robust one.
|
|
// But if there were existing tables, we might keep them or comment them out.
|
|
// For this task, I will overwrite completely as this is a "System Architect" redesign.
|