143 lines
3.9 KiB
TypeScript
143 lines
3.9 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { z } from "zod";
|
|
import {
|
|
createPeopleMutationInvocation,
|
|
peopleMutationService,
|
|
} from "@/features/housekeeping/domains/people/services/mutations";
|
|
import { createCorrelationId } from "@/features/housekeeping/foundation/contracts";
|
|
import { PERMS } from "@/lib/permissions";
|
|
import { adminAction } from "@/lib/safe-action";
|
|
import { ActionError, actionOk } from "@/lib/safe-action-shared";
|
|
import { canonicalTicketId } from "@/lib/services/ticket-replies";
|
|
|
|
const ticketIdField = z
|
|
.union([z.string(), z.number(), z.bigint()])
|
|
.transform((value, context) => {
|
|
try {
|
|
return canonicalTicketId(value);
|
|
} catch {
|
|
context.addIssue({
|
|
code: "custom",
|
|
message: "Invalid ticket identifier",
|
|
});
|
|
return z.NEVER;
|
|
}
|
|
});
|
|
|
|
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("/ase/people/support/help-tickets");
|
|
revalidatePath(`/ase/people/support/help-tickets/${id}`);
|
|
revalidatePath("/help/tickets");
|
|
revalidatePath(`/help/tickets/${id}`);
|
|
}
|
|
|
|
async function execute(
|
|
staff: { readonly id: number },
|
|
operation: "help-ticket.reply" | "help-ticket.status" | "help-ticket.unban",
|
|
input: unknown,
|
|
) {
|
|
return peopleMutationService.execute(
|
|
createPeopleMutationInvocation(staff, createCorrelationId()),
|
|
operation,
|
|
input,
|
|
);
|
|
}
|
|
|
|
export const liftBanFromHelpTicket = adminAction(
|
|
{
|
|
permission: PERMS.USERS_BAN,
|
|
schema: helpCenterTicketIdSchema,
|
|
},
|
|
async (ctx) => {
|
|
const ticketId = ctx.data.ticketId;
|
|
const result = await execute(ctx.session.user, "help-ticket.unban", {
|
|
ticketId: ticketId.toString(),
|
|
});
|
|
if (!result.ok) {
|
|
throw new ActionError(
|
|
result.error.code === "CONFLICT"
|
|
? "Ticket has no requester to unban"
|
|
: "Ticket not found",
|
|
);
|
|
}
|
|
|
|
revalidateHelpCenterTicketPaths(ticketId);
|
|
revalidatePath("/ase/people/moderation/bans");
|
|
const removed = Number(result.data.output?.removed ?? 0);
|
|
const userId = Number(result.data.output?.userId);
|
|
revalidatePath(`/ase/people/users/${userId}`);
|
|
return actionOk({ removed, 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 result = await execute(ctx.session.user, "help-ticket.reply", {
|
|
ticketId: ticketId.toString(),
|
|
content: ctx.data.content.trim(),
|
|
});
|
|
if (!result.ok) throw new ActionError("Ticket not found");
|
|
|
|
revalidateHelpCenterTicketPaths(ticketId);
|
|
return actionOk();
|
|
},
|
|
);
|
|
|
|
export const closeHelpCenterTicket = adminAction(
|
|
{ permission: HELP_TICKET_EDIT, schema: helpCenterTicketIdSchema },
|
|
async (ctx) => {
|
|
const ticketId = ctx.data.ticketId;
|
|
const result = await execute(ctx.session.user, "help-ticket.status", {
|
|
ticketId: ticketId.toString(),
|
|
status: "close",
|
|
});
|
|
if (!result.ok) {
|
|
throw new ActionError(
|
|
result.error.code === "CONFLICT"
|
|
? "Ticket is already closed"
|
|
: "Ticket not found",
|
|
);
|
|
}
|
|
|
|
revalidateHelpCenterTicketPaths(ticketId);
|
|
return actionOk();
|
|
},
|
|
);
|
|
|
|
export const reopenHelpCenterTicket = adminAction(
|
|
{ permission: HELP_TICKET_EDIT, schema: helpCenterTicketIdSchema },
|
|
async (ctx) => {
|
|
const ticketId = ctx.data.ticketId;
|
|
const result = await execute(ctx.session.user, "help-ticket.status", {
|
|
ticketId: ticketId.toString(),
|
|
status: "reopen",
|
|
});
|
|
if (!result.ok) {
|
|
throw new ActionError(
|
|
result.error.code === "CONFLICT"
|
|
? "Ticket is already open"
|
|
: "Ticket not found",
|
|
);
|
|
}
|
|
|
|
revalidateHelpCenterTicketPaths(ticketId);
|
|
return actionOk();
|
|
},
|
|
);
|