- Created ExampleTest and RoutesTest for basic application response and route testing. - Implemented IsolatedDatabaseTestCase for isolated database testing. - Added unit tests for various models and services including Contract, CPE, and LeadDetail. - Developed request validation tests for CreateClientRequest, UpdateClientRequest, and ContractRequest. - Introduced Vite configuration for asset management in the Laravel application.
112 lines
4.0 KiB
PHP
112 lines
4.0 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
class CleanMinioBackups extends Command
|
|
{
|
|
protected $signature = 'backup:clean-minio';
|
|
protected $description = 'Keeps only the last 5 backups in MinIO, safely removing older files';
|
|
|
|
public function handle()
|
|
{
|
|
$disk = Storage::disk(env('FILESYSTEM_DISK', 's3'));
|
|
$folder = 'laravel-backup';
|
|
$keep = 5;
|
|
|
|
try {
|
|
$this->info('Connecting to MinIO bucket...');
|
|
|
|
$allFiles = $disk->allFiles($folder);
|
|
|
|
if (empty($allFiles)) {
|
|
$this->info('No backup files found in the directory.');
|
|
return 0;
|
|
}
|
|
|
|
$this->info('Found ' . count($allFiles) . ' files. Processing...');
|
|
|
|
$zipFiles = collect($allFiles)
|
|
->filter(fn($file) => str_ends_with($file, '.zip'))
|
|
->values();
|
|
|
|
if ($zipFiles->isEmpty()) {
|
|
$this->info('No ZIP backup files found.');
|
|
return 0;
|
|
}
|
|
|
|
$this->info('Found ' . $zipFiles->count() . ' ZIP files.');
|
|
|
|
$backups = $zipFiles->map(function ($file) use ($disk) {
|
|
try {
|
|
$lastModified = (int) $disk->lastModified($file);
|
|
return [
|
|
'file' => $file,
|
|
'lastModified' => $lastModified,
|
|
'date' => date('Y-m-d H:i:s', $lastModified),
|
|
'estimated' => false
|
|
];
|
|
} catch (\Exception $e) {
|
|
$this->warn("Cannot read metadata for {$file}, using filename for sorting");
|
|
|
|
preg_match('/(\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2})/', $file, $matches);
|
|
|
|
$timestamp = isset($matches[1])
|
|
? \DateTime::createFromFormat('Y-m-d-H-i-s', $matches[1])->getTimestamp()
|
|
: time();
|
|
|
|
return [
|
|
'file' => $file,
|
|
'lastModified' => $timestamp,
|
|
'date' => date('Y-m-d H:i:s', $timestamp),
|
|
'estimated' => true
|
|
];
|
|
}
|
|
});
|
|
|
|
$backups = $backups->sortByDesc('lastModified')->values();
|
|
|
|
$totalBackups = $backups->count();
|
|
$toDelete = $backups->slice($keep);
|
|
|
|
$this->info("Valid backups found: {$totalBackups}");
|
|
if ($toDelete->isEmpty()) {
|
|
$this->info("Only {$totalBackups} backup(s) found (less than {$keep}), nothing to delete.");
|
|
return 0;
|
|
}
|
|
|
|
$this->info("Keeping the latest {$keep}, deleting {$toDelete->count()} old backups.");
|
|
|
|
$this->info("Backups scheduled for deletion:");
|
|
foreach ($toDelete as $item) {
|
|
$this->info(" - {$item['file']} ({$item['date']})" . ($item['estimated'] ? ' [estimated]' : ''));
|
|
}
|
|
|
|
$deletedCount = 0;
|
|
foreach ($toDelete as $item) {
|
|
try {
|
|
$disk->delete($item['file']);
|
|
$this->info("✓ Deleted: {$item['file']}" . ($item['estimated'] ? ' [estimated]' : ''));
|
|
$deletedCount++;
|
|
} catch (\Exception $e) {
|
|
$this->error("✗ Failed to delete {$item['file']}: {$e->getMessage()}");
|
|
}
|
|
}
|
|
|
|
$this->info("Cleanup finished. Deleted {$deletedCount} files. Kept the last {$keep} backups.");
|
|
|
|
} catch (\Exception $e) {
|
|
$this->error("Failed to access backup directory: {$e->getMessage()}");
|
|
$this->error('Debug info:');
|
|
$this->error('Bucket: ' . env('AWS_BUCKET'));
|
|
$this->error('Endpoint: ' . env('AWS_ENDPOINT'));
|
|
$this->error('Key: ' . substr(env('AWS_ACCESS_KEY_ID', ''), 0, 5) . '...');
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|