optimizations
This commit is contained in:
154
app/Services/CollectionHistoryService.php
Normal file
154
app/Services/CollectionHistoryService.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Collection;
|
||||
use App\Models\CollectionHistory;
|
||||
use App\Models\User;
|
||||
use App\Services\CollectionHealthService;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class CollectionHistoryService
|
||||
{
|
||||
/**
|
||||
* @var array<string, array<int, string>>
|
||||
*/
|
||||
private const RESTORABLE_FIELDS = [
|
||||
'updated' => ['title', 'visibility', 'lifecycle_state'],
|
||||
'workflow_updated' => ['workflow_state', 'program_key', 'partner_key', 'experiment_key', 'placement_eligibility'],
|
||||
'partner_program_metadata_updated' => ['partner_key', 'trust_tier', 'promotion_tier', 'sponsorship_state', 'ownership_domain', 'commercial_review_state', 'legal_review_state'],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly CollectionHealthService $health,
|
||||
) {
|
||||
}
|
||||
|
||||
public function record(Collection $collection, ?User $actor, string $actionType, ?string $summary = null, ?array $before = null, ?array $after = null): void
|
||||
{
|
||||
CollectionHistory::query()->create([
|
||||
'collection_id' => $collection->id,
|
||||
'actor_user_id' => $actor?->id,
|
||||
'action_type' => $actionType,
|
||||
'summary' => $summary,
|
||||
'before_json' => $before,
|
||||
'after_json' => $after,
|
||||
'created_at' => now(),
|
||||
]);
|
||||
|
||||
$collection->forceFill([
|
||||
'history_count' => (int) $collection->history_count + 1,
|
||||
])->save();
|
||||
}
|
||||
|
||||
public function historyFor(Collection $collection, int $perPage = 40): LengthAwarePaginator
|
||||
{
|
||||
return CollectionHistory::query()
|
||||
->with('actor:id,username,name')
|
||||
->where('collection_id', $collection->id)
|
||||
->orderByDesc('created_at')
|
||||
->paginate(max(10, min($perPage, 80)));
|
||||
}
|
||||
|
||||
public function mapPaginator(LengthAwarePaginator $paginator): array
|
||||
{
|
||||
return [
|
||||
'data' => collect($paginator->items())->map(function (CollectionHistory $entry): array {
|
||||
$restorableFields = $this->restorablePayload($entry);
|
||||
|
||||
return [
|
||||
'id' => (int) $entry->id,
|
||||
'action_type' => $entry->action_type,
|
||||
'summary' => $entry->summary,
|
||||
'before' => $entry->before_json,
|
||||
'after' => $entry->after_json,
|
||||
'can_restore' => $restorableFields !== [],
|
||||
'restore_fields' => array_keys($restorableFields),
|
||||
'created_at' => $entry->created_at?->toISOString(),
|
||||
'actor' => $entry->actor ? [
|
||||
'id' => (int) $entry->actor->id,
|
||||
'username' => $entry->actor->username,
|
||||
'name' => $entry->actor->name,
|
||||
] : null,
|
||||
];
|
||||
})->values()->all(),
|
||||
'meta' => [
|
||||
'current_page' => $paginator->currentPage(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'total' => $paginator->total(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function canRestore(CollectionHistory $entry): bool
|
||||
{
|
||||
return $this->restorablePayload($entry) !== [];
|
||||
}
|
||||
|
||||
public function restore(Collection $collection, CollectionHistory $entry, ?User $actor = null): Collection
|
||||
{
|
||||
if ((int) $entry->collection_id !== (int) $collection->id) {
|
||||
throw ValidationException::withMessages([
|
||||
'history' => 'This history entry does not belong to the selected collection.',
|
||||
]);
|
||||
}
|
||||
|
||||
$payload = $this->restorablePayload($entry);
|
||||
|
||||
if ($payload === []) {
|
||||
throw ValidationException::withMessages([
|
||||
'history' => 'This history entry cannot be restored safely.',
|
||||
]);
|
||||
}
|
||||
|
||||
$working = $collection->fresh();
|
||||
$before = [];
|
||||
|
||||
foreach (array_keys($payload) as $field) {
|
||||
$before[$field] = $working->{$field};
|
||||
}
|
||||
|
||||
$working->forceFill($payload);
|
||||
|
||||
$healthPayload = $this->health->evaluate($working);
|
||||
|
||||
$working->forceFill(array_merge($healthPayload, $payload, [
|
||||
'last_activity_at' => now(),
|
||||
]))->save();
|
||||
|
||||
$fresh = $working->fresh();
|
||||
|
||||
$this->record(
|
||||
$fresh,
|
||||
$actor,
|
||||
'history_restored',
|
||||
sprintf('Collection restored from history entry #%d.', $entry->id),
|
||||
array_merge(['restored_history_id' => (int) $entry->id], $before),
|
||||
array_merge(['restored_history_id' => (int) $entry->id], $payload),
|
||||
);
|
||||
|
||||
return $fresh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function restorablePayload(CollectionHistory $entry): array
|
||||
{
|
||||
$before = is_array($entry->before_json) ? $entry->before_json : [];
|
||||
$fields = self::RESTORABLE_FIELDS[$entry->action_type] ?? [];
|
||||
$payload = [];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
if (array_key_exists($field, $before)) {
|
||||
$payload[$field] = $before[$field];
|
||||
}
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user