import { type Guild, type TicketWithRelations, type User } from "@stealth-developers/db"; type TicketAttachment = TicketWithRelations["attachments"][number]; type TicketComment = TicketWithRelations["comments"][number]; type TicketMessage = TicketWithRelations["messages"][number]; type TicketParticipant = TicketWithRelations["participants"][number]; import type { ActorWithGuild } from "./db"; import { type SanitisedActor, type SanitisedGuild, type SanitisedTicket, type SanitisedTicketAttachment, type SanitisedTicketComment, type SanitisedTicketMessage, type SanitisedTicketParticipant, type SanitisedUser, } from "./schemas"; export interface SanitiseOptions { isModerator: boolean; anonymiseStaff?: boolean; } type SanitiseActorInput = Omit & { guild?: Guild | null }; // Guild export function sanitiseGuild(guild: Guild): SanitisedGuild { return { id: guild.id, discordId: guild.discordId, name: guild.name, icon: guild.icon, createdAt: guild.createdAt, updatedAt: guild.updatedAt, }; } export function sanitiseGuilds(guilds: Guild[]): SanitisedGuild[] { return guilds.map(sanitiseGuild); } // User export function sanitiseUser(user: User): SanitisedUser { return { id: user.id, discordId: user.discordId, username: user.username, displayName: user.displayName, createdAt: user.createdAt, updatedAt: user.updatedAt, }; } export function sanitiseUsers(users: User[]): SanitisedUser[] { return users.map(sanitiseUser); } // Actor export function sanitiseActor(actor: SanitiseActorInput, excludeGuild = true): SanitisedActor { return { actorId: actor.id, guildId: actor.guildId, userId: actor.userId, discordId: actor.discordId, username: actor.username, displayName: actor.displayName, avatar: actor.avatar, createdAt: actor.createdAt, updatedAt: actor.updatedAt, ...(actor.guild && !excludeGuild ? { guild: sanitiseGuild(actor.guild) } : {}), }; } export function sanitiseActors( actors: SanitiseActorInput[], excludeGuild = true, ): SanitisedActor[] { return actors.map((actor) => sanitiseActor(actor, excludeGuild)); } // Ticket export function sanitiseTicket( ticket: TicketWithRelations, options: SanitiseOptions, ): SanitisedTicket { const { isModerator, anonymiseStaff = false } = options; const shouldAnonymise = !isModerator && anonymiseStaff; return { id: ticket.id, guildId: ticket.guildId, localId: ticket.localId, status: ticket.status, channelId: ticket.channelId, threadId: ticket.threadId, closeReason: ticket.closeReason, privateReason: isModerator ? ticket.privateReason : null, openedAt: ticket.openedAt, closedAt: ticket.closedAt, closedBy: shouldAnonymise ? null : ticket.closedBy, lastSubjectActivityAt: ticket.lastSubjectActivityAt, lastClaimantActivityAt: isModerator ? ticket.lastClaimantActivityAt : null, openedBy: ticket.openedBy, openReason: ticket.openReason, subject: ticket.subject, claimedBy: shouldAnonymise ? null : ticket.claimedBy, claimedAt: shouldAnonymise ? null : ticket.claimedAt, thankedAt: ticket.thankedAt, warnedAt: ticket.warnedAt, lastMessageAt: ticket.lastMessageAt, hasModeratorMessage: ticket.hasModeratorMessage, hasAuthorMessage: ticket.hasAuthorMessage, subjects: { opener: sanitiseActor(ticket.opener as unknown as SanitiseActorInput), subject: sanitiseActor(ticket.subjectActor as unknown as SanitiseActorInput), closedBy: shouldAnonymise || !ticket.closedByActor ? null : sanitiseActor(ticket.closedByActor as unknown as SanitiseActorInput), claimedBy: shouldAnonymise || !ticket.claimedByActor ? null : sanitiseActor(ticket.claimedByActor as unknown as SanitiseActorInput), participants: ticket.participants .filter((p) => !(shouldAnonymise && p.role === "moderator")) .map((p) => sanitiseActor(p.actor as unknown as SanitiseActorInput)), }, }; } // TicketMessage export function sanitiseTicketMessage( message: TicketMessage, options: SanitiseOptions, ): SanitisedTicketMessage | null { const { isModerator, anonymiseStaff = false } = options; if (message.isPrivate && !isModerator) return null; const shouldAnonymise = !isModerator && anonymiseStaff && message.actorRole === "moderator"; return { id: message.id, messageId: message.messageId, ticketId: message.ticketId, actorId: shouldAnonymise ? null : message.actorId, actorRole: message.actorRole, isPrivate: message.isPrivate, content: message.content, embeds: message.embeds ?? [], components: message.components ?? [], createdAt: message.createdAt, editedAt: message.editedAt, deletedAt: message.deletedAt, actors: { author: shouldAnonymise ? null : sanitiseActor(message.actor as unknown as SanitiseActorInput), }, }; } export function sanitiseTicketMessages( messages: TicketMessage[], options: SanitiseOptions, ): SanitisedTicketMessage[] { return messages .map((msg) => sanitiseTicketMessage(msg, options)) .filter((msg): msg is SanitisedTicketMessage => msg !== null); } // TicketParticipant export function sanitiseTicketParticipant( participant: TicketParticipant, options: SanitiseOptions, ): SanitisedTicketParticipant | null { const { isModerator, anonymiseStaff = false } = options; const isStaffParticipant = participant.role === "moderator"; const shouldAnonymise = !isModerator && anonymiseStaff && isStaffParticipant; if (shouldAnonymise) return null; return { id: participant.id, ticketId: participant.ticketId, actorId: participant.actorId, role: participant.role, reason: isModerator ? participant.reason : null, createdAt: participant.createdAt, actors: { participant: sanitiseActor(participant.actor as unknown as SanitiseActorInput), }, }; } export function sanitiseTicketParticipants( participants: TicketParticipant[], options: SanitiseOptions, ): SanitisedTicketParticipant[] { return participants .map((p) => sanitiseTicketParticipant(p, options)) .filter((p): p is SanitisedTicketParticipant => p !== null); } // TicketComment export function sanitiseTicketComment( comment: TicketComment, options: SanitiseOptions, ): SanitisedTicketComment | null { const { isModerator } = options; if (!isModerator) return null; return { id: comment.id, ticketId: comment.ticketId, actorId: comment.actorId, content: comment.content, createdAt: comment.createdAt, updatedAt: comment.updatedAt, actors: { author: sanitiseActor(comment.actor as unknown as SanitiseActorInput), }, }; } export function sanitiseTicketComments( comments: TicketComment[], options: SanitiseOptions, ): SanitisedTicketComment[] { return comments .map((c) => sanitiseTicketComment(c, options)) .filter((c): c is SanitisedTicketComment => c !== null); } // TicketAttachment export function sanitiseTicketAttachment( attachment: TicketAttachment, options: SanitiseOptions, ): SanitisedTicketAttachment { const { isModerator, anonymiseStaff = false } = options; const shouldAnonymise = !isModerator && anonymiseStaff; return { id: attachment.id, messageId: attachment.messageId, ticketId: attachment.ticketId, actorId: shouldAnonymise ? null : attachment.actorId, key: attachment.key, fileName: attachment.fileName, fileType: attachment.fileType, fileSize: attachment.fileSize, actors: { author: shouldAnonymise ? null : sanitiseActor(attachment.actor as unknown as SanitiseActorInput), }, }; } export function sanitiseTicketAttachments( attachments: TicketAttachment[], options: SanitiseOptions, ): SanitisedTicketAttachment[] { return attachments.map((a) => sanitiseTicketAttachment(a, options)); }