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.
65 lines
2.3 KiB
JavaScript
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 }
|
|
}));
|