whereRaw('LOWER(username) = ?', [$normalized])->first(); if (! $user) { $redirect = DB::table('username_redirects') ->whereRaw('LOWER(old_username) = ?', [$normalized]) ->value('new_username'); if ($redirect) { return redirect()->route('profile.collections.show', [ 'username' => strtolower((string) $redirect), 'slug' => $slug, ], 301); } abort(404); } if ($username !== strtolower((string) $user->username)) { return redirect()->route('profile.collections.show', [ 'username' => strtolower((string) $user->username), 'slug' => $slug, ], 301); } $collection = Collection::query() ->with([ 'user.profile', 'coverArtwork:id,user_id,title,slug,hash,thumb_ext,published_at,is_public,is_approved,deleted_at', 'canonicalCollection.user.profile', ]) ->where('user_id', $user->id) ->where('slug', $slug) ->firstOrFail(); $viewer = $request->user(); $ownerView = $viewer && (int) $viewer->id === (int) $user->id; if ($collection->canonical_collection_id) { $canonicalTarget = $collection->canonicalCollection; if ($canonicalTarget instanceof Collection && (int) $canonicalTarget->id !== (int) $collection->id && $canonicalTarget->user instanceof User && $canonicalTarget->canBeViewedBy($viewer)) { return redirect()->route('profile.collections.show', [ 'username' => strtolower((string) $canonicalTarget->user->username), 'slug' => $canonicalTarget->slug, ], 301); } } if (! $collection->canBeViewedBy($viewer)) { abort(404); } $collection = $this->collections->recordView($collection); $this->saves->touchSavedCollectionView($viewer, $collection); $artworks = $this->collections->getCollectionDetailArtworks($collection, $ownerView, 24); $collectionPayload = $this->collections->mapCollectionDetailPayload($collection, $ownerView); $manualRelatedCollections = $this->linkedCollections->publicLinkedCollections($collection, 6); $recommendedCollections = $this->recommendations->relatedPublicCollections($collection, 6); $seriesContext = $collection->inSeries() ? $this->series->seriesContext($collection) : ['previous' => null, 'next' => null, 'items' => [], 'title' => null, 'description' => null]; event(new CollectionViewed($collection, $viewer?->id)); return Inertia::render('Collection/CollectionShow', [ 'collection' => $collectionPayload, 'artworks' => $this->collections->mapArtworkPaginator($artworks), 'owner' => $collectionPayload['owner'], 'isOwner' => $ownerView, 'manageUrl' => $ownerView ? route('settings.collections.show', ['collection' => $collection->id]) : null, 'editUrl' => $ownerView ? route('settings.collections.edit', ['collection' => $collection->id]) : null, 'analyticsUrl' => $ownerView && $collection->supportsAnalytics() ? route('settings.collections.analytics', ['collection' => $collection->id]) : null, 'historyUrl' => $ownerView ? route('settings.collections.history', ['collection' => $collection->id]) : null, 'engagement' => [ 'liked' => $this->likes->isLiked($viewer, $collection), 'following' => $this->follows->isFollowing($viewer, $collection), 'saved' => $this->saves->isSaved($viewer, $collection), 'can_interact' => ! $ownerView && $collection->isPubliclyEngageable(), 'like_url' => route('collections.like', ['collection' => $collection->id]), 'unlike_url' => route('collections.unlike', ['collection' => $collection->id]), 'follow_url' => route('collections.follow', ['collection' => $collection->id]), 'unfollow_url' => route('collections.unfollow', ['collection' => $collection->id]), 'save_url' => route('collections.save', ['collection' => $collection->id]), 'unsave_url' => route('collections.unsave', ['collection' => $collection->id]), 'share_url' => route('collections.share', ['collection' => $collection->id]), 'login_url' => route('login'), ], 'members' => $this->collaborators->mapMembers($collection, $viewer), 'submissions' => $this->submissions->mapSubmissions($collection, $viewer), 'comments' => $this->comments->mapComments($collection, $viewer), 'entityLinks' => $this->entityLinks->links($collection, true), 'relatedCollections' => $this->collections->mapCollectionCardPayloads( $manualRelatedCollections ->concat($recommendedCollections) ->unique('id') ->take(6) ->values(), false ), 'seriesContext' => [ 'key' => $seriesContext['key'] ?? $collection->series_key, 'title' => $seriesContext['title'] ?? $collection->series_title, 'description' => $seriesContext['description'] ?? $collection->series_description, 'url' => ! empty($seriesContext['key']) ? route('collections.series.show', ['seriesKey' => $seriesContext['key']]) : null, 'previous' => $seriesContext['previous'] ? ($this->collections->mapCollectionCardPayloads(collect([$seriesContext['previous']]), false)[0] ?? null) : null, 'next' => $seriesContext['next'] ? ($this->collections->mapCollectionCardPayloads(collect([$seriesContext['next']]), false)[0] ?? null) : null, 'siblings' => $this->collections->mapCollectionCardPayloads(collect($seriesContext['items'] ?? [])->filter(fn (Collection $item) => (int) $item->id !== (int) $collection->id), false), ], 'submitEndpoint' => route('collections.submissions.store', ['collection' => $collection->id]), 'commentsEndpoint' => route('collections.comments.store', ['collection' => $collection->id]), 'submissionArtworkOptions' => $viewer ? $this->collections->getSubmissionArtworkOptions($viewer) : [], 'canSubmit' => $collection->canReceiveSubmissionsFrom($viewer), 'canComment' => $collection->canReceiveCommentsFrom($viewer), 'profileCollectionsUrl' => route('profile.tab', [ 'username' => strtolower((string) $user->username), 'tab' => 'collections', ]), 'featuredCollectionsUrl' => route('collections.featured'), 'reportEndpoint' => $viewer ? route('api.reports.store') : null, 'seo' => [ 'title' => $collection->is_featured ? sprintf('Featured: %s by %s — Skinbase Nova', $collection->title, $collection->displayOwnerName()) : sprintf('%s by %s — Skinbase Nova', $collection->title, $collection->displayOwnerName()), 'description' => $collection->summary ?: $collection->description ?: sprintf('Explore the %s collection by %s on Skinbase Nova.', $collection->title, $collection->displayOwnerName()), 'canonical' => $collectionPayload['public_url'], 'og_image' => $collectionPayload['cover_image'], 'robots' => $collection->visibility === Collection::VISIBILITY_PUBLIC ? 'index,follow' : 'noindex,nofollow', ], ])->rootView('collections'); } public function showSeries(Request $request, string $seriesKey) { $seriesCollections = $this->series->publicSeriesItems($seriesKey); abort_if($seriesCollections->isEmpty(), 404); $mappedCollections = $this->collections->mapCollectionCardPayloads($seriesCollections, false); $leadCollection = $mappedCollections[0] ?? null; $ownersCount = $seriesCollections->pluck('user_id')->unique()->count(); $artworksCount = $seriesCollections->sum(fn (Collection $collection) => (int) $collection->artworks_count); $latestActivityAt = $seriesCollections->max('last_activity_at'); $seriesMeta = $this->series->metadataFor($seriesCollections); $seriesTitle = $seriesMeta['title'] ?: collect($mappedCollections) ->pluck('campaign_label') ->filter() ->first(); $seriesDescription = $seriesMeta['description']; return Inertia::render('Collection/CollectionSeriesShow', [ 'seriesKey' => $seriesKey, 'title' => $seriesTitle ?: sprintf('Collection Series: %s', str_replace(['-', '_'], ' ', $seriesKey)), 'description' => $seriesDescription ?: sprintf('Browse the %s collection series in sequence, with public entries ordered for smooth navigation across related curations.', $seriesKey), 'collections' => $mappedCollections, 'leadCollection' => $leadCollection, 'stats' => [ 'collections' => $seriesCollections->count(), 'owners' => $ownersCount, 'artworks' => $artworksCount, 'latest_activity_at' => optional($latestActivityAt)?->toISOString(), ], 'seo' => [ 'title' => sprintf('Series: %s — Skinbase Nova', $seriesKey), 'description' => sprintf('Explore the %s collection series on Skinbase Nova.', $seriesKey), 'canonical' => route('collections.series.show', ['seriesKey' => $seriesKey]), 'robots' => 'index,follow', ], ])->rootView('collections'); } }