diff --git a/.gitignore b/.gitignore index 97c7c14..aa772aa 100644 --- a/.gitignore +++ b/.gitignore @@ -46,5 +46,4 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json src/db/data src/db/log scratchpad/ -tickets/ bot/assets/graphics/items diff --git a/bot/commands/admin/case.ts b/bot/commands/admin/case.ts index 508797e..9d6e544 100644 --- a/bot/commands/admin/case.ts +++ b/bot/commands/admin/case.ts @@ -1,6 +1,6 @@ import { createCommand } from "@shared/lib/utils"; import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js"; -import { ModerationService } from "@shared/modules/moderation/moderation.service"; +import { moderationService } from "@shared/modules/moderation/moderation.service"; import { getCaseEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view"; export const moderationCase = createCommand({ @@ -30,7 +30,7 @@ export const moderationCase = createCommand({ } // Get the case - const moderationCase = await ModerationService.getCaseById(caseId); + const moderationCase = await moderationService.getCaseById(caseId); if (!moderationCase) { await interaction.editReply({ diff --git a/bot/commands/admin/cases.ts b/bot/commands/admin/cases.ts index c9b6fc0..0eadb4d 100644 --- a/bot/commands/admin/cases.ts +++ b/bot/commands/admin/cases.ts @@ -1,6 +1,6 @@ import { createCommand } from "@shared/lib/utils"; import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js"; -import { ModerationService } from "@shared/modules/moderation/moderation.service"; +import { moderationService } from "@shared/modules/moderation/moderation.service"; import { getCasesListEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view"; export const cases = createCommand({ @@ -29,7 +29,7 @@ export const cases = createCommand({ const activeOnly = interaction.options.getBoolean("active_only") || false; // Get cases for the user - const userCases = await ModerationService.getUserCases(targetUser.id, activeOnly); + const userCases = await moderationService.getUserCases(targetUser.id, activeOnly); const title = activeOnly ? `⚠️ Active Cases for ${targetUser.username}` diff --git a/bot/commands/admin/clearwarning.ts b/bot/commands/admin/clearwarning.ts index 7da9db9..0df875b 100644 --- a/bot/commands/admin/clearwarning.ts +++ b/bot/commands/admin/clearwarning.ts @@ -1,6 +1,6 @@ import { createCommand } from "@shared/lib/utils"; import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js"; -import { ModerationService } from "@shared/modules/moderation/moderation.service"; +import { moderationService } from "@shared/modules/moderation/moderation.service"; import { getClearSuccessEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view"; export const clearwarning = createCommand({ @@ -38,7 +38,7 @@ export const clearwarning = createCommand({ } // Check if case exists and is active - const existingCase = await ModerationService.getCaseById(caseId); + const existingCase = await moderationService.getCaseById(caseId); if (!existingCase) { await interaction.editReply({ @@ -62,7 +62,7 @@ export const clearwarning = createCommand({ } // Clear the warning - await ModerationService.clearCase({ + await moderationService.clearCase({ caseId, clearedBy: interaction.user.id, clearedByName: interaction.user.username, diff --git a/bot/commands/admin/note.ts b/bot/commands/admin/note.ts index d7c3586..05622ba 100644 --- a/bot/commands/admin/note.ts +++ b/bot/commands/admin/note.ts @@ -1,6 +1,6 @@ import { createCommand } from "@shared/lib/utils"; import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js"; -import { ModerationService } from "@shared/modules/moderation/moderation.service"; +import { moderationService } from "@shared/modules/moderation/moderation.service"; import { CaseType } from "@shared/lib/constants"; import { getNoteSuccessEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view"; @@ -31,7 +31,7 @@ export const note = createCommand({ const noteText = interaction.options.getString("note", true); // Create the note case - const moderationCase = await ModerationService.createCase({ + const moderationCase = await moderationService.createCase({ type: CaseType.NOTE, userId: targetUser.id, username: targetUser.username, diff --git a/bot/commands/admin/notes.ts b/bot/commands/admin/notes.ts index a2c09e5..a46b3bb 100644 --- a/bot/commands/admin/notes.ts +++ b/bot/commands/admin/notes.ts @@ -1,6 +1,6 @@ import { createCommand } from "@shared/lib/utils"; import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js"; -import { ModerationService } from "@shared/modules/moderation/moderation.service"; +import { moderationService } from "@shared/modules/moderation/moderation.service"; import { getCasesListEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view"; export const notes = createCommand({ @@ -22,7 +22,7 @@ export const notes = createCommand({ const targetUser = interaction.options.getUser("user", true); // Get all notes for the user - const userNotes = await ModerationService.getUserNotes(targetUser.id); + const userNotes = await moderationService.getUserNotes(targetUser.id); // Display the notes await interaction.editReply({ diff --git a/bot/commands/admin/prune.ts b/bot/commands/admin/prune.ts index 091bb0e..3cc1c0e 100644 --- a/bot/commands/admin/prune.ts +++ b/bot/commands/admin/prune.ts @@ -1,7 +1,7 @@ import { createCommand } from "@shared/lib/utils"; import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags, ComponentType } from "discord.js"; import { config } from "@shared/lib/config"; -import { PruneService } from "@shared/modules/moderation/prune.service"; +import { pruneService } from "@shared/modules/moderation/prune.service"; import { getConfirmationMessage, getProgressEmbed, @@ -66,7 +66,7 @@ export const prune = createCommand({ let estimatedCount: number | undefined; if (all) { try { - estimatedCount = await PruneService.estimateMessageCount(interaction.channel!); + estimatedCount = await pruneService.estimateMessageCount(interaction.channel!); } catch { estimatedCount = undefined; } @@ -97,7 +97,7 @@ export const prune = createCommand({ }); // Execute deletion with progress callback for 'all' mode - const result = await PruneService.deleteMessages( + const result = await pruneService.deleteMessages( interaction.channel!, { amount: typeof finalAmount === 'number' ? finalAmount : undefined, @@ -129,7 +129,7 @@ export const prune = createCommand({ } } else { // No confirmation needed, proceed directly - const result = await PruneService.deleteMessages( + const result = await pruneService.deleteMessages( interaction.channel!, { amount: finalAmount as number, diff --git a/bot/commands/admin/warn.ts b/bot/commands/admin/warn.ts index aa78858..bfa1c99 100644 --- a/bot/commands/admin/warn.ts +++ b/bot/commands/admin/warn.ts @@ -1,6 +1,6 @@ import { createCommand } from "@shared/lib/utils"; import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js"; -import { ModerationService } from "@shared/modules/moderation/moderation.service"; +import { moderationService } from "@shared/modules/moderation/moderation.service"; import { getWarnSuccessEmbed, getModerationErrorEmbed, @@ -54,7 +54,7 @@ export const warn = createCommand({ const guildConfig = await getGuildConfig(interaction.guildId!); // Issue the warning via service - const { moderationCase, warningCount, autoTimeoutIssued } = await ModerationService.issueWarning({ + const { moderationCase, warningCount, autoTimeoutIssued } = await moderationService.issueWarning({ userId: targetUser.id, username: targetUser.username, moderatorId: interaction.user.id, diff --git a/bot/commands/admin/warnings.ts b/bot/commands/admin/warnings.ts index e2937cb..f8395cd 100644 --- a/bot/commands/admin/warnings.ts +++ b/bot/commands/admin/warnings.ts @@ -1,6 +1,6 @@ import { createCommand } from "@shared/lib/utils"; import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js"; -import { ModerationService } from "@shared/modules/moderation/moderation.service"; +import { moderationService } from "@shared/modules/moderation/moderation.service"; import { getWarningsEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view"; export const warnings = createCommand({ @@ -22,7 +22,7 @@ export const warnings = createCommand({ const targetUser = interaction.options.getUser("user", true); // Get active warnings for the user - const activeWarnings = await ModerationService.getUserWarnings(targetUser.id); + const activeWarnings = await moderationService.getUserWarnings(targetUser.id); // Display the warnings await interaction.editReply({ diff --git a/shared/modules/moderation/moderation.service.test.ts b/shared/modules/moderation/moderation.service.test.ts index 54daa0f..ea24b6f 100644 --- a/shared/modules/moderation/moderation.service.test.ts +++ b/shared/modules/moderation/moderation.service.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, mock, beforeEach } from "bun:test"; -import { ModerationService } from "@shared/modules/moderation/moderation.service"; +import { moderationService } from "@shared/modules/moderation/moderation.service"; import { moderationCases } from "@db/schema"; import { CaseType } from "@shared/lib/constants"; @@ -43,7 +43,7 @@ mockUpdate.mockReturnValue({ set: mockSet }); mockSet.mockReturnValue({ where: mockWhere }); mockWhere.mockReturnValue({ returning: mockReturning }); -describe("ModerationService", () => { +describe("moderationService", () => { beforeEach(() => { mockFindFirst.mockReset(); mockFindMany.mockReset(); @@ -73,7 +73,7 @@ describe("ModerationService", () => { const mockDmTarget = { send: mock() }; - const result = await ModerationService.issueWarning({ + const result = await moderationService.issueWarning({ ...defaultOptions, dmTarget: mockDmTarget }); @@ -91,7 +91,7 @@ describe("ModerationService", () => { const mockDmTarget = { send: mock() }; - await ModerationService.issueWarning({ + await moderationService.issueWarning({ ...defaultOptions, dmTarget: mockDmTarget, config: { dmOnWarn: false } @@ -108,7 +108,7 @@ describe("ModerationService", () => { const mockTimeoutTarget = { timeout: mock() }; - const result = await ModerationService.issueWarning({ + const result = await moderationService.issueWarning({ ...defaultOptions, timeoutTarget: mockTimeoutTarget, config: { autoTimeoutThreshold: 3 } @@ -128,7 +128,7 @@ describe("ModerationService", () => { const mockTimeoutTarget = { timeout: mock() }; - const result = await ModerationService.issueWarning({ + const result = await moderationService.issueWarning({ ...defaultOptions, timeoutTarget: mockTimeoutTarget }); @@ -139,27 +139,6 @@ describe("ModerationService", () => { }); }); - describe("getNextCaseId", () => { - it("should return CASE-0001 if no cases exist", async () => { - mockFindFirst.mockResolvedValue(undefined); - // Accessing private method via bracket notation for testing - const nextId = await (ModerationService as any).getNextCaseId(); - expect(nextId).toBe("CASE-0001"); - }); - - it("should increment the latest case ID", async () => { - mockFindFirst.mockResolvedValue({ caseId: "CASE-0042" }); - const nextId = await (ModerationService as any).getNextCaseId(); - expect(nextId).toBe("CASE-0043"); - }); - - it("should handle padding correctly (e.g., 9 -> 0010)", async () => { - mockFindFirst.mockResolvedValue({ caseId: "CASE-0009" }); - const nextId = await (ModerationService as any).getNextCaseId(); - expect(nextId).toBe("CASE-0010"); - }); - }); - describe("createCase", () => { it("should create a new moderation case with correct values", async () => { mockFindFirst.mockResolvedValue({ caseId: "CASE-0001" }); @@ -176,7 +155,7 @@ describe("ModerationService", () => { }; mockReturning.mockResolvedValue([mockNewCase]); - const result = await ModerationService.createCase({ + const result = await moderationService.createCase({ type: CaseType.WARN, userId: "123456789", username: "testuser", @@ -199,7 +178,7 @@ describe("ModerationService", () => { mockFindFirst.mockResolvedValue(undefined); mockReturning.mockImplementation((values) => [values]); // Simplified mock - const result = await ModerationService.createCase({ + const result = await moderationService.createCase({ type: CaseType.BAN, userId: "123456789", username: "testuser", @@ -219,7 +198,7 @@ describe("ModerationService", () => { const mockCase = { caseId: "CASE-0001", reason: "test" }; mockFindFirst.mockResolvedValue(mockCase); - const result = await ModerationService.getCaseById("CASE-0001"); + const result = await moderationService.getCaseById("CASE-0001"); expect(result).toEqual(mockCase as any); }); }); @@ -229,7 +208,7 @@ describe("ModerationService", () => { const mockCases = [{ caseId: "CASE-0001" }, { caseId: "CASE-0002" }]; mockFindMany.mockResolvedValue(mockCases); - const result = await ModerationService.getUserCases("123456789"); + const result = await moderationService.getUserCases("123456789"); expect(result).toHaveLength(2); expect(mockFindMany).toHaveBeenCalled(); }); @@ -240,7 +219,7 @@ describe("ModerationService", () => { const mockUpdatedCase = { caseId: "CASE-0001", active: false }; mockReturning.mockResolvedValue([mockUpdatedCase]); - const result = await ModerationService.clearCase({ + const result = await moderationService.clearCase({ caseId: "CASE-0001", clearedBy: "987654321", clearedByName: "mod", @@ -264,13 +243,13 @@ describe("ModerationService", () => { { id: 2n, type: CaseType.WARN, active: true } ]); - const count = await ModerationService.getActiveWarningCount("123456789"); + const count = await moderationService.getActiveWarningCount("123456789"); expect(count).toBe(2); }); it("should return 0 if no active warnings", async () => { mockFindMany.mockResolvedValue([]); - const count = await ModerationService.getActiveWarningCount("123456789"); + const count = await moderationService.getActiveWarningCount("123456789"); expect(count).toBe(0); }); }); diff --git a/shared/modules/moderation/moderation.service.ts b/shared/modules/moderation/moderation.service.ts index b5617ca..14ab89c 100644 --- a/shared/modules/moderation/moderation.service.ts +++ b/shared/modules/moderation/moderation.service.ts @@ -10,34 +10,56 @@ export interface ModerationCaseConfig { autoTimeoutThreshold?: number; } -export class ModerationService { - /** - * Generate the next sequential case ID - */ - private static async getNextCaseId(): Promise { - const latestCase = await DrizzleClient.query.moderationCases.findFirst({ - orderBy: [desc(moderationCases.id)], - }); +/** + * Generate the next sequential case ID + */ +async function getNextCaseId(): Promise { + const latestCase = await DrizzleClient.query.moderationCases.findFirst({ + orderBy: [desc(moderationCases.id)], + }); - if (!latestCase) { - return "CASE-0001"; - } - - // Extract number from case ID (e.g., "CASE-0042" -> 42) - const match = latestCase.caseId.match(/CASE-(\d+)/); - if (!match || !match[1]) { - return "CASE-0001"; - } - - const nextNumber = parseInt(match[1], 10) + 1; - return `CASE-${nextNumber.toString().padStart(4, '0')}`; + if (!latestCase) { + return "CASE-0001"; } + // Extract number from case ID (e.g., "CASE-0042" -> 42) + const match = latestCase.caseId.match(/CASE-(\d+)/); + if (!match || !match[1]) { + return "CASE-0001"; + } + + const nextNumber = parseInt(match[1], 10) + 1; + return `CASE-${nextNumber.toString().padStart(4, '0')}`; +} + +/** + * Get active warnings for a user + */ +async function getUserWarnings(userId: string) { + return await DrizzleClient.query.moderationCases.findMany({ + where: and( + eq(moderationCases.userId, BigInt(userId)), + eq(moderationCases.type, CaseType.WARN), + eq(moderationCases.active, true) + ), + orderBy: [desc(moderationCases.createdAt)], + }); +} + +/** + * Get total count of active warnings for a user (useful for auto-timeout) + */ +async function getActiveWarningCount(userId: string): Promise { + const warnings = await getUserWarnings(userId); + return warnings.length; +} + +export const moderationService = { /** * Create a new moderation case */ - static async createCase(options: CreateCaseOptions) { - const caseId = await this.getNextCaseId(); + async createCase(options: CreateCaseOptions) { + const caseId = await getNextCaseId(); const [newCase] = await DrizzleClient.insert(moderationCases).values({ caseId, @@ -52,12 +74,12 @@ export class ModerationService { }).returning(); return newCase; - } + }, /** * Issue a warning with DM and threshold logic */ - static async issueWarning(options: { + async issueWarning(options: { userId: string; username: string; moderatorId: string; @@ -81,7 +103,7 @@ export class ModerationService { throw new Error("Failed to create moderation case"); } - const warningCount = await this.getActiveWarningCount(options.userId); + const warningCount = await getActiveWarningCount(options.userId); const config = options.config ?? {}; // Try to DM the user if configured @@ -127,21 +149,21 @@ export class ModerationService { } return { moderationCase, warningCount, autoTimeoutIssued }; - } + }, /** * Get a case by its case ID */ - static async getCaseById(caseId: string) { + async getCaseById(caseId: string) { return await DrizzleClient.query.moderationCases.findFirst({ where: eq(moderationCases.caseId, caseId), }); - } + }, /** * Get all cases for a specific user */ - static async getUserCases(userId: string, activeOnly: boolean = false) { + async getUserCases(userId: string, activeOnly: boolean = false) { const conditions = [eq(moderationCases.userId, BigInt(userId))]; if (activeOnly) { @@ -152,26 +174,19 @@ export class ModerationService { where: and(...conditions), orderBy: [desc(moderationCases.createdAt)], }); - } + }, /** - * Get active warnings for a user + * Get active warnings for a user (public alias) */ - static async getUserWarnings(userId: string) { - return await DrizzleClient.query.moderationCases.findMany({ - where: and( - eq(moderationCases.userId, BigInt(userId)), - eq(moderationCases.type, CaseType.WARN), - eq(moderationCases.active, true) - ), - orderBy: [desc(moderationCases.createdAt)], - }); - } + async getUserWarnings(userId: string) { + return await getUserWarnings(userId); + }, /** * Get all notes for a user */ - static async getUserNotes(userId: string) { + async getUserNotes(userId: string) { return await DrizzleClient.query.moderationCases.findMany({ where: and( eq(moderationCases.userId, BigInt(userId)), @@ -179,12 +194,12 @@ export class ModerationService { ), orderBy: [desc(moderationCases.createdAt)], }); - } + }, /** * Clear/resolve a warning */ - static async clearCase(options: ClearCaseOptions) { + async clearCase(options: ClearCaseOptions) { const [updatedCase] = await DrizzleClient.update(moderationCases) .set({ active: false, @@ -196,12 +211,12 @@ export class ModerationService { .returning(); return updatedCase; - } + }, /** * Search cases with various filters */ - static async searchCases(filter: SearchCasesFilter) { + async searchCases(filter: SearchCasesFilter) { const conditions = []; if (filter.userId) { @@ -228,13 +243,12 @@ export class ModerationService { limit: filter.limit || 50, offset: filter.offset || 0, }); - } + }, /** * Get total count of active warnings for a user (useful for auto-timeout) */ - static async getActiveWarningCount(userId: string): Promise { - const warnings = await this.getUserWarnings(userId); - return warnings.length; - } -} + async getActiveWarningCount(userId: string): Promise { + return await getActiveWarningCount(userId); + }, +}; diff --git a/shared/modules/moderation/prune.service.ts b/shared/modules/moderation/prune.service.ts index 6d756b9..62a10ac 100644 --- a/shared/modules/moderation/prune.service.ts +++ b/shared/modules/moderation/prune.service.ts @@ -3,11 +3,73 @@ import type { TextBasedChannel } from "discord.js"; import type { PruneOptions, PruneResult, PruneProgress } from "@/modules/moderation/prune.types"; import { config } from "@shared/lib/config"; -export class PruneService { +/** + * Fetch messages from a channel + */ +async function fetchMessages( + channel: TextBasedChannel, + limit: number, + before?: string +): Promise> { + if (!('messages' in channel)) { + return new Collection(); + } + + return await channel.messages.fetch({ + limit, + before + }); +} + +/** + * Process a batch of messages for deletion + */ +async function processBatch( + channel: TextBasedChannel, + messages: Collection, + userId?: string +): Promise<{ deleted: number; skipped: number }> { + if (!('bulkDelete' in channel)) { + throw new Error("This channel type does not support bulk deletion"); + } + + // Filter by user if specified + let messagesToDelete = messages; + if (userId) { + messagesToDelete = messages.filter(msg => msg.author.id === userId); + } + + if (messagesToDelete.size === 0) { + return { deleted: 0, skipped: 0 }; + } + + try { + // bulkDelete with filterOld=true will automatically skip messages >14 days + const deleted = await channel.bulkDelete(messagesToDelete, true); + const skipped = messagesToDelete.size - deleted.size; + + return { + deleted: deleted.size, + skipped + }; + } catch (error) { + console.error("Error during bulk delete:", error); + throw new Error("Failed to delete messages"); + } +} + +/** + * Helper to delay execution + */ +function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +export const pruneService = { /** * Delete messages from a channel based on provided options */ - static async deleteMessages( + async deleteMessages( channel: TextBasedChannel, options: PruneOptions, progressCallback?: (progress: PruneProgress) => Promise @@ -38,11 +100,11 @@ export class PruneService { requestedCount = estimatedTotal; while (true) { - const messages = await this.fetchMessages(channel, batchSize, lastMessageId); + const messages = await fetchMessages(channel, batchSize, lastMessageId); if (messages.size === 0) break; - const { deleted, skipped } = await this.processBatch( + const { deleted, skipped } = await processBatch( channel, messages, userId @@ -70,15 +132,15 @@ export class PruneService { // Delay to avoid rate limits if (messages.size >= batchSize) { - await this.delay(batchDelay); + await delay(batchDelay); } } } else { // Delete specific amount const limit = Math.min(amount || 10, config.moderation.prune.maxAmount); - const messages = await this.fetchMessages(channel, limit, undefined); + const messages = await fetchMessages(channel, limit, undefined); - const { deleted, skipped } = await this.processBatch( + const { deleted, skipped } = await processBatch( channel, messages, userId @@ -106,67 +168,12 @@ export class PruneService { username, skippedOld: totalSkipped > 0 ? totalSkipped : undefined }; - } - - /** - * Fetch messages from a channel - */ - private static async fetchMessages( - channel: TextBasedChannel, - limit: number, - before?: string - ): Promise> { - if (!('messages' in channel)) { - return new Collection(); - } - - return await channel.messages.fetch({ - limit, - before - }); - } - - /** - * Process a batch of messages for deletion - */ - private static async processBatch( - channel: TextBasedChannel, - messages: Collection, - userId?: string - ): Promise<{ deleted: number; skipped: number }> { - if (!('bulkDelete' in channel)) { - throw new Error("This channel type does not support bulk deletion"); - } - - // Filter by user if specified - let messagesToDelete = messages; - if (userId) { - messagesToDelete = messages.filter(msg => msg.author.id === userId); - } - - if (messagesToDelete.size === 0) { - return { deleted: 0, skipped: 0 }; - } - - try { - // bulkDelete with filterOld=true will automatically skip messages >14 days - const deleted = await channel.bulkDelete(messagesToDelete, true); - const skipped = messagesToDelete.size - deleted.size; - - return { - deleted: deleted.size, - skipped - }; - } catch (error) { - console.error("Error during bulk delete:", error); - throw new Error("Failed to delete messages"); - } - } + }, /** * Estimate the total number of messages in a channel */ - static async estimateMessageCount(channel: TextBasedChannel): Promise { + async estimateMessageCount(channel: TextBasedChannel): Promise { if (!('messages' in channel)) { return 0; } @@ -187,12 +194,5 @@ export class PruneService { } catch { return 100; // Default estimate } - } - - /** - * Helper to delay execution - */ - private static delay(ms: number): Promise { - return new Promise(resolve => setTimeout(resolve, ms)); - } -} + }, +}; diff --git a/web/src/routes/moderation.routes.ts b/web/src/routes/moderation.routes.ts index e898b99..d8591d9 100644 --- a/web/src/routes/moderation.routes.ts +++ b/web/src/routes/moderation.routes.ts @@ -29,7 +29,7 @@ async function handler(ctx: RouteContext): Promise { return null; } - const { ModerationService } = await import("@shared/modules/moderation/moderation.service"); + const { moderationService } = await import("@shared/modules/moderation/moderation.service"); /** * @route GET /api/moderation @@ -78,7 +78,7 @@ async function handler(ctx: RouteContext): Promise { filter.limit = url.searchParams.get("limit") ? parseInt(url.searchParams.get("limit")!) : 50; filter.offset = url.searchParams.get("offset") ? parseInt(url.searchParams.get("offset")!) : 0; - const cases = await ModerationService.searchCases(filter); + const cases = await moderationService.searchCases(filter); return jsonResponse({ cases }); }, "fetch moderation cases"); } @@ -97,7 +97,7 @@ async function handler(ctx: RouteContext): Promise { const caseId = pathname.split("/").pop()!.toUpperCase(); return withErrorHandling(async () => { - const moderationCase = await ModerationService.getCaseById(caseId); + const moderationCase = await moderationService.getCaseById(caseId); if (!moderationCase) { return errorResponse("Case not found", 404); @@ -148,7 +148,7 @@ async function handler(ctx: RouteContext): Promise { ); } - const newCase = await ModerationService.createCase({ + const newCase = await moderationService.createCase({ type: data.type, userId: data.userId, username: data.username, @@ -193,7 +193,7 @@ async function handler(ctx: RouteContext): Promise { return errorResponse("Missing required fields: clearedBy, clearedByName", 400); } - const updatedCase = await ModerationService.clearCase({ + const updatedCase = await moderationService.clearCase({ caseId, clearedBy: data.clearedBy, clearedByName: data.clearedByName,