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

@@ -20,6 +20,8 @@ use Illuminate\Pagination\AbstractCursorPaginator;
class BrowseGalleryController extends \App\Http\Controllers\Controller
{
private const CACHE_VERSION = 'v4';
/**
* Meilisearch sort-field arrays per sort alias.
* First element is primary sort; subsequent elements are tie-breakers.
@@ -28,18 +30,18 @@ class BrowseGalleryController extends \App\Http\Controllers\Controller
// ── Nova sort aliases ─────────────────────────────────────────────────
// trending_score_24h only covers artworks ≤ 7 days old; use 7d score
// and favorites_count as fallbacks so older artworks don't all tie at 0.
'trending' => ['trending_score_24h:desc', 'trending_score_7d:desc', 'favorites_count:desc', 'created_at:desc'],
'trending' => ['trending_score_24h:desc', 'trending_score_7d:desc', 'favorites_count:desc', 'published_at_ts:desc'],
// "New & Hot": 30-day trending window surfaces recently-active artworks.
'fresh' => ['trending_score_7d:desc', 'favorites_count:desc', 'created_at:desc'],
'fresh' => ['published_at_ts:desc', 'trending_score_7d:desc', 'favorites_count:desc'],
'top-rated' => ['awards_received_count:desc', 'favorites_count:desc'],
'favorited' => ['favorites_count:desc', 'trending_score_24h:desc'],
'downloaded' => ['downloads_count:desc', 'trending_score_24h:desc'],
'oldest' => ['created_at:asc'],
'favorited' => ['favorites_count:desc', 'trending_score_24h:desc', 'published_at_ts:desc'],
'downloaded' => ['downloads_count:desc', 'trending_score_24h:desc', 'published_at_ts:desc'],
'oldest' => ['published_at_ts:asc'],
// ── Legacy aliases (backward compat) ──────────────────────────────────
'latest' => ['created_at:desc'],
'popular' => ['views:desc', 'favorites_count:desc'],
'liked' => ['likes:desc', 'favorites_count:desc'],
'downloads' => ['downloads:desc', 'downloads_count:desc'],
'latest' => ['published_at_ts:desc'],
'popular' => ['views:desc', 'favorites_count:desc', 'published_at_ts:desc'],
'liked' => ['likes:desc', 'favorites_count:desc', 'published_at_ts:desc'],
'downloads' => ['downloads:desc', 'downloads_count:desc', 'published_at_ts:desc'],
];
/**
@@ -66,6 +68,7 @@ class BrowseGalleryController extends \App\Http\Controllers\Controller
private const SORT_OPTIONS = [
['value' => 'trending', 'label' => '🔥 Trending'],
['value' => 'fresh', 'label' => '🆕 Fresh'],
['value' => 'latest', 'label' => '🕐 Latest'],
['value' => 'top-rated', 'label' => '⭐ Top Rated'],
['value' => 'favorited', 'label' => '❤️ Most Favorited'],
['value' => 'downloaded', 'label' => '⬇ Most Downloaded'],
@@ -88,11 +91,11 @@ class BrowseGalleryController extends \App\Http\Controllers\Controller
$ttl = self::SORT_TTL_MAP[$sort] ?? 300;
$artworks = Cache::remember(
"browse.all.catalog-visible.v2.{$sort}.{$page}",
"browse.all.catalog-visible." . self::CACHE_VERSION . ".{$sort}.{$page}",
$ttl,
fn () => $this->search->searchWithThumbnailPreference([
'filter' => 'is_public = true AND is_approved = true',
'sort' => self::SORT_MAP[$sort] ?? ['created_at:desc'],
'sort' => self::SORT_MAP[$sort] ?? ['published_at_ts:desc'],
], $perPage, false, $page)
);
$artworks->getCollection()->transform(fn ($a) => $this->presentArtwork($a));
@@ -150,11 +153,11 @@ class BrowseGalleryController extends \App\Http\Controllers\Controller
$normalizedPath = trim((string) $path, '/');
if ($normalizedPath === '') {
$artworks = Cache::remember(
"gallery.ct.catalog-visible.v2.{$contentSlug}.{$sort}.{$page}",
"gallery.ct.catalog-visible." . self::CACHE_VERSION . ".{$contentSlug}.{$sort}.{$page}",
$ttl,
fn () => $this->search->searchWithThumbnailPreference([
'filter' => 'is_public = true AND is_approved = true AND content_type = "' . $contentSlug . '"',
'sort' => self::SORT_MAP[$sort] ?? ['created_at:desc'],
'filter' => 'is_public = true AND is_approved = true AND ' . $this->contentTypeFilterClause($contentSlug),
'sort' => self::SORT_MAP[$sort] ?? ['published_at_ts:desc'],
], $perPage, false, $page)
);
$artworks->getCollection()->transform(fn ($a) => $this->presentArtwork($a));
@@ -192,16 +195,14 @@ class BrowseGalleryController extends \App\Http\Controllers\Controller
}
$categorySlugs = $this->categoryFilterSlugs($category);
$categoryFilter = collect($categorySlugs)
->map(fn (string $slug) => 'category = "' . addslashes($slug) . '"')
->implode(' OR ');
$filterExpression = $this->categoryPageFilterExpression($contentSlug, $categorySlugs);
$artworks = Cache::remember(
'gallery.cat.catalog-visible.v2.' . md5($contentSlug . '|' . implode('|', $categorySlugs)) . ".{$sort}.{$page}",
'gallery.cat.catalog-visible.' . self::CACHE_VERSION . '.' . md5($contentSlug . '|' . implode('|', $categorySlugs)) . ".{$sort}.{$page}",
$ttl,
fn () => $this->search->searchWithThumbnailPreference([
'filter' => 'is_public = true AND is_approved = true AND (' . $categoryFilter . ')',
'sort' => self::SORT_MAP[$sort] ?? ['created_at:desc'],
'filter' => $filterExpression,
'sort' => self::SORT_MAP[$sort] ?? ['published_at_ts:desc'],
], $perPage, false, $page)
);
$artworks->getCollection()->transform(fn ($a) => $this->presentArtwork($a));
@@ -369,6 +370,31 @@ class BrowseGalleryController extends \App\Http\Controllers\Controller
return array_values(array_unique($slugs));
}
private function categoryFilterClause(string $categorySlug): string
{
$quoted = addslashes($categorySlug);
return '(category = "' . $quoted . '" OR categories = "' . $quoted . '")';
}
private function categoryPageFilterExpression(string $contentTypeSlug, array $categorySlugs): string
{
$categoryFilter = collect($categorySlugs)
->map(fn (string $slug) => $this->categoryFilterClause($slug))
->implode(' OR ');
return 'is_public = true AND is_approved = true AND '
. $this->contentTypeFilterClause($contentTypeSlug)
. ' AND (' . $categoryFilter . ')';
}
private function contentTypeFilterClause(string $contentTypeSlug): string
{
$quoted = addslashes($contentTypeSlug);
return '(content_type = "' . $quoted . '" OR content_types = "' . $quoted . '")';
}
private function resolvePerPage(Request $request): int
{
$limit = (int) $request->query('limit', 0);
@@ -393,7 +419,7 @@ class BrowseGalleryController extends \App\Http\Controllers\Controller
private function mainCategories(): Collection
{
return $this->contentTypeResolver
->publicContentTypes()
->toolbarContentTypes()
->map(function (ContentType $type) {
return (object) [
'id' => $type->id,