lthn.io/app/Core/Cdn/Console/CdnPurge.php
Claude 41a90cbff8
feat: lthn.io API serving live chain data
Fixed: basePath self→static binding, namespace detection, event wiring,
SQLite cache, file cache driver. All Mod Boot classes converted to
$listens pattern for lifecycle event discovery.

Working endpoints:
- /v1/explorer/info — live chain height, difficulty, aliases
- /v1/explorer/stats — formatted chain statistics
- /v1/names/directory — alias directory grouped by type
- /v1/names/available/{name} — name availability check
- /v1/names/lookup/{name} — name details

Co-Authored-By: Charon <charon@lethean.io>
2026-04-03 17:17:42 +01:00

324 lines
8.7 KiB
PHP

<?php
/*
* Core PHP Framework
*
* Licensed under the European Union Public Licence (EUPL) v1.2.
* See LICENSE file for details.
*/
declare(strict_types=1);
namespace Core\Cdn\Console;
use Core\Plug\Cdn\Bunny\Purge;
use Core\Tenant\Models\Workspace;
use Illuminate\Console\Command;
class CdnPurge extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'cdn:purge
{workspace? : Workspace slug or "all" to purge all workspaces}
{--url=* : Specific URL(s) to purge}
{--tag= : Purge by cache tag}
{--everything : Purge entire CDN cache (use with caution)}
{--dry-run : Show what would be purged without making changes}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Purge content from CDN edge cache';
/**
* Purger instance (Core\Plug\Cdn\Bunny\Purge when available).
*/
protected ?object $purger = null;
public function __construct()
{
parent::__construct();
if (class_exists(Purge::class)) {
$this->purger = new Purge;
}
}
/**
* Execute the console command.
*/
public function handle(): int
{
if ($this->purger === null) {
$this->error('CDN Purge requires Core\Plug\Cdn\Bunny\Purge class. Plug module not installed.');
return self::FAILURE;
}
$workspaceArg = $this->argument('workspace');
$urls = $this->option('url');
$tag = $this->option('tag');
$everything = $this->option('everything');
$dryRun = $this->option('dry-run');
if ($dryRun) {
$this->info('Dry run mode - no changes will be made');
$this->newLine();
}
// Check for mutually exclusive options
$optionCount = ($everything ? 1 : 0) + (! empty($urls) ? 1 : 0) + (! empty($tag) ? 1 : 0) + (! empty($workspaceArg) ? 1 : 0);
if ($optionCount > 1 && $everything) {
$this->error('Cannot use --everything with other options');
return self::FAILURE;
}
// Purge everything
if ($everything) {
return $this->purgeEverything($dryRun);
}
// Purge specific URLs
if (! empty($urls)) {
return $this->purgeUrls($urls, $dryRun);
}
// Purge by tag
if (! empty($tag)) {
return $this->purgeByTag($tag, $dryRun);
}
// Purge by workspace
if (empty($workspaceArg)) {
$workspaceOptions = ['all', 'Select specific URLs'];
if (class_exists(Workspace::class)) {
$workspaceOptions = array_merge($workspaceOptions, Workspace::pluck('slug')->toArray());
}
$workspaceArg = $this->choice(
'What would you like to purge?',
$workspaceOptions,
0
);
if ($workspaceArg === 'Select specific URLs') {
$urlInput = $this->ask('Enter URL(s) to purge (comma-separated)');
$urls = array_map('trim', explode(',', $urlInput));
return $this->purgeUrls($urls, $dryRun);
}
}
if ($workspaceArg === 'all') {
return $this->purgeAllWorkspaces($dryRun);
}
return $this->purgeWorkspace($workspaceArg, $dryRun);
}
protected function purgeEverything(bool $dryRun): int
{
if (! $dryRun && ! $this->confirm('Are you sure you want to purge the ENTIRE CDN cache? This will affect all content.', false)) {
$this->info('Aborted');
return self::SUCCESS;
}
$this->warn('Purging entire CDN cache...');
if ($dryRun) {
$this->info('Would purge: entire CDN cache');
return self::SUCCESS;
}
try {
$result = $this->purger->all();
if ($result->isOk()) {
$this->info('CDN cache purged successfully');
return self::SUCCESS;
}
$this->error('Failed to purge CDN cache: '.$result->message());
return self::FAILURE;
} catch (\Exception $e) {
$this->error("Purge failed: {$e->getMessage()}");
return self::FAILURE;
}
}
protected function purgeUrls(array $urls, bool $dryRun): int
{
$this->info('Purging '.count($urls).' URL(s)...');
foreach ($urls as $url) {
$this->line(" - {$url}");
}
if ($dryRun) {
return self::SUCCESS;
}
try {
$result = $this->purger->urls($urls);
if ($result->isOk()) {
$this->newLine();
$this->info('URLs purged successfully');
return self::SUCCESS;
}
$this->error('Failed to purge URLs: '.$result->message());
return self::FAILURE;
} catch (\Exception $e) {
$this->error("Purge failed: {$e->getMessage()}");
return self::FAILURE;
}
}
protected function purgeByTag(string $tag, bool $dryRun): int
{
$this->info("Purging cache tag: {$tag}");
if ($dryRun) {
$this->info("Would purge: all content with tag '{$tag}'");
return self::SUCCESS;
}
try {
$result = $this->purger->tag($tag);
if ($result->isOk()) {
$this->info('Cache tag purged successfully');
return self::SUCCESS;
}
$this->error('Failed to purge cache tag: '.$result->message());
return self::FAILURE;
} catch (\Exception $e) {
$this->error("Purge failed: {$e->getMessage()}");
return self::FAILURE;
}
}
protected function purgeAllWorkspaces(bool $dryRun): int
{
if (! class_exists(Workspace::class)) {
$this->error('Workspace purge requires Tenant module to be installed.');
return self::FAILURE;
}
$workspaces = Workspace::all();
if ($workspaces->isEmpty()) {
$this->error('No workspaces found');
return self::FAILURE;
}
$this->info("Purging {$workspaces->count()} workspaces...");
$this->newLine();
$success = true;
foreach ($workspaces as $workspace) {
$this->line("Workspace: <info>{$workspace->slug}</info>");
if ($dryRun) {
$this->line(" Would purge: workspace-{$workspace->uuid}");
continue;
}
try {
$result = $this->purger->workspace($workspace->uuid);
if ($result->isOk()) {
$this->line(' <fg=green>Purged</>');
} else {
$this->line(' <fg=red>Failed: '.$result->message().'</>');
$success = false;
}
} catch (\Exception $e) {
$this->line(" <fg=red>Error: {$e->getMessage()}</>");
$success = false;
}
}
$this->newLine();
if ($success) {
$this->info('All workspaces purged successfully');
return self::SUCCESS;
}
$this->warn('Some workspaces failed to purge');
return self::FAILURE;
}
protected function purgeWorkspace(string $slug, bool $dryRun): int
{
if (! class_exists(Workspace::class)) {
$this->error('Workspace purge requires Tenant module to be installed.');
return self::FAILURE;
}
$workspace = Workspace::where('slug', $slug)->first();
if (! $workspace) {
$this->error("Workspace not found: {$slug}");
$this->newLine();
$this->info('Available workspaces:');
Workspace::pluck('slug')->each(fn ($s) => $this->line(" - {$s}"));
return self::FAILURE;
}
$this->info("Purging workspace: {$workspace->slug}");
if ($dryRun) {
$this->line("Would purge: workspace-{$workspace->uuid}");
return self::SUCCESS;
}
try {
$result = $this->purger->workspace($workspace->uuid);
if ($result->isOk()) {
$this->info('Workspace cache purged successfully');
return self::SUCCESS;
}
$this->error('Failed to purge workspace cache: '.$result->message());
return self::FAILURE;
} catch (\Exception $e) {
$this->error("Purge failed: {$e->getMessage()}");
return self::FAILURE;
}
}
}