/** * Grace Period Expiration Job * Checks for expired grace periods and downgrades users to Awakened * * Philosophy: "We Don't Kick People Out" * - Expired grace periods downgrade to permanent Awakened tier * - Users keep community access, just lose premium perks * * Task #87: Arbiter Lifecycle Handlers * Date: April 6, 2026 */ const db = require('../database'); const { downgradeToAwakened } = require('../services/discordRoleSync'); /** * Process all expired grace periods * Called hourly from cron.js * * @returns {Promise<{processed: number, errors: number}>} */ async function processExpiredGracePeriods() { console.log('🔍 Checking for expired grace periods...'); const client = await db.pool.connect(); let processed = 0; let errors = 0; try { // Find all expired grace periods const { rows: expired } = await client.query(` SELECT s.discord_id, s.tier_level, u.minecraft_username FROM subscriptions s LEFT JOIN users u ON s.discord_id = u.discord_id WHERE s.status = 'grace_period' AND s.grace_period_ends_at < NOW() AND s.is_lifetime = FALSE `); if (expired.length === 0) { console.log('✅ No expired grace periods found'); return { processed: 0, errors: 0 }; } console.log(`📋 Found ${expired.length} expired grace period(s)`); for (const sub of expired) { try { await client.query('BEGIN'); // Record the tier change in history await client.query(` INSERT INTO player_history (discord_id, previous_tier, new_tier, change_reason) VALUES ($1, $2, 1, 'grace_period_expired') `, [sub.discord_id, sub.tier_level]); // Downgrade to Awakened (tier 1, lifetime) await client.query(` UPDATE subscriptions SET tier_level = 1, status = 'lifetime', is_lifetime = TRUE, mrr_value = 0, grace_period_started_at = NULL, grace_period_ends_at = NULL, payment_failure_reason = NULL, stripe_subscription_id = NULL, updated_at = CURRENT_TIMESTAMP WHERE discord_id = $1 `, [sub.discord_id]); // Log in audit await client.query(` INSERT INTO admin_audit_log (action_type, target_identifier, details) VALUES ('GRACE_PERIOD_EXPIRED', $1, $2) `, [sub.discord_id, JSON.stringify({ previous_tier: sub.tier_level, new_tier: 1, minecraft_username: sub.minecraft_username, reason: 'Automatic downgrade after grace period expiration' })]); await client.query('COMMIT'); // Sync Discord role to Awakened const syncResult = await downgradeToAwakened(sub.discord_id); if (!syncResult.success) { console.warn(`⚠️ Role sync failed for ${sub.discord_id}: ${syncResult.message}`); } console.log(`✅ Downgraded ${sub.minecraft_username || sub.discord_id} to Awakened`); processed++; } catch (error) { await client.query('ROLLBACK'); console.error(`❌ Error processing ${sub.discord_id}:`, error.message); errors++; } } } finally { client.release(); } console.log(`📊 Grace period processing complete: ${processed} processed, ${errors} errors`); return { processed, errors }; } module.exports = { processExpiredGracePeriods };