From f2c41549c69f15b0a30596a655bfcbe69fa8644f Mon Sep 17 00:00:00 2001 From: Tom Udding Date: Mon, 21 Oct 2024 00:54:05 +0200 Subject: [PATCH 1/3] chore: add Doctrine migrations Adds support for migrations while using Doctrine ORM. The initial migration is based on the current `main`. Normally, a `migrations:rollup` would be used to ensure that this migration is successful. However, in our case it is necessary to export ALL data (but not structure) from the database, drop everything in the database, and only then perform the migration. Data can be easily exported from PostgreSQL using: ```sh pg_dump -U gewisdb --column-inserts --data-only gewisdb pg_dump -U gewisdb --column-inserts --data-only gewisdb_report ``` This ensures that _all_ FKs are the same for newer migrations preventing headaches in the future. --- Convenience methods have been added for `make` to make using the migrations easier. However, in production everything must go directly through `./orm`. --- .idea/php.xml | 1 + Makefile | 26 +- composer.json | 3 +- composer.lock | 381 +++++++++++------- .../doctrine.local.development.php.dist | 34 ++ .../doctrine.local.production.php.dist | 34 ++ .../migrations/Version20241020224949.php | 98 +++++ .../migrations/Version20241020224949.php | 86 ++++ 8 files changed, 520 insertions(+), 143 deletions(-) create mode 100644 module/Database/migrations/migrations/Version20241020224949.php create mode 100644 module/Report/migrations/migrations/Version20241020224949.php diff --git a/.idea/php.xml b/.idea/php.xml index c7afd3284..d949c81ca 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -201,6 +201,7 @@ + diff --git a/Makefile b/Makefile index be11c73ce..2eaf27408 100644 --- a/Makefile +++ b/Makefile @@ -35,9 +35,29 @@ rundev: builddev @make replenish @docker compose exec web rm -rf data/cache/module-config-cache.application.config.cache.php -updatedb: rundev - @docker compose exec -T web ./orm orm:schema-tool:update --force --no-interaction --complete - @docker compose exec -T web /bin/sh -c "EM_ALIAS=orm_report ./orm orm:schema-tool:update --force --no-interaction --complete" +migration-list: replenish + @docker compose exec -T web ./orm migrations:list --object-manager doctrine.entitymanager.orm_default + @docker compose exec -T web ./orm migrations:list --object-manager doctrine.entitymanager.orm_report + +migration-diff: replenish + @docker compose exec -T web ./orm migrations:diff --object-manager doctrine.entitymanager.orm_default + @docker cp "$(shell docker compose ps -q web)":/code/module/Database/migrations ./module/Database/migrations + @docker compose exec -T web ./orm migrations:diff --object-manager doctrine.entitymanager.orm_report + @docker cp "$(shell docker compose ps -q web)":/code/module/Report/migrations ./module/Report/migrations + +migration-migrate: replenish + @docker compose exec -it web ./orm migrations:migrate --object-manager doctrine.entitymanager.orm_default + @docker compose exec -it web ./orm migrations:migrate --object-manager doctrine.entitymanager.orm_report + +migration-up: replenish migration-list + @read -p "Enter EM_ALIAS (orm_default or orm_report): " alias; \ + read -p "Enter the migration version to execute (e.g., -- note escaping the backslashes is required): " version; \ + docker compose exec -it web ./orm migrations:execute --up $$version --object-manager doctrine.entitymanager.$$alias + +migration-down: replenish migration-list + @read -p "Enter EM_ALIAS (orm_default or orm_report): " alias; \ + read -p "Enter the migration version to down (e.g., -- note escaping the backslashes is required): " version; \ + docker compose exec -it web ./orm migrations:execute --down $$version --object-manager doctrine.entitymanager.$$alias stop: @docker compose down diff --git a/composer.json b/composer.json index 000ec3a76..0d04df052 100644 --- a/composer.json +++ b/composer.json @@ -53,7 +53,8 @@ "doctrine/doctrine-laminas-hydrator": "^3.4.0", "monolog/monolog": "^1.27.1", "cweagans/composer-patches": "^1.7.3", - "stripe/stripe-php": "^10.21" + "stripe/stripe-php": "^10.21", + "doctrine/migrations": "^3.8" }, "require-dev": { "laminas/laminas-component-installer": "^3.4.0", diff --git a/composer.lock b/composer.lock index 937be31fb..c2cfd7c99 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "60e6c7734c911b4c7ba9522775b72fcb", + "content-hash": "5352720d1e12078a7f8180a8bb8a441b", "packages": [ { "name": "brick/varexporter", @@ -1346,6 +1346,109 @@ ], "time": "2024-02-05T11:56:58+00:00" }, + { + "name": "doctrine/migrations", + "version": "3.8.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/migrations.git", + "reference": "5007eb1168691225ac305fe16856755c20860842" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/5007eb1168691225ac305fe16856755c20860842", + "reference": "5007eb1168691225ac305fe16856755c20860842", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/dbal": "^3.6 || ^4", + "doctrine/deprecations": "^0.5.3 || ^1", + "doctrine/event-manager": "^1.2 || ^2.0", + "php": "^8.1", + "psr/log": "^1.1.3 || ^2 || ^3", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.2 || ^7.0" + }, + "conflict": { + "doctrine/orm": "<2.12 || >=4" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "doctrine/orm": "^2.13 || ^3", + "doctrine/persistence": "^2 || ^3", + "doctrine/sql-formatter": "^1.0", + "ext-pdo_sqlite": "*", + "fig/log-test": "^1", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpstan/phpstan-strict-rules": "^1.4", + "phpstan/phpstan-symfony": "^1.3", + "phpunit/phpunit": "^10.3", + "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "doctrine/sql-formatter": "Allows to generate formatted SQL with the diff command.", + "symfony/yaml": "Allows the use of yaml for migration configuration files." + }, + "bin": [ + "bin/doctrine-migrations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Migrations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Michael Simonson", + "email": "contact@mikesimonson.com" + } + ], + "description": "PHP Doctrine Migrations project offer additional functionality on top of the database abstraction layer (DBAL) for versioning your database schema and easily deploying changes to it. It is a very easy to use and a powerful tool.", + "homepage": "https://www.doctrine-project.org/projects/migrations.html", + "keywords": [ + "database", + "dbal", + "migrations" + ], + "support": { + "issues": "https://github.com/doctrine/migrations/issues", + "source": "https://github.com/doctrine/migrations/tree/3.8.2" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fmigrations", + "type": "tidelift" + } + ], + "time": "2024-10-10T21:35:27+00:00" + }, { "name": "doctrine/orm", "version": "2.20.0", @@ -5783,6 +5886,68 @@ ], "time": "2024-04-18T09:32:20+00:00" }, + { + "name": "symfony/stopwatch", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d", + "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, { "name": "symfony/string", "version": "v7.1.5", @@ -5870,6 +6035,82 @@ ], "time": "2024-09-20T08:28:38+00:00" }, + { + "name": "symfony/var-exporter", + "version": "v7.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "b80a669a2264609f07f1667f891dbfca25eba44c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/b80a669a2264609f07f1667f891dbfca25eba44c", + "reference": "b80a669a2264609f07f1667f891dbfca25eba44c", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/property-access": "^6.4|^7.0", + "symfony/serializer": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Allows exporting any serializable PHP data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "lazy-loading", + "proxy", + "serialize" + ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v7.1.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-28T08:00:31+00:00" + }, { "name": "webimpress/safe-writer", "version": "2.2.0", @@ -11639,68 +11880,6 @@ ], "time": "2024-09-20T12:13:15+00:00" }, - { - "name": "symfony/stopwatch", - "version": "v7.1.1", - "source": { - "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d", - "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/service-contracts": "^2.5|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Stopwatch\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides a way to profile code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.1.1" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-05-31T14:57:53+00:00" - }, { "name": "symfony/translation-contracts", "version": "v3.5.0", @@ -12041,82 +12220,6 @@ ], "time": "2024-09-16T10:07:02+00:00" }, - { - "name": "symfony/var-exporter", - "version": "v7.1.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/var-exporter.git", - "reference": "b80a669a2264609f07f1667f891dbfca25eba44c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/b80a669a2264609f07f1667f891dbfca25eba44c", - "reference": "b80a669a2264609f07f1667f891dbfca25eba44c", - "shasum": "" - }, - "require": { - "php": ">=8.2" - }, - "require-dev": { - "symfony/property-access": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\VarExporter\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Allows exporting any serializable PHP data structure to plain PHP code", - "homepage": "https://symfony.com", - "keywords": [ - "clone", - "construct", - "export", - "hydrate", - "instantiate", - "lazy-loading", - "proxy", - "serialize" - ], - "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.1.2" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-06-28T08:00:31+00:00" - }, { "name": "theseer/tokenizer", "version": "1.2.3", diff --git a/config/autoload/doctrine.local.development.php.dist b/config/autoload/doctrine.local.development.php.dist index 09cb851c9..dc20ce479 100644 --- a/config/autoload/doctrine.local.development.php.dist +++ b/config/autoload/doctrine.local.development.php.dist @@ -226,5 +226,39 @@ return [ // configuration for the `doctrine.entity_resolver.orm_report` service 'orm_report' => [], ], + 'migrations_configuration' => [ + 'orm_default' => [ + 'table_storage' => [ + 'table_name' => 'doctrine_migration_versions', + 'version_column_name' => 'version', + 'version_column_length' => 191, + 'executed_at_column_name' => 'executed_at', + 'execution_time_column_name' => 'execution_time', + ], + 'migrations_paths' => [ + 'Database\Migrations' => './module/Database/migrations', + ], + 'all_or_nothing' => true, + 'transactional' => true, + 'check_database_platform' => true, + 'organize_migrations' => 'none', + ], + 'orm_report' => [ + 'table_storage' => [ + 'table_name' => 'doctrine_migration_versions', + 'version_column_name' => 'version', + 'version_column_length' => 191, + 'executed_at_column_name' => 'executed_at', + 'execution_time_column_name' => 'execution_time', + ], + 'migrations_paths' => [ + 'Report\Migrations' => './module/Report/migrations', + ], + 'all_or_nothing' => true, + 'transactional' => true, + 'check_database_platform' => true, + 'organize_migrations' => 'none', + ], + ], ], ]; diff --git a/config/autoload/doctrine.local.production.php.dist b/config/autoload/doctrine.local.production.php.dist index 0a2b90d73..b3499084c 100644 --- a/config/autoload/doctrine.local.production.php.dist +++ b/config/autoload/doctrine.local.production.php.dist @@ -226,5 +226,39 @@ return [ // configuration for the `doctrine.entity_resolver.orm_report` service 'orm_report' => [], ], + 'migrations_configuration' => [ + 'orm_default' => [ + 'table_storage' => [ + 'table_name' => 'doctrine_migration_versions', + 'version_column_name' => 'version', + 'version_column_length' => 191, + 'executed_at_column_name' => 'executed_at', + 'execution_time_column_name' => 'execution_time', + ], + 'migrations_paths' => [ + 'Database\Migrations' => './module/Database/migrations', + ], + 'all_or_nothing' => true, + 'transactional' => true, + 'check_database_platform' => true, + 'organize_migrations' => 'none', + ], + 'orm_report' => [ + 'table_storage' => [ + 'table_name' => 'doctrine_migration_versions', + 'version_column_name' => 'version', + 'version_column_length' => 191, + 'executed_at_column_name' => 'executed_at', + 'execution_time_column_name' => 'execution_time', + ], + 'migrations_paths' => [ + 'Report\Migrations' => './module/Report/migrations', + ], + 'all_or_nothing' => true, + 'transactional' => true, + 'check_database_platform' => true, + 'organize_migrations' => 'none', + ], + ], ], ]; diff --git a/module/Database/migrations/migrations/Version20241020224949.php b/module/Database/migrations/migrations/Version20241020224949.php new file mode 100644 index 000000000..4e6a6101e --- /dev/null +++ b/module/Database/migrations/migrations/Version20241020224949.php @@ -0,0 +1,98 @@ +addSql('CREATE SEQUENCE ActionLink_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE ApiPrincipal_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE AuditEntry_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE CheckoutSession_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE ConfigItem_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE InstallationFunction_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE Member_lidnr_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE ProspectiveMember_lidnr_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE SavedQuery_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE users_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE ActionLink (id INT NOT NULL, prospective_member INT DEFAULT NULL, member INT DEFAULT NULL, used BOOLEAN NOT NULL, token VARCHAR(255) NOT NULL, type VARCHAR(255) NOT NULL, currentExpiration DATE DEFAULT NULL, newExpiration DATE DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_A952B2A5740EE3E7 ON ActionLink (prospective_member)'); + $this->addSql('CREATE INDEX IDX_A952B2A570E4FA78 ON ActionLink (member)'); + $this->addSql('CREATE TABLE Address (type VARCHAR(255) NOT NULL, lidnr INT NOT NULL, country VARCHAR(255) NOT NULL, street VARCHAR(255) NOT NULL, number VARCHAR(255) NOT NULL, postalCode VARCHAR(255) NOT NULL, city VARCHAR(255) NOT NULL, phone VARCHAR(255) NOT NULL, PRIMARY KEY(lidnr, type))'); + $this->addSql('CREATE INDEX IDX_C2F3561DD665E01D ON Address (lidnr)'); + $this->addSql('CREATE TABLE ApiPrincipal (id INT NOT NULL, token VARCHAR(255) NOT NULL, description VARCHAR(255) DEFAULT NULL, permissions TEXT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('COMMENT ON COLUMN ApiPrincipal.permissions IS \'(DC2Type:simple_array)\''); + $this->addSql('CREATE TABLE AuditEntry (id INT NOT NULL, user_id INT DEFAULT NULL, member INT DEFAULT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updatedAt TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, type VARCHAR(255) NOT NULL, note VARCHAR(255) DEFAULT NULL, oldExpiration TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, newExpiration TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_DE382FBBA76ED395 ON AuditEntry (user_id)'); + $this->addSql('CREATE INDEX IDX_DE382FBB70E4FA78 ON AuditEntry (member)'); + $this->addSql('CREATE TABLE CheckoutSession (id INT NOT NULL, prospective_member INT DEFAULT NULL, recovered_from_id INT DEFAULT NULL, checkoutId VARCHAR(255) NOT NULL, created TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, expiration TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, paymentIntentId VARCHAR(255) DEFAULT NULL, recoveryUrl VARCHAR(255) DEFAULT NULL, state INT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_BC63300E198D234 ON CheckoutSession (checkoutId)'); + $this->addSql('CREATE INDEX IDX_BC63300E740EE3E7 ON CheckoutSession (prospective_member)'); + $this->addSql('CREATE INDEX IDX_BC63300EE03E402D ON CheckoutSession (recovered_from_id)'); + $this->addSql('CREATE TABLE ConfigItem (id INT NOT NULL, namespace VARCHAR(255) NOT NULL, key VARCHAR(255) NOT NULL, valueString VARCHAR(255) DEFAULT NULL, valueDate TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, createdAt TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updatedAt TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX configitem_unique_idx ON ConfigItem (namespace, key)'); + $this->addSql('CREATE TABLE Decision (meeting_type VARCHAR(255) NOT NULL, meeting_number INT NOT NULL, point INT NOT NULL, number INT NOT NULL, PRIMARY KEY(meeting_type, meeting_number, point, number))'); + $this->addSql('CREATE INDEX IDX_7DDADC1E602FAFFB96F82E16 ON Decision (meeting_type, meeting_number)'); + $this->addSql('CREATE TABLE InstallationFunction (id INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE MailingList (name VARCHAR(255) NOT NULL, nl_description TEXT NOT NULL, en_description TEXT NOT NULL, onForm BOOLEAN NOT NULL, defaultSub BOOLEAN NOT NULL, PRIMARY KEY(name))'); + $this->addSql('CREATE TABLE Meeting (type VARCHAR(255) NOT NULL, number INT NOT NULL, date DATE NOT NULL, PRIMARY KEY(type, number))'); + $this->addSql('CREATE TABLE Member (lidnr INT NOT NULL, email VARCHAR(255) DEFAULT NULL, lastName VARCHAR(255) NOT NULL, middleName VARCHAR(255) NOT NULL, initials VARCHAR(255) NOT NULL, firstName VARCHAR(255) NOT NULL, generation INT NOT NULL, tueUsername VARCHAR(255) DEFAULT NULL, study VARCHAR(255) DEFAULT NULL, type VARCHAR(255) NOT NULL, changedOn DATE NOT NULL, isStudying BOOLEAN NOT NULL, membershipEndsOn DATE DEFAULT NULL, expiration DATE NOT NULL, lastCheckedOn DATE DEFAULT NULL, birth DATE NOT NULL, paid INT NOT NULL, supremum VARCHAR(255) DEFAULT NULL, hidden BOOLEAN DEFAULT false NOT NULL, authenticationKey VARCHAR(255) DEFAULT NULL, deleted BOOLEAN DEFAULT false NOT NULL, PRIMARY KEY(lidnr))'); + $this->addSql('CREATE TABLE members_mailinglists (lidnr INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(lidnr, name))'); + $this->addSql('CREATE INDEX IDX_5AD357D9D665E01D ON members_mailinglists (lidnr)'); + $this->addSql('CREATE INDEX IDX_5AD357D95E237E06 ON members_mailinglists (name)'); + $this->addSql('CREATE TABLE MemberUpdate (lidnr INT NOT NULL, requestedDate DATE NOT NULL, email VARCHAR(255) NOT NULL, lastName VARCHAR(255) NOT NULL, middleName VARCHAR(255) NOT NULL, initials VARCHAR(255) NOT NULL, firstName VARCHAR(255) NOT NULL, PRIMARY KEY(lidnr))'); + $this->addSql('CREATE TABLE ProspectiveMember (lidnr INT NOT NULL, email VARCHAR(255) NOT NULL, lastName VARCHAR(255) NOT NULL, middleName VARCHAR(255) NOT NULL, initials VARCHAR(255) NOT NULL, firstName VARCHAR(255) NOT NULL, tueUsername VARCHAR(255) DEFAULT NULL, study VARCHAR(255) DEFAULT NULL, changedOn DATE NOT NULL, birth DATE NOT NULL, paid INT NOT NULL, country VARCHAR(255) NOT NULL, street VARCHAR(255) NOT NULL, number VARCHAR(255) NOT NULL, postalCode VARCHAR(255) NOT NULL, city VARCHAR(255) NOT NULL, phone VARCHAR(255) NOT NULL, PRIMARY KEY(lidnr))'); + $this->addSql('CREATE TABLE prospective_members_mailinglists (lidnr INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(lidnr, name))'); + $this->addSql('CREATE INDEX IDX_C86F0498D665E01D ON prospective_members_mailinglists (lidnr)'); + $this->addSql('CREATE INDEX IDX_C86F04985E237E06 ON prospective_members_mailinglists (name)'); + $this->addSql('CREATE TABLE SavedQuery (id INT NOT NULL, name VARCHAR(255) NOT NULL, query TEXT NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE SubDecision (meeting_type VARCHAR(255) NOT NULL, meeting_number INT NOT NULL, decision_point INT NOT NULL, decision_number INT NOT NULL, sequence INT NOT NULL, lidnr INT DEFAULT NULL, r_meeting_type VARCHAR(255) DEFAULT NULL, r_meeting_number INT DEFAULT NULL, r_decision_point INT DEFAULT NULL, r_decision_number INT DEFAULT NULL, r_sequence INT DEFAULT NULL, type VARCHAR(255) NOT NULL, name VARCHAR(255) DEFAULT NULL, organType VARCHAR(255) DEFAULT NULL, version VARCHAR(32) DEFAULT NULL, date DATE DEFAULT NULL, approval BOOLEAN DEFAULT NULL, changes BOOLEAN DEFAULT NULL, abbr VARCHAR(255) DEFAULT NULL, function VARCHAR(255) DEFAULT NULL, content TEXT DEFAULT NULL, until DATE DEFAULT NULL, withdrawnOn DATE DEFAULT NULL, PRIMARY KEY(meeting_type, meeting_number, decision_point, decision_number, sequence))'); + $this->addSql('CREATE INDEX IDX_F0D6EE40602FAFFB96F82E1690E0342DEF6BE237 ON SubDecision (meeting_type, meeting_number, decision_point, decision_number)'); + $this->addSql('CREATE INDEX IDX_F0D6EE40D665E01D ON SubDecision (lidnr)'); + $this->addSql('CREATE INDEX IDX_F0D6EE40EFBA85FF292FAD512F37B76A76CE1878B79BB36 ON SubDecision (r_meeting_type, r_meeting_number, r_decision_point, r_decision_number, r_sequence)'); + $this->addSql('CREATE INDEX IDX_F0D6EE40EFBA85FF292FAD512F37B76A76CE187 ON SubDecision (r_meeting_type, r_meeting_number, r_decision_point, r_decision_number)'); + $this->addSql('CREATE INDEX IDX_F0D6EE40EFBA85FF292FAD51 ON SubDecision (r_meeting_type, r_meeting_number)'); + $this->addSql('CREATE TABLE users (id INT NOT NULL, login VARCHAR(255) NOT NULL, password VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('ALTER TABLE ActionLink ADD CONSTRAINT FK_A952B2A5740EE3E7 FOREIGN KEY (prospective_member) REFERENCES ProspectiveMember (lidnr) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE ActionLink ADD CONSTRAINT FK_A952B2A570E4FA78 FOREIGN KEY (member) REFERENCES Member (lidnr) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE Address ADD CONSTRAINT FK_C2F3561DD665E01D FOREIGN KEY (lidnr) REFERENCES Member (lidnr) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE AuditEntry ADD CONSTRAINT FK_DE382FBBA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE AuditEntry ADD CONSTRAINT FK_DE382FBB70E4FA78 FOREIGN KEY (member) REFERENCES Member (lidnr) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE CheckoutSession ADD CONSTRAINT FK_BC63300E740EE3E7 FOREIGN KEY (prospective_member) REFERENCES ProspectiveMember (lidnr) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE CheckoutSession ADD CONSTRAINT FK_BC63300EE03E402D FOREIGN KEY (recovered_from_id) REFERENCES CheckoutSession (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE Decision ADD CONSTRAINT FK_7DDADC1E602FAFFB96F82E16 FOREIGN KEY (meeting_type, meeting_number) REFERENCES Meeting (type, number) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE members_mailinglists ADD CONSTRAINT FK_5AD357D9D665E01D FOREIGN KEY (lidnr) REFERENCES Member (lidnr) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE members_mailinglists ADD CONSTRAINT FK_5AD357D95E237E06 FOREIGN KEY (name) REFERENCES MailingList (name) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE MemberUpdate ADD CONSTRAINT FK_6FA192D9D665E01D FOREIGN KEY (lidnr) REFERENCES Member (lidnr) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE prospective_members_mailinglists ADD CONSTRAINT FK_C86F0498D665E01D FOREIGN KEY (lidnr) REFERENCES ProspectiveMember (lidnr) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE prospective_members_mailinglists ADD CONSTRAINT FK_C86F04985E237E06 FOREIGN KEY (name) REFERENCES MailingList (name) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE SubDecision ADD CONSTRAINT FK_F0D6EE40602FAFFB96F82E1690E0342DEF6BE237 FOREIGN KEY (meeting_type, meeting_number, decision_point, decision_number) REFERENCES Decision (meeting_type, meeting_number, point, number) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE SubDecision ADD CONSTRAINT FK_F0D6EE40D665E01D FOREIGN KEY (lidnr) REFERENCES Member (lidnr) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE SubDecision ADD CONSTRAINT FK_F0D6EE40EFBA85FF292FAD512F37B76A76CE1878B79BB36 FOREIGN KEY (r_meeting_type, r_meeting_number, r_decision_point, r_decision_number, r_sequence) REFERENCES SubDecision (meeting_type, meeting_number, decision_point, decision_number, sequence) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE SubDecision ADD CONSTRAINT FK_F0D6EE40EFBA85FF292FAD512F37B76A76CE187 FOREIGN KEY (r_meeting_type, r_meeting_number, r_decision_point, r_decision_number) REFERENCES Decision (meeting_type, meeting_number, point, number) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE SubDecision ADD CONSTRAINT FK_F0D6EE40EFBA85FF292FAD51 FOREIGN KEY (r_meeting_type, r_meeting_number) REFERENCES Meeting (type, number) NOT DEFERRABLE INITIALLY IMMEDIATE'); + // phpcs:enable SlevomatCodingStandard.Functions.RequireMultiLineCall.RequiredMultiLineCall + } + + public function down(Schema $schema): void + { + // phpcs:disable SlevomatCodingStandard.Functions.RequireMultiLineCall.RequiredMultiLineCall + $this->throwIrreversibleMigrationException(); + // phpcs:enable SlevomatCodingStandard.Functions.RequireMultiLineCall.RequiredMultiLineCall + } +} diff --git a/module/Report/migrations/migrations/Version20241020224949.php b/module/Report/migrations/migrations/Version20241020224949.php new file mode 100644 index 000000000..550ead341 --- /dev/null +++ b/module/Report/migrations/migrations/Version20241020224949.php @@ -0,0 +1,86 @@ +addSql('CREATE SEQUENCE BoardMember_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE Keyholder_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE Organ_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE OrganMember_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE Address (type VARCHAR(255) NOT NULL, lidnr INT NOT NULL, country VARCHAR(255) NOT NULL, street VARCHAR(255) NOT NULL, number VARCHAR(255) NOT NULL, postalCode VARCHAR(255) NOT NULL, city VARCHAR(255) NOT NULL, phone VARCHAR(255) NOT NULL, PRIMARY KEY(lidnr, type))'); + $this->addSql('CREATE INDEX IDX_C2F3561DD665E01D ON Address (lidnr)'); + $this->addSql('CREATE TABLE BoardMember (id INT NOT NULL, lidnr INT NOT NULL, r_meeting_type VARCHAR(255) DEFAULT NULL, r_meeting_number INT DEFAULT NULL, r_decision_point INT DEFAULT NULL, r_decision_number INT DEFAULT NULL, r_sequence INT DEFAULT NULL, function VARCHAR(255) NOT NULL, installDate DATE NOT NULL, releaseDate DATE DEFAULT NULL, dischargeDate DATE DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_D9517B2ED665E01D ON BoardMember (lidnr)'); + $this->addSql('CREATE UNIQUE INDEX installationDec_uniq ON BoardMember (r_meeting_type, r_meeting_number, r_decision_point, r_decision_number, r_sequence)'); + $this->addSql('CREATE TABLE Decision (meeting_type VARCHAR(255) NOT NULL, meeting_number INT NOT NULL, point INT NOT NULL, number INT NOT NULL, content TEXT NOT NULL, PRIMARY KEY(meeting_type, meeting_number, point, number))'); + $this->addSql('CREATE INDEX IDX_7DDADC1E602FAFFB96F82E16 ON Decision (meeting_type, meeting_number)'); + $this->addSql('CREATE TABLE Keyholder (id INT NOT NULL, lidnr INT NOT NULL, r_meeting_type VARCHAR(255) DEFAULT NULL, r_meeting_number INT DEFAULT NULL, r_decision_point INT DEFAULT NULL, r_decision_number INT DEFAULT NULL, r_sequence INT DEFAULT NULL, expirationDate DATE NOT NULL, withdrawnDate DATE DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_3C5F7B4DD665E01D ON Keyholder (lidnr)'); + $this->addSql('CREATE UNIQUE INDEX grantingDec_uniq ON Keyholder (r_meeting_type, r_meeting_number, r_decision_point, r_decision_number, r_sequence)'); + $this->addSql('CREATE TABLE MailingList (name VARCHAR(255) NOT NULL, nl_description TEXT NOT NULL, en_description TEXT NOT NULL, onForm BOOLEAN NOT NULL, defaultSub BOOLEAN NOT NULL, PRIMARY KEY(name))'); + $this->addSql('CREATE TABLE Meeting (type VARCHAR(255) NOT NULL, number INT NOT NULL, date DATE NOT NULL, PRIMARY KEY(type, number))'); + $this->addSql('CREATE TABLE Member (lidnr INT NOT NULL, email VARCHAR(255) DEFAULT NULL, lastName VARCHAR(255) NOT NULL, middleName VARCHAR(255) NOT NULL, initials VARCHAR(255) NOT NULL, firstName VARCHAR(255) NOT NULL, generation INT NOT NULL, type VARCHAR(255) NOT NULL, changedOn DATE NOT NULL, membershipEndsOn DATE DEFAULT NULL, birth DATE NOT NULL, expiration DATE NOT NULL, paid INT NOT NULL, supremum VARCHAR(255) DEFAULT NULL, hidden BOOLEAN DEFAULT false NOT NULL, authenticationKey VARCHAR(255) DEFAULT NULL, deleted BOOLEAN DEFAULT false NOT NULL, PRIMARY KEY(lidnr))'); + $this->addSql('CREATE TABLE members_mailinglists (lidnr INT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(lidnr, name))'); + $this->addSql('CREATE INDEX IDX_5AD357D9D665E01D ON members_mailinglists (lidnr)'); + $this->addSql('CREATE INDEX IDX_5AD357D95E237E06 ON members_mailinglists (name)'); + $this->addSql('CREATE TABLE Organ (id INT NOT NULL, r_meeting_type VARCHAR(255) DEFAULT NULL, r_meeting_number INT DEFAULT NULL, r_decision_point INT DEFAULT NULL, r_decision_number INT DEFAULT NULL, r_sequence INT DEFAULT NULL, abbr VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, type VARCHAR(255) NOT NULL, foundationDate DATE NOT NULL, abrogationDate DATE DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE UNIQUE INDEX foundation_uniq ON Organ (r_meeting_type, r_meeting_number, r_decision_point, r_decision_number, r_sequence)'); + $this->addSql('CREATE TABLE organs_subdecisions (organ_id INT NOT NULL, meeting_type VARCHAR(255) NOT NULL, meeting_number INT NOT NULL, decision_point INT NOT NULL, decision_number INT NOT NULL, subdecision_sequence INT NOT NULL, PRIMARY KEY(organ_id, meeting_type, meeting_number, decision_point, decision_number, subdecision_sequence))'); + $this->addSql('CREATE INDEX IDX_6177E308E4445171 ON organs_subdecisions (organ_id)'); + $this->addSql('CREATE INDEX IDX_6177E308602FAFFB96F82E1690E0342DEF6BE237DD50EB88 ON organs_subdecisions (meeting_type, meeting_number, decision_point, decision_number, subdecision_sequence)'); + $this->addSql('CREATE TABLE OrganMember (id INT NOT NULL, organ_id INT DEFAULT NULL, lidnr INT DEFAULT NULL, r_meeting_type VARCHAR(255) DEFAULT NULL, r_meeting_number INT DEFAULT NULL, r_decision_point INT DEFAULT NULL, r_decision_number INT DEFAULT NULL, r_sequence INT DEFAULT NULL, function VARCHAR(255) NOT NULL, installDate DATE NOT NULL, dischargeDate DATE DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_E5CB2C7DE4445171 ON OrganMember (organ_id)'); + $this->addSql('CREATE INDEX IDX_E5CB2C7DD665E01D ON OrganMember (lidnr)'); + $this->addSql('CREATE UNIQUE INDEX installation_uniq ON OrganMember (r_meeting_type, r_meeting_number, r_decision_point, r_decision_number, r_sequence)'); + $this->addSql('CREATE TABLE SubDecision (meeting_type VARCHAR(255) NOT NULL, meeting_number INT NOT NULL, decision_point INT NOT NULL, decision_number INT NOT NULL, sequence INT NOT NULL, lidnr INT DEFAULT NULL, r_meeting_type VARCHAR(255) DEFAULT NULL, r_meeting_number INT DEFAULT NULL, r_decision_point INT DEFAULT NULL, r_decision_number INT DEFAULT NULL, r_sequence INT DEFAULT NULL, content TEXT NOT NULL, type VARCHAR(255) NOT NULL, name VARCHAR(255) DEFAULT NULL, organType VARCHAR(255) DEFAULT NULL, version VARCHAR(32) DEFAULT NULL, date DATE DEFAULT NULL, approval BOOLEAN DEFAULT NULL, changes BOOLEAN DEFAULT NULL, abbr VARCHAR(255) DEFAULT NULL, function VARCHAR(255) DEFAULT NULL, until DATE DEFAULT NULL, withdrawnOn DATE DEFAULT NULL, PRIMARY KEY(meeting_type, meeting_number, decision_point, decision_number, sequence))'); + $this->addSql('CREATE INDEX IDX_F0D6EE40602FAFFB96F82E1690E0342DEF6BE237 ON SubDecision (meeting_type, meeting_number, decision_point, decision_number)'); + $this->addSql('CREATE INDEX IDX_F0D6EE40D665E01D ON SubDecision (lidnr)'); + $this->addSql('CREATE INDEX IDX_F0D6EE40EFBA85FF292FAD512F37B76A76CE1878B79BB36 ON SubDecision (r_meeting_type, r_meeting_number, r_decision_point, r_decision_number, r_sequence)'); + $this->addSql('CREATE INDEX IDX_F0D6EE40EFBA85FF292FAD512F37B76A76CE187 ON SubDecision (r_meeting_type, r_meeting_number, r_decision_point, r_decision_number)'); + $this->addSql('CREATE INDEX IDX_F0D6EE40EFBA85FF292FAD51 ON SubDecision (r_meeting_type, r_meeting_number)'); + $this->addSql('ALTER TABLE Address ADD CONSTRAINT FK_C2F3561DD665E01D FOREIGN KEY (lidnr) REFERENCES Member (lidnr) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE BoardMember ADD CONSTRAINT FK_D9517B2ED665E01D FOREIGN KEY (lidnr) REFERENCES Member (lidnr) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE BoardMember ADD CONSTRAINT FK_D9517B2EEFBA85FF292FAD512F37B76A76CE1878B79BB36 FOREIGN KEY (r_meeting_type, r_meeting_number, r_decision_point, r_decision_number, r_sequence) REFERENCES SubDecision (meeting_type, meeting_number, decision_point, decision_number, sequence) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE Decision ADD CONSTRAINT FK_7DDADC1E602FAFFB96F82E16 FOREIGN KEY (meeting_type, meeting_number) REFERENCES Meeting (type, number) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE Keyholder ADD CONSTRAINT FK_3C5F7B4DD665E01D FOREIGN KEY (lidnr) REFERENCES Member (lidnr) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE Keyholder ADD CONSTRAINT FK_3C5F7B4DEFBA85FF292FAD512F37B76A76CE1878B79BB36 FOREIGN KEY (r_meeting_type, r_meeting_number, r_decision_point, r_decision_number, r_sequence) REFERENCES SubDecision (meeting_type, meeting_number, decision_point, decision_number, sequence) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE members_mailinglists ADD CONSTRAINT FK_5AD357D9D665E01D FOREIGN KEY (lidnr) REFERENCES Member (lidnr) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE members_mailinglists ADD CONSTRAINT FK_5AD357D95E237E06 FOREIGN KEY (name) REFERENCES MailingList (name) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE Organ ADD CONSTRAINT FK_46C39B8EEFBA85FF292FAD512F37B76A76CE1878B79BB36 FOREIGN KEY (r_meeting_type, r_meeting_number, r_decision_point, r_decision_number, r_sequence) REFERENCES SubDecision (meeting_type, meeting_number, decision_point, decision_number, sequence) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE organs_subdecisions ADD CONSTRAINT FK_6177E308E4445171 FOREIGN KEY (organ_id) REFERENCES Organ (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE organs_subdecisions ADD CONSTRAINT FK_6177E308602FAFFB96F82E1690E0342DEF6BE237DD50EB88 FOREIGN KEY (meeting_type, meeting_number, decision_point, decision_number, subdecision_sequence) REFERENCES SubDecision (meeting_type, meeting_number, decision_point, decision_number, sequence) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE OrganMember ADD CONSTRAINT FK_E5CB2C7DE4445171 FOREIGN KEY (organ_id) REFERENCES Organ (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE OrganMember ADD CONSTRAINT FK_E5CB2C7DD665E01D FOREIGN KEY (lidnr) REFERENCES Member (lidnr) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE OrganMember ADD CONSTRAINT FK_E5CB2C7DEFBA85FF292FAD512F37B76A76CE1878B79BB36 FOREIGN KEY (r_meeting_type, r_meeting_number, r_decision_point, r_decision_number, r_sequence) REFERENCES SubDecision (meeting_type, meeting_number, decision_point, decision_number, sequence) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE SubDecision ADD CONSTRAINT FK_F0D6EE40602FAFFB96F82E1690E0342DEF6BE237 FOREIGN KEY (meeting_type, meeting_number, decision_point, decision_number) REFERENCES Decision (meeting_type, meeting_number, point, number) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE SubDecision ADD CONSTRAINT FK_F0D6EE40D665E01D FOREIGN KEY (lidnr) REFERENCES Member (lidnr) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE SubDecision ADD CONSTRAINT FK_F0D6EE40EFBA85FF292FAD512F37B76A76CE1878B79BB36 FOREIGN KEY (r_meeting_type, r_meeting_number, r_decision_point, r_decision_number, r_sequence) REFERENCES SubDecision (meeting_type, meeting_number, decision_point, decision_number, sequence) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE SubDecision ADD CONSTRAINT FK_F0D6EE40EFBA85FF292FAD512F37B76A76CE187 FOREIGN KEY (r_meeting_type, r_meeting_number, r_decision_point, r_decision_number) REFERENCES Decision (meeting_type, meeting_number, point, number) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('ALTER TABLE SubDecision ADD CONSTRAINT FK_F0D6EE40EFBA85FF292FAD51 FOREIGN KEY (r_meeting_type, r_meeting_number) REFERENCES Meeting (type, number) NOT DEFERRABLE INITIALLY IMMEDIATE'); + // phpcs:enable SlevomatCodingStandard.Functions.RequireMultiLineCall.RequiredMultiLineCall + } + + public function down(Schema $schema): void + { + // phpcs:disable SlevomatCodingStandard.Functions.RequireMultiLineCall.RequiredMultiLineCall + $this->throwIrreversibleMigrationException(); + // phpcs:enable SlevomatCodingStandard.Functions.RequireMultiLineCall.RequiredMultiLineCall + } +} From f80419ffa4270b16b0e7bf2aecd5c4384a279a21 Mon Sep 17 00:00:00 2001 From: Tom Udding Date: Mon, 21 Oct 2024 12:30:19 +0200 Subject: [PATCH 2/3] feat: add DBAL middleware to ensure proper role is used for all queries Initially, we attempted to use the `wrapperClass` option to handle setting the database role. However, we encountered issues because DBAL checks for the exact `Connection` class type and not the interface, making it difficult to extend the `Connection` class as needed. We also considered overwriting the PgSQL-specific connection class (PDO variant) to set the role upon connection. Unfortunately, this was not an option because the class is declared as `final`, preventing us from extending it. The next potential solution was to use an `EventSubscriber` to set the role after the connection was established (using `postConnect`). However, this approach is already deprecated in our version of DBAL and completely removed in the next major release, rendering it unsuitable for us (maintainability). Ultimately, we implemented the `SET ROLE` functionality using DBAL's middleware. By wrapping the driver, and manually creating the `Connection` we can perform the `SET ROLE` query before the connection is used by the application. Runtime checks exist to ensure that the role for each database is defined. However, validation of the actual value is done by PostgreSQL itself (it will complain if the role does not exist). Co-Authored-By: Rink --- .env.dist | 2 + .../doctrine.local.development.php.dist | 7 ++ .../doctrine.local.production.php.dist | 7 ++ .../Extensions/Doctrine/Middleware/Driver.php | 47 ++++++++++++ .../Doctrine/Middleware/SetRoleMiddleware.php | 75 +++++++++++++++++++ module/Application/src/Module.php | 4 + 6 files changed, 142 insertions(+) create mode 100644 module/Application/src/Extensions/Doctrine/Middleware/Driver.php create mode 100644 module/Application/src/Extensions/Doctrine/Middleware/SetRoleMiddleware.php diff --git a/.env.dist b/.env.dist index 5a64604fd..827fd0b3d 100644 --- a/.env.dist +++ b/.env.dist @@ -7,11 +7,13 @@ DOCTRINE_DEFAULT_HOST=postgresql DOCTRINE_DEFAULT_PORT=5432 DOCTRINE_DEFAULT_USER=gewisdb DOCTRINE_DEFAULT_PASSWORD=gewisdb +DOCTRINE_DEFAULT_ROLE=gewisdb DOCTRINE_DEFAULT_DATABASE=gewisdb DOCTRINE_REPORT_HOST=postgresql DOCTRINE_REPORT_PORT=5432 DOCTRINE_REPORT_USER=gewisdb DOCTRINE_REPORT_PASSWORD=gewisdb +DOCTRINE_REPORT_ROLE=gewisdb DOCTRINE_REPORT_DATABASE=gewisdb_report # Laminas settings diff --git a/config/autoload/doctrine.local.development.php.dist b/config/autoload/doctrine.local.development.php.dist index dc20ce479..b3181aa43 100644 --- a/config/autoload/doctrine.local.development.php.dist +++ b/config/autoload/doctrine.local.development.php.dist @@ -2,6 +2,7 @@ declare(strict_types=1); +use Application\Extensions\Doctrine\Middleware\SetRoleMiddleware; use Doctrine\DBAL\Driver\PDO\PgSQL\Driver as PgSQLDriver; use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; @@ -82,6 +83,9 @@ return [ // to use the default chained driver. The retrieved service name will // be `doctrine.driver.$thisSetting` 'driver' => 'orm_default', + 'middlewares' => [ + SetRoleMiddleware::class, + ], // Generate proxies automatically (turn off for production) 'generate_proxies' => true, @@ -128,6 +132,9 @@ return [ // to use the default chained driver. The retrieved service name will // be `doctrine.driver.$thisSetting` 'driver' => 'orm_report', + 'middlewares' => [ + SetRoleMiddleware::class, + ], // Generate proxies automatically (turn off for production) 'generate_proxies' => true, diff --git a/config/autoload/doctrine.local.production.php.dist b/config/autoload/doctrine.local.production.php.dist index b3499084c..86ec8cf8f 100644 --- a/config/autoload/doctrine.local.production.php.dist +++ b/config/autoload/doctrine.local.production.php.dist @@ -2,6 +2,7 @@ declare(strict_types=1); +use Application\Extensions\Doctrine\Middleware\SetRoleMiddleware; use Doctrine\DBAL\Driver\PDO\PgSQL\Driver as PgSQLDriver; use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; @@ -82,6 +83,9 @@ return [ // to use the default chained driver. The retrieved service name will // be `doctrine.driver.$thisSetting` 'driver' => 'orm_default', + 'middlewares' => [ + SetRoleMiddleware::class, + ], // Generate proxies automatically (turn off for production) 'generate_proxies' => false, @@ -128,6 +132,9 @@ return [ // to use the default chained driver. The retrieved service name will // be `doctrine.driver.$thisSetting` 'driver' => 'orm_report', + 'middlewares' => [ + SetRoleMiddleware::class, + ], // Generate proxies automatically (turn off for production) 'generate_proxies' => false, diff --git a/module/Application/src/Extensions/Doctrine/Middleware/Driver.php b/module/Application/src/Extensions/Doctrine/Middleware/Driver.php new file mode 100644 index 000000000..098429edd --- /dev/null +++ b/module/Application/src/Extensions/Doctrine/Middleware/Driver.php @@ -0,0 +1,47 @@ + $roles + */ + public function __construct( + DriverInterface $driver, + private readonly array $roles, + private readonly bool $isPgSQL, + ) { + parent::__construct($driver); + } + + /** + * {@inheritDoc} + */ + public function connect( + #[SensitiveParameter] + array $params, + ): ConnectionInterface { + $connection = parent::connect($params); + + if ( + $this->isPgSQL + && isset($params['host'], $params['port'], $params['dbname']) + ) { + $role = $this->roles[implode(':', [$params['host'], $params['port'], $params['dbname']])]; + + $connection->exec('SET ROLE ' . $connection->quote($role)); + } + + return $connection; + } +} diff --git a/module/Application/src/Extensions/Doctrine/Middleware/SetRoleMiddleware.php b/module/Application/src/Extensions/Doctrine/Middleware/SetRoleMiddleware.php new file mode 100644 index 000000000..d37db6348 --- /dev/null +++ b/module/Application/src/Extensions/Doctrine/Middleware/SetRoleMiddleware.php @@ -0,0 +1,75 @@ + $roleDefaultRole, + implode( + ':', + [ + $roleReportHost, + $roleReportPort, + $roleReportDB, + ], + ) => $roleReportRole, + ]; + + return new Driver($driver, $roles, $isPgSQL); + } +} diff --git a/module/Application/src/Module.php b/module/Application/src/Module.php index 2bc7df728..495595034 100644 --- a/module/Application/src/Module.php +++ b/module/Application/src/Module.php @@ -4,6 +4,7 @@ namespace Application; +use Application\Extensions\Doctrine\Middleware\SetRoleMiddleware; use Application\Mapper\ConfigItem as ConfigItemMapper; use Application\Mapper\Factory\ConfigItemFactory as ConfigItemMapperFactory; use Application\Service\Config as ConfigService; @@ -123,6 +124,9 @@ public function getConfig(): array public function getServiceConfig(): array { return [ + 'invokables' => [ + SetRoleMiddleware::class => SetRoleMiddleware::class, + ], 'factories' => [ ConfigItemMapper::class => ConfigItemMapperFactory::class, ConfigService::class => ConfigServiceFactory::class, From b140a2c42d0c68b7b30eab779da96f52cd45bbef Mon Sep 17 00:00:00 2001 From: Tom Udding Date: Sat, 2 Nov 2024 13:40:45 +0100 Subject: [PATCH 3/3] chore: require SSL for PostgreSQL connections in production --- config/autoload/doctrine.local.production.php.dist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/autoload/doctrine.local.production.php.dist b/config/autoload/doctrine.local.production.php.dist index 86ec8cf8f..098bd1b3b 100644 --- a/config/autoload/doctrine.local.production.php.dist +++ b/config/autoload/doctrine.local.production.php.dist @@ -31,6 +31,7 @@ return [ 'dbname' => getenv('DOCTRINE_DEFAULT_DATABASE'), 'charset' => 'utf8', 'collate' => 'utf8_unicode_ci', + 'sslmode' => 'require', ], ], 'orm_report' => [ @@ -55,6 +56,7 @@ return [ 'dbname' => getenv('DOCTRINE_REPORT_DATABASE'), 'charset' => 'utf8', 'collate' => 'utf8_unicode_ci', + 'sslmode' => 'require', ], ], ],