feat(housekeeping): complete people moderation parity
This commit is contained in:
1 parent
3d385d1869
commit
29fe22297b
41 files changed
+3465
-580
No files matched your search
@@ -1,18 +1,15 @@
|
||||
"use server";
|
||||
|
||||
import { eq } from "drizzle-orm";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
Ban,
|
||||
db,
|
||||
WebsiteHelpCenterTicketReplies,
|
||||
WebsiteHelpCenterTickets,
|
||||
} from "@/lib/db";
|
||||
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 { logAudit } from "@/lib/services/audit";
|
||||
|
||||
const ticketIdField = z
|
||||
.union([z.string(), z.number(), z.bigint()])
|
||||
@@ -37,6 +34,21 @@ function revalidateHelpCenterTicketPaths(ticketId: bigint) {
|
||||
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,
|
||||
@@ -44,50 +56,23 @@ export const liftBanFromHelpTicket = adminAction(
|
||||
},
|
||||
async (ctx) => {
|
||||
const ticketId = ctx.data.ticketId;
|
||||
const [ticket] = await db
|
||||
.select({
|
||||
id: WebsiteHelpCenterTickets.id,
|
||||
userId: WebsiteHelpCenterTickets.userId,
|
||||
open: WebsiteHelpCenterTickets.open,
|
||||
title: WebsiteHelpCenterTickets.title,
|
||||
})
|
||||
.from(WebsiteHelpCenterTickets)
|
||||
.where(eq(WebsiteHelpCenterTickets.id, ticketId))
|
||||
.limit(1);
|
||||
if (!ticket) throw new ActionError("Ticket not found");
|
||||
if (ticket.userId == null) {
|
||||
throw new ActionError("Ticket has no requester to unban");
|
||||
}
|
||||
|
||||
const result = await db.delete(Ban).where(eq(Ban.userId, ticket.userId));
|
||||
const removed = Number(
|
||||
(result as unknown as [{ affectedRows: number }])[0]?.affectedRows ?? 0,
|
||||
);
|
||||
|
||||
const now = new Date();
|
||||
if (ticket.open) {
|
||||
await db
|
||||
.update(WebsiteHelpCenterTickets)
|
||||
.set({ open: false, updatedAt: now })
|
||||
.where(eq(WebsiteHelpCenterTickets.id, ticketId));
|
||||
}
|
||||
|
||||
logAudit({
|
||||
userId: ctx.session.user.id,
|
||||
action: "unban_via_help_ticket",
|
||||
target: "User",
|
||||
targetId: ticket.userId,
|
||||
after: {
|
||||
ticketId: String(ticketId),
|
||||
removedBans: removed,
|
||||
title: ticket.title,
|
||||
},
|
||||
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("/admin/bans");
|
||||
revalidatePath(`/admin/users/show/${ticket.userId}`);
|
||||
return actionOk({ removed, userId: ticket.userId });
|
||||
const removed = Number(result.data.output?.removed ?? 0);
|
||||
const userId = Number(result.data.output?.userId);
|
||||
revalidatePath(`/admin/users/show/${userId}`);
|
||||
return actionOk({ removed, userId });
|
||||
},
|
||||
);
|
||||
|
||||
@@ -97,40 +82,11 @@ export const replyHelpCenterTicket = adminAction(
|
||||
{ permission: HELP_TICKET_EDIT, schema: replyHelpCenterTicketSchema },
|
||||
async (ctx) => {
|
||||
const ticketId = ctx.data.ticketId;
|
||||
const [ticket] = await db
|
||||
.select({
|
||||
id: WebsiteHelpCenterTickets.id,
|
||||
open: WebsiteHelpCenterTickets.open,
|
||||
})
|
||||
.from(WebsiteHelpCenterTickets)
|
||||
.where(eq(WebsiteHelpCenterTickets.id, ticketId))
|
||||
.limit(1);
|
||||
|
||||
if (!ticket) throw new ActionError("Ticket not found");
|
||||
|
||||
const now = new Date();
|
||||
const staffId = Number(ctx.session.user.id);
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
await tx.insert(WebsiteHelpCenterTicketReplies).values({
|
||||
ticketId,
|
||||
userId: staffId,
|
||||
content: ctx.data.content.trim(),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
await tx
|
||||
.update(WebsiteHelpCenterTickets)
|
||||
.set({ updatedAt: now })
|
||||
.where(eq(WebsiteHelpCenterTickets.id, ticketId));
|
||||
});
|
||||
|
||||
logAudit({
|
||||
userId: staffId,
|
||||
action: "help_center_ticket_reply",
|
||||
target: "WebsiteHelpCenterTickets",
|
||||
targetId: Number(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();
|
||||
@@ -141,32 +97,17 @@ export const closeHelpCenterTicket = adminAction(
|
||||
{ permission: HELP_TICKET_EDIT, schema: helpCenterTicketIdSchema },
|
||||
async (ctx) => {
|
||||
const ticketId = ctx.data.ticketId;
|
||||
const [ticket] = await db
|
||||
.select({
|
||||
id: WebsiteHelpCenterTickets.id,
|
||||
open: WebsiteHelpCenterTickets.open,
|
||||
})
|
||||
.from(WebsiteHelpCenterTickets)
|
||||
.where(eq(WebsiteHelpCenterTickets.id, ticketId))
|
||||
.limit(1);
|
||||
|
||||
if (!ticket) throw new ActionError("Ticket not found");
|
||||
if (!ticket.open) throw new ActionError("Ticket is already closed");
|
||||
|
||||
const now = new Date();
|
||||
await db
|
||||
.update(WebsiteHelpCenterTickets)
|
||||
.set({ open: false, updatedAt: now })
|
||||
.where(eq(WebsiteHelpCenterTickets.id, ticketId));
|
||||
|
||||
logAudit({
|
||||
userId: Number(ctx.session.user.id),
|
||||
action: "help_center_ticket_close",
|
||||
target: "WebsiteHelpCenterTickets",
|
||||
targetId: Number(ticketId),
|
||||
before: { open: true },
|
||||
after: { open: false },
|
||||
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();
|
||||
@@ -177,32 +118,17 @@ export const reopenHelpCenterTicket = adminAction(
|
||||
{ permission: HELP_TICKET_EDIT, schema: helpCenterTicketIdSchema },
|
||||
async (ctx) => {
|
||||
const ticketId = ctx.data.ticketId;
|
||||
const [ticket] = await db
|
||||
.select({
|
||||
id: WebsiteHelpCenterTickets.id,
|
||||
open: WebsiteHelpCenterTickets.open,
|
||||
})
|
||||
.from(WebsiteHelpCenterTickets)
|
||||
.where(eq(WebsiteHelpCenterTickets.id, ticketId))
|
||||
.limit(1);
|
||||
|
||||
if (!ticket) throw new ActionError("Ticket not found");
|
||||
if (ticket.open) throw new ActionError("Ticket is already open");
|
||||
|
||||
const now = new Date();
|
||||
await db
|
||||
.update(WebsiteHelpCenterTickets)
|
||||
.set({ open: true, updatedAt: now })
|
||||
.where(eq(WebsiteHelpCenterTickets.id, ticketId));
|
||||
|
||||
logAudit({
|
||||
userId: Number(ctx.session.user.id),
|
||||
action: "help_center_ticket_reopen",
|
||||
target: "WebsiteHelpCenterTickets",
|
||||
targetId: Number(ticketId),
|
||||
before: { open: false },
|
||||
after: { open: true },
|
||||
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();
|
||||
|
||||
Reference in new issue
Block a user