refactor(actions): 组 C 模块迁移至 invalidateFor(lesson-preparation/elective/announcements/messaging/notifications/settings/rbac)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use server"
|
||||
|
||||
import { revalidatePath } from "next/cache"
|
||||
import { invalidateFor } from "@/shared/lib/cache"
|
||||
import { createId } from "@paralleldrive/cuid2"
|
||||
import { getTranslations } from "next-intl/server"
|
||||
|
||||
@@ -149,8 +149,7 @@ export async function createAnnouncementAction(
|
||||
}
|
||||
}
|
||||
|
||||
revalidatePath("/admin/announcements")
|
||||
revalidatePath("/announcements")
|
||||
await invalidateFor("announcements.create", { id })
|
||||
|
||||
void trackEvent({
|
||||
event: isPublished ? "announcement.published" : "announcement.created",
|
||||
@@ -226,9 +225,7 @@ export async function updateAnnouncementAction(
|
||||
}
|
||||
}
|
||||
|
||||
revalidatePath("/admin/announcements")
|
||||
revalidatePath(`/admin/announcements/${id}`)
|
||||
revalidatePath("/announcements")
|
||||
await invalidateFor("announcements.update", { id })
|
||||
|
||||
void trackEvent({
|
||||
event: isPublished && !wasPublished ? "announcement.published" : "announcement.updated",
|
||||
@@ -257,8 +254,7 @@ export async function deleteAnnouncementAction(id: string): Promise<ActionState<
|
||||
|
||||
await deleteAnnouncementById(id)
|
||||
|
||||
revalidatePath("/admin/announcements")
|
||||
revalidatePath("/announcements")
|
||||
await invalidateFor("announcements.delete", { id })
|
||||
|
||||
void trackEvent({
|
||||
event: "announcement.deleted",
|
||||
@@ -289,9 +285,7 @@ export async function publishAnnouncementAction(id: string): Promise<ActionState
|
||||
// 发布成功后触发通知(失败不阻塞)
|
||||
await notifyAnnouncementPublished(existing)
|
||||
|
||||
revalidatePath("/admin/announcements")
|
||||
revalidatePath(`/admin/announcements/${id}`)
|
||||
revalidatePath("/announcements")
|
||||
await invalidateFor("announcements.publish", { id })
|
||||
|
||||
void trackEvent({
|
||||
event: "announcement.published",
|
||||
@@ -316,9 +310,7 @@ export async function archiveAnnouncementAction(id: string): Promise<ActionState
|
||||
|
||||
await archiveAnnouncementById(id)
|
||||
|
||||
revalidatePath("/admin/announcements")
|
||||
revalidatePath(`/admin/announcements/${id}`)
|
||||
revalidatePath("/announcements")
|
||||
await invalidateFor("announcements.archive", { id })
|
||||
|
||||
void trackEvent({
|
||||
event: "announcement.archived",
|
||||
@@ -348,8 +340,7 @@ export async function toggleAnnouncementPinAction(id: string): Promise<ActionSta
|
||||
if (!existing) return { success: false, message: t("messages.notFound") }
|
||||
|
||||
await toggleAnnouncementPin(id)
|
||||
revalidatePath("/admin/announcements")
|
||||
revalidatePath("/announcements")
|
||||
await invalidateFor("announcements.togglePin", { id })
|
||||
|
||||
void trackEvent({
|
||||
event: "announcement.pin_toggled",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use server"
|
||||
|
||||
import { revalidatePath } from "next/cache"
|
||||
import { invalidateFor } from "@/shared/lib/cache"
|
||||
import { getTranslations } from "next-intl/server"
|
||||
import { requirePermission } from "@/shared/lib/auth-guard"
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
@@ -32,19 +32,6 @@ import {
|
||||
} from "./data-access-operations"
|
||||
import { COURSE_SELECTION_STATUS_LABEL_KEYS } from "./constants"
|
||||
|
||||
const revalidateElectivePaths = (id?: string) => {
|
||||
revalidatePath("/admin/elective")
|
||||
revalidatePath("/teacher/elective")
|
||||
revalidatePath("/student/elective")
|
||||
revalidatePath("/parent/elective")
|
||||
if (id) {
|
||||
revalidatePath(`/admin/elective/${id}`)
|
||||
revalidatePath(`/admin/elective/${id}/edit`)
|
||||
revalidatePath(`/teacher/elective/${id}`)
|
||||
revalidatePath(`/teacher/elective/${id}/edit`)
|
||||
}
|
||||
}
|
||||
|
||||
const requireCourseId = (formData: FormData): string => {
|
||||
const id = String(formData.get("courseId") ?? "")
|
||||
if (!id) throw new Error("Course ID is required")
|
||||
@@ -119,7 +106,7 @@ export async function createElectiveCourseAction(
|
||||
}
|
||||
}
|
||||
const id = await createElectiveCourse(parsed.data, ctx.userId)
|
||||
revalidateElectivePaths(id)
|
||||
await invalidateFor("elective.create", { id })
|
||||
await trackEvent({
|
||||
event: "elective.course_created",
|
||||
userId: ctx.userId,
|
||||
@@ -175,7 +162,7 @@ export async function updateElectiveCourseAction(
|
||||
}
|
||||
}
|
||||
await updateElectiveCourse(id, parsed.data)
|
||||
revalidateElectivePaths(id)
|
||||
await invalidateFor("elective.update", { id })
|
||||
await trackEvent({
|
||||
event: "elective.course_updated",
|
||||
userId: ctx.userId,
|
||||
@@ -206,7 +193,7 @@ export async function deleteElectiveCourseAction(
|
||||
}
|
||||
|
||||
await deleteElectiveCourse(id)
|
||||
revalidateElectivePaths()
|
||||
await invalidateFor("elective.delete", { id })
|
||||
await trackEvent({
|
||||
event: "elective.course_deleted",
|
||||
userId: ctx.userId,
|
||||
@@ -237,7 +224,7 @@ export async function openSelectionAction(
|
||||
}
|
||||
|
||||
await openSelection(courseId)
|
||||
revalidateElectivePaths(courseId)
|
||||
await invalidateFor("elective.openSelection", { id: courseId })
|
||||
await trackEvent({
|
||||
event: "elective.selection_opened",
|
||||
userId: ctx.userId,
|
||||
@@ -268,7 +255,7 @@ export async function closeSelectionAction(
|
||||
}
|
||||
|
||||
await closeSelection(courseId)
|
||||
revalidateElectivePaths(courseId)
|
||||
await invalidateFor("elective.closeSelection", { id: courseId })
|
||||
await trackEvent({
|
||||
event: "elective.selection_closed",
|
||||
userId: ctx.userId,
|
||||
@@ -308,7 +295,7 @@ export async function runLotteryAction(
|
||||
}
|
||||
|
||||
const result = await runLottery(parsed.data.courseId)
|
||||
revalidateElectivePaths(parsed.data.courseId)
|
||||
await invalidateFor("elective.runLottery", { id: parsed.data.courseId })
|
||||
await trackEvent({
|
||||
event: "elective.lottery_completed",
|
||||
userId: ctx.userId,
|
||||
@@ -348,7 +335,7 @@ export async function selectCourseAction(
|
||||
}
|
||||
}
|
||||
const result = await selectCourse(parsed.data.courseId, ctx.userId, parsed.data.priority)
|
||||
revalidateElectivePaths(parsed.data.courseId)
|
||||
await invalidateFor("elective.selectCourse", { id: parsed.data.courseId })
|
||||
await trackEvent({
|
||||
event: "elective.course_selected",
|
||||
userId: ctx.userId,
|
||||
@@ -385,7 +372,7 @@ export async function dropCourseAction(
|
||||
}
|
||||
}
|
||||
await dropCourse(parsed.data.courseId, ctx.userId, parsed.data.dropReason)
|
||||
revalidateElectivePaths(parsed.data.courseId)
|
||||
await invalidateFor("elective.dropCourse", { id: parsed.data.courseId })
|
||||
await trackEvent({
|
||||
event: "elective.course_dropped",
|
||||
userId: ctx.userId,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"use server";
|
||||
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { invalidateFor } from "@/shared/lib/cache";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
getAttachmentsByPlanId,
|
||||
@@ -80,7 +80,7 @@ export async function createLessonPlanAttachmentAction(
|
||||
}
|
||||
await createAttachment(parseResult.data, auth.userId);
|
||||
const attachment = await getAttachmentById(parseResult.data.planId);
|
||||
revalidatePath(`/teacher/lesson-plans/${parseResult.data.planId}/edit`);
|
||||
await invalidateFor("lesson-preparation.attachment.create", { id: parseResult.data.planId });
|
||||
return { success: true, data: { attachment } };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { invalidateFor } from "@/shared/lib/cache";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
getCommentsByPlanId,
|
||||
@@ -73,7 +73,7 @@ export async function createLessonPlanCommentAction(
|
||||
return { success: false, message: "Unauthorized" };
|
||||
}
|
||||
const comment = await createComment(parseResult.data, auth.userId);
|
||||
revalidatePath(`/teacher/lesson-plans/${parseResult.data.planId}/edit`);
|
||||
await invalidateFor("lesson-preparation.comment.create", { id: parseResult.data.planId });
|
||||
return { success: true, data: { comment } };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"use server";
|
||||
|
||||
import { z } from "zod";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { invalidateFor } from "@/shared/lib/cache";
|
||||
import {
|
||||
getFormativeItemsByPlanId,
|
||||
createFormativeItem,
|
||||
@@ -65,7 +65,7 @@ export async function createFormativeItemAction(
|
||||
return { success: false, message: "Invalid input" };
|
||||
}
|
||||
const item = await createFormativeItem(parseResult.data);
|
||||
revalidatePath(`/teacher/lesson-plans/${parseResult.data.planId}/edit`);
|
||||
await invalidateFor("lesson-preparation.formative.create", { id: parseResult.data.planId });
|
||||
return { success: true, data: { item } };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { invalidateFor } from "@/shared/lib/cache";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import {
|
||||
requirePermission,
|
||||
@@ -57,8 +57,7 @@ export async function publishLessonPlanHomeworkAction(input: {
|
||||
homeworkTitle,
|
||||
homeworkDescription,
|
||||
});
|
||||
revalidatePath("/teacher/lesson-plans");
|
||||
revalidatePath("/teacher/homework");
|
||||
await invalidateFor("lesson-preparation.publishHomework");
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"use server";
|
||||
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { invalidateFor } from "@/shared/lib/cache";
|
||||
import {
|
||||
submitLessonPlanForReviewSchema,
|
||||
reviewLessonPlanSchema,
|
||||
@@ -40,7 +40,7 @@ export async function submitLessonPlanForReviewAction(
|
||||
return { success: false, message: t("error.unauthorized") };
|
||||
}
|
||||
const status = await submitForReview(parseResult.data.planId, auth.userId);
|
||||
revalidatePath(`/teacher/lesson-plans/${parseResult.data.planId}/edit`);
|
||||
await invalidateFor("lesson-preparation.review.submit", { id: parseResult.data.planId });
|
||||
return { success: true, data: { status } };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
@@ -70,7 +70,7 @@ export async function reviewLessonPlanAction(
|
||||
parseResult.data.decision,
|
||||
parseResult.data.reviewComment,
|
||||
);
|
||||
revalidatePath(`/teacher/lesson-plans/${parseResult.data.planId}/edit`);
|
||||
await invalidateFor("lesson-preparation.review.review", { id: parseResult.data.planId });
|
||||
return { success: true, data: { newStatus, record } };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
@@ -120,7 +120,7 @@ export async function withdrawSubmissionAction(
|
||||
try {
|
||||
await requirePermission(Permissions.LESSON_PLAN_UPDATE);
|
||||
const status = await withdrawSubmission(planId);
|
||||
revalidatePath(`/teacher/lesson-plans/${planId}/edit`);
|
||||
await invalidateFor("lesson-preparation.review.withdraw", { id: planId });
|
||||
return { success: true, data: { status } };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"use server";
|
||||
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { invalidateFor } from "@/shared/lib/cache";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
getSchedulesByPlanId,
|
||||
@@ -64,8 +64,7 @@ export async function createLessonPlanScheduleAction(
|
||||
createdBy: auth.userId,
|
||||
});
|
||||
|
||||
revalidatePath("/teacher/lesson-plans");
|
||||
revalidatePath("/teacher/lesson-plans/calendar");
|
||||
await invalidateFor("lesson-preparation.schedule.create");
|
||||
return { success: true, data: { schedule } };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
@@ -78,8 +77,7 @@ export async function deleteLessonPlanScheduleAction(
|
||||
try {
|
||||
await requirePermission(Permissions.LESSON_PLAN_UPDATE);
|
||||
await deleteSchedule(scheduleId);
|
||||
revalidatePath("/teacher/lesson-plans");
|
||||
revalidatePath("/teacher/lesson-plans/calendar");
|
||||
await invalidateFor("lesson-preparation.schedule.delete");
|
||||
return { success: true, data: null };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"use server";
|
||||
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { invalidateFor } from "@/shared/lib/cache";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
getSubstitutesByPlanId,
|
||||
@@ -105,7 +105,7 @@ export async function createSubstituteAction(
|
||||
},
|
||||
auth.userId,
|
||||
);
|
||||
revalidatePath(`/teacher/lesson-plans/${parseResult.data.planId}/edit`);
|
||||
await invalidateFor("lesson-preparation.substitute.create", { id: parseResult.data.planId });
|
||||
return { success: true, data: { substitute } };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { invalidateFor } from "@/shared/lib/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { requirePermission } from "@/shared/lib/auth-guard";
|
||||
@@ -125,7 +125,7 @@ export async function createLessonPlanAction(
|
||||
return key;
|
||||
},
|
||||
});
|
||||
revalidatePath("/teacher/lesson-plans");
|
||||
await invalidateFor("lesson-preparation.create");
|
||||
return { success: true, data: { planId } };
|
||||
} catch (e) {
|
||||
if (e instanceof LessonPlanDataError && e.code === "TEMPLATE_NOT_FOUND")
|
||||
@@ -154,7 +154,7 @@ export async function updateLessonPlanAction(input: {
|
||||
...(parsed.data.title ? { title: parsed.data.title } : {}),
|
||||
content,
|
||||
});
|
||||
revalidatePath("/teacher/lesson-plans");
|
||||
await invalidateFor("lesson-preparation.update");
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
@@ -235,7 +235,7 @@ export async function revertLessonPlanVersionAction(input: {
|
||||
revertLabel,
|
||||
);
|
||||
if (!result) return { success: false, message: t("error.versionNotFound") };
|
||||
revalidatePath(`/teacher/lesson-plans/${parsed.data.planId}/edit`);
|
||||
await invalidateFor("lesson-preparation.revertVersion", { id: parsed.data.planId });
|
||||
return { success: true, data: { newVersionNo: result.newVersionNo } };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
@@ -249,7 +249,7 @@ export async function deleteLessonPlanAction(
|
||||
try {
|
||||
const ctx = await requirePermission(Permissions.LESSON_PLAN_DELETE);
|
||||
await softDeleteLessonPlan(planId, ctx.userId);
|
||||
revalidatePath("/teacher/lesson-plans");
|
||||
await invalidateFor("lesson-preparation.delete");
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
@@ -265,8 +265,7 @@ export async function publishLessonPlanAction(
|
||||
try {
|
||||
const ctx = await requirePermission(Permissions.LESSON_PLAN_PUBLISH);
|
||||
await publishLessonPlan(planId, ctx.userId);
|
||||
revalidatePath("/teacher/lesson-plans");
|
||||
revalidatePath(`/teacher/lesson-plans/${planId}/edit`);
|
||||
await invalidateFor("lesson-preparation.publish", { id: planId });
|
||||
return { success: true, message: t("action.publishPlanSuccess") };
|
||||
} catch (e) {
|
||||
if (e instanceof LessonPlanDataError && e.code === "NOT_FOUND")
|
||||
@@ -284,8 +283,7 @@ export async function unpublishLessonPlanAction(
|
||||
try {
|
||||
const ctx = await requirePermission(Permissions.LESSON_PLAN_PUBLISH);
|
||||
await unpublishLessonPlan(planId, ctx.userId);
|
||||
revalidatePath("/teacher/lesson-plans");
|
||||
revalidatePath(`/teacher/lesson-plans/${planId}/edit`);
|
||||
await invalidateFor("lesson-preparation.unpublish", { id: planId });
|
||||
return { success: true, message: t("action.unpublishPlanSuccess") };
|
||||
} catch (e) {
|
||||
if (e instanceof LessonPlanDataError && e.code === "NOT_FOUND")
|
||||
@@ -307,7 +305,7 @@ export async function duplicateLessonPlanAction(
|
||||
ctx.userId,
|
||||
t("error.duplicateSuffix"),
|
||||
);
|
||||
revalidatePath("/teacher/lesson-plans");
|
||||
await invalidateFor("lesson-preparation.duplicate");
|
||||
return { success: true, data: { newPlanId } };
|
||||
} catch (e) {
|
||||
if (e instanceof LessonPlanDataError && e.code === "NOT_FOUND")
|
||||
@@ -324,7 +322,7 @@ export async function duplicateLessonPlanFormAction(
|
||||
if (!planId) return;
|
||||
const result = await duplicateLessonPlanAction(planId);
|
||||
if (result.success && result.data) {
|
||||
revalidatePath("/teacher/lesson-plans");
|
||||
await invalidateFor("lesson-preparation.duplicate");
|
||||
redirect(`/teacher/lesson-plans/${result.data.newPlanId}/edit`);
|
||||
}
|
||||
}
|
||||
@@ -376,7 +374,7 @@ export async function deleteTemplateAction(
|
||||
try {
|
||||
const ctx = await requirePermission(Permissions.LESSON_PLAN_DELETE);
|
||||
await deletePersonalTemplate(templateId, ctx.userId);
|
||||
revalidatePath("/teacher/lesson-plans");
|
||||
await invalidateFor("lesson-preparation.template.delete");
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
return handleActionError(e);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use server"
|
||||
|
||||
import { revalidatePath } from "next/cache"
|
||||
import { invalidateFor } from "@/shared/lib/cache"
|
||||
import { getTranslations } from "next-intl/server"
|
||||
import { PermissionDeniedError, requirePermission } from "@/shared/lib/auth-guard"
|
||||
import { trackEvent } from "@/shared/lib/track-event"
|
||||
@@ -129,8 +129,7 @@ export async function sendMessageAction(
|
||||
metadata: { messageType: "message", messageId: id },
|
||||
})
|
||||
|
||||
revalidatePath("/messages")
|
||||
revalidatePath(`/messages/${id}`)
|
||||
await invalidateFor("messaging.send", { id })
|
||||
|
||||
void trackEvent({
|
||||
event: "message.sent",
|
||||
@@ -170,8 +169,7 @@ export async function markMessageAsReadAction(messageId: string): Promise<Action
|
||||
}
|
||||
|
||||
await markMessageAsRead(parsed.data.messageId, ctx.userId)
|
||||
revalidatePath("/messages")
|
||||
revalidatePath(`/messages/${parsed.data.messageId}`)
|
||||
await invalidateFor("messaging.markRead", { id: parsed.data.messageId })
|
||||
|
||||
void trackEvent({
|
||||
event: "message.marked_read",
|
||||
@@ -198,8 +196,7 @@ export async function deleteMessageAction(messageId: string): Promise<ActionStat
|
||||
}
|
||||
|
||||
await deleteMessage(parsed.data.messageId, ctx.userId)
|
||||
revalidatePath("/messages")
|
||||
revalidatePath(`/messages/${parsed.data.messageId}`)
|
||||
await invalidateFor("messaging.delete", { id: parsed.data.messageId })
|
||||
|
||||
void trackEvent({
|
||||
event: "message.deleted",
|
||||
@@ -310,8 +307,7 @@ export async function toggleMessageStarAction(messageId: string): Promise<Action
|
||||
}
|
||||
|
||||
await toggleMessageStar(parsed.data.messageId, ctx.userId)
|
||||
revalidatePath("/messages")
|
||||
revalidatePath(`/messages/${parsed.data.messageId}`)
|
||||
await invalidateFor("messaging.toggleStar", { id: parsed.data.messageId })
|
||||
|
||||
void trackEvent({
|
||||
event: "message.star_toggled",
|
||||
@@ -422,7 +418,7 @@ export async function deleteMessageDraftAction(draftId: string): Promise<ActionS
|
||||
const ctx = await requirePermission(Permissions.MESSAGE_SEND)
|
||||
|
||||
await deleteMessageDraft(draftId, ctx.userId)
|
||||
revalidatePath("/messages/compose")
|
||||
await invalidateFor("messaging.draft.delete")
|
||||
|
||||
return { success: true, message: "Draft deleted" }
|
||||
} catch (e) {
|
||||
@@ -448,7 +444,7 @@ export async function bulkMarkMessagesAsReadAction(
|
||||
}
|
||||
|
||||
const updated = await bulkMarkMessagesAsRead(parsed.data.ids, ctx.userId)
|
||||
revalidatePath("/messages")
|
||||
await invalidateFor("messaging.bulkMarkRead")
|
||||
|
||||
void trackEvent({
|
||||
event: "message.marked_read",
|
||||
@@ -477,7 +473,7 @@ export async function bulkDeleteMessagesAction(
|
||||
}
|
||||
|
||||
await bulkDeleteMessages(parsed.data.ids, ctx.userId)
|
||||
revalidatePath("/messages")
|
||||
await invalidateFor("messaging.bulkDelete")
|
||||
|
||||
void trackEvent({
|
||||
event: "message.deleted",
|
||||
@@ -506,7 +502,7 @@ export async function bulkToggleMessagesStarAction(
|
||||
}
|
||||
|
||||
await bulkToggleMessagesStar(parsed.data.ids, ctx.userId)
|
||||
revalidatePath("/messages")
|
||||
await invalidateFor("messaging.bulkToggleStar")
|
||||
|
||||
void trackEvent({
|
||||
event: "message.star_toggled",
|
||||
@@ -550,8 +546,7 @@ export async function recallMessageAction(
|
||||
return { success: false, message: "Recall window expired" }
|
||||
}
|
||||
|
||||
revalidatePath("/messages")
|
||||
revalidatePath(`/messages/${parsed.data.messageId}`)
|
||||
await invalidateFor("messaging.recall", { id: parsed.data.messageId })
|
||||
|
||||
void trackEvent({
|
||||
event: "message.recalled",
|
||||
@@ -632,7 +627,7 @@ export async function sendGroupMessageAction(
|
||||
)
|
||||
)
|
||||
|
||||
revalidatePath("/messages")
|
||||
await invalidateFor("messaging.sendGroup")
|
||||
|
||||
void trackEvent({
|
||||
event: "message.group_sent",
|
||||
@@ -713,7 +708,7 @@ export async function saveMessageTemplateAction(
|
||||
content: input.content,
|
||||
sortOrder: input.sortOrder,
|
||||
})
|
||||
revalidatePath("/messages/compose")
|
||||
await invalidateFor("messaging.template.save", { id: input.templateId })
|
||||
return { success: true, message: "Template updated", data: input.templateId }
|
||||
}
|
||||
|
||||
@@ -724,7 +719,7 @@ export async function saveMessageTemplateAction(
|
||||
sortOrder: input.sortOrder,
|
||||
})
|
||||
|
||||
revalidatePath("/messages/compose")
|
||||
await invalidateFor("messaging.template.save", { id })
|
||||
|
||||
void trackEvent({
|
||||
event: "message.template_created",
|
||||
@@ -751,7 +746,7 @@ export async function deleteMessageTemplateAction(templateId: string): Promise<A
|
||||
}
|
||||
|
||||
await deleteMessageTemplate(parsed.data.messageId, ctx.userId)
|
||||
revalidatePath("/messages/compose")
|
||||
await invalidateFor("messaging.template.delete", { id: parsed.data.messageId })
|
||||
|
||||
return { success: true, message: "Template deleted" }
|
||||
} catch (e) {
|
||||
@@ -891,7 +886,7 @@ export async function blockUserAction(
|
||||
return { success: false, message: "User already blocked" }
|
||||
}
|
||||
|
||||
revalidatePath("/messages")
|
||||
await invalidateFor("messaging.block", { id: input.blockedId })
|
||||
|
||||
void trackEvent({
|
||||
event: "message.user_blocked",
|
||||
@@ -915,7 +910,7 @@ export async function unblockUserAction(blockedId: string): Promise<ActionState<
|
||||
try {
|
||||
const ctx = await requirePermission(Permissions.MESSAGE_SEND)
|
||||
await unblockUser(ctx.userId, blockedId)
|
||||
revalidatePath("/messages")
|
||||
await invalidateFor("messaging.unblock", { id: blockedId })
|
||||
|
||||
void trackEvent({
|
||||
event: "message.user_unblocked",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* 站内通知读取/标记已读复用 MESSAGE_READ 权限(任何能读消息的用户都能查看通知)。
|
||||
*/
|
||||
|
||||
import { revalidatePath } from "next/cache"
|
||||
import { invalidateFor } from "@/shared/lib/cache"
|
||||
import { z } from "zod"
|
||||
import { PermissionDeniedError, requirePermission } from "@/shared/lib/auth-guard"
|
||||
import { trackEvent } from "@/shared/lib/track-event"
|
||||
@@ -232,7 +232,7 @@ export async function markNotificationAsReadAction(
|
||||
}
|
||||
|
||||
await markNotificationAsRead(parsed.data, ctx.userId)
|
||||
revalidatePath("/messages")
|
||||
await invalidateFor("notifications.markRead", { id: parsed.data })
|
||||
|
||||
void trackEvent({
|
||||
event: "notification.marked_read",
|
||||
@@ -256,7 +256,7 @@ export async function markAllNotificationsAsReadAction(): Promise<ActionState<st
|
||||
try {
|
||||
const ctx = await requirePermission(Permissions.MESSAGE_READ)
|
||||
await markAllNotificationsAsRead(ctx.userId)
|
||||
revalidatePath("/messages")
|
||||
await invalidateFor("notifications.markAllRead")
|
||||
|
||||
void trackEvent({
|
||||
event: "notification.marked_all_read",
|
||||
@@ -287,7 +287,7 @@ export async function archiveNotificationAction(
|
||||
}
|
||||
|
||||
await archiveNotification(parsed.data, ctx.userId)
|
||||
revalidatePath("/messages")
|
||||
await invalidateFor("notifications.archive", { id: parsed.data })
|
||||
|
||||
void trackEvent({
|
||||
event: "notification.archived",
|
||||
@@ -372,7 +372,7 @@ export async function updateNotificationPreferencesAction(
|
||||
return { success: false, message: "Failed to update notification preferences" }
|
||||
}
|
||||
|
||||
revalidatePath("/settings")
|
||||
await invalidateFor("notifications.updatePreferences")
|
||||
|
||||
return { success: true, message: "Notification preferences updated", data: updated }
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use server"
|
||||
|
||||
import { revalidatePath } from "next/cache"
|
||||
import { after } from "next/server"
|
||||
|
||||
import type { ActionState } from "@/shared/types/action-state"
|
||||
@@ -8,6 +7,7 @@ import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guar
|
||||
import { Permissions, type Permission } from "@/shared/types/permissions"
|
||||
import { logAudit } from "@/shared/lib/audit-logger"
|
||||
import { logDataChange } from "@/shared/lib/change-logger"
|
||||
import { invalidateFor } from "@/shared/lib/cache"
|
||||
|
||||
import {
|
||||
AssignUserRolesSchema,
|
||||
@@ -71,7 +71,7 @@ export async function createRoleAction(
|
||||
})
|
||||
)
|
||||
|
||||
revalidatePath("/admin/roles")
|
||||
await invalidateFor("rbac.role.create", { id: role.id })
|
||||
return { success: true, data: role, message: "Role created" }
|
||||
} catch (error) {
|
||||
if (error instanceof PermissionDeniedError) {
|
||||
@@ -131,8 +131,7 @@ export async function updateRoleAction(
|
||||
})
|
||||
)
|
||||
|
||||
revalidatePath("/admin/roles")
|
||||
revalidatePath(`/admin/roles/${roleId}`)
|
||||
await invalidateFor("rbac.role.update", { id: roleId })
|
||||
return { success: true, data: role, message: "Role updated" }
|
||||
} catch (error) {
|
||||
if (error instanceof PermissionDeniedError) {
|
||||
@@ -177,7 +176,7 @@ export async function deleteRoleAction(
|
||||
})
|
||||
)
|
||||
|
||||
revalidatePath("/admin/roles")
|
||||
await invalidateFor("rbac.role.delete", { id: roleId })
|
||||
return { success: true, message: "Role deleted" }
|
||||
} catch (error) {
|
||||
if (error instanceof PermissionDeniedError) {
|
||||
@@ -212,8 +211,7 @@ export async function toggleRoleEnabledAction(
|
||||
})
|
||||
)
|
||||
|
||||
revalidatePath("/admin/roles")
|
||||
revalidatePath(`/admin/roles/${roleId}`)
|
||||
await invalidateFor("rbac.role.toggleEnabled", { id: roleId })
|
||||
return { success: true, data: role, message: enabled ? "Role enabled" : "Role disabled" }
|
||||
} catch (error) {
|
||||
if (error instanceof PermissionDeniedError) {
|
||||
@@ -283,8 +281,7 @@ export async function setRolePermissionsAction(
|
||||
})
|
||||
)
|
||||
|
||||
revalidatePath("/admin/roles")
|
||||
revalidatePath(`/admin/roles/${parsed.data.roleId}`)
|
||||
await invalidateFor("rbac.role.setPermissions", { id: parsed.data.roleId })
|
||||
return { success: true, data: permissions, message: "Permissions updated" }
|
||||
} catch (error) {
|
||||
if (error instanceof PermissionDeniedError) {
|
||||
@@ -348,7 +345,7 @@ export async function assignUserRolesAction(
|
||||
})
|
||||
)
|
||||
|
||||
revalidatePath("/admin/users")
|
||||
await invalidateFor("rbac.assignUserRoles", { id: parsed.data.userId })
|
||||
return {
|
||||
success: true,
|
||||
data: parsed.data.roleNames,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"use server"
|
||||
|
||||
import { revalidatePath } from "next/cache"
|
||||
|
||||
import type { ActionState } from "@/shared/types/action-state"
|
||||
import { requirePermission } from "@/shared/lib/auth-guard"
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
@@ -11,6 +9,7 @@ import {
|
||||
deleteFileAttachment,
|
||||
getFileByUrl,
|
||||
} from "@/modules/files/data-access"
|
||||
import { invalidateFor } from "@/shared/lib/cache"
|
||||
|
||||
/**
|
||||
* 清理旧头像文件(磁盘 + DB 记录)
|
||||
@@ -62,8 +61,7 @@ export async function updateUserAvatarAction(
|
||||
await cleanupOldAvatarFile(oldImageUrl)
|
||||
}
|
||||
|
||||
revalidatePath("/profile")
|
||||
revalidatePath("/settings")
|
||||
await invalidateFor("settings.avatar.update", { id: ctx.userId })
|
||||
|
||||
return { success: true, data: { image: updated.image ?? imageUrl } }
|
||||
} catch (error) {
|
||||
@@ -94,8 +92,7 @@ export async function removeUserAvatarAction(): Promise<ActionState<null>> {
|
||||
await cleanupOldAvatarFile(oldImageUrl)
|
||||
}
|
||||
|
||||
revalidatePath("/profile")
|
||||
revalidatePath("/settings")
|
||||
await invalidateFor("settings.avatar.remove", { id: ctx.userId })
|
||||
|
||||
return { success: true, data: null }
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
"use server"
|
||||
|
||||
import { z } from "zod"
|
||||
import { revalidatePath } from "next/cache"
|
||||
|
||||
import type { ActionState } from "@/shared/types/action-state"
|
||||
import { requirePermission } from "@/shared/lib/auth-guard"
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
import { getSession } from "@/shared/lib/session"
|
||||
import { invalidateFor } from "@/shared/lib/cache"
|
||||
|
||||
import { getBrandConfig, saveBrandConfig } from "./data-access-brand"
|
||||
import type { BrandConfig } from "./brand-config"
|
||||
@@ -67,9 +67,7 @@ export async function saveBrandConfigAction(
|
||||
}
|
||||
|
||||
await saveBrandConfig(config, session?.user?.id)
|
||||
revalidatePath("/admin/settings")
|
||||
revalidatePath("/(auth)/login")
|
||||
revalidatePath("/(auth)/register")
|
||||
await invalidateFor("settings.brand.save")
|
||||
|
||||
return { success: true, data: config, message: "Brand configuration saved" }
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use server"
|
||||
|
||||
import { revalidatePath } from "next/cache"
|
||||
import { compare, hash } from "bcryptjs"
|
||||
import { z } from "zod"
|
||||
|
||||
@@ -11,6 +10,7 @@ import { validatePassword } from "@/shared/lib/password-policy"
|
||||
import { rateLimit, rateLimitKey, RATE_LIMIT_RULES } from "@/shared/lib/rate-limit"
|
||||
import { normalizeBcryptHash } from "@/shared/lib/bcrypt-utils"
|
||||
import { checkBreachedPassword } from "@/shared/lib/breached-password"
|
||||
import { invalidateFor } from "@/shared/lib/cache"
|
||||
|
||||
import {
|
||||
getPasswordSecurityByUserId,
|
||||
@@ -103,7 +103,7 @@ export async function changePasswordAction(
|
||||
await updateUserPassword(userId, newHash, now)
|
||||
await upsertPasswordSecurityOnPasswordChange(userId, now, existingSecurity)
|
||||
|
||||
revalidatePath("/settings")
|
||||
await invalidateFor("settings.password.change")
|
||||
return { success: true, message: "Password changed successfully", data: null }
|
||||
} catch (error) {
|
||||
if (error instanceof PermissionDeniedError) return { success: false, message: error.message }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"use server"
|
||||
|
||||
import { revalidatePath } from "next/cache"
|
||||
import { desc, eq } from "drizzle-orm"
|
||||
|
||||
import type { ActionState } from "@/shared/types/action-state"
|
||||
@@ -11,6 +10,7 @@ import { loginLogs } from "@/shared/db/schema"
|
||||
import { logLoginEvent } from "@/shared/lib/login-logger"
|
||||
import { trackAuthEvent } from "@/shared/lib/track-event"
|
||||
import { getUserProfile } from "@/modules/users/data-access"
|
||||
import { invalidateFor } from "@/shared/lib/cache"
|
||||
|
||||
import {
|
||||
getSystemSettingsByCategory,
|
||||
@@ -236,7 +236,7 @@ export async function verifyTwoFactorAction(
|
||||
await setTwoFactorEnabled(ctx.userId, true)
|
||||
await setTwoFactorEnabledAt(ctx.userId, now)
|
||||
|
||||
revalidatePath("/settings")
|
||||
await invalidateFor("settings.security.verify2FA", { id: ctx.userId })
|
||||
|
||||
const status = await getTwoFactorStatus(ctx.userId)
|
||||
|
||||
@@ -301,7 +301,7 @@ export async function disableTwoFactorAction(
|
||||
await deleteTotpSecret(ctx.userId)
|
||||
await deleteBackupCodes(ctx.userId)
|
||||
|
||||
revalidatePath("/settings")
|
||||
await invalidateFor("settings.security.disable2FA", { id: ctx.userId })
|
||||
|
||||
const status = await getTwoFactorStatus(ctx.userId)
|
||||
|
||||
@@ -348,7 +348,7 @@ export async function regenerateBackupCodesAction(
|
||||
const hashedJson = await hashBackupCodes(backupCodes)
|
||||
await setBackupCodesHashed(ctx.userId, hashedJson)
|
||||
|
||||
revalidatePath("/settings")
|
||||
await invalidateFor("settings.security.regenerateBackupCodes", { id: ctx.userId })
|
||||
|
||||
const status = await getTwoFactorStatus(ctx.userId)
|
||||
return {
|
||||
@@ -397,7 +397,7 @@ export async function revokeAllOtherSessionsAction(): Promise<
|
||||
"Remote logout requested (JWT-based, no active DB sessions to revoke)",
|
||||
})
|
||||
|
||||
revalidatePath("/settings")
|
||||
await invalidateFor("settings.security.revokeSessions", { id: ctx.userId })
|
||||
|
||||
return { success: true, data: { revokedCount } }
|
||||
} catch (error) {
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
* 的函数必须是 "use server" 标记的 Server Action)。
|
||||
*/
|
||||
|
||||
import { revalidatePath } from "next/cache"
|
||||
|
||||
import type { ActionState } from "@/shared/types/action-state"
|
||||
import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard"
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
@@ -23,6 +21,7 @@ import type {
|
||||
NotificationPreferences,
|
||||
UpdateNotificationPreferencesInput,
|
||||
} from "@/modules/notifications/types"
|
||||
import { invalidateFor } from "@/shared/lib/cache"
|
||||
|
||||
/**
|
||||
* 更新用户资料(Server Action wrapper)
|
||||
@@ -52,7 +51,7 @@ export async function updateNotificationPreferencesAction(
|
||||
return { success: false, message: "Failed to update notification preferences" }
|
||||
}
|
||||
|
||||
revalidatePath("/settings")
|
||||
await invalidateFor("settings.notifications.updatePreferences")
|
||||
|
||||
return { success: true, message: "Notification preferences updated", data: updated }
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"use server"
|
||||
|
||||
import { z } from "zod"
|
||||
import { revalidatePath } from "next/cache"
|
||||
|
||||
import type { ActionState } from "@/shared/types/action-state"
|
||||
import { requirePermission } from "@/shared/lib/auth-guard"
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
import { invalidateFor } from "@/shared/lib/cache"
|
||||
|
||||
import {
|
||||
getAllSystemSettings,
|
||||
@@ -157,7 +157,7 @@ export async function saveAdminSystemSettingsAction(
|
||||
|
||||
await upsertSystemSettings(items, ctx.userId)
|
||||
|
||||
revalidatePath("/admin/settings")
|
||||
await invalidateFor("settings.systemSettings.save")
|
||||
|
||||
return { success: true, data: null }
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use server"
|
||||
|
||||
import { z } from "zod"
|
||||
import { revalidatePath } from "next/cache"
|
||||
import { createId } from "@paralleldrive/cuid2"
|
||||
|
||||
import type { ActionState } from "@/shared/types/action-state"
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
} from "@/shared/lib/auth-guard"
|
||||
import { Permissions } from "@/shared/types/permissions"
|
||||
import { encryptAiApiKey, getAiErrorMessage, testAiProviderById, testAiProviderConfig } from "@/shared/lib/ai"
|
||||
import { invalidateFor } from "@/shared/lib/cache"
|
||||
|
||||
import {
|
||||
countDefaultAiProviders,
|
||||
@@ -181,8 +181,7 @@ export async function upsertAiProviderAction(
|
||||
payload.isDefault === true
|
||||
)
|
||||
|
||||
revalidatePath("/admin/ai-settings")
|
||||
revalidatePath("/settings")
|
||||
await invalidateFor("settings.aiProvider.upsert", { id })
|
||||
return { success: true, message: "AI provider updated", data: id }
|
||||
}
|
||||
|
||||
@@ -214,8 +213,7 @@ export async function upsertAiProviderAction(
|
||||
shouldMakeDefault
|
||||
)
|
||||
|
||||
revalidatePath("/admin/ai-settings")
|
||||
revalidatePath("/settings")
|
||||
await invalidateFor("settings.aiProvider.upsert", { id })
|
||||
return { success: true, message: "AI provider created", data: id }
|
||||
} catch (error) {
|
||||
if (error instanceof PermissionDeniedError) return { success: false, message: error.message }
|
||||
@@ -281,8 +279,7 @@ export async function deleteAiProviderAction(
|
||||
parsed.data.id,
|
||||
user.isAdmin ? undefined : user.id
|
||||
)
|
||||
revalidatePath("/admin/ai-settings")
|
||||
revalidatePath("/settings")
|
||||
await invalidateFor("settings.aiProvider.delete", { id: parsed.data.id })
|
||||
return { success: true, message: "AI provider deleted", data: null }
|
||||
} catch (error) {
|
||||
if (error instanceof PermissionDeniedError) return { success: false, message: error.message }
|
||||
|
||||
@@ -152,6 +152,13 @@ export const CLIENT_INVALIDATION_MAP = {
|
||||
"lesson-preparation.substitute.create": { queryKeys: [["lesson-preparation", "substitutes"]] },
|
||||
"lesson-preparation.substitute.updateStatus": { queryKeys: [["lesson-preparation", "substitutes"]] },
|
||||
"lesson-preparation.substitute.delete": { queryKeys: [["lesson-preparation", "substitutes"]] },
|
||||
"lesson-preparation.attachment.create": { queryKeys: [["lesson-preparation", "detail"]] },
|
||||
"lesson-preparation.review.submit": { queryKeys: [["lesson-preparation", "detail"]] },
|
||||
"lesson-preparation.review.review": { queryKeys: [["lesson-preparation", "detail"]] },
|
||||
"lesson-preparation.review.withdraw": { queryKeys: [["lesson-preparation", "detail"]] },
|
||||
"lesson-preparation.schedule.create": { queryKeys: [["lesson-preparation", "schedules"]] },
|
||||
"lesson-preparation.schedule.delete": { queryKeys: [["lesson-preparation", "schedules"]] },
|
||||
"lesson-preparation.publishHomework": { queryKeys: [["lesson-preparation", "list"], ["homework", "list"]] },
|
||||
|
||||
// ===== messaging 模块 =====
|
||||
"messaging.send": { queryKeys: [["messaging", "list"]] },
|
||||
|
||||
35
src/shared/lib/cache/invalidation-map.ts
vendored
35
src/shared/lib/cache/invalidation-map.ts
vendored
@@ -685,6 +685,41 @@ export const INVALIDATION_MAP = {
|
||||
queryKeys: [["lesson-preparation", "substitutes"]],
|
||||
paths: ["/teacher/lesson-plans"],
|
||||
},
|
||||
"lesson-preparation.attachment.create": {
|
||||
tags: ["lesson-preparation"],
|
||||
queryKeys: [["lesson-preparation", "detail"]],
|
||||
paths: ["/teacher/lesson-plans"],
|
||||
},
|
||||
"lesson-preparation.review.submit": {
|
||||
tags: ["lesson-preparation"],
|
||||
queryKeys: [["lesson-preparation", "detail"]],
|
||||
paths: ["/teacher/lesson-plans"],
|
||||
},
|
||||
"lesson-preparation.review.review": {
|
||||
tags: ["lesson-preparation"],
|
||||
queryKeys: [["lesson-preparation", "detail"]],
|
||||
paths: ["/teacher/lesson-plans"],
|
||||
},
|
||||
"lesson-preparation.review.withdraw": {
|
||||
tags: ["lesson-preparation"],
|
||||
queryKeys: [["lesson-preparation", "detail"]],
|
||||
paths: ["/teacher/lesson-plans"],
|
||||
},
|
||||
"lesson-preparation.schedule.create": {
|
||||
tags: ["lesson-preparation"],
|
||||
queryKeys: [["lesson-preparation", "schedules"]],
|
||||
paths: ["/teacher/lesson-plans", "/teacher/lesson-plans/calendar"],
|
||||
},
|
||||
"lesson-preparation.schedule.delete": {
|
||||
tags: ["lesson-preparation"],
|
||||
queryKeys: [["lesson-preparation", "schedules"]],
|
||||
paths: ["/teacher/lesson-plans", "/teacher/lesson-plans/calendar"],
|
||||
},
|
||||
"lesson-preparation.publishHomework": {
|
||||
tags: ["lesson-preparation", "homework"],
|
||||
queryKeys: [["lesson-preparation", "list"], ["homework", "list"]],
|
||||
paths: ["/teacher/lesson-plans", "/teacher/homework"],
|
||||
},
|
||||
|
||||
// ===== messaging 模块 =====
|
||||
"messaging.send": {
|
||||
|
||||
Reference in New Issue
Block a user