QUAL-006 store ticket.creatorId on creation; legacy split-pop returned the
message ID for discord-msg-* tickets, breaking transcript DM, close
log, and channel rename for context-menu-created tickets. Adds the
field to the Ticket schema and writes a one-shot backfill script
(scripts/backfill-creatorId.js, dry-run by default).
QUEUE-001 add enqueueOverwrite + enqueueTopic to services/channelQueue.js
(chain on renameChains alongside enqueueMove). Migrate handleAdd /
handleRemove / handleMove / handleTopic so permissionOverwrites,
setParent, and setTopic no longer race pending renames or sends.
handleMove now uses the existing enqueueMove. Initial overwrites in
handleTicketModal stay inline; channel doesn't exist yet so no race.
DISCORD-001 replace ephemeral: true with flags: MessageFlags.Ephemeral across
broccolini-discord.js, handlers/sharedHelpers.js, handlers/buttons.js,
handlers/commands.js. runDeferred opts now take { flags } directly.
SEC-003 /gmailpoll min interval is 30s. Drop the 5s/10s slash-command
choices and clamp Math.max(30000, ms) in handleGmailPoll for
defense in depth.
QUAL-001 upgrade silent .catch(() => {}) on the lastActivity updateOne in
handlers/messages.js to log via logError, so transient Mongo errors
surface in the debug channel instead of disappearing.
QUAL-002 drop await from logError/logWarn calls in services/staffThread.js
and services/pinMessage.js — fire-and-forget per CLAUDE.md hard rule.
QUAL-003 wrap stray setTimeouts (handleConfirmCloseRequest force-close timer,
runFinalClose channel-delete + overflow-cleanup, checkAutoClose
delete-after-email) in trackTimeout via lazy require so they clear
on shutdown.
55 lines
2.0 KiB
JavaScript
55 lines
2.0 KiB
JavaScript
/**
|
|
* Shared helpers for slash-command and button handlers.
|
|
*
|
|
* Both handlers/commands.js and handlers/buttons.js use these to avoid
|
|
* repeating the lookup-and-defer-and-try-catch pattern across 30+ branches.
|
|
*/
|
|
const { MessageFlags } = require('discord.js');
|
|
const { mongoose } = require('../db-connection');
|
|
const { logError } = require('../services/debugLog');
|
|
|
|
const Ticket = mongoose.model('Ticket');
|
|
|
|
/**
|
|
* Look up the ticket linked to this channel; reply with `missingMessage`
|
|
* (default: "This channel is not linked to a ticket.") and return null if
|
|
* the channel is not a ticket. Returns the ticket on success.
|
|
*
|
|
* @param {import('discord.js').Interaction} interaction
|
|
* @param {string} [missingMessage]
|
|
*/
|
|
async function findTicketForChannel(interaction, missingMessage = 'This channel is not linked to a ticket.') {
|
|
const ticket = await Ticket.findOne({ discordThreadId: interaction.channel.id }).lean();
|
|
if (!ticket) {
|
|
await interaction.reply({ content: missingMessage, flags: MessageFlags.Ephemeral });
|
|
return null;
|
|
}
|
|
return ticket;
|
|
}
|
|
|
|
/**
|
|
* Defer + run + log + reply on error. `verb` is the user-facing verb
|
|
* (e.g. "escalate"); error messages render as "Failed to <verb> this ticket."
|
|
* Errors are logged to console + DEBUGGING_CHANNEL_ID via logError(verb, ...).
|
|
*
|
|
* @param {import('discord.js').Interaction} interaction
|
|
* @param {string} verb
|
|
* @param {() => Promise<void>} fn
|
|
* @param {{ flags?: number }} [opts] - pass `MessageFlags.Ephemeral` for ephemeral defer
|
|
*/
|
|
async function runDeferred(interaction, verb, fn, { flags } = {}) {
|
|
try {
|
|
await interaction.deferReply(flags ? { flags } : {});
|
|
await fn();
|
|
} catch (err) {
|
|
console.error(`${verb} error:`, err);
|
|
logError(verb, err, interaction).catch(() => {});
|
|
const msg = `Failed to ${verb} this ticket.`;
|
|
await interaction.editReply({ content: msg }).catch(() =>
|
|
interaction.followUp({ content: msg, flags: MessageFlags.Ephemeral }).catch(() => {})
|
|
);
|
|
}
|
|
}
|
|
|
|
module.exports = { findTicketForChannel, runDeferred };
|