optimizations

This commit is contained in:
2026-03-28 19:15:39 +01:00
parent 0b25d9570a
commit cab4fbd83e
509 changed files with 1016804 additions and 1605 deletions

View File

@@ -0,0 +1,235 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Models\Category;
use App\Models\Collection;
use App\Models\User;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;
class CollectionSearchService
{
public function publicSearch(array $filters, int $perPage = 18): LengthAwarePaginator
{
$query = Collection::query()
->publicEligible()
->with(['user:id,username,name', 'coverArtwork:id,user_id,title,slug,hash,thumb_ext,published_at,is_public,is_approved,deleted_at']);
$this->applySharedFilters($query, $filters, false);
$this->applyPublicSort($query, (string) ($filters['sort'] ?? 'trending'));
return $query->paginate(max(1, min($perPage, 24)))->withQueryString();
}
public function ownerSearch(User $user, array $filters, int $perPage = 20): LengthAwarePaginator
{
$query = Collection::query()
->with(['user:id,username,name', 'coverArtwork:id,user_id,title,slug,hash,thumb_ext,published_at,is_public,is_approved,deleted_at'])
->when(! $user->isAdmin() && ! $user->isModerator(), fn ($builder) => $builder->where('user_id', $user->id));
$this->applySharedFilters($query, $filters, true);
return $query
->orderByDesc('updated_at')
->paginate(max(1, min($perPage, 50)))
->withQueryString();
}
public function publicFilterOptions(): array
{
$themeOptions = Collection::query()
->publicEligible()
->whereNotNull('theme_token')
->where('theme_token', '!=', '')
->select('theme_token')
->distinct()
->orderBy('theme_token')
->limit(12)
->pluck('theme_token')
->map(fn ($token): array => [
'value' => (string) $token,
'label' => $this->humanizeToken((string) $token),
])
->values()
->all();
$categoryOptions = Category::query()
->active()
->orderBy('sort_order')
->orderBy('name')
->limit(16)
->get(['slug', 'name'])
->map(fn (Category $category): array => [
'value' => (string) $category->slug,
'label' => (string) $category->name,
])
->values()
->all();
$styleOptions = collect((array) config('collections.smart_rules.style_terms', []))
->map(fn ($term): array => [
'value' => (string) $term,
'label' => $this->humanizeToken((string) $term),
])
->values()
->all();
$colorOptions = collect((array) config('collections.smart_rules.color_terms', []))
->map(fn ($term): array => [
'value' => (string) $term,
'label' => $this->humanizeToken((string) $term),
])
->values()
->all();
return [
'category' => $categoryOptions,
'style' => $styleOptions,
'theme' => $themeOptions,
'color' => $colorOptions,
'quality_tier' => [
['value' => 'editorial', 'label' => 'Editorial'],
['value' => 'high', 'label' => 'High'],
['value' => 'standard', 'label' => 'Standard'],
['value' => 'limited', 'label' => 'Limited'],
],
];
}
private function applySharedFilters($query, array $filters, bool $includeInternal): void
{
if (filled($filters['q'] ?? null)) {
$term = '%' . trim((string) $filters['q']) . '%';
$query->where(function ($builder) use ($term): void {
$builder->where('title', 'like', $term)
->orWhere('summary', 'like', $term)
->orWhere('description', 'like', $term)
->orWhere('campaign_label', 'like', $term)
->orWhere('series_title', 'like', $term)
->orWhereHas('user', function (Builder $userQuery) use ($term): void {
$userQuery->where('username', 'like', $term)
->orWhere('name', 'like', $term);
});
});
}
foreach (['type', 'visibility', 'lifecycle_state', 'mode', 'campaign_key', 'program_key', 'workflow_state', 'health_state'] as $field) {
if (filled($filters[$field] ?? null)) {
$query->where($field, (string) $filters[$field]);
}
}
if (filled($filters['quality_tier'] ?? null)) {
$query->where('trust_tier', (string) $filters['quality_tier']);
}
if (filled($filters['theme'] ?? null)) {
$theme = trim((string) $filters['theme']);
$themeLike = '%' . mb_strtolower($theme) . '%';
$query->where(function (Builder $builder) use ($theme, $themeLike): void {
$builder->whereRaw('LOWER(theme_token) = ?', [mb_strtolower($theme)])
->orWhereHas('entityLinks', function (Builder $linkQuery) use ($themeLike): void {
$linkQuery->where('linked_type', CollectionLinkService::TYPE_TAG)
->whereExists(function ($tagQuery) use ($themeLike): void {
$tagQuery->select(DB::raw('1'))
->from('tags')
->whereColumn('tags.id', 'collection_entity_links.linked_id')
->where(function ($tagBuilder) use ($themeLike): void {
$tagBuilder->whereRaw('LOWER(tags.slug) like ?', [$themeLike])
->orWhereRaw('LOWER(tags.name) like ?', [$themeLike]);
});
});
})
->orWhereHas('artworks.tags', function (Builder $tagQuery) use ($themeLike): void {
$tagQuery->whereRaw('LOWER(tags.slug) like ?', [$themeLike])
->orWhereRaw('LOWER(tags.name) like ?', [$themeLike]);
});
});
}
if (filled($filters['category'] ?? null)) {
$category = trim((string) $filters['category']);
$categoryLike = '%' . mb_strtolower($category) . '%';
$query->where(function (Builder $builder) use ($categoryLike): void {
$builder->whereHas('entityLinks', function (Builder $linkQuery) use ($categoryLike): void {
$linkQuery->where('linked_type', CollectionLinkService::TYPE_CATEGORY)
->whereExists(function ($categoryQuery) use ($categoryLike): void {
$categoryQuery->select(DB::raw('1'))
->from('categories')
->whereColumn('categories.id', 'collection_entity_links.linked_id')
->where(function ($inner) use ($categoryLike): void {
$inner->whereRaw('LOWER(categories.slug) like ?', [$categoryLike])
->orWhereRaw('LOWER(categories.name) like ?', [$categoryLike]);
});
});
})->orWhereHas('artworks.categories', function (Builder $categoryQuery) use ($categoryLike): void {
$categoryQuery->whereRaw('LOWER(categories.slug) like ?', [$categoryLike])
->orWhereRaw('LOWER(categories.name) like ?', [$categoryLike]);
});
});
}
if (filled($filters['style'] ?? null)) {
$style = trim((string) $filters['style']);
$styleLike = '%' . mb_strtolower($style) . '%';
$query->where(function (Builder $builder) use ($styleLike): void {
$builder->whereRaw('LOWER(spotlight_style) like ?', [$styleLike])
->orWhereRaw('LOWER(presentation_style) like ?', [$styleLike])
->orWhereHas('artworks.tags', function (Builder $tagQuery) use ($styleLike): void {
$tagQuery->whereRaw('LOWER(tags.slug) like ?', [$styleLike])
->orWhereRaw('LOWER(tags.name) like ?', [$styleLike]);
});
});
}
if (filled($filters['color'] ?? null)) {
$color = trim((string) $filters['color']);
$colorLike = '%' . mb_strtolower($color) . '%';
$query->where(function (Builder $builder) use ($colorLike): void {
$builder->whereRaw('LOWER(theme_token) like ?', [$colorLike])
->orWhereHas('artworks.tags', function (Builder $tagQuery) use ($colorLike): void {
$tagQuery->whereRaw('LOWER(tags.slug) like ?', [$colorLike])
->orWhereRaw('LOWER(tags.name) like ?', [$colorLike]);
});
});
}
if (filled($filters['placement_eligibility'] ?? null) && $includeInternal) {
$query->where('placement_eligibility', filter_var($filters['placement_eligibility'], FILTER_VALIDATE_BOOLEAN));
}
if (filled($filters['partner_key'] ?? null) && $includeInternal) {
$query->where('partner_key', (string) $filters['partner_key']);
}
if (filled($filters['experiment_key'] ?? null) && $includeInternal) {
$query->where('experiment_key', (string) $filters['experiment_key']);
}
}
private function applyPublicSort($query, string $sort): void
{
match ($sort) {
'recent' => $query->orderByDesc('published_at')->orderByDesc('updated_at'),
'quality' => $query->orderByDesc('health_score')->orderByDesc('quality_score'),
'evergreen' => $query->orderByDesc('quality_score')->orderByDesc('followers_count'),
default => $query->orderByDesc('ranking_score')->orderByDesc('health_score')->orderByDesc('updated_at'),
};
}
private function humanizeToken(string $value): string
{
return str($value)
->replace(['_', '-'], ' ')
->title()
->value();
}
}