253 lines
8.8 KiB
PHP
253 lines
8.8 KiB
PHP
<?php
|
|
|
|
use Illuminate\Foundation\Inspiring;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\Schedule;
|
|
use App\Uploads\Services\CleanupService;
|
|
|
|
Artisan::command('inspire', function () {
|
|
$this->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');
|
|
Schedule::command('analytics:aggregate-discovery-feedback')->dailyAt('03:25');
|
|
|
|
// 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();
|
|
|
|
// ── Similar Artworks (Hybrid Recommender) ──────────────────────────────────────
|
|
// Build co-occurrence pairs from favourites every 4 hours.
|
|
Schedule::job(new \App\Jobs\RecBuildItemPairsFromFavouritesJob())
|
|
->everyFourHours()
|
|
->name('rec-build-item-pairs')
|
|
->withoutOverlapping();
|
|
|
|
// Nightly: recompute tag, behavior, and hybrid similarity lists.
|
|
Schedule::job(new \App\Jobs\RecComputeSimilarByTagsJob())
|
|
->dailyAt('02:00')
|
|
->name('rec-compute-tags')
|
|
->withoutOverlapping();
|
|
|
|
Schedule::job(new \App\Jobs\RecComputeSimilarByBehaviorJob())
|
|
->dailyAt('02:15')
|
|
->name('rec-compute-behavior')
|
|
->withoutOverlapping();
|
|
|
|
Schedule::job(new \App\Jobs\RecComputeSimilarHybridJob())
|
|
->dailyAt('02:30')
|
|
->name('rec-compute-hybrid')
|
|
->withoutOverlapping();
|
|
|
|
// ── Feed 2.0: Scheduled Posts ─────────────────────────────────────────────────
|
|
// Publish queued posts every minute.
|
|
Schedule::command('posts:publish-scheduled')
|
|
->everyMinute()
|
|
->name('publish-scheduled-posts')
|
|
->withoutOverlapping();
|
|
|
|
// ── Scheduled content publishing ──────────────────────────────────────────────
|
|
// These must live in routes/console.php for Laravel 11's active scheduler.
|
|
Schedule::command('artworks:publish-scheduled')
|
|
->everyMinute()
|
|
->name('publish-scheduled-artworks')
|
|
->withoutOverlapping(2)
|
|
->runInBackground();
|
|
|
|
Schedule::command('news:publish-scheduled')
|
|
->everyMinute()
|
|
->name('publish-scheduled-news')
|
|
->withoutOverlapping(2)
|
|
->runInBackground();
|
|
|
|
Schedule::command('nova-cards:publish-scheduled')
|
|
->everyMinute()
|
|
->name('publish-scheduled-nova-cards')
|
|
->withoutOverlapping(2)
|
|
->runInBackground();
|
|
|
|
Schedule::command('collections:sync-lifecycle')
|
|
->everyTenMinutes()
|
|
->name('sync-collection-lifecycle')
|
|
->withoutOverlapping()
|
|
->runInBackground();
|
|
|
|
Schedule::command('homepage:warm-guest-cache')
|
|
->everyTenMinutes()
|
|
->name('warm-homepage-guest-cache')
|
|
->withoutOverlapping()
|
|
->runInBackground();
|
|
|
|
// ── Feed 2.0: Trending Cache Warm-up ─────────────────────────────────────────
|
|
// Warm the post trending cache every 2 minutes (complements the 2-min TTL).
|
|
Schedule::command('posts:warm-trending')
|
|
->everyTwoMinutes()
|
|
->name('warm-post-trending')
|
|
->withoutOverlapping();
|
|
|
|
// ── Ranking Engine V2 ──────────────────────────────────────────────────────────
|
|
// Recalculate ranking_score + engagement_velocity every 30 minutes.
|
|
// Also syncs V2 scores to rank_artwork_scores so list builds benefit.
|
|
Schedule::command('nova:recalculate-rankings --sync-rank-scores')
|
|
->everyThirtyMinutes()
|
|
->name('ranking-v2')
|
|
->withoutOverlapping()
|
|
->runInBackground();
|
|
|
|
// ── Rising Engine (Heat / Momentum) ───────────────────────────────────────────
|
|
// Snapshot current totals each hour, then recalculate heat every 15 minutes.
|
|
Schedule::command('nova:metrics-snapshot-hourly')
|
|
->hourly()
|
|
->name('metrics-snapshot-hourly')
|
|
->withoutOverlapping()
|
|
->runInBackground();
|
|
|
|
Schedule::command('nova:recalculate-heat')
|
|
->everyFifteenMinutes()
|
|
->name('recalculate-heat')
|
|
->withoutOverlapping()
|
|
->runInBackground();
|
|
|
|
// Additional production schedules that must live here because Laravel 11's
|
|
// active scheduler in this app is defined in routes/console.php, not Kernel.
|
|
Schedule::command('skinbase:sitemaps:publish --sync')
|
|
->everySixHours()
|
|
->name('sitemaps-publish')
|
|
->withoutOverlapping()
|
|
->runInBackground();
|
|
|
|
Schedule::command('skinbase:sitemaps:validate')
|
|
->dailyAt('04:45')
|
|
->name('sitemaps-validate')
|
|
->withoutOverlapping()
|
|
->runInBackground();
|
|
|
|
Schedule::job(new \App\Jobs\Sitemaps\CleanupSitemapReleasesJob())
|
|
->dailyAt('05:00')
|
|
->name('sitemaps-cleanup')
|
|
->withoutOverlapping();
|
|
|
|
Schedule::command('collections:dispatch-maintenance')
|
|
->hourly()
|
|
->name('dispatch-collection-maintenance')
|
|
->withoutOverlapping()
|
|
->runInBackground();
|
|
|
|
Schedule::job(new \App\Jobs\RankComputeArtworkScoresJob())
|
|
->hourlyAt(5)
|
|
->name('rank-compute-artwork-scores');
|
|
|
|
Schedule::job(new \App\Jobs\RankBuildListsJob())
|
|
->hourlyAt(15)
|
|
->name('rank-build-lists')
|
|
->withoutOverlapping();
|
|
|
|
Schedule::job(new \App\Jobs\UpdateLeaderboardsJob())
|
|
->hourlyAt(20)
|
|
->name('leaderboards-refresh')
|
|
->withoutOverlapping();
|
|
|
|
Schedule::job(new \App\Jobs\RebuildTrendingNovaCardsJob())
|
|
->hourlyAt(25)
|
|
->name('nova-cards-trending-refresh')
|
|
->withoutOverlapping();
|
|
|
|
Schedule::job(new \App\Jobs\RecalculateRisingNovaCardsJob())
|
|
->everyFifteenMinutes()
|
|
->name('nova-cards-rising-cache-refresh')
|
|
->withoutOverlapping();
|
|
|
|
Schedule::command('nova:prune-metric-snapshots --keep-days=7')
|
|
->dailyAt('04:00')
|
|
->name('prune-metric-snapshots')
|
|
->withoutOverlapping();
|
|
|
|
Schedule::command('skinbase:sync-countries')
|
|
->monthlyOn(1, '03:40')
|
|
->name('sync-countries')
|
|
->withoutOverlapping()
|
|
->runInBackground();
|
|
|
|
Schedule::command('health:tick')
|
|
->everyMinute()
|
|
->name('health-scheduler-tick')
|
|
->withoutOverlapping();
|
|
|
|
Schedule::command('forum:ai-scan')
|
|
->everyTenMinutes()
|
|
->name('forum-ai-scan')
|
|
->withoutOverlapping()
|
|
->runInBackground();
|
|
|
|
Schedule::command('forum:bot-scan')
|
|
->everyFiveMinutes()
|
|
->name('forum-bot-scan')
|
|
->withoutOverlapping()
|
|
->runInBackground();
|
|
|
|
Schedule::command('forum:scan-posts --limit=250')
|
|
->everyFifteenMinutes()
|
|
->name('forum-post-scan')
|
|
->withoutOverlapping()
|
|
->runInBackground();
|
|
|
|
Schedule::command('forum:firewall-scan')
|
|
->everyFiveMinutes()
|
|
->name('forum-firewall-scan')
|
|
->withoutOverlapping()
|
|
->runInBackground();
|
|
|
|
Schedule::command('horizon:snapshot')
|
|
->everyFiveMinutes()
|
|
->name('horizon-snapshot')
|
|
->withoutOverlapping();
|