feat: Add Discord audit routes to Arbiter
New endpoints for Trinity Console: - GET /admin/discord/audit — Full server audit (channels, roles, structure) - GET /admin/discord/channels — Just channels - GET /admin/discord/roles — Just roles Returns: - Server info (name, member count, features) - Categories with nested children - Orphan channels (not in categories) - Role hierarchy with positions and member counts - Permission overwrites per channel Uses existing Discord.js client from app.locals. Chronicler #70
This commit is contained in:
183
services/arbiter-3.0/src/routes/admin/discord-audit.js
Normal file
183
services/arbiter-3.0/src/routes/admin/discord-audit.js
Normal file
@@ -0,0 +1,183 @@
|
||||
/**
|
||||
* Discord Audit Routes
|
||||
* Provides server structure auditing via Trinity Console
|
||||
*
|
||||
* Created: April 8, 2026
|
||||
* Chronicler: #70
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { isAuthenticated, isAdmin } = require('../../middleware/auth');
|
||||
|
||||
/**
|
||||
* GET /admin/discord/audit
|
||||
* Full Discord server audit - channels, roles, members
|
||||
*/
|
||||
router.get('/audit', isAuthenticated, isAdmin, async (req, res) => {
|
||||
try {
|
||||
const client = req.app.locals.client;
|
||||
const guildId = process.env.GUILD_ID;
|
||||
|
||||
if (!client || !client.isReady()) {
|
||||
return res.status(503).json({ error: 'Discord client not ready' });
|
||||
}
|
||||
|
||||
const guild = client.guilds.cache.get(guildId);
|
||||
if (!guild) {
|
||||
return res.status(404).json({ error: 'Guild not found' });
|
||||
}
|
||||
|
||||
// Fetch fresh data
|
||||
await guild.channels.fetch();
|
||||
await guild.roles.fetch();
|
||||
|
||||
// Build channel structure
|
||||
const channels = guild.channels.cache.map(ch => ({
|
||||
id: ch.id,
|
||||
name: ch.name,
|
||||
type: ch.type,
|
||||
typeName: getChannelTypeName(ch.type),
|
||||
parentId: ch.parentId,
|
||||
position: ch.position,
|
||||
nsfw: ch.nsfw || false,
|
||||
topic: ch.topic || null,
|
||||
permissionOverwrites: ch.permissionOverwrites?.cache.map(p => ({
|
||||
id: p.id,
|
||||
type: p.type, // 0 = role, 1 = member
|
||||
allow: p.allow.bitfield.toString(),
|
||||
deny: p.deny.bitfield.toString()
|
||||
})) || []
|
||||
})).sort((a, b) => a.position - b.position);
|
||||
|
||||
// Build role structure
|
||||
const roles = guild.roles.cache.map(r => ({
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
color: r.hexColor,
|
||||
position: r.position,
|
||||
permissions: r.permissions.bitfield.toString(),
|
||||
mentionable: r.mentionable,
|
||||
managed: r.managed, // Bot roles
|
||||
memberCount: r.members.size
|
||||
})).sort((a, b) => b.position - a.position); // Higher position first
|
||||
|
||||
// Categories with their children
|
||||
const categories = channels
|
||||
.filter(ch => ch.type === 4)
|
||||
.map(cat => ({
|
||||
...cat,
|
||||
children: channels.filter(ch => ch.parentId === cat.id)
|
||||
}));
|
||||
|
||||
// Orphan channels (not in any category)
|
||||
const orphanChannels = channels.filter(ch => !ch.parentId && ch.type !== 4);
|
||||
|
||||
// Server info
|
||||
const serverInfo = {
|
||||
id: guild.id,
|
||||
name: guild.name,
|
||||
memberCount: guild.memberCount,
|
||||
ownerId: guild.ownerId,
|
||||
createdAt: guild.createdAt,
|
||||
icon: guild.iconURL(),
|
||||
features: guild.features
|
||||
};
|
||||
|
||||
res.json({
|
||||
server: serverInfo,
|
||||
categories,
|
||||
orphanChannels,
|
||||
allChannels: channels,
|
||||
roles,
|
||||
summary: {
|
||||
totalChannels: channels.length,
|
||||
totalRoles: roles.length,
|
||||
categoryCount: categories.length,
|
||||
orphanCount: orphanChannels.length
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Discord audit error:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /admin/discord/channels
|
||||
* Just channels
|
||||
*/
|
||||
router.get('/channels', isAuthenticated, isAdmin, async (req, res) => {
|
||||
try {
|
||||
const client = req.app.locals.client;
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
|
||||
if (!guild) return res.status(404).json({ error: 'Guild not found' });
|
||||
|
||||
await guild.channels.fetch();
|
||||
|
||||
const channels = guild.channels.cache.map(ch => ({
|
||||
id: ch.id,
|
||||
name: ch.name,
|
||||
type: ch.type,
|
||||
typeName: getChannelTypeName(ch.type),
|
||||
parentId: ch.parentId,
|
||||
position: ch.position
|
||||
})).sort((a, b) => a.position - b.position);
|
||||
|
||||
res.json({ channels });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /admin/discord/roles
|
||||
* Just roles
|
||||
*/
|
||||
router.get('/roles', isAuthenticated, isAdmin, async (req, res) => {
|
||||
try {
|
||||
const client = req.app.locals.client;
|
||||
const guild = client.guilds.cache.get(process.env.GUILD_ID);
|
||||
|
||||
if (!guild) return res.status(404).json({ error: 'Guild not found' });
|
||||
|
||||
await guild.roles.fetch();
|
||||
|
||||
const roles = guild.roles.cache.map(r => ({
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
color: r.hexColor,
|
||||
position: r.position,
|
||||
memberCount: r.members.size,
|
||||
managed: r.managed
|
||||
})).sort((a, b) => b.position - a.position);
|
||||
|
||||
res.json({ roles });
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Helper: Channel type to human name
|
||||
*/
|
||||
function getChannelTypeName(type) {
|
||||
const types = {
|
||||
0: 'Text',
|
||||
2: 'Voice',
|
||||
4: 'Category',
|
||||
5: 'Announcement',
|
||||
10: 'Announcement Thread',
|
||||
11: 'Public Thread',
|
||||
12: 'Private Thread',
|
||||
13: 'Stage',
|
||||
14: 'Directory',
|
||||
15: 'Forum',
|
||||
16: 'Media'
|
||||
};
|
||||
return types[type] || `Unknown (${type})`;
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
@@ -12,6 +12,7 @@ const graceRouter = require('./grace');
|
||||
const auditRouter = require('./audit');
|
||||
const rolesRouter = require('./roles');
|
||||
const schedulerRouter = require('./scheduler');
|
||||
const discordAuditRouter = require('./discord-audit');
|
||||
|
||||
router.use(requireTrinityAccess);
|
||||
|
||||
@@ -77,5 +78,6 @@ router.use('/grace', graceRouter);
|
||||
router.use('/audit', auditRouter);
|
||||
router.use('/roles', rolesRouter);
|
||||
router.use('/scheduler', schedulerRouter);
|
||||
router.use('/discord', discordAuditRouter);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user