feat(db): add migrations for RBAC, diagnostic, messaging, attendance, leave, invitation codes

- 0011: RBAC role flags and seed data

- 0012: diagnostic grade_id

- 0013: message recall

- 0014: message templates

- 0015: group messages and message reports/blocks

- 0016: invitation codes

- 0017: draft device sync

- 0018: attendance status reason

- 0019: attendance warning thresholds

- 0020: leave requests

- 0021: attendance period
This commit is contained in:
SpecialX
2026-07-03 10:23:24 +08:00
parent 48829bd02b
commit 365c36d97b
13 changed files with 308 additions and 0 deletions

View File

@@ -0,0 +1,85 @@
-- 0011_rbac_role_flags_and_seed.sql
-- RBAC: add is_system/is_enabled columns to roles table and seed role_permissions.
-- 1. Add is_system and is_enabled columns to roles table.
ALTER TABLE `roles`
ADD COLUMN `is_system` boolean DEFAULT false NOT NULL,
ADD COLUMN `is_enabled` boolean DEFAULT true NOT NULL;
-- 2. Mark the 6 builtin roles as system roles.
UPDATE `roles` SET `is_system` = true
WHERE `name` IN ('admin', 'teacher', 'student', 'parent', 'grade_head', 'teaching_head');
-- 3. Seed role_permissions for builtin roles.
-- Uses INSERT IGNORE so re-running is safe (role_permissions has a composite PK).
-- Permissions are resolved from ROLE_PERMISSIONS_SEED in src/shared/lib/permissions.ts.
-- admin
INSERT IGNORE INTO `role_permissions` (`role_id`, `permission`)
SELECT id, 'exam:create' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'exam:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'exam:update' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'exam:delete' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'exam:duplicate' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'exam:publish' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'exam:ai_generate' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'homework:create' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'homework:grade' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'question:create' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'question:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'question:update' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'question:delete' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'textbook:create' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'textbook:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'textbook:update' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'textbook:delete' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'class:create' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'class:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'class:update' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'class:delete' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'class:enroll' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'class:schedule' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'school:manage' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'grade:manage' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'user:manage' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'user:profile_update' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'ai:chat' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'ai:configure' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'settings:admin' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'audit_log:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'announcement:manage' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'announcement:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'grade_record:manage' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'grade_record:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'course_plan:manage' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'course_plan:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'attendance:manage' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'attendance:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'message:send' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'message:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'message:delete' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'schedule:auto' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'schedule:adjust' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'elective:manage' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'elective:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'exam:proctor' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'exam:proctor:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'diagnostic:manage' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'diagnostic:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'lesson_plan:create' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'lesson_plan:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'lesson_plan:update' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'lesson_plan:delete' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'lesson_plan:publish' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'file:upload' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'file:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'file:delete' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'dashboard:admin_read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'error_book:analytics_read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'adaptive_practice:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'role:create' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'role:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'role:update' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'role:delete' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'role:assign' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'permission:read' FROM `roles` WHERE `name` = 'admin';

View File

@@ -0,0 +1,4 @@
-- v4-P2-3: Add gradeId column to learning_diagnostic_reports for grade-level diagnostic reports
ALTER TABLE `learning_diagnostic_reports` ADD COLUMN `grade_id` varchar(128);--> statement-breakpoint
ALTER TABLE `learning_diagnostic_reports` ADD CONSTRAINT `learning_diagnostic_reports_grade_id_grades_id_fk` FOREIGN KEY (`grade_id`) REFERENCES `grades`(`id`) ON DELETE set null ON UPDATE no action;--> statement-breakpoint
CREATE INDEX `diagnostic_grade_idx` ON `learning_diagnostic_reports` (`grade_id`);

View File

@@ -0,0 +1,2 @@
-- P2-4: 消息撤回功能2 分钟窗口)
ALTER TABLE `messages` ADD COLUMN `recalled_at` timestamp;

View File

@@ -0,0 +1,13 @@
-- P2-6: 快捷回复模板
CREATE TABLE `message_templates` (
`id` varchar(128) PRIMARY KEY NOT NULL,
`user_id` varchar(128) NOT NULL,
`title` varchar(100) NOT NULL,
`content` text NOT NULL,
`sort_order` integer DEFAULT 0 NOT NULL,
`updated_at` timestamp DEFAULT (now()) ON UPDATE now() NOT NULL,
`created_at` timestamp DEFAULT (now()) NOT NULL,
CONSTRAINT `message_templates_user_id_users_id_fk` FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE cascade ON UPDATE no action
);
CREATE INDEX `message_templates_user_idx` ON `message_templates`(`user_id`);
CREATE INDEX `message_templates_user_sort_idx` ON `message_templates`(`user_id`, `sort_order`);

View File

@@ -0,0 +1,3 @@
-- P2-2: 群组消息教师→全班家长fan-out on write
ALTER TABLE `messages` ADD COLUMN `group_message_id` varchar(128);
CREATE INDEX `messages_group_message_idx` ON `messages`(`group_message_id`);

View File

@@ -0,0 +1,19 @@
-- audit-P2-3: 注册邀请码表admin 批量生成,注册时校验 + 自动分配角色/班级)
CREATE TABLE IF NOT EXISTS `invitation_codes` (
`id` serial PRIMARY KEY,
`code` varchar(64) NOT NULL,
`email` varchar(255),
`role` varchar(50) NOT NULL,
`class_id` varchar(128),
`notes` varchar(500),
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`created_by` varchar(128),
`expires_at` timestamp NULL,
`used_by` varchar(128),
`used_at` timestamp NULL,
UNIQUE KEY `invitation_codes_code_unique` (`code`)
);
CREATE INDEX `invitation_codes_code_idx` ON `invitation_codes`(`code`);
CREATE INDEX `invitation_codes_email_idx` ON `invitation_codes`(`email`);
CREATE INDEX `invitation_codes_used_by_idx` ON `invitation_codes`(`used_by`);

View File

@@ -0,0 +1,35 @@
-- P2-5: 消息举报 + 用户屏蔽
-- 1. message_reports: 举报记录reporterId → messageId / reportedUserId
-- 2. user_blocks: 用户屏蔽关系blockerId → blockedId唯一约束防重复
CREATE TABLE `message_reports` (
`id` varchar(128) NOT NULL,
`reporter_id` varchar(128) NOT NULL,
`message_id` varchar(128) NOT NULL,
`reported_user_id` varchar(128) NOT NULL,
`reason` varchar(50) NOT NULL,
`description` text,
`status` varchar(20) NOT NULL DEFAULT 'pending',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `message_reports_reporter_idx` (`reporter_id`),
INDEX `message_reports_message_idx` (`message_id`),
INDEX `message_reports_reported_user_idx` (`reported_user_id`),
INDEX `message_reports_status_idx` (`status`),
CONSTRAINT `message_reports_reporter_id_users_id_fk` FOREIGN KEY (`reporter_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
CONSTRAINT `message_reports_message_id_messages_id_fk` FOREIGN KEY (`message_id`) REFERENCES `messages`(`id`) ON DELETE CASCADE,
CONSTRAINT `message_reports_reported_user_id_users_id_fk` FOREIGN KEY (`reported_user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
);
CREATE TABLE `user_blocks` (
`id` varchar(128) NOT NULL,
`blocker_id` varchar(128) NOT NULL,
`blocked_id` varchar(128) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE INDEX `user_blocks_blocker_blocked_uniq` (`blocker_id`, `blocked_id`),
INDEX `user_blocks_blocker_idx` (`blocker_id`),
CONSTRAINT `user_blocks_blocker_id_users_id_fk` FOREIGN KEY (`blocker_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
CONSTRAINT `user_blocks_blocked_id_users_id_fk` FOREIGN KEY (`blocked_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
);

View File

@@ -0,0 +1,8 @@
-- P2-10: 跨设备草稿同步
-- 1. 新增 device_id 列(标识来源设备)
-- 2. 新增 version 列(乐观锁,冲突检测)
-- 3. 新增 (user_id, parent_message_id) 索引(按会话查询最新版本草稿)
ALTER TABLE `message_drafts` ADD COLUMN `device_id` varchar(128);
ALTER TABLE `message_drafts` ADD COLUMN `version` int NOT NULL DEFAULT 1;
CREATE INDEX `message_drafts_user_parent_idx` ON `message_drafts`(`user_id`, `parent_message_id`);

View File

@@ -0,0 +1,11 @@
-- L-1: 考勤状态细分 + 原因字段
-- 1. 扩展 attendance_records.status 枚举:新增 'school_activity'(校内活动请假,如运动会、研学、社团活动)
-- 2. 新增 reason 字段:记录缺勤/迟到/早退/请假的具体原因(最多 255 字符),与 remark 区分
-- - remark教师备注自由文本可记录点名时观察到的情况
-- - reason标准化原因用于统计与家长通知如"病假-感冒"、"家中有事"等)
ALTER TABLE `attendance_records`
MODIFY COLUMN `status`
ENUM('present','absent','late','early_leave','excused','school_activity') NOT NULL;
ALTER TABLE `attendance_records` ADD COLUMN `reason` varchar(255);

View File

@@ -0,0 +1,6 @@
-- L-3出勤率阈值预警
-- 新增 attendance_rate_threshold出勤率阈值默认 90%
-- 新增 consecutive_absence_threshold连续缺勤阈值默认 3 次)
ALTER TABLE `attendance_rules`
ADD COLUMN `attendance_rate_threshold` int DEFAULT 90,
ADD COLUMN `consecutive_absence_threshold` int DEFAULT 3;

View File

@@ -0,0 +1,53 @@
-- 0020_leave_requests.sql
-- L-5 在线请假流程:创建 leave_requests 表。
CREATE TABLE IF NOT EXISTS `leave_requests` (
`id` varchar(128) NOT NULL,
`student_id` varchar(128) NOT NULL,
`requester_id` varchar(128) NOT NULL,
`class_id` varchar(128) NOT NULL,
`leave_type` enum('sick','personal','family','other') NOT NULL,
`start_date` date NOT NULL,
`end_date` date NOT NULL,
`reason` text NOT NULL,
`status` enum('pending','approved','rejected','cancelled') NOT NULL DEFAULT 'pending',
`reviewer_id` varchar(128) DEFAULT NULL,
`review_comment` text,
`reviewed_at` timestamp NULL DEFAULT NULL,
`attendance_synced` boolean NOT NULL DEFAULT false,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `leave_requests_student_idx` (`student_id`),
KEY `leave_requests_requester_idx` (`requester_id`),
KEY `leave_requests_class_idx` (`class_id`),
KEY `leave_requests_status_idx` (`status`),
KEY `leave_requests_reviewer_idx` (`reviewer_id`),
KEY `leave_requests_class_status_idx` (`class_id`, `status`),
KEY `leave_requests_student_status_idx` (`student_id`, `status`),
CONSTRAINT `lr_c_fk` FOREIGN KEY (`class_id`) REFERENCES `classes` (`id`) ON DELETE CASCADE,
CONSTRAINT `lr_s_fk` FOREIGN KEY (`student_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `lr_rq_fk` FOREIGN KEY (`requester_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `lr_rv_fk` FOREIGN KEY (`reviewer_id`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 为 builtin 角色注入新权限INSERT IGNORE 幂等)
-- admin三个权限全有
INSERT IGNORE INTO `role_permissions` (`role_id`, `permission`)
SELECT id, 'leave_request:create' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'leave_request:read' FROM `roles` WHERE `name` = 'admin'
UNION ALL SELECT id, 'leave_request:review' FROM `roles` WHERE `name` = 'admin'
-- teacherread + review班主任审批本班请假
UNION ALL SELECT id, 'leave_request:read' FROM `roles` WHERE `name` = 'teacher'
UNION ALL SELECT id, 'leave_request:review' FROM `roles` WHERE `name` = 'teacher'
-- parentcreate + read代子女提交、查看子女请假记录
UNION ALL SELECT id, 'leave_request:create' FROM `roles` WHERE `name` = 'parent'
UNION ALL SELECT id, 'leave_request:read' FROM `roles` WHERE `name` = 'parent'
-- studentcreate + read本人提交、查看本人请假记录
UNION ALL SELECT id, 'leave_request:create' FROM `roles` WHERE `name` = 'student'
UNION ALL SELECT id, 'leave_request:read' FROM `roles` WHERE `name` = 'student'
-- grade_head / teaching_headread + review年级主任/教务主任可审批)
UNION ALL SELECT id, 'leave_request:read' FROM `roles` WHERE `name` = 'grade_head'
UNION ALL SELECT id, 'leave_request:review' FROM `roles` WHERE `name` = 'grade_head'
UNION ALL SELECT id, 'leave_request:read' FROM `roles` WHERE `name` = 'teaching_head'
UNION ALL SELECT id, 'leave_request:review' FROM `roles` WHERE `name` = 'teaching_head';

View File

@@ -0,0 +1,20 @@
-- 0021_attendance_period.sql
-- L-6 节次考勤:为 attendance_records 表添加 period 字段。
--
-- 节次枚举:
-- morning_reading 早读
-- morning 上午
-- afternoon 下午
-- evening 晚自习
-- full_day 全日默认值向后兼容null 也表示全日)
--
-- 向后兼容策略:
-- - 新增 period 列为 NULLABLE已有数据保持 NULL视为 full_day
-- - 新记录由应用层显式写入 period未指定时写入 'full_day'
-- - 查询层 period IS NULL OR period = 'full_day' 均视为全日记录
ALTER TABLE `attendance_records`
ADD COLUMN `period` ENUM('morning_reading','morning','afternoon','evening','full_day') NULL DEFAULT NULL AFTER `date`;
-- 复合索引:班级 + 日期 + 节次,支持按节次查询班级当日点名
CREATE INDEX `attendance_records_class_date_period_idx` ON `attendance_records` (`class_id`, `date`, `period`);

View File

@@ -43,6 +43,55 @@
"when": 1782141546400,
"tag": "0005_messy_pride",
"breakpoints": true
},
{
"idx": 6,
"version": "5",
"when": 1782374400000,
"tag": "0013_message_recall",
"breakpoints": true
},
{
"idx": 7,
"version": "5",
"when": 1782374500000,
"tag": "0014_message_templates",
"breakpoints": true
},
{
"idx": 8,
"version": "5",
"when": 1782374600000,
"tag": "0015_group_messages",
"breakpoints": true
},
{
"idx": 9,
"version": "5",
"when": 1782445200000,
"tag": "0018_attendance_status_reason",
"breakpoints": true
},
{
"idx": 10,
"version": "5",
"when": 1782448800000,
"tag": "0019_attendance_warning_thresholds",
"breakpoints": true
},
{
"idx": 11,
"version": "5",
"when": 1782452400000,
"tag": "0020_leave_requests",
"breakpoints": true
},
{
"idx": 12,
"version": "5",
"when": 1782456000000,
"tag": "0021_attendance_period",
"breakpoints": true
}
]
}