diff --git a/.env.dist b/.env.dist index d70fb5f1..07dcba45 100644 --- a/.env.dist +++ b/.env.dist @@ -96,3 +96,7 @@ PHP_IDE_CONFIG=serverName=database.gewis.nl # Demo mode credentials (this will prepopulate the credential fields with these values) DEMO_CREDENTIALS_USERNAME=admin DEMO_CREDENTIALS_PASSWORD=gewisdbgewis + +# Timezone (incl Postfix) +TZ=Europe/Amsterdam +PGTZ=Europe/Amsterdam \ No newline at end of file diff --git a/Makefile b/Makefile index 2156d696..bb0e31a1 100644 --- a/Makefile +++ b/Makefile @@ -44,10 +44,6 @@ migrate: replenish migrate-to: @docker compose exec -u www-data web sh -c '. ./scripts/migrate-version.sh && ./orm migrations:migrate $$migrations --object-manager doctrine.entitymanager.$$alias' -migration-list: replenish - @docker compose exec -u www-data -T web ./orm migrations:list --object-manager doctrine.entitymanager.orm_default - @docker compose exec -u www-data -T web ./orm migrations:list --object-manager doctrine.entitymanager.orm_report - migration-diff: replenish @docker compose exec -u root web chown www-data:www-data /code/module/Database/migrations/ @docker compose exec -u www-data -T web ./orm migrations:diff --object-manager doctrine.entitymanager.orm_default @@ -58,10 +54,10 @@ migration-diff: replenish @docker cp "$(shell docker compose ps -q web)":/code/module/Report/migrations ./module/Report @docker compose exec -u root web chown -R root:root /code/module/Report/migrations/ -migration-up: replenish migration-list +migration-up: replenish @docker compose exec -u www-data web sh -c '. ./scripts/migrate-version.sh && ./orm migrations:execute --up $$migrations --object-manager doctrine.entitymanager.$$alias' -migration-down: replenish migration-list +migration-down: replenish @docker compose exec -u www-data web sh -c '. ./scripts/migrate-version.sh && ./orm migrations:execute --down $$migrations --object-manager doctrine.entitymanager.$$alias' seed: replenish diff --git a/module/Application/src/Model/ConfigItem.php b/module/Application/src/Model/ConfigItem.php index 0f1b7b75..c11d10f3 100644 --- a/module/Application/src/Model/ConfigItem.php +++ b/module/Application/src/Model/ConfigItem.php @@ -6,6 +6,7 @@ use Application\Model\Enums\ConfigNamespaces; use Database\Model\Trait\TimestampableTrait; +use Database\Model\Trait\VersionTrait; use DateTime; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; @@ -34,6 +35,11 @@ class ConfigItem { use TimestampableTrait; + // We implement locking by using version numbers (optimistic locking) + // rather than by banning other processes from locking the same row. + // This is more versatile and is possible because we do not care which + // process in the end changes the config, as long as it is only one. + use VersionTrait; /** * Primary key item ID (to avoid reference issues). diff --git a/module/Database/migrations/Version20251207150548.php b/module/Database/migrations/Version20251207150548.php new file mode 100644 index 00000000..04fcf281 --- /dev/null +++ b/module/Database/migrations/Version20251207150548.php @@ -0,0 +1,26 @@ +addSql('ALTER TABLE configitem ADD version INT DEFAULT 1000 NOT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE ConfigItem DROP version'); + } +} diff --git a/module/Database/migrations/Version20260211203320.php b/module/Database/migrations/Version20260211203320.php new file mode 100644 index 00000000..508cfbcd --- /dev/null +++ b/module/Database/migrations/Version20260211203320.php @@ -0,0 +1,31 @@ +addSql('UPDATE configitem SET valuebool = null, valuedate = updatedat + interval \'23h\' * valuebool::int WHERE key = \'locked\' AND namespace = \'database_mailman\''); + } + + public function down(Schema $schema): void + { + $this->addSql('UPDATE configitem SET valuebool = valuedate > \'' . (new DateTime())->format('Y-m-d H:i:s') . '\', valuedate = null WHERE key = \'locked\' AND namespace = \'database_mailman\''); + } +} diff --git a/module/Database/src/Model/Trait/VersionTrait.php b/module/Database/src/Model/Trait/VersionTrait.php new file mode 100644 index 00000000..163392c1 --- /dev/null +++ b/module/Database/src/Model/Trait/VersionTrait.php @@ -0,0 +1,20 @@ +isSyncLocked()) { + if ($this->isSyncLocked() && !$renew) { throw new RuntimeException('Unable to acquire sync lock for Mailman sync: locked by other process.'); } - $this->configService->setConfig(ConfigNamespaces::DatabaseMailman, 'locked', true); + if (!$this->isSyncLocked() && $renew) { + throw new RuntimeException('Unable to renew sync lock for Mailman sync: currently unlocked.'); + } + + $this->configService->setConfig( + ConfigNamespaces::DatabaseMailman, + 'locked', + (new DateTime())->modify('+23 hours'), + ); if ($this->isSyncLocked()) { return; @@ -159,7 +168,7 @@ private function acquireSyncLock(int $retries = 3): void */ private function releaseSyncLock(): void { - $this->configService->setConfig(ConfigNamespaces::DatabaseMailman, 'locked', false); + $this->configService->setConfig(ConfigNamespaces::DatabaseMailman, 'locked', new DateTime()); } /** @@ -167,7 +176,7 @@ private function releaseSyncLock(): void */ public function isSyncLocked(): bool { - return $this->configService->getConfig(ConfigNamespaces::DatabaseMailman, 'locked', false); + return $this->configService->getConfig(ConfigNamespaces::DatabaseMailman, 'locked') > new DateTime(); } /** @@ -185,6 +194,7 @@ public function syncMembership( $lists = $this->mailingListMapper->findAll(); foreach ($lists as $list) { + $this->acquireSyncLock(renew: true); $this->syncMembershipSingle($list, $output, $dryRun); } diff --git a/scripts/migrate-list.sh b/scripts/migrate-list.sh new file mode 100755 index 00000000..082ec26b --- /dev/null +++ b/scripts/migrate-list.sh @@ -0,0 +1,11 @@ +#/bin/sh + +# This script prints the available migrations for a specific alias +set -e + +. /code/scripts/migrate-alias.sh + +./orm migrations:list --no-interaction --object-manager doctrine.entitymanager.$alias + +export alias=$alias +export migrations=$migrations diff --git a/scripts/migrate-version.sh b/scripts/migrate-version.sh index 9034d49b..47fc5a20 100755 --- a/scripts/migrate-version.sh +++ b/scripts/migrate-version.sh @@ -4,7 +4,7 @@ # If you put this directly in the makefile, replace $ with $$ set -e -. /code/scripts/migrate-alias.sh +. /code/scripts/migrate-list.sh read -rp "Give (partial, unique) version name (e.g. Database\Migrations\Version20241020224949 or 20241020)): " version