Files
EpicNext-Cms/src/app/api/tickets/[id]/reply/route.ts
T

66 lines
2.2 KiB
TypeScript

// Public REST API — post a reply to a help-center ticket.
//
// Bearer-authed. POST inserts a reply ({ content }) authored by the current user
// into website_help_center_ticket_replies. The target ticket must exist and
// belong to the authed user. Fail-soft: never a 500.
import { apiError, apiJson, positiveBigInt } from "@/lib/api";
import { bearerUserId } from "@/lib/api-auth";
import { prisma } from "@/lib/prisma";
import { createOwnedTicketReply } from "@/lib/services/ticket-replies";
export const dynamic = "force-dynamic";
// POST /api/tickets/:id/reply body: { content }
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
const uid = await bearerUserId(req);
if (!uid) return apiError("Unauthorized", 401);
const { id } = await params;
const ticketId = positiveBigInt(id);
if (!ticketId) return apiError("Invalid ticket id", 422);
const body = (await req.json().catch(() => ({}))) as { content?: unknown };
const content = String(body.content ?? "")
.trim()
.slice(0, 5000);
if (!content) return apiError("Content is required");
try {
// Ownership check — only the ticket owner may reply.
const reply = await prisma.$transaction((tx) => createOwnedTicketReply(
{
findTicket: (ticketId) => tx.websiteHelpCenterTickets.findUnique({
where: { id: ticketId },
select: { id: true, userId: true },
}),
createReply: (data) => tx.websiteHelpCenterTicketReplies.create({
data,
select: { id: true, userId: true, content: true, createdAt: true },
}),
touchTicket: (ticketId, updatedAt) => tx.websiteHelpCenterTickets.update({
where: { id: ticketId },
data: { updatedAt },
select: { id: true },
}),
},
{ ticketId, userId: uid, content },
));
if (!reply) return apiError("Ticket not found", 404);
return apiJson(
{
reply: {
id: reply.id,
userId: reply.userId,
content: reply.content,
createdAt: reply.createdAt,
},
},
{ status: 201 },
);
} catch {
return apiError("Failed to post reply", 503);
}
}