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; } }