Implement academy analytics, billing, and web stories updates

This commit is contained in:
2026-05-26 07:27:29 +02:00
parent 456c3d6bb0
commit 0b33a1b074
177 changed files with 27360 additions and 2685 deletions

View File

@@ -5,8 +5,10 @@ declare(strict_types=1);
namespace App\Console\Commands;
use App\Models\Artwork;
use App\Services\Sitemaps\SitemapReleaseManager;
use App\Services\Vision\ArtworkVisionImageUrl;
use Illuminate\Console\Command;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
@@ -25,10 +27,10 @@ use Throwable;
class HealthCheckCommand extends Command
{
protected $signature = 'health:check
{--only= : Run only a named check (mysql|redis|cache|meilisearch|qdrant|reverb|vision|horizon|webserver|phpfpm|paths|ram|disk|load|s3|failed_jobs|queue_backlog|ssl|scheduler|log_errors|app)}
{--only= : Run only a named check (mysql|redis|cache|meilisearch|qdrant|reverb|vision|horizon|webserver|phpfpm|paths|ram|disk|load|s3|failed_jobs|queue_backlog|ssl|scheduler|sitemap|log_errors|app)}
{--json : Output results as JSON}';
protected $description = 'Check health of all critical services (MySQL, Redis, Cache, Meilisearch, Qdrant, Reverb, Vision, Horizon, Nginx, PHP-FPM, writable paths, RAM, disk, load, S3/Contabo, failed jobs, queue backlog, SSL, scheduler, log errors, App).';
protected $description = 'Check health of all critical services (MySQL, Redis, Cache, Meilisearch, Qdrant, Reverb, Vision, Horizon, Nginx, PHP-FPM, writable paths, RAM, disk, load, S3/Contabo, failed jobs, queue backlog, SSL, scheduler, sitemap, log errors, App).';
/** Collected results: [name => [status, message, details]] */
private array $results = [];
@@ -57,6 +59,7 @@ class HealthCheckCommand extends Command
'queue_backlog' => fn () => $this->checkQueueBacklog(),
'ssl' => fn () => $this->checkSsl(),
'scheduler' => fn () => $this->checkScheduler(),
'sitemap' => fn () => $this->checkSitemap(),
'log_errors' => fn () => $this->checkLogErrors(),
'app' => fn () => $this->checkApp(),
];
@@ -1041,6 +1044,51 @@ class HealthCheckCommand extends Command
}
}
private function checkSitemap(): void
{
try {
$releases = app(SitemapReleaseManager::class)->listReleases();
if ($releases === []) {
$this->failCheck('sitemap', 'No sitemap releases found. Run `php artisan skinbase:sitemaps:publish` to build one.');
return;
}
$latest = $releases[0];
$releaseId = (string) ($latest['release_id'] ?? 'unknown');
$builtAtRaw = (string) ($latest['built_at'] ?? $latest['published_at'] ?? '');
if ($builtAtRaw === '') {
$this->warn_check('sitemap', "Latest sitemap release [{$releaseId}] is missing a build timestamp.", [
'release_id' => $releaseId,
'status' => (string) ($latest['status'] ?? 'unknown'),
]);
return;
}
$builtAt = Carbon::parse($builtAtRaw);
$ageSeconds = max(0, $builtAt->diffInSeconds(now()));
$builtAtLabel = $builtAt->toAtomString();
$details = [
'release_id' => $releaseId,
'built_at' => $builtAtLabel,
'age_seconds' => $ageSeconds,
'status' => (string) ($latest['status'] ?? 'unknown'),
];
$message = "Latest sitemap release [{$releaseId}] built at {$builtAtLabel} ({$ageSeconds}s ago).";
if ($ageSeconds > 72 * 3600) {
$this->failCheck('sitemap', 'Sitemap build is stale — ' . $message, $details);
} elseif ($ageSeconds > 36 * 3600) {
$this->warn_check('sitemap', 'Sitemap build is getting old — ' . $message, $details);
} else {
$this->pass('sitemap', $message, $details);
}
} catch (Throwable $e) {
$this->warn_check('sitemap', 'Could not inspect sitemap releases: ' . $e->getMessage());
}
}
private function checkLogErrors(): void
{
$logFile = storage_path('logs/laravel.log');