ADD is_admin * -> seed des roles systeme -> migration des donnees -> DROP roles. * * Dependance avec Task 6 (fixtures) : * Les fixtures applicatives reposent sur l'existence des roles systeme * 'admin' et 'user' seedes ici par SQL brut. Cette migration est donc * auto-suffisante et n'a pas besoin que les fixtures soient executees. */ final class Version20260414150034 extends AbstractMigration { public function getDescription(): string { return 'RBAC : tables permission/role + jointures + is_admin + migration des donnees depuis user.roles'; } public function up(Schema $schema): void { // 1) Creation des tables RBAC (permission, role, jointures). $this->addSql(<<<'SQL' CREATE TABLE permission ( id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, code VARCHAR(255) NOT NULL, label VARCHAR(255) NOT NULL, module VARCHAR(100) NOT NULL, orphan BOOLEAN DEFAULT false NOT NULL, PRIMARY KEY (id) ) SQL); $this->addSql('CREATE INDEX idx_permission_module ON permission (module)'); $this->addSql('CREATE INDEX idx_permission_orphan ON permission (orphan)'); $this->addSql('CREATE UNIQUE INDEX uniq_permission_code ON permission (code)'); $this->addSql(<<<'SQL' CREATE TABLE "role" ( id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, code VARCHAR(100) NOT NULL, label VARCHAR(255) NOT NULL, description TEXT DEFAULT NULL, is_system BOOLEAN DEFAULT false NOT NULL, PRIMARY KEY (id) ) SQL); $this->addSql('CREATE INDEX idx_role_is_system ON "role" (is_system)'); $this->addSql('CREATE UNIQUE INDEX uniq_role_code ON "role" (code)'); $this->addSql(<<<'SQL' CREATE TABLE role_permission ( role_id INT NOT NULL, permission_id INT NOT NULL, PRIMARY KEY (role_id, permission_id) ) SQL); $this->addSql('CREATE INDEX IDX_6F7DF886D60322AC ON role_permission (role_id)'); $this->addSql('CREATE INDEX IDX_6F7DF886FED90CCA ON role_permission (permission_id)'); $this->addSql(<<<'SQL' CREATE TABLE user_role ( user_id INT NOT NULL, role_id INT NOT NULL, PRIMARY KEY (user_id, role_id) ) SQL); $this->addSql('CREATE INDEX IDX_2DE8C6A3A76ED395 ON user_role (user_id)'); $this->addSql('CREATE INDEX IDX_2DE8C6A3D60322AC ON user_role (role_id)'); $this->addSql(<<<'SQL' CREATE TABLE user_permission ( user_id INT NOT NULL, permission_id INT NOT NULL, PRIMARY KEY (user_id, permission_id) ) SQL); $this->addSql('CREATE INDEX IDX_472E5446A76ED395 ON user_permission (user_id)'); $this->addSql('CREATE INDEX IDX_472E5446FED90CCA ON user_permission (permission_id)'); $this->addSql(<<<'SQL' ALTER TABLE role_permission ADD CONSTRAINT FK_6F7DF886D60322AC FOREIGN KEY (role_id) REFERENCES "role" (id) ON DELETE CASCADE SQL); $this->addSql(<<<'SQL' ALTER TABLE role_permission ADD CONSTRAINT FK_6F7DF886FED90CCA FOREIGN KEY (permission_id) REFERENCES permission (id) ON DELETE CASCADE SQL); $this->addSql(<<<'SQL' ALTER TABLE user_role ADD CONSTRAINT FK_2DE8C6A3A76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE SQL); $this->addSql(<<<'SQL' ALTER TABLE user_role ADD CONSTRAINT FK_2DE8C6A3D60322AC FOREIGN KEY (role_id) REFERENCES "role" (id) ON DELETE CASCADE SQL); $this->addSql(<<<'SQL' ALTER TABLE user_permission ADD CONSTRAINT FK_472E5446A76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE SQL); $this->addSql(<<<'SQL' ALTER TABLE user_permission ADD CONSTRAINT FK_472E5446FED90CCA FOREIGN KEY (permission_id) REFERENCES permission (id) ON DELETE CASCADE SQL); // 2) Ajout de la colonne is_admin sur "user" (avant la data-migration // qui en a besoin pour marquer les super-admins). $this->addSql('ALTER TABLE "user" ADD is_admin BOOLEAN DEFAULT false NOT NULL'); // 3) Seed des roles systeme avant toute migration de donnees utilisateurs. // Les codes sont centralises dans App\Module\Core\Domain\Security\SystemRoles // mais dupliques ici volontairement pour que la migration reste auto-suffisante. $this->addSql("INSERT INTO \"role\" (code, label, description, is_system) VALUES ('admin', 'Administrateur', 'Role administrateur - bypass complet via is_admin', TRUE)"); $this->addSql("INSERT INTO \"role\" (code, label, description, is_system) VALUES ('user', 'Utilisateur', 'Role de base sans permission specifique', TRUE)"); // 4) Bascule is_admin a TRUE pour tout user dont le JSON roles contient ROLE_ADMIN. $this->addSql(<<<'SQL' UPDATE "user" u SET is_admin = TRUE WHERE EXISTS ( SELECT 1 FROM jsonb_array_elements_text(COALESCE(u.roles::jsonb, '[]'::jsonb)) AS role_code WHERE role_code = 'ROLE_ADMIN' ) SQL); // 5) Rattachement des admins au role systeme 'admin'. $this->addSql(<<<'SQL' INSERT INTO user_role (user_id, role_id) SELECT u.id, r.id FROM "user" u CROSS JOIN "role" r WHERE r.code = 'admin' AND EXISTS ( SELECT 1 FROM jsonb_array_elements_text(COALESCE(u.roles::jsonb, '[]'::jsonb)) AS role_code WHERE role_code = 'ROLE_ADMIN' ) ON CONFLICT DO NOTHING SQL); // 6) Rattachement des autres users (y compris roles vide ou NULL) au role 'user'. $this->addSql(<<<'SQL' INSERT INTO user_role (user_id, role_id) SELECT u.id, r.id FROM "user" u CROSS JOIN "role" r WHERE r.code = 'user' AND NOT EXISTS ( SELECT 1 FROM jsonb_array_elements_text(COALESCE(u.roles::jsonb, '[]'::jsonb)) AS role_code WHERE role_code = 'ROLE_ADMIN' ) ON CONFLICT DO NOTHING SQL); // 7) Drop de la colonne "user".roles (DOIT etre la derniere instruction, // apres la migration des donnees qui la lit). $this->addSql('ALTER TABLE "user" DROP roles'); } public function down(Schema $schema): void { // 1) Recreation de la colonne roles (avec un defaut pour permettre le // backfill). Le NOT NULL est conserve comme dans le schema d'origine. $this->addSql('ALTER TABLE "user" ADD roles JSON NOT NULL DEFAULT \'[]\''); // 2) Rehydratation du JSON roles depuis is_admin avant de perdre la colonne. $this->addSql(<<<'SQL' UPDATE "user" SET roles = CASE WHEN is_admin THEN '["ROLE_ADMIN"]'::json ELSE '["ROLE_USER"]'::json END SQL); // 3) Drop des FK puis des tables de jointure (enfants d'abord). $this->addSql('ALTER TABLE role_permission DROP CONSTRAINT FK_6F7DF886D60322AC'); $this->addSql('ALTER TABLE role_permission DROP CONSTRAINT FK_6F7DF886FED90CCA'); $this->addSql('ALTER TABLE user_role DROP CONSTRAINT FK_2DE8C6A3A76ED395'); $this->addSql('ALTER TABLE user_role DROP CONSTRAINT FK_2DE8C6A3D60322AC'); $this->addSql('ALTER TABLE user_permission DROP CONSTRAINT FK_472E5446A76ED395'); $this->addSql('ALTER TABLE user_permission DROP CONSTRAINT FK_472E5446FED90CCA'); $this->addSql('DROP TABLE user_permission'); $this->addSql('DROP TABLE user_role'); $this->addSql('DROP TABLE role_permission'); // 4) Drop des tables parentes permission et role. $this->addSql('DROP TABLE permission'); $this->addSql('DROP TABLE "role"'); // 5) Drop de is_admin, la colonne roles ayant ete rehydratee en amont. $this->addSql('ALTER TABLE "user" DROP is_admin'); } }