Skip to content

Commit

Permalink
60. api resource for catalog products
Browse files Browse the repository at this point in the history
  • Loading branch information
dmitrakovich committed Jan 25, 2025

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent 65f73d7 commit 5b7a40f
Showing 9 changed files with 138 additions and 24 deletions.
11 changes: 3 additions & 8 deletions src/app/Http/Controllers/Api/CatalogController.php
Original file line number Diff line number Diff line change
@@ -2,12 +2,12 @@

namespace App\Http\Controllers\Api;

use App\Enums\StockTypeEnum;
use App\Events\Analytics\ProductView;
use App\Facades\Sale;
use App\Helpers\UrlHelper;
use App\Http\Controllers\Controller;
use App\Http\Requests\FilterRequest;
use App\Http\Resources\Product\CatalogProductCollection;
use App\Http\Resources\Product\ProductResource;
use App\Models\Category;
use App\Models\Product;
@@ -20,7 +20,6 @@
use App\Services\Seo\ProductSeoService;
use App\Services\SliderService;
use Diglactic\Breadcrumbs\Breadcrumbs;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;

class CatalogController extends Controller
@@ -42,7 +41,7 @@ public function index(
UrlHelper::setCurrentFilters($currentFilters);
UrlHelper::setCurrentCity($currentCity);

$products = $catalogService->getProducts($currentFilters, $sort, $searchQuery);
$products = $catalogService->getProductsWithPagination($currentFilters, $sort, $searchQuery);

$sortingList = [
'rating' => 'по популярности',
@@ -57,7 +56,7 @@ public function index(
$gtmService->setForCatalog($products, $category, $searchQuery);

$data = [
'products' => $products,
'products' => new CatalogProductCollection($products),
'category' => $category,
'currentFilters' => $currentFilters,
'badges' => $badges,
@@ -90,10 +89,6 @@ public function show(
// ProductSeoService $seoService,
FeedbackService $feedbackService,
): array {
$product->load([
'availableSizes' => fn (Builder $query) => $query->whereRelation('stock', 'type', StockTypeEnum::SHOP),
'availableSizes.stock.city',
]);
// $productService->addToRecent($product->id);

// $seoService->setProduct($product)->generate(); // !!!
31 changes: 31 additions & 0 deletions src/app/Http/Resources/Product/CatalogProductCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace App\Http\Resources\Product;

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;

class CatalogProductCollection extends ResourceCollection
{
/**
* The resource that this resource collects.
*
* @var string
*/
public $collects = CatalogProductResource::class;

/**
* Transform the resource collection into an array.
*
* @return array<int|string, mixed>
*/
public function toArray(Request $request): array
{
return [
...$this->resource->toArray($request),
'total' => $this->resource->totalCount,
'minPrice' => $this->resource->minPrice,
'maxPrice' => $this->resource->maxPrice,
];
}
}
44 changes: 44 additions & 0 deletions src/app/Http/Resources/Product/CatalogProductResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

namespace App\Http\Resources\Product;

use App\Facades\Currency;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

/**
* @mixin \App\Models\Product
*/
class CatalogProductResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
return [
'id' => $this->id,
'slug' => $this->slug,
'sku' => $this->sku,
'url' => $this->getUrl(),
'prices' => [
'price' => $this->getFinalPrice(),
'old_price' => $this->getFinalOldPrice(),
'formatted_price' => $this->getFormattedPrice(),
'formatted_old_price' => $this->getFormattedOldPrice(),
'has_discount' => $this->hasDiscount(),
'sale_percentage' => $this->getSalePercentage(),
'sales' => $this->getSales(),
'currency' => Currency::getCurrentCurrency(),
],

'is_favorite' => $this->isFavorite(),
'is_new' => $this->isNew(),
'short_name' => $this->shortName(),

'media' => MediaResource::collection($this->getMedia()),
];
}
}
9 changes: 8 additions & 1 deletion src/app/Http/Resources/Product/ProductResource.php
Original file line number Diff line number Diff line change
@@ -2,8 +2,10 @@

namespace App\Http\Resources\Product;

use App\Enums\StockTypeEnum;
use App\Facades\Currency;
use App\Models\AvailableSizes;
use Illuminate\Contracts\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

@@ -19,11 +21,15 @@ class ProductResource extends JsonResource
*/
public function toArray(Request $request): array
{
$this->load([
'availableSizes' => fn (Builder $query) => $query->whereRelation('stock', 'type', StockTypeEnum::SHOP),
'availableSizes.stock.city',
]);

return [
'id' => $this->id,
'slug' => $this->slug,
'sku' => $this->sku,
'season_id' => $this->season_id,
'color_txt' => $this->color_txt,
'fabric_top_txt' => $this->fabric_top_txt,
'fabric_inner_txt' => $this->fabric_inner_txt,
@@ -46,6 +52,7 @@ public function toArray(Request $request): array
'currency' => Currency::getCurrentCurrency(),
],

'is_favorite' => $this->isFavorite(),
'is_installment_available' => $this->availableInstallment(),
'is_new' => $this->isNew(),
'short_name' => $this->shortName(),
8 changes: 8 additions & 0 deletions src/app/Models/Product.php
Original file line number Diff line number Diff line change
@@ -475,6 +475,14 @@ public function isNew(): bool
return $this->old_price == 0;
}

/**
* Is the product in favorite list
*/
public function isFavorite(): bool
{
return isset($this->favorite);
}

/**
* Checks that the product has only one size
*/
15 changes: 15 additions & 0 deletions src/app/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
@@ -17,7 +17,9 @@
use Illuminate\Notifications\ChannelManager;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\ServiceProvider;
use Sentry\Severity;
use Spatie\Permission\Models\Role;
@@ -70,6 +72,8 @@ public function boot(): void

// $this->modelShouldBeStrict($app->isProduction());

// $this->logQueries();

if ($app->isLocal()) {
$app['config']['filesystems.disks.public.url'] = 'https://barocco.by/media';
}
@@ -95,4 +99,15 @@ private function modelShouldBeStrict(bool $isProduction): void
// });
// }
}

private function logQueries(): void
{
DB::listen(function ($query) {
$sql = $query->sql;
$bindings = $query->bindings;
$executionTime = $query->time;

Log::debug($sql, compact('bindings', 'executionTime'));
});
}
}
34 changes: 24 additions & 10 deletions src/app/Services/CatalogService.php
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@
use App\Models\Category;
use App\Models\Product;
use App\Models\ProductAttributes\Top;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Pagination\CursorPaginator;
use Illuminate\Support\Collection;
@@ -21,15 +22,9 @@ class CatalogService
*/
protected const PAGE_SIZE = 12;

/**
* @var ProductService
*/
private $productService;

public function __construct()
{
$this->productService = new ProductService();
}
public function __construct(
private readonly ProductService $productService
) {}

/**
* @return \Illuminate\Contracts\Pagination\CursorPaginator
@@ -55,6 +50,25 @@ public function getProducts(array $filters, string $sort, ?string $search = null
return $products;
}

public function getProductsWithPagination(array $filters, string $sort, ?string $search = null): LengthAwarePaginator
{
/** @var Builder $productsQuery */
$productsQuery = $this->productService
->applyFilters($filters)
->search($search)
->sorting($sort);

$products = $productsQuery->paginate(self::PAGE_SIZE);
$this->addTopProducts($products, $filters);
$products->totalCount = $products->total() + $this->topProductsCount($products);

$this->productService->addEager($products);
$this->addMinMaxPrices($products, $productsQuery);
$this->addGtmData($products);

return $products;
}

public function getFilterBadges(array $currentFiltersGroups = [], ?string $searchQuery = null): array
{
$badges = [];
@@ -148,7 +162,7 @@ protected function topProductsCount($products): int
return $products->count() - self::PAGE_SIZE;
}

protected function addMinMaxPrices(CursorPaginator $products, Builder $productsQuery): void
protected function addMinMaxPrices(CursorPaginator|LengthAwarePaginator $products, Builder $productsQuery): void
{
$priceQuery = clone $productsQuery;
$query = $priceQuery->getQuery();
4 changes: 2 additions & 2 deletions src/app/Services/ProductService.php
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@

use App\Models\Product;
use Illuminate\Contracts\Pagination\CursorPaginator;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Facades\Session;
@@ -31,15 +32,14 @@ public function applyFilters(array $filters): Builder
/**
* Load the relationships that should be eager loaded.
*/
public function addEager(CursorPaginator|EloquentCollection $products): void
public function addEager(CursorPaginator|LengthAwarePaginator|EloquentCollection $products): void
{
$products->load([
'category:id,parent_id,title,path',
'category.parentCategory:id,parent_id,title,path',
'brand:id,name',
'sizes:id,name',
'media',
'styles:id,name',
'favorite:product_id',
]);
}
6 changes: 3 additions & 3 deletions src/stubs/model.stub
Original file line number Diff line number Diff line change
@@ -7,9 +7,9 @@ use Illuminate\Database\Eloquent\Model;
class {{ class }} extends Model
{
/**
* The attributes that aren't mass assignable.
* Indicates if all mass assignment is enabled.
*
* @var array<string>|bool
* @var bool
*/
protected $guarded = ['id'];
protected static $unguarded = true;
}

0 comments on commit 5b7a40f

Please sign in to comment.