audit week 1: creator ID tracking, channel-queue migration, deprecation cleanup
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.
This commit is contained in:
@@ -113,6 +113,81 @@ function enqueueMove(channel, categoryId) {
|
||||
return next;
|
||||
}
|
||||
|
||||
// Shares renameChains so a permissionOverwrite mutation serializes with pending
|
||||
// renames/moves on the same channel. Mode 'create' calls
|
||||
// `channel.permissionOverwrites.create(id, perms)`; 'delete' calls
|
||||
// `channel.permissionOverwrites.delete(id)`. No coalescing.
|
||||
function enqueueOverwrite(channel, id, perms, mode = 'create') {
|
||||
let entry = renameChains.get(channel.id);
|
||||
if (!entry) {
|
||||
entry = { chain: Promise.resolve(), pendingName: null };
|
||||
renameChains.set(channel.id, entry);
|
||||
}
|
||||
|
||||
const next = entry.chain.catch(() => {}).then(() =>
|
||||
mode === 'delete'
|
||||
? channel.permissionOverwrites.delete(id)
|
||||
: channel.permissionOverwrites.create(id, perms)
|
||||
);
|
||||
entry.chain = next;
|
||||
|
||||
next.catch((err) => {
|
||||
logWarn('overwriteQueue', `Overwrite ${mode} failed for ${channel.name}: ${err && err.message || err}`).catch(() => {});
|
||||
const status = err && err.status;
|
||||
const msg = (err && err.message) || String(err);
|
||||
if (status === 401 || status === 403) {
|
||||
logError(
|
||||
'overwriteQueue:token/permission',
|
||||
new Error(`${status} channel=${channel.id} target=${id} mode=${mode}: ${msg}`)
|
||||
).catch(() => {});
|
||||
} else if (status === 429) {
|
||||
logError(
|
||||
'overwriteQueue:ratelimited',
|
||||
new Error(`429 channel=${channel.id} target=${id} mode=${mode}: ${msg}`)
|
||||
).catch(() => {});
|
||||
}
|
||||
}).finally(() => {
|
||||
if (renameChains.get(channel.id) === entry && entry.chain === next && entry.pendingName == null) {
|
||||
renameChains.delete(channel.id);
|
||||
}
|
||||
});
|
||||
return next;
|
||||
}
|
||||
|
||||
// Shares renameChains so setTopic serializes with pending renames/moves.
|
||||
function enqueueTopic(channel, text) {
|
||||
let entry = renameChains.get(channel.id);
|
||||
if (!entry) {
|
||||
entry = { chain: Promise.resolve(), pendingName: null };
|
||||
renameChains.set(channel.id, entry);
|
||||
}
|
||||
|
||||
const next = entry.chain.catch(() => {}).then(() => channel.setTopic(text));
|
||||
entry.chain = next;
|
||||
|
||||
next.catch((err) => {
|
||||
logWarn('topicQueue', `Topic set failed for ${channel.name}: ${err && err.message || err}`).catch(() => {});
|
||||
const status = err && err.status;
|
||||
const msg = (err && err.message) || String(err);
|
||||
if (status === 401 || status === 403) {
|
||||
logError(
|
||||
'topicQueue:token/permission',
|
||||
new Error(`${status} channel=${channel.id}: ${msg}`)
|
||||
).catch(() => {});
|
||||
} else if (status === 429) {
|
||||
logError(
|
||||
'topicQueue:ratelimited',
|
||||
new Error(`429 channel=${channel.id}: ${msg}`)
|
||||
).catch(() => {});
|
||||
}
|
||||
}).finally(() => {
|
||||
if (renameChains.get(channel.id) === entry && entry.chain === next && entry.pendingName == null) {
|
||||
renameChains.delete(channel.id);
|
||||
}
|
||||
});
|
||||
return next;
|
||||
}
|
||||
|
||||
// Per-channel promise chain for send ordering and to prevent interleaving.
|
||||
const sendChains = new Map();
|
||||
|
||||
@@ -157,4 +232,4 @@ function enqueueDelete(channel) {
|
||||
return next;
|
||||
}
|
||||
|
||||
module.exports = { enqueueRename, enqueueMove, enqueueSend, enqueueDelete };
|
||||
module.exports = { enqueueRename, enqueueMove, enqueueOverwrite, enqueueTopic, enqueueSend, enqueueDelete };
|
||||
|
||||
Reference in New Issue
Block a user