refactor: convert ModerationService and PruneService from classes to singleton objects

- Convert ModerationService class to moderationService singleton
- Convert PruneService class to pruneService singleton
- Update all command files to use new singleton imports
- Update web routes to use new singleton imports
- Update tests for singleton pattern
- Remove getNextCaseId from tests (now private module function)
This commit is contained in:
syntaxbullet
2026-02-13 13:33:58 +01:00
parent 55d2376ca1
commit 099601ce6d
13 changed files with 176 additions and 184 deletions

View File

@@ -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<Collection<string, Message>> {
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<string, Message>,
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<void> {
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<void>
@@ -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<Collection<string, Message>> {
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<string, Message>,
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<number> {
async estimateMessageCount(channel: TextBasedChannel): Promise<number> {
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<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
},
};