import { randomUUID } from "node:crypto"; import { and, desc, eq } from "drizzle-orm"; import db, { ticketMessages, ticketAttachments, ticketParticipants, tickets, type Ticket, } from ".."; // fetch export async function getTicket(ticketId: string): Promise; export async function getTicket(guildId: number, ticketId: number): Promise; export async function getTicket( idOrGuildId: string | number, ticketId?: number, ): Promise { return typeof idOrGuildId === "string" ? getTicketById(idOrGuildId) : getTicketByGuild(idOrGuildId, ticketId); } export async function getTicketById(id: string) { const [ticket] = await db.select().from(tickets).where(eq(tickets.id, id)); return ticket; } export async function getTicketByGuild(guildId: number, localId?: number) { if (localId === undefined) return undefined; const [ticket] = await db .select() .from(tickets) .where(and(eq(tickets.guildId, guildId), eq(tickets.localId, localId))); return ticket; } export async function getTicketByChannelId(channelId: string) { const [ticket] = await db.select().from(tickets).where(eq(tickets.channelId, channelId)); return ticket; } export async function editTicket(ticketId: string, data: Partial) { const [result] = await db.update(tickets).set(data).where(eq(tickets.id, ticketId)).returning(); return result; } // standard ops export async function createTicket(data: { guildId: number; openedBy: number; subject: number; status: string; channelId?: string | null; openReason?: string | null; }) { const id = randomUUID(); return await db.transaction(async (tx) => { const [lastTicket] = await tx .select({ localId: tickets.localId }) .from(tickets) .where(eq(tickets.guildId, data.guildId)) .orderBy(desc(tickets.localId)) .limit(1); const nextLocalId = lastTicket ? lastTicket.localId + 1 : 1; const [newTicket] = await tx .insert(tickets) .values({ id: id, guildId: data.guildId, localId: nextLocalId, status: data.status, channelId: data.channelId, openedBy: data.openedBy, subject: data.subject, openReason: data.openReason, openedAt: new Date(), }) .returning(); if (!newTicket) throw new Error("Failed to create ticket"); await tx.insert(ticketParticipants).values({ ticketId: newTicket.id, actorId: data.openedBy, role: data.openedBy === data.subject ? "subject" : "participant", createdAt: new Date(), }); return newTicket; }); } export async function closeTicket(data: { ticketId: string; closedBy: number; reason?: string | null; status?: string; }) { const [closedTicket] = await db .update(tickets) .set({ status: data.status ?? "closed", closedAt: new Date(), closedBy: data.closedBy, closeReason: data.reason, }) .where(eq(tickets.id, data.ticketId)) .returning(); return closedTicket; } export async function claimTicket(ticketId: string, actorId: number) { const [ticket] = await db .update(tickets) .set({ claimedBy: actorId, claimedAt: new Date(), }) .where(eq(tickets.id, ticketId)) .returning(); return ticket; } export async function unclaimTicket(ticketId: string) { const [ticket] = await db .update(tickets) .set({ claimedBy: null, claimedAt: null, }) .where(eq(tickets.id, ticketId)) .returning(); return ticket; } // messages export async function createTicketMessageWithAttachments(data: { ticketId: string; actorId: number; publicId: string; messageId?: string | null; content: string; actorRole: "participant" | "moderator" | "subject"; attachments?: { publicId: string; fileName: string; fileType: string; fileSize: number; }[]; }) { return await db.transaction(async (tx) => { const [message] = await tx .insert(ticketMessages) .values({ publicId: data.publicId, messageId: data.messageId, ticketId: data.ticketId, actorId: data.actorId, content: data.content, actorRole: data.actorRole, createdAt: new Date(), }) .returning(); if (data.attachments && data.attachments.length > 0) { await tx.insert(ticketAttachments).values( data.attachments.map((att) => ({ publicId: att.publicId, messageId: message?.id, ticketId: data.ticketId, actorId: data.actorId, fileName: att.fileName, fileType: att.fileType, fileSize: att.fileSize, })), ); } const ticketUpdate: Partial = { lastMessageAt: new Date(), }; if (data.actorRole === "moderator") { ticketUpdate.hasModeratorMessage = true; } else if (data.actorRole === "subject") { ticketUpdate.hasAuthorMessage = true; } await tx.update(tickets).set(ticketUpdate).where(eq(tickets.id, data.ticketId)); return message; }); } export async function getTicketTranscript(ticketId: string) { return await db.query.tickets.findFirst({ where: { id: ticketId, }, with: { opener: true, subjectActor: true, claimedByActor: true, participants: { with: { actor: true, }, }, messages: { orderBy: (messages, { asc }) => [asc(messages.createdAt)], with: { actor: true, attachments: true, }, }, }, }); } // participants export async function addTicketParticipant(data: { ticketId: string; actorId: number; role: "participant" | "moderator" | "subject"; reason?: string | null; }) { const [participant] = await db .insert(ticketParticipants) .values({ ticketId: data.ticketId, actorId: data.actorId, role: data.role, reason: data.reason, createdAt: new Date(), }) .returning(); return participant; } export async function removeTicketParticipant(ticketId: string, actorId: number) { const [removed] = await db .delete(ticketParticipants) .where(and(eq(ticketParticipants.ticketId, ticketId), eq(ticketParticipants.actorId, actorId))) .returning(); return removed; } export async function getTicketParticipants(ticketId: string) { return await db.query.ticketParticipants.findMany({ where: { ticketId: ticketId, }, with: { actor: true, }, }); }