169 lines
6.6 KiB
PHP
169 lines
6.6 KiB
PHP
<?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.',
|
|
};
|
|
}
|
|
} |