fix: add server_name to moderation_actions for full context retention

Denormalize guild name alongside username so the moderation dashboard
shows both TARGET and server even after message table purges.
Migration 0018. Frontend displays 'username · server_name' in TARGET.
This commit is contained in:
asepharyana
2026-08-27 12:32:12 +07:00
parent cccfd89266
commit 0de393625f
10 changed files with 30 additions and 2 deletions
@@ -0,0 +1 @@
ALTER TABLE "moderation_actions" ADD COLUMN "server_name" text;
@@ -127,6 +127,13 @@
"when": 1787810000000,
"tag": "0017_add_username_to_moderation_actions",
"breakpoints": true
},
{
"idx": 18,
"version": "7",
"when": 1787820000000,
"tag": "0018_add_server_name_to_moderation_actions",
"breakpoints": true
}
]
}
@@ -188,6 +188,7 @@ function hasPermissionApi(channel: unknown): channel is {
async function logAutoDeleteAttempt(
message: MessageRecord,
result: AutoDeleteResult,
serverName?: string | null,
): Promise<void> {
try {
await messageStore.createModerationAction({
@@ -198,6 +199,7 @@ async function logAutoDeleteAttempt(
reason: result.reason,
...verdictToActionFields(message),
username: message.username,
server_name: serverName ?? null,
executed_by: "auto-delete-manager",
status: result.deleted
? "executed"
@@ -264,6 +266,7 @@ export async function attemptAutoDeleteFlaggedMessage(
"nickname melanggar aturan server (offensive_username); pesan dibiarkan",
...verdictToActionFields(message),
username: message.username,
server_name: null,
executed_by: "auto-delete-manager",
status: resetOk ? "executed" : "failed",
error: resetOk ? null : "nickname_reset_failed",
@@ -384,7 +387,7 @@ export async function attemptAutoDeleteFlaggedMessage(
skipped: true,
reason: "dry_run",
};
await logAutoDeleteAttempt(message, result);
await logAutoDeleteAttempt(message, result, guild.name);
logger.info(
{ messageId: message.id, channelId },
"Auto-delete dry-run: would delete flagged message",
@@ -416,7 +419,7 @@ export async function attemptAutoDeleteFlaggedMessage(
skipped: false,
reason: "deleted",
};
await logAutoDeleteAttempt(message, result);
await logAutoDeleteAttempt(message, result, guild.name);
logger.info(
{ messageId: message.id, channelId },
"Auto-deleted AI-flagged message",
@@ -104,6 +104,7 @@ export class ModerationHandler {
| "ban_user",
reason: payload.reason ?? null,
username: null,
server_name: null,
executed_by: payload.executed_by ?? "command-handler",
status: "executed",
error: null,
@@ -643,6 +643,7 @@ export const pgModerationActionsTable = pgTable(
evidence: pgText("evidence"), // JSON array of short quoted snippets
policy_version: pgText("policy_version"), // rules.ts policy version string
username: pgText("username"), // denormalized from messages table for retention safety
server_name: pgText("server_name"), // denormalized guild name for retention safety
},
(table) => ({
messageIdIdx: pgIndex("idx_moderation_actions_message_id").on(
@@ -60,6 +60,7 @@ export const pgModerationActionsTable = pgTable(
evidence: pgText("evidence"), // JSON array of short quoted snippets
policy_version: pgText("policy_version"), // rules.ts policy version string
username: pgText("username"), // denormalized from messages table for retention safety
server_name: pgText("server_name"), // denormalized guild name for retention safety
},
(table) => ({
messageIdIdx: pgIndex("idx_moderation_actions_message_id").on(
@@ -209,6 +209,7 @@ export interface ModerationAction {
action_type: ModerationActionType;
reason: string | null;
username: string | null;
server_name: string | null;
executed_by: string | null;
status: "pending" | "executed" | "failed";
error: string | null;