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

@@ -18,6 +18,7 @@ use App\Services\ThumbnailPresenter;
use Illuminate\Http\Request;
use Illuminate\Pagination\AbstractCursorPaginator;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
@@ -238,12 +239,102 @@ final class ExploreController extends Controller
return $this->byType($request, $type);
}
// ── /explore/best (Hall of Fame) ────────────────────────────────────
/**
* Hall of Fame: all-time highest-medal artworks, ranked by prestige.
*
* Algorithm:
* 1. Primary: score_total DESC (all-time weighted medal score: gold×5 + silver×3 + bronze×1)
* 2. Secondary: gold_count DESC (prestige tiebreak golds are rarer and more deliberate)
* 3. Tertiary: favorites_count DESC (overall community love)
*
* Only artworks published 30 days ago are eligible so freshly-viral
* pieces don't crowd out genuine all-time standouts.
*
* Cache TTL is 1 hour rankings shift slowly for the HoF.
*/
public function hallOfFame(Request $request)
{
$perPage = 24;
$page = max(1, (int) $request->query('page', 1));
$minAge = now()->subDays(30);
$maturityUser = $request->user();
$cacheVersion = $this->cacheVersion();
$viewerSegment = $maturityUser ? 'auth.' . $maturityUser->id : 'guest';
$cacheKey = "explore.hall-of-fame.v{$cacheVersion}.{$viewerSegment}.p{$page}";
$paginator = Cache::remember($cacheKey, 3600, function () use ($perPage, $page, $minAge, $maturityUser): LengthAwarePaginator {
$query = Artwork::query()
->public()
->published()
->tap(fn ($b) => $this->maturity->applyViewerFilter($b, $maturityUser))
->withoutMissingThumbnails()
->with([
'user:id,name,username',
'user.profile:user_id,avatar_hash',
'group:id,name,slug,headline,avatar_path,followers_count',
'categories:id,name,slug,content_type_id,sort_order',
'categories.contentType:id,name,slug',
'awardStat:artwork_id,gold_count,silver_count,bronze_count,score_total',
'stats:artwork_id,favorites',
])
->leftJoin('artwork_medal_stats as hof', 'hof.artwork_id', '=', 'artworks.id')
->leftJoin('artwork_stats as hof_stats', 'hof_stats.artwork_id', '=', 'artworks.id')
->select('artworks.*')
// Must have at least one medal
->whereRaw('COALESCE(hof.score_total, 0) > 0')
// Minimum 30-day age to exclude freshly-viral pieces
->where('artworks.published_at', '<=', $minAge)
// Ranking: prestige-weighted medal score, then gold count, then favorites
->orderByRaw('COALESCE(hof.score_total, 0) DESC')
->orderByRaw('COALESCE(hof.gold_count, 0) DESC')
->orderByRaw('COALESCE(hof_stats.favorites, 0) DESC');
return $query->paginate($perPage, ['artworks.*'], 'page', $page)
->withPath(url('/explore/best'));
});
$paginator->getCollection()->transform(fn (Artwork $a) => $this->presentArtwork($a));
$mainCategories = $this->mainCategories();
$seo = $this->paginationSeo($request, url('/explore/best'), $paginator);
return view('gallery.index', [
'gallery_type' => 'browse',
'gallery_nav_section' => 'artworks',
'mainCategories' => $mainCategories,
'subcategories' => $mainCategories,
'contentType' => null,
'category' => null,
'artworks' => $paginator,
'spotlight' => collect(),
'hide_rank_tabs' => true,
'current_sort' => 'top-rated',
'sort_options' => [],
'hero_title' => 'Hall of Fame',
'hero_description' => 'All-time medal standouts ranked by prestige — the artworks the community has honoured most across the years.',
'breadcrumbs' => collect([
(object) ['name' => 'Explore', 'url' => '/explore'],
(object) ['name' => 'Hall of Fame', 'url' => '/explore/best'],
]),
'page_title' => 'Hall of Fame — All-Time Best Artworks - Skinbase',
'page_meta_description' => 'The highest-medal artworks of all time on Skinbase, ranked by gold, silver and bronze prestige.',
'page_meta_keywords' => 'hall of fame, best artworks, top rated, medals, skinbase',
'page_canonical' => $seo['canonical'],
'page_rel_prev' => $seo['prev'],
'page_rel_next' => $seo['next'],
'page_robots' => 'index,follow',
]);
}
// ── Helpers ──────────────────────────────────────────────────────────
private function mainCategories(): Collection
{
$categories = $this->contentTypeResolver
->publicContentTypes()
->toolbarContentTypes()
->map(fn ($ct) => (object) [
'name' => $ct->name,
'slug' => $ct->slug,
@@ -311,7 +402,8 @@ final class ExploreController extends Controller
];
if ($contentType !== null && $contentType !== '') {
$filterParts[] = 'content_type = "' . addslashes($contentType) . '"';
$quoted = addslashes($contentType);
$filterParts[] = '(content_type = "' . $quoted . '" OR content_types = "' . $quoted . '")';
}
$orientation = strtolower(trim((string) $request->query('orientation', '')));