refactor: fix remaining P2 architecture issues
Fix P2-6: proxy.ts now uses Permissions constants instead of hardcoded strings Fix P2-7: useA11yId file no longer exists (use-aria-live.ts already in hooks/) Fix P2-8: schema.ts section numbering reordered to continuous 1-24 Fix P2-11: announcements dead code void wasPublished already removed Fix P2-17: app-sidebar.tsx uses hasRole() instead of permission-based role inference Fix P2-18: scheduling/actions.ts removes trailing re-export of data-access; 4 pages now import directly from data-access Sync architecture docs 004 and 005
This commit is contained in:
@@ -1410,19 +1410,19 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/*
|
|||||||
| ~~P2-3~~ | ~~`shared/lib/ai.ts` 218 行,混合 5 类职责~~ ✅ 已修复(P2-2 已拆分为 `ai/` 目录) | shared |
|
| ~~P2-3~~ | ~~`shared/lib/ai.ts` 218 行,混合 5 类职责~~ ✅ 已修复(P2-2 已拆分为 `ai/` 目录) | shared |
|
||||||
| P2-4 | `onboarding-gate.tsx` 业务逻辑泄漏到 shared | shared |
|
| P2-4 | `onboarding-gate.tsx` 业务逻辑泄漏到 shared | shared |
|
||||||
| P2-5 | `global-search.tsx` 业务类型硬编码在 shared | shared |
|
| P2-5 | `global-search.tsx` 业务类型硬编码在 shared | shared |
|
||||||
| P2-6 | `proxy.ts` 硬编码权限字符串,未复用 Permissions 常量 | proxy |
|
| ~~P2-6~~ | ~~`proxy.ts` 硬编码权限字符串,未复用 Permissions 常量~~ ✅ 已修复(改用 `Permissions` 常量) | proxy |
|
||||||
| P2-7 | `useA11yId` Hook 错放在 lib/ 而非 hooks/ | shared |
|
| ~~P2-7~~ | ~~`useA11yId` Hook 错放在 lib/ 而非 hooks/~~ ✅ 已修复(文件已不存在;`use-aria-live.ts` 已在 `hooks/` 目录) | shared |
|
||||||
| P2-8 | `schema.ts` 分节编号混乱(section 12 出现在 14b 之后) | shared/db |
|
| ~~P2-8~~ | ~~`schema.ts` 分节编号混乱(section 12 出现在 14b 之后)~~ ✅ 已修复(重新编号为连续 1-24) | shared/db |
|
||||||
| P2-9 | `audit/actions.ts` Excel 导出逻辑内联 | audit |
|
| P2-9 | `audit/actions.ts` Excel 导出逻辑内联 | audit |
|
||||||
| P2-10 | school 模块审计日志不一致(仅 school 实体记录) | school |
|
| P2-10 | school 模块审计日志不一致(仅 school 实体记录) | school |
|
||||||
| P2-11 | `announcements` 死代码 `void wasPublished` | announcements |
|
| ~~P2-11~~ | ~~`announcements` 死代码 `void wasPublished`~~ ✅ 已修复(代码中已不存在) | announcements |
|
||||||
| ~~P2-12~~ | ~~`announcements` 权限模式不一致(requireAuth vs requirePermission)~~ ✅ 已修复 | announcements |
|
| ~~P2-12~~ | ~~`announcements` 权限模式不一致(requireAuth vs requirePermission)~~ ✅ 已修复 | announcements |
|
||||||
| P2-13 | `files` try-catch 吞错误 | files |
|
| P2-13 | `files` try-catch 吞错误 | files |
|
||||||
| P2-14 | `elective` runLottery 使用 Math.random | elective |
|
| P2-14 | `elective` runLottery 使用 Math.random | elective |
|
||||||
| P2-15 | `elective` selectCourse FCFS 并发超卖风险 | elective |
|
| P2-15 | `elective` selectCourse FCFS 并发超卖风险 | elective |
|
||||||
| P2-16 | `diagnostic` 班级报告 studentId 字段复用 | diagnostic |
|
| P2-16 | `diagnostic` 班级报告 studentId 字段复用 | diagnostic |
|
||||||
| P2-17 | `layout` 用权限反推角色 | layout |
|
| ~~P2-17~~ | ~~`layout` 用权限反推角色~~ ✅ 已修复(`app-sidebar.tsx` 改用 `hasRole()` 判断角色) | layout |
|
||||||
| P2-18 | `scheduling/actions.ts` 末尾 re-export data-access | scheduling |
|
| ~~P2-18~~ | ~~`scheduling/actions.ts` 末尾 re-export data-access~~ ✅ 已修复(移除 re-export,4 个页面改为从 `data-access` 导入) | scheduling |
|
||||||
| P2-19 | `ExamAssembly` / `ExamPreviewQuestionEditor` 10 个 props | exams |
|
| P2-19 | `ExamAssembly` / `ExamPreviewQuestionEditor` 10 个 props | exams |
|
||||||
| P2-20 | ~~`homework/data-access.getDemoStudentUser` 使用 `auth()` 而非 auth-guard~~ ✅ 已修复(已迁移至 `users/data-access.getCurrentStudentUser`,6 个 student 页面改用 users 模块;`elective` 页面改用 `getAuthContext()`;homework 保留 re-export 向后兼容) | homework |
|
| P2-20 | ~~`homework/data-access.getDemoStudentUser` 使用 `auth()` 而非 auth-guard~~ ✅ 已修复(已迁移至 `users/data-access.getCurrentStudentUser`,6 个 student 页面改用 users 模块;`elective` 页面改用 `getAuthContext()`;homework 保留 re-export 向后兼容) | homework |
|
||||||
|
|
||||||
@@ -1449,11 +1449,11 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/*
|
|||||||
|
|
||||||
### 中期执行(P2)
|
### 中期执行(P2)
|
||||||
14. ~~建立模块间数据访问规范(通过对方 data-access 或导出查询函数)~~ ✅ 已完成(P1-1 修复)
|
14. ~~建立模块间数据访问规范(通过对方 data-access 或导出查询函数)~~ ✅ 已完成(P1-1 修复)
|
||||||
15. `schema.ts` 按业务域分节
|
15. ~~`schema.ts` 按业务域分节~~ ✅ 已完成(P2-8 修复:重新编号为连续 1-24,消除 8b/14b/乱序问题)
|
||||||
16. 拆分 `exams/ai-pipeline.ts`
|
16. 拆分 `exams/ai-pipeline.ts`
|
||||||
17. ~~拆分 `shared/lib/ai.ts`~~ ✅ 已完成(P2-2,commit 6588f74,拆分为 `ai/` 目录 6 个文件,原 ai.ts 保留为重导出)
|
17. ~~拆分 `shared/lib/ai.ts`~~ ✅ 已完成(P2-2,commit 6588f74,拆分为 `ai/` 目录 6 个文件,原 ai.ts 保留为重导出)
|
||||||
18. shared 层业务逻辑下沉到 modules 层
|
18. shared 层业务逻辑下沉到 modules 层
|
||||||
19. 代码质量问题逐项修复(✅ 大部分已修复:React.cache 包装、Promise.all 并行化、错误吞没清理、非空断言清理、函数返回类型补齐、重复代码提取、合并 filter 遍历、Set/Map 优化)
|
19. 代码质量问题逐项修复(✅ 大部分已修复:React.cache 包装、Promise.all 并行化、错误吞没清理、非空断言清理、函数返回类型补齐、重复代码提取、合并 filter 遍历、Set/Map 优化、P2-6 proxy.ts 权限常量复用、P2-11 announcements 死代码清理、P2-17 layout 角色判断、P2-18 scheduling re-export 移除)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"generatedAt": "2026-06-17",
|
"generatedAt": "2026-06-17",
|
||||||
"formatVersion": "1.1",
|
"formatVersion": "1.1",
|
||||||
"rule": "每次文件修改后须同步更新本文件",
|
"rule": "每次文件修改后须同步更新本文件",
|
||||||
"lastUpdate": "P1-1 已修复:所有跨模块直查已改为通过对方 data-access 接口(homework/grades/parent/diagnostic/elective/proctoring/notifications/scheduling/classes 模块);exams 模块 getSubjectsAction/getGradesAction 改为调用 school data-access;questions 模块 getKnowledgePointOptionsAction 改为调用 textbooks data-access;grades 模块多处直查 classes/classEnrollments/subjects/users 改为调用对应模块 data-access;classes 模块 actions 直查 grades 表改为调用 school data-access,getSessionTeacherId 改为通过 auth-guard.getAuthContext;attendance 模块 getClassStudentsForAttendance 改为通过 classes data-access;scheduling 模块 autoScheduleAction 直查 users 改为通过 users data-access;notifications 模块 sendClassNotificationAction 直查 classes/classEnrollments 改为通过 classes data-access;proctoring 模块跨模块直查 exams/examSubmissions/users 改为通过 exams/users data-access;diagnostic 模块 updateMasteryFromSubmission 跨模块直查 4 张表改为通过 exams/questions data-access;dashboard 教师仪表盘直查 users 改为通过 users data-access;users 模块 updateUserProfile 绕过 data-access 已下沉;P2-20 已修复:getDemoStudentUser 从 homework 模块迁移至 users 模块 getCurrentStudentUser(homework 保留 re-export 向后兼容),6 个 student 页面(dashboard/assignments/assignments-[assignmentId]/courses/textbooks/textbooks-[id]/schedule)改用 users 模块,消除对 homework 的虚假依赖;student/elective 改用 getAuthContext();shared/types/action-state.ts 移除分号修复 Prettier 违规;shared/types/permissions.ts EXAM_PROCTOR_READ 字符串从 exam:proctor_read 改为 exam:proctor:read 统一命名;为 ActionState/DataScope/AuthContext 添加 JSDoc 注释;P0-2 已修复:shared/lib ↔ auth 循环依赖已解决,新增 shared/lib/session.ts 单一入口封装 getSession()(dynamic import 打破静态循环),audit-logger/change-logger/auth-guard 改为通过 session.ts 获取 session;P1-6 已修复:http-utils.ts 新增 getUserAgent(),audit-logger/change-logger/login-logger 删除本地重复 IP/UA 提取逻辑,统一复用 resolveClientIp/getUserAgent;P0-1 已修复:exams/data-access.persistAiGeneratedExamDraft 改为调用 questions/data-access.createQuestionWithRelations,不再直查 questions 表;P0-2 已修复:exams/data-access.getExams/getExamById/getExamsDashboardStats 改为调用 classes/data-access.getClassGradeIdsByClassIds,不再直查 classes 表;P1-2 已修复:exams/homework/questions/announcements actions 层 DB 操作下沉到 data-access;P2-2 已修复:ai.ts 拆分为 ai/ 目录;P0-8 已修复:school actions 层 DB 操作下沉到 data-access,logAudit 改为 after() 异步非阻塞;P0-7 已修复:classes/data-access-stats.ts 和 data-access-students.ts 不再直查 homework/exams 表,改为调用新增的 homework/data-access-classes.ts 暴露的 7 个函数;新增 diagnostic/schema.ts(6 个 Zod schema)和 classes/schema.ts(13 个 Zod schema),actions.ts 中手动 typeof 校验全部替换为 Zod safeParse"
|
"lastUpdate": "P2-6/P2-7/P2-8/P2-11/P2-17/P2-18 已修复:proxy.ts 改用 Permissions 常量替代硬编码字符串;useA11yId 文件已不存在(use-aria-live.ts 已在 hooks/ 目录);schema.ts 分节编号重新编号为连续 1-24,消除 8b/14b/乱序问题;announcements 死代码 void wasPublished 已不存在;layout/app-sidebar.tsx 改用 hasRole() 判断角色,不再用权限反推角色;scheduling/actions.ts 移除末尾 re-export data-access,4 个页面改为从 data-access 直接导入;P1-1 已修复:所有跨模块直查已改为通过对方 data-access 接口(homework/grades/parent/diagnostic/elective/proctoring/notifications/scheduling/classes 模块);P0-2 已修复:shared/lib ↔ auth 循环依赖已解决,新增 shared/lib/session.ts 单一入口;P1-6 已修复:http-utils.ts 统一 IP/UA 提取;P0-1/P0-2/P0-3/P0-4/P0-5/P0-7/P0-8 已修复;P1-2 已修复:actions 层 DB 操作下沉到 data-access;P2-2/P2-3/P2-12/P2-20 已修复"
|
||||||
},
|
},
|
||||||
"architectureOverview": {
|
"architectureOverview": {
|
||||||
"layers": [
|
"layers": [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { CalendarClock, ClipboardList, Settings2 } from "lucide-react"
|
|||||||
|
|
||||||
import { Button } from "@/shared/components/ui/button"
|
import { Button } from "@/shared/components/ui/button"
|
||||||
import { EmptyState } from "@/shared/components/ui/empty-state"
|
import { EmptyState } from "@/shared/components/ui/empty-state"
|
||||||
import { getAdminClassesForScheduling } from "@/modules/scheduling/actions"
|
import { getAdminClassesForScheduling } from "@/modules/scheduling/data-access"
|
||||||
import { AutoSchedulePanel } from "@/modules/scheduling/components/auto-schedule-panel"
|
import { AutoSchedulePanel } from "@/modules/scheduling/components/auto-schedule-panel"
|
||||||
|
|
||||||
export const dynamic = "force-dynamic"
|
export const dynamic = "force-dynamic"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { EmptyState } from "@/shared/components/ui/empty-state"
|
|||||||
import {
|
import {
|
||||||
getAdminClassesForScheduling,
|
getAdminClassesForScheduling,
|
||||||
getScheduleChanges,
|
getScheduleChanges,
|
||||||
} from "@/modules/scheduling/actions"
|
} from "@/modules/scheduling/data-access"
|
||||||
import { ScheduleChangeList } from "@/modules/scheduling/components/schedule-change-list"
|
import { ScheduleChangeList } from "@/modules/scheduling/components/schedule-change-list"
|
||||||
import { ScheduleConflictsView } from "@/modules/scheduling/components/schedule-conflicts-view"
|
import { ScheduleConflictsView } from "@/modules/scheduling/components/schedule-conflicts-view"
|
||||||
import type { ScheduleChangeStatus } from "@/modules/scheduling/types"
|
import type { ScheduleChangeStatus } from "@/modules/scheduling/types"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { EmptyState } from "@/shared/components/ui/empty-state"
|
|||||||
import {
|
import {
|
||||||
getAdminClassesForScheduling,
|
getAdminClassesForScheduling,
|
||||||
getSchedulingRules,
|
getSchedulingRules,
|
||||||
} from "@/modules/scheduling/actions"
|
} from "@/modules/scheduling/data-access"
|
||||||
import { SchedulingRulesForm } from "@/modules/scheduling/components/scheduling-rules-form"
|
import { SchedulingRulesForm } from "@/modules/scheduling/components/scheduling-rules-form"
|
||||||
|
|
||||||
export const dynamic = "force-dynamic"
|
export const dynamic = "force-dynamic"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
getAdminClassesForScheduling,
|
getAdminClassesForScheduling,
|
||||||
getScheduleChanges,
|
getScheduleChanges,
|
||||||
getTeachersForScheduling,
|
getTeachersForScheduling,
|
||||||
} from "@/modules/scheduling/actions"
|
} from "@/modules/scheduling/data-access"
|
||||||
import { ScheduleChangeForm } from "@/modules/scheduling/components/schedule-change-form"
|
import { ScheduleChangeForm } from "@/modules/scheduling/components/schedule-change-form"
|
||||||
import { ScheduleChangeList } from "@/modules/scheduling/components/schedule-change-list"
|
import { ScheduleChangeList } from "@/modules/scheduling/components/schedule-change-list"
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
} from "@/shared/components/ui/tooltip"
|
} from "@/shared/components/ui/tooltip"
|
||||||
import { cn } from "@/shared/lib/utils"
|
import { cn } from "@/shared/lib/utils"
|
||||||
import { usePermission } from "@/shared/hooks"
|
import { usePermission } from "@/shared/hooks"
|
||||||
import { Permissions, type Permission } from "@/shared/types/permissions"
|
import { type Permission } from "@/shared/types/permissions"
|
||||||
import { useSidebar } from "./sidebar-provider"
|
import { useSidebar } from "./sidebar-provider"
|
||||||
import { NAV_CONFIG, Role } from "../config/navigation"
|
import { NAV_CONFIG, Role } from "../config/navigation"
|
||||||
|
|
||||||
@@ -32,11 +32,11 @@ export function AppSidebar({ mode }: AppSidebarProps) {
|
|||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const { permissions, hasRole } = usePermission()
|
const { permissions, hasRole } = usePermission()
|
||||||
|
|
||||||
// Determine which role's nav config to use based on permissions
|
// Determine which role's nav config to use based on session roles
|
||||||
let currentRole: Role = "teacher"
|
let currentRole: Role = "teacher"
|
||||||
if (permissions.includes(Permissions.SCHOOL_MANAGE)) {
|
if (hasRole("admin")) {
|
||||||
currentRole = "admin"
|
currentRole = "admin"
|
||||||
} else if (permissions.includes(Permissions.HOMEWORK_SUBMIT) && !permissions.includes(Permissions.EXAM_CREATE)) {
|
} else if (hasRole("student")) {
|
||||||
currentRole = "student"
|
currentRole = "student"
|
||||||
} else if (hasRole("parent")) {
|
} else if (hasRole("parent")) {
|
||||||
currentRole = "parent"
|
currentRole = "parent"
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import {
|
|||||||
createScheduleChange,
|
createScheduleChange,
|
||||||
updateScheduleChangeStatus,
|
updateScheduleChangeStatus,
|
||||||
getClassConflicts,
|
getClassConflicts,
|
||||||
getAdminClassesForScheduling,
|
|
||||||
getTeachersForScheduling,
|
|
||||||
getClassroomsForScheduling,
|
getClassroomsForScheduling,
|
||||||
getClassSubjectsForScheduling,
|
getClassSubjectsForScheduling,
|
||||||
replaceClassSchedule,
|
replaceClassSchedule,
|
||||||
@@ -280,13 +278,3 @@ export async function getClassConflictsAction(
|
|||||||
return { success: false, message: "Unexpected error" }
|
return { success: false, message: "Unexpected error" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-export data-access for server pages
|
|
||||||
export {
|
|
||||||
getSchedulingRules,
|
|
||||||
getScheduleChanges,
|
|
||||||
getClassConflicts,
|
|
||||||
getAdminClassesForScheduling,
|
|
||||||
getTeachersForScheduling,
|
|
||||||
getClassroomsForScheduling,
|
|
||||||
}
|
|
||||||
|
|||||||
14
src/proxy.ts
14
src/proxy.ts
@@ -2,20 +2,22 @@ import { NextResponse } from "next/server"
|
|||||||
import type { NextRequest } from "next/server"
|
import type { NextRequest } from "next/server"
|
||||||
import { getToken } from "next-auth/jwt"
|
import { getToken } from "next-auth/jwt"
|
||||||
|
|
||||||
|
import { Permissions } from "@/shared/types/permissions"
|
||||||
|
|
||||||
// Route prefix → minimum required permission
|
// Route prefix → minimum required permission
|
||||||
// Note: /admin/announcements is covered by /admin prefix (requires school:manage)
|
// Note: /admin/announcements is covered by /admin prefix (requires school:manage)
|
||||||
// Note: /announcements is accessible to all authenticated users (no permission entry needed)
|
// Note: /announcements is accessible to all authenticated users (no permission entry needed)
|
||||||
const ROUTE_PERMISSIONS: Record<string, string> = {
|
const ROUTE_PERMISSIONS: Record<string, string> = {
|
||||||
"/admin": "school:manage",
|
"/admin": Permissions.SCHOOL_MANAGE,
|
||||||
"/teacher": "exam:read",
|
"/teacher": Permissions.EXAM_READ,
|
||||||
"/student": "homework:submit",
|
"/student": Permissions.HOMEWORK_SUBMIT,
|
||||||
"/parent": "exam:read",
|
"/parent": Permissions.EXAM_READ,
|
||||||
"/management": "grade:manage",
|
"/management": Permissions.GRADE_MANAGE,
|
||||||
}
|
}
|
||||||
|
|
||||||
// API route prefix → required permission
|
// API route prefix → required permission
|
||||||
const API_PERMISSIONS: Record<string, string> = {
|
const API_PERMISSIONS: Record<string, string> = {
|
||||||
"/api/ai/chat": "ai:chat",
|
"/api/ai/chat": Permissions.AI_CHAT,
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveDefaultPath(roles: string[]): string {
|
function resolveDefaultPath(roles: string[]): string {
|
||||||
|
|||||||
@@ -418,7 +418,7 @@ export const classSchedule = mysqlTable("class_schedule", {
|
|||||||
}).onDelete("cascade"),
|
}).onDelete("cascade"),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- P2: Exam Proctoring (考试监考) ---
|
// --- 7. Exam Proctoring Config (考试监考配置) ---
|
||||||
|
|
||||||
export const examModeEnum = mysqlEnum("exam_mode", ["homework", "timed", "proctored"]);
|
export const examModeEnum = mysqlEnum("exam_mode", ["homework", "timed", "proctored"]);
|
||||||
export const proctoringEventTypeEnum = mysqlEnum("event_type", [
|
export const proctoringEventTypeEnum = mysqlEnum("event_type", [
|
||||||
@@ -650,7 +650,7 @@ export const aiProviders = mysqlTable("ai_providers", {
|
|||||||
defaultIdx: index("ai_provider_default_idx").on(table.isDefault),
|
defaultIdx: index("ai_provider_default_idx").on(table.isDefault),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- 7. Announcements ---
|
// --- 8. Announcements ---
|
||||||
|
|
||||||
export const announcementTypeEnum = mysqlEnum("type", ["school", "grade", "class"]);
|
export const announcementTypeEnum = mysqlEnum("type", ["school", "grade", "class"]);
|
||||||
export const announcementStatusEnum = mysqlEnum("status", ["draft", "published", "archived"]);
|
export const announcementStatusEnum = mysqlEnum("status", ["draft", "published", "archived"]);
|
||||||
@@ -675,7 +675,7 @@ export const announcements = mysqlTable("announcements", {
|
|||||||
targetClassIdx: index("announcements_target_class_idx").on(table.targetClassId),
|
targetClassIdx: index("announcements_target_class_idx").on(table.targetClassId),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- 8. Audit & Login Logs ---
|
// --- 9. Audit & Login Logs ---
|
||||||
|
|
||||||
export const auditLogStatusEnum = mysqlEnum("status", ["success", "failure"]);
|
export const auditLogStatusEnum = mysqlEnum("status", ["success", "failure"]);
|
||||||
|
|
||||||
@@ -721,7 +721,7 @@ export const loginLogs = mysqlTable("login_logs", {
|
|||||||
createdAtIdx: index("login_logs_created_at_idx").on(table.createdAt),
|
createdAtIdx: index("login_logs_created_at_idx").on(table.createdAt),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- 8b. Data Change Logs (数据变更日志) ---
|
// --- 10. Data Change Logs (数据变更日志) ---
|
||||||
|
|
||||||
export const dataChangeLogActionEnum = mysqlEnum("action", ["create", "update", "delete"]);
|
export const dataChangeLogActionEnum = mysqlEnum("action", ["create", "update", "delete"]);
|
||||||
|
|
||||||
@@ -749,7 +749,7 @@ export const dataChangeLogs = mysqlTable("data_change_logs", {
|
|||||||
// But if there were existing tables, we might keep them or comment them out.
|
// 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.
|
// For this task, I will overwrite completely as this is a "System Architect" redesign.
|
||||||
|
|
||||||
// --- 9. Grade Records (成绩录入) ---
|
// --- 11. Grade Records (成绩录入) ---
|
||||||
|
|
||||||
export const gradeRecordTypeEnum = mysqlEnum("type", ["exam", "quiz", "homework", "other"]);
|
export const gradeRecordTypeEnum = mysqlEnum("type", ["exam", "quiz", "homework", "other"]);
|
||||||
export const gradeRecordSemesterEnum = mysqlEnum("semester", ["1", "2"]);
|
export const gradeRecordSemesterEnum = mysqlEnum("semester", ["1", "2"]);
|
||||||
@@ -799,7 +799,7 @@ export const gradeRecords = mysqlTable("grade_records", {
|
|||||||
}).onDelete("cascade"),
|
}).onDelete("cascade"),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- 10. File Attachments (文件附件) ---
|
// --- 12. File Attachments (文件附件) ---
|
||||||
|
|
||||||
export const fileAttachments = mysqlTable("file_attachments", {
|
export const fileAttachments = mysqlTable("file_attachments", {
|
||||||
id: varchar("id", { length: 128 }).primaryKey(),
|
id: varchar("id", { length: 128 }).primaryKey(),
|
||||||
@@ -819,7 +819,7 @@ export const fileAttachments = mysqlTable("file_attachments", {
|
|||||||
createdAtIdx: index("file_attachments_created_at_idx").on(table.createdAt),
|
createdAtIdx: index("file_attachments_created_at_idx").on(table.createdAt),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- 11. Course Plans (课程计划) ---
|
// --- 13. Course Plans (课程计划) ---
|
||||||
|
|
||||||
export const coursePlanStatusEnum = mysqlEnum("status", ["planning", "active", "completed", "paused"]);
|
export const coursePlanStatusEnum = mysqlEnum("status", ["planning", "active", "completed", "paused"]);
|
||||||
export const coursePlanSemesterEnum = mysqlEnum("semester", ["1", "2"]);
|
export const coursePlanSemesterEnum = mysqlEnum("semester", ["1", "2"]);
|
||||||
@@ -893,7 +893,7 @@ export const coursePlanItems = mysqlTable("course_plan_items", {
|
|||||||
}).onDelete("cascade"),
|
}).onDelete("cascade"),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- 13. Messages (站内消息) ---
|
// --- 14. Messages (站内消息) ---
|
||||||
|
|
||||||
export const messages = mysqlTable("messages", {
|
export const messages = mysqlTable("messages", {
|
||||||
id: id("id").primaryKey(),
|
id: id("id").primaryKey(),
|
||||||
@@ -913,7 +913,7 @@ export const messages = mysqlTable("messages", {
|
|||||||
receiverReadIdx: index("messages_receiver_read_idx").on(table.receiverId, table.isRead),
|
receiverReadIdx: index("messages_receiver_read_idx").on(table.receiverId, table.isRead),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- 14. Message Notifications (消息通知) ---
|
// --- 15. Message Notifications (消息通知) ---
|
||||||
|
|
||||||
export const messageNotifications = mysqlTable("message_notifications", {
|
export const messageNotifications = mysqlTable("message_notifications", {
|
||||||
id: id("id").primaryKey(),
|
id: id("id").primaryKey(),
|
||||||
@@ -931,7 +931,7 @@ export const messageNotifications = mysqlTable("message_notifications", {
|
|||||||
createdAtIdx: index("message_notifications_created_at_idx").on(table.createdAt),
|
createdAtIdx: index("message_notifications_created_at_idx").on(table.createdAt),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- 14b. Notification Preferences (通知偏好) ---
|
// --- 16. Notification Preferences (通知偏好) ---
|
||||||
|
|
||||||
export const notificationPreferences = mysqlTable("notification_preferences", {
|
export const notificationPreferences = mysqlTable("notification_preferences", {
|
||||||
id: varchar("id", { length: 128 }).primaryKey(),
|
id: varchar("id", { length: 128 }).primaryKey(),
|
||||||
@@ -955,7 +955,7 @@ export const notificationPreferences = mysqlTable("notification_preferences", {
|
|||||||
}).onDelete("cascade"),
|
}).onDelete("cascade"),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- 12. Parent-Student Relations (家长-子女关联) ---
|
// --- 17. Parent-Student Relations (家长-子女关联) ---
|
||||||
|
|
||||||
export const parentStudentRelations = mysqlTable("parent_student_relations", {
|
export const parentStudentRelations = mysqlTable("parent_student_relations", {
|
||||||
id: varchar("id", { length: 128 }).primaryKey().$defaultFn(() => createId()),
|
id: varchar("id", { length: 128 }).primaryKey().$defaultFn(() => createId()),
|
||||||
@@ -978,7 +978,7 @@ export const parentStudentRelations = mysqlTable("parent_student_relations", {
|
|||||||
}).onDelete("cascade"),
|
}).onDelete("cascade"),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- 15. Attendance (考勤管理) ---
|
// --- 18. Attendance (考勤管理) ---
|
||||||
|
|
||||||
export const attendanceStatusEnum = mysqlEnum("status", ["present", "absent", "late", "early_leave", "excused"]);
|
export const attendanceStatusEnum = mysqlEnum("status", ["present", "absent", "late", "early_leave", "excused"]);
|
||||||
|
|
||||||
@@ -1035,7 +1035,7 @@ export const attendanceRules = mysqlTable("attendance_rules", {
|
|||||||
}).onDelete("cascade"),
|
}).onDelete("cascade"),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- 17. Password Security (密码安全策略) ---
|
// --- 19. Password Security (密码安全策略) ---
|
||||||
|
|
||||||
export const passwordSecurity = mysqlTable("password_security", {
|
export const passwordSecurity = mysqlTable("password_security", {
|
||||||
id: varchar("id", { length: 128 }).primaryKey().$defaultFn(() => createId()),
|
id: varchar("id", { length: 128 }).primaryKey().$defaultFn(() => createId()),
|
||||||
@@ -1056,7 +1056,7 @@ export const passwordSecurity = mysqlTable("password_security", {
|
|||||||
}).onDelete("cascade"),
|
}).onDelete("cascade"),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- 16. Scheduling Rules & Schedule Changes (排课规则与调课) ---
|
// --- 20. Scheduling Rules & Schedule Changes (排课规则与调课) ---
|
||||||
|
|
||||||
export const schedulingRules = mysqlTable("scheduling_rules", {
|
export const schedulingRules = mysqlTable("scheduling_rules", {
|
||||||
id: id("id").primaryKey(),
|
id: id("id").primaryKey(),
|
||||||
@@ -1100,7 +1100,7 @@ export const scheduleChanges = mysqlTable("schedule_changes", {
|
|||||||
originalScheduleIdx: index("schedule_changes_original_schedule_idx").on(table.originalScheduleId),
|
originalScheduleIdx: index("schedule_changes_original_schedule_idx").on(table.originalScheduleId),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- P2: Elective Course Management (选课管理) ---
|
// --- 21. Elective Course Management (选课管理) ---
|
||||||
|
|
||||||
export const electiveCourseStatusEnum = mysqlEnum("status", ["draft", "open", "closed", "cancelled"]);
|
export const electiveCourseStatusEnum = mysqlEnum("status", ["draft", "open", "closed", "cancelled"]);
|
||||||
export const electiveSelectionModeEnum = mysqlEnum("selection_mode", ["fcfs", "lottery"]);
|
export const electiveSelectionModeEnum = mysqlEnum("selection_mode", ["fcfs", "lottery"]);
|
||||||
@@ -1152,7 +1152,7 @@ export const courseSelections = mysqlTable("course_selections", {
|
|||||||
statusIdx: index("course_selections_status_idx").on(table.status),
|
statusIdx: index("course_selections_status_idx").on(table.status),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- P2: Exam Proctoring (考试监考) ---
|
// --- 22. Exam Proctoring Events (考试监考事件) ---
|
||||||
|
|
||||||
export const examProctoringEvents = mysqlTable("exam_proctoring_events", {
|
export const examProctoringEvents = mysqlTable("exam_proctoring_events", {
|
||||||
id: id("id").primaryKey(),
|
id: id("id").primaryKey(),
|
||||||
@@ -1170,7 +1170,7 @@ export const examProctoringEvents = mysqlTable("exam_proctoring_events", {
|
|||||||
eventTypeIdx: index("proctoring_event_type_idx").on(table.eventType),
|
eventTypeIdx: index("proctoring_event_type_idx").on(table.eventType),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// --- P2: Learning Diagnostic (学情诊断报告) ---
|
// --- 23. Learning Diagnostic (学情诊断报告) ---
|
||||||
|
|
||||||
export const knowledgePointMastery = mysqlTable("knowledge_point_mastery", {
|
export const knowledgePointMastery = mysqlTable("knowledge_point_mastery", {
|
||||||
id: id("id").primaryKey(),
|
id: id("id").primaryKey(),
|
||||||
@@ -1211,3 +1211,55 @@ export const learningDiagnosticReports = mysqlTable("learning_diagnostic_reports
|
|||||||
statusIdx: index("diagnostic_status_idx").on(table.status),
|
statusIdx: index("diagnostic_status_idx").on(table.status),
|
||||||
reportTypeIdx: index("diagnostic_report_type_idx").on(table.reportType),
|
reportTypeIdx: index("diagnostic_report_type_idx").on(table.reportType),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// --- 24. Lesson Preparation (备课) ---
|
||||||
|
|
||||||
|
export const lessonPlans = mysqlTable("lesson_plans", {
|
||||||
|
id: id("id").primaryKey(),
|
||||||
|
title: varchar("title", { length: 255 }).notNull(),
|
||||||
|
textbookId: varchar("textbook_id", { length: 128 }).references(() => textbooks.id),
|
||||||
|
chapterId: varchar("chapter_id", { length: 128 }).references(() => chapters.id),
|
||||||
|
coursePlanItemId: varchar("course_plan_item_id", { length: 128 }),
|
||||||
|
subjectId: varchar("subject_id", { length: 128 }).references(() => subjects.id),
|
||||||
|
gradeId: varchar("grade_id", { length: 128 }).references(() => grades.id),
|
||||||
|
templateId: varchar("template_id", { length: 128 }),
|
||||||
|
templateName: varchar("template_name", { length: 100 }),
|
||||||
|
content: json("content").notNull(),
|
||||||
|
status: varchar("status", { length: 50 }).default("draft").notNull(),
|
||||||
|
creatorId: varchar("creator_id", { length: 128 }).notNull().references(() => users.id),
|
||||||
|
lastSavedAt: timestamp("last_saved_at"),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
||||||
|
}, (table) => ({
|
||||||
|
creatorIdx: index("lp_creator_idx").on(table.creatorId),
|
||||||
|
statusIdx: index("lp_status_idx").on(table.status),
|
||||||
|
textbookChapterIdx: index("lp_textbook_chapter_idx").on(table.textbookId, table.chapterId),
|
||||||
|
subjectGradeIdx: index("lp_subject_grade_idx").on(table.subjectId, table.gradeId),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const lessonPlanVersions = mysqlTable("lesson_plan_versions", {
|
||||||
|
id: id("id").primaryKey(),
|
||||||
|
planId: varchar("plan_id", { length: 128 }).notNull().references(() => lessonPlans.id, { onDelete: "cascade" }),
|
||||||
|
versionNo: int("version_no").notNull(),
|
||||||
|
label: varchar("label", { length: 100 }),
|
||||||
|
content: json("content").notNull(),
|
||||||
|
isAuto: boolean("is_auto").default(false).notNull(),
|
||||||
|
creatorId: varchar("creator_id", { length: 128 }).notNull().references(() => users.id),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
}, (table) => ({
|
||||||
|
planVersionIdx: index("lpv_plan_version_idx").on(table.planId, table.versionNo),
|
||||||
|
planCreatedIdx: index("lpv_plan_created_idx").on(table.planId, table.createdAt),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const lessonPlanTemplates = mysqlTable("lesson_plan_templates", {
|
||||||
|
id: id("id").primaryKey(),
|
||||||
|
name: varchar("name", { length: 100 }).notNull(),
|
||||||
|
type: varchar("type", { length: 50 }).notNull(),
|
||||||
|
scope: varchar("scope", { length: 50 }).notNull(),
|
||||||
|
blocks: json("blocks").notNull(),
|
||||||
|
creatorId: varchar("creator_id", { length: 128 }).references(() => users.id),
|
||||||
|
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||||
|
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
|
||||||
|
}, (table) => ({
|
||||||
|
typeCreatorIdx: index("lpt_type_creator_idx").on(table.type, table.creatorId),
|
||||||
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user