import { userTimers } from "@db/schema"; import { eq, and, lt } from "drizzle-orm"; import { DrizzleClient } from "@shared/db/DrizzleClient"; import { AuroraClient } from "@/lib/BotClient"; import { env } from "@shared/lib/env"; import { TimerType } from "@shared/lib/constants"; export const temporaryRoleService = { /** * Checks for and revokes expired temporary roles. * This is intended to run as a high-frequency maintenance task. */ processExpiredRoles: async (): Promise => { const now = new Date(); let revokedCount = 0; // Find all expired ACCESS (temporary role) timers const expiredTimers = await DrizzleClient.query.userTimers.findMany({ where: and( eq(userTimers.type, TimerType.ACCESS), lt(userTimers.expiresAt, now) ) }); if (expiredTimers.length === 0) return 0; for (const timer of expiredTimers) { const userIdStr = timer.userId.toString(); const meta = timer.metadata as any; // We only handle keys that indicate role management 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).catch(() => null); if (member) { await member.roles.remove(roleId); console.log(`👋 Temporary role ${roleId} revoked from ${member.user.tag} (Expired)`); } else { console.log(`⚠️ Could not find member ${userIdStr} to revoke role ${roleId}.`); } } } catch (err) { console.error(`❌ Failed to revoke role for user ${userIdStr}:`, err); } } // Always delete the timer record after trying to revoke (or if it's not a role key) // to prevent repeated failed attempts. await DrizzleClient.delete(userTimers) .where(and( eq(userTimers.userId, timer.userId), eq(userTimers.type, timer.type), eq(userTimers.key, timer.key) )); revokedCount++; } return revokedCount; } };