comment(Inspiring::quote()); })->purpose('Display an inspiring quote'); Artisan::command('uploads:cleanup {--limit=100 : Maximum drafts to clean in one run}', function (): void { $limit = (int) $this->option('limit'); $deleted = app(CleanupService::class)->cleanupStaleDrafts($limit); $this->info("Uploads cleanup deleted {$deleted} draft(s)."); })->purpose('Delete stale draft uploads and temporary files'); // ── Scheduled tasks ──────────────────────────────────────────────────────────── // Recalculate trending scores every 30 minutes (staggered: 24h first, then 7d) Schedule::command('skinbase:recalculate-trending --period=24h') ->everyThirtyMinutes() ->name('trending-24h') ->withoutOverlapping(); Schedule::command('skinbase:recalculate-trending --period=7d --skip-index') ->everyThirtyMinutes() ->name('trending-7d') ->runInBackground() ->withoutOverlapping(); // Reset windowed view/download counters so trending uses recent-activity data. // Downloads are recomputed from the artwork_downloads log (accurate). // Views are zeroed (no per-view event log) and re-accumulate from midnight. Schedule::command('skinbase:reset-windowed-stats --period=24h') ->dailyAt('03:30') ->name('reset-windowed-stats-24h') ->withoutOverlapping(); Schedule::command('skinbase:reset-windowed-stats --period=7d') ->weeklyOn(1, '03:30') // Monday 03:30 ->name('reset-windowed-stats-7d') ->withoutOverlapping(); // Daily maintenance Schedule::command('uploads:cleanup')->dailyAt('03:00'); Schedule::command('analytics:aggregate-similar-artworks')->dailyAt('03:10'); Schedule::command('analytics:aggregate-feed')->dailyAt('03:20'); // Drain Redis artwork-stat delta queue so MySQL counters stay fresh. // Run every 5 minutes with overlap protection. Schedule::command('skinbase:flush-redis-stats') ->everyFiveMinutes() ->name('flush-redis-stats') ->withoutOverlapping(); // Prune artwork_view_events rows older than 90 days. // Runs Sunday at 04:00, after all other weekly maintenance. Schedule::command('skinbase:prune-view-events --days=90') ->weekly() ->sundays() ->at('04:00') ->name('prune-view-events') ->withoutOverlapping();