fresh(); } /** /rss/discover/trending */ public function trending(): Response { $feedUrl = url('/rss/discover/trending'); $artworks = Cache::remember('rss:discover:trending', 600, fn () => Artwork::public()->published() ->with(['user:id,username', 'categories:id,name,slug,content_type_id']) ->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() ); return $this->builder->buildFromArtworks( 'Trending Artworks', 'The most-viewed and trending artworks on Skinbase over the past 7 days.', $feedUrl, $artworks, ); } /** /rss/discover/fresh */ public function fresh(): Response { $feedUrl = url('/rss/discover/fresh'); $artworks = Cache::remember('rss:discover:fresh', 300, fn () => Artwork::public()->published() ->with(['user:id,username', 'categories:id,name,slug,content_type_id']) ->latest('published_at') ->limit(RSSFeedBuilder::FEED_LIMIT) ->get() ); return $this->builder->buildFromArtworks( 'Fresh Uploads', 'The latest artworks just published on Skinbase.', $feedUrl, $artworks, ); } /** /rss/discover/rising */ public function rising(): Response { $feedUrl = url('/rss/discover/rising'); $windowDays = $this->timeWindow->getTrendingWindowDays(30); $artworks = Cache::remember( "rss:discover:rising.{$windowDays}d", 600, function () use ($windowDays) { $artworks = $this->risingArtworks($windowDays); if ($this->collectionHasNoRisingMomentum($artworks)) { return $this->risingLowSignalArtworks($windowDays); } return $artworks; } ); return $this->builder->buildFromArtworks( 'Rising Artworks', 'Fastest-growing artworks gaining momentum on Skinbase right now.', $feedUrl, $artworks, ); } private function risingArtworks(int $windowDays): Collection { $cutoff = now()->subDays($windowDays)->startOfDay(); return Artwork::public() ->published() ->with(['user:id,username', 'categories:id,name,slug,content_type_id']) ->leftJoin('artwork_stats', 'artwork_stats.artwork_id', '=', 'artworks.id') ->select('artworks.*') ->selectRaw('COALESCE(artwork_stats.heat_score, 0) as heat_score') ->selectRaw('COALESCE(artwork_stats.engagement_velocity, 0) as engagement_velocity') ->where('artworks.published_at', '>=', $cutoff) ->orderByDesc('artwork_stats.heat_score') ->orderByDesc('artwork_stats.engagement_velocity') ->orderByDesc('artworks.published_at') ->orderByDesc('artworks.id') ->limit(RSSFeedBuilder::FEED_LIMIT) ->get(); } private function risingLowSignalArtworks(int $windowDays): Collection { $cutoff = now()->subDays($windowDays)->startOfDay(); return Artwork::public() ->published() ->with(['user:id,username', 'categories:id,name,slug,content_type_id']) ->leftJoin('artwork_stats', 'artwork_stats.artwork_id', '=', 'artworks.id') ->leftJoinSub($this->risingRecentActivitySubquery(), 'recent_rising_activity', function ($join): void { $join->on('recent_rising_activity.artwork_id', '=', 'artworks.id'); }) ->select('artworks.*') ->selectRaw('COALESCE(artwork_stats.heat_score, 0) as heat_score') ->selectRaw('COALESCE(artwork_stats.engagement_velocity, 0) as engagement_velocity') ->selectRaw('COALESCE(recent_rising_activity.recent_signal_24h, 0) as recent_signal_24h') ->where('artworks.published_at', '>=', $cutoff) ->orderByDesc('recent_signal_24h') ->orderByDesc('artworks.published_at') ->orderByDesc('artworks.id') ->limit(RSSFeedBuilder::FEED_LIMIT) ->get(); } private function collectionHasNoRisingMomentum(Collection $artworks): bool { if ($artworks->isEmpty()) { return false; } return $artworks->every(function (Artwork $artwork): bool { return (float) ($artwork->heat_score ?? 0) <= 0 && (float) ($artwork->engagement_velocity ?? 0) <= 0; }); } private function risingRecentActivitySubquery() { $since = now()->startOfHour()->subHours(24); return DB::table('artwork_metric_snapshots_hourly as rising_snapshots') ->selectRaw('rising_snapshots.artwork_id') ->selectRaw('( COALESCE(MAX(rising_snapshots.views_count) - MIN(rising_snapshots.views_count), 0) + (COALESCE(MAX(rising_snapshots.downloads_count) - MIN(rising_snapshots.downloads_count), 0) * 3) + (COALESCE(MAX(rising_snapshots.favourites_count) - MIN(rising_snapshots.favourites_count), 0) * 4) + (COALESCE(MAX(rising_snapshots.comments_count) - MIN(rising_snapshots.comments_count), 0) * 5) + (COALESCE(MAX(rising_snapshots.shares_count) - MIN(rising_snapshots.shares_count), 0) * 6) ) as recent_signal_24h') ->where('rising_snapshots.bucket_hour', '>=', $since) ->groupBy('rising_snapshots.artwork_id'); } }