all repos — stealth-developers @ 75b3455c0ab83f42ca6511de32864991d10876f8

db: retrieve related data when fetching tickets
vi did:web:vt3e.cat
Fri, 26 Jun 2026 08:32:51 +0100
commit

75b3455c0ab83f42ca6511de32864991d10876f8

parent

b7a615a91bcbb87e449d2d1032ab961b30a107da

2 files changed, 147 insertions(+), 38 deletions(-)

jump to
M pkgs/db/src/schemas/relations.tspkgs/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.tspkgs/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({