all repos — stealth-developers @ 2139da3743617ff966f14c44322f461fef88192c

apps/bot/src/feats/bugs/modals.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
 109
 110
 111
 112
 113
 114
 115
 116
 117
import db, { eq, sql } from "@stealth-developers/db";
import { bugs } from "@stealth-developers/db/src/schemas/bugs";
import { AttachmentBuilder } from "discord.js";

import { errorMessage, getTextChannel } from "@/lib";
import { useGetData } from "@/middleware";

import { constructBugContainer } from "./shared";

export const bugModal = useGetData.modal("bug-report", {
	run: async (interaction, _, { actor, guild }) => {
		if (!actor || !guild)
			return errorMessage(interaction, "You must be in a server to report a bug.");

		const { fields: _fields } = interaction;
		const fields = {
			affected: _fields.getStringSelectValues("affected"),
			title: _fields.getTextInputValue("title"),
			description: _fields.getTextInputValue("description"),
			media: _fields.getUploadedFiles("media", false),
		};

		await interaction.deferReply({ flags: ["Ephemeral"] });

		const bugRes = await db.transaction(async (tx) => {
			const [res] = await tx
				.select({ count: sql<number>`COUNT(*)`.mapWith(Number) })
				.from(bugs)
				.where(eq(bugs.guildId, guild.id));

			if (!res) return null;
			const { count } = res;

			const bugNumber = count + 1;

			return await tx
				.insert(bugs)
				.values({
					guildId: guild.id,
					actorId: actor.id,

					bugNumber: bugNumber,
					status: "open",

					title: fields.title,
					description: fields.description,
					affectedProjects: [...fields.affected],

					sent: false,
					messageId: null,
					threadId: null,

					createdAt: new Date(),
					updatedAt: new Date(),
					closedAt: null,
				})
				.returning();
		});
		if (!bugRes) return errorMessage(interaction, "Failed to create bug report.");

		const bug = bugRes[0];
		if (!bug) return errorMessage(interaction, "Failed to create bug report.");

		let attachment: AttachmentBuilder | undefined;
		if (fields.media && fields.media.size > 0) {
			const [_, _attachment] = Array.from(fields.media)[0]!;
			try {
				const fileUrl = _attachment.url;
				const fileName = _attachment.name;
				if (fileUrl) {
					const res = await fetch(fileUrl);
					if (res.ok) {
						const arrayBuffer = await res.arrayBuffer();
						const buffer = Buffer.from(arrayBuffer);
						attachment = new AttachmentBuilder(buffer, { name: fileName });
					}
				}
			} catch (err) {
				console.error("error processing uploaded file:", err);
			}
		}

		const container = await constructBugContainer(bug, actor, attachment);
		if (!container) return;

		const channel = await getTextChannel("1512924775842840767");
		if (!channel) return errorMessage(interaction, "Failed to send bug report.");

		const message = await channel.send({
			components: [container],
			flags: ["IsComponentsV2"],
			...(attachment ? { files: [attachment] } : {}),
		});

		function truncateTitle(title: string, maxLength: number): string {
			return title.length > maxLength ? `${title.substring(0, maxLength)}...` : title;
		}
		const paddedNumber = bug.bugNumber.toString().padStart(4, "0");
		const threadName = `#${paddedNumber} ${truncateTitle(fields.title, 150)}`;
		const thread = await message.startThread({
			name: threadName,
			autoArchiveDuration: 1440,
		});

		await db
			.update(bugs)
			.set({ threadId: thread.id, messageId: message.id })
			.where(eq(bugs.id, bug.id));

		thread.send({
			content: "Use this space to discuss the bug, provide additional details, or ask questions.",
		});
		interaction.editReply(
			`Bug #${paddedNumber} created successfully! Find it here: ${message.url}`,
		);
	},
});