added a help system, replaced the words "tenant" and "Pwa" with better alternatives. corrected and implemented cron jobs. prepared going live on a coolify-powered system.

This commit is contained in:
Codex Agent
2025-11-10 16:23:09 +01:00
parent ba9e64dfcb
commit 447a90a742
123 changed files with 6398 additions and 153 deletions

View File

@@ -0,0 +1,106 @@
<?php
namespace App\Console\Commands;
use App\Console\Concerns\InteractsWithCacheLocks;
use App\Jobs\ArchiveEventMediaAssets;
use App\Models\Event;
use Illuminate\Console\Command;
use Illuminate\Contracts\Cache\Lock;
use Illuminate\Support\Facades\Log;
class DispatchStorageArchiveCommand extends Command
{
use InteractsWithCacheLocks;
protected $signature = 'storage:archive-pending
{--event= : Limit processing to a specific event ID}
{--force : Run even if another dispatcher instance is active}';
protected $description = 'Queue archive jobs for events whose galleries expired or were manually archived.';
public function handle(): int
{
$lockSeconds = (int) config('storage-monitor.archive.lock_seconds', 1800);
$lock = $this->acquireCommandLock('storage:archive-dispatcher', $lockSeconds, (bool) $this->option('force'));
if ($lock === false) {
$this->warn('Another archive dispatcher run is already executing.');
return self::SUCCESS;
}
$eventLockTtl = (int) config('storage-monitor.archive.event_lock_seconds', 3600);
$graceDays = max(0, (int) config('storage-monitor.archive.grace_days', 3));
$cutoff = now()->subDays($graceDays);
$chunkSize = max(1, (int) config('storage-monitor.archive.chunk', 25));
$maxDispatch = max(1, (int) config('storage-monitor.archive.max_dispatch', 100));
$eventId = $this->option('event');
$dispatched = 0;
try {
$query = Event::query()
->with('eventPackages:id,event_id,gallery_expires_at')
->whereHas('mediaAssets', function ($builder) {
$builder->where('status', '!=', 'archived');
});
if ($eventId) {
$query->whereKey($eventId);
} else {
$query->where(function ($builder) use ($cutoff) {
$builder->where('status', 'archived')
->orWhereHas('eventPackages', function ($packages) use ($cutoff) {
$packages->whereNotNull('gallery_expires_at')
->where('gallery_expires_at', '<=', $cutoff);
});
});
}
$query->chunkById($chunkSize, function ($events) use (&$dispatched, $maxDispatch, $eventLockTtl) {
foreach ($events as $event) {
if ($dispatched >= $maxDispatch) {
return false;
}
$eventLock = $this->acquireCommandLock('storage:archive-event-'.$event->id, $eventLockTtl);
if ($eventLock === false) {
Log::channel('storage-jobs')->info('Archive dispatch skipped due to in-flight lock', [
'event_id' => $event->id,
]);
continue;
}
try {
ArchiveEventMediaAssets::dispatch($event->id);
$dispatched++;
Log::channel('storage-jobs')->info('Archive job dispatched', [
'event_id' => $event->id,
'queue' => 'media-storage',
]);
} finally {
if ($eventLock instanceof Lock) {
$eventLock->release();
}
}
}
return null;
});
$this->info(sprintf('Dispatched %d archive job(s).', $dispatched));
Log::channel('storage-jobs')->info('Archive dispatch run finished', [
'dispatched' => $dispatched,
'event_limit' => $eventId,
]);
return self::SUCCESS;
} finally {
if ($lock instanceof Lock) {
$lock->release();
}
}
}
}