TenantAtlas/app/Jobs/ApplyBackupScheduleRetentionJob.php

165 lines
6.1 KiB
PHP

<?php
namespace App\Jobs;
use App\Models\BackupSchedule;
use App\Models\BackupSet;
use App\Models\OperationRun;
use App\Services\Intune\AuditLogger;
use App\Services\Settings\SettingsResolver;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
class ApplyBackupScheduleRetentionJob implements ShouldQueue
{
use Queueable;
public function __construct(public int $backupScheduleId) {}
public function handle(AuditLogger $auditLogger, SettingsResolver $settingsResolver): void
{
$schedule = BackupSchedule::query()
->with(['tenant.workspace'])
->find($this->backupScheduleId);
if (! $schedule || ! $schedule->tenant) {
return;
}
$operationRun = OperationRun::query()->create([
'workspace_id' => (int) $schedule->tenant->workspace_id,
'tenant_id' => (int) $schedule->tenant_id,
'user_id' => null,
'initiator_name' => 'System',
'type' => 'backup_schedule_retention',
'status' => OperationRunStatus::Running->value,
'outcome' => OperationRunOutcome::Pending->value,
'run_identity_hash' => hash('sha256', (string) $schedule->tenant_id.':backup_schedule_retention:'.$schedule->id.':'.Str::uuid()->toString()),
'context' => [
'backup_schedule_id' => (int) $schedule->id,
],
'started_at' => now(),
]);
$keepLast = $schedule->retention_keep_last;
$retentionFloor = 1;
if ($schedule->tenant->workspace instanceof \App\Models\Workspace) {
$resolvedFloor = $settingsResolver->resolveValue(
workspace: $schedule->tenant->workspace,
domain: 'backup',
key: 'retention_min_floor',
tenant: $schedule->tenant,
);
if (is_numeric($resolvedFloor)) {
$retentionFloor = max(1, (int) $resolvedFloor);
}
}
if ($keepLast === null && $schedule->tenant->workspace instanceof \App\Models\Workspace) {
$resolvedDefault = $settingsResolver->resolveValue(
workspace: $schedule->tenant->workspace,
domain: 'backup',
key: 'retention_keep_last_default',
tenant: $schedule->tenant,
);
if (is_numeric($resolvedDefault)) {
$keepLast = (int) $resolvedDefault;
}
}
if (! is_numeric($keepLast)) {
$keepLast = 30;
}
$keepLast = (int) $keepLast;
if ($keepLast < $retentionFloor) {
$keepLast = $retentionFloor;
}
/** @var Collection<int, int> $keepBackupSetIds */
$keepBackupSetIds = OperationRun::query()
->where('tenant_id', (int) $schedule->tenant_id)
->where('type', 'backup_schedule_run')
->where('status', OperationRunStatus::Completed->value)
->where('context->backup_schedule_id', (int) $schedule->id)
->whereNotNull('context->backup_set_id')
->orderByDesc('completed_at')
->orderByDesc('id')
->limit($keepLast)
->get()
->map(fn (OperationRun $run): ?int => is_numeric(data_get($run->context, 'backup_set_id')) ? (int) data_get($run->context, 'backup_set_id') : null)
->filter(fn (?int $id): bool => is_int($id) && $id > 0)
->values();
/** @var Collection<int, int> $allBackupSetIds */
$allBackupSetIds = OperationRun::query()
->where('tenant_id', (int) $schedule->tenant_id)
->where('type', 'backup_schedule_run')
->where('status', OperationRunStatus::Completed->value)
->where('context->backup_schedule_id', (int) $schedule->id)
->whereNotNull('context->backup_set_id')
->get()
->map(fn (OperationRun $run): ?int => is_numeric(data_get($run->context, 'backup_set_id')) ? (int) data_get($run->context, 'backup_set_id') : null)
->filter(fn (?int $id): bool => is_int($id) && $id > 0)
->unique()
->values();
/** @var Collection<int, int> $deleteBackupSetIds */
$deleteBackupSetIds = $allBackupSetIds
->reject(fn (int $backupSetId): bool => $keepBackupSetIds->contains($backupSetId))
->values();
$deletedCount = 0;
if ($deleteBackupSetIds->isNotEmpty()) {
BackupSet::query()
->where('tenant_id', $schedule->tenant_id)
->whereIn('id', $deleteBackupSetIds->all())
->whereNull('deleted_at')
->chunkById(200, function (Collection $sets) use (&$deletedCount): void {
foreach ($sets as $set) {
$set->delete();
$deletedCount++;
}
});
}
$operationRun->update([
'status' => OperationRunStatus::Completed->value,
'outcome' => OperationRunOutcome::Succeeded->value,
'summary_counts' => [
'total' => (int) $deleteBackupSetIds->count(),
'processed' => (int) $deleteBackupSetIds->count(),
'succeeded' => $deletedCount,
'failed' => max(0, (int) $deleteBackupSetIds->count() - $deletedCount),
'updated' => $deletedCount,
],
'completed_at' => now(),
]);
$auditLogger->log(
tenant: $schedule->tenant,
action: 'backup_schedule.retention_applied',
resourceType: 'backup_schedule',
resourceId: (string) $schedule->id,
status: 'success',
context: [
'metadata' => [
'keep_last' => $keepLast,
'retention_floor' => $retentionFloor,
'deleted_backup_sets' => $deletedCount,
'operation_run_id' => (int) $operationRun->getKey(),
],
],
);
}
}