optimizations

This commit is contained in:
2026-03-28 19:15:39 +01:00
parent 0b25d9570a
commit cab4fbd83e
509 changed files with 1016804 additions and 1605 deletions

View File

@@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Models\Collection;
use App\Models\User;
use App\Services\CollectionBackgroundJobService;
use App\Services\CollectionCampaignService;
use App\Services\CollectionService;
use App\Services\CollectionWorkflowService;
use Illuminate\Validation\ValidationException;
class CollectionBulkActionService
{
public function __construct(
private readonly CollectionService $collections,
private readonly CollectionCampaignService $campaigns,
private readonly CollectionWorkflowService $workflow,
private readonly CollectionBackgroundJobService $backgroundJobs,
) {
}
public function apply(User $user, array $payload): array
{
$action = (string) $payload['action'];
$collectionIds = collect($payload['collection_ids'] ?? [])
->map(static fn ($id): int => (int) $id)
->filter(static fn (int $id): bool => $id > 0)
->unique()
->values();
if ($collectionIds->isEmpty()) {
throw ValidationException::withMessages([
'collection_ids' => 'Select at least one collection.',
]);
}
$collections = Collection::query()
->ownedBy((int) $user->id)
->whereIn('id', $collectionIds->all())
->get()
->keyBy(fn (Collection $collection): int => (int) $collection->id);
if ($collections->count() !== $collectionIds->count()) {
throw ValidationException::withMessages([
'collection_ids' => 'One or more selected collections are unavailable.',
]);
}
$items = [];
$updatedCollections = $collectionIds->map(function (int $collectionId) use ($collections, $user, $payload, $action, &$items): Collection {
$collection = $collections->get($collectionId);
if (! $collection instanceof Collection) {
throw ValidationException::withMessages([
'collection_ids' => 'One or more selected collections are unavailable.',
]);
}
$updated = match ($action) {
'archive' => $this->archive($collection->loadMissing('user'), $user),
'assign_campaign' => $this->assignCampaign($collection->loadMissing('user'), $payload, $user),
'update_lifecycle' => $this->updateLifecycle($collection->loadMissing('user'), $payload, $user),
'request_ai_review' => $this->requestAiReview($collection->loadMissing('user'), $user, $items),
'mark_editorial_review' => $this->markEditorialReview($collection->loadMissing('user'), $user),
default => throw ValidationException::withMessages([
'action' => 'Unsupported bulk action.',
]),
};
if ($action !== 'request_ai_review') {
$items[] = [
'collection_id' => (int) $updated->id,
'action' => $action,
];
}
return $updated;
});
return [
'action' => $action,
'count' => $updatedCollections->count(),
'items' => $items,
'collections' => $updatedCollections,
'message' => $this->messageFor($action, $updatedCollections->count()),
];
}
private function archive(Collection $collection, User $user): Collection
{
return $this->collections->updateCollection($collection, [
'lifecycle_state' => Collection::LIFECYCLE_ARCHIVED,
'archived_at' => now(),
], $user);
}
private function assignCampaign(Collection $collection, array $payload, User $user): Collection
{
return $this->campaigns->updateCampaign($collection, [
'campaign_key' => (string) $payload['campaign_key'],
'campaign_label' => $payload['campaign_label'] ?? null,
], $user);
}
private function updateLifecycle(Collection $collection, array $payload, User $user): Collection
{
$lifecycleState = (string) $payload['lifecycle_state'];
$attributes = [
'lifecycle_state' => $lifecycleState,
];
if ($lifecycleState === Collection::LIFECYCLE_ARCHIVED) {
$attributes['archived_at'] = now();
} else {
$attributes['archived_at'] = null;
}
if ($lifecycleState === Collection::LIFECYCLE_PUBLISHED) {
$attributes['visibility'] = Collection::VISIBILITY_PUBLIC;
$attributes['published_at'] = $collection->published_at ?? now();
$attributes['expired_at'] = null;
}
if ($lifecycleState === Collection::LIFECYCLE_DRAFT) {
$attributes['visibility'] = Collection::VISIBILITY_PRIVATE;
$attributes['expired_at'] = null;
$attributes['unpublished_at'] = null;
}
return $this->collections->updateCollection($collection, $attributes, $user);
}
private function requestAiReview(Collection $collection, User $user, array &$items): Collection
{
$result = $this->backgroundJobs->dispatchQualityRefresh($collection, $user);
$items[] = [
'collection_id' => (int) $collection->id,
'job' => $result['job'] ?? 'quality_refresh',
'status' => $result['status'] ?? 'queued',
];
return $collection->fresh()->loadMissing('user');
}
private function markEditorialReview(Collection $collection, User $user): Collection
{
if ($collection->workflow_state === Collection::WORKFLOW_IN_REVIEW) {
return $collection->fresh()->loadMissing('user');
}
return $this->workflow->update($collection, [
'workflow_state' => Collection::WORKFLOW_IN_REVIEW,
], $user);
}
private function messageFor(string $action, int $count): string
{
return match ($action) {
'archive' => $count === 1 ? 'Collection archived.' : sprintf('%d collections archived.', $count),
'assign_campaign' => $count === 1 ? 'Campaign assigned.' : sprintf('Campaign assigned to %d collections.', $count),
'update_lifecycle' => $count === 1 ? 'Collection lifecycle updated.' : sprintf('Lifecycle updated for %d collections.', $count),
'request_ai_review' => $count === 1 ? 'AI review requested.' : sprintf('AI review requested for %d collections.', $count),
'mark_editorial_review' => $count === 1 ? 'Collection marked for editorial review.' : sprintf('%d collections marked for editorial review.', $count),
default => 'Bulk action completed.',
};
}
}