189 lines
7.6 KiB
PHP
189 lines
7.6 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs;
|
|
|
|
use App\Models\AnnoTeocratico;
|
|
use App\Models\Assegnazione;
|
|
use App\Models\Campagna;
|
|
use App\Models\Proclamatore;
|
|
use App\Models\Setting;
|
|
use App\Models\Territorio;
|
|
use App\Models\Tipologia;
|
|
use App\Models\User;
|
|
use App\Models\Zona;
|
|
use App\Services\BackupArchive;
|
|
use App\Services\BackupOperationState;
|
|
use Database\Seeders\RolesAndPermissionsSeeder;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* Riporta l'installazione allo stato di primo avvio: nessun dato gestionale,
|
|
* nessun file caricato, impostazioni ai valori di default, ruoli e permessi
|
|
* riseminati.
|
|
*
|
|
* Non e' reversibile e non fa backup da solo: parte solo dopo che l'archivio
|
|
* generato da CreateBackupArchive risulta scaricato per intero (vedi
|
|
* BackupController::file), cosi' non esiste una via che azzeri senza che una
|
|
* copia sia gia' al sicuro sul computer di chi ha premuto il pulsante.
|
|
*/
|
|
class ResetApplication implements ShouldQueue
|
|
{
|
|
use Dispatchable;
|
|
use InteractsWithQueue;
|
|
use Queueable;
|
|
use SerializesModels;
|
|
|
|
public int $tries = 1;
|
|
|
|
public int $timeout = 570;
|
|
|
|
/**
|
|
* @param int $actorId L'amministratore che ha chiesto l'azzeramento.
|
|
* E' l'unico account che sopravvive: un'installazione
|
|
* appena creata ha un amministratore, e cancellarli
|
|
* tutti lascerebbe l'applicazione senza nessuno che
|
|
* possa accedere fino al riavvio del container.
|
|
*/
|
|
public function __construct(
|
|
public string $operationId,
|
|
public int $actorId,
|
|
) {
|
|
}
|
|
|
|
public function handle(BackupOperationState $stato, BackupArchive $archivio): void
|
|
{
|
|
$stato->markRunning($this->operationId);
|
|
|
|
try {
|
|
// I file per primi, prima di toccare il database: svuotare il disco
|
|
// e' l'unico passo che puo' fallire per cause esterne (permessi sul
|
|
// volume), e fallire qui non ha ancora cancellato niente. Nell'ordine
|
|
// inverso un errore sui file lascerebbe un'applicazione con il
|
|
// database azzerato e il disco pieno di PDF che non appartengono
|
|
// piu' a nessun territorio.
|
|
$stato->fase($this->operationId, 'Cancellazione dei file caricati', 1);
|
|
$fileRimossi = $archivio->svuotaDiscoPubblico();
|
|
$stato->avanza($this->operationId, 1);
|
|
$stato->log($this->operationId, $fileRimossi . ' file rimossi dal disco.');
|
|
|
|
$stato->fase($this->operationId, 'Cancellazione dei dati gestionali', 8);
|
|
|
|
DB::transaction(function () use ($stato) {
|
|
$passi = [
|
|
'Assegnazioni' => fn () => Assegnazione::query()->forceDelete(),
|
|
'Campagne' => fn () => Campagna::query()->delete(),
|
|
'Territori' => fn () => Territorio::query()->withTrashed()->forceDelete(),
|
|
'Proclamatori' => fn () => Proclamatore::query()->withTrashed()->forceDelete(),
|
|
'Tipologie' => fn () => Tipologia::query()->delete(),
|
|
'Zone' => fn () => Zona::query()->delete(),
|
|
'Anni teocratici' => fn () => AnnoTeocratico::query()->delete(),
|
|
'Impostazioni' => fn () => Setting::query()->delete(),
|
|
];
|
|
|
|
$fatti = 0;
|
|
foreach ($passi as $etichetta => $azione) {
|
|
$azione();
|
|
$stato->avanza($this->operationId, ++$fatti);
|
|
$stato->log($this->operationId, $etichetta . ': azzerate.');
|
|
}
|
|
});
|
|
|
|
// Il mass delete non emette eventi, quindi l'istanza memoizzata di
|
|
// Setting punta ancora alla riga cancellata: senza questo, il
|
|
// firstOrCreate piu' sotto tornerebbe il fantasma invece di
|
|
// ricreare la riga.
|
|
Setting::flushCache();
|
|
|
|
$stato->fase($this->operationId, 'Rimozione degli altri utenti');
|
|
$utentiRimossi = $this->rimuoviAltriUtenti($stato);
|
|
|
|
// Il registro si svuota *dopo* le cancellazioni, non prima: gli
|
|
// altri utenti sono modelli con LogsActivity, quindi eliminarli
|
|
// scrive righe nuove che una pulizia anticipata non intercetta e
|
|
// che resterebbero li' come unica traccia — un residuo, non una
|
|
// scelta. Svuotato per ultimo, l'unica riga che sopravvive e'
|
|
// quella scritta apposta qui sotto.
|
|
$stato->fase($this->operationId, 'Pulizia del registro attivita\'', 1);
|
|
DB::table('activity_log')->delete();
|
|
activity()
|
|
->causedBy(User::find($this->actorId))
|
|
->log('Applicazione azzerata e riportata allo stato iniziale');
|
|
$stato->avanza($this->operationId, 1);
|
|
|
|
$stato->fase($this->operationId, 'Ripristino di ruoli e impostazioni', 3);
|
|
|
|
// Ruoli e permessi non vengono cancellati, ma il seeder e' additivo
|
|
// e idempotente: rieseguirlo garantisce il set di partenza anche se
|
|
// qualcuno ne aveva rimosso qualcuno a mano.
|
|
(new RolesAndPermissionsSeeder())->run();
|
|
$stato->avanza($this->operationId, 1);
|
|
|
|
Setting::instance();
|
|
$stato->avanza($this->operationId, 2);
|
|
|
|
$attore = User::find($this->actorId);
|
|
if ($attore && ! $attore->hasRole('amministratore')) {
|
|
$attore->assignRole('amministratore');
|
|
}
|
|
$stato->avanza($this->operationId, 3);
|
|
} catch (\Throwable $e) {
|
|
Log::error('Azzeramento dell\'applicazione non riuscito.', ['exception' => $e]);
|
|
$stato->markFailed($this->operationId, $e->getMessage());
|
|
|
|
return;
|
|
}
|
|
|
|
$messaggio = sprintf(
|
|
'Applicazione azzerata: dati gestionali rimossi, %d file cancellati, %d utenti eliminati. '
|
|
. 'Resta il tuo account di amministratore.',
|
|
$fileRimossi,
|
|
$utentiRimossi
|
|
);
|
|
|
|
$stato->log($this->operationId, $messaggio);
|
|
$stato->markCompleted($this->operationId, ['messaggio' => $messaggio]);
|
|
}
|
|
|
|
/**
|
|
* Vedi CreateBackupArchive::failed(): chiude l'operazione quando il job
|
|
* muore per cause che handle() non intercetta, liberando il posto unico.
|
|
*/
|
|
public function failed(?\Throwable $e): void
|
|
{
|
|
app(BackupOperationState::class)->markFailed(
|
|
$this->operationId,
|
|
($e?->getMessage() ?: 'Azzeramento interrotto (timeout o errore del worker).')
|
|
. ' Controlla lo stato dei dati prima di ritentare.'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Cancella ogni utente tranne chi ha chiesto l'azzeramento.
|
|
*
|
|
* Uno alla volta e via Eloquent, non con un delete di massa: il trait
|
|
* HasRoles di Spatie stacca ruoli e permessi nell'evento `deleting`, che un
|
|
* delete di massa non fa scattare — resterebbero righe orfane in
|
|
* model_has_roles pronte a essere riassegnate al primo utente che riceve un
|
|
* id gia' usato.
|
|
*/
|
|
private function rimuoviAltriUtenti(BackupOperationState $stato): int
|
|
{
|
|
$altri = User::query()->where('id', '!=', $this->actorId)->get();
|
|
$stato->avanza($this->operationId, 0, $altri->count());
|
|
|
|
$rimossi = 0;
|
|
foreach ($altri as $utente) {
|
|
$utente->delete();
|
|
$stato->avanza($this->operationId, ++$rimossi);
|
|
}
|
|
|
|
return $rimossi;
|
|
}
|
|
}
|