Files
broccolini-bot/scripts/backfill-creatorId.js
indifferentketchup cdf85f6364 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.
2026-05-08 20:19:14 +00:00

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);
});