139 lines
4.4 KiB
PHP
Executable File
139 lines
4.4 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\File;
|
|
|
|
class NetgesconRestoreRecordCommand extends Command
|
|
{
|
|
protected $signature = 'netgescon:restore-record
|
|
{table : Tabella da ripristinare}
|
|
{pk : Valore chiave primaria (default id)}
|
|
{--snapshot-id= : Snapshot specifico (default ultimo)}
|
|
{--pk-column=id : Nome colonna PK}
|
|
{--apply : Esegue il ripristino (altrimenti solo preview)}';
|
|
|
|
protected $description = 'Ripristina una singola scrittura da snapshot differenziale pre-update.';
|
|
|
|
public function handle(): int
|
|
{
|
|
$table = trim((string) $this->argument('table'));
|
|
$pk = trim((string) $this->argument('pk'));
|
|
$pkColumn = trim((string) $this->option('pk-column')) ?: 'id';
|
|
|
|
if ($table === '' || $pk === '') {
|
|
$this->error('Parametri table/pk obbligatori.');
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$snapshotDir = $this->resolveSnapshotDir();
|
|
if ($snapshotDir === null) {
|
|
$this->error('Snapshot non trovato.');
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$file = $snapshotDir . '/db/' . $table . '.ndjson';
|
|
if (! is_file($file)) {
|
|
$this->error('Export tabella non trovato nello snapshot: ' . $file);
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$record = $this->findRecordByPk($file, $pk);
|
|
if (! is_array($record)) {
|
|
$this->error('Record non trovato nello snapshot per PK=' . $pk);
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$this->line('Snapshot: ' . $snapshotDir);
|
|
$this->line('Tabella: ' . $table . ' · PK: ' . $pkColumn . '=' . $pk);
|
|
$this->line('Hash: ' . (string) ($record['hash'] ?? '-'));
|
|
|
|
$data = is_array($record['data'] ?? null) ? (array) $record['data'] : [];
|
|
if (! array_key_exists($pkColumn, $data)) {
|
|
$data[$pkColumn] = $pk;
|
|
}
|
|
|
|
if (! (bool) $this->option('apply')) {
|
|
$this->warn('Preview mode: nessuna scrittura eseguita. Usa --apply per ripristinare.');
|
|
$this->line(json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
if (! DB::getSchemaBuilder()->hasTable($table)) {
|
|
$this->error('Tabella non esistente su DB corrente: ' . $table);
|
|
return self::FAILURE;
|
|
}
|
|
|
|
DB::table($table)->updateOrInsert(
|
|
[$pkColumn => $data[$pkColumn]],
|
|
$data
|
|
);
|
|
|
|
$this->info('Record ripristinato con successo.');
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function resolveSnapshotDir(): ?string
|
|
{
|
|
$snapshotId = trim((string) $this->option('snapshot-id'));
|
|
$root = storage_path('app/update-backups/snapshots');
|
|
|
|
if ($snapshotId !== '') {
|
|
$dir = $root . '/' . $snapshotId;
|
|
return is_dir($dir) ? $dir : null;
|
|
}
|
|
|
|
$latest = storage_path('app/update-backups/latest-manifest.json');
|
|
if (is_file($latest)) {
|
|
$decoded = json_decode((string) @file_get_contents($latest), true);
|
|
if (is_array($decoded) && isset($decoded['snapshot_dir']) && is_string($decoded['snapshot_dir']) && is_dir($decoded['snapshot_dir'])) {
|
|
return $decoded['snapshot_dir'];
|
|
}
|
|
}
|
|
|
|
if (! is_dir($root)) {
|
|
return null;
|
|
}
|
|
|
|
$dirs = array_values(array_filter(scandir($root) ?: [], function (string $name) use ($root): bool {
|
|
return $name !== '.' && $name !== '..' && is_dir($root . '/' . $name);
|
|
}));
|
|
|
|
rsort($dirs, SORT_NATURAL);
|
|
if ($dirs === []) {
|
|
return null;
|
|
}
|
|
|
|
return $root . '/' . $dirs[0];
|
|
}
|
|
|
|
/** @return array<string,mixed>|null */
|
|
private function findRecordByPk(string $file, string $pk): ?array
|
|
{
|
|
$handle = fopen($file, 'rb');
|
|
if (! is_resource($handle)) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
while (($line = fgets($handle)) !== false) {
|
|
$decoded = json_decode((string) $line, true);
|
|
if (! is_array($decoded)) {
|
|
continue;
|
|
}
|
|
|
|
$candidate = (string) ($decoded['pk'] ?? '');
|
|
if ($candidate === $pk) {
|
|
return $decoded;
|
|
}
|
|
}
|
|
} finally {
|
|
fclose($handle);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|