Skip to content

Commit 7c0381f

Browse files
committed
Refactor unified logging system and dashboard UX
1 parent a46e5e1 commit 7c0381f

32 files changed

Lines changed: 1383 additions & 1299 deletions

src/commands/Community/apply.js

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { logger } from '../../utils/logger.js';
55
import { handleInteractionError, withErrorHandling, createError, ErrorTypes } from '../../utils/errorHandler.js';
66
import ApplicationService from '../../services/applicationService.js';
77
import { InteractionHelper } from '../../utils/interactionHelper.js';
8+
import { logEvent, EVENT_TYPES, resolveApplicationLogChannel } from '../../services/loggingService.js';
9+
import { formatLogLine, resolveUserAuthor } from '../../utils/logEmbeds.js';
10+
import { getGuildConfig } from '../../services/guildConfig.js';
811
import {
912
getApplicationSettings,
1013
getUserApplications,
@@ -179,26 +182,35 @@ export async function handleApplicationModal(interaction) {
179182

180183
const settings = await getApplicationSettings(interaction.client, interaction.guild.id);
181184
const roleSettings = await getApplicationRoleSettings(interaction.client, interaction.guild.id, roleId);
185+
const guildConfig = await getGuildConfig(interaction.client, interaction.guild.id);
186+
187+
const logChannelId = resolveApplicationLogChannel(guildConfig, roleSettings, settings);
182188

183-
const logChannelId = roleSettings.logChannelId || settings.logChannelId;
184-
185189
if (logChannelId) {
186-
const logChannel = interaction.guild.channels.cache.get(logChannelId);
187-
if (logChannel) {
188-
const logEmbed = createEmbed({
189-
title: 'New Application',
190-
description: `**User:** <@${interaction.user.id}> (${interaction.user.tag})\n` +
191-
`**Application:** ${applicationRole.name}\n` +
192-
`**Role:** ${role.name}\n` +
193-
`**Application ID:** \`${application.id}\`\n` +
194-
`**Status:** 🟡 In Progress`
195-
}).setColor(getColor('warning'));
196-
197-
const logMessage = await logChannel.send({ embeds: [logEmbed] });
198-
190+
const logMessage = await logEvent({
191+
client: interaction.client,
192+
guildId: interaction.guild.id,
193+
eventType: EVENT_TYPES.APPLICATION_SUBMIT,
194+
channelId: logChannelId,
195+
data: {
196+
title: 'Application Submitted',
197+
lines: [
198+
formatLogLine('Applicant', `<@${interaction.user.id}> (${interaction.user.tag})`),
199+
formatLogLine('Application', applicationRole.name),
200+
formatLogLine('Role', role.name),
201+
formatLogLine('Application ID', `\`${application.id}\``),
202+
],
203+
inlineFields: [
204+
{ name: 'Status', value: '🟡 In Progress', inline: true },
205+
],
206+
author: await resolveUserAuthor(interaction.client, interaction.user.id),
207+
},
208+
});
209+
210+
if (logMessage) {
199211
await updateApplication(interaction.client, interaction.guild.id, application.id, {
200212
logMessageId: logMessage.id,
201-
logChannelId: logChannelId
213+
logChannelId,
202214
});
203215
}
204216
}

src/commands/Community/modules/app_dashboard.js

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,13 @@ import {
3434
getApplications,
3535
deleteApplication,
3636
} from '../../../utils/database.js';
37+
import { getGuildConfig } from '../../../services/guildConfig.js';
38+
import { setLogChannel, resolveApplicationLogChannel, resolveLogChannel } from '../../../services/loggingService.js';
3739

38-
function buildDashboardEmbed(settings, roles, guild) {
39-
const logChannel = settings.logChannelId ? `<#${settings.logChannelId}>` : '`Not set`';
40+
async function buildDashboardEmbed(settings, roles, guild, client) {
41+
const guildConfig = await getGuildConfig(client, guild.id);
42+
const applicationsChannel = resolveLogChannel(guildConfig, 'applications') || settings.logChannelId;
43+
const logChannel = applicationsChannel ? `<#${applicationsChannel}>` : '`Not set`';
4044
const managerRoleList =
4145
settings.managerRoles?.length > 0
4246
? settings.managerRoles.map(id => `<@&${id}>`).join(',')
@@ -121,10 +125,10 @@ function buildButtonRow(settings, guildId, disabled = false) {
121125
);
122126
}
123127

124-
async function refreshDashboard(rootInteraction, settings, roles, guildId) {
128+
async function refreshDashboard(rootInteraction, settings, roles, guildId, client) {
125129
const selectMenu = buildSelectMenu(guildId);
126130
await InteractionHelper.safeEditReply(rootInteraction, {
127-
embeds: [buildDashboardEmbed(settings, roles, rootInteraction.guild)],
131+
embeds: [await buildDashboardEmbed(settings, roles, rootInteraction.guild, client)],
128132
components: [
129133
buildButtonRow(settings, guildId),
130134
new ActionRowBuilder().addComponents(selectMenu),
@@ -145,8 +149,11 @@ export default {
145149
getApplicationRoles(client, guildId),
146150
]);
147151

152+
const guildConfig = await getGuildConfig(client, guildId);
153+
const applicationsChannel = resolveLogChannel(guildConfig, 'applications') || settings.logChannelId;
154+
148155
const isCompletelyUnconfigured =
149-
!settings.logChannelId &&
156+
!applicationsChannel &&
150157
!settings.enabled &&
151158
(settings.managerRoles?.length ?? 0) === 0 &&
152159
roles.length === 0;
@@ -246,7 +253,7 @@ async function showGlobalDashboard(interaction, settings, roles, guildId, client
246253
const selectMenu = buildSelectMenu(guildId);
247254

248255
await InteractionHelper.safeEditReply(interaction, {
249-
embeds: [buildDashboardEmbed(settings, roles, interaction.guild)],
256+
embeds: [await buildDashboardEmbed(settings, roles, interaction.guild, client)],
250257
components: [
251258
buildButtonRow(settings, guildId),
252259
new ActionRowBuilder().addComponents(selectMenu),
@@ -259,9 +266,10 @@ async function showGlobalDashboard(interaction, settings, roles, guildId, client
259266
async function showApplicationDashboard(rootInteraction, selectedRole, settings, roles, guildId, client) {
260267
const roleObj = rootInteraction.guild.roles.cache.get(selectedRole.roleId);
261268

269+
const guildConfig = await getGuildConfig(client, guildId);
262270
const appSettings = await getApplicationRoleSettings(client, guildId, selectedRole.roleId);
263271
const questions = appSettings.questions || settings.questions || [];
264-
const appLogChannelId = appSettings.logChannelId || settings.logChannelId;
272+
const appLogChannelId = resolveApplicationLogChannel(guildConfig, appSettings, settings);
265273
const isEnabled = selectedRole.enabled !== false;
266274

267275
const logChannelDisplay = appLogChannelId
@@ -712,16 +720,17 @@ async function handleLogChannel(selectInteraction, rootInteraction, settings, ro
712720
roleSettings.logChannelId = channelId;
713721
await saveApplicationRoleSettings(client, guildId, selectedRoleId, roleSettings);
714722
} else {
723+
await setLogChannel(client, guildId, 'applications', channelId);
715724
settings.logChannelId = channelId;
716725
await saveApplicationSettings(client, guildId, settings);
717726
}
718727

719728
await modalSubmission.reply({
720-
embeds: [successEmbed('Log Channel Updated', `Application logs will now be sent to ${channel ??`<#${channelId}>`}.`)],
729+
embeds: [successEmbed('Log Channel Updated', `Application logs will now be sent to ${channel ?? `<#${channelId}>`}.\nYou can also manage this from \`/logging dashboard\`.`)],
721730
flags: MessageFlags.Ephemeral,
722731
});
723732

724-
await refreshDashboard(rootInteraction, settings, roles, guildId);
733+
await refreshDashboard(rootInteraction, settings, roles, guildId, client);
725734
} catch (error) {
726735
if (error.code === 'INTERACTION_TIMEOUT') return;
727736
logger.error('Error in log channel modal:', error);
@@ -782,7 +791,7 @@ async function handleManagerRole(selectInteraction, rootInteraction, settings, r
782791
flags: MessageFlags.Ephemeral,
783792
});
784793

785-
await refreshDashboard(rootInteraction, settings, roles, guildId);
794+
await refreshDashboard(rootInteraction, settings, roles, guildId, client);
786795
} catch (error) {
787796
if (error.code === 'INTERACTION_TIMEOUT') return;
788797
logger.error('Error in manager role modal:', error);
@@ -898,7 +907,7 @@ async function handleQuestions(selectInteraction, rootInteraction, settings, rol
898907
flags: MessageFlags.Ephemeral,
899908
});
900909

901-
await refreshDashboard(rootInteraction, settings, roles, guildId);
910+
await refreshDashboard(rootInteraction, settings, roles, guildId, client);
902911
}
903912

904913
async function handleRoleAdd(selectInteraction, rootInteraction, settings, roles, guildId, client) {
@@ -956,7 +965,7 @@ async function handleRoleAdd(selectInteraction, rootInteraction, settings, roles
956965
flags: MessageFlags.Ephemeral,
957966
});
958967

959-
await refreshDashboard(rootInteraction, settings, roles, guildId);
968+
await refreshDashboard(rootInteraction, settings, roles, guildId, client);
960969
} catch (error) {
961970
if (error.code === 'INTERACTION_TIMEOUT') return;
962971
logger.error('Error in role add modal:', error);
@@ -1021,7 +1030,7 @@ async function handleRoleRemove(selectInteraction, rootInteraction, settings, ro
10211030
flags: MessageFlags.Ephemeral,
10221031
});
10231032

1024-
await refreshDashboard(rootInteraction, settings, roles, guildId);
1033+
await refreshDashboard(rootInteraction, settings, roles, guildId, client);
10251034
} catch (error) {
10261035
if (error.code === 'INTERACTION_TIMEOUT') return;
10271036
logger.error('Error in role remove modal:', error);
@@ -1117,7 +1126,7 @@ async function handleRetention(selectInteraction, rootInteraction, settings, rol
11171126
flags: MessageFlags.Ephemeral,
11181127
});
11191128

1120-
await refreshDashboard(rootInteraction, settings, roles, guildId);
1129+
await refreshDashboard(rootInteraction, settings, roles, guildId, client);
11211130
}
11221131

11231132
async function handleDeleteApplication(confirmSubmit, selectedRoleId, guildId, roles, client) {

src/commands/Core/configWizard.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ function buildDashboardEmbed(config, guild) {
112112
},
113113
{
114114
name: '📋 Log Channel',
115-
value: formatChannelMention(guild, config.logChannelId),
115+
value: formatChannelMention(guild, config.logging?.channels?.audit),
116116
inline: true,
117117
},
118118
{

src/commands/Logging/logging.js

Lines changed: 23 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -4,108 +4,64 @@ import { logger } from '../../utils/logger.js';
44
import { InteractionHelper } from '../../utils/interactionHelper.js';
55

66
import dashboard from './modules/logging_dashboard.js';
7-
import setchannel from './modules/logging_setchannel.js';
8-
import filter from './modules/logging_filter.js';
7+
import channel from './modules/logging_channel.js';
98

109
export default {
1110
data: new SlashCommandBuilder()
1211
.setName('logging')
13-
.setDescription('Manage audit logging for this server.')
12+
.setDescription('Manage server logging — channels, filters, and event categories.')
1413
.setDefaultMemberPermissions(PermissionFlagsBits.ManageGuild)
1514
.setDMPermission(false)
1615
.addSubcommand((subcommand) =>
1716
subcommand
1817
.setName('dashboard')
19-
.setDescription('Open the interactive logging dashboard — view status and toggle event categories.'),
18+
.setDescription('Open the logging dashboard — set channels, filters, and toggle categories.'),
2019
)
2120
.addSubcommand((subcommand) =>
2221
subcommand
23-
.setName('setchannel')
24-
.setDescription('Set the audit log channel for this server.')
22+
.setName('channel')
23+
.setDescription('Quick-set a log channel without opening the dashboard.')
24+
.addStringOption((option) =>
25+
option
26+
.setName('destination')
27+
.setDescription('Which log destination to configure.')
28+
.setRequired(true)
29+
.addChoices(
30+
{ name: 'Audit (moderation, messages, members…)', value: 'audit' },
31+
{ name: 'Applications', value: 'applications' },
32+
{ name: 'Reports', value: 'reports' },
33+
),
34+
)
2535
.addChannelOption((option) =>
2636
option
2737
.setName('channel')
28-
.setDescription('The text channel for audit logs.')
38+
.setDescription('The text channel for logs.')
2939
.addChannelTypes(ChannelType.GuildText)
3040
.setRequired(false),
3141
)
3242
.addBooleanOption((option) =>
3343
option
3444
.setName('disable')
35-
.setDescription('Set to True to disable audit logging entirely.')
45+
.setDescription('Set to True to clear this log channel.')
3646
.setRequired(false),
3747
),
38-
)
39-
.addSubcommandGroup((group) =>
40-
group
41-
.setName('filter')
42-
.setDescription('Manage the log ignore list (users and channels to skip).')
43-
.addSubcommand((subcommand) =>
44-
subcommand
45-
.setName('add')
46-
.setDescription('Add a user or channel to the log ignore list.')
47-
.addStringOption((option) =>
48-
option
49-
.setName('type')
50-
.setDescription('Whether to ignore a user or channel.')
51-
.setRequired(true)
52-
.addChoices(
53-
{ name: 'User', value: 'user' },
54-
{ name: 'Channel', value: 'channel' },
55-
),
56-
)
57-
.addStringOption((option) =>
58-
option
59-
.setName('id')
60-
.setDescription('The ID of the user or channel to ignore.')
61-
.setRequired(true),
62-
),
63-
)
64-
.addSubcommand((subcommand) =>
65-
subcommand
66-
.setName('remove')
67-
.setDescription('Remove a user or channel from the log ignore list.')
68-
.addStringOption((option) =>
69-
option
70-
.setName('type')
71-
.setDescription('Whether this is a user or channel.')
72-
.setRequired(true)
73-
.addChoices(
74-
{ name: 'User', value: 'user' },
75-
{ name: 'Channel', value: 'channel' },
76-
),
77-
)
78-
.addStringOption((option) =>
79-
option
80-
.setName('id')
81-
.setDescription('The ID of the user or channel to remove from the ignore list.')
82-
.setRequired(true),
83-
),
84-
),
8548
),
8649

8750
async execute(interaction, config, client) {
8851
try {
89-
90-
const subcommandGroup = interaction.options.getSubcommandGroup(false);
9152
const subcommand = interaction.options.getSubcommand();
9253

9354
if (subcommand === 'dashboard') {
9455
return await dashboard.execute(interaction, config, client);
9556
}
9657

97-
await InteractionHelper.safeDefer(interaction);
98-
99-
if (subcommand === 'setchannel') {
100-
return await setchannel.execute(interaction, config, client);
101-
}
102-
103-
if (subcommandGroup === 'filter') {
104-
return await filter.execute(interaction, config, client);
58+
if (subcommand === 'channel') {
59+
return await channel.execute(interaction, config, client);
10560
}
10661

107-
await InteractionHelper.safeEditReply(interaction, {
62+
await InteractionHelper.safeReply(interaction, {
10863
embeds: [errorEmbed('Unknown Subcommand', 'This subcommand is not recognised.')],
64+
ephemeral: true,
10965
});
11066
} catch (error) {
11167
logger.error('logging command error:', error);
@@ -115,4 +71,4 @@ export default {
11571
}).catch(() => {});
11672
}
11773
},
118-
};
74+
};

0 commit comments

Comments
 (0)