audit
This commit is contained in:
@@ -440,6 +440,7 @@ async function handleCommand(interaction) {
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO(queue-migrate): permissionOverwrites mutation bypasses channelQueue — could race a pending rename/send on the same channel.
|
||||
await interaction.channel.permissionOverwrites.create(user.id, {
|
||||
ViewChannel: true,
|
||||
SendMessages: true,
|
||||
@@ -462,6 +463,7 @@ async function handleCommand(interaction) {
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO(queue-migrate): permissionOverwrites mutation bypasses channelQueue — could race a pending rename/send on the same channel.
|
||||
await interaction.channel.permissionOverwrites.delete(user.id);
|
||||
await interaction.reply({ content: `Removed ${user} from this ticket.`, allowedMentions: { parse: ['users'] } });
|
||||
} catch (err) {
|
||||
@@ -524,6 +526,7 @@ async function handleCommand(interaction) {
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO(queue-migrate): setParent bypasses channelQueue (enqueueMove) — use enqueueMove so moves serialize with pending renames/sends.
|
||||
await interaction.channel.setParent(category.id, { lockPermissions: true });
|
||||
await interaction.reply(`Moved ticket to **${category.name}**.`);
|
||||
|
||||
@@ -711,6 +714,7 @@ async function handleCommand(interaction) {
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO(queue-migrate): setTopic bypasses channelQueue — could race a pending rename/send on the same channel.
|
||||
await interaction.channel.setTopic(text);
|
||||
await interaction.reply('Topic updated successfully.');
|
||||
} catch (err) {
|
||||
@@ -1085,19 +1089,46 @@ async function handleCommand(interaction) {
|
||||
return interaction.editReply('BACKUP_EXPORT_CHANNEL_ID is not set in .env.');
|
||||
}
|
||||
try {
|
||||
const tickets = await Ticket.find().sort({ ticketNumber: 1 }).lean();
|
||||
const lines = ['# Ticket backup – ' + new Date().toISOString(), 'ticketNumber\tstatus\tsenderEmail\tsubject\tcreatedAt\tclaimedBy\tpriority\tescalationTier'];
|
||||
for (const t of tickets) {
|
||||
// Stream every ticket through a Mongoose cursor to a tmp file so peak RSS
|
||||
// stays bounded regardless of collection size; attach the file, then unlink.
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const tmpName = `ticket-backup-${Date.now()}-${process.pid}.txt`;
|
||||
const tmpPath = path.join(os.tmpdir(), tmpName);
|
||||
const ws = fs.createWriteStream(tmpPath, { encoding: 'utf8' });
|
||||
|
||||
ws.write('# Ticket backup – ' + new Date().toISOString() + '\n');
|
||||
ws.write('ticketNumber\tstatus\tsenderEmail\tsubject\tcreatedAt\tclaimedBy\tpriority\tescalationTier\n');
|
||||
|
||||
let count = 0;
|
||||
const cursor = Ticket.find().sort({ ticketNumber: 1 }).lean().cursor();
|
||||
for await (const t of cursor) {
|
||||
const created = t.createdAt ? new Date(t.createdAt).toISOString() : '';
|
||||
lines.push([t.ticketNumber, t.status || '', (t.senderEmail || '').replace(/\t/g, ' '), (t.subject || '').replace(/\t/g, ' ').slice(0, 200), created, (t.claimedBy || '').replace(/\t/g, ' '), t.priority || '', t.escalationTier ?? ''].join('\t'));
|
||||
ws.write([
|
||||
t.ticketNumber,
|
||||
t.status || '',
|
||||
(t.senderEmail || '').replace(/\t/g, ' '),
|
||||
(t.subject || '').replace(/\t/g, ' ').slice(0, 200),
|
||||
created,
|
||||
(t.claimedBy || '').replace(/\t/g, ' '),
|
||||
t.priority || '',
|
||||
t.escalationTier ?? ''
|
||||
].join('\t') + '\n');
|
||||
count++;
|
||||
}
|
||||
await new Promise((resolve, reject) => ws.end(err => err ? reject(err) : resolve()));
|
||||
|
||||
try {
|
||||
const channel = await interaction.client.channels.fetch(CONFIG.BACKUP_EXPORT_CHANNEL_ID);
|
||||
await enqueueSend(channel, {
|
||||
content: `Ticket backup by ${interaction.user.tag} (${count} tickets)`,
|
||||
files: [new AttachmentBuilder(tmpPath, { name: tmpName })]
|
||||
});
|
||||
await interaction.editReply(`Backup complete. ${count} tickets sent to the backup channel.`);
|
||||
} finally {
|
||||
fs.promises.unlink(tmpPath).catch(() => {});
|
||||
}
|
||||
const buf = Buffer.from(lines.join('\n'), 'utf8');
|
||||
const channel = await interaction.client.channels.fetch(CONFIG.BACKUP_EXPORT_CHANNEL_ID);
|
||||
await enqueueSend(channel, {
|
||||
content: `Ticket backup by ${interaction.user.tag} (${tickets.length} tickets)`,
|
||||
files: [new AttachmentBuilder(buf, { name: `ticket-backup-${Date.now()}.txt` })]
|
||||
});
|
||||
await interaction.editReply(`Backup complete. ${tickets.length} tickets sent to the backup channel.`);
|
||||
} catch (err) {
|
||||
trackError('backup-command', err, interaction);
|
||||
await interaction.editReply('Failed to create backup: ' + (err.message || err));
|
||||
|
||||
Reference in New Issue
Block a user