Feat: Displaying Draft Products, only when AppDebug = true

This commit is contained in:
2026-09-22 13:46:26 +03:00
parent 0437057e5d
commit cc1cf6ea7f
2 changed files with 36 additions and 5 deletions
+5 -2
View File
@@ -254,7 +254,10 @@ class ProductService
public function random(int $limit): array public function random(int $limit): array
{ {
$raw = Product::search('') $raw = Product::search('')
->options(['attributesToRetrieve' => ['id']]) ->options([
'attributesToRetrieve' => ['id'],
'filter' => $this->filterBuilder->withVisibility(),
])
->raw(); ->raw();
$ids = collect($raw['hits'] ?? [])->pluck('id')->shuffle()->take($limit)->values(); $ids = collect($raw['hits'] ?? [])->pluck('id')->shuffle()->take($limit)->values();
@@ -287,7 +290,7 @@ class ProductService
private function findAllWhere(string $filter, int $limit = 1000): array private function findAllWhere(string $filter, int $limit = 1000): array
{ {
$paginator = Product::search('') $paginator = Product::search('')
->options(['filter' => $filter]) ->options(['filter' => $this->filterBuilder->withVisibility($filter)])
->paginateRaw(perPage: $limit, page: 1); ->paginateRaw(perPage: $limit, page: 1);
return collect($this->localizer->hitsFrom($paginator)) return collect($this->localizer->hitsFrom($paginator))
+31 -3
View File
@@ -10,6 +10,13 @@ use Modules\Core\Catalog\DTOs\ProductFilters;
* out of ProductService (where it originated, scoped to browsing/filtering * out of ProductService (where it originated, scoped to browsing/filtering
* without a search term) so ProductSearchService can apply the exact same * without a search term) so ProductSearchService can apply the exact same
* filter semantics to a text query too, rather than reimplementing it. * filter semantics to a text query too, rather than reimplementing it.
*
* Also the single place that composes the draft-visibility clause (see
* withVisibility()) — every Meilisearch `filter` string ProductService
* constructs, including the handful of ad-hoc ones that don't call build()
* at all (getById()/getBySlug()'s id lookup, random()'s id-only fetch),
* goes through this class so none of them can silently omit it the way a
* status filter was missing everywhere until now.
*/ */
class ProductFilterBuilder class ProductFilterBuilder
{ {
@@ -19,10 +26,10 @@ class ProductFilterBuilder
* ProductService::priceRange() excludes 'price' so a price slider's own * ProductService::priceRange() excludes 'price' so a price slider's own
* bounds don't shrink to whatever range is already selected on it. * bounds don't shrink to whatever range is already selected on it.
*/ */
public function build(?ProductFilters $filters, array $exclude = []): ?string public function build(?ProductFilters $filters, array $exclude = []): string
{ {
if ($filters === null) { if ($filters === null) {
return null; return $this->withVisibility();
} }
$clauses = Collection::make([ $clauses = Collection::make([
@@ -36,6 +43,27 @@ class ProductFilterBuilder
'inStockOnly' => $filters->inStockOnly ? 'in_stock = true' : null, 'inStockOnly' => $filters->inStockOnly ? 'in_stock = true' : null,
])->except($exclude)->filter(); ])->except($exclude)->filter();
return $clauses->isEmpty() ? null : $clauses->join(' AND '); return $this->withVisibility($clauses->isEmpty() ? null : $clauses->join(' AND '));
}
/**
* A draft product (status = 'draft', see Lunar\Filament\Resources\
* ProductResource's own status Select) is only ever visible while
* APP_DEBUG is true — a merchant/developer previewing an unfinished
* product locally or on a staging box, never a real storefront
* visitor. Every ProductService method that builds a Meilisearch
* `filter` string, build() included, calls this rather than passing
* $rawClause straight to Product::search() — the one seam that
* guarantees none of them can omit the visibility rule.
*
* Always returns a non-empty string (never null) — a bare
* 'status = "published"' is itself a complete, valid Meilisearch
* filter on its own when $rawClause is null.
*/
public function withVisibility(?string $rawClause = null): string
{
$visibility = config('app.debug') ? null : 'status = "published"';
return Collection::make([$visibility, $rawClause])->filter()->join(' AND ');
} }
} }