mirror of
https://github.com/JetSprow/J-Board-Lite.git
synced 2026-05-03 09:54:11 +05:30
Initial commit
This commit is contained in:
189
src/actions/admin/announcements.ts
Normal file
189
src/actions/admin/announcements.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { requireAdmin } from "@/lib/require-auth";
|
||||
import { actorFromSession, recordAuditLog } from "@/services/audit";
|
||||
import {
|
||||
deleteAnnouncementNotifications,
|
||||
dispatchAnnouncementNotifications,
|
||||
syncAnnouncementNotifications,
|
||||
} from "@/services/announcements";
|
||||
|
||||
const announcementSchema = z.object({
|
||||
title: z.string().trim().min(1, "标题不能为空"),
|
||||
body: z.string().trim().min(1, "内容不能为空"),
|
||||
audience: z.enum(["PUBLIC", "USERS", "ADMINS", "SPECIFIC_USER"]),
|
||||
displayType: z.enum(["INLINE", "BIG", "POPUP"]).default("INLINE"),
|
||||
targetUserId: z.string().optional(),
|
||||
dismissible: z.string().optional(),
|
||||
sendNotification: z.string().optional(),
|
||||
startAt: z.string().optional(),
|
||||
endAt: z.string().optional(),
|
||||
});
|
||||
|
||||
function revalidateAnnouncementViews() {
|
||||
revalidatePath("/admin/announcements");
|
||||
revalidatePath("/dashboard");
|
||||
revalidatePath("/account");
|
||||
revalidatePath("/notifications");
|
||||
revalidatePath("/login");
|
||||
}
|
||||
|
||||
function parseAnnouncementInput(formData: FormData) {
|
||||
const data = announcementSchema.parse(Object.fromEntries(formData));
|
||||
|
||||
if (data.audience === "SPECIFIC_USER" && !data.targetUserId) {
|
||||
throw new Error("定向消息必须选择用户");
|
||||
}
|
||||
|
||||
const startAt = data.startAt ? new Date(data.startAt) : null;
|
||||
const endAt = data.endAt ? new Date(data.endAt) : null;
|
||||
if (startAt && Number.isNaN(startAt.getTime())) {
|
||||
throw new Error("开始时间格式无效");
|
||||
}
|
||||
if (endAt && Number.isNaN(endAt.getTime())) {
|
||||
throw new Error("结束时间格式无效");
|
||||
}
|
||||
if (startAt && endAt && endAt <= startAt) {
|
||||
throw new Error("结束时间必须晚于开始时间");
|
||||
}
|
||||
|
||||
return {
|
||||
title: data.title,
|
||||
body: data.body,
|
||||
audience: data.audience,
|
||||
displayType: data.displayType,
|
||||
targetUserId: data.audience === "SPECIFIC_USER" ? data.targetUserId ?? null : null,
|
||||
dismissible: data.dismissible === "true",
|
||||
sendNotification: data.sendNotification === "true",
|
||||
startAt,
|
||||
endAt,
|
||||
};
|
||||
}
|
||||
|
||||
export async function createAnnouncement(formData: FormData) {
|
||||
const session = await requireAdmin();
|
||||
const data = parseAnnouncementInput(formData);
|
||||
|
||||
const announcement = await prisma.announcement.create({
|
||||
data: {
|
||||
title: data.title,
|
||||
body: data.body,
|
||||
audience: data.audience,
|
||||
displayType: data.displayType,
|
||||
targetUserId: data.targetUserId,
|
||||
createdById: session.user.id,
|
||||
isActive: true,
|
||||
dismissible: data.dismissible,
|
||||
sendNotification: data.sendNotification,
|
||||
startAt: data.startAt,
|
||||
endAt: data.endAt,
|
||||
},
|
||||
});
|
||||
|
||||
await dispatchAnnouncementNotifications(announcement.id);
|
||||
await recordAuditLog({
|
||||
actor: actorFromSession(session),
|
||||
action: "announcement.create",
|
||||
targetType: "Announcement",
|
||||
targetId: announcement.id,
|
||||
targetLabel: announcement.title,
|
||||
message: `创建公告/消息 ${announcement.title}`,
|
||||
});
|
||||
|
||||
revalidateAnnouncementViews();
|
||||
}
|
||||
|
||||
export async function updateAnnouncement(id: string, formData: FormData) {
|
||||
const session = await requireAdmin();
|
||||
const data = parseAnnouncementInput(formData);
|
||||
|
||||
const announcement = await prisma.announcement.update({
|
||||
where: { id },
|
||||
data: {
|
||||
title: data.title,
|
||||
body: data.body,
|
||||
audience: data.audience,
|
||||
displayType: data.displayType,
|
||||
targetUserId: data.targetUserId,
|
||||
dismissible: data.dismissible,
|
||||
sendNotification: data.sendNotification,
|
||||
startAt: data.startAt,
|
||||
endAt: data.endAt,
|
||||
},
|
||||
});
|
||||
|
||||
await syncAnnouncementNotifications(announcement.id);
|
||||
await recordAuditLog({
|
||||
actor: actorFromSession(session),
|
||||
action: "announcement.update",
|
||||
targetType: "Announcement",
|
||||
targetId: announcement.id,
|
||||
targetLabel: announcement.title,
|
||||
message: `更新公告/消息 ${announcement.title}`,
|
||||
});
|
||||
|
||||
revalidateAnnouncementViews();
|
||||
}
|
||||
|
||||
export async function toggleAnnouncement(id: string, isActive: boolean) {
|
||||
const session = await requireAdmin();
|
||||
const announcement = await prisma.announcement.update({
|
||||
where: { id },
|
||||
data: { isActive },
|
||||
});
|
||||
|
||||
await recordAuditLog({
|
||||
actor: actorFromSession(session),
|
||||
action: isActive ? "announcement.enable" : "announcement.disable",
|
||||
targetType: "Announcement",
|
||||
targetId: announcement.id,
|
||||
targetLabel: announcement.title,
|
||||
message: `${isActive ? "启用" : "停用"}公告/消息 ${announcement.title}`,
|
||||
});
|
||||
|
||||
if (isActive) {
|
||||
await dispatchAnnouncementNotifications(announcement.id);
|
||||
}
|
||||
|
||||
revalidateAnnouncementViews();
|
||||
}
|
||||
|
||||
export async function deleteAnnouncement(id: string) {
|
||||
const session = await requireAdmin();
|
||||
const announcement = await prisma.announcement.findUnique({
|
||||
where: { id },
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!announcement) {
|
||||
throw new Error("公告不存在");
|
||||
}
|
||||
|
||||
await prisma.$transaction(async (tx) => {
|
||||
await deleteAnnouncementNotifications(announcement.id, tx);
|
||||
await tx.announcement.delete({
|
||||
where: {
|
||||
id: announcement.id,
|
||||
},
|
||||
});
|
||||
await recordAuditLog(
|
||||
{
|
||||
actor: actorFromSession(session),
|
||||
action: "announcement.delete",
|
||||
targetType: "Announcement",
|
||||
targetId: announcement.id,
|
||||
targetLabel: announcement.title,
|
||||
message: `删除公告/消息 ${announcement.title}`,
|
||||
},
|
||||
tx,
|
||||
);
|
||||
});
|
||||
|
||||
revalidateAnnouncementViews();
|
||||
}
|
||||
Reference in New Issue
Block a user