['title' => 'Latest Uploads', 'url' => '/rss/latest-uploads.xml'], 'skins' => ['title' => 'Latest Skins', 'url' => '/rss/latest-skins.xml'], 'wallpapers' => ['title' => 'Latest Wallpapers', 'url' => '/rss/latest-wallpapers.xml'], 'photos' => ['title' => 'Latest Photos', 'url' => '/rss/latest-photos.xml'], ]; public function __construct(private readonly ContentTypeSlugResolver $contentTypeResolver) { } /** Info page at /rss-feeds */ public function index(): View { return view('web.rss-feeds', [ 'page_title' => 'RSS Feeds — Skinbase', 'page_meta_description' => 'Subscribe to Skinbase RSS feeds to stay up to date with the latest uploads, skins, wallpapers, and photos.', 'page_canonical' => url('/rss-feeds'), 'hero_title' => 'RSS Feeds', 'hero_description' => 'Subscribe to stay up to date with the latest content on Skinbase.', 'breadcrumbs' => collect([ (object) ['name' => 'Home', 'url' => '/'], (object) ['name' => 'RSS Feeds', 'url' => '/rss-feeds'], ]), 'feeds' => self::FEEDS, 'feed_groups' => $this->feedGroups(), 'center_content' => true, 'center_max' => '3xl', ]); } /** /rss/latest-uploads.xml — all content types */ public function latestUploads(): Response { $artworks = Artwork::published() ->with(['user']) ->latest('published_at') ->limit(self::FEED_LIMIT) ->get(); return $this->buildFeed('Latest Uploads', url('/rss/latest-uploads.xml'), $artworks); } /** /rss/latest-skins.xml */ public function latestSkins(): Response { return $this->feedByContentType('skins', 'Latest Skins', '/rss/latest-skins.xml'); } /** /rss/latest-wallpapers.xml */ public function latestWallpapers(): Response { return $this->feedByContentType('wallpapers', 'Latest Wallpapers', '/rss/latest-wallpapers.xml'); } /** /rss/latest-photos.xml */ public function latestPhotos(): Response { return $this->feedByContentType('photography', 'Latest Photos', '/rss/latest-photos.xml'); } // ------------------------------------------------------------------------- private function feedByContentType(string $slug, string $title, string $feedPath): Response { $contentType = $this->contentTypeResolver->resolve($slug)->contentType; $query = Artwork::published()->with(['user'])->latest('published_at')->limit(self::FEED_LIMIT); if ($contentType) { $query->whereHas('categories', fn ($q) => $q->where('content_type_id', $contentType->id)); } return $this->buildFeed($title, url($feedPath), $query->get()); } private function buildFeed(string $channelTitle, string $feedUrl, $artworks): Response { $content = view('rss.feed', [ 'channelTitle' => $channelTitle . ' — Skinbase', 'channelDescription' => 'The latest ' . strtolower($channelTitle) . ' from Skinbase.org', 'channelLink' => url('/'), 'feedUrl' => $feedUrl, 'artworks' => $artworks, 'buildDate' => now()->toRfc2822String(), ])->render(); return response($content, 200, [ 'Content-Type' => 'application/rss+xml; charset=utf-8', ]); } private function feedGroups(): array { $exploreFeeds = [[ 'title' => 'All Artworks', 'url' => '/rss/explore/artworks', 'description' => 'Latest artworks of all types.', ]]; foreach ($this->contentTypeResolver->publicContentTypes() as $contentType) { $name = (string) $contentType->name; $slug = (string) $contentType->slug; $exploreFeeds[] = [ 'title' => $name, 'url' => '/rss/explore/' . $slug, 'description' => 'Latest ' . strtolower($name) . '.', ]; } if ($this->contentTypeResolver->publicContentTypes()->isNotEmpty()) { $firstType = $this->contentTypeResolver->publicContentTypes()->first(); $exploreFeeds[] = [ 'title' => 'Trending ' . $firstType->name, 'url' => '/rss/explore/' . $firstType->slug . '/trending', 'description' => 'Trending ' . strtolower((string) $firstType->name) . ' this week.', ]; } return [ 'global' => [ 'label' => 'Global', 'feeds' => [ ['title' => 'Latest Artworks', 'url' => '/rss', 'description' => 'All new artworks across the platform.'], ], ], 'discover' => [ 'label' => 'Discover', 'feeds' => [ ['title' => 'Fresh Uploads', 'url' => '/rss/discover/fresh', 'description' => 'The newest artworks just published.'], ['title' => 'Trending', 'url' => '/rss/discover/trending', 'description' => 'Most-viewed artworks over the past 7 days.'], ['title' => 'Rising', 'url' => '/rss/discover/rising', 'description' => 'Artworks gaining momentum right now.'], ], ], 'explore' => [ 'label' => 'Explore', 'feeds' => $exploreFeeds, ], 'blog' => [ 'label' => 'Blog', 'feeds' => [ ['title' => 'Blog Posts', 'url' => '/rss/blog', 'description' => 'Latest posts from the Skinbase blog.'], ], ], 'legacy' => [ 'label' => 'Legacy Feeds', 'feeds' => [ ['title' => 'Latest Uploads (XML)', 'url' => '/rss/latest-uploads.xml', 'description' => 'Legacy XML feed.'], ['title' => 'Latest Skins (XML)', 'url' => '/rss/latest-skins.xml', 'description' => 'Legacy XML feed.'], ['title' => 'Latest Wallpapers (XML)', 'url' => '/rss/latest-wallpapers.xml', 'description' => 'Legacy XML feed.'], ['title' => 'Latest Photos (XML)', 'url' => '/rss/latest-photos.xml', 'description' => 'Legacy XML feed.'], ], ], ]; } }