optimizations
This commit is contained in:
247
app/Services/CollectionProgrammingService.php
Normal file
247
app/Services/CollectionProgrammingService.php
Normal file
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Collection;
|
||||
use App\Models\CollectionProgramAssignment;
|
||||
use App\Models\User;
|
||||
use App\Services\CollectionCanonicalService;
|
||||
use App\Services\CollectionExperimentService;
|
||||
use App\Services\CollectionHealthService;
|
||||
use App\Services\CollectionHistoryService;
|
||||
use App\Services\CollectionObservabilityService;
|
||||
use App\Services\CollectionPartnerProgramService;
|
||||
use App\Services\CollectionWorkflowService;
|
||||
use Illuminate\Support\Collection as SupportCollection;
|
||||
|
||||
class CollectionProgrammingService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CollectionHealthService $health,
|
||||
private readonly CollectionRankingService $ranking,
|
||||
private readonly CollectionMergeService $merge,
|
||||
private readonly CollectionCanonicalService $canonical,
|
||||
private readonly CollectionWorkflowService $workflow,
|
||||
private readonly CollectionExperimentService $experiments,
|
||||
private readonly CollectionPartnerProgramService $partnerPrograms,
|
||||
private readonly CollectionObservabilityService $observability,
|
||||
) {
|
||||
}
|
||||
|
||||
public function diagnostics(Collection $collection): array
|
||||
{
|
||||
return $this->observability->diagnostics($collection->fresh());
|
||||
}
|
||||
|
||||
public function syncHooks(Collection $collection, array $attributes, ?User $actor = null): array
|
||||
{
|
||||
$workflowAttributes = array_intersect_key($attributes, array_flip([
|
||||
'placement_eligibility',
|
||||
]));
|
||||
|
||||
$experimentAttributes = array_intersect_key($attributes, array_flip([
|
||||
'experiment_key',
|
||||
'experiment_treatment',
|
||||
'placement_variant',
|
||||
'ranking_mode_variant',
|
||||
'collection_pool_version',
|
||||
'test_label',
|
||||
]));
|
||||
|
||||
$partnerAttributes = array_intersect_key($attributes, array_flip([
|
||||
'partner_key',
|
||||
'trust_tier',
|
||||
'promotion_tier',
|
||||
'sponsorship_state',
|
||||
'ownership_domain',
|
||||
'commercial_review_state',
|
||||
'legal_review_state',
|
||||
]));
|
||||
|
||||
$updated = $collection->fresh();
|
||||
|
||||
if ($workflowAttributes !== []) {
|
||||
$updated = $this->workflow->update($updated->loadMissing('user'), $workflowAttributes, $actor);
|
||||
}
|
||||
|
||||
if ($experimentAttributes !== []) {
|
||||
$updated = $this->experiments->sync($updated->loadMissing('user'), $experimentAttributes, $actor);
|
||||
}
|
||||
|
||||
if ($partnerAttributes !== []) {
|
||||
$updated = $this->partnerPrograms->sync($updated->loadMissing('user'), $partnerAttributes, $actor);
|
||||
}
|
||||
|
||||
$updated = $updated->fresh();
|
||||
|
||||
return [
|
||||
'collection' => $updated,
|
||||
'diagnostics' => $this->observability->diagnostics($updated),
|
||||
];
|
||||
}
|
||||
|
||||
public function mergeQueue(bool $ownerView = true, int $pendingLimit = 8, int $recentLimit = 8): array
|
||||
{
|
||||
return $this->merge->queueOverview($ownerView, $pendingLimit, $recentLimit);
|
||||
}
|
||||
|
||||
public function canonicalizePair(Collection $source, Collection $target, ?User $actor = null): array
|
||||
{
|
||||
$updatedSource = $this->canonical->designate($source->loadMissing('user'), $target->loadMissing('user'), $actor);
|
||||
|
||||
return [
|
||||
'source' => $updatedSource,
|
||||
'target' => $target->fresh(),
|
||||
'mergeQueue' => $this->mergeQueue(true),
|
||||
];
|
||||
}
|
||||
|
||||
public function mergePair(Collection $source, Collection $target, ?User $actor = null): array
|
||||
{
|
||||
$result = $this->merge->mergeInto($source->loadMissing('user'), $target->loadMissing('user'), $actor);
|
||||
|
||||
return [
|
||||
'source' => $result['source'],
|
||||
'target' => $result['target'],
|
||||
'attached_artwork_ids' => $result['attached_artwork_ids'],
|
||||
'mergeQueue' => $this->mergeQueue(true),
|
||||
];
|
||||
}
|
||||
|
||||
public function rejectPair(Collection $source, Collection $target, ?User $actor = null): array
|
||||
{
|
||||
$updatedSource = $this->merge->rejectCandidate($source->loadMissing('user'), $target->loadMissing('user'), $actor);
|
||||
|
||||
return [
|
||||
'source' => $updatedSource,
|
||||
'target' => $target->fresh(),
|
||||
'mergeQueue' => $this->mergeQueue(true),
|
||||
];
|
||||
}
|
||||
|
||||
public function assignments(): SupportCollection
|
||||
{
|
||||
return CollectionProgramAssignment::query()
|
||||
->with(['collection.user:id,username,name', 'creator:id,username,name'])
|
||||
->orderBy('program_key')
|
||||
->orderByDesc('priority')
|
||||
->orderBy('id')
|
||||
->get();
|
||||
}
|
||||
|
||||
public function upsertAssignment(array $attributes, ?User $actor = null): CollectionProgramAssignment
|
||||
{
|
||||
$assignmentId = isset($attributes['id']) ? (int) $attributes['id'] : null;
|
||||
|
||||
$payload = [
|
||||
'collection_id' => (int) $attributes['collection_id'],
|
||||
'program_key' => (string) $attributes['program_key'],
|
||||
'campaign_key' => $attributes['campaign_key'] ?? null,
|
||||
'placement_scope' => $attributes['placement_scope'] ?? null,
|
||||
'starts_at' => $attributes['starts_at'] ?? null,
|
||||
'ends_at' => $attributes['ends_at'] ?? null,
|
||||
'priority' => (int) ($attributes['priority'] ?? 0),
|
||||
'notes' => $attributes['notes'] ?? null,
|
||||
'created_by_user_id' => $actor?->id,
|
||||
];
|
||||
|
||||
if ($assignmentId > 0) {
|
||||
$assignment = CollectionProgramAssignment::query()->findOrFail($assignmentId);
|
||||
$assignment->fill($payload)->save();
|
||||
} else {
|
||||
$assignment = CollectionProgramAssignment::query()->create($payload);
|
||||
}
|
||||
|
||||
$collection = Collection::query()->findOrFail((int) $payload['collection_id']);
|
||||
$collection->forceFill(['program_key' => $payload['program_key']])->save();
|
||||
|
||||
app(CollectionHistoryService::class)->record(
|
||||
$collection->fresh(),
|
||||
$actor,
|
||||
$assignmentId > 0 ? 'program_assignment_updated' : 'program_assignment_created',
|
||||
'Collection program assignment updated.',
|
||||
null,
|
||||
$payload
|
||||
);
|
||||
|
||||
return $assignment->fresh(['collection.user', 'creator']);
|
||||
}
|
||||
|
||||
public function previewProgram(string $programKey, int $limit = 12): SupportCollection
|
||||
{
|
||||
return Collection::query()
|
||||
->public()
|
||||
->where('program_key', $programKey)
|
||||
->where('placement_eligibility', true)
|
||||
->with(['user:id,username,name', 'coverArtwork:id,user_id,title,slug,hash,thumb_ext,published_at,is_public,is_approved,deleted_at'])
|
||||
->orderByDesc('ranking_score')
|
||||
->orderByDesc('health_score')
|
||||
->limit(max(1, min($limit, 24)))
|
||||
->get();
|
||||
}
|
||||
|
||||
public function refreshEligibility(?Collection $collection = null, ?User $actor = null): array
|
||||
{
|
||||
$items = $collection ? collect([$collection]) : Collection::query()->whereNotNull('program_key')->get();
|
||||
|
||||
$results = $items->map(function (Collection $item) use ($actor): array {
|
||||
$fresh = $this->health->refresh($item, $actor, 'programming-eligibility');
|
||||
|
||||
return [
|
||||
'collection_id' => (int) $fresh->id,
|
||||
'placement_eligibility' => (bool) $fresh->placement_eligibility,
|
||||
'health_state' => $fresh->health_state,
|
||||
'readiness_state' => $fresh->readiness_state,
|
||||
];
|
||||
})->values();
|
||||
|
||||
return [
|
||||
'count' => $results->count(),
|
||||
'items' => $results->all(),
|
||||
];
|
||||
}
|
||||
|
||||
public function refreshRecommendations(?Collection $collection = null): array
|
||||
{
|
||||
$items = $collection ? collect([$collection]) : Collection::query()->where('placement_eligibility', true)->limit(100)->get();
|
||||
|
||||
$results = $items->map(function (Collection $item): array {
|
||||
$fresh = $this->ranking->refresh($item);
|
||||
|
||||
return [
|
||||
'collection_id' => (int) $fresh->id,
|
||||
'recommendation_tier' => $fresh->recommendation_tier,
|
||||
'ranking_bucket' => $fresh->ranking_bucket,
|
||||
'search_boost_tier' => $fresh->search_boost_tier,
|
||||
];
|
||||
})->values();
|
||||
|
||||
return [
|
||||
'count' => $results->count(),
|
||||
'items' => $results->all(),
|
||||
];
|
||||
}
|
||||
|
||||
public function duplicateScan(?Collection $collection = null): array
|
||||
{
|
||||
$items = $collection ? collect([$collection]) : Collection::query()->whereNull('canonical_collection_id')->limit(100)->get();
|
||||
|
||||
$results = $items->map(function (Collection $item): array {
|
||||
return [
|
||||
'collection_id' => (int) $item->id,
|
||||
'candidates' => $this->merge->duplicateCandidates($item)->map(fn (Collection $candidate) => [
|
||||
'id' => (int) $candidate->id,
|
||||
'title' => (string) $candidate->title,
|
||||
'slug' => (string) $candidate->slug,
|
||||
])->values()->all(),
|
||||
];
|
||||
})->filter(fn (array $row): bool => $row['candidates'] !== [])->values();
|
||||
|
||||
return [
|
||||
'count' => $results->count(),
|
||||
'items' => $results->all(),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user