Refactor moderation actions to include channel and source metadata

- Updated moderation action functions to include `channelId` and `source` in the metadata for better tracking of actions.
- Introduced an `auditFrom` function to streamline the addition of audit information across various commands.
- Removed `prisma/migrations` from .gitignore to ensure migration files are tracked.
- Enhanced the `CreateCaseInput` type to accommodate new metadata structure.
This commit is contained in:
smueller
2026-07-22 11:28:43 +02:00
parent 878478e4fc
commit 405b70bdfc
7 changed files with 140 additions and 8 deletions

View File

@@ -43,7 +43,12 @@ export async function executeBan(
moderatorId: interaction.user.id,
action: 'BAN',
reason: payload.reason,
metadata: { confirmed: true }
channelId: interaction.channelId ?? undefined,
source: 'button_confirmation',
metadata: {
confirmed: true,
duration: payload.duration
}
});
if (payload.duration) {
@@ -98,11 +103,21 @@ export async function executePurge(
moderatorId: interaction.user.id,
action: 'PURGE',
reason: `Deleted ${deletedCount} messages`,
channelId: payload.channelId,
source: 'button_confirmation',
metadata: {
confirmed: true,
channelId: payload.channelId,
requestedAmount: payload.amount,
deletedCount
purge: {
requestedAmount: payload.amount,
deletedCount,
filters: {
targetUserId: payload.targetUserId,
botsOnly: payload.botsOnly,
linksOnly: payload.linksOnly,
attachmentsOnly: payload.attachmentsOnly,
regex: payload.regex
}
}
}
});
@@ -147,7 +162,13 @@ export async function executeCaseDelete(
moderatorId: interaction.user.id,
action: 'CASE_DELETE',
reason: `Soft-deleted case #${payload.caseNumber}`,
metadata: { confirmed: true, deletedCaseId: existing.id }
channelId: interaction.channelId ?? undefined,
source: 'button_confirmation',
metadata: {
confirmed: true,
deletedCaseId: existing.id,
deletedCaseNumber: payload.caseNumber
}
});
await interaction.update({

View File

@@ -18,6 +18,13 @@ function reasonFrom(i: ChatInputCommandInteraction, locale: 'de' | 'en'): string
return i.options.getString('reason') ?? t(locale, 'moderation.reason.none');
}
function auditFrom(interaction: ChatInputCommandInteraction) {
return {
channelId: interaction.channelId ?? undefined,
source: 'slash_command' as const
};
}
async function ensureMod(
interaction: ChatInputCommandInteraction,
permission: bigint,
@@ -70,6 +77,7 @@ const unbanCommand: SlashCommand = {
guildId: interaction.guildId!,
targetUserId: userId,
moderatorId: interaction.user.id,
...auditFrom(interaction),
action: 'UNBAN',
reason
});
@@ -94,6 +102,7 @@ const kickCommand: SlashCommand = {
guildId: interaction.guildId!,
targetUserId: user.id,
moderatorId: interaction.user.id,
...auditFrom(interaction),
action: 'KICK',
reason
});
@@ -122,6 +131,7 @@ const timeoutCommand: SlashCommand = {
guildId: interaction.guildId!,
targetUserId: user.id,
moderatorId: interaction.user.id,
...auditFrom(interaction),
action: 'TIMEOUT',
reason,
metadata: { durationMs: duration }
@@ -147,6 +157,7 @@ const untimeoutCommand: SlashCommand = {
guildId: interaction.guildId!,
targetUserId: user.id,
moderatorId: interaction.user.id,
...auditFrom(interaction),
action: 'UN_TIMEOUT',
reason
});
@@ -369,6 +380,7 @@ const warnCommand: SlashCommand = {
guildId: interaction.guildId!,
targetUserId: user.id,
moderatorId: interaction.user.id,
...auditFrom(interaction),
action: 'WARN_CLEAR',
reason: 'Warnings cleared'
});
@@ -446,6 +458,7 @@ const slowmodeCommand: SlashCommand = {
guildId: interaction.guildId!,
targetUserId: interaction.user.id,
moderatorId: interaction.user.id,
...auditFrom(interaction),
action: 'SLOWMODE',
reason: `Set slowmode to ${seconds}s`
});
@@ -467,6 +480,7 @@ const lockCommand: SlashCommand = {
guildId: interaction.guildId!,
targetUserId: interaction.user.id,
moderatorId: interaction.user.id,
...auditFrom(interaction),
action: 'LOCK',
reason: 'Channel locked'
});
@@ -488,6 +502,7 @@ const unlockCommand: SlashCommand = {
guildId: interaction.guildId!,
targetUserId: interaction.user.id,
moderatorId: interaction.user.id,
...auditFrom(interaction),
action: 'UNLOCK',
reason: 'Channel unlocked'
});

View File

@@ -2,6 +2,7 @@ import { moderationQueue } from '../../jobs.js';
import type { BotContext } from '../../types.js';
import { PermissionFlagsBits, type ChatInputCommandInteraction } from 'discord.js';
import type { Prisma } from '@prisma/client';
import { buildCaseMetadata, type CaseAuditSource } from '@nexumi/shared';
type CreateCaseInput = {
guildId: string;
@@ -9,7 +10,9 @@ type CreateCaseInput = {
moderatorId: string;
action: string;
reason?: string;
metadata?: Prisma.InputJsonValue;
source?: CaseAuditSource;
channelId?: string;
metadata?: Record<string, unknown>;
};
export async function createCase(context: BotContext, input: CreateCaseInput) {
@@ -24,6 +27,12 @@ export async function createCase(context: BotContext, input: CreateCaseInput) {
orderBy: { caseNumber: 'desc' }
});
const metadata = buildCaseMetadata({
source: input.source ?? 'slash_command',
channelId: input.channelId,
extras: input.metadata
});
return context.prisma.case.create({
data: {
guildId: input.guildId,
@@ -32,7 +41,7 @@ export async function createCase(context: BotContext, input: CreateCaseInput) {
moderatorId: input.moderatorId,
action: input.action,
reason: input.reason,
metadata: input.metadata
metadata: metadata as Prisma.InputJsonValue
}
});
}
@@ -94,6 +103,8 @@ export async function applyWarnEscalation(
moderatorId,
action: 'TIMEOUT',
reason,
channelId: interaction.channelId ?? undefined,
source: 'escalation',
metadata: { escalation: true, durationMs: rule.durationMs, warningCount }
});
return `Auto escalation applied: timeout (${rule.durationMs} ms).`;
@@ -110,6 +121,8 @@ export async function applyWarnEscalation(
moderatorId,
action: 'KICK',
reason,
channelId: interaction.channelId ?? undefined,
source: 'escalation',
metadata: { escalation: true, warningCount }
});
return 'Auto escalation applied: kick.';
@@ -125,6 +138,8 @@ export async function applyWarnEscalation(
moderatorId,
action: 'BAN',
reason,
channelId: interaction.channelId ?? undefined,
source: 'escalation',
metadata: { escalation: true, warningCount }
});
return 'Auto escalation applied: ban.';