188 lines
5.0 KiB
TypeScript
188 lines
5.0 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { z } from "zod";
|
|
import { PERMS } from "@/lib/permissions";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { adminAction } from "@/lib/safe-action";
|
|
import { ActionError, actionOk } from "@/lib/safe-action-shared";
|
|
import { logAudit } from "@/lib/services/audit";
|
|
|
|
const ticketIdField = z
|
|
.union([z.string(), z.number(), z.bigint()])
|
|
.transform((v) => BigInt(String(v)));
|
|
|
|
const replyHelpCenterTicketSchema = z.object({
|
|
ticketId: ticketIdField,
|
|
content: z.string().min(1).max(5000),
|
|
});
|
|
|
|
const helpCenterTicketIdSchema = z.object({
|
|
ticketId: ticketIdField,
|
|
});
|
|
|
|
function revalidateHelpCenterTicketPaths(ticketId: bigint) {
|
|
const id = String(ticketId);
|
|
revalidatePath("/admin/help-tickets");
|
|
revalidatePath(`/admin/help-tickets/${id}`);
|
|
revalidatePath("/mod/help-tickets");
|
|
revalidatePath(`/mod/help-tickets/${id}`);
|
|
revalidatePath("/help/tickets");
|
|
revalidatePath(`/help/tickets/${id}`);
|
|
}
|
|
|
|
export const liftBanFromHelpTicket = adminAction(
|
|
{
|
|
permission: PERMS.USERS_BAN,
|
|
schema: helpCenterTicketIdSchema,
|
|
},
|
|
async (ctx) => {
|
|
const ticketId = ctx.data.ticketId;
|
|
const ticket = await prisma.websiteHelpCenterTickets.findUnique({
|
|
where: { id: ticketId },
|
|
select: { id: true, userId: true, open: true, title: true },
|
|
});
|
|
if (!ticket) throw new ActionError("Ticket not found");
|
|
if (ticket.userId == null) {
|
|
throw new ActionError("Ticket has no requester to unban");
|
|
}
|
|
|
|
const removed = await prisma.ban.deleteMany({
|
|
where: { userId: ticket.userId },
|
|
});
|
|
|
|
const now = new Date();
|
|
if (ticket.open) {
|
|
await prisma.websiteHelpCenterTickets.update({
|
|
where: { id: ticketId },
|
|
data: { open: false, updatedAt: now },
|
|
});
|
|
}
|
|
|
|
logAudit({
|
|
userId: ctx.session.user.id,
|
|
action: "unban_via_help_ticket",
|
|
target: "User",
|
|
targetId: ticket.userId,
|
|
after: {
|
|
ticketId: String(ticketId),
|
|
removedBans: removed.count,
|
|
title: ticket.title,
|
|
},
|
|
});
|
|
|
|
revalidateHelpCenterTicketPaths(ticketId);
|
|
revalidatePath("/admin/bans");
|
|
revalidatePath(`/admin/users/show/${ticket.userId}`);
|
|
return actionOk({ removed: removed.count, userId: ticket.userId });
|
|
},
|
|
);
|
|
|
|
const HELP_TICKET_EDIT = [PERMS.TICKETS_EDIT, PERMS.MOD_TICKETS_EDIT] as const;
|
|
|
|
export const replyHelpCenterTicket = adminAction(
|
|
{ permission: HELP_TICKET_EDIT, schema: replyHelpCenterTicketSchema },
|
|
async (ctx) => {
|
|
const ticketId = ctx.data.ticketId;
|
|
const ticket = await prisma.websiteHelpCenterTickets.findUnique({
|
|
where: { id: ticketId },
|
|
select: { id: true, open: true },
|
|
});
|
|
|
|
if (!ticket) throw new ActionError("Ticket not found");
|
|
|
|
const now = new Date();
|
|
const staffId = Number(ctx.session.user.id);
|
|
|
|
await prisma.$transaction([
|
|
prisma.websiteHelpCenterTicketReplies.create({
|
|
data: {
|
|
ticketId,
|
|
userId: staffId,
|
|
content: ctx.data.content.trim(),
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
},
|
|
}),
|
|
prisma.websiteHelpCenterTickets.update({
|
|
where: { id: ticketId },
|
|
data: { updatedAt: now },
|
|
}),
|
|
]);
|
|
|
|
logAudit({
|
|
userId: staffId,
|
|
action: "help_center_ticket_reply",
|
|
target: "WebsiteHelpCenterTickets",
|
|
targetId: Number(ticketId),
|
|
});
|
|
|
|
revalidateHelpCenterTicketPaths(ticketId);
|
|
return actionOk();
|
|
},
|
|
);
|
|
|
|
export const closeHelpCenterTicket = adminAction(
|
|
{ permission: HELP_TICKET_EDIT, schema: helpCenterTicketIdSchema },
|
|
async (ctx) => {
|
|
const ticketId = ctx.data.ticketId;
|
|
const ticket = await prisma.websiteHelpCenterTickets.findUnique({
|
|
where: { id: ticketId },
|
|
select: { id: true, open: true },
|
|
});
|
|
|
|
if (!ticket) throw new ActionError("Ticket not found");
|
|
if (!ticket.open) throw new ActionError("Ticket is already closed");
|
|
|
|
const now = new Date();
|
|
await prisma.websiteHelpCenterTickets.update({
|
|
where: { id: ticketId },
|
|
data: { open: false, updatedAt: now },
|
|
});
|
|
|
|
logAudit({
|
|
userId: Number(ctx.session.user.id),
|
|
action: "help_center_ticket_close",
|
|
target: "WebsiteHelpCenterTickets",
|
|
targetId: Number(ticketId),
|
|
before: { open: true },
|
|
after: { open: false },
|
|
});
|
|
|
|
revalidateHelpCenterTicketPaths(ticketId);
|
|
return actionOk();
|
|
},
|
|
);
|
|
|
|
export const reopenHelpCenterTicket = adminAction(
|
|
{ permission: HELP_TICKET_EDIT, schema: helpCenterTicketIdSchema },
|
|
async (ctx) => {
|
|
const ticketId = ctx.data.ticketId;
|
|
const ticket = await prisma.websiteHelpCenterTickets.findUnique({
|
|
where: { id: ticketId },
|
|
select: { id: true, open: true },
|
|
});
|
|
|
|
if (!ticket) throw new ActionError("Ticket not found");
|
|
if (ticket.open) throw new ActionError("Ticket is already open");
|
|
|
|
const now = new Date();
|
|
await prisma.websiteHelpCenterTickets.update({
|
|
where: { id: ticketId },
|
|
data: { open: true, updatedAt: now },
|
|
});
|
|
|
|
logAudit({
|
|
userId: Number(ctx.session.user.id),
|
|
action: "help_center_ticket_reopen",
|
|
target: "WebsiteHelpCenterTickets",
|
|
targetId: Number(ticketId),
|
|
before: { open: false },
|
|
after: { open: true },
|
|
});
|
|
|
|
revalidateHelpCenterTicketPaths(ticketId);
|
|
return actionOk();
|
|
},
|
|
);
|