TenantAtlas/app/Console/Commands/TenantpilotPurgeNonPersistentData.php
2026-01-19 18:50:11 +01:00

165 lines
5.1 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\AuditLog;
use App\Models\BackupItem;
use App\Models\BackupSchedule;
use App\Models\BackupScheduleRun;
use App\Models\BackupSet;
use App\Models\OperationRun;
use App\Models\Policy;
use App\Models\PolicyVersion;
use App\Models\RestoreRun;
use App\Models\Tenant;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use RuntimeException;
class TenantpilotPurgeNonPersistentData extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'tenantpilot:purge-nonpersistent
{tenant? : Tenant id / tenant_id / external_id (defaults to current tenant)}
{--all : Purge for all tenants}
{--force : Actually delete rows}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Permanently delete non-persistent (regeneratable) tenant data like policies, backups, runs, and logs.';
/**
* Execute the console command.
*/
public function handle(): int
{
$tenants = $this->resolveTenants();
if ($tenants->isEmpty()) {
$this->error('No tenants selected. Provide {tenant} or use --all.');
return self::FAILURE;
}
$isDryRun = ! (bool) $this->option('force');
if ($isDryRun) {
$this->warn('Dry run: no rows will be deleted. Re-run with --force to apply.');
} else {
$this->warn('This will PERMANENTLY delete non-persistent tenant data.');
if ($this->input->isInteractive() && ! $this->confirm('Proceed?', false)) {
$this->info('Aborted.');
return self::SUCCESS;
}
}
foreach ($tenants as $tenant) {
$counts = $this->countsForTenant($tenant);
$this->line('');
$this->info("Tenant: {$tenant->id} ({$tenant->name})");
$this->table(
['Table', 'Rows'],
collect($counts)
->map(fn (int $count, string $table) => [$table, $count])
->values()
->all(),
);
if ($isDryRun) {
continue;
}
DB::transaction(function () use ($tenant): void {
BackupScheduleRun::query()
->where('tenant_id', $tenant->id)
->delete();
BackupSchedule::query()
->where('tenant_id', $tenant->id)
->delete();
OperationRun::query()
->where('tenant_id', $tenant->id)
->delete();
AuditLog::query()
->where('tenant_id', $tenant->id)
->delete();
RestoreRun::withTrashed()
->where('tenant_id', $tenant->id)
->forceDelete();
BackupItem::withTrashed()
->where('tenant_id', $tenant->id)
->forceDelete();
BackupSet::withTrashed()
->where('tenant_id', $tenant->id)
->forceDelete();
PolicyVersion::withTrashed()
->where('tenant_id', $tenant->id)
->forceDelete();
Policy::query()
->where('tenant_id', $tenant->id)
->delete();
});
$this->info('Purged.');
}
return self::SUCCESS;
}
private function resolveTenants()
{
if ((bool) $this->option('all')) {
return Tenant::query()->get();
}
$tenantArg = $this->argument('tenant');
if ($tenantArg !== null && $tenantArg !== '') {
$tenant = Tenant::query()->forTenant($tenantArg)->first();
return $tenant ? collect([$tenant]) : collect();
}
try {
return collect([Tenant::current()]);
} catch (RuntimeException) {
return collect();
}
}
/**
* @return array<string,int>
*/
private function countsForTenant(Tenant $tenant): array
{
return [
'backup_schedule_runs' => BackupScheduleRun::query()->where('tenant_id', $tenant->id)->count(),
'backup_schedules' => BackupSchedule::query()->where('tenant_id', $tenant->id)->count(),
'operation_runs' => OperationRun::query()->where('tenant_id', $tenant->id)->count(),
'audit_logs' => AuditLog::query()->where('tenant_id', $tenant->id)->count(),
'restore_runs' => RestoreRun::withTrashed()->where('tenant_id', $tenant->id)->count(),
'backup_items' => BackupItem::withTrashed()->where('tenant_id', $tenant->id)->count(),
'backup_sets' => BackupSet::withTrashed()->where('tenant_id', $tenant->id)->count(),
'policy_versions' => PolicyVersion::withTrashed()->where('tenant_id', $tenant->id)->count(),
'policies' => Policy::query()->where('tenant_id', $tenant->id)->count(),
];
}
}