import config from "@/config"; import type { Ticket } from "@/database/schema"; import client from "@/discord"; import { marshallIntoApiUser } from "./api"; import _logger from "@/utils/logging"; import type { User as UserProfile, Ticket as ApiTicket } from "@stealth-developers/api"; const logger = _logger.child({ name: "server" }); export const cache = new Map(); export async function getUsers(ids: (string | null | undefined)[]): Promise { const uniqueIds = [...new Set(ids.filter(Boolean) as string[])]; const missingIds = uniqueIds.filter((id) => id !== "system" && !cache.has(id)); if (missingIds.length === 0) return; logger.debug(missingIds, `fetching ${missingIds.length} users`); const guild = client.guilds.cache.get(config.discord.guild); if (guild) { try { const members = await guild.members.fetch({ user: missingIds }); members.forEach((member) => cache.set(member.id, marshallIntoApiUser(member))); } catch (err) { logger.error({ err }, "failed to fetch guild members chunk"); } } const stillMissing = missingIds.filter((id) => !cache.has(id)); if (stillMissing.length > 0) { await Promise.all( stillMissing.map(async (id) => { try { const user = await client.users.fetch(id); cache.set(id, marshallIntoApiUser(user)); } catch { cache.set(id, null); } }), ); } } export async function getUser(id: string): Promise { await getUsers([id]); return cache.get(id) || null; } export async function hydrateTickets(allTickets: Ticket[]): Promise { const allIds = new Set(); for (const ticket of allTickets) { if (ticket.authorId) allIds.add(ticket.authorId); if (ticket.openedBy) allIds.add(ticket.openedBy); if (ticket.closedBy && ticket.closedBy !== "reporter") allIds.add(ticket.closedBy); } await getUsers([...allIds]); return allTickets.map((ticket) => { const fallbackSubject: UserProfile = { id: ticket.authorId, name: "Subject", avatarUrl: "", isModerator: false, }; return { number: String(ticket.id), status: ticket.status as "open" | "closed" | "archived", closedBy: ticket.closedBy ? (cache.get(ticket.closedBy) ?? null) : null, closedAt: ticket.closedAt?.toUTCString() || null, topic: ticket.topic, closeReason: ticket.closeReason, privateReason: ticket.privateReason, openedAt: ticket.createdAt?.toUTCString(), openedBy: ticket.openedBy ? (cache.get(ticket.openedBy) ?? null) : null, subject: cache.get(ticket.authorId) ?? fallbackSubject, }; }); }