authorizeStaff($request); $assignments = $this->programming->assignments(); if ($request->expectsJson()) { return response()->json([ 'ok' => true, 'assignments' => $assignments->map(fn (CollectionProgramAssignment $assignment): array => $this->mapAssignment($assignment))->values()->all(), ]); } $collectionOptions = Collection::query() ->with(['user:id,username,name', 'coverArtwork:id,user_id,title,slug,hash,thumb_ext,published_at,is_public,is_approved,deleted_at']) ->where(function ($query): void { $query->where(function ($public): void { $public->where('visibility', Collection::VISIBILITY_PUBLIC) ->whereIn('lifecycle_state', [Collection::LIFECYCLE_PUBLISHED, Collection::LIFECYCLE_FEATURED]) ->where('moderation_status', Collection::MODERATION_ACTIVE); })->orWhereNotNull('program_key'); }) ->orderByDesc('ranking_score') ->orderByDesc('updated_at') ->limit(40) ->get(); $programKeyOptions = $assignments->pluck('program_key') ->merge($collectionOptions->pluck('program_key')) ->filter(fn ($value): bool => is_string($value) && $value !== '') ->unique() ->sort() ->values() ->all(); return Inertia::render('Collection/CollectionStaffProgramming', [ 'assignments' => $assignments->map(fn (CollectionProgramAssignment $assignment): array => $this->mapAssignment($assignment))->values()->all(), 'collectionOptions' => $this->collections->mapCollectionCardPayloads($collectionOptions, true), 'programKeyOptions' => $programKeyOptions, 'mergeQueue' => $this->programming->mergeQueue(true), 'observabilitySummary' => $this->observability->summary(), 'historyPattern' => route('settings.collections.history', ['collection' => '__COLLECTION__']), 'viewer' => [ 'isAdmin' => $this->isAdmin($request), ], 'endpoints' => [ 'store' => route('staff.collections.programs.store'), 'updatePattern' => route('staff.collections.programs.update', ['program' => '__PROGRAM__']), 'publicProgramPattern' => route('collections.program.show', ['programKey' => '__PROGRAM__']), 'preview' => route('staff.collections.surfaces.preview'), 'refreshEligibility' => route('staff.collections.eligibility.refresh'), 'duplicateScan' => route('staff.collections.duplicate-scan'), 'refreshRecommendations' => route('staff.collections.recommendation-refresh'), 'metadataUpdate' => route('staff.collections.metadata.update'), 'canonicalizeCandidate' => route('staff.collections.merge-queue.canonicalize'), 'mergeCandidate' => route('staff.collections.merge-queue.merge'), 'rejectCandidate' => route('staff.collections.merge-queue.reject'), 'managePattern' => route('settings.collections.show', ['collection' => '__COLLECTION__']), 'surfaces' => route('settings.collections.surfaces.index'), ], 'seo' => [ 'title' => 'Collection Programming — Skinbase Nova', 'description' => 'Staff programming tools for assignments, previews, eligibility diagnostics, and recommendation refreshes.', 'canonical' => route('staff.collections.programming'), 'robots' => 'noindex,follow', ], ])->rootView('collections'); } public function storeProgram(CollectionProgramAssignmentRequest $request): JsonResponse { $this->authorizeStaff($request); $assignment = $this->programming->upsertAssignment($request->validated(), $request->user()); return response()->json([ 'ok' => true, 'assignment' => $this->mapAssignment($assignment), ]); } public function updateProgram(CollectionProgramAssignmentRequest $request, CollectionProgramAssignment $program): JsonResponse { $this->authorizeStaff($request); $payload = $request->validated(); $payload['id'] = (int) $program->id; $assignment = $this->programming->upsertAssignment($payload, $request->user()); return response()->json([ 'ok' => true, 'assignment' => $this->mapAssignment($assignment), ]); } public function preview(CollectionProgrammingPreviewRequest $request): JsonResponse { $this->authorizeStaff($request); $payload = $request->validated(); return response()->json([ 'ok' => true, 'collections' => $this->collections->mapCollectionCardPayloads( $this->programming->previewProgram((string) $payload['program_key'], (int) ($payload['limit'] ?? 12)), true, ), ]); } public function refreshEligibility(CollectionProgrammingCollectionRequest $request): JsonResponse { $this->authorizeStaff($request); $collection = $this->resolveCollection($request); return response()->json([ 'ok' => true, 'queued' => true, 'result' => $this->backgroundJobs->dispatchHealthRefresh($collection, $request->user()), ]); } public function duplicateScan(CollectionProgrammingCollectionRequest $request): JsonResponse { $this->authorizeStaff($request); $collection = $this->resolveCollection($request); return response()->json([ 'ok' => true, 'queued' => true, 'result' => $this->backgroundJobs->dispatchDuplicateScan($collection, $request->user()), ]); } public function refreshRecommendations(CollectionProgrammingCollectionRequest $request): JsonResponse { $this->authorizeStaff($request); $collection = $this->resolveCollection($request); return response()->json([ 'ok' => true, 'queued' => true, 'result' => $this->backgroundJobs->dispatchRecommendationRefresh($collection, $request->user()), ]); } public function updateMetadata(CollectionProgrammingMetadataRequest $request): JsonResponse { $this->authorizeStaff($request); $payload = $request->validated(); $collection = Collection::query()->findOrFail((int) $payload['collection_id']); $result = $this->programming->syncHooks($collection, $payload, $request->user()); return response()->json([ 'ok' => true, 'message' => 'Experiment and program governance hooks updated.', 'collection' => $this->collections->mapCollectionCardPayloads([$result['collection']->loadMissing('user')], true)[0], 'diagnostics' => $result['diagnostics'], ]); } public function canonicalizeCandidate(CollectionProgrammingMergePairRequest $request): JsonResponse { $this->authorizeStaff($request); [$source, $target] = $this->resolveMergePair($request); $payload = $this->programming->canonicalizePair($source, $target, $request->user()); return response()->json([ 'ok' => true, 'message' => 'Canonical target updated from the staff merge queue.', 'source' => $this->collections->mapCollectionCardPayloads([$payload['source']->loadMissing('user')], true)[0], 'target' => $this->collections->mapCollectionCardPayloads([$payload['target']->loadMissing('user')], true)[0], 'mergeQueue' => $payload['mergeQueue'], ]); } public function mergeCandidate(CollectionProgrammingMergePairRequest $request): JsonResponse { $this->authorizeStaff($request); [$source, $target] = $this->resolveMergePair($request); $payload = $this->programming->mergePair($source, $target, $request->user()); return response()->json([ 'ok' => true, 'message' => 'Collections merged from the staff merge queue.', 'source' => $this->collections->mapCollectionCardPayloads([$payload['source']->loadMissing('user')], true)[0], 'target' => $this->collections->mapCollectionCardPayloads([$payload['target']->loadMissing('user')], true)[0], 'attached_artwork_ids' => $payload['attached_artwork_ids'], 'mergeQueue' => $payload['mergeQueue'], ]); } public function rejectCandidate(CollectionProgrammingMergePairRequest $request): JsonResponse { $this->authorizeStaff($request); [$source, $target] = $this->resolveMergePair($request); $payload = $this->programming->rejectPair($source, $target, $request->user()); return response()->json([ 'ok' => true, 'message' => 'Duplicate candidate dismissed from the staff merge queue.', 'source' => $this->collections->mapCollectionCardPayloads([$payload['source']->loadMissing('user')], true)[0], 'target' => $this->collections->mapCollectionCardPayloads([$payload['target']->loadMissing('user')], true)[0], 'mergeQueue' => $payload['mergeQueue'], ]); } private function resolveCollection(CollectionProgrammingCollectionRequest $request): ?Collection { $payload = $request->validated(); if (! isset($payload['collection_id'])) { return null; } return Collection::query()->find((int) $payload['collection_id']); } /** * @return array{0: Collection, 1: Collection} */ private function resolveMergePair(CollectionProgrammingMergePairRequest $request): array { $payload = $request->validated(); return [ Collection::query()->findOrFail((int) $payload['source_collection_id']), Collection::query()->findOrFail((int) $payload['target_collection_id']), ]; } private function mapAssignment(CollectionProgramAssignment $assignment): array { $assignment->loadMissing(['collection.user', 'creator']); return [ 'id' => (int) $assignment->id, 'program_key' => (string) $assignment->program_key, 'campaign_key' => $assignment->campaign_key, 'placement_scope' => $assignment->placement_scope, 'starts_at' => optional($assignment->starts_at)?->toISOString(), 'ends_at' => optional($assignment->ends_at)?->toISOString(), 'priority' => (int) $assignment->priority, 'notes' => $assignment->notes, 'collection' => $this->collections->mapCollectionCardPayloads([$assignment->collection], true)[0], 'creator' => $assignment->creator ? [ 'id' => (int) $assignment->creator->id, 'username' => (string) $assignment->creator->username, 'name' => $assignment->creator->name, ] : null, ]; } private function authorizeStaff(Request $request): void { $user = $request->user(); abort_unless($user && ($user->isAdmin() || $user->isModerator()), 403); } private function isAdmin(Request $request): bool { $user = $request->user(); return $user !== null && $user->isAdmin(); } }