106 lines
3.6 KiB
PHP
106 lines
3.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Controllers\RSS;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Artwork;
|
|
use App\Models\ContentType;
|
|
use App\Services\RSS\RSSFeedBuilder;
|
|
use Illuminate\Http\Response;
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
/**
|
|
* ExploreFeedController
|
|
*
|
|
* Powers the /rss/explore/* feeds (spec §3.3).
|
|
*
|
|
* GET /rss/explore/{type} → latest by content type
|
|
* GET /rss/explore/{type}/{mode} → sorted by mode (trending|latest|best)
|
|
*
|
|
* Valid types: artworks | wallpapers | skins | photography | other
|
|
* Valid modes: trending | latest | best
|
|
*/
|
|
final class ExploreFeedController extends Controller
|
|
{
|
|
private const SORT_TTL = [
|
|
'trending' => 600,
|
|
'best' => 600,
|
|
'latest' => 300,
|
|
];
|
|
|
|
public function __construct(private readonly RSSFeedBuilder $builder) {}
|
|
|
|
/** /rss/explore/{type} — defaults to latest */
|
|
public function byType(string $type): Response
|
|
{
|
|
return $this->feed($type, 'latest');
|
|
}
|
|
|
|
/** /rss/explore/{type}/{mode} */
|
|
public function byTypeMode(string $type, string $mode): Response
|
|
{
|
|
return $this->feed($type, $mode);
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────
|
|
|
|
private function feed(string $type, string $mode): Response
|
|
{
|
|
$mode = in_array($mode, ['trending', 'latest', 'best'], true) ? $mode : 'latest';
|
|
$ttl = self::SORT_TTL[$mode] ?? 300;
|
|
$feedUrl = url('/rss/explore/' . $type . ($mode !== 'latest' ? '/' . $mode : ''));
|
|
$label = ucfirst(str_replace('-', ' ', $type));
|
|
|
|
$artworks = Cache::remember("rss:explore:{$type}:{$mode}", $ttl, function () use ($type, $mode) {
|
|
$contentType = ContentType::where('slug', $type)->first();
|
|
|
|
$query = Artwork::public()->published()
|
|
->with(['user:id,username', 'categories:id,name,slug,content_type_id']);
|
|
|
|
if ($contentType) {
|
|
$query->whereHas('categories', fn ($q) =>
|
|
$q->where('content_type_id', $contentType->id)
|
|
);
|
|
}
|
|
|
|
return match ($mode) {
|
|
'trending' => $query
|
|
->leftJoin('artwork_stats', 'artwork_stats.artwork_id', '=', 'artworks.id')
|
|
->orderByDesc('artwork_stats.trending_score_7d')
|
|
->orderByDesc('artworks.published_at')
|
|
->select('artworks.*')
|
|
->limit(RSSFeedBuilder::FEED_LIMIT)
|
|
->get(),
|
|
|
|
'best' => $query
|
|
->leftJoin('artwork_stats', 'artwork_stats.artwork_id', '=', 'artworks.id')
|
|
->orderByDesc('artwork_stats.favorites')
|
|
->orderByDesc('artwork_stats.downloads')
|
|
->select('artworks.*')
|
|
->limit(RSSFeedBuilder::FEED_LIMIT)
|
|
->get(),
|
|
|
|
default => $query
|
|
->latest('artworks.published_at')
|
|
->limit(RSSFeedBuilder::FEED_LIMIT)
|
|
->get(),
|
|
};
|
|
});
|
|
|
|
$modeLabel = match ($mode) {
|
|
'trending' => 'Trending',
|
|
'best' => 'Best',
|
|
default => 'Latest',
|
|
};
|
|
|
|
return $this->builder->buildFromArtworks(
|
|
"{$modeLabel} {$label}",
|
|
"{$modeLabel} {$label} artworks on Skinbase.",
|
|
$feedUrl,
|
|
$artworks,
|
|
);
|
|
}
|
|
}
|