/** * Reward Distribution Scheduler * Runs scheduled jobs for monthly bonus distribution and other rewards */ import { distributeMonthlyBonusesToAllUsers, getDistributionStats } from './tierRewardDistributionService.ts'; import { notifyOwner } from './_core/notification.ts'; // Job configuration const JOBS = { MONTHLY_BONUS: { name: 'Monthly VIP Bonus Distribution', schedule: '0 0 1 * *', // First day of every month at midnight handler: async () => { console.log('[Scheduler] Starting monthly bonus distribution job...'); try { const results = await distributeMonthlyBonusesToAllUsers(); const successful = results.filter((r) => r.status === 'success').length; console.log(`[Scheduler] Monthly bonus distribution completed: ${successful}/${results.length} successful`); return { success: true, processed: results.length }; } catch (error) { console.error('[Scheduler] Monthly bonus distribution failed:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } }, }, DAILY_STATS: { name: 'Daily Reward Distribution Stats', schedule: '0 0 * * *', // Every day at midnight handler: async () => { console.log('[Scheduler] Collecting daily reward distribution stats...'); try { const stats = await getDistributionStats(); console.log('[Scheduler] Daily stats collected:', stats); // Notify admin if significant distribution occurred if (stats.totalDistributed > 0) { await notifyOwner({ title: '📊 Daily Reward Distribution Report', content: `VIP Users: ${stats.totalVIPUsers}\nTotal Distributed: $${stats.totalDistributed}\nBy Tier:\n- Bronze: ${stats.byTier.bronze}\n- Silver: ${stats.byTier.silver}\n- Gold: ${stats.byTier.gold}\n- Platinum: ${stats.byTier.platinum}`, }); } return { success: true, stats }; } catch (error) { console.error('[Scheduler] Daily stats collection failed:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } }, }, WEEKLY_SUMMARY: { name: 'Weekly Reward Distribution Summary', schedule: '0 9 * * 1', // Every Monday at 9 AM handler: async () => { console.log('[Scheduler] Generating weekly reward distribution summary...'); try { const stats = await getDistributionStats(); const summary = `Weekly Reward Distribution Summary\n\nTotal VIP Users: ${stats.totalVIPUsers}\nTotal Distributed This Week: $${stats.totalDistributed}\n\nBreakdown by Tier:\n- Bronze: ${stats.byTier.bronze} users\n- Silver: ${stats.byTier.silver} users\n- Gold: ${stats.byTier.gold} users\n- Platinum: ${stats.byTier.platinum} users`; await notifyOwner({ title: '📈 Weekly Reward Distribution Summary', content: summary, }); console.log('[Scheduler] Weekly summary generated and sent to admin'); return { success: true }; } catch (error) { console.error('[Scheduler] Weekly summary generation failed:', error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } }, }, }; /** * Parse cron expression and determine if job should run */ function shouldRunJob(cronExpression, now = new Date()) { const [minute, hour, dayOfMonth, month, dayOfWeek] = cronExpression.split(' '); // Simple cron parser (not comprehensive, but covers common patterns) const currentMinute = now.getMinutes(); const currentHour = now.getHours(); const currentDay = now.getDate(); const currentMonth = now.getMonth() + 1; const currentDayOfWeek = now.getDay(); const matchMinute = minute === '*' || parseInt(minute) === currentMinute; const matchHour = hour === '*' || parseInt(hour) === currentHour; const matchDay = dayOfMonth === '*' || parseInt(dayOfMonth) === currentDay; const matchMonth = month === '*' || parseInt(month) === currentMonth; const matchDayOfWeek = dayOfWeek === '*' || parseInt(dayOfWeek) === currentDayOfWeek; return matchMinute && matchHour && matchDay && matchMonth && matchDayOfWeek; } /** * Initialize and run scheduler */ export async function initializeScheduler() { console.log('[Scheduler] Initializing reward distribution scheduler...'); // Run jobs immediately on startup (for testing/initialization) for (const [jobKey, job] of Object.entries(JOBS)) { console.log(`[Scheduler] Job available: ${job.name}`); } // Set up interval to check jobs every minute setInterval(async () => { const now = new Date(); for (const [jobKey, job] of Object.entries(JOBS)) { if (shouldRunJob(job.schedule, now)) { console.log(`[Scheduler] Running job: ${job.name}`); try { const result = await job.handler(); console.log(`[Scheduler] Job completed: ${job.name}`, result); } catch (error) { console.error(`[Scheduler] Job failed: ${job.name}`, error); } } } }, 60000); // Check every minute console.log('[Scheduler] Reward distribution scheduler initialized'); } /** * Manual job trigger (for admin panel) */ export async function triggerJob(jobName) { const job = Object.values(JOBS).find((j) => j.name === jobName); if (!job) { return { success: false, error: `Job not found: ${jobName}` }; } console.log(`[Scheduler] Manually triggering job: ${job.name}`); try { const result = await job.handler(); console.log(`[Scheduler] Manual job completed: ${job.name}`, result); return { success: true, result }; } catch (error) { console.error(`[Scheduler] Manual job failed: ${job.name}`, error); return { success: false, error: error instanceof Error ? error.message : 'Unknown error' }; } } /** * Get all scheduled jobs */ export function getScheduledJobs() { return Object.entries(JOBS).map(([key, job]) => ({ id: key, name: job.name, schedule: job.schedule, description: `Runs on schedule: ${job.schedule}`, })); } // Export for use in server export default { initializeScheduler, triggerJob, getScheduledJobs, JOBS, };