import { lootdrops, userTimers, userQuests } from "@/db/schema"; import { eq, and, lt, isNull, sql } from "drizzle-orm"; import { DrizzleClient } from "@/lib/DrizzleClient"; import { AuroraClient } from "@/lib/BotClient"; import { env } from "@/lib/env"; import { config } from "@/lib/config"; import { TimerType } from "@/lib/constants"; export const cleanupService = { /** * Runs all cleanup tasks */ runAll: async () => { console.log("๐Ÿงน Starting system cleanup..."); const stats = { lootdrops: 0, timers: 0, quests: 0 }; try { stats.lootdrops = await cleanupService.cleanupLootdrops(); stats.timers = await cleanupService.cleanupTimers(); stats.quests = await cleanupService.cleanupQuests(); console.log(`โœ… Cleanup finished. Stats: - Lootdrops: ${stats.lootdrops} removed - Timers: ${stats.timers} removed - Quests: ${stats.quests} cleaned`); } catch (error) { console.error("โŒ Error during cleanup:", error); } }, /** * Deletes unclaimed expired lootdrops */ cleanupLootdrops: async (): Promise => { const now = new Date(); const result = await DrizzleClient.delete(lootdrops) .where(and( lt(lootdrops.expiresAt, now), isNull(lootdrops.claimedBy) )) .returning({ id: lootdrops.messageId }); return result.length; }, /** * Cleans up expired user timers and handles side effects (like role removal) */ cleanupTimers: async (): Promise => { const now = new Date(); let deletedCount = 0; // 1. Specific handling for ACCESS timers (revoking roles etc) // This is migrated from scheduler.ts const expiredAccess = await DrizzleClient.query.userTimers.findMany({ where: and( eq(userTimers.type, TimerType.ACCESS), lt(userTimers.expiresAt, now) ) }); for (const timer of expiredAccess) { const meta = timer.metadata as any; const userIdStr = timer.userId.toString(); if (timer.key.startsWith('role_')) { try { const roleId = meta?.roleId || timer.key.replace('role_', ''); const guildId = env.DISCORD_GUILD_ID; if (guildId) { const guild = await AuroraClient.guilds.fetch(guildId); const member = await guild.members.fetch(userIdStr); await member.roles.remove(roleId); console.log(`๐Ÿ‘‹ Removed temporary role ${roleId} from ${member.user.tag}`); } } catch (err) { console.error(`Failed to remove role for user ${userIdStr}:`, err); } } // Delete specifically this one await DrizzleClient.delete(userTimers) .where(and( eq(userTimers.userId, timer.userId), eq(userTimers.type, timer.type), eq(userTimers.key, timer.key) )); deletedCount++; } // 2. Bulk delete all other expired timers (COOLDOWN, EFFECT, etc) const result = await DrizzleClient.delete(userTimers) .where(lt(userTimers.expiresAt, now)) .returning({ userId: userTimers.userId }); deletedCount += result.length; return deletedCount; }, /** * Deletes or archives old completed quests */ cleanupQuests: async (): Promise => { const archiveDays = config.system.cleanup.questArchiveDays; const threshold = new Date(); threshold.setDate(threshold.getDate() - archiveDays); const result = await DrizzleClient.delete(userQuests) .where(lt(userQuests.completedAt, threshold)) .returning({ userId: userQuests.userId }); return result.length; } };