Wire admin studio SSR and search infrastructure

This commit is contained in:
2026-05-01 11:46:06 +02:00
parent 257b0dbef6
commit 18cea8b0f0
329 changed files with 197465 additions and 2741 deletions

View File

@@ -55,7 +55,7 @@ final class ArtworkSearchService
}
if (! empty($filters['category'])) {
$filterParts[] = 'category = "' . addslashes((string) $filters['category']) . '"';
$filterParts[] = $this->categoryFilterClause((string) $filters['category']);
}
if (! empty($filters['orientation'])) {
@@ -90,7 +90,7 @@ final class ArtworkSearchService
return $results;
}
$page = max(1, (int) request()->get('page', 1));
$page = $this->currentPage();
$candidateCount = $this->determineSearchCandidatePoolSize($perPage, $page);
$fallbackResults = Artwork::search($q ?: '')
->options($options)
@@ -108,7 +108,7 @@ final class ArtworkSearchService
public function searchWithThumbnailPreference(array $options, int $perPage, bool $excludeMissing = false, ?int $page = null): LengthAwarePaginator
{
$page = max(1, $page ?? (int) request()->get('page', 1));
$page = max(1, $page ?? $this->currentPage());
$candidateCount = $this->determineSearchCandidatePoolSize($perPage, $page);
$results = Artwork::search('')
->options($this->viewerAwareOptions($options))
@@ -139,7 +139,7 @@ final class ArtworkSearchService
}
$sort = in_array($sort, self::TAG_SORTS, true) ? $sort : 'popular';
$cacheKey = "search.tag.{$slug}.{$sort}.{$perPage}.{$this->viewerCacheSegment()}.page." . request()->get('page', 1);
$cacheKey = "search.tag.{$slug}.{$sort}.{$perPage}.{$this->viewerCacheSegment()}.page." . $this->currentPage();
return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($tag, $perPage, $sort) {
$query = Artwork::query()
@@ -180,12 +180,12 @@ final class ArtworkSearchService
*/
public function byCategory(string $cat, int $perPage = 24, array $filters = []): LengthAwarePaginator
{
$page = (int) request()->get('page', 1);
$page = $this->currentPage();
$cacheKey = "search.cat.catalog-visible.v2.{$cat}.{$this->viewerCacheSegment()}.page." . $page;
return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($cat, $perPage, $page) {
return $this->searchWithThumbnailPreference([
'filter' => self::BASE_FILTER . ' AND category = "' . addslashes($cat) . '"',
'filter' => self::BASE_FILTER . ' AND ' . $this->categoryFilterClause($cat),
'sort' => ['created_at:desc'],
], $perPage, false, $page);
});
@@ -226,15 +226,15 @@ final class ArtworkSearchService
public function categoryPageSort(string $categorySlug, string $sort = 'trending', int $perPage = 24): LengthAwarePaginator
{
$sort = array_key_exists($sort, self::CATEGORY_SORT_FIELDS) ? $sort : 'trending';
$page = (int) request()->get('page', 1);
$page = $this->currentPage();
$ttl = self::CATEGORY_SORT_TTL[$sort] ?? self::CACHE_TTL;
$cacheKey = "category.catalog-visible.v2.{$categorySlug}.{$sort}.{$this->viewerCacheSegment()}.{$page}";
return Cache::remember($cacheKey, $ttl, function () use ($categorySlug, $sort, $perPage) {
return $this->searchWithThumbnailPreference([
'filter' => self::BASE_FILTER . ' AND category = "' . addslashes($categorySlug) . '"',
'filter' => self::BASE_FILTER . ' AND ' . $this->categoryFilterClause($categorySlug),
'sort' => self::CATEGORY_SORT_FIELDS[$sort],
], $perPage, false, (int) request()->get('page', 1));
], $perPage, false, $this->currentPage());
});
}
@@ -247,15 +247,15 @@ final class ArtworkSearchService
public function contentTypePageSort(string $contentTypeSlug, string $sort = 'trending', int $perPage = 24): LengthAwarePaginator
{
$sort = array_key_exists($sort, self::CATEGORY_SORT_FIELDS) ? $sort : 'trending';
$page = (int) request()->get('page', 1);
$page = $this->currentPage();
$ttl = self::CATEGORY_SORT_TTL[$sort] ?? self::CACHE_TTL;
$cacheKey = "content_type.catalog-visible.v2.{$contentTypeSlug}.{$sort}.{$this->viewerCacheSegment()}.{$page}";
return Cache::remember($cacheKey, $ttl, function () use ($contentTypeSlug, $sort, $perPage) {
return $this->searchWithThumbnailPreference([
'filter' => self::BASE_FILTER . ' AND content_type = "' . addslashes($contentTypeSlug) . '"',
'filter' => self::BASE_FILTER . ' AND ' . $this->contentTypeFilterClause($contentTypeSlug),
'sort' => self::CATEGORY_SORT_FIELDS[$sort],
], $perPage, false, (int) request()->get('page', 1));
], $perPage, false, $this->currentPage());
});
}
@@ -295,7 +295,7 @@ final class ArtworkSearchService
*/
public function popular(int $perPage = 24): LengthAwarePaginator
{
return Cache::remember('search.popular.' . $this->viewerCacheSegment() . '.page.' . request()->get('page', 1), self::CACHE_TTL, function () use ($perPage) {
return Cache::remember('search.popular.' . $this->viewerCacheSegment() . '.page.' . $this->currentPage(), self::CACHE_TTL, function () use ($perPage) {
return Artwork::search('')
->options($this->viewerAwareOptions([
'filter' => self::BASE_FILTER,
@@ -310,7 +310,7 @@ final class ArtworkSearchService
*/
public function recent(int $perPage = 24): LengthAwarePaginator
{
return Cache::remember('search.recent.' . $this->viewerCacheSegment() . '.page.' . request()->get('page', 1), self::CACHE_TTL, function () use ($perPage) {
return Cache::remember('search.recent.' . $this->viewerCacheSegment() . '.page.' . $this->currentPage(), self::CACHE_TTL, function () use ($perPage) {
return Artwork::search('')
->options($this->viewerAwareOptions([
'filter' => self::BASE_FILTER,
@@ -330,7 +330,7 @@ final class ArtworkSearchService
*/
public function discoverTrending(int $perPage = 24): LengthAwarePaginator
{
$page = (int) request()->get('page', 1);
$page = $this->currentPage();
$windowDays = $this->timeWindow->getTrendingWindowDays(30);
$cutoff = now()->subDays($windowDays)->toDateString();
// Include window in cache key so adaptive expansions surface immediately
@@ -352,7 +352,7 @@ final class ArtworkSearchService
*/
public function discoverRising(int $perPage = 24): LengthAwarePaginator
{
$page = (int) request()->get('page', 1);
$page = $this->currentPage();
$windowDays = $this->timeWindow->getTrendingWindowDays(30);
$cutoff = now()->subDays($windowDays)->toDateString();
$cacheKey = "discover.rising.{$windowDays}d.{$this->viewerCacheSegment()}.{$page}";
@@ -370,7 +370,7 @@ final class ArtworkSearchService
*/
public function discoverFresh(int $perPage = 24): LengthAwarePaginator
{
$page = (int) request()->get('page', 1);
$page = $this->currentPage();
return Cache::remember("discover.fresh.{$this->viewerCacheSegment()}.{$page}", self::CACHE_TTL, function () use ($perPage) {
return $this->searchWithThumbnailPreference([
'filter' => self::BASE_FILTER,
@@ -384,7 +384,7 @@ final class ArtworkSearchService
*/
public function discoverTopRated(int $perPage = 24): LengthAwarePaginator
{
$page = (int) request()->get('page', 1);
$page = $this->currentPage();
return Cache::remember("discover.top-rated.{$this->viewerCacheSegment()}.{$page}", self::CACHE_TTL, function () use ($perPage) {
return $this->searchWithThumbnailPreference([
'filter' => self::BASE_FILTER,
@@ -398,7 +398,7 @@ final class ArtworkSearchService
*/
public function discoverMostDownloaded(int $perPage = 24): LengthAwarePaginator
{
$page = (int) request()->get('page', 1);
$page = $this->currentPage();
return Cache::remember("discover.most-downloaded.{$this->viewerCacheSegment()}.{$page}", self::CACHE_TTL, function () use ($perPage) {
return $this->searchWithThumbnailPreference([
'filter' => self::BASE_FILTER,
@@ -441,6 +441,11 @@ final class ArtworkSearchService
return $options;
}
private function currentPage(): int
{
return max(1, (int) request()->query('page', 1));
}
private function shouldFallbackToViewerVisibilityFiltering(LengthAwarePaginator $results): bool
{
if ($results->total() > 0) {
@@ -468,7 +473,7 @@ final class ArtworkSearchService
}
$catFilter = implode(' OR ', array_map(
fn (string $c): string => 'category = "' . addslashes($c) . '"',
fn (string $c): string => $this->categoryFilterClause($c),
array_slice($categorySlugs, 0, 3)
));
@@ -494,6 +499,20 @@ final class ArtworkSearchService
return in_array($field, $allowed, true) ? [$field, $dir] : [null, 'desc'];
}
private function categoryFilterClause(string $categorySlug): string
{
$quoted = addslashes($categorySlug);
return '(category = "' . $quoted . '" OR categories = "' . $quoted . '")';
}
private function contentTypeFilterClause(string $contentTypeSlug): string
{
$quoted = addslashes($contentTypeSlug);
return '(content_type = "' . $quoted . '" OR content_types = "' . $quoted . '")';
}
private function rerankSearchCollectionByThumbnailHealth(Collection $items, bool $excludeMissing): Collection
{
if ($items->isEmpty()) {