manualRelatedCollections() ->with(['user:id,username,name', 'coverArtwork:id,user_id,title,slug,hash,thumb_ext,published_at,is_public,is_approved,deleted_at']) ->get(); } public function publicLinkedCollections(Collection $collection, int $limit = 6): EloquentCollection { return $collection->manualRelatedCollections() ->public() ->with(['user:id,username,name', 'coverArtwork:id,user_id,title,slug,hash,thumb_ext,published_at,is_public,is_approved,deleted_at']) ->limit(max(1, min($limit, 12))) ->get(); } public function manageableLinkOptions(Collection $collection, User $actor, int $limit = 24): EloquentCollection { $linkedIds = $collection->manualRelatedCollections()->pluck('collections.id')->map(static fn ($id) => (int) $id)->all(); return Collection::query() ->with([ 'user:id,username,name', 'members', 'coverArtwork:id,user_id,title,slug,hash,thumb_ext,published_at,is_public,is_approved,deleted_at', ]) ->where('id', '!=', $collection->id) ->whereNotIn('id', $linkedIds) ->orderByDesc('updated_at') ->get() ->filter(fn (Collection $candidate): bool => $candidate->canBeManagedBy($actor)) ->take(max(1, min($limit, 48))) ->values(); } /** * @param array $relatedCollectionIds */ public function syncLinks(Collection $collection, User $actor, array $relatedCollectionIds): Collection { $normalizedIds = collect($relatedCollectionIds) ->map(static fn ($id) => (int) $id) ->filter(static fn (int $id): bool => $id > 0) ->reject(fn (int $id): bool => $id === (int) $collection->id) ->unique() ->values(); $targets = $normalizedIds->isEmpty() ? collect() : Collection::query() ->with('members') ->whereIn('id', $normalizedIds->all()) ->get(); if ($targets->count() !== $normalizedIds->count()) { throw ValidationException::withMessages([ 'related_collection_ids' => 'Choose valid collections to link.', ]); } if ($targets->contains(fn (Collection $target): bool => ! $target->canBeManagedBy($actor))) { throw ValidationException::withMessages([ 'related_collection_ids' => 'You can only link collections that you can manage.', ]); } $before = $collection->manualRelatedCollections()->pluck('collections.id')->map(static fn ($id) => (int) $id)->all(); DB::transaction(function () use ($collection, $actor, $normalizedIds): void { DB::table('collection_related_links') ->where('collection_id', $collection->id) ->delete(); if ($normalizedIds->isEmpty()) { return; } $now = now(); DB::table('collection_related_links')->insert($normalizedIds->values()->map( fn (int $relatedId, int $index): array => [ 'collection_id' => $collection->id, 'related_collection_id' => $relatedId, 'sort_order' => $index, 'created_by_user_id' => $actor->id, 'created_at' => $now, 'updated_at' => $now, ] )->all()); }); $fresh = $collection->fresh(['user.profile', 'coverArtwork']); app(CollectionHistoryService::class)->record($fresh, $actor, 'linked_collections_updated', 'Manual linked collections updated.', [ 'related_collection_ids' => $before, ], [ 'related_collection_ids' => $normalizedIds->all(), ]); return $fresh; } }