Wire admin studio SSR and search infrastructure
This commit is contained in:
@@ -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', '')));
|
||||
|
||||
Reference in New Issue
Block a user