ec759b89c1
- ListBackups API now returns snapshots alongside backups - DeleteBackup API accepts snapshot- prefixed names - New "Database Snapshots" panel in admin UI with delete buttons - i18n: English + Hebrew translations Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
74 lines
1.7 KiB
PHP
74 lines
1.7 KiB
PHP
<?php
|
|
|
|
namespace Espo\Modules\DataMigration\Api;
|
|
|
|
use Espo\Core\Api\Action;
|
|
use Espo\Core\Api\Request;
|
|
use Espo\Core\Api\Response;
|
|
use Espo\Core\Api\ResponseComposer;
|
|
use Espo\Core\Exceptions\BadRequest;
|
|
use Espo\Core\Exceptions\Forbidden;
|
|
use Espo\Entities\User;
|
|
|
|
class DeleteBackup implements Action
|
|
{
|
|
public function __construct(
|
|
private User $user
|
|
) {}
|
|
|
|
public function process(Request $request): Response
|
|
{
|
|
if (!$this->user->isAdmin()) {
|
|
throw new Forbidden();
|
|
}
|
|
|
|
$body = $request->getParsedBody();
|
|
$backupName = $body->backupName ?? null;
|
|
|
|
if (!$backupName) {
|
|
throw new BadRequest('backupName is required.');
|
|
}
|
|
|
|
// Sanitize to prevent directory traversal
|
|
$safeName = basename($backupName);
|
|
|
|
if (!str_starts_with($safeName, 'datamigration-') && !str_starts_with($safeName, 'snapshot-')) {
|
|
throw new Forbidden('Invalid backup name.');
|
|
}
|
|
|
|
$fullPath = 'data/backups/' . $safeName;
|
|
|
|
if (!is_dir($fullPath)) {
|
|
throw new BadRequest("Backup not found: {$safeName}");
|
|
}
|
|
|
|
$this->deleteDirectory($fullPath);
|
|
|
|
return ResponseComposer::json([
|
|
'success' => true,
|
|
'deleted' => $safeName,
|
|
]);
|
|
}
|
|
|
|
private function deleteDirectory(string $dir): void
|
|
{
|
|
$items = scandir($dir);
|
|
|
|
foreach ($items as $item) {
|
|
if ($item === '.' || $item === '..') {
|
|
continue;
|
|
}
|
|
|
|
$path = $dir . '/' . $item;
|
|
|
|
if (is_dir($path)) {
|
|
$this->deleteDirectory($path);
|
|
} else {
|
|
unlink($path);
|
|
}
|
|
}
|
|
|
|
rmdir($dir);
|
|
}
|
|
}
|