feat: add reusable gallery carousel and ranking feed infrastructure

This commit is contained in:
2026-02-28 07:56:25 +01:00
parent 67ef79766c
commit 6536d4ae78
36 changed files with 3177 additions and 373 deletions

View File

@@ -113,6 +113,80 @@ final class ArtworkSearchService
});
}
// ── Category / Content-Type page sorts ────────────────────────────────────
/**
* Meilisearch sort fields per alias.
* Used by categoryPageSort() and contentTypePageSort().
*/
private const CATEGORY_SORT_FIELDS = [
'trending' => ['trending_score_24h:desc', 'created_at:desc'],
'fresh' => ['created_at: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'],
];
/** Cache TTL (seconds) per sort alias for category pages. */
private const CATEGORY_SORT_TTL = [
'trending' => 300, // 5 min
'fresh' => 120, // 2 min
'top-rated' => 600, // 10 min
'favorited' => 300,
'downloaded' => 300,
'oldest' => 600,
];
/**
* Artworks for a single category page, sorted via Meilisearch.
* Default sort: trending (trending_score_24h:desc).
*
* Cache key pattern: category.{slug}.{sort}.{page}
* TTL varies by sort (see spec: 5/2/10 min).
*/
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);
$ttl = self::CATEGORY_SORT_TTL[$sort] ?? self::CACHE_TTL;
$cacheKey = "category.{$categorySlug}.{$sort}.{$page}";
return Cache::remember($cacheKey, $ttl, function () use ($categorySlug, $sort, $perPage) {
return Artwork::search('')
->options([
'filter' => self::BASE_FILTER . ' AND category = "' . addslashes($categorySlug) . '"',
'sort' => self::CATEGORY_SORT_FIELDS[$sort],
])
->paginate($perPage);
});
}
/**
* Artworks for a content-type root page, sorted via Meilisearch.
* Default sort: trending.
*
* Cache key pattern: content_type.{slug}.{sort}.{page}
*/
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);
$ttl = self::CATEGORY_SORT_TTL[$sort] ?? self::CACHE_TTL;
$cacheKey = "content_type.{$contentTypeSlug}.{$sort}.{$page}";
return Cache::remember($cacheKey, $ttl, function () use ($contentTypeSlug, $sort, $perPage) {
return Artwork::search('')
->options([
'filter' => self::BASE_FILTER . ' AND content_type = "' . addslashes($contentTypeSlug) . '"',
'sort' => self::CATEGORY_SORT_FIELDS[$sort],
])
->paginate($perPage);
});
}
// -------------------------------------------------------------------------
/**
* Related artworks: same tags, different artwork, ranked by views + likes.
* Limit 12.