optimizations
This commit is contained in:
221
app/Http/Controllers/User/ProfileCollectionController.php
Normal file
221
app/Http/Controllers/User/ProfileCollectionController.php
Normal file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\User;
|
||||
|
||||
use App\Events\Collections\CollectionViewed;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Collection;
|
||||
use App\Models\User;
|
||||
use App\Services\CollectionCollaborationService;
|
||||
use App\Services\CollectionCommentService;
|
||||
use App\Services\CollectionDiscoveryService;
|
||||
use App\Services\CollectionFollowService;
|
||||
use App\Services\CollectionLinkService;
|
||||
use App\Services\CollectionLikeService;
|
||||
use App\Services\CollectionLinkedCollectionsService;
|
||||
use App\Services\CollectionRecommendationService;
|
||||
use App\Services\CollectionSaveService;
|
||||
use App\Services\CollectionSeriesService;
|
||||
use App\Services\CollectionSubmissionService;
|
||||
use App\Services\CollectionService;
|
||||
use App\Support\UsernamePolicy;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class ProfileCollectionController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CollectionService $collections,
|
||||
private readonly CollectionLikeService $likes,
|
||||
private readonly CollectionFollowService $follows,
|
||||
private readonly CollectionSaveService $saves,
|
||||
private readonly CollectionCollaborationService $collaborators,
|
||||
private readonly CollectionSubmissionService $submissions,
|
||||
private readonly CollectionCommentService $comments,
|
||||
private readonly CollectionDiscoveryService $discovery,
|
||||
private readonly CollectionRecommendationService $recommendations,
|
||||
private readonly CollectionLinkService $entityLinks,
|
||||
private readonly CollectionLinkedCollectionsService $linkedCollections,
|
||||
private readonly CollectionSeriesService $series,
|
||||
) {
|
||||
}
|
||||
|
||||
public function show(Request $request, string $username, string $slug)
|
||||
{
|
||||
$normalized = UsernamePolicy::normalize($username);
|
||||
$user = User::query()->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');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user