db: retrieve related data when fetching tickets
vi did:web:vt3e.cat
Fri, 26 Jun 2026 08:32:51 +0100
2 files changed,
147 insertions(+),
38 deletions(-)
M
pkgs/db/src/schemas/relations.ts
→
pkgs/db/src/schemas/relations.ts
@@ -88,6 +88,11 @@ from: r.tickets.claimedBy,
to: r.actors.id, alias: "ticket_claimed_by", }), + closedByActor: r.one.actors({ + from: r.tickets.closedBy, + to: r.actors.id, + alias: "ticket_closed_by", + }), participants: r.many.ticketParticipants({ from: r.tickets.id,
M
pkgs/db/src/utils/ticket.ts
→
pkgs/db/src/utils/ticket.ts
@@ -9,38 +9,128 @@ ticketComments,
type Ticket, } from ".."; -// fetch -export async function getTicket(ticketId: string): Promise<Ticket | undefined>; -export async function getTicket(guildId: number, ticketId: number): Promise<Ticket | undefined>; +export const ticketWithRelations = { + guild: true, + opener: { + with: { + user: true, + }, + }, + subjectActor: { + with: { + user: true, + }, + }, + claimedByActor: { + with: { + user: true, + }, + }, + closedByActor: { + with: { + user: true, + }, + }, + participants: { + with: { + actor: { + with: { + user: true, + }, + }, + }, + }, + messages: { + orderBy: { + createdAt: "asc", + }, + with: { + actor: { + with: { + user: true, + }, + }, + attachments: true, + }, + }, + attachments: { + with: { + actor: { + with: { + user: true, + }, + }, + }, + }, + comments: { + with: { + actor: { + with: { + user: true, + }, + }, + }, + }, +} as const; + +export async function getTicketById(id: string) { + return await db.query.tickets.findFirst({ + with: ticketWithRelations, + where: { + id: id, + }, + }); +} + +export type TicketWithRelations = NonNullable<Awaited<ReturnType<typeof getTicketById>>>; + +export async function getTicket(ticketId: string): Promise<TicketWithRelations | undefined>; +export async function getTicket( + guildId: number, + ticketId: number, +): Promise<TicketWithRelations | undefined>; export async function getTicket( idOrGuildId: string | number, ticketId?: number, -): Promise<Ticket | undefined> { +): Promise<TicketWithRelations | undefined> { 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) { + +export async function getTicketByGuild( + guildId: number, + localId?: number, +): Promise<TicketWithRelations | undefined> { if (localId === undefined) return undefined; - const [ticket] = await db - .select() - .from(tickets) - .where(and(eq(tickets.guildId, guildId), eq(tickets.localId, localId))); - return ticket; + return await db.query.tickets.findFirst({ + where: { + guildId: guildId, + localId: localId, + }, + with: ticketWithRelations, + }); } -export async function getTicketByChannelId(channelId: string) { - const [ticket] = await db.select().from(tickets).where(eq(tickets.channelId, channelId)); - return ticket; + +export async function getTicketByChannelId( + channelId: string, +): Promise<TicketWithRelations | undefined> { + return await db.query.tickets.findFirst({ + where: { + channelId: channelId, + }, + with: ticketWithRelations, + }); } -export async function editTicket(ticketId: string, data: Partial<Ticket>) { +export async function editTicket( + ticketId: string, + data: Partial<Ticket>, +): Promise<TicketWithRelations | undefined> { const [result] = await db.update(tickets).set(data).where(eq(tickets.id, ticketId)).returning(); - return result; + if (!result) return undefined; + return await getTicketById(ticketId); } // standard ops@@ -51,7 +141,7 @@ subject: number;
status: string; channelId?: string | null; openReason?: string | null; -}) { +}): Promise<TicketWithRelations> { const id = randomUUID(); return await db.transaction(async (tx) => {@@ -88,7 +178,15 @@ role: data.openedBy === data.subject ? "subject" : "participant",
createdAt: new Date(), }); - return newTicket; + const populatedTicket = await tx.query.tickets.findFirst({ + where: { + id: newTicket.id, + }, + with: ticketWithRelations, + }); + + if (!populatedTicket) throw new Error("Failed to retrieve created ticket"); + return populatedTicket as TicketWithRelations; }); }@@ -97,7 +195,7 @@ ticketId: string;
closedBy: number; reason?: string | null; status?: string; -}) { +}): Promise<TicketWithRelations | undefined> { const [closedTicket] = await db .update(tickets) .set({@@ -109,10 +207,14 @@ })
.where(eq(tickets.id, data.ticketId)) .returning(); - return closedTicket; + if (!closedTicket) return undefined; + return await getTicketById(data.ticketId); } -export async function claimTicket(ticketId: string, actorId: number) { +export async function claimTicket( + ticketId: string, + actorId: number, +): Promise<TicketWithRelations | undefined> { const [ticket] = await db .update(tickets) .set({@@ -122,10 +224,11 @@ })
.where(eq(tickets.id, ticketId)) .returning(); - return ticket; + if (!ticket) return undefined; + return await getTicketById(ticketId); } -export async function unclaimTicket(ticketId: string) { +export async function unclaimTicket(ticketId: string): Promise<TicketWithRelations | undefined> { const [ticket] = await db .update(tickets) .set({@@ -135,7 +238,8 @@ })
.where(eq(tickets.id, ticketId)) .returning(); - return ticket; + if (!ticket) return undefined; + return await getTicketById(ticketId); } // messages@@ -185,11 +289,8 @@ const ticketUpdate: Partial<Ticket> = {
lastMessageAt: new Date(), }; - if (data.actorRole === "moderator") { - ticketUpdate.hasModeratorMessage = true; - } else if (data.actorRole === "subject") { - ticketUpdate.hasAuthorMessage = true; - } + 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));@@ -199,13 +300,12 @@ }
export async function getTicketTranscript(ticketId: string) { return await db.query.tickets.findFirst({ - where: { - id: ticketId, - }, + where: { id: ticketId }, with: { opener: true, subjectActor: true, claimedByActor: true, + participants: { with: { actor: true,@@ -270,7 +370,11 @@ where: {
ticketId: ticketId, }, with: { - actor: true, + actor: { + with: { + user: true, + }, + }, }, }); }@@ -295,7 +399,7 @@
return comment; } -export async function removeTicketComment(commentId: number) { +export async function removeTicketComment(commentId: string) { const [removed] = await db .delete(ticketComments) .where(eq(ticketComments.id, commentId))@@ -304,7 +408,7 @@
return removed; } -export async function editTicketComment(commentId: number, content: string) { +export async function editTicketComment(commentId: string, content: string) { const [updated] = await db .update(ticketComments) .set({