From ef01a5338a2e285c3c74ca42ee7bb1d799979bad Mon Sep 17 00:00:00 2001 From: bernardhanna Date: Fri, 9 Oct 2026 14:39:19 +0100 Subject: [PATCH] Store bulk event upload temps on S3 so import works across servers. Local disk left the validated file on one web node; Import on another failed with "Uploaded file no longer available". Co-authored-by: Cursor --- .env.example | 4 ++- .../Controllers/BulkEventUploadController.php | 27 ++++++++++++++++--- config/filesystems.php | 19 ++++++++++++- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index e8bd362d1..5ddc53c95 100644 --- a/.env.example +++ b/.env.example @@ -121,7 +121,9 @@ CERTIFICATE_ADMIN_EMAILS= FILESYSTEM_CLOUD=s3 RESOURCES_BUCKET= RESOURCES_URL= -BULK_UPLOAD_TEMP_DISK=local +# Bulk event/user upload temp files — Amazon S3 so every web node/worker sees them. +# Use local only for single-machine development without AWS. +BULK_UPLOAD_TEMP_DISK=s3 RESOURCES_IMPORT_TEMP_DISK=local # --------------------------------------------------------------------------- diff --git a/app/Http/Controllers/BulkEventUploadController.php b/app/Http/Controllers/BulkEventUploadController.php index 76c4504e5..d41e72ae7 100644 --- a/app/Http/Controllers/BulkEventUploadController.php +++ b/app/Http/Controllers/BulkEventUploadController.php @@ -9,6 +9,7 @@ use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Illuminate\View\View; @@ -75,8 +76,12 @@ function ($attribute, $value, $fail) { ->withInput(); } - $tempDisk = config('filesystems.bulk_upload_temp_disk', 'local'); - $path = $file->storeAs('temp', 'bulk_events_'.time().'.'.$extension, $tempDisk); + $tempDisk = $this->tempDisk(); + $path = $file->storeAs( + 'temp/bulk-events', + 'bulk_events_'.time().'_'.Str::random(8).'.'.$extension, + ['disk' => $tempDisk, 'visibility' => 'private'] + ); $headerCheck = BulkEventUploadValidator::validateRequiredColumns($path, $tempDisk); if (isset($headerCheck['error'])) { @@ -183,8 +188,14 @@ public function import(Request $request): RedirectResponse } $path = (string) ($payload['path'] ?? ''); - $disk = (string) ($payload['disk'] ?? 'local'); + $disk = (string) ($payload['disk'] ?? $this->tempDisk()); if ($path === '' || ! Storage::disk($disk)->exists($path)) { + Log::warning('Bulk event import: temp file missing', [ + 'token' => $token, + 'disk' => $disk, + 'path' => $path, + ]); + return redirect()->route('admin.bulk-upload.index') ->withErrors(['import' => 'Uploaded file no longer available. Please upload again.']); } @@ -226,4 +237,14 @@ public function report(Request $request, ?string $token = null): View|RedirectRe return redirect()->route('admin.bulk-upload.index') ->with('info', 'Report no longer available. Run an import to see a new report.'); } + + /** + * Shared temp disk (Amazon S3). `local` is not shared across web nodes. + */ + private function tempDisk(): string + { + $disk = (string) config('filesystems.bulk_upload_temp_disk', 's3'); + + return $disk !== '' ? $disk : 's3'; + } } diff --git a/config/filesystems.php b/config/filesystems.php index 756e57d00..a7cdd2415 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -5,9 +5,26 @@ 'cloud' => env('FILESYSTEM_CLOUD', 's3'), 'resources_import_temp_disk' => env('RESOURCES_IMPORT_TEMP_DISK', 'local'), - 'bulk_upload_temp_disk' => env('BULK_UPLOAD_TEMP_DISK', 'local'), + + // Shared Amazon S3 (or FILESYSTEM_CLOUD). `local` breaks multi-server: + // validate stores on one node, import hits another → file missing. + 'bulk_upload_temp_disk' => env('BULK_UPLOAD_TEMP_DISK', 's3'), 'disks' => [ + 'local' => [ + 'driver' => 'local', + 'root' => storage_path('app'), + 'throw' => false, + ], + + 'public' => [ + 'driver' => 'local', + 'root' => storage_path('app/public'), + 'url' => env('APP_URL').'/storage', + 'visibility' => 'public', + 'throw' => false, + ], + 'latex' => [ 'driver' => 'local', 'root' => resource_path('latex'),