all repos — stealth-developers @ 26f02a7c5bf180e51541a9d20e2468bd36593736

bot: add bug editing
vi did:web:vt3e.cat
Mon, 29 Jun 2026 22:37:35 +0100
commit

26f02a7c5bf180e51541a9d20e2468bd36593736

parent

2139da3743617ff966f14c44322f461fef88192c

A apps/bot/src/feats/bugs/buttons.ts

@@ -0,0 +1,26 @@

+import { option } from "@purrkit/router"; +import db, { bugs, eq } from "@stealth-developers/db"; + +import { errorMessage } from "@/lib"; +import { useGetData } from "@/middleware"; + +import { buildReportModal } from "./shared"; + +export const editButton = useGetData.button("edit-bug", { + options: { + id: option.integer({ required: true }), + }, + run: async (interaction, { id }, { actor }) => { + if (!actor) return errorMessage(interaction, "Failed to retrieve your user data"); + const [bug] = await db.select().from(bugs).where(eq(bugs.id, id)); + if (!bug) return errorMessage(interaction, "Bug not found."); + + if (!actor.moderator && bug.actorId !== actor.id) { + return errorMessage(interaction, "You do not have permission to edit this bug."); + } + + const modal = buildReportModal(bug); + console.log(modal.toJSON()); + interaction.showModal(modal); + }, +});
M apps/bot/src/feats/bugs/index.tsapps/bot/src/feats/bugs/index.ts

@@ -1,2 +1,6 @@

-export { bugCommand } from "./commands"; -export { bugModal } from "./modals"; +import { editButton } from "./buttons"; +import { bugCommand } from "./commands"; +import { bugModal, editBugModal } from "./modals"; + +export const bugCommands = [bugCommand]; +export const bugComponents = [bugModal, editBugModal, editButton];
M apps/bot/src/feats/bugs/modals.tsapps/bot/src/feats/bugs/modals.ts

@@ -1,5 +1,5 @@

-import db, { eq, sql } from "@stealth-developers/db"; -import { bugs } from "@stealth-developers/db/src/schemas/bugs"; +import { option } from "@purrkit/router"; +import db, { bugs, eq, getActor, sql } from "@stealth-developers/db"; import { AttachmentBuilder } from "discord.js"; import { errorMessage, getTextChannel } from "@/lib";

@@ -115,3 +115,65 @@ `Bug #${paddedNumber} created successfully! Find it here: ${message.url}`,

); }, }); + +export const editBugModal = useGetData.modal("m-edit-bug", { + options: { + id: option.integer({ required: true }), + }, + run: async (interaction, { id }, { actor, guild }) => { + if (!actor || !guild) + return errorMessage(interaction, "You must be in a server to edit a bug."); + + const { fields: _fields } = interaction; + const fields = { + affected: _fields.getStringSelectValues("affected"), + title: _fields.getTextInputValue("title"), + description: _fields.getTextInputValue("description"), + }; + + await interaction.deferReply({ flags: ["Ephemeral"] }); + + const [bug] = await db.select().from(bugs).where(eq(bugs.id, id)); + + if (!bug) return errorMessage(interaction, "Bug report not found."); + + const [updatedBug] = await db + .update(bugs) + .set({ + title: fields.title, + description: fields.description, + affectedProjects: [...fields.affected], + updatedAt: new Date(), + }) + .where(eq(bugs.id, bug.id)) + .returning(); + + if (!updatedBug) return errorMessage(interaction, "Failed to update bug report."); + + const originalActor = await getActor(updatedBug.actorId); + if (!originalActor) return errorMessage(interaction, "Failed to fetch original bug author."); + + const container = await constructBugContainer(updatedBug, originalActor); + if (!container) return; + + const channel = await getTextChannel("1512924775842840767"); + if (!channel) return errorMessage(interaction, "Failed to find bug report channel."); + + if (updatedBug.messageId) { + try { + const message = await channel.messages.fetch(updatedBug.messageId); + await message.edit({ + components: [container], + }); + } catch (err) { + console.error("error updating discord message:", err); + } + } + + const paddedNumber = updatedBug.bugNumber.toString().padStart(4, "0"); + + await interaction.editReply({ + content: `Bug #${paddedNumber} updated,`, + }); + }, +});
M apps/bot/src/feats/bugs/shared.tsapps/bot/src/feats/bugs/shared.ts

@@ -19,7 +19,8 @@ TextInputStyle,

ThumbnailBuilder, } from "discord.js"; -import { bugModal } from "./modals"; +import { editButton } from "./buttons"; +import { bugModal, editBugModal } from "./modals"; export const Projects = { gw: {

@@ -101,21 +102,18 @@ },

) as ProjectChoice<T>[]; } -export function buildReportModal() { - const terminology = "game"; - const capitalizedTerminology = terminology.charAt(0).toUpperCase() + terminology.slice(1); - +export function buildReportModal(bug?: Bug) { const projectOptions = getProjectChoices("label"); const gameLabel = new LabelBuilder() - .setLabel(capitalizedTerminology) - .setDescription(`The ${terminology} affected by this bug`) + .setLabel("Affected Game") + .setDescription(`The game affected by this bug`) .setStringSelectMenuComponent( new StringSelectMenuBuilder() .setMaxValues(1) .setMinValues(1) .setCustomId("affected") - .setPlaceholder(`Select the ${terminology}`) + .setPlaceholder(`Select a game`) .addOptions(projectOptions), );

@@ -127,6 +125,7 @@ (() => {

const input = new TextInputBuilder() .setCustomId("title") .setStyle(TextInputStyle.Short) + .setValue(bug?.title ?? "") .setRequired(true) .setMaxLength(128);

@@ -142,6 +141,7 @@ (() => {

const input = new TextInputBuilder() .setCustomId("description") .setStyle(TextInputStyle.Paragraph) + .setValue(bug?.description ?? "") .setRequired(true) .setMaxLength(1024);

@@ -149,13 +149,18 @@ return input;

})(), ); - const modal = new ModalBuilder().setCustomId(bugModal.id()).setTitle("Report a Bug"); + const modalId = bug ? editBugModal.id({ id: bug.id }) : bugModal.id(); + const modal = new ModalBuilder().setCustomId(modalId).setTitle("Report a Bug"); - const mediaLabel = new LabelBuilder() - .setLabel("Media") - .setDescription("Provide any screenshots or videos that demonstrate the bug") - .setFileUploadComponent(new FileUploadBuilder().setCustomId("media").setRequired(false)); - modal.setLabelComponents(gameLabel, titleLabel, descriptionLabel, mediaLabel); + modal.setLabelComponents(gameLabel, titleLabel, descriptionLabel); + + if (!bug) { + const mediaLabel = new LabelBuilder() + .setLabel("Media") + .setDescription("Provide any screenshots or videos that demonstrate the bug") + .setFileUploadComponent(new FileUploadBuilder().setCustomId("media").setRequired(false)); + modal.addLabelComponents(mediaLabel); + } return modal; }

@@ -171,20 +176,16 @@ const statusToggle = new ButtonBuilder()

.setCustomId(statusId) .setLabel(statusLabel) .setStyle(isOpen ? ButtonStyle.Danger : ButtonStyle.Success); - const editButton = new ButtonBuilder() - .setCustomId(`bug:edit:${bug.id}`) + const _editButton = new ButtonBuilder() + .setCustomId(editButton.id({ id: bug.id })) .setLabel("Edit") .setStyle(ButtonStyle.Secondary); - const deleteButton = new ButtonBuilder() - .setCustomId(`bug:delete:${bug.id}`) - .setLabel("Delete") - .setStyle(ButtonStyle.Secondary); const trelloButton = new ButtonBuilder() .setCustomId(`bug:trello:${bug.id}`) .setLabel("Trello") .setStyle(ButtonStyle.Secondary); - row.addComponents(statusToggle, editButton, deleteButton, trelloButton); + row.addComponents(statusToggle, _editButton, trelloButton); return row; }
M apps/bot/src/feats/index.tsapps/bot/src/feats/index.ts

@@ -1,7 +1,7 @@

-import { bugCommand, bugModal } from "./bugs"; +import { bugCommands, bugComponents } from "./bugs"; import { miscCommands } from "./misc"; import { robloxUserCommand } from "./roblox"; import { ticketCommands, ticketComponents } from "./tickets"; -export const commands = [...ticketCommands, ...miscCommands, bugCommand, robloxUserCommand]; -export const components = [...ticketComponents, bugModal]; +export const commands = [...ticketCommands, ...miscCommands, ...bugCommands, robloxUserCommand]; +export const components = [...ticketComponents, ...bugComponents];
M apps/bot/src/feats/tickets/commands.tsapps/bot/src/feats/tickets/commands.ts

@@ -426,7 +426,7 @@ (group) => {

group.subcommand("list", { description: "List your tickets", async run(interaction, _, { actor }) { - if (!actor) return errorMessage(interaction, "You must be logged in to run this command"); + if (!actor) return errorMessage(interaction, "Failed to retrieve your user data"); const tickets = await getTicketsByUser(actor.id); if (tickets.length === 0) return errorMessage(interaction, "You have no tickets");

@@ -443,7 +443,7 @@

group.subcommand("export", { description: "Export your tickets", async run(interaction, _, { actor }) { - if (!actor) return errorMessage(interaction, "You must be logged in to run this command"); + if (!actor) return errorMessage(interaction, "Failed to retrieve your user data"); const tickets = await getTicketsByUserGDPR(actor.id); if (tickets.length === 0) return errorMessage(interaction, "You have no tickets");