From 6900d942a4079160c48c25dfb8de5c26f9aa27de Mon Sep 17 00:00:00 2001 From: DevEnv nis2-agile Date: Sat, 13 Jun 2026 08:47:09 +0200 Subject: [PATCH] [DEMO] resetDataset cablato a DemoSeedService (reset reale dataset demo/sandbox) - application/services/DemoSeedService.php: logica seed/clone golden #151 -> 996001/996002 (riusabile) - DemoController::resetDataset: re-seed reale via service (?scope=demo|sandbox|default both), non piu stub - scripts/seed-demo-dataset.php: thin CLI wrapper sul service Testato prod: POST /api/demo/reset-dataset (X-Internal-Key) scope=sandbox -> 200, 996002 ri-seedata. Co-Authored-By: Claude Opus 4.8 --- application/controllers/DemoController.php | 20 ++- application/services/DemoSeedService.php | 134 +++++++++++++++++++++ scripts/seed-demo-dataset.php | 98 +++------------ 3 files changed, 169 insertions(+), 83 deletions(-) create mode 100644 application/services/DemoSeedService.php diff --git a/application/controllers/DemoController.php b/application/controllers/DemoController.php index 278e9fa..0910cf9 100644 --- a/application/controllers/DemoController.php +++ b/application/controllers/DemoController.php @@ -10,6 +10,7 @@ */ require_once __DIR__ . '/BaseController.php'; +require_once __DIR__ . '/../services/DemoSeedService.php'; class DemoController extends BaseController { @@ -180,12 +181,21 @@ class DemoController extends BaseController $this->jsonError('Non autorizzato', 401, 'UNAUTHORIZED'); } - // TODO Fase 1 (increment dataset): DELETE + re-seed org demo 996001 da seed idempotente. + // Reset idempotente del dataset demo (clona la golden #151 in 996001/996002). + // ?scope=demo → solo 996001 (read-only) | ?scope=sandbox → solo 996002 | default: entrambe. + $scope = (string) $this->getParam('scope', ''); + $targets = $scope === 'demo' ? [996001 => DemoSeedService::TARGETS[996001]] + : ($scope === 'sandbox' ? [996002 => DemoSeedService::TARGETS[996002]] + : DemoSeedService::TARGETS); + + $result = DemoSeedService::seed($targets); + $failed = array_filter($result, fn($v) => str_starts_with($v, 'errore')); + $this->jsonSuccess([ - 'reset' => 'pending_implementation', - 'company_ids' => [self::DEMO_ORG_ID], - 'note' => 'Logica di reset dataset demo in arrivo (seed isolato org 996001).', - ], 'Reset demo (stub)'); + 'reset' => empty($failed) ? 'done' : 'partial', + 'company_ids' => array_keys($result), + 'detail' => $result, + ], empty($failed) ? 'Dataset demo reimpostato' : 'Reset demo parziale (vedi detail)', empty($failed) ? 200 : 207); } // ═══ Helpers ═════════════════════════════════════════════════════════════ diff --git a/application/services/DemoSeedService.php b/application/services/DemoSeedService.php new file mode 100644 index 0000000..0b2ae73 --- /dev/null +++ b/application/services/DemoSeedService.php @@ -0,0 +1,134 @@ + 'DataCore Demo S.r.l.', + 996002 => 'NIS2 Sandbox Formazione S.r.l.', + ]; + + private const USERCOLS = ['owner_user_id','responsible_user_id','assigned_to','reported_by','created_by','uploaded_by','relevance_assessed_by','assessed_by','approved_by','reviewed_by','closed_by','user_id']; + /** parent → child (FK figlio) da remappare al nuovo parent */ + private const PC = [ + ['assessments','assessment_responses','assessment_id'], + ['risks','risk_treatments','risk_id'], + ['incidents','incident_timeline','incident_id'], + ['training_courses','training_assignments','course_id'], + ['policies','policy_versions','policy_id'], + ]; + private const SIMPLE = ['compliance_controls','suppliers','assets']; + + /** + * Esegue il seed/reset. $targets: mappa orgId=>nome (default: entrambe). + * @return array esito per org ("ok uid=N" | "errore: ...") + */ + public static function seed(?array $targets = null): array + { + $targets = $targets ?: self::TARGETS; + $pdo = Database::getInstance(); + $out = []; + $pdo->exec('SET FOREIGN_KEY_CHECKS=0'); + foreach ($targets as $T => $NAME) { + $T = (int) $T; + $pdo->beginTransaction(); + try { + self::purge($pdo, $T); + $org = Database::fetchOne('SELECT * FROM organizations WHERE id = ?', [self::SRC_ORG]); + self::copyRow($pdo, 'organizations', $org, ['id' => $T, 'name' => $NAME], 0); + + $uid = (int) Database::insert('users', [ + 'email' => "demo-$T@nis2-demo.local", 'full_name' => "Demo NIS2 $T", 'role' => 'org_admin', + 'password_hash' => password_hash(bin2hex(random_bytes(12)), PASSWORD_DEFAULT), + 'is_active' => 1, 'must_change_password' => 0, 'password_version' => 1, + ]); + $pdo->prepare('INSERT INTO user_organizations (user_id,organization_id,role) VALUES (?,?,?)')->execute([$uid, $T, 'org_admin']); + + foreach (self::PC as [$par, $chi, $fk]) { + if (!self::tableExists($pdo, $par)) continue; + foreach (Database::fetchAll("SELECT * FROM `$par` WHERE organization_id = ? ORDER BY id", [self::SRC_ORG]) as $r) { + $newPar = self::copyRow($pdo, $par, $r, ['organization_id' => $T], $uid); + if (self::tableExists($pdo, $chi)) { + foreach (Database::fetchAll("SELECT * FROM `$chi` WHERE `$fk` = ? ORDER BY id", [$r['id']]) as $cr) { + $ov = [$fk => $newPar]; + if (in_array('organization_id', self::cols($pdo, $chi), true)) $ov['organization_id'] = $T; + self::copyRow($pdo, $chi, $cr, $ov, $uid); + } + } + } + } + foreach (self::SIMPLE as $t) { + if (!self::tableExists($pdo, $t)) continue; + foreach (Database::fetchAll("SELECT * FROM `$t` WHERE organization_id = ? ORDER BY id", [self::SRC_ORG]) as $r) { + self::copyRow($pdo, $t, $r, ['organization_id' => $T], $uid); + } + } + $pdo->commit(); + $out[$T] = "ok (uid=$uid)"; + } catch (Throwable $ex) { + $pdo->rollBack(); + $out[$T] = 'errore: ' . $ex->getMessage(); + } + } + $pdo->exec('SET FOREIGN_KEY_CHECKS=1'); + return $out; + } + + private static function purge(PDO $pdo, int $T): void + { + foreach (self::PC as [$par, $chi, $fk]) { + if (self::tableExists($pdo, $chi)) $pdo->exec("DELETE FROM `$chi` WHERE `$fk` IN (SELECT id FROM `$par` WHERE organization_id = $T)"); + } + foreach (array_merge(array_column(self::PC, 0), self::SIMPLE) as $t) { + if (self::tableExists($pdo, $t)) $pdo->exec("DELETE FROM `$t` WHERE organization_id = $T"); + } + $pdo->exec("DELETE FROM user_organizations WHERE organization_id = $T"); + $pdo->exec("DELETE FROM users WHERE email = 'demo-$T@nis2-demo.local'"); + $pdo->exec("DELETE FROM organizations WHERE id = $T"); + } + + private static function cols(PDO $pdo, string $t): array + { + static $cache = []; + if (!isset($cache[$t])) { + $cache[$t] = $pdo->query("SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='$t' ORDER BY ORDINAL_POSITION")->fetchAll(PDO::FETCH_COLUMN); + } + return $cache[$t]; + } + + private static function tableExists(PDO $pdo, string $t): bool + { + return (int) $pdo->query("SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='$t'")->fetchColumn() > 0; + } + + /** Copia una riga in $table applicando $overrides e rimappando le colonne utente a $uid. */ + private static function copyRow(PDO $pdo, string $table, array $row, array $overrides, int $uid): int + { + $data = []; + foreach (self::cols($pdo, $table) as $c) { + if ($c === 'id') { if (array_key_exists('id', $overrides)) $data['id'] = $overrides['id']; continue; } + if (array_key_exists($c, $overrides)) { $data[$c] = $overrides[$c]; continue; } + $v = $row[$c] ?? null; + if (in_array($c, self::USERCOLS, true) && $v !== null) $v = $uid; + $data[$c] = $v; + } + $cl = implode(',', array_map(fn($x) => "`$x`", array_keys($data))); + $ph = implode(',', array_fill(0, count($data), '?')); + $pdo->prepare("INSERT INTO `$table` ($cl) VALUES ($ph)")->execute(array_values($data)); + return (int) $pdo->lastInsertId(); + } +} diff --git a/scripts/seed-demo-dataset.php b/scripts/seed-demo-dataset.php index 62742ca..0e576df 100644 --- a/scripts/seed-demo-dataset.php +++ b/scripts/seed-demo-dataset.php @@ -1,16 +1,12 @@ 'DataCore Demo S.r.l.', 996002=>'NIS2 Sandbox Formazione S.r.l.']; -$TARGETS = isset($opts['only']) ? array_intersect_key($ALL,[(int)$opts['only']=>1]) : $ALL; +$opts = getopt('', ['only::']); +$targets = isset($opts['only']) + ? array_intersect_key(DemoSeedService::TARGETS, [(int) $opts['only'] => 1]) + : DemoSeedService::TARGETS; -$USERCOLS=['owner_user_id','responsible_user_id','assigned_to','reported_by','created_by','uploaded_by','relevance_assessed_by','assessed_by','approved_by','reviewed_by','closed_by','user_id']; -function cols($pdo,$t){ return $pdo->query("SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='$t' ORDER BY ORDINAL_POSITION")->fetchAll(PDO::FETCH_COLUMN); } -function tableExists($pdo,$t){ return (int)$pdo->query("SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='$t'")->fetchColumn()>0; } -function copyRow($pdo,$table,$row,$overrides,$uid,$USERCOLS){ - static $cc=[]; if(!isset($cc[$table])) $cc[$table]=cols($pdo,$table); - $data=[]; - foreach($cc[$table] as $c){ - if($c==='id'){ if(array_key_exists('id',$overrides)) $data['id']=$overrides['id']; continue; } - if(array_key_exists($c,$overrides)){ $data[$c]=$overrides[$c]; continue; } - $v=$row[$c]??null; if(in_array($c,$USERCOLS,true)&&$v!==null) $v=$uid; $data[$c]=$v; - } - $cl=implode(',',array_map(fn($x)=>"`$x`",array_keys($data))); $ph=implode(',',array_fill(0,count($data),'?')); - $pdo->prepare("INSERT INTO `$table` ($cl) VALUES ($ph)")->execute(array_values($data)); - return (int)$pdo->lastInsertId(); -} - -$PC=[['assessments','assessment_responses','assessment_id'],['risks','risk_treatments','risk_id'],['incidents','incident_timeline','incident_id'],['training_courses','training_assignments','course_id'],['policies','policy_versions','policy_id']]; -$SIMPLE=['compliance_controls','suppliers','assets']; - -$pdo->exec("SET FOREIGN_KEY_CHECKS=0"); -foreach($TARGETS as $T=>$NAME){ - $pdo->beginTransaction(); - try { - foreach($PC as [$par,$chi,$fk]){ if(tableExists($pdo,$chi)) $pdo->exec("DELETE FROM `$chi` WHERE `$fk` IN (SELECT id FROM `$par` WHERE organization_id=$T)"); } - foreach(array_merge(array_column($PC,0),$SIMPLE) as $t){ if(tableExists($pdo,$t)) $pdo->exec("DELETE FROM `$t` WHERE organization_id=$T"); } - $pdo->exec("DELETE FROM user_organizations WHERE organization_id=$T"); - $pdo->exec("DELETE FROM users WHERE email='demo-$T@nis2-demo.local'"); - $pdo->exec("DELETE FROM organizations WHERE id=$T"); - - $org=Database::fetchOne("SELECT * FROM organizations WHERE id=?",[$SRC]); - copyRow($pdo,'organizations',$org,['id'=>$T,'name'=>$NAME],0,$USERCOLS); - // Utente demo: Database::insert (solo colonne fornite → i default DB si applicano; - // copyRow forzerebbe null sulle NOT NULL con default es. must_change_password). - $uid=(int)Database::insert('users',[ - 'email'=>"demo-$T@nis2-demo.local",'full_name'=>"Demo NIS2 $T",'role'=>'org_admin', - 'password_hash'=>password_hash(bin2hex(random_bytes(12)),PASSWORD_DEFAULT), - 'is_active'=>1,'must_change_password'=>0,'password_version'=>1, - ]); - $pdo->prepare("INSERT INTO user_organizations (user_id,organization_id,role) VALUES (?,?,'org_admin')")->execute([$uid,$T]); - - foreach($PC as [$par,$chi,$fk]){ - if(!tableExists($pdo,$par)) continue; - foreach(Database::fetchAll("SELECT * FROM `$par` WHERE organization_id=? ORDER BY id",[$SRC]) as $r){ - $newPar=copyRow($pdo,$par,$r,['organization_id'=>$T],$uid,$USERCOLS); - if(tableExists($pdo,$chi)) foreach(Database::fetchAll("SELECT * FROM `$chi` WHERE `$fk`=? ORDER BY id",[$r['id']]) as $cr){ - $ov=[$fk=>$newPar]; if(in_array('organization_id',cols($pdo,$chi),true)) $ov['organization_id']=$T; - copyRow($pdo,$chi,$cr,$ov,$uid,$USERCOLS); - } - } - } - foreach($SIMPLE as $t){ if(!tableExists($pdo,$t)) continue; - foreach(Database::fetchAll("SELECT * FROM `$t` WHERE organization_id=? ORDER BY id",[$SRC]) as $r) copyRow($pdo,$t,$r,['organization_id'=>$T],$uid,$USERCOLS); - } - $pdo->commit(); - echo "ORG $T ($NAME): seed OK (uid demo=$uid)\n"; - } catch(Throwable $ex){ $pdo->rollBack(); echo "ORG $T ERRORE ROLLBACK: ".$ex->getMessage()."\n"; } -} -$pdo->exec("SET FOREIGN_KEY_CHECKS=1"); -echo "Seed dataset demo completato (".implode(',',array_keys($TARGETS)).").\n"; +$res = DemoSeedService::seed($targets); +foreach ($res as $org => $msg) echo "ORG $org: $msg\n"; +echo "Seed dataset demo completato (" . implode(',', array_keys($res)) . ").\n";