81 lines
2.2 KiB
TypeScript
81 lines
2.2 KiB
TypeScript
// Public REST API — a single help-center ticket (with replies).
|
|
//
|
|
// Bearer-authed. GET returns one ticket that MUST belong to the authed user,
|
|
// together with its replies; reply author usernames are resolved in a single
|
|
// users lookup. Fail-soft: never a 500.
|
|
|
|
import { apiError, apiJson, positiveBigInt } from "@/lib/api";
|
|
import { bearerUserId } from "@/lib/api-auth";
|
|
import { prisma } from "@/lib/prisma";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
// GET /api/tickets/:id
|
|
export async function GET(
|
|
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);
|
|
|
|
try {
|
|
const ticket = await prisma.websiteHelpCenterTickets.findUnique({
|
|
where: { id: ticketId },
|
|
select: {
|
|
id: true,
|
|
userId: true,
|
|
categoryId: true,
|
|
title: true,
|
|
content: true,
|
|
open: true,
|
|
createdAt: true,
|
|
},
|
|
});
|
|
|
|
// Ownership check — return 404 (not 403) so a foreign id is indistinguishable
|
|
// from a missing one.
|
|
if (!ticket || ticket.userId !== uid)
|
|
return apiError("Ticket not found", 404);
|
|
|
|
const replies = await prisma.websiteHelpCenterTicketReplies.findMany({
|
|
where: { ticketId },
|
|
select: { id: true, userId: true, content: true, createdAt: true },
|
|
orderBy: { id: "asc" },
|
|
});
|
|
|
|
// Resolve author usernames in one query.
|
|
const authorIds = [...new Set(replies.map((r) => r.userId))];
|
|
const authors = authorIds.length
|
|
? await prisma.user.findMany({
|
|
where: { id: { in: authorIds } },
|
|
select: { id: true, username: true },
|
|
})
|
|
: [];
|
|
const nameById = new Map(authors.map((a) => [a.id, a.username]));
|
|
|
|
return apiJson({
|
|
ticket: {
|
|
id: ticket.id,
|
|
categoryId: ticket.categoryId,
|
|
title: ticket.title,
|
|
content: ticket.content,
|
|
open: ticket.open,
|
|
createdAt: ticket.createdAt,
|
|
replies: replies.map((r) => ({
|
|
id: r.id,
|
|
userId: r.userId,
|
|
username: nameById.get(r.userId) ?? null,
|
|
content: r.content,
|
|
createdAt: r.createdAt,
|
|
})),
|
|
},
|
|
});
|
|
} catch {
|
|
return apiError("Failed to load ticket", 503);
|
|
}
|
|
}
|