From b2207a622c9b9d765e97a8a901f1d9d2a5973437 Mon Sep 17 00:00:00 2001 From: Konstantinos Arvanitakis Date: Thu, 3 Sep 2026 11:47:38 +0300 Subject: [PATCH] Feat: Updating COntrollers to remove business logic moved to boboko/core --- app/Catalog/CategoryListing.php | 12 +++---- app/Catalog/ProductCard.php | 29 ++++++++++++++++ app/Http/Controllers/CategoryController.php | 38 +++++++-------------- app/Http/Controllers/HomeController.php | 17 ++++----- app/Http/Controllers/ProductController.php | 9 +---- docker/entrypoint.sh | 1 + 6 files changed, 56 insertions(+), 50 deletions(-) create mode 100644 app/Catalog/ProductCard.php diff --git a/app/Catalog/CategoryListing.php b/app/Catalog/CategoryListing.php index 8282a53..5315dd8 100644 --- a/app/Catalog/CategoryListing.php +++ b/app/Catalog/CategoryListing.php @@ -19,8 +19,8 @@ final class CategoryListing { private function __construct( public readonly ?ProductSort $sort, - public readonly ?int $minPrice, - public readonly ?int $maxPrice, + public readonly ?float $minPrice, + public readonly ?float $maxPrice, public readonly bool $inStockOnly, public readonly int $page, ) {} @@ -29,8 +29,8 @@ public static function fromRequest(Request $request): self { return new self( sort: ProductSort::tryFrom((string) $request->query('sort')), - minPrice: self::intOrNull($request->query('price_min')), - maxPrice: self::intOrNull($request->query('price_max')), + minPrice: self::floatOrNull($request->query('price_min')), + maxPrice: self::floatOrNull($request->query('price_max')), inStockOnly: $request->boolean('in_stock'), page: max(1, (int) $request->query('page', 1)), ); @@ -79,8 +79,8 @@ public function isRefined(): bool || $this->page > 1; } - private static function intOrNull(mixed $value): ?int + private static function floatOrNull(mixed $value): ?float { - return is_numeric($value) ? (int) $value : null; + return is_numeric($value) ? (float) $value : null; } } diff --git a/app/Catalog/ProductCard.php b/app/Catalog/ProductCard.php new file mode 100644 index 0000000..371df75 --- /dev/null +++ b/app/Catalog/ProductCard.php @@ -0,0 +1,29 @@ + $product['name'], + 'price' => $product['price'], + 'image' => $product['media'][0]['url'] ?? null, + 'href' => route('product.show', ['id' => $product['id']]), + ]; + } +} diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php index a6f2050..acd3fa8 100644 --- a/app/Http/Controllers/CategoryController.php +++ b/app/Http/Controllers/CategoryController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\Catalog\CategoryListing; +use App\Catalog\ProductCard; use Illuminate\Http\Response; use Modules\Core\Catalog\Services\CollectionService; use Modules\Core\Catalog\Services\ProductService; @@ -23,42 +24,29 @@ public function show(string $locale, int $collection) $filters = $listing->filters($collectionData['id']); $perPage = 12; - // Listing/filtering reads from the Meilisearch index via ProductService, - // not Eloquent — see Modules\Core\Catalog\Services\ProductService. list() returns a - // real LengthAwarePaginator of plain arrays (already localized/flattened), - // not Product models. Sort/filter/page all come from the query string via - // CategoryListing, which is the single source of truth for that state. - $products = $this->products->list( + // One call for both the product page and the price slider's bounds — + // see ProductService::list()'s own docblock for why a controller no + // longer orchestrates list() + priceSliderBounds() itself. + $listingResult = $this->products->list( filters: $filters, perPage: $perPage, page: $listing->page, sort: $listing->sort, - )->through(fn (array $product) => [ - 'name' => $product['name'], - 'price' => $product['price'], - 'image' => $product['media'][0]['url'] ?? null, - 'href' => route('product.show', ['id' => $product['id']]), - ])->appends($listing->query(['page' => null])); + ); - // Slider bounds — the price span of everything matching the *other* - // filters (priceRange() drops the price filter itself, so the handles - // don't collapse to whatever's already selected). Whole euros. - $priceRange = $this->products->priceRange($filters); - $priceFloor = $priceRange['min'] !== null ? (int) floor($priceRange['min']) : null; - $priceCeil = $priceRange['max'] !== null ? (int) ceil($priceRange['max']) : null; + $products = $listingResult->products + ->through(fn (array $product) => ProductCard::fromIndexed($product)) + ->appends($listing->query(['page' => null])); - // A price param is only a real filter if it's tighter than the bounds — - // drives whether the "clear" link shows. - $priceFiltered = ($listing->minPrice !== null && $listing->minPrice > ($priceFloor ?? PHP_INT_MIN)) - || ($listing->maxPrice !== null && $listing->maxPrice < ($priceCeil ?? PHP_INT_MAX)); + $priceBounds = $listingResult->priceBounds; return view('category.show', [ 'collection' => $collectionData, 'products' => $products, 'listing' => $listing, - 'priceFloor' => $priceFloor, - 'priceCeil' => $priceCeil, - 'priceFiltered' => $priceFiltered, + 'priceFloor' => $priceBounds->floor, + 'priceCeil' => $priceBounds->ceil, + 'priceFiltered' => $priceBounds->filtered, ]); } } diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 5717aec..9493531 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -2,11 +2,14 @@ namespace App\Http\Controllers; +use App\Catalog\ProductCard; use App\Models\StoicPage; -use Lunar\Models\Product; +use Modules\Core\Catalog\Services\ProductService; class HomeController extends Controller { + public function __construct(private readonly ProductService $products) {} + public function index(string $locale) { $page = StoicPage::firstWhere('slug', 'home'); @@ -15,16 +18,8 @@ public function index(string $locale) abort(404); } - $products = Product::with(['variants.prices.currency', 'media']) - ->inRandomOrder() - ->limit(13) - ->get() - ->map(fn (Product $product) => [ - 'name' => $product->translateAttribute('name'), - 'price' => $product->variants->first()?->prices->first()?->price->decimal, - 'image' => $product->media->first()?->getUrl(), - 'href' => route('product.show', ['id' => $product->id]), - ]); + $products = collect($this->products->random(13)) + ->map(fn (array $product) => ProductCard::fromIndexed($product)); return view('home', [ 'page' => $page, diff --git a/app/Http/Controllers/ProductController.php b/app/Http/Controllers/ProductController.php index b468bf9..66e88a2 100644 --- a/app/Http/Controllers/ProductController.php +++ b/app/Http/Controllers/ProductController.php @@ -16,14 +16,7 @@ public function show(string $locale, int $id) $collection = $product['collections'][0] ?? null; - $variantsData = collect($product['variants']) - ->map(fn (array $variant) => [ - 'id' => $variant['id'], - 'price' => $variant['prices'][0]['price'] ?? null, - 'image' => $variant['media'][0]['url'] ?? null, - ]) - ->values() - ->all(); + $variantsData = $this->products->variantSummaries($product); $firstVariant = $product['variants'][0] ?? null; $option = $firstVariant['options'][0]['option'] ?? null; diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index e5cb57f..a19cc7f 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -86,6 +86,7 @@ php artisan lunar:install --quiet || true # just shipped, same reasoning as optimize:clear above. echo "[entrypoint] Syncing search indexes..." php artisan lunar:meilisearch:setup +php artisan lunar:meilisearch:tune-product-search --quiet || true php artisan lunar:search:index --quiet || true if [ "$APP_ENV" = "production" ]; then