165 lines
6.1 KiB
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(),
|
|
],
|
|
],
|
|
);
|
|
}
|
|
}
|