diff --git a/app/Cqs/Actions/Products/Create/CreateProductInput.php b/app/Cqs/Actions/Products/Create/CreateProductInput.php index e2b59f19..399a9b51 100644 --- a/app/Cqs/Actions/Products/Create/CreateProductInput.php +++ b/app/Cqs/Actions/Products/Create/CreateProductInput.php @@ -20,7 +20,7 @@ final readonly class CreateProductInput implements ActionInput * @param BreedingMethod[] $breedingMethods * @param Business[] $businesses * @param ProductFormat[] $formats - * @param array{unit: NutritionnalUnit, value: string}[] $nutritionalValues + * @param array $nutritionalValues */ public function __construct( public string $name, diff --git a/app/Cqs/Actions/Products/Update/UpdateProductInput.php b/app/Cqs/Actions/Products/Update/UpdateProductInput.php index 34747a36..f7c68968 100644 --- a/app/Cqs/Actions/Products/Update/UpdateProductInput.php +++ b/app/Cqs/Actions/Products/Update/UpdateProductInput.php @@ -21,7 +21,7 @@ final readonly class UpdateProductInput implements ActionInput * @param BreedingMethod[] $breedingMethods * @param Business[] $businesses * @param ProductFormat[] $formats - * @param array{unit: NutritionnalUnit, value: string}[] $nutritionalValues + * @param array $nutritionalValues */ public function __construct( public ProductId $id, diff --git a/app/Http/Controllers/Back/ProductController.php b/app/Http/Controllers/Back/ProductController.php index 67170fc7..1c254e61 100644 --- a/app/Http/Controllers/Back/ProductController.php +++ b/app/Http/Controllers/Back/ProductController.php @@ -123,18 +123,21 @@ class ProductController extends BackendController UpdateProductRequest $request, Product $product, UpdateProduct $updateProduct, - ): ProductDto { + ): RedirectResponse { $this->authorize('update', $product); $result = $this->executor->execute($updateProduct, $request->toUpdateProductInput($product)); - if (! $result instanceof ProductDto) { - abort(500, trans('uwil-backend::crud.messages.cannot_be_updated', [ - 'model' => Str::ucfirst(trans_choice('models.classes.product', 1)), - ])); - } - - return $result; + return match (true) { + $result instanceof ProductDto => redirect()->route('back.products.edit', $product) + ->with('success', trans('uwil-backend::crud.messages.has_been_updated', [ + 'model' => Str::ucfirst(trans_choice('models.classes.product', 1)), + ])), + default => redirect()->route('back.products.edit', $product) + ->with('error', trans('uwil-backend::crud.messages.cannot_be_updated', [ + 'model' => Str::ucfirst(trans_choice('models.classes.product', 1)), + ])), + }; } public function duplicate(Product $product): Response diff --git a/app/Http/Controllers/Back/RecipeController.php b/app/Http/Controllers/Back/RecipeController.php index fe1bd7c6..5047ea64 100644 --- a/app/Http/Controllers/Back/RecipeController.php +++ b/app/Http/Controllers/Back/RecipeController.php @@ -128,18 +128,21 @@ class RecipeController extends BackendController UpdateRecipeRequest $request, Recipe $recipe, UpdateRecipe $updateRecipe, - ): RecipeDto { + ): RedirectResponse { $this->authorize('update', $recipe); $result = $this->executor->execute($updateRecipe, $request->toUpdateRecipeInput($recipe)); - if (! $result instanceof RecipeDto) { - abort(500, trans('uwil-backend::crud.messages.cannot_be_updated', [ - 'model' => Str::ucfirst(trans_choice('models.classes.recipe', 1)), - ])); - } - - return $result; + return match (true) { + $result instanceof RecipeDto => redirect()->route('back.recipes.edit', $recipe) + ->with('success', trans('uwil-backend::crud.messages.has_been_updated', [ + 'model' => Str::ucfirst(trans_choice('models.classes.recipe', 1)), + ])), + default => redirect()->route('back.recipes.edit', $recipe) + ->with('error', trans('uwil-backend::crud.messages.cannot_be_updated', [ + 'model' => Str::ucfirst(trans_choice('models.classes.recipe', 1)), + ])), + }; } public function duplicate(Recipe $recipe, GetProductsSummariesQuery $getProductsSummaries): Response diff --git a/app/Http/Middlewares/SecureHeaders.php b/app/Http/Middlewares/SecureHeaders.php index dbee66da..dda21ae0 100644 --- a/app/Http/Middlewares/SecureHeaders.php +++ b/app/Http/Middlewares/SecureHeaders.php @@ -4,12 +4,11 @@ declare(strict_types=1); namespace App\Http\Middlewares; -use App\Lib\PublicS3Disk; -use Closure; use Illuminate\Contracts\Container\Container; use Illuminate\Http\Request; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Vite; +use Illuminate\Support\Str; use Symfony\Component\HttpFoundation\Response; class SecureHeaders @@ -36,7 +35,7 @@ class SecureHeaders * * @param \Closure(\Illuminate\Http\Request):(\Illuminate\Http\Response) $next */ - public function handle(Request $request, Closure $next): Response + public function handle(Request $request, \Closure $next): Response { $appUrl = Config::string('app.url'); $appDebug = Config::boolean('app.debug'); @@ -67,7 +66,8 @@ class SecureHeaders $response->headers->set('X-Frame-Options', 'SAMEORIGIN'); $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload'); - $s3PublicEndpoint = $this->container->make(PublicS3Disk::class)->urlEndpoint; + $s3PublicEndpoint = Str::of(Config::string('filesystems.disks.s3.url')) + ->chopEnd('/')->append('/')->value(); // * Ignore CSP on some conditions. if ( diff --git a/app/Http/Requests/Back/Products/StoreProductRequest.php b/app/Http/Requests/Back/Products/StoreProductRequest.php index 0a25199c..f5ecf591 100644 --- a/app/Http/Requests/Back/Products/StoreProductRequest.php +++ b/app/Http/Requests/Back/Products/StoreProductRequest.php @@ -47,6 +47,20 @@ class StoreProductRequest extends FormRequest public function toCreateProductInput(): CreateProductInput { + $picture = null; + if ($this->hasFile('picture')) { + $picture = $this->storeFile($this->file('picture')); + } elseif ($this->filled('picture')) { + $picture = new StorageFilePath($this->string('picture')->value()); + } + + $pdf = null; + if ($this->hasFile('pdf')) { + $pdf = $this->storeFile($this->file('pdf')); + } elseif ($this->filled('pdf')) { + $pdf = new StorageFilePath($this->string('pdf')->value()); + } + return new CreateProductInput( name: $this->string('name')->trim()->value(), seoTitle: $this->string('seo_title')->trim()->value(), @@ -79,21 +93,11 @@ class StoreProductRequest extends FormRequest conservationBeforeOpening: $this->string('conservation_before_opening')->trim()->value(), conservationAfterOpening: $this->filled('conservation_after_opening') ? $this->string('conservation_after_opening')->trim()->value() : null, - picture: $this->hasFile('picture') ? $this->storeFile($this->file('picture')) : null, + picture: $picture, pictureAlt: $this->string('picture_alt')->trim()->value(), pictureTitle: $this->string('picture_title')->trim()->value(), - pdf: $this->hasFile('pdf') ? $this->storeFile($this->file('pdf')) : null, - nutritionalValues: array_map( - function ($value) { - $value = $this->mixedToArray($value); - - return [ - 'unit' => NutritionnalUnit::from(Arr::string($value, 'unit')), - 'value' => Arr::string($value, 'value'), - ]; - }, - $this->array('nutritional_values') - ), + pdf: $pdf, + nutritionalValues: $this->getNutritionalValues(), ); } @@ -102,7 +106,7 @@ class StoreProductRequest extends FormRequest */ protected function prepareForValidation(): void { - $this->merge([ + $merges = [ 'name' => $this->string('name')->trim()->value(), 'seo_title' => $this->string('seo_title')->trim()->value(), 'seo_description' => $this->string('seo_description')->trim()->value(), @@ -110,7 +114,27 @@ class StoreProductRequest extends FormRequest 'picture_title' => $this->string('picture_title')->trim()->value(), 'conservation_before_opening' => $this->string('conservation_before_opening')->trim()->value(), 'conservation_after_opening' => $this->string('conservation_after_opening')->trim()->value() ?: null, - ]); + ]; + + if ($this->has('nutritional_values') && is_array($this->input('nutritional_values'))) { + $nutritionalValues = $this->array('nutritional_values'); + foreach ($nutritionalValues as $key => $item) { + $value = (is_array($item) && isset($item['value']) && is_numeric($item['value'])) ? + $this->mixedToString($item['value']) : null; + + if ($value !== null) { + $nutValue = when( + is_array($item) && isset($item['value']) && is_numeric($item['value']), + fn () => $this->mixedToString($item['value']), + fn () => null + ); + $nutritionalValues[$key] = $nutValue; + } + } + $merges['nutritional_values'] = $nutritionalValues; + } + + $this->merge($merges); } /** @@ -225,13 +249,64 @@ class StoreProductRequest extends FormRequest return (string) $value; } - /** @return array */ - protected function mixedToArray(mixed $value): array + /** + * @return array + */ + public function getNutritionalValues(): array { - if (! is_array($value)) { - throw new \InvalidArgumentException('Value must be an array to convert to array.'); + $raw = $this->input('nutritional_values'); + + if (! is_array($raw) || $raw === []) { + return []; } - return $value; + $result = []; + + foreach ($raw as $key => $item) { + if (! is_array($item)) { + continue; + } + + $rawUnit = Arr::get($item, 'unit'); + $rawValue = Arr::get($item, 'value'); + + if ($rawUnit === null || $rawValue === null) { + continue; + } + + $unit = $rawUnit instanceof NutritionnalUnit + ? $rawUnit + : NutritionnalUnit::tryFrom($this->mixedToString($rawUnit)); + + $value = mb_trim($this->mixedToString($rawValue)); + + if ($unit === null || $value === '') { + continue; + } + + $result[(string) $key] = [ + 'unit' => $unit, + 'value' => $value, + ]; + } + + $nutrients = array_filter( + $result, + fn (string $k) => $k !== 'refWeightG', + ARRAY_FILTER_USE_KEY + ); + + if (empty($nutrients)) { + return []; + } + + if (! isset($result['refWeightG'])) { + $result['refWeightG'] = [ + 'unit' => NutritionnalUnit::GRAM, + 'value' => '100', + ]; + } + + return $result; } } diff --git a/app/Http/Requests/Back/Products/UpdateProductRequest.php b/app/Http/Requests/Back/Products/UpdateProductRequest.php index 29f83b01..fbf31784 100644 --- a/app/Http/Requests/Back/Products/UpdateProductRequest.php +++ b/app/Http/Requests/Back/Products/UpdateProductRequest.php @@ -8,11 +8,9 @@ use App\Cqs\Actions\Products\Update\UpdateProductInput; use App\Models\Product; use Domain\Models\ValueObjects\Products\BreedingMethod; use Domain\Models\ValueObjects\Products\Business; -use Domain\Models\ValueObjects\Products\NutritionnalUnit; use Domain\Models\ValueObjects\Products\ProductCategory; use Domain\Models\ValueObjects\Products\ProductFormat; use Domain\Models\ValueObjects\Products\Technology; -use Illuminate\Support\Arr; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; use Uwil\Storage\Lib\ValueObjects\StorageFilePath; @@ -81,17 +79,7 @@ class UpdateProductRequest extends StoreProductRequest pictureAlt: $this->string('picture_alt')->trim()->value(), pictureTitle: $this->string('picture_title')->trim()->value(), pdf: $pdf, - nutritionalValues: array_map( - function ($value) { - $value = $this->mixedToArray($value); - - return [ - 'unit' => NutritionnalUnit::from(Arr::string($value, 'unit')), - 'value' => Arr::string($value, 'value'), - ]; - }, - $this->array('nutritional_values') - ), + nutritionalValues: $this->getNutritionalValues(), ); }