onQueue('media-storage'); } public function handle(EventStorageManager $storageManager): void { $event = Event::with('storageAssignments.storageTarget')->find($this->eventId); if (! $event) { Log::warning('Archive job aborted: event missing', ['event_id' => $this->eventId]); return; } $archiveDisk = $storageManager->getArchiveDiskForEvent($event); if (! $archiveDisk) { Log::warning('Archive job aborted: no archive disk configured', ['event_id' => $event->id]); return; } $archiveAssignment = $storageManager->ensureAssignment($event, null, 'archive'); $archiveTargetId = $archiveAssignment->media_storage_target_id; $assets = EventMediaAsset::where('event_id', $event->id) ->whereIn('status', ['hot', 'pending', 'restoring']) ->orderBy('id') ->get(); foreach ($assets as $asset) { $sourceDisk = $asset->disk; if ($sourceDisk === $archiveDisk && $asset->status === 'archived') { continue; } $archivePath = $asset->path; $stream = null; try { $stream = Storage::disk($sourceDisk)->readStream($asset->path); if (! $stream) { throw new \RuntimeException('Source stream is null'); } Storage::disk($archiveDisk)->put($archivePath, $stream); $asset->fill([ 'disk' => $archiveDisk, 'media_storage_target_id' => $archiveTargetId, 'status' => 'archived', 'archived_at' => now(), 'error_message' => null, ])->save(); if ($this->deleteSource) { Storage::disk($sourceDisk)->delete($asset->path); } } catch (\Throwable $e) { Log::error('Failed to archive media asset', [ 'asset_id' => $asset->id, 'event_id' => $event->id, 'source_disk' => $sourceDisk, 'archive_disk' => $archiveDisk, 'error' => $e->getMessage(), ]); $asset->update([ 'status' => 'failed', 'error_message' => $e->getMessage(), ]); } finally { if (is_resource($stream)) { fclose($stream); } } } } }