fix: patch P0 security vulnerabilities and critical UX issues across 6 modules

Security: Add admin/layout.tsx auth guard; Add requirePermission() to 12 admin pages

Dashboard: Fix StudentStatsGrid rendering; Fix teacher greeting; Add loading/error boundaries; Fix col-span; Add metadata

Announcements: Fix audience filtering; Add user detail page; Trigger notifications on publish; Pass classes data; Add loading.tsx

Messages: Implement soft delete; Add unread badge with polling; Add notification dropdown polling; Add keyword search; Add quiet hours DND

Management: Add loading/error for 9 admin routes; Fix admin-classes-view to use Select for school/grade

Profile/Settings: Add loading/error; Fix parent role routing; Create ParentSettingsView; Integrate AiProviderSettingsCard; Add Tab URL persistence; Add logout confirm; Add avatar; Fix Progress arbitrary class

Schema: Add senderDeletedAt/receiverDeletedAt to messages; Add quietHours to notificationPreferences; Add uniqueIndex import

Docs: Update architecture docs 004/005
This commit is contained in:
SpecialX
2026-06-22 13:57:31 +08:00
parent 5ff7ab9e72
commit a4d096a6fc
81 changed files with 2145 additions and 124 deletions

View File

@@ -39,6 +39,9 @@ const mapRow = (
announcementNotifications: row.announcementNotifications,
messageNotifications: row.messageNotifications,
attendanceNotifications: row.attendanceNotifications,
quietHoursEnabled: row.quietHoursEnabled,
quietHoursStart: row.quietHoursStart,
quietHoursEnd: row.quietHoursEnd,
createdAt: toIso(row.createdAt),
updatedAt: toIso(row.updatedAt),
})
@@ -53,6 +56,9 @@ const DEFAULTS = {
announcementNotifications: true,
messageNotifications: true,
attendanceNotifications: true,
quietHoursEnabled: false,
quietHoursStart: null,
quietHoursEnd: null,
}
/**
@@ -133,6 +139,9 @@ export async function upsertNotificationPreferences(
if (input.announcementNotifications !== undefined) updateData.announcementNotifications = input.announcementNotifications
if (input.messageNotifications !== undefined) updateData.messageNotifications = input.messageNotifications
if (input.attendanceNotifications !== undefined) updateData.attendanceNotifications = input.attendanceNotifications
if (input.quietHoursEnabled !== undefined) updateData.quietHoursEnabled = input.quietHoursEnabled
if (input.quietHoursStart !== undefined) updateData.quietHoursStart = input.quietHoursStart
if (input.quietHoursEnd !== undefined) updateData.quietHoursEnd = input.quietHoursEnd
if (Object.keys(updateData).length === 0) {
return mapRow(existing)
@@ -165,6 +174,9 @@ export async function upsertNotificationPreferences(
announcementNotifications: input.announcementNotifications ?? DEFAULTS.announcementNotifications,
messageNotifications: input.messageNotifications ?? DEFAULTS.messageNotifications,
attendanceNotifications: input.attendanceNotifications ?? DEFAULTS.attendanceNotifications,
quietHoursEnabled: input.quietHoursEnabled ?? DEFAULTS.quietHoursEnabled,
quietHoursStart: input.quietHoursStart ?? DEFAULTS.quietHoursStart,
quietHoursEnd: input.quietHoursEnd ?? DEFAULTS.quietHoursEnd,
})
const [created] = await db

View File

@@ -95,6 +95,9 @@ export interface NotificationPreferences {
announcementNotifications: boolean
messageNotifications: boolean
attendanceNotifications: boolean
quietHoursEnabled: boolean
quietHoursStart: string | null
quietHoursEnd: string | null
createdAt: string
updatedAt: string
}
@@ -109,6 +112,9 @@ export interface UpdateNotificationPreferencesInput {
announcementNotifications?: boolean
messageNotifications?: boolean
attendanceNotifications?: boolean
quietHoursEnabled?: boolean
quietHoursStart?: string | null
quietHoursEnd?: string | null
}
/** SMS 渠道配置 */