Event-sourced tracking of staff/ticket lifecycle actions, plus a /stats command. Foundation for a future tickets-website analytics dashboard. Data: - StaffAction model (event log) + Ticket.game / Ticket.closedAt - STATS_ADMIN_IDS config (who may view others' stats) Recording (fire-and-forget, idempotent on real state transitions): - claim, response (channel reply + /response send), escalate, de-escalate, transfer, close (4 sites), reopen — each denormalizes ticketType, tier, priority, game, requester (senderEmail / creatorId), guildId - close events carry closerType / resolverId (claimer credit) / wasClaimed; transfer carries fromId / toId; reopen stamps resolverId - conditional close transition helper (atomic open->closed + closedAt) shared by all four close paths Query + command: - pure period parser (presets + free-text) and stats shaper (per-metric keys) - command-aware autocomplete dispatch - /stats: period (autocomplete) + member (admin-gated) + source (all/email/ discord), ManageMessages + staff-role gated, ephemeral, tier-labeled embed 288+ unit tests; timing/busiest-times data is collected but displayed later.
92 lines
3.1 KiB
JavaScript
92 lines
3.1 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'] },
|
|
lastActivity: Date,
|
|
welcomeMessageId: String,
|
|
claimerId: String,
|
|
creatorId: String,
|
|
parentCategoryId: String,
|
|
pendingDelete: { type: Boolean, default: false },
|
|
game: String,
|
|
closedAt: Date
|
|
});
|
|
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 }
|
|
}));
|
|
|
|
const staffActionSchema = new mongoose.Schema({
|
|
staffId: { type: String, required: true },
|
|
type: { type: String, required: true },
|
|
tier: { type: Number, default: 0 },
|
|
ticketType: String,
|
|
priority: String,
|
|
game: String,
|
|
senderEmail: String,
|
|
creatorId: String,
|
|
gmailThreadId: String,
|
|
guildId: String,
|
|
createdAt: { type: Date, default: Date.now },
|
|
|
|
// close-only
|
|
closerType: String,
|
|
resolverId: String,
|
|
wasClaimed: Boolean,
|
|
|
|
// transfer-only
|
|
fromId: String,
|
|
toId: String
|
|
});
|
|
staffActionSchema.index({ staffId: 1, createdAt: -1 });
|
|
staffActionSchema.index({ gmailThreadId: 1, createdAt: 1 });
|
|
mongoose.model('StaffAction', staffActionSchema);
|