Files
broccolini-bot/models.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

65 lines
2.3 KiB
JavaScript

var mongoose = require('mongoose');
// ===== Broccolini Bot Models =====
const ticketSchema = new mongoose.Schema({
gmailThreadId: { type: String, required: true, unique: true, index: true },
discordThreadId: String,
senderEmail: { type: String, required: true },
subject: String,
createdAt: { type: Date, default: Date.now },
status: { type: String, default: 'open', enum: ['open', 'closed'] },
transcriptMessageId: String,
claimedBy: String,
escalated: { type: Boolean, default: false },
escalationTier: { type: Number, default: 0 },
ticketNumber: Number,
priority: { type: String, default: 'normal', enum: ['low', 'normal', 'medium', 'high'] },
ticketTag: String,
lastActivity: Date,
welcomeMessageId: String,
claimerId: String,
creatorId: String,
parentCategoryId: String,
pendingDelete: { type: Boolean, default: false }
});
ticketSchema.index({ status: 1, lastActivity: 1 });
ticketSchema.index({ senderEmail: 1, status: 1 });
ticketSchema.index({ discordThreadId: 1 });
mongoose.model('Ticket', ticketSchema);
mongoose.model('TicketCounter', new mongoose.Schema({
senderLocal: { type: String, required: true, unique: true },
counter: { type: Number, default: 1 }
}));
mongoose.model('Transcript', new mongoose.Schema({
gmailThreadId: { type: String, required: true },
transcriptMessageId: String,
createdAt: { type: Date, default: Date.now }
}));
mongoose.model('Tag', new mongoose.Schema({
name: { type: String, required: true, unique: true },
content: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
createdBy: String,
useCount: { type: Number, default: 0 }
}));
mongoose.model('StaffSettings', new mongoose.Schema({
userId: { type: String, required: true, unique: true },
guildId: { type: String, required: true },
notifyDm: { type: Boolean, default: false },
updatedAt: { type: Date, default: Date.now }
}));
mongoose.model('StaffSignature', new mongoose.Schema({
userId: { type: String, required: true, unique: true },
guildId: { type: String, required: true },
valediction: { type: String, default: '' },
displayName: { type: String, default: '' },
tagline: { type: String, default: '' },
updatedAt: { type: Date, default: Date.now }
}));