diff --git a/services/backend/src/modules/moderation/moderation.repository.ts b/services/backend/src/modules/moderation/moderation.repository.ts index da182b89..e7052a5c 100644 --- a/services/backend/src/modules/moderation/moderation.repository.ts +++ b/services/backend/src/modules/moderation/moderation.repository.ts @@ -125,6 +125,7 @@ export class ModerationRepository { a.evidence, a.policy_version, a.username, + a.server_name, LEFT(m.content, 300) AS content FROM moderation_actions a LEFT JOIN messages m ON m.id = a.message_id @@ -155,6 +156,7 @@ export class ModerationRepository { evidence: parseJsonArray(r.evidence), policy_version: r.policy_version ? String(r.policy_version) : null, username: r.username ? String(r.username) : null, + server_name: r.server_name ? String(r.server_name) : null, content: r.content ? String(r.content) : null, })); @@ -338,6 +340,7 @@ export class ModerationRepository { confidence: r.confidence != null ? Number(r.confidence) : null, score: r.score != null ? Number(r.score) : null, username: r.username ? String(r.username) : null, + server_name: r.server_name ? String(r.server_name) : null, content: r.content ? String(r.content) : null, })); } diff --git a/services/discord-gateway/drizzle/migrations/0018_add_server_name_to_moderation_actions.sql b/services/discord-gateway/drizzle/migrations/0018_add_server_name_to_moderation_actions.sql new file mode 100644 index 00000000..ac29ddfe --- /dev/null +++ b/services/discord-gateway/drizzle/migrations/0018_add_server_name_to_moderation_actions.sql @@ -0,0 +1 @@ +ALTER TABLE "moderation_actions" ADD COLUMN "server_name" text; diff --git a/services/discord-gateway/drizzle/migrations/meta/_journal.json b/services/discord-gateway/drizzle/migrations/meta/_journal.json index 2af0157f..6028ab67 100644 --- a/services/discord-gateway/drizzle/migrations/meta/_journal.json +++ b/services/discord-gateway/drizzle/migrations/meta/_journal.json @@ -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 } ] } \ No newline at end of file diff --git a/services/discord-gateway/src/modules/ai-moderation/autoDeleteManager.ts b/services/discord-gateway/src/modules/ai-moderation/autoDeleteManager.ts index 9a7c0fa3..2c7718b1 100644 --- a/services/discord-gateway/src/modules/ai-moderation/autoDeleteManager.ts +++ b/services/discord-gateway/src/modules/ai-moderation/autoDeleteManager.ts @@ -188,6 +188,7 @@ function hasPermissionApi(channel: unknown): channel is { async function logAutoDeleteAttempt( message: MessageRecord, result: AutoDeleteResult, + serverName?: string | null, ): Promise { 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", diff --git a/services/discord-gateway/src/modules/command-handler/moderation.handler.ts b/services/discord-gateway/src/modules/command-handler/moderation.handler.ts index 1a2b23f6..216e4661 100644 --- a/services/discord-gateway/src/modules/command-handler/moderation.handler.ts +++ b/services/discord-gateway/src/modules/command-handler/moderation.handler.ts @@ -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, diff --git a/services/discord-gateway/src/shared/database/schema.ts b/services/discord-gateway/src/shared/database/schema.ts index 7d93e756..e56f1098 100644 --- a/services/discord-gateway/src/shared/database/schema.ts +++ b/services/discord-gateway/src/shared/database/schema.ts @@ -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( diff --git a/services/discord-gateway/src/shared/database/schema/messages.ts b/services/discord-gateway/src/shared/database/schema/messages.ts index 6fe03744..1eb4e056 100644 --- a/services/discord-gateway/src/shared/database/schema/messages.ts +++ b/services/discord-gateway/src/shared/database/schema/messages.ts @@ -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( diff --git a/services/discord-gateway/src/shared/moderation-types.ts b/services/discord-gateway/src/shared/moderation-types.ts index 849c52a5..2b32dc8e 100644 --- a/services/discord-gateway/src/shared/moderation-types.ts +++ b/services/discord-gateway/src/shared/moderation-types.ts @@ -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; diff --git a/services/frontend/src/components/LiveModerationFeed.tsx b/services/frontend/src/components/LiveModerationFeed.tsx index c649b9f0..c3e164ee 100644 --- a/services/frontend/src/components/LiveModerationFeed.tsx +++ b/services/frontend/src/components/LiveModerationFeed.tsx @@ -151,6 +151,14 @@ export function LiveModerationFeed({ {a.username ?? a.user_id ?? "UNKNOWN_SUBJECT"} + {a.server_name && ( + <> + · + + {a.server_name} + + + )} diff --git a/services/frontend/src/lib/types/moderation.ts b/services/frontend/src/lib/types/moderation.ts index 0138447a..8c204894 100644 --- a/services/frontend/src/lib/types/moderation.ts +++ b/services/frontend/src/lib/types/moderation.ts @@ -20,6 +20,7 @@ export interface ModerationAction { created_at: number | null; executed_at: number | null; username: string | null; + server_name: string | null; content: string | null; // ── Explainability (structured verdict, surfaced read-only to public web) ── flags: string[] | null; @@ -80,6 +81,7 @@ export interface CategoryAction { confidence: number | null; score: number | null; username: string | null; + server_name: string | null; content: string | null; }