From 5518e674c0845614ee57af97eaf8f1cee1339c2d Mon Sep 17 00:00:00 2001 From: "Ronald A. Richardson" Date: Tue, 6 Oct 2026 13:06:09 +0800 Subject: [PATCH] fix(reviews): upload review photos without a public ACL The S3 media bucket enforces bucket-owner object ownership and rejects PUTs that carry an ACL, so put(..., 'public') returned false and no photo was stored. --- .../src/Http/Controllers/v1/ReviewController.php | 4 ++-- .../ReviewAndOrderControllerContractsTest.php | 14 +++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/server/src/Http/Controllers/v1/ReviewController.php b/server/src/Http/Controllers/v1/ReviewController.php index dd51ba78..7a8ce79a 100644 --- a/server/src/Http/Controllers/v1/ReviewController.php +++ b/server/src/Http/Controllers/v1/ReviewController.php @@ -258,8 +258,8 @@ public function create(CreateReviewRequest $request) $extension = File::getExtensionFromMimeType($mimeType); $bucketPath = 'hyperstore/' . $about->public_id . '/review-photos/' . $review->uuid . '/' . File::randomFileName($extension); - // upload file to path - $upload = Storage::disk($disk)->put($bucketPath, base64_decode($data), 'public'); + // upload file to path; no 'public' ACL, the S3 media bucket is private and rejects ACLs + $upload = Storage::disk($disk)->put($bucketPath, base64_decode($data)); // create the file $uploadedFiles->push(File::create([ diff --git a/server/tests/Unit/Http/Controllers/ReviewAndOrderControllerContractsTest.php b/server/tests/Unit/Http/Controllers/ReviewAndOrderControllerContractsTest.php index 14c86195..611b28c8 100644 --- a/server/tests/Unit/Http/Controllers/ReviewAndOrderControllerContractsTest.php +++ b/server/tests/Unit/Http/Controllers/ReviewAndOrderControllerContractsTest.php @@ -774,17 +774,22 @@ function createReviewControllerSchema(): void 'uuid' => 'owned_review_uuid', 'public_id' => 'review_owned', ]); - Illuminate\Support\Facades\Storage::swap(new class { + $reviewStorage = new class { public function disk(string $disk): self { return $this; } - public function put(string $path, string $contents, string $visibility): bool + public array $writes = []; + + public function put(string $path, string $contents, mixed $options = []): bool { + $this->writes[] = [$path, $options]; + return true; } - }); + }; + Illuminate\Support\Facades\Storage::swap($reviewStorage); session(['storefront_key' => 'store_key']); $withPhoto = $controller->create( Fleetbase\Storefront\Http\Requests\CreateReviewRequest::create('/reviews', 'POST', [ @@ -820,6 +825,9 @@ public function put(string $path, string $contents, string $visibility): bool ->and($photo->bucket)->toBe('review-bucket') ->and($photo->file_size)->toBe(strlen('image-bytes')) ->and($photo->type)->toBe('storefront_review_upload') + // review photos are written without a 'public' visibility/ACL + ->and($reviewStorage->writes)->toHaveCount(1) + ->and($reviewStorage->writes[0][1])->toBe([]) ->and($deleted->resource->uuid)->toBe('owned_review_uuid') ->and($connection->table('reviews')->where('uuid', 'owned_review_uuid')->value('deleted_at'))->not->toBeNull(); });