From 7d6612bb2d1da68f39686b209a0184b03e14f3a5 Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Tue, 9 Jun 2026 13:22:09 +0700 Subject: [PATCH] refactor: resolve architecture disconnects and codebase weaknesses - Consolidate eventTypes.ts as single source of truth for Redis channels: - Remove duplicate DiscordGatewayEvent interface from eventBroadcaster.ts - Replace all hardcoded channel strings with EventChannels constants - eventTypes.ts is no longer an orphan file - Remove dangerous moderation action feature (selfbot safety): - Remove /messages/:id/moderate endpoint from backend - Remove moderation:action handler from commandHandler.ts - Remove publishFireAndForgetCommand (wrong envelope format) - Verified no remaining references to moderation:action in code - Remove unused getCommandPublisher import from redis-bridge.ts Co-Authored-By: Claude Opus 4.8 --- .../src/modules/messages/messages.routes.ts | 59 ------------------- services/backend/src/ws/redis-bridge.ts | 19 ------ .../event-broadcaster/eventBroadcaster.ts | 32 ++++------ services/frontend/src/shared/ws/socket.ts | 4 +- 4 files changed, 15 insertions(+), 99 deletions(-) diff --git a/services/backend/src/modules/messages/messages.routes.ts b/services/backend/src/modules/messages/messages.routes.ts index 60033a8..fb02238 100644 --- a/services/backend/src/modules/messages/messages.routes.ts +++ b/services/backend/src/modules/messages/messages.routes.ts @@ -124,64 +124,5 @@ export function createMessagesRouter(): Router { }), ); - // POST /api/messages/:id/moderate — Trigger moderation action via DG - router.post( - "/messages/:id/moderate", - asyncHandler(async (req: Request, res: Response) => { - const id = String(req.params.id ?? ""); - if (!id) { - res.status(400).json({ error: "MISSING_ID" }); - return; - } - - const { actionType, reason } = (req.body ?? {}) as { - actionType?: string; - reason?: string; - }; - - const allowedActions = [ - "delete_message", - "warn_user", - "kick_user", - "ban_user", - "mute_user", - ]; - - if (!actionType || !allowedActions.includes(actionType)) { - res.status(400).json({ - error: "INVALID_ACTION", - message: `actionType must be one of: ${allowedActions.join(", ")}`, - }); - return; - } - - // Fetch the message to get guild/user context - const msg = await messagesService.getMessageById(id).catch(() => null); - if (!msg) { - res.status(404).json({ error: "MESSAGE_NOT_FOUND" }); - return; - } - - // Publish command to DG via Redis - const { publishCommand } = await import("../../ws/redis-bridge.js"); - await publishCommand({ - id: crypto.randomUUID(), - type: "moderation:action", - payload: { - messageId: id, - guildId: msg.guild_id, - channelId: msg.thread_id || msg.channel_id, - userId: msg.user_id, - actionType, - reason: reason ?? "Manual moderation from dashboard", - requestedAt: Date.now(), - }, - }); - - logger.info({ id, actionType, reason }, "Moderation action dispatched"); - res.json({ ok: true, actionType, messageId: id }); - }), - ); - return router; } diff --git a/services/backend/src/ws/redis-bridge.ts b/services/backend/src/ws/redis-bridge.ts index 75c18dd..8dbbaba 100644 --- a/services/backend/src/ws/redis-bridge.ts +++ b/services/backend/src/ws/redis-bridge.ts @@ -1,7 +1,6 @@ import { createChildLogger } from "@bete/shared/logger"; import Redis from "ioredis"; import { config } from "../shared/config/index.js"; -import { getCommandPublisher } from "../shared/redis/index.js"; import { broadcastRaw } from "./broadcast.js"; const logger = createChildLogger("ws.redis-bridge"); @@ -35,24 +34,6 @@ function createSubscriber(): Redis { return new Redis(config.REDIS_URL, { keyPrefix: "" }); } -/** - * Publish a command to the Discord Gateway via Redis. - * The DG's commandHandler listens on "backend:command" channel. - */ -export async function publishCommand( - payload: Record, -): Promise { - const pub = getCommandPublisher(); - const envelope = { - type: "command", - data: payload, - timestamp: Date.now(), - source: "backend", - }; - await pub.publish("backend:command", JSON.stringify(envelope)); - logger.debug({ payload }, "Published command to DG"); -} - function handleSubscriptionMessage(channel: string, message: string): void { const mapping = SUBSCRIPTIONS.find((m) => m.channel === channel); if (!mapping) { diff --git a/services/discord-gateway/src/modules/event-broadcaster/eventBroadcaster.ts b/services/discord-gateway/src/modules/event-broadcaster/eventBroadcaster.ts index c84733e..51f44ec 100644 --- a/services/discord-gateway/src/modules/event-broadcaster/eventBroadcaster.ts +++ b/services/discord-gateway/src/modules/event-broadcaster/eventBroadcaster.ts @@ -1,12 +1,6 @@ import type { CustomLogger } from "@bete/shared/logger"; import Redis from "ioredis"; - -export interface DiscordGatewayEvent { - type: string; - data: unknown; - timestamp: number; - source: string; -} +import { type DiscordGatewayEvent, EventChannels } from "./eventTypes.js"; export class RedisEventPublisher { private redis: Redis; @@ -49,7 +43,7 @@ export class EventBroadcaster { } async messageCreated(data: unknown): Promise { - await this.publisher.publish("discord:message:created", { + await this.publisher.publish(EventChannels.MESSAGE_CREATED, { type: "message_created", data, timestamp: Date.now(), @@ -58,7 +52,7 @@ export class EventBroadcaster { } async messageUpdated(data: unknown): Promise { - await this.publisher.publish("discord:message:updated", { + await this.publisher.publish(EventChannels.MESSAGE_UPDATED, { type: "message_updated", data, timestamp: Date.now(), @@ -67,7 +61,7 @@ export class EventBroadcaster { } async messageDeleted(data: unknown): Promise { - await this.publisher.publish("discord:message:deleted", { + await this.publisher.publish(EventChannels.MESSAGE_DELETED, { type: "message_deleted", data, timestamp: Date.now(), @@ -76,7 +70,7 @@ export class EventBroadcaster { } async messageAnalyzed(data: unknown): Promise { - await this.publisher.publish("discord:message:analyzed", { + await this.publisher.publish(EventChannels.MESSAGE_ANALYZED, { type: "message_analyzed", data, timestamp: Date.now(), @@ -85,7 +79,7 @@ export class EventBroadcaster { } async attachmentCreated(data: unknown): Promise { - await this.publisher.publish("discord:attachment:created", { + await this.publisher.publish(EventChannels.ATTACHMENT_CREATED, { type: "attachment_created", data, timestamp: Date.now(), @@ -94,7 +88,7 @@ export class EventBroadcaster { } async attachmentUploaded(data: unknown): Promise { - await this.publisher.publish("discord:attachment:uploaded", { + await this.publisher.publish(EventChannels.ATTACHMENT_UPLOADED, { type: "attachment_uploaded", data, timestamp: Date.now(), @@ -103,7 +97,7 @@ export class EventBroadcaster { } async voiceRecordingStarted(data: unknown): Promise { - await this.publisher.publish("discord:voice:started", { + await this.publisher.publish(EventChannels.VOICE_STARTED, { type: "voice_recording_started", data, timestamp: Date.now(), @@ -112,7 +106,7 @@ export class EventBroadcaster { } async voiceRecordingStopped(data: unknown): Promise { - await this.publisher.publish("discord:voice:stopped", { + await this.publisher.publish(EventChannels.VOICE_STOPPED, { type: "voice_recording_stopped", data, timestamp: Date.now(), @@ -121,7 +115,7 @@ export class EventBroadcaster { } async voiceRecordingUploaded(data: unknown): Promise { - await this.publisher.publish("discord:voice:uploaded", { + await this.publisher.publish(EventChannels.VOICE_UPLOADED, { type: "voice_recording_uploaded", data, timestamp: Date.now(), @@ -140,7 +134,7 @@ export class EventBroadcaster { userId: string, metadata?: Record, ): Promise { - await this.publisher.publish("discord:voice:pcm", { + await this.publisher.publish(EventChannels.VOICE_PCM, { type: "voice_pcm_data", data: { userId, @@ -161,7 +155,7 @@ export class EventBroadcaster { userId: string, data: { username: string; avatar: string; speaking: boolean }, ): Promise { - await this.publisher.publish("discord:voice:active_user", { + await this.publisher.publish(EventChannels.VOICE_ACTIVE_USER, { type: "voice_active_user", data: { userId, @@ -173,7 +167,7 @@ export class EventBroadcaster { } async analysisQueueStatus(data: unknown): Promise { - await this.publisher.publish("discord:analysis:queue_status", { + await this.publisher.publish(EventChannels.ANALYSIS_QUEUE_STATUS, { type: "analysis_queue_status", data, timestamp: Date.now(), diff --git a/services/frontend/src/shared/ws/socket.ts b/services/frontend/src/shared/ws/socket.ts index f1bff69..fade1ea 100644 --- a/services/frontend/src/shared/ws/socket.ts +++ b/services/frontend/src/shared/ws/socket.ts @@ -152,12 +152,12 @@ export function useDashboardSocket(handlers: WsHandlers) { onUserState: (u) => handlersRef.current.onUserState?.(u), onUiState: (s) => handlersRef.current.onUiState?.(s), onMediaState: (s) => handlersRef.current.onMediaState?.(s), - onVoiceRecordingUploaded: (d) => - handlersRef.current.onVoiceRecordingUploaded?.(d), onVoiceRecordingStarted: (d) => handlersRef.current.onVoiceRecordingStarted?.(d), onVoiceRecordingStopped: (d) => handlersRef.current.onVoiceRecordingStopped?.(d), + onVoiceRecordingUploaded: (d) => + handlersRef.current.onVoiceRecordingUploaded?.(d), onVoicePcmData: (d) => handlersRef.current.onVoicePcmData?.(d), onVoiceActiveUser: (d) => handlersRef.current.onVoiceActiveUser?.(d), };