refactor: P0-3/5/6 解耦修复 - 循环依赖/通知分发/课表写入口

P0-3: 修复 shared/lib <-> auth 循环依赖
- audit-logger.ts, change-logger.ts, auth-guard.ts, classes/data-access.ts
  改用动态 import("@/auth") 打破静态模块级循环依赖
- shared/lib 不再静态导入 @/auth

P0-5: messaging 改用 notifications dispatcher
- messaging/actions.ts 的 sendMessageAction 改用 sendNotification
  替代直接调用 createNotification
- 用户通知偏好(SMS/微信/邮件/站内)现在被正确尊重

P0-6: 统一 classSchedule 写入口到 scheduling/data-access
- 新增 insertClassScheduleItem/updateClassScheduleItemById/
  deleteClassScheduleItemById/replaceClassSchedule 统一写入函数
- classes/data-access.ts 的三个 schedule 写入函数委托给 scheduling
- scheduling/actions.ts 的 applyAutoScheduleAction 改用 replaceClassSchedule
- 移除 scheduling/actions.ts 中不再使用的 classSchedule/createId 导入

验证: tsc --noEmit 0 errors, npm run lint 0 errors
This commit is contained in:
SpecialX
2026-06-17 23:44:02 +08:00
parent 02dc1093fb
commit 220061d62e
7 changed files with 155 additions and 31 deletions

View File

@@ -4,7 +4,6 @@ import { createId } from "@paralleldrive/cuid2"
import { headers } from "next/headers"
import { db } from "@/shared/db"
import { auditLogs } from "@/shared/db/schema"
import { auth } from "@/auth"
export type AuditLogStatus = "success" | "failure"
@@ -17,13 +16,23 @@ export interface LogAuditParams {
status?: AuditLogStatus
}
/**
* Get the current session without creating a static circular dependency
* on @/auth (which itself imports from @/shared/lib/*).
* Dynamic import breaks the module-level cycle.
*/
async function getCurrentSession() {
const { auth } = await import("@/auth")
return auth()
}
/**
* Record an audit log entry for the current authenticated user.
* Silently fails on error so it never breaks the main operation.
*/
export async function logAudit(params: LogAuditParams): Promise<void> {
try {
const session = await auth()
const session = await getCurrentSession()
const headerList = await headers()
const ipAddress =
headerList.get("x-forwarded-for") ??

View File

@@ -1,4 +1,3 @@
import { auth } from "@/auth"
import type { Permission, DataScope, AuthContext } from "@/shared/types/permissions"
import { db } from "@/shared/db"
import {
@@ -16,12 +15,22 @@ export class PermissionDeniedError extends Error {
}
}
/**
* Get the current session without creating a static circular dependency
* on @/auth (which itself imports from @/shared/lib/*).
* Dynamic import breaks the module-level cycle.
*/
async function getCurrentSession() {
const { auth } = await import("@/auth")
return auth()
}
/**
* Get the full authentication context for the current user.
* Throws if not authenticated.
*/
export async function getAuthContext(): Promise<AuthContext> {
const session = await auth()
const session = await getCurrentSession()
const userId = session?.user?.id
if (!userId) throw new PermissionDeniedError("auth_required")

View File

@@ -3,7 +3,6 @@
import { createId } from "@paralleldrive/cuid2"
import { headers } from "next/headers"
import { auth } from "@/auth"
import { db } from "@/shared/db"
import { dataChangeLogs } from "@/shared/db/schema"
@@ -17,13 +16,23 @@ export interface LogDataChangeParams {
newValue?: Record<string, unknown>
}
/**
* Get the current session without creating a static circular dependency
* on @/auth (which itself imports from @/shared/lib/*).
* Dynamic import breaks the module-level cycle.
*/
async function getCurrentSession() {
const { auth } = await import("@/auth")
return auth()
}
/**
* Record a data change log entry for the current authenticated user.
* Silently fails on error so it never blocks the main operation.
*/
export async function logDataChange(params: LogDataChangeParams): Promise<void> {
try {
const session = await auth()
const session = await getCurrentSession()
const headerList = await headers()
const ipAddress =
headerList.get("x-forwarded-for") ??