Files
Mitgliederverwaltung/lib/Command/BackupRestoreCommand.php
T
shahondin1624 d48e2b7d9d feat: v0.2.0 — backup system, self-update with Ed25519 signing, column pickers, import fixes
Major features:
- Full backup & restore system (JSON snapshots of all 20 tables + settings)
  - Web UI, REST API, OCC CLI commands, scheduled background job
- Self-update from Gitea releases with Ed25519 signature verification
- Configurable column visibility on all data tables (persisted via localStorage)

Fixes:
- NC admin group fallback for PermissionService (IGroupManager)
- Bundle import inline error correction (editable error rows)

New files: BackupService, BackupSettingsService, BackupController, BackupJob,
SelfUpdateService, 4 OCC commands, ColumnPicker component, Backup.vue,
Ed25519 signing scripts, signature verification tests (18 tests)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:11:51 +02:00

83 lines
3.2 KiB
PHP

<?php
declare(strict_types=1);
namespace OCA\Mitgliederverwaltung\Command;
use OCA\Mitgliederverwaltung\Service\BackupService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;
class BackupRestoreCommand extends Command {
private BackupService $backupService;
public function __construct(BackupService $backupService) {
parent::__construct();
$this->backupService = $backupService;
}
protected function configure(): void {
$this->setName('mitgliederverwaltung:backup:restore')
->setDescription('Restore Mitgliederverwaltung from a backup file')
->addArgument('filename', InputArgument::REQUIRED, 'Backup filename')
->addOption('password', 'p', InputOption::VALUE_OPTIONAL, 'Password if backup is encrypted')
->addOption('yes', 'y', InputOption::VALUE_NONE, 'Skip confirmation prompt');
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$filename = $input->getArgument('filename');
$password = $input->getOption('password');
$skipConfirm = $input->getOption('yes');
// Show backup info
$info = $this->backupService->getBackupInfo($filename);
if ($info === null) {
$output->writeln('<error>Backup not found: ' . $filename . '</error>');
return Command::FAILURE;
}
$output->writeln('Backup info:');
$output->writeln(' Filename: ' . $info['filename']);
$output->writeln(' Created: ' . $info['createdAt']);
$output->writeln(' Version: ' . ($info['appVersion'] ?? 'unknown'));
$output->writeln(' Tables: ' . count($info['tables']));
$totalRows = 0;
foreach ($info['tables'] as $count) {
$totalRows += $count;
}
$output->writeln(' Total rows: ' . $totalRows);
$output->writeln('');
// Confirmation
if (!$skipConfirm) {
$output->writeln('<comment>WARNING: This will DELETE ALL existing data and replace it with the backup.</comment>');
$helper = $this->getHelper('question');
$question = new ConfirmationQuestion('Continue? [y/N] ', false);
if (!$helper->ask($input, $output, $question)) {
$output->writeln('Restore cancelled.');
return Command::SUCCESS;
}
}
$output->writeln('Restoring...');
try {
$result = $this->backupService->restoreBackup($filename, $password);
$output->writeln('');
$output->writeln('<info>Restore completed successfully!</info>');
$output->writeln(' Tables restored: ' . $result['restoredTables']);
$output->writeln(' Total rows: ' . $result['totalRows']);
return Command::SUCCESS;
} catch (\Exception $e) {
$output->writeln('<error>Restore failed: ' . $e->getMessage() . '</error>');
return Command::FAILURE;
}
}
}