diff --git a/app/api/commission/tickets/[id]/messages/[messageId]/route.test.ts b/app/api/commission/tickets/[id]/messages/[messageId]/route.test.ts index 0863776..3d1a73e 100644 --- a/app/api/commission/tickets/[id]/messages/[messageId]/route.test.ts +++ b/app/api/commission/tickets/[id]/messages/[messageId]/route.test.ts @@ -38,7 +38,7 @@ describe("commission message deletion", () => { beforeEach(() => { process.env.BETTER_AUTH_URL = "https://guide.sudloh.com"; vi.clearAllMocks(); - authorizeTicket.mockResolvedValue({ ticket: { userId: ownerId }, user: { id: ownerId } }); + authorizeTicket.mockResolvedValue({ ticket: { userId: ownerId, status: "open" }, user: { id: ownerId } }); deleteWhere.mockImplementation(() => ({ returning: deleteReturning })); deleteReturning.mockResolvedValue([{ imageObjectKey: "commission/messages/image-1" }]); updateWhere.mockResolvedValue(undefined); @@ -66,6 +66,15 @@ describe("commission message deletion", () => { expect(notifyCommission).toHaveBeenCalledWith(ticketId, ownerId); }); + it("rejects deletion when the ticket is closed", async () => { + authorizeTicket.mockResolvedValue({ ticket: { userId: ownerId, status: "closed" }, user: { id: ownerId } }); + const response = await DELETE(deletionRequest(), context()); + expect(response.status).toBe(409); + expect(tx.delete).not.toHaveBeenCalled(); + expect(deleteObject).not.toHaveBeenCalled(); + expect(notifyCommission).not.toHaveBeenCalled(); + }); + it("leaves storage and notifications untouched when no owned message exists", async () => { deleteReturning.mockResolvedValue([]); const response = await DELETE(deletionRequest(), context()); diff --git a/app/api/commission/tickets/[id]/messages/[messageId]/route.ts b/app/api/commission/tickets/[id]/messages/[messageId]/route.ts index b36bfd3..611fe7d 100644 --- a/app/api/commission/tickets/[id]/messages/[messageId]/route.ts +++ b/app/api/commission/tickets/[id]/messages/[messageId]/route.ts @@ -15,6 +15,7 @@ export async function DELETE(request: Request, const { id, messageId } = await context.params; if (!z.uuid().safeParse(messageId).success) throw new HttpError(404, "message-not-found"); const { ticket, user } = await authorizeTicket(id); + if (ticket.status !== "open") throw new HttpError(409, "ticket-closed"); await limitRequest("commission-message-delete", user.id, 30); const imageObjectKey = await getDb().transaction(async (tx) => { const [message] = await tx.delete(commissionMessages) diff --git a/components/commission/ticket-chat.tsx b/components/commission/ticket-chat.tsx index 253b01c..b297900 100644 --- a/components/commission/ticket-chat.tsx +++ b/components/commission/ticket-chat.tsx @@ -869,7 +869,7 @@ export function CommissionTicketChat({ )} - {mine && ( + {mine && status === "open" && (