apps/bot/src/commands/tickets/actions.ts (view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
import { randomUUID } from "node:crypto";
import { TextChannel, ChannelType, type Guild } from "discord.js";
import { container, Result } from "@sapphire/framework";
import db, { desc, eq, getGuildByDiscordId, tickets } from "@stealth-developers/db";
export * from "./config";
import { upsertUser, getCategory, PublicError } from "@/lib";
import type { AnyUser } from "@/types";
type CreateTicketArgs =
| undefined
| {
for: AnyUser;
reason?: string;
};
export async function createTicket(
opener: AnyUser,
guild: Guild,
args?: CreateTicketArgs,
): Promise<{ channelId: string; publicId: string }> {
const isOnBehalf = !!args?.for;
const publicId = randomUUID();
const dbGuild = await getGuildByDiscordId(guild.id);
if (!dbGuild) throw new PublicError("Failed to create ticket: Guild not found in database");
const creator = await upsertUser(opener, guild);
if (!creator) throw new PublicError("Failed to create ticket: Creator user not found");
const subject = isOnBehalf ? args?.for : opener;
const subjectUser = subject ? await upsertUser(subject, guild) : creator;
if (!subjectUser) throw new PublicError("Failed to create ticket: Subject user not found");
if (isOnBehalf && !creator.moderator)
throw new PublicError("You don't have permission to create a ticket for another user.");
if (!dbGuild.ticketCategory)
throw new PublicError(
"Ticket category is not set up in this guild. Please contact an administrator.",
);
const ticketCategory = await getCategory(dbGuild.ticketCategory);
let createdChannel: TextChannel | undefined = undefined;
const txResult = await Result.fromAsync(async () => {
return await db.transaction(async (tx) => {
const [maxTicket] = await tx
.select({ localId: tickets.localId })
.from(tickets)
.where(eq(tickets.guildId, creator.guildId))
.orderBy(desc(tickets.localId))
.limit(1);
const localId = (maxTicket?.localId ?? 0) + 1;
const [ticket] = await tx
.insert(tickets)
.values({
id: publicId,
guildId: creator.guildId,
localId: localId,
status: "open",
openedAt: new Date(),
openedBy: creator.id,
subject: subjectUser.id,
})
.returning();
if (!ticket) throw new PublicError("failed to add ticket to database, please try again.");
const ticketNumber = ticket.localId;
const paddedTicketNumber = String(ticketNumber).padStart(4, "0");
const channel = await guild.channels.create({
name: `ticket-${paddedTicketNumber}`,
type: ChannelType.GuildText,
parent: ticketCategory,
});
if (!channel) throw new PublicError("failed to create Discord channel.");
createdChannel = channel;
await tx.update(tickets).set({ channelId: channel.id }).where(eq(tickets.id, ticket.id));
await channel.send({ content: `Hi! Thanks for submitting a report.` });
return channel.id;
});
});
if (txResult.isErr()) {
if (createdChannel) await Result.fromAsync(() => createdChannel?.delete());
const error = txResult.unwrapErr();
const message =
error instanceof PublicError
? `Failed to create ticket: ${error.message}`
: "There was an error creating the ticket.";
if (error instanceof Error) container.logger.error(error.message, error.stack);
throw new PublicError(message);
}
const channelId = txResult.unwrap();
return { channelId, publicId };
}
|