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.
89 lines
2.5 KiB
JavaScript
89 lines
2.5 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* One-shot backfill for Ticket.creatorId on Discord-originated tickets.
|
|
*
|
|
* Modal-created tickets (`discord-${ts}-${userId}`): tail segment is the user ID — extract it.
|
|
* Context-menu tickets (`discord-msg-${ts}-${msgId}`): tail segment is the *message* ID, not the
|
|
* user ID. Set creatorId = null and let runtime code fall through to the default-name path.
|
|
* Recovering these would require a Discord API fetch per message, which is unreliable for
|
|
* already-deleted ticket channels.
|
|
*
|
|
* Idempotent: skips tickets that already have creatorId set.
|
|
*
|
|
* Usage:
|
|
* node scripts/backfill-creatorId.js # dry-run, prints summary only
|
|
* node scripts/backfill-creatorId.js --apply # writes
|
|
*/
|
|
|
|
require('dotenv').config();
|
|
const { connectMongoDB, closeMongoDB, mongoose } = require('../db-connection');
|
|
|
|
const APPLY = process.argv.includes('--apply');
|
|
const MODAL_RE = /^discord-\d+-(\d{17,20})$/;
|
|
|
|
async function main() {
|
|
if (!process.env.MONGODB_URI) {
|
|
console.error('MONGODB_URI not set');
|
|
process.exit(1);
|
|
}
|
|
await connectMongoDB(process.env.MONGODB_URI);
|
|
|
|
const Ticket = mongoose.model('Ticket');
|
|
|
|
const candidates = await Ticket.find({
|
|
gmailThreadId: /^discord-/,
|
|
creatorId: { $in: [null, undefined, ''] }
|
|
}).select('gmailThreadId creatorId').lean();
|
|
|
|
let modalHits = 0;
|
|
let msgSkipped = 0;
|
|
let unknown = 0;
|
|
const ops = [];
|
|
|
|
for (const t of candidates) {
|
|
const id = t.gmailThreadId;
|
|
const modalMatch = id.match(MODAL_RE);
|
|
if (modalMatch) {
|
|
modalHits++;
|
|
ops.push({
|
|
updateOne: {
|
|
filter: { _id: t._id },
|
|
update: { $set: { creatorId: modalMatch[1] } }
|
|
}
|
|
});
|
|
continue;
|
|
}
|
|
if (id.startsWith('discord-msg-')) {
|
|
msgSkipped++;
|
|
continue;
|
|
}
|
|
unknown++;
|
|
}
|
|
|
|
console.log(`Scanned ${candidates.length} Discord-originated tickets without creatorId.`);
|
|
console.log(` Modal-pattern recoverable: ${modalHits}`);
|
|
console.log(` Context-menu (unrecoverable, leaving null): ${msgSkipped}`);
|
|
console.log(` Unknown shape: ${unknown}`);
|
|
|
|
if (!APPLY) {
|
|
console.log('\nDry-run only. Re-run with --apply to write changes.');
|
|
await closeMongoDB();
|
|
return;
|
|
}
|
|
|
|
if (ops.length === 0) {
|
|
console.log('Nothing to write.');
|
|
await closeMongoDB();
|
|
return;
|
|
}
|
|
|
|
const res = await Ticket.bulkWrite(ops, { ordered: false });
|
|
console.log(`Wrote ${res.modifiedCount} updates.`);
|
|
await closeMongoDB();
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|