refactor(actions): 组 A 模块迁移至 invalidateFor(users/school/textbooks/questions/course-plans/standards/scheduling/audit/onboarding/invitation-codes/proctoring/i18n)

This commit is contained in:
SpecialX
2026-07-05 22:43:08 +08:00
parent 0f33484bd2
commit 05e68a3dad
12 changed files with 73 additions and 100 deletions

View File

@@ -1,7 +1,6 @@
"use server" "use server"
import { cookies } from "next/headers" import { cookies } from "next/headers"
import { revalidatePath } from "next/cache"
import { import {
LOCALE_COOKIE, LOCALE_COOKIE,
@@ -9,6 +8,7 @@ import {
type Locale, type Locale,
isLocale, isLocale,
} from "@/shared/i18n/locale" } from "@/shared/i18n/locale"
import { invalidateFor } from "@/shared/lib/cache"
/** /**
* 切换语言:写入 cookie + 触发页面刷新。 * 切换语言:写入 cookie + 触发页面刷新。
@@ -16,7 +16,7 @@ import {
* 设计说明: * 设计说明:
* - 不使用 URL 路由段,避免破坏现有路由组结构 * - 不使用 URL 路由段,避免破坏现有路由组结构
* - cookie 持久化用户偏好SSR 时由 i18n/request.ts 读取 * - cookie 持久化用户偏好SSR 时由 i18n/request.ts 读取
* - revalidatePath 确保所有页面用新 locale 重新渲染 * - invalidateFor 确保所有页面用新 locale 重新渲染
*/ */
export async function setLocaleAction(locale: string): Promise<{ success: boolean }> { export async function setLocaleAction(locale: string): Promise<{ success: boolean }> {
if (!isLocale(locale)) { if (!isLocale(locale)) {
@@ -27,6 +27,6 @@ export async function setLocaleAction(locale: string): Promise<{ success: boolea
cookieStore.set(LOCALE_COOKIE, locale as Locale, LOCALE_COOKIE_OPTIONS) cookieStore.set(LOCALE_COOKIE, locale as Locale, LOCALE_COOKIE_OPTIONS)
// 刷新所有页面,让 SSR 用新 locale 重新渲染 // 刷新所有页面,让 SSR 用新 locale 重新渲染
revalidatePath("/", "layout") await invalidateFor("i18n.setLocale")
return { success: true } return { success: true }
} }

View File

@@ -1,9 +1,9 @@
"use server" "use server"
import { revalidatePath } from "next/cache"
import { PermissionDeniedError, requirePermission } from "@/shared/lib/auth-guard" import { PermissionDeniedError, requirePermission } from "@/shared/lib/auth-guard"
import { Permissions } from "@/shared/types/permissions" import { Permissions } from "@/shared/types/permissions"
import type { ActionState } from "@/shared/types/action-state" import type { ActionState } from "@/shared/types/action-state"
import { invalidateFor } from "@/shared/lib/cache"
import { trackEvent } from "@/shared/lib/track-event" import { trackEvent } from "@/shared/lib/track-event"
import { getSession } from "@/shared/lib/session" import { getSession } from "@/shared/lib/session"
@@ -180,7 +180,7 @@ export async function saveAuditRetentionConfigAction(
}, },
}) })
revalidatePath("/admin/audit-logs/overview") await invalidateFor("audit.saveRetentionConfig")
return { success: true, data: config } return { success: true, data: config }
} catch (e) { } catch (e) {
if (e instanceof PermissionDeniedError) return { success: false, message: e.message } if (e instanceof PermissionDeniedError) return { success: false, message: e.message }
@@ -215,8 +215,7 @@ export async function purgeAuditLogsAction(
}, },
}) })
revalidatePath("/admin/audit-logs/overview") await invalidateFor("audit.purge")
revalidatePath("/admin/audit-logs")
return { success: true, data: result } return { success: true, data: result }
} catch (e) { } catch (e) {
if (e instanceof PermissionDeniedError) return { success: false, message: e.message } if (e instanceof PermissionDeniedError) return { success: false, message: e.message }

View File

@@ -1,9 +1,9 @@
"use server" "use server"
import { revalidatePath } from "next/cache"
import { requirePermission } from "@/shared/lib/auth-guard" import { requirePermission } from "@/shared/lib/auth-guard"
import { Permissions } from "@/shared/types/permissions" import { Permissions } from "@/shared/types/permissions"
import type { ActionState } from "@/shared/types/action-state" import type { ActionState } from "@/shared/types/action-state"
import { invalidateFor } from "@/shared/lib/cache"
import { handleActionError } from "@/shared/lib/action-utils" import { handleActionError } from "@/shared/lib/action-utils"
import type { DataScope } from "@/shared/types/permissions" import type { DataScope } from "@/shared/types/permissions"
@@ -90,19 +90,6 @@ function resolveScope(
return { userId: ctx.userId, isAdmin: false } return { userId: ctx.userId, isAdmin: false }
} }
const revalidatePlanPaths = (id?: string): void => {
revalidatePath("/admin/course-plans")
revalidatePath("/teacher/course-plans")
revalidatePath("/parent/course-plans")
revalidatePath("/student/course-plans")
if (id) {
revalidatePath(`/admin/course-plans/${id}`)
revalidatePath(`/teacher/course-plans/${id}`)
revalidatePath(`/parent/course-plans/${id}`)
revalidatePath(`/student/course-plans/${id}`)
}
}
export async function createCoursePlanAction( export async function createCoursePlanAction(
prevState: ActionState<string> | null, prevState: ActionState<string> | null,
formData: FormData formData: FormData
@@ -134,7 +121,7 @@ export async function createCoursePlanAction(
} }
const id = await createCoursePlan(parsed.data, ctx.userId) const id = await createCoursePlan(parsed.data, ctx.userId)
revalidatePlanPaths(id) await invalidateFor("course-plans.create", { id })
return { success: true, message: "Course plan created", data: id } return { success: true, message: "Course plan created", data: id }
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)
@@ -179,7 +166,7 @@ export async function updateCoursePlanAction(
} }
await updateCoursePlan(id, parsed.data) await updateCoursePlan(id, parsed.data)
revalidatePlanPaths(id) await invalidateFor("course-plans.update", { id })
return { success: true, message: "Course plan updated", data: id } return { success: true, message: "Course plan updated", data: id }
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)
@@ -198,7 +185,7 @@ export async function deleteCoursePlanAction(
if (!existing) return { success: false, message: "Course plan not found" } if (!existing) return { success: false, message: "Course plan not found" }
await deleteCoursePlan(id) await deleteCoursePlan(id)
revalidatePlanPaths() await invalidateFor("course-plans.delete")
return { success: true, message: "Course plan deleted" } return { success: true, message: "Course plan deleted" }
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)
@@ -279,7 +266,7 @@ export async function createCoursePlanItemAction(
if (!existing) return { success: false, message: "Course plan not found" } if (!existing) return { success: false, message: "Course plan not found" }
const itemId = await createCoursePlanItem(parsed.data) const itemId = await createCoursePlanItem(parsed.data)
revalidatePlanPaths(parsed.data.planId) await invalidateFor("course-plans.item.create", { id: parsed.data.planId })
return { success: true, message: "Week plan added", data: itemId } return { success: true, message: "Week plan added", data: itemId }
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)
@@ -319,7 +306,7 @@ export async function updateCoursePlanItemAction(
} }
await updateCoursePlanItem(id, parsed.data) await updateCoursePlanItem(id, parsed.data)
revalidatePlanPaths() await invalidateFor("course-plans.item.update")
return { success: true, message: "Week plan updated", data: id } return { success: true, message: "Week plan updated", data: id }
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)
@@ -332,7 +319,7 @@ export async function deleteCoursePlanItemAction(
try { try {
await requirePermission(Permissions.COURSE_PLAN_MANAGE) await requirePermission(Permissions.COURSE_PLAN_MANAGE)
await deleteCoursePlanItem(id) await deleteCoursePlanItem(id)
revalidatePlanPaths() await invalidateFor("course-plans.item.delete")
return { success: true, message: "Week plan deleted" } return { success: true, message: "Week plan deleted" }
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)
@@ -349,7 +336,7 @@ export async function toggleCoursePlanItemCompletedAction(
isCompleted: completed, isCompleted: completed,
completedAt: completed ? new Date().toISOString().slice(0, 10) : null, completedAt: completed ? new Date().toISOString().slice(0, 10) : null,
}) })
revalidatePlanPaths() await invalidateFor("course-plans.item.toggleCompleted")
return { return {
success: true, success: true,
message: completed ? "Marked as completed" : "Marked as incomplete", message: completed ? "Marked as completed" : "Marked as incomplete",
@@ -379,7 +366,7 @@ export async function reorderCoursePlanItemsAction(
if (!existing) return { success: false, message: "Course plan not found" } if (!existing) return { success: false, message: "Course plan not found" }
await reorderCoursePlanItems(parsed.data.planId, parsed.data.items) await reorderCoursePlanItems(parsed.data.planId, parsed.data.items)
revalidatePlanPaths(parsed.data.planId) await invalidateFor("course-plans.item.reorder", { id: parsed.data.planId })
return { success: true, message: "Order saved", data: parsed.data.planId } return { success: true, message: "Order saved", data: parsed.data.planId }
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)
@@ -402,7 +389,7 @@ export async function bulkToggleItemsAction(
} }
const count = await bulkUpdateItemCompleted(parsed.data.itemIds, parsed.data.completed) const count = await bulkUpdateItemCompleted(parsed.data.itemIds, parsed.data.completed)
revalidatePlanPaths() await invalidateFor("course-plans.item.bulkToggle")
return { success: true, message: "Bulk updated", data: count } return { success: true, message: "Bulk updated", data: count }
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)
@@ -430,7 +417,7 @@ export async function copyCoursePlanAction(
if (!existing) return { success: false, message: "Course plan not found" } if (!existing) return { success: false, message: "Course plan not found" }
const ids = await copyCoursePlanToClasses(parsed.data.sourcePlanId, parsed.data.targetClassIds) const ids = await copyCoursePlanToClasses(parsed.data.sourcePlanId, parsed.data.targetClassIds)
revalidatePlanPaths() await invalidateFor("course-plans.copy")
return { success: true, message: "Copied", data: ids } return { success: true, message: "Copied", data: ids }
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)

View File

@@ -1,10 +1,9 @@
"use server" "use server"
import { revalidatePath } from "next/cache"
import type { ActionState } from "@/shared/types/action-state" import type { ActionState } from "@/shared/types/action-state"
import { requirePermission } from "@/shared/lib/auth-guard" import { requirePermission } from "@/shared/lib/auth-guard"
import { Permissions } from "@/shared/types/permissions" import { Permissions } from "@/shared/types/permissions"
import { invalidateFor } from "@/shared/lib/cache"
import { trackEvent } from "@/shared/lib/track-event" import { trackEvent } from "@/shared/lib/track-event"
import { import {
@@ -63,7 +62,7 @@ export async function generateInvitationCodesAction(
}, },
}) })
revalidatePath("/admin/invitation-codes") await invalidateFor("invitation-codes.generate")
return { success: true, data: result } return { success: true, data: result }
} catch (error) { } catch (error) {
@@ -121,7 +120,7 @@ export async function deleteInvitationCodeAction(
targetId: codeId, targetId: codeId,
}) })
revalidatePath("/admin/invitation-codes") await invalidateFor("invitation-codes.delete")
return { success: true, data: null } return { success: true, data: null }
} catch (error) { } catch (error) {
const message = const message =

View File

@@ -1,10 +1,10 @@
"use server" "use server"
import { revalidatePath } from "next/cache"
import { eq } from "drizzle-orm" import { eq } from "drizzle-orm"
import type { ActionState } from "@/shared/types/action-state" import type { ActionState } from "@/shared/types/action-state"
import { requireAuth } from "@/shared/lib/auth-guard" import { requireAuth } from "@/shared/lib/auth-guard"
import { invalidateFor } from "@/shared/lib/cache"
import { logAudit } from "@/shared/lib/audit-logger" import { logAudit } from "@/shared/lib/audit-logger"
import { resolveDefaultPath } from "@/shared/lib/route-resolver" import { resolveDefaultPath } from "@/shared/lib/route-resolver"
import { import {
@@ -243,7 +243,7 @@ export async function completeOnboardingAction(
status: failures.length === 0 ? "success" : "failure", status: failures.length === 0 ? "success" : "failure",
}) })
revalidatePath("/onboarding") await invalidateFor("onboarding.complete")
// 若有局部失败返回成功但附带失败列表P1-2 // 若有局部失败返回成功但附带失败列表P1-2
if (failures.length > 0) { if (failures.length > 0) {

View File

@@ -1,8 +1,8 @@
"use server" "use server"
import { revalidatePath } from "next/cache"
import type { ActionState } from "@/shared/types/action-state" import type { ActionState } from "@/shared/types/action-state"
import { requirePermission } from "@/shared/lib/auth-guard" import { requirePermission } from "@/shared/lib/auth-guard"
import { invalidateFor } from "@/shared/lib/cache"
import { handleActionError } from "@/shared/lib/action-utils" import { handleActionError } from "@/shared/lib/action-utils"
import { Permissions } from "@/shared/types/permissions" import { Permissions } from "@/shared/types/permissions"
import { z } from "zod" import { z } from "zod"
@@ -85,7 +85,7 @@ export async function recordProctoringEventAction(
eventDetail: parsed.data.eventDetail, eventDetail: parsed.data.eventDetail,
}) })
revalidatePath(`/teacher/exams/${parsed.data.examId}/proctoring`) await invalidateFor("proctoring.recordEvent", { examId: parsed.data.examId })
return successState({ id: event.id }, "Event recorded") return successState({ id: event.id }, "Event recorded")
} catch (error) { } catch (error) {

View File

@@ -5,7 +5,7 @@ import { Permissions } from "@/shared/types/permissions"
import { CreateQuestionSchema } from "./schema" import { CreateQuestionSchema } from "./schema"
import type { CreateQuestionInput } from "./schema" import type { CreateQuestionInput } from "./schema"
import type { ActionState } from "@/shared/types/action-state" import type { ActionState } from "@/shared/types/action-state"
import { revalidatePath } from "next/cache" import { invalidateFor } from "@/shared/lib/cache"
import { z } from "zod" import { z } from "zod"
import { import {
createQuestionWithRelations, createQuestionWithRelations,
@@ -68,8 +68,7 @@ export async function createQuestionAction(
trackQuestionCreated(questionId, input.type, input.difficulty) trackQuestionCreated(questionId, input.type, input.difficulty)
revalidatePath("/teacher/questions") await invalidateFor("questions.create")
revalidatePath("/admin/questions")
return { return {
success: true, success: true,
@@ -117,8 +116,7 @@ export async function updateQuestionAction(
trackQuestionUpdated(id, updateData.type, updateData.difficulty) trackQuestionUpdated(id, updateData.type, updateData.difficulty)
revalidatePath("/teacher/questions") await invalidateFor("questions.update")
revalidatePath("/admin/questions")
return { success: true, message: "Question updated successfully", data: id } return { success: true, message: "Question updated successfully", data: id }
} catch (e) { } catch (e) {
@@ -143,8 +141,7 @@ export async function deleteQuestionAction(
trackQuestionDeleted(questionId) trackQuestionDeleted(questionId)
revalidatePath("/teacher/questions") await invalidateFor("questions.delete")
revalidatePath("/admin/questions")
return { success: true, message: "Question deleted successfully", data: questionId } return { success: true, message: "Question deleted successfully", data: questionId }
} catch (e) { } catch (e) {
@@ -190,8 +187,7 @@ export async function deleteQuestionsBatchAction(
trackQuestionDeleted(id) trackQuestionDeleted(id)
} }
revalidatePath("/teacher/questions") await invalidateFor("questions.deleteBatch")
revalidatePath("/admin/questions")
return { success: true, message: "Batch delete completed", data: { deleted: deletedCount } } return { success: true, message: "Batch delete completed", data: { deleted: deletedCount } }
} catch (e) { } catch (e) {
@@ -369,8 +365,7 @@ export async function importQuestionsAction(
trackQuestionCreated(createdIds[i], item.type, item.difficulty) trackQuestionCreated(createdIds[i], item.type, item.difficulty)
} }
revalidatePath("/teacher/questions") await invalidateFor("questions.import")
revalidatePath("/admin/questions")
return { success: true, message: "Import completed", data: { imported: createdIds.length } } return { success: true, message: "Import completed", data: { imported: createdIds.length } }
} catch (e) { } catch (e) {

View File

@@ -1,10 +1,9 @@
"use server" "use server"
import { revalidatePath } from "next/cache"
import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard" import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard"
import { Permissions } from "@/shared/types/permissions" import { Permissions } from "@/shared/types/permissions"
import type { ActionState } from "@/shared/types/action-state" import type { ActionState } from "@/shared/types/action-state"
import { invalidateFor } from "@/shared/lib/cache"
import { getUserNamesByIds } from "@/modules/users/data-access" import { getUserNamesByIds } from "@/modules/users/data-access"
@@ -61,7 +60,7 @@ export async function saveSchedulingRulesAction(
} }
const id = await upsertSchedulingRules(parsed.data) const id = await upsertSchedulingRules(parsed.data)
revalidatePath("/admin/scheduling/rules") await invalidateFor("scheduling.saveRules")
return { success: true, message: "Scheduling rules saved", data: id } return { success: true, message: "Scheduling rules saved", data: id }
} catch (e) { } catch (e) {
if (e instanceof PermissionDeniedError) return { success: false, message: e.message } if (e instanceof PermissionDeniedError) return { success: false, message: e.message }
@@ -167,8 +166,7 @@ export async function applyAutoScheduleAction(
})) }))
) )
revalidatePath("/admin/scheduling/auto") await invalidateFor("scheduling.autoSchedule")
revalidatePath("/teacher/classes/schedule")
return { success: true, message: `Applied ${schedules.length} schedule items`, data: schedules.length } return { success: true, message: `Applied ${schedules.length} schedule items`, data: schedules.length }
} catch (e) { } catch (e) {
if (e instanceof PermissionDeniedError) return { success: false, message: e.message } if (e instanceof PermissionDeniedError) return { success: false, message: e.message }
@@ -205,8 +203,7 @@ export async function requestScheduleChangeAction(
} }
const id = await createScheduleChange(parsed.data, ctx.userId) const id = await createScheduleChange(parsed.data, ctx.userId)
revalidatePath("/teacher/schedule-changes") await invalidateFor("scheduling.requestChange")
revalidatePath("/admin/scheduling/changes")
return { success: true, message: "Schedule change request submitted", data: id } return { success: true, message: "Schedule change request submitted", data: id }
} catch (e) { } catch (e) {
if (e instanceof PermissionDeniedError) return { success: false, message: e.message } if (e instanceof PermissionDeniedError) return { success: false, message: e.message }
@@ -221,8 +218,7 @@ export async function approveScheduleChangeAction(changeId: string): Promise<Act
if (!changeId) return { success: false, message: "Missing change id" } if (!changeId) return { success: false, message: "Missing change id" }
await updateScheduleChangeStatus(changeId, "approved", ctx.userId) await updateScheduleChangeStatus(changeId, "approved", ctx.userId)
revalidatePath("/admin/scheduling/changes") await invalidateFor("scheduling.approveChange")
revalidatePath("/teacher/schedule-changes")
return { success: true, message: "Schedule change approved" } return { success: true, message: "Schedule change approved" }
} catch (e) { } catch (e) {
if (e instanceof PermissionDeniedError) return { success: false, message: e.message } if (e instanceof PermissionDeniedError) return { success: false, message: e.message }
@@ -240,8 +236,7 @@ export async function rejectScheduleChangeAction(
if (!changeId) return { success: false, message: "Missing change id" } if (!changeId) return { success: false, message: "Missing change id" }
await updateScheduleChangeStatus(changeId, "rejected", ctx.userId) await updateScheduleChangeStatus(changeId, "rejected", ctx.userId)
revalidatePath("/admin/scheduling/changes") await invalidateFor("scheduling.rejectChange")
revalidatePath("/teacher/schedule-changes")
return { success: true, message: reason ? `Rejected: ${reason}` : "Schedule change rejected" } return { success: true, message: reason ? `Rejected: ${reason}` : "Schedule change rejected" }
} catch (e) { } catch (e) {
if (e instanceof PermissionDeniedError) return { success: false, message: e.message } if (e instanceof PermissionDeniedError) return { success: false, message: e.message }

View File

@@ -1,10 +1,10 @@
"use server" "use server"
import { revalidatePath } from "next/cache"
import { after } from "next/server" import { after } from "next/server"
import { createId } from "@paralleldrive/cuid2" import { createId } from "@paralleldrive/cuid2"
import type { ActionState } from "@/shared/types/action-state" import type { ActionState } from "@/shared/types/action-state"
import { invalidateFor } from "@/shared/lib/cache"
import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard" import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard"
import { Permissions } from "@/shared/types/permissions" import { Permissions } from "@/shared/types/permissions"
import { logAudit } from "@/shared/lib/audit-logger" import { logAudit } from "@/shared/lib/audit-logger"
@@ -54,7 +54,7 @@ export async function createDepartmentAction(
}) })
) )
revalidatePath("/admin/school/departments") await invalidateFor("school.department.create")
return { success: true, message: "Department created" } return { success: true, message: "Department created" }
} catch (error) { } catch (error) {
if (error instanceof PermissionDeniedError) return { success: false, message: error.message } if (error instanceof PermissionDeniedError) return { success: false, message: error.message }
@@ -93,7 +93,7 @@ export async function updateDepartmentAction(
}) })
) )
revalidatePath("/admin/school/departments") await invalidateFor("school.department.update")
return { success: true, message: "Department updated" } return { success: true, message: "Department updated" }
} catch (error) { } catch (error) {
if (error instanceof PermissionDeniedError) return { success: false, message: error.message } if (error instanceof PermissionDeniedError) return { success: false, message: error.message }
@@ -114,7 +114,7 @@ export async function deleteDepartmentAction(departmentId: string): Promise<Acti
targetType: "department", targetType: "department",
}) })
) )
revalidatePath("/admin/school/departments") await invalidateFor("school.department.delete")
return { success: true, message: "Department deleted" } return { success: true, message: "Department deleted" }
} catch (error) { } catch (error) {
if (error instanceof PermissionDeniedError) return { success: false, message: error.message } if (error instanceof PermissionDeniedError) return { success: false, message: error.message }
@@ -156,7 +156,7 @@ export async function createAcademicYearAction(
}) })
) )
revalidatePath("/admin/school/academic-year") await invalidateFor("school.academicYear.create")
return { success: true, message: "Academic year created" } return { success: true, message: "Academic year created" }
} catch (error) { } catch (error) {
if (error instanceof PermissionDeniedError) return { success: false, message: error.message } if (error instanceof PermissionDeniedError) return { success: false, message: error.message }
@@ -199,7 +199,7 @@ export async function updateAcademicYearAction(
}) })
) )
revalidatePath("/admin/school/academic-year") await invalidateFor("school.academicYear.update")
return { success: true, message: "Academic year updated" } return { success: true, message: "Academic year updated" }
} catch (error) { } catch (error) {
if (error instanceof PermissionDeniedError) return { success: false, message: error.message } if (error instanceof PermissionDeniedError) return { success: false, message: error.message }
@@ -212,7 +212,7 @@ export async function deleteAcademicYearAction(academicYearId: string): Promise<
try { try {
await requirePermission(Permissions.SCHOOL_MANAGE) await requirePermission(Permissions.SCHOOL_MANAGE)
await deleteAcademicYear(academicYearId) await deleteAcademicYear(academicYearId)
revalidatePath("/admin/school/academic-year") await invalidateFor("school.academicYear.delete")
return { success: true, message: "Academic year deleted" } return { success: true, message: "Academic year deleted" }
} catch (error) { } catch (error) {
if (error instanceof PermissionDeniedError) return { success: false, message: error.message } if (error instanceof PermissionDeniedError) return { success: false, message: error.message }
@@ -245,7 +245,7 @@ export async function createSchoolAction(
logAudit({ action: "school.create", module: "school", targetType: "school", detail: { name: parsed.data.name } }) logAudit({ action: "school.create", module: "school", targetType: "school", detail: { name: parsed.data.name } })
) )
revalidatePath("/admin/school/schools") await invalidateFor("school.school.create")
return { success: true, message: "School created" } return { success: true, message: "School created" }
} catch (error) { } catch (error) {
if (error instanceof PermissionDeniedError) return { success: false, message: error.message } if (error instanceof PermissionDeniedError) return { success: false, message: error.message }
@@ -284,7 +284,7 @@ export async function updateSchoolAction(
}) })
) )
revalidatePath("/admin/school/schools") await invalidateFor("school.school.update")
return { success: true, message: "School updated" } return { success: true, message: "School updated" }
} catch (error) { } catch (error) {
if (error instanceof PermissionDeniedError) return { success: false, message: error.message } if (error instanceof PermissionDeniedError) return { success: false, message: error.message }
@@ -302,8 +302,7 @@ export async function deleteSchoolAction(schoolId: string): Promise<ActionState<
logAudit({ action: "school.delete", module: "school", targetId: schoolId, targetType: "school" }) logAudit({ action: "school.delete", module: "school", targetId: schoolId, targetType: "school" })
) )
revalidatePath("/admin/school/schools") await invalidateFor("school.school.delete")
revalidatePath("/admin/school/grades")
return { success: true, message: "School deleted" } return { success: true, message: "School deleted" }
} catch (error) { } catch (error) {
if (error instanceof PermissionDeniedError) return { success: false, message: error.message } if (error instanceof PermissionDeniedError) return { success: false, message: error.message }
@@ -347,7 +346,7 @@ export async function createGradeAction(
}) })
) )
revalidatePath("/admin/school/grades") await invalidateFor("school.grade.create")
return { success: true, message: "Grade created" } return { success: true, message: "Grade created" }
} catch (error) { } catch (error) {
if (error instanceof PermissionDeniedError) return { success: false, message: error.message } if (error instanceof PermissionDeniedError) return { success: false, message: error.message }
@@ -392,7 +391,7 @@ export async function updateGradeAction(
}) })
) )
revalidatePath("/admin/school/grades") await invalidateFor("school.grade.update")
return { success: true, message: "Grade updated" } return { success: true, message: "Grade updated" }
} catch (error) { } catch (error) {
if (error instanceof PermissionDeniedError) return { success: false, message: error.message } if (error instanceof PermissionDeniedError) return { success: false, message: error.message }
@@ -413,7 +412,7 @@ export async function deleteGradeAction(gradeId: string): Promise<ActionState<st
targetType: "grade", targetType: "grade",
}) })
) )
revalidatePath("/admin/school/grades") await invalidateFor("school.grade.delete")
return { success: true, message: "Grade deleted" } return { success: true, message: "Grade deleted" }
} catch (error) { } catch (error) {
if (error instanceof PermissionDeniedError) return { success: false, message: error.message } if (error instanceof PermissionDeniedError) return { success: false, message: error.message }
@@ -447,7 +446,7 @@ export async function promoteGradesAction(
}) })
) )
revalidatePath("/admin/school/grades") await invalidateFor("school.grade.promote")
return { success: true, message: `Promoted ${result.promoted} grades` } return { success: true, message: `Promoted ${result.promoted} grades` }
} catch (error) { } catch (error) {
if (error instanceof PermissionDeniedError) return { success: false, message: error.message } if (error instanceof PermissionDeniedError) return { success: false, message: error.message }

View File

@@ -7,7 +7,7 @@
"use server"; "use server";
import { getTranslations } from "next-intl/server"; import { getTranslations } from "next-intl/server";
import { revalidatePath } from "next/cache"; import { invalidateFor } from "@/shared/lib/cache";
import { import {
createStandardSchema, createStandardSchema,
updateStandardSchema, updateStandardSchema,
@@ -99,7 +99,7 @@ export async function createStandardAction(
return { success: false, message: t("error.unauthorized") }; return { success: false, message: t("error.unauthorized") };
} }
const standard = await createStandard(parseResult.data, auth.userId); const standard = await createStandard(parseResult.data, auth.userId);
revalidatePath("/admin/standards"); await invalidateFor("standards.create");
return { success: true, data: { standard } }; return { success: true, data: { standard } };
} catch (e) { } catch (e) {
return handleActionError(e); return handleActionError(e);
@@ -123,7 +123,7 @@ export async function updateStandardAction(
if (!updated) { if (!updated) {
return { success: false, message: t("error.notFound") }; return { success: false, message: t("error.notFound") };
} }
revalidatePath("/admin/standards"); await invalidateFor("standards.update");
return { success: true, data: { standard: updated } }; return { success: true, data: { standard: updated } };
} catch (e) { } catch (e) {
return handleActionError(e); return handleActionError(e);
@@ -137,7 +137,7 @@ export async function deactivateStandardAction(id: string): Promise<ActionState<
try { try {
await requirePermission(Permissions.STANDARD_MANAGE); await requirePermission(Permissions.STANDARD_MANAGE);
await deactivateStandard(id); await deactivateStandard(id);
revalidatePath("/admin/standards"); await invalidateFor("standards.deactivate");
return { success: true, data: null }; return { success: true, data: null };
} catch (e) { } catch (e) {
return handleActionError(e); return handleActionError(e);
@@ -197,7 +197,7 @@ export async function linkPlanToStandardAction(
parseResult.data.relationType, parseResult.data.relationType,
auth.userId, auth.userId,
); );
revalidatePath(`/teacher/lesson-plans/${parseResult.data.planId}/edit`); await invalidateFor("standards.linkPlan", { planId: parseResult.data.planId });
return { success: true, data: { link } }; return { success: true, data: { link } };
} catch (e) { } catch (e) {
return handleActionError(e); return handleActionError(e);
@@ -214,7 +214,7 @@ export async function unlinkPlanFromStandardAction(
try { try {
await requirePermission(Permissions.STANDARD_LINK); await requirePermission(Permissions.STANDARD_LINK);
await unlinkPlanFromStandard(planId, standardId); await unlinkPlanFromStandard(planId, standardId);
revalidatePath(`/teacher/lesson-plans/${planId}/edit`); await invalidateFor("standards.unlinkPlan", { planId });
return { success: true, data: null }; return { success: true, data: null };
} catch (e) { } catch (e) {
return handleActionError(e); return handleActionError(e);

View File

@@ -3,7 +3,7 @@
import { requirePermission } from "@/shared/lib/auth-guard"; import { requirePermission } from "@/shared/lib/auth-guard";
import { Permissions } from "@/shared/types/permissions"; import { Permissions } from "@/shared/types/permissions";
import type { ActionState } from "@/shared/types/action-state"; import type { ActionState } from "@/shared/types/action-state";
import { revalidatePath } from "next/cache"; import { invalidateFor } from "@/shared/lib/cache";
import { getTranslations } from "next-intl/server"; import { getTranslations } from "next-intl/server";
import { getCurrentStudentUser } from "@/modules/users/data-access"; import { getCurrentStudentUser } from "@/modules/users/data-access";
import { getGradeNameById } from "@/modules/school/data-access"; import { getGradeNameById } from "@/modules/school/data-access";
@@ -66,7 +66,7 @@ export async function reorderChaptersAction(
return { success: false, message: t("chapterNotBelong") }; return { success: false, message: t("chapterNotBelong") };
} }
await reorderChapters(chapterId, newIndex, parentId); await reorderChapters(chapterId, newIndex, parentId);
revalidatePath(`/teacher/textbooks/${textbookId}`); await invalidateFor("textbooks.chapter.reorder", { textbookId });
return { success: true, message: t("chaptersReordered") }; return { success: true, message: t("chaptersReordered") };
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)
@@ -96,7 +96,7 @@ export async function createTextbookAction(
try { try {
await requirePermission(Permissions.TEXTBOOK_CREATE); await requirePermission(Permissions.TEXTBOOK_CREATE);
await createTextbook(parsed.data); await createTextbook(parsed.data);
revalidatePath("/teacher/textbooks"); await invalidateFor("textbooks.create");
return { return {
success: true, success: true,
message: t("createSuccess"), message: t("createSuccess"),
@@ -131,7 +131,7 @@ export async function updateTextbookAction(
try { try {
await requirePermission(Permissions.TEXTBOOK_UPDATE); await requirePermission(Permissions.TEXTBOOK_UPDATE);
await updateTextbook(parsed.data); await updateTextbook(parsed.data);
revalidatePath(`/teacher/textbooks/${textbookId}`); await invalidateFor("textbooks.update", { id: textbookId });
return { return {
success: true, success: true,
message: t("updateSuccess"), message: t("updateSuccess"),
@@ -148,7 +148,7 @@ export async function deleteTextbookAction(
const t = await getTranslations("textbooks.action"); const t = await getTranslations("textbooks.action");
await requirePermission(Permissions.TEXTBOOK_DELETE); await requirePermission(Permissions.TEXTBOOK_DELETE);
await deleteTextbook(textbookId); await deleteTextbook(textbookId);
revalidatePath("/teacher/textbooks"); await invalidateFor("textbooks.delete");
return { return {
success: true, success: true,
message: t("deleteSuccess"), message: t("deleteSuccess"),
@@ -183,7 +183,7 @@ export async function createChapterAction(
try { try {
await requirePermission(Permissions.TEXTBOOK_CREATE); await requirePermission(Permissions.TEXTBOOK_CREATE);
await createChapter(parsed.data); await createChapter(parsed.data);
revalidatePath(`/teacher/textbooks/${textbookId}`); await invalidateFor("textbooks.chapter.create", { textbookId });
return { success: true, message: t("chapterCreateSuccess") }; return { success: true, message: t("chapterCreateSuccess") };
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)
@@ -217,7 +217,7 @@ export async function updateChapterContentAction(
return { success: false, message: t("chapterNotBelong") }; return { success: false, message: t("chapterNotBelong") };
} }
await updateChapterContent(parsed.data); await updateChapterContent(parsed.data);
revalidatePath(`/teacher/textbooks/${textbookId}`); await invalidateFor("textbooks.chapter.update", { textbookId });
return { success: true, message: t("contentUpdateSuccess") }; return { success: true, message: t("contentUpdateSuccess") };
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)
@@ -237,7 +237,7 @@ export async function deleteChapterAction(
return { success: false, message: t("chapterNotBelong") }; return { success: false, message: t("chapterNotBelong") };
} }
await deleteChapter(chapterId); await deleteChapter(chapterId);
revalidatePath(`/teacher/textbooks/${textbookId}`); await invalidateFor("textbooks.chapter.delete", { textbookId });
return { success: true, message: t("chapterDeleteSuccess") }; return { success: true, message: t("chapterDeleteSuccess") };
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)
@@ -274,7 +274,7 @@ export async function createKnowledgePointAction(
return { success: false, message: t("chapterNotBelong") }; return { success: false, message: t("chapterNotBelong") };
} }
await createKnowledgePoint(parsed.data); await createKnowledgePoint(parsed.data);
revalidatePath(`/teacher/textbooks/${textbookId}`); await invalidateFor("textbooks.knowledgePoint.create", { textbookId });
return { success: true, message: t("kpCreateSuccess") }; return { success: true, message: t("kpCreateSuccess") };
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)
@@ -294,7 +294,7 @@ export async function deleteKnowledgePointAction(
return { success: false, message: t("kpNotBelong") }; return { success: false, message: t("kpNotBelong") };
} }
await deleteKnowledgePoint(kpId); await deleteKnowledgePoint(kpId);
revalidatePath(`/teacher/textbooks/${textbookId}`); await invalidateFor("textbooks.knowledgePoint.delete", { textbookId });
return { success: true, message: t("kpDeleteSuccess") }; return { success: true, message: t("kpDeleteSuccess") };
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)
@@ -331,7 +331,7 @@ export async function updateKnowledgePointAction(
return { success: false, message: t("kpNotBelong") }; return { success: false, message: t("kpNotBelong") };
} }
await updateKnowledgePoint(parsed.data); await updateKnowledgePoint(parsed.data);
revalidatePath(`/teacher/textbooks/${textbookId}`); await invalidateFor("textbooks.knowledgePoint.update", { textbookId });
return { success: true, message: t("kpUpdateSuccess") }; return { success: true, message: t("kpUpdateSuccess") };
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)
@@ -490,7 +490,7 @@ export async function createPrerequisiteAction(
} }
await createPrerequisite(parsed.data); await createPrerequisite(parsed.data);
revalidatePath(`/teacher/textbooks/${textbookId}`); await invalidateFor("textbooks.prerequisite.create", { textbookId });
return { success: true, message: t("prerequisiteCreated") }; return { success: true, message: t("prerequisiteCreated") };
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)
@@ -536,7 +536,7 @@ export async function deletePrerequisiteAction(
} }
await deletePrerequisite(parsed.data); await deletePrerequisite(parsed.data);
revalidatePath(`/teacher/textbooks/${textbookId}`); await invalidateFor("textbooks.prerequisite.delete", { textbookId });
return { success: true, message: t("prerequisiteDeleted") }; return { success: true, message: t("prerequisiteDeleted") };
} catch (e) { } catch (e) {
return handleActionError(e) return handleActionError(e)

View File

@@ -1,8 +1,8 @@
"use server" "use server"
import { revalidatePath } from "next/cache"
import { z } from "zod" import { z } from "zod"
import { invalidateFor } from "@/shared/lib/cache"
import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard" import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard"
import { Permissions } from "@/shared/types/permissions" import { Permissions } from "@/shared/types/permissions"
import type { ActionState } from "@/shared/types/action-state" import type { ActionState } from "@/shared/types/action-state"
@@ -54,8 +54,7 @@ export async function updateUserProfile(
await updateUserProfileById(ctx.userId, parsed.data) await updateUserProfileById(ctx.userId, parsed.data)
revalidatePath("/profile") await invalidateFor("users.update")
revalidatePath("/settings")
return { success: true, message: "Profile updated successfully" } return { success: true, message: "Profile updated successfully" }
} catch (e) { } catch (e) {
@@ -127,7 +126,7 @@ export async function importUsersAction(
} }
const result = await batchImportUsers(validation.valid) const result = await batchImportUsers(validation.valid)
revalidatePath("/admin/users/import") await invalidateFor("users.import")
return { return {
success: true, success: true,
@@ -180,7 +179,7 @@ export async function deleteUserAction(
try { try {
await requirePermission(Permissions.USER_MANAGE) await requirePermission(Permissions.USER_MANAGE)
await deleteUserById(userId) await deleteUserById(userId)
revalidatePath("/admin/users") await invalidateFor("users.delete")
return { success: true, message: "用户已删除" } return { success: true, message: "用户已删除" }
} catch (e) { } catch (e) {
if (e instanceof PermissionDeniedError) { if (e instanceof PermissionDeniedError) {