validate([ 'token' => ['required', 'string'], 'file' => ['required', 'file', 'mimes:jpeg,png,gif,bmp,webp', 'max:10240'], 'filename' => ['nullable', 'string'], ]); $gallery = $this->resolveGalleryByToken($request->string('token')); if (! $gallery) { return response()->json(['error' => 'Invalid token.'], Response::HTTP_FORBIDDEN); } if (! $gallery->upload_enabled) { return response()->json(['error' => 'Uploads are disabled for this gallery.'], Response::HTTP_FORBIDDEN); } if ($gallery->upload_token_expires_at && now()->greaterThanOrEqualTo($gallery->upload_token_expires_at)) { return response()->json(['error' => 'Upload token expired.'], Response::HTTP_FORBIDDEN); } $file = $request->file('file'); $safeName = $this->buildFilename($file->getClientOriginalExtension(), $request->input('filename')); $relativePath = trim($gallery->images_path, '/').'/'.$safeName; $destinationPath = public_path('storage/'.dirname($relativePath)); if (! File::exists($destinationPath)) { File::makeDirectory($destinationPath, 0755, true); } $file->move($destinationPath, basename($relativePath)); $image = Image::create([ 'gallery_id' => $gallery->id, 'path' => $relativePath, 'is_public' => true, ]); return response()->json([ 'message' => 'Upload ok', 'image_id' => $image->id, 'url' => asset('storage/'.$relativePath), ]); } private function resolveGalleryByToken(string $token): ?Gallery { $galleries = Gallery::query() ->whereNotNull('upload_token_hash') ->where('upload_enabled', true) ->get(); foreach ($galleries as $gallery) { if ($gallery->upload_token_hash && Hash::check($token, $gallery->upload_token_hash)) { return $gallery; } } return null; } private function buildFilename(string $extension, ?string $preferred = null): string { $extension = strtolower($extension ?: 'jpg'); $base = $preferred ? Str::slug(pathinfo($preferred, PATHINFO_FILENAME)) : 'sparkbooth_'.now()->format('Ymd_His').'_'.Str::random(6); return $base.'.'.$extension; } }