diff --git a/composer.json b/composer.json index 6468378890..7e94b1cd53 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "friendsofsymfony/jsrouting-bundle": "^3.5.0", "furqansiddiqui/bip39-mnemonic-php": "^0.1.7", "gumlet/php-image-resize": "^2.0.4", + "heymoon/doctrine-psql-enum": "^3.4", "imagine/imagine": "^1.5", "knplabs/knp-time-bundle": "^2.4.0", "knpuniversity/oauth2-client-bundle": "^2.18.1", diff --git a/composer.lock b/composer.lock index aac8332ebd..0b4e51485a 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": "558bd22be6be876ab7f5debca824160a", + "content-hash": "a51ed118696bdeb6ebcabc293f682d46", "packages": [ { "name": "aws/aws-crt-php", @@ -2656,6 +2656,62 @@ ], "time": "2026-03-10T16:41:02+00:00" }, + { + "name": "heymoon/doctrine-psql-enum", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/heymoon-cc/doctrine-psql-enum.git", + "reference": "2fcaccd489ca87ce81dda1d50ae63df955d4d304" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/heymoon-cc/doctrine-psql-enum/zipball/2fcaccd489ca87ce81dda1d50ae63df955d4d304", + "reference": "2fcaccd489ca87ce81dda1d50ae63df955d4d304", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^4", + "doctrine/doctrine-bundle": "^2.0|^3.0", + "doctrine/orm": "^3.0", + "php": ">=8.2", + "symfony/framework-bundle": "7.*|8.*" + }, + "require-dev": { + "doctrine/doctrine-migrations-bundle": "3.*", + "phpunit/phpunit": "^9.5", + "symfony/orm-pack": "2.*", + "symfony/var-exporter": "^7", + "symfony/yaml": "7.*" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "HeyMoon\\DoctrinePostgresEnum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Egor", + "email": "me@heymoon.cc" + } + ], + "description": "Store PHP native enums as PostgeSQL custom enum types", + "keywords": [ + "doctrine-orm", + "enum", + "postgresql" + ], + "support": { + "issues": "https://github.com/heymoon-cc/doctrine-psql-enum/issues", + "source": "https://github.com/heymoon-cc/doctrine-psql-enum/tree/3.4.0" + }, + "time": "2026-03-30T12:59:08+00:00" + }, { "name": "imagine/imagine", "version": "1.5.2", diff --git a/config/bundles.php b/config/bundles.php index 08bbb4a26f..ad8b6db464 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -36,4 +36,5 @@ League\Bundle\OAuth2ServerBundle\LeagueOAuth2ServerBundle::class => ['all' => true], Scheb\TwoFactorBundle\SchebTwoFactorBundle::class => ['all' => true], Omines\AntiSpamBundle\AntiSpamBundle::class => ['all' => true], + HeyMoon\DoctrinePostgresEnum\DoctrinePostgresEnumBundle::class => ['all' => true], ]; diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 2eda99cc2f..5678f52fbe 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -3,19 +3,8 @@ doctrine: url: '%env(resolve:DATABASE_URL)%' types: citext: App\DoctrineExtensions\DBAL\Types\Citext - enumApplicationStatus: App\DoctrineExtensions\DBAL\Types\EnumApplicationStatus - enumNotificationStatus: App\DoctrineExtensions\DBAL\Types\EnumNotificationStatus - enumSortOptions: App\DoctrineExtensions\DBAL\Types\EnumSortOptions - enumDirectMessageSettings: App\DoctrineExtensions\DBAL\Types\EnumDirectMessageSettings - enumFrontContentOptions: App\DoctrineExtensions\DBAL\Types\EnumFrontContentOptions mapping_types: - user_type: string citext: citext - enumApplicationStatus: string - enumNotificationStatus: string - enumSortOptions: string - enumDirectMessageSettings: string - enumFrontContentOptions: string # IMPORTANT: You MUST configure your server version, # either here or in the DATABASE_URL env var (see .env file) diff --git a/config/packages/doctrine_postgres_enum.yaml b/config/packages/doctrine_postgres_enum.yaml new file mode 100644 index 0000000000..8e4fabba95 --- /dev/null +++ b/config/packages/doctrine_postgres_enum.yaml @@ -0,0 +1,5 @@ +doctrine_postgres_enum: + type_name: enum + migrations: + enabled: true + comment_tag: DC2Enum diff --git a/migrations/Version20260330184207.php b/migrations/Version20260330184207.php new file mode 100644 index 0000000000..16ad203bc7 --- /dev/null +++ b/migrations/Version20260330184207.php @@ -0,0 +1,192 @@ +addSql('ALTER TABLE message ALTER uuid DROP DEFAULT'); + $this->addSql('ALTER TABLE message_thread ALTER updated_at DROP NOT NULL'); + $this->addSql('ALTER TABLE "user" ALTER fields TYPE JSONB'); + $this->addSql('ALTER TABLE "user" ALTER notify_on_user_signup SET NOT NULL'); + + $this->addSql('COMMENT ON COLUMN activity.uuid IS \'\''); + $this->addSql('COMMENT ON COLUMN activity.inner_activity_id IS \'\''); + $this->addSql('COMMENT ON COLUMN activity.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN ap_activity.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN bookmark.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN domain_block.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN domain_subscription.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN embed.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN entry.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN entry.edited_at IS \'\''); + $this->addSql('COMMENT ON COLUMN entry_comment.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN entry_comment.edited_at IS \'\''); + $this->addSql('COMMENT ON COLUMN entry_comment_vote.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN entry_vote.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN favourite.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN instance.last_successful_deliver IS \'\''); + $this->addSql('COMMENT ON COLUMN instance.last_successful_receive IS \'\''); + $this->addSql('COMMENT ON COLUMN instance.last_failed_deliver IS \'\''); + $this->addSql('COMMENT ON COLUMN instance.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN instance.updated_at IS \'\''); + $this->addSql('COMMENT ON COLUMN magazine.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN magazine_ban.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN magazine_block.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN magazine_log.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN magazine_ownership_request.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN magazine_subscription.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN magazine_subscription_request.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN message.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN message.uuid IS \'\''); + $this->addSql('COMMENT ON COLUMN message.edited_at IS \'\''); + $this->addSql('COMMENT ON COLUMN message_thread.updated_at IS \'\''); + $this->addSql('COMMENT ON COLUMN moderator.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN moderator_request.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN notification.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN oauth2_access_token.expiry IS \'\''); + $this->addSql('COMMENT ON COLUMN oauth2_access_token.scopes IS \'\''); + $this->addSql('COMMENT ON COLUMN oauth2_authorization_code.expiry IS \'\''); + $this->addSql('COMMENT ON COLUMN oauth2_authorization_code.scopes IS \'\''); + $this->addSql('COMMENT ON COLUMN oauth2_client.redirect_uris IS \'\''); + $this->addSql('COMMENT ON COLUMN oauth2_client.grants IS \'\''); + $this->addSql('COMMENT ON COLUMN oauth2_client.scopes IS \'\''); + $this->addSql('COMMENT ON COLUMN oauth2_client.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN oauth2_client_access.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN oauth2_refresh_token.expiry IS \'\''); + $this->addSql('COMMENT ON COLUMN oauth2_user_consent.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN oauth2_user_consent.expires_at IS \'\''); + $this->addSql('COMMENT ON COLUMN post.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN post.edited_at IS \'\''); + $this->addSql('COMMENT ON COLUMN post_comment.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN post_comment.edited_at IS \'\''); + $this->addSql('COMMENT ON COLUMN post_comment_vote.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN post_vote.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN report.considered_at IS \'\''); + $this->addSql('COMMENT ON COLUMN report.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN reset_password_request.requested_at IS \'\''); + $this->addSql('COMMENT ON COLUMN reset_password_request.expires_at IS \'\''); + $this->addSql('COMMENT ON COLUMN "user".created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN "user".featured_magazines IS \'\''); + $this->addSql('COMMENT ON COLUMN user_block.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN user_follow.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN user_follow_request.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN user_note.created_at IS \'\''); + $this->addSql('COMMENT ON COLUMN user_push_subscription.device_key IS \'\''); + + /* these types of changes must be filtered out manually always, as Doctrine can't handle tsvector correctly + * $this->addSql('ALTER TABLE entry ALTER title_ts SET DEFAULT \'english\''); + * $this->addSql('ALTER TABLE entry ALTER body_ts SET DEFAULT \'english\''); + * $this->addSql('ALTER TABLE entry_comment ALTER body_ts SET DEFAULT \'english\''); + * $this->addSql('ALTER TABLE magazine ALTER name_ts DROP DEFAULT'); + * $this->addSql('ALTER TABLE magazine ALTER title_ts DROP DEFAULT'); + * $this->addSql('ALTER TABLE magazine ALTER description_ts DROP DEFAULT'); + * $this->addSql('ALTER TABLE post_comment ALTER body_ts SET DEFAULT \'english\''); + * $this->addSql('ALTER TABLE post ALTER body_ts SET DEFAULT \'english\''); + * $this->addSql('ALTER TABLE "user" ALTER username_ts DROP DEFAULT'); + * $this->addSql('ALTER TABLE "user" ALTER about_ts DROP DEFAULT'); + * $this->addSql('ALTER TABLE "user" ALTER title SET NOT NULL'); + * $this->addSql('ALTER TABLE "user" ALTER title_ts DROP DEFAULT'); + */ + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE message ALTER uuid SET DEFAULT \'gen_random_uuid()\''); + $this->addSql('ALTER TABLE message_thread ALTER updated_at SET NOT NULL'); + $this->addSql('ALTER TABLE "user" ALTER fields TYPE JSON'); + $this->addSql('ALTER TABLE "user" ALTER notify_on_user_signup DROP NOT NULL'); + + $this->addSql('COMMENT ON COLUMN activity.uuid IS \'(DC2Type:uuid)\''); + $this->addSql('COMMENT ON COLUMN activity.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN activity.inner_activity_id IS \'(DC2Type:uuid)\''); + $this->addSql('COMMENT ON COLUMN ap_activity.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN bookmark.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN domain_block.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN domain_subscription.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN embed.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN entry.edited_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN entry.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN entry_comment.edited_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN entry_comment.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN entry_comment_vote.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN entry_vote.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN favourite.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN instance.last_successful_deliver IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN instance.last_failed_deliver IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN instance.last_successful_receive IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN instance.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN instance.updated_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN magazine.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN magazine_ban.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN magazine_block.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN magazine_log.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN magazine_ownership_request.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN magazine_subscription.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN magazine_subscription_request.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN message.uuid IS \'(DC2Type:uuid)\''); + $this->addSql('COMMENT ON COLUMN message.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN message.edited_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN message_thread.updated_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN moderator.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN moderator_request.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN notification.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN oauth2_access_token.expiry IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN oauth2_access_token.scopes IS \'(DC2Type:oauth2_scope)\''); + $this->addSql('COMMENT ON COLUMN oauth2_authorization_code.expiry IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN oauth2_authorization_code.scopes IS \'(DC2Type:oauth2_scope)\''); + $this->addSql('COMMENT ON COLUMN "oauth2_client".redirect_uris IS \'(DC2Type:oauth2_redirect_uri)\''); + $this->addSql('COMMENT ON COLUMN "oauth2_client".grants IS \'(DC2Type:oauth2_grant)\''); + $this->addSql('COMMENT ON COLUMN "oauth2_client".scopes IS \'(DC2Type:oauth2_scope)\''); + $this->addSql('COMMENT ON COLUMN "oauth2_client".created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN oauth2_client_access.created_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN oauth2_refresh_token.expiry IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN oauth2_user_consent.created_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN oauth2_user_consent.expires_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN post.edited_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN post.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN post_comment.edited_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN post_comment.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN post_comment_vote.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN post_vote.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN report.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN report.considered_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN reset_password_request.requested_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN reset_password_request.expires_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN "user".featured_magazines IS \'(DC2Type:array)\''); + $this->addSql('COMMENT ON COLUMN "user".created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN user_block.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN user_follow.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN user_follow_request.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN user_note.created_at IS \'(DC2Type:datetimetz_immutable)\''); + $this->addSql('COMMENT ON COLUMN user_push_subscription.device_key IS \'(DC2Type:uuid)\''); + + /* these types of changes must be filtered out manually always, as Doctrine can't handle tsvector correctly + * $this->addSql('ALTER TABLE entry ALTER title_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, (title)::text)\''); + * $this->addSql('ALTER TABLE entry ALTER body_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, body)\''); + * $this->addSql('ALTER TABLE entry_comment ALTER body_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, body)\''); + * $this->addSql('ALTER TABLE magazine ALTER name_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, (name)::text)\''); + * $this->addSql('ALTER TABLE magazine ALTER title_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, (title)::text)\''); + * $this->addSql('ALTER TABLE magazine ALTER description_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, description)\''); + * $this->addSql('ALTER TABLE post ALTER body_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, body)\''); + * $this->addSql('ALTER TABLE post_comment ALTER body_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, body)\''); + * $this->addSql('ALTER TABLE "user" ALTER username_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, (username)::text)\''); + * $this->addSql('ALTER TABLE "user" ALTER title_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, (title)::text)\''); + * $this->addSql('ALTER TABLE "user" ALTER about_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, about)\''); + */ + } +} diff --git a/migrations/Version20260331125749.php b/migrations/Version20260331125749.php new file mode 100644 index 0000000000..f09c42ba07 --- /dev/null +++ b/migrations/Version20260331125749.php @@ -0,0 +1,57 @@ +addSql('ALTER TABLE "user" ALTER title SET NOT NULL'); + */ + + /* junk tsvector migrations + * $this->addSql('ALTER TABLE entry ALTER title_ts SET DEFAULT \'english\''); + * $this->addSql('ALTER TABLE entry ALTER body_ts SET DEFAULT \'english\''); + * $this->addSql('ALTER TABLE entry_comment ALTER body_ts SET DEFAULT \'english\''); + * $this->addSql('ALTER TABLE magazine ALTER name_ts DROP DEFAULT'); + * $this->addSql('ALTER TABLE magazine ALTER title_ts DROP DEFAULT'); + * $this->addSql('ALTER TABLE magazine ALTER description_ts DROP DEFAULT'); + * $this->addSql('ALTER TABLE post ALTER body_ts SET DEFAULT \'english\''); + * $this->addSql('ALTER TABLE post_comment ALTER body_ts SET DEFAULT \'english\''); + * $this->addSql('ALTER TABLE "user" ALTER username_ts DROP DEFAULT'); + * $this->addSql('ALTER TABLE "user" ALTER about_ts DROP DEFAULT'); + * $this->addSql('ALTER TABLE "user" ALTER title_ts DROP DEFAULT'); + */ + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE entry ALTER title_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, (title)::text)\''); + $this->addSql('ALTER TABLE entry ALTER body_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, body)\''); + $this->addSql('ALTER TABLE entry_comment ALTER body_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, body)\''); + $this->addSql('ALTER TABLE magazine ALTER name_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, (name)::text)\''); + $this->addSql('ALTER TABLE magazine ALTER title_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, (title)::text)\''); + $this->addSql('ALTER TABLE magazine ALTER description_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, description)\''); + $this->addSql('ALTER TABLE post ALTER body_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, body)\''); + $this->addSql('ALTER TABLE post_comment ALTER body_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, body)\''); + $this->addSql('ALTER TABLE "user" ALTER username_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, (username)::text)\''); + $this->addSql('ALTER TABLE "user" ALTER title_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, (title)::text)\''); + $this->addSql('ALTER TABLE "user" ALTER about_ts SET DEFAULT \'to_tsvector(\'\'english\'\'::regconfig, about)\''); + $this->addSql('ALTER TABLE "user" ALTER title DROP NOT NULL'); + } +} diff --git a/src/Controller/Entry/EntryFrontController.php b/src/Controller/Entry/EntryFrontController.php index 2d501a69d0..944ca0867b 100644 --- a/src/Controller/Entry/EntryFrontController.php +++ b/src/Controller/Entry/EntryFrontController.php @@ -175,7 +175,7 @@ public function magazineRedirect( private function createCriteria(string $content, Request $request, ?User $user): Criteria { if ('default' === $content) { - $content = $user?->frontDefaultContent ?? 'threads'; + $content = $user?->frontDefaultContent?->value ?? 'threads'; } if ('threads' === $content || 'combined' === $content) { diff --git a/src/DataFixtures/UserFixtures.php b/src/DataFixtures/UserFixtures.php index d15f455ffe..52f6324843 100644 --- a/src/DataFixtures/UserFixtures.php +++ b/src/DataFixtures/UserFixtures.php @@ -5,6 +5,7 @@ namespace App\DataFixtures; use App\Entity\User; +use App\Enums\EUserType; use App\Repository\ImageRepository; use App\Repository\UserRepository; use App\Service\ImageManagerInterface; @@ -30,7 +31,7 @@ public function loadData(ObjectManager $manager): void $user['email'], $user['username'], $user['password'], - $user['type'] + EUserType::getFromString($user['type']) ); $newUser->setPassword( diff --git a/src/DoctrineExtensions/DBAL/Types/Citext.php b/src/DoctrineExtensions/DBAL/Types/Citext.php index d454685636..f643871163 100644 --- a/src/DoctrineExtensions/DBAL/Types/Citext.php +++ b/src/DoctrineExtensions/DBAL/Types/Citext.php @@ -23,4 +23,9 @@ public function getSQLDeclaration(array $column, AbstractPlatform $platform): st { return $platform->getDoctrineTypeMapping(self::CITEXT); } + + public function getMappedDatabaseTypes(AbstractPlatform $platform): array + { + return ['citext']; + } } diff --git a/src/DoctrineExtensions/DBAL/Types/EnumApplicationStatus.php b/src/DoctrineExtensions/DBAL/Types/EnumApplicationStatus.php deleted file mode 100644 index 7b6865d52f..0000000000 --- a/src/DoctrineExtensions/DBAL/Types/EnumApplicationStatus.php +++ /dev/null @@ -1,20 +0,0 @@ -getValues()); - - return 'ENUM('.implode(', ', $values).')'; - } - - public function convertToPHPValue($value, AbstractPlatform $platform): mixed - { - return $value; - } - - public function convertToDatabaseValue($value, AbstractPlatform $platform): mixed - { - if (!\in_array($value, $this->getValues())) { - throw new \InvalidArgumentException("Invalid '".$this->getName()."' value."); - } - - return $value; - } - - public function requiresSQLCommentHint(AbstractPlatform $platform): bool - { - return true; - } -} diff --git a/src/Entity/EntryComment.php b/src/Entity/EntryComment.php index f7318cb00f..31242dc809 100644 --- a/src/Entity/EntryComment.php +++ b/src/Entity/EntryComment.php @@ -22,6 +22,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Criteria; +use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\GeneratedValue; @@ -78,7 +79,7 @@ class EntryComment implements VotableInterface, VisibilityInterface, ReportInter public ?\DateTime $lastActive = null; #[Column(type: 'string', nullable: true)] public ?string $ip = null; - #[Column(type: 'json', nullable: true)] + #[Column(type: Types::JSONB, nullable: true)] public ?array $mentions = null; #[OneToMany(mappedBy: 'parent', targetEntity: EntryComment::class, cascade: ['persist', 'remove'], orphanRemoval: true)] #[OrderBy(['createdAt' => 'ASC'])] diff --git a/src/Entity/NotificationSettings.php b/src/Entity/NotificationSettings.php index ef494c1f97..86d2c497a8 100644 --- a/src/Entity/NotificationSettings.php +++ b/src/Entity/NotificationSettings.php @@ -40,8 +40,8 @@ class NotificationSettings #[JoinColumn(nullable: true, onDelete: 'CASCADE')] public ?User $targetUser = null; - #[Column(type: 'enumNotificationStatus', nullable: false, options: ['default' => ENotificationStatus::Default->value])] - private string $notificationStatus = ENotificationStatus::Default->value; + #[Column(type: 'enum', enumType: ENotificationStatus::class, nullable: false, options: ['default' => ENotificationStatus::Default])] + private ENotificationStatus $notificationStatus = ENotificationStatus::Default; public function __construct(User $user, Entry|Post|User|Magazine $target, ENotificationStatus $status) { @@ -60,11 +60,11 @@ public function __construct(User $user, Entry|Post|User|Magazine $target, ENotif public function setStatus(ENotificationStatus $status): void { - $this->notificationStatus = $status->value; + $this->notificationStatus = $status; } public function getStatus(): ENotificationStatus { - return ENotificationStatus::getFromString($this->notificationStatus); + return $this->notificationStatus; } } diff --git a/src/Entity/Traits/ActivityPubActorTrait.php b/src/Entity/Traits/ActivityPubActorTrait.php index da08d2c20f..e19b8dea85 100644 --- a/src/Entity/Traits/ActivityPubActorTrait.php +++ b/src/Entity/Traits/ActivityPubActorTrait.php @@ -39,7 +39,7 @@ trait ActivityPubActorTrait #[Column(type: 'string', nullable: true)] public ?string $apPreferredUsername = null; - #[Column(type: 'string')] + #[Column(type: 'string', nullable: false)] public ?string $title = null; #[Column(type: 'boolean', nullable: true)] diff --git a/src/Entity/User.php b/src/Entity/User.php index 6874674fc2..7c5d66dc85 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -12,7 +12,9 @@ use App\Entity\Traits\VisibilityTrait; use App\Enums\EApplicationStatus; use App\Enums\EDirectMessageSettings; +use App\Enums\EFrontContentOptions; use App\Enums\ESortOptions; +use App\Enums\EUserType; use App\Repository\UserRepository; use App\Service\ActivityPub\ApHttpClientInterface; use Doctrine\Common\Collections\ArrayCollection; @@ -112,14 +114,14 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Visibil public string $homepage = self::HOMEPAGE_ALL; #[Column(type: 'boolean', nullable: false, options: ['default' => false])] public bool $showBoostsOfFollowing = false; - #[Column(type: 'enumSortOptions', nullable: false, options: ['default' => ESortOptions::Hot->value])] - public string $frontDefaultSort = ESortOptions::Hot->value; - #[Column(type: 'enumFrontContentOptions', nullable: true)] - public ?string $frontDefaultContent = null; - #[Column(type: 'enumSortOptions', nullable: false, options: ['default' => ESortOptions::Hot->value])] - public string $commentDefaultSort = ESortOptions::Hot->value; - #[Column(type: 'enumDirectMessageSettings', nullable: false, options: ['default' => EDirectMessageSettings::Everyone->value])] - public string $directMessageSetting = EDirectMessageSettings::Everyone->value; + #[Column(type: 'enum', enumType: ESortOptions::class, nullable: false, options: ['default' => ESortOptions::Hot])] + public ESortOptions $frontDefaultSort = ESortOptions::Hot; + #[Column(type: 'enum', enumType: EFrontContentOptions::class, nullable: true)] + public ?EFrontContentOptions $frontDefaultContent = null; + #[Column(type: 'enum', enumType: ESortOptions::class, nullable: false, options: ['default' => ESortOptions::Hot])] + public ESortOptions $commentDefaultSort = ESortOptions::Hot; + #[Column(type: 'enum', enumType: EDirectMessageSettings::class, nullable: false, options: ['default' => EDirectMessageSettings::Everyone])] + public EDirectMessageSettings $directMessageSetting = EDirectMessageSettings::Everyone; #[Column(type: 'text', nullable: true)] public ?string $about = null; #[Column(type: 'datetimetz')] @@ -260,8 +262,8 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Visibil private array $totpBackupCodes = []; #[OneToMany(mappedBy: 'user', targetEntity: OAuth2UserConsent::class, orphanRemoval: true)] private Collection $oAuth2UserConsents; - #[Column(type: 'string', nullable: false, options: ['default' => self::USER_TYPE_PERSON])] - public string $type; + #[Column(type: 'enum', enumType: EUserType::class, nullable: false, options: ['default' => EUserType::Person])] + public EUserType $type = EUserType::Person; #[Column(type: 'text', nullable: true)] public ?string $applicationText; @@ -273,14 +275,14 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface, Visibil #[Column(type: 'text', nullable: true, insertable: false, updatable: false, options: ['default' => null])] private ?string $aboutTs; - #[Column(type: 'enumApplicationStatus', nullable: false, options: ['default' => EApplicationStatus::Approved->value])] - private string $applicationStatus; + #[Column(type: 'enum', enumType: EApplicationStatus::class, nullable: false, options: ['default' => EApplicationStatus::Approved])] + private EApplicationStatus $applicationStatus = EApplicationStatus::Approved; public function __construct( string $email, string $username, string $password, - string $type, + EUserType $type, ?string $apProfileId = null, ?string $apId = null, EApplicationStatus $applicationStatus = EApplicationStatus::Approved, @@ -960,12 +962,12 @@ public function canUpdateUser(User $actor): bool public function getApplicationStatus(): EApplicationStatus { - return EApplicationStatus::getFromString($this->applicationStatus); + return $this->applicationStatus; } public function setApplicationStatus(EApplicationStatus $applicationStatus): void { - $this->applicationStatus = $applicationStatus->value; + $this->applicationStatus = $applicationStatus; } /** @@ -975,9 +977,9 @@ public function setApplicationStatus(EApplicationStatus $applicationStatus): voi */ public function canReceiveDirectMessage(User $dmAuthor): bool { - if (EDirectMessageSettings::Everyone->value === $this->directMessageSetting) { + if (EDirectMessageSettings::Everyone === $this->directMessageSetting) { return true; - } elseif (EDirectMessageSettings::FollowersOnly->value === $this->directMessageSetting) { + } elseif (EDirectMessageSettings::FollowersOnly === $this->directMessageSetting) { $criteria = Criteria::create()->where(Criteria::expr()->eq('follower', $dmAuthor)); return $this->followers->matching($criteria)->count() > 0; diff --git a/src/Enums/EApplicationStatus.php b/src/Enums/EApplicationStatus.php index f18291658b..265bc56dab 100644 --- a/src/Enums/EApplicationStatus.php +++ b/src/Enums/EApplicationStatus.php @@ -4,6 +4,9 @@ namespace App\Enums; +use HeyMoon\DoctrinePostgresEnum\Attribute\EnumType; + +#[EnumType('enum_application_status')] enum EApplicationStatus: string { case Approved = 'Approved'; diff --git a/src/Enums/EDirectMessageSettings.php b/src/Enums/EDirectMessageSettings.php index 0358349db8..99154c66e5 100644 --- a/src/Enums/EDirectMessageSettings.php +++ b/src/Enums/EDirectMessageSettings.php @@ -4,6 +4,9 @@ namespace App\Enums; +use HeyMoon\DoctrinePostgresEnum\Attribute\EnumType; + +#[EnumType('enum_direct_message_settings')] enum EDirectMessageSettings: string { case Everyone = 'everyone'; diff --git a/src/Enums/EFrontContentOptions.php b/src/Enums/EFrontContentOptions.php index 679b1b0ee3..795df0838a 100644 --- a/src/Enums/EFrontContentOptions.php +++ b/src/Enums/EFrontContentOptions.php @@ -4,6 +4,9 @@ namespace App\Enums; +use HeyMoon\DoctrinePostgresEnum\Attribute\EnumType; + +#[EnumType('enum_front_content_options')] enum EFrontContentOptions: string { case Combined = 'combined'; @@ -16,6 +19,16 @@ enum EFrontContentOptions: string EFrontContentOptions::Microblog->value, ]; + public static function getFromString(string $value): ?EFrontContentOptions + { + return match ($value) { + EFrontContentOptions::Combined->value => EFrontContentOptions::Combined, + EFrontContentOptions::Threads->value => EFrontContentOptions::Threads, + EFrontContentOptions::Microblog->value => EFrontContentOptions::Microblog, + default => null, + }; + } + /** * @return string[] */ diff --git a/src/Enums/ENotificationStatus.php b/src/Enums/ENotificationStatus.php index 18f177d483..2c7a04879c 100644 --- a/src/Enums/ENotificationStatus.php +++ b/src/Enums/ENotificationStatus.php @@ -4,6 +4,9 @@ namespace App\Enums; +use HeyMoon\DoctrinePostgresEnum\Attribute\EnumType; + +#[EnumType('enum_notification_status')] enum ENotificationStatus: string { case Default = 'Default'; diff --git a/src/Enums/ESortOptions.php b/src/Enums/ESortOptions.php index 54f356c129..98c057ab22 100644 --- a/src/Enums/ESortOptions.php +++ b/src/Enums/ESortOptions.php @@ -4,6 +4,9 @@ namespace App\Enums; +use HeyMoon\DoctrinePostgresEnum\Attribute\EnumType; + +#[EnumType('enum_sort_options')] enum ESortOptions: string { case Hot = 'hot'; diff --git a/src/Enums/EUserType.php b/src/Enums/EUserType.php new file mode 100644 index 0000000000..eead1e1cd2 --- /dev/null +++ b/src/Enums/EUserType.php @@ -0,0 +1,40 @@ +value => self::Person, + self::Service->value => self::Service, + self::Organization->value => self::Organization, + self::Application->value => self::Application, + default => null, + }; + } + + /** + * @return string[] + */ + public static function getValues(): array + { + return [ + self::Person->value, + self::Service->value, + self::Organization->value, + self::Application->value, + ]; + } +} diff --git a/src/Factory/ActivityPub/PersonFactory.php b/src/Factory/ActivityPub/PersonFactory.php index 1f02fcb704..845736fdc1 100644 --- a/src/Factory/ActivityPub/PersonFactory.php +++ b/src/Factory/ActivityPub/PersonFactory.php @@ -30,7 +30,7 @@ public function create(User $user, bool $context = true): array $person = array_merge( $person ?? [], [ 'id' => $this->getActivityPubId($user), - 'type' => $user->type, + 'type' => $user->type->value, 'name' => $user->title ?? $user->getUsername(), 'preferredUsername' => $user->username, 'inbox' => $this->urlGenerator->generate( diff --git a/src/Factory/UserFactory.php b/src/Factory/UserFactory.php index cad96ed7a0..c6f068da12 100644 --- a/src/Factory/UserFactory.php +++ b/src/Factory/UserFactory.php @@ -8,6 +8,7 @@ use App\DTO\UserSignupResponseDto; use App\DTO\UserSmallResponseDto; use App\Entity\User; +use App\Enums\EUserType; use App\Repository\InstanceRepository; use Symfony\Bundle\SecurityBundle\Security; @@ -36,7 +37,7 @@ public function createDto(User $user, ?int $reputationPoints = null): UserDto $user->apProfileId, $user->getId(), $user->followersCount, - 'Service' === $user->type, // setting isBot + EUserType::Service === $user->type, // setting isBot $user->isAdmin(), $user->isModerator(), $currentUser && ($currentUser->isAdmin() || $currentUser->isModerator()) ? $user->applicationText : null, diff --git a/src/MessageHandler/ActivityPub/Inbox/BlockHandler.php b/src/MessageHandler/ActivityPub/Inbox/BlockHandler.php index 6e1e55655f..2b96b1c521 100644 --- a/src/MessageHandler/ActivityPub/Inbox/BlockHandler.php +++ b/src/MessageHandler/ActivityPub/Inbox/BlockHandler.php @@ -8,6 +8,7 @@ use App\Entity\Magazine; use App\Entity\MagazineBan; use App\Entity\User; +use App\Enums\EUserType; use App\Exception\UserCannotBeBanned; use App\Message\ActivityPub\Inbox\BlockMessage; use App\Message\ActivityPub\Outbox\GenericAnnounceMessage; @@ -102,7 +103,7 @@ public function doWork(MessageInterface $message): void $expireDate = new \DateTimeImmutable($payload['expires']); } - if (null === $target || ($target instanceof User && 'Application' === $target->type)) { + if (null === $target || ($target instanceof User && EUserType::Application === $target->type)) { $this->handleInstanceBan($bannedUser, $actor, $reason, $isUndo); } else { $this->handleMagazineBan($message->payload, $bannedUser, $actor, $target, $reason, $expireDate, $isUndo); diff --git a/src/PageView/ContentPageView.php b/src/PageView/ContentPageView.php index b3a342a55e..95015ce91a 100644 --- a/src/PageView/ContentPageView.php +++ b/src/PageView/ContentPageView.php @@ -23,7 +23,7 @@ public function resolveSort(?string $value): string $defaultRoute = $routes['hot']; $user = $this->security->getUser(); if ($user instanceof User) { - $defaultRoute = $user->frontDefaultSort; + $defaultRoute = $user->frontDefaultSort->value; } return 'default' !== $value ? $routes[$value] : $defaultRoute; diff --git a/src/PageView/EntryCommentPageView.php b/src/PageView/EntryCommentPageView.php index 535c777423..8ba8385ac7 100644 --- a/src/PageView/EntryCommentPageView.php +++ b/src/PageView/EntryCommentPageView.php @@ -39,7 +39,7 @@ public function resolveSort(?string $value): string $defaultRoute = $routes['hot']; $user = $this->security->getUser(); if ($user instanceof User) { - $defaultRoute = $user->commentDefaultSort; + $defaultRoute = $user->commentDefaultSort->value; } return 'default' !== $value ? $routes[$value] : $defaultRoute; diff --git a/src/PageView/EntryPageView.php b/src/PageView/EntryPageView.php index bafc9c1ad3..967974b2f9 100644 --- a/src/PageView/EntryPageView.php +++ b/src/PageView/EntryPageView.php @@ -31,7 +31,7 @@ public function resolveSort(?string $value): string $defaultRoute = $routes['hot']; $user = $this->security->getUser(); if ($user instanceof User) { - $defaultRoute = $user->frontDefaultSort; + $defaultRoute = $user->frontDefaultSort->value; } return 'default' !== $value ? $routes[$value] : $defaultRoute; diff --git a/src/PageView/PostCommentPageView.php b/src/PageView/PostCommentPageView.php index 667c9d14ae..82eaa34158 100644 --- a/src/PageView/PostCommentPageView.php +++ b/src/PageView/PostCommentPageView.php @@ -33,7 +33,7 @@ public function resolveSort(?string $value): string $defaultRoute = $routes['hot']; $user = $this->security->getUser(); if ($user instanceof User) { - $defaultRoute = $user->commentDefaultSort; + $defaultRoute = $user->commentDefaultSort->value; } return 'default' !== $value ? $routes[$value] : $defaultRoute; diff --git a/src/PageView/PostPageView.php b/src/PageView/PostPageView.php index 37f9eb37ae..93ab8b9c66 100644 --- a/src/PageView/PostPageView.php +++ b/src/PageView/PostPageView.php @@ -31,7 +31,7 @@ public function resolveSort(?string $value): string $defaultRoute = $routes['hot']; $user = $this->security->getUser(); if ($user instanceof User) { - $defaultRoute = $user->frontDefaultSort; + $defaultRoute = $user->frontDefaultSort->value; } return 'default' !== $value ? $routes[$value] : $defaultRoute; diff --git a/src/Service/ActivityPubManager.php b/src/Service/ActivityPubManager.php index 614603455d..a71d1cb6e3 100644 --- a/src/Service/ActivityPubManager.php +++ b/src/Service/ActivityPubManager.php @@ -18,6 +18,7 @@ use App\Entity\Post; use App\Entity\PostComment; use App\Entity\User; +use App\Enums\EUserType; use App\Exception\InstanceBannedException; use App\Exception\InvalidApPostException; use App\Exception\InvalidWebfingerException; @@ -396,7 +397,7 @@ private function updateUser(string $actorUrl): ?User // Check if actor isn't empty (not set/null/empty array/etc.) if (isset($actor['endpoints']['sharedInbox']) || isset($actor['inbox'])) { // Update the following user columns - $user->type = $actor['type'] ?? 'Person'; + $user->type = EUserType::getFromString($actor['type']) ?? EUserType::Person; $user->apInboxUrl = $actor['endpoints']['sharedInbox'] ?? $actor['inbox']; $user->apDomain = parse_url($actor['id'], PHP_URL_HOST); if ($actor['preferredUsername']) { diff --git a/src/Service/UserManager.php b/src/Service/UserManager.php index 81369e63a4..5525279b7a 100644 --- a/src/Service/UserManager.php +++ b/src/Service/UserManager.php @@ -10,6 +10,7 @@ use App\Entity\User; use App\Entity\UserFollowRequest; use App\Enums\EApplicationStatus; +use App\Enums\EUserType; use App\Event\Instance\InstanceBanEvent; use App\Event\User\UserApplicationApprovedEvent; use App\Event\User\UserApplicationRejectedEvent; @@ -168,7 +169,7 @@ public function create(UserDto $dto, bool $verifyUserEmail = true, $rateLimit = $status = EApplicationStatus::Pending; } - $user = new User($dto->email, $dto->username, '', ($dto->isBot) ? 'Service' : 'Person', $dto->apProfileId, $dto->apId, applicationStatus: $status, applicationText: $dto->applicationText); + $user = new User($dto->email, $dto->username, '', ($dto->isBot) ? EUserType::Service : EUserType::Person, $dto->apProfileId, $dto->apId, applicationStatus: $status, applicationText: $dto->applicationText); $user->setPassword($this->passwordHasher->hashPassword($user, $dto->plainPassword)); if (!$dto->apId) { diff --git a/src/Service/UserSettingsManager.php b/src/Service/UserSettingsManager.php index d5e8d7af44..6eccbd2cdc 100644 --- a/src/Service/UserSettingsManager.php +++ b/src/Service/UserSettingsManager.php @@ -6,6 +6,9 @@ use App\DTO\UserSettingsDto; use App\Entity\User; +use App\Enums\EDirectMessageSettings; +use App\Enums\EFrontContentOptions; +use App\Enums\ESortOptions; use Doctrine\ORM\EntityManagerInterface; class UserSettingsManager @@ -29,16 +32,16 @@ public function createDto(User $user): UserSettingsDto $user->addMentionsEntries, $user->addMentionsPosts, $user->homepage, - $user->frontDefaultSort, - $user->commentDefaultSort, + $user->frontDefaultSort->value, + $user->commentDefaultSort->value, $user->showBoostsOfFollowing, $user->featuredMagazines, $user->preferredLanguages, $user->customCss, $user->ignoreMagazinesCustomCss, $user->notifyOnUserSignup, - $user->directMessageSetting, - $user->frontDefaultContent, + $user->directMessageSetting->value, + $user->frontDefaultContent->value, $user->apDiscoverable, $user->apIndexable, ); @@ -53,8 +56,8 @@ public function update(User $user, UserSettingsDto $dto): void $user->notifyOnNewEntryReply = $dto->notifyOnNewEntryReply; $user->notifyOnNewPostCommentReply = $dto->notifyOnNewPostCommentReply; $user->homepage = $dto->homepage; - $user->frontDefaultSort = $dto->frontDefaultSort; - $user->commentDefaultSort = $dto->commentDefaultSort; + $user->frontDefaultSort = null !== $dto->frontDefaultSort ? ESortOptions::getFromString($dto->frontDefaultSort) : null; + $user->commentDefaultSort = null !== $dto->commentDefaultSort ? ESortOptions::getFromString($dto->commentDefaultSort) : null; $user->showBoostsOfFollowing = $dto->showFollowingBoosts ?? false; $user->hideAdult = $dto->hideAdult; $user->showProfileSubscriptions = $dto->showProfileSubscriptions; @@ -65,8 +68,8 @@ public function update(User $user, UserSettingsDto $dto): void $user->preferredLanguages = $dto->preferredLanguages ? array_unique($dto->preferredLanguages) : []; $user->customCss = $dto->customCss; $user->ignoreMagazinesCustomCss = $dto->ignoreMagazinesCustomCss; - $user->directMessageSetting = $dto->directMessageSetting; - $user->frontDefaultContent = $dto->frontDefaultContent; + $user->directMessageSetting = null !== $dto->directMessageSetting ? EDirectMessageSettings::getFromString($dto->directMessageSetting) : null; + $user->frontDefaultContent = null !== $dto->frontDefaultContent ? EFrontContentOptions::getFromString($dto->frontDefaultContent) : null; if (null !== $dto->notifyOnUserSignup) { $user->notifyOnUserSignup = $dto->notifyOnUserSignup; diff --git a/src/Service/VoteManager.php b/src/Service/VoteManager.php index 0be618a201..1ffbbecb08 100644 --- a/src/Service/VoteManager.php +++ b/src/Service/VoteManager.php @@ -11,6 +11,7 @@ use App\Entity\PostComment; use App\Entity\User; use App\Entity\Vote; +use App\Enums\EUserType; use App\Event\VoteEvent; use App\Factory\VoteFactory; use App\Utils\DownvotesMode; @@ -44,7 +45,7 @@ public function vote(int $choice, VotableInterface $votable, User $user, $rateLi if (DownvotesMode::Disabled === $downVotesMode && VotableInterface::VOTE_DOWN === $choice) { throw new \LogicException('cannot downvote, because that is disabled'); } - if (VotableInterface::VOTE_DOWN === $choice && 'Service' === $user->type) { + if (VotableInterface::VOTE_DOWN === $choice && EUserType::Service === $user->type) { throw new AccessDeniedHttpException('Bots are not allowed to vote on items!'); } diff --git a/src/Twig/Runtime/ContextExtensionRuntime.php b/src/Twig/Runtime/ContextExtensionRuntime.php index 9e88e457bd..e45b237c96 100644 --- a/src/Twig/Runtime/ContextExtensionRuntime.php +++ b/src/Twig/Runtime/ContextExtensionRuntime.php @@ -73,7 +73,7 @@ public function getDefaultSortOption(): string $defaultSort = 'hot'; $user = $this->security->getUser(); if ($user instanceof User) { - $defaultSort = $user->frontDefaultSort; + $defaultSort = $user->frontDefaultSort->value; } return $defaultSort; @@ -92,7 +92,7 @@ public function getDefaultSortOptionForComments(): string $defaultSort = 'hot'; $user = $this->security->getUser(); if ($user instanceof User) { - $defaultSort = $user->commentDefaultSort; + $defaultSort = $user->commentDefaultSort->value; } return $defaultSort; diff --git a/symfony.lock b/symfony.lock index 74822b17ae..6a53cfdc67 100644 --- a/symfony.lock +++ b/symfony.lock @@ -135,6 +135,9 @@ "guzzlehttp/psr7": { "version": "2.0.0" }, + "heymoon/doctrine-psql-enum": { + "version": "3.3.0" + }, "imagine/imagine": { "version": "1.2.4" }, diff --git a/tests/FactoryTrait.php b/tests/FactoryTrait.php index cac582ac2d..2834827461 100644 --- a/tests/FactoryTrait.php +++ b/tests/FactoryTrait.php @@ -27,6 +27,7 @@ use App\Entity\PostComment; use App\Entity\Site; use App\Entity\User; +use App\Enums\EUserType; use App\Service\UserManager; use League\Bundle\OAuth2ServerBundle\Manager\ClientManagerInterface; use League\Bundle\OAuth2ServerBundle\ValueObject\Grant; @@ -84,7 +85,7 @@ private function provideUsers(): iterable ]; } - private function createUser(string $username, ?string $email = null, ?string $password = null, string $type = 'Person', $active = true, $hideAdult = true, $about = null, $addImage = true): User + private function createUser(string $username, ?string $email = null, ?string $password = null, EUserType $type = EUserType::Person, $active = true, $hideAdult = true, $about = null, $addImage = true): User { $user = new User($email ?: $username.'@example.com', $username, $password ?: 'secret', $type); diff --git a/tests/Functional/ActivityPub/Inbox/CreateHandlerTest.php b/tests/Functional/ActivityPub/Inbox/CreateHandlerTest.php index 971fdfeabb..8c68c7a82d 100644 --- a/tests/Functional/ActivityPub/Inbox/CreateHandlerTest.php +++ b/tests/Functional/ActivityPub/Inbox/CreateHandlerTest.php @@ -202,14 +202,14 @@ public function testCreateMessage(): void public function testCreateMessageFollowersOnlyFails(): void { - $this->localUser->directMessageSetting = EDirectMessageSettings::FollowersOnly->value; + $this->localUser->directMessageSetting = EDirectMessageSettings::FollowersOnly; self::expectException(HandlerFailedException::class); $this->bus->dispatch(new ActivityMessage(json_encode($this->createMessage))); } public function testCreateMessageFollowersOnly(): void { - $this->localUser->directMessageSetting = EDirectMessageSettings::FollowersOnly->value; + $this->localUser->directMessageSetting = EDirectMessageSettings::FollowersOnly; $this->userManager->follow($this->remoteUser, $this->localUser); $this->bus->dispatch(new ActivityMessage(json_encode($this->createMessage))); $message = $this->messageRepository->findOneBy(['apId' => $this->createMessage['object']['id']]); @@ -218,7 +218,7 @@ public function testCreateMessageFollowersOnly(): void public function testCreateMessageNobodyFails(): void { - $this->localUser->directMessageSetting = EDirectMessageSettings::Nobody->value; + $this->localUser->directMessageSetting = EDirectMessageSettings::Nobody; $this->userManager->follow($this->remoteUser, $this->localUser); self::expectException(HandlerFailedException::class); $this->bus->dispatch(new ActivityMessage(json_encode($this->createMessage))); diff --git a/tests/Functional/ActivityPub/MarkdownConverterTest.php b/tests/Functional/ActivityPub/MarkdownConverterTest.php index 47ff05a613..838f68a63d 100644 --- a/tests/Functional/ActivityPub/MarkdownConverterTest.php +++ b/tests/Functional/ActivityPub/MarkdownConverterTest.php @@ -5,6 +5,7 @@ namespace App\Tests\Functional\ActivityPub; use App\Entity\User; +use App\Enums\EUserType; use PHPUnit\Framework\Attributes\DataProvider; use function PHPUnit\Framework\assertEquals; @@ -33,7 +34,7 @@ public function setUp(): void // generate the local user 'someUser' $user = $this->getUserByUsername('someUser', email: 'someUser@kbin.test'); $this->getMagazineByName('someMagazine', $user); - $mastodonUser = new User('SomeUser@mastodon.tld', 'SomeUser@mastodon.tld', '', 'Person', 'https://mastodon.tld/users/SomeAccount'); + $mastodonUser = new User('SomeUser@mastodon.tld', 'SomeUser@mastodon.tld', '', EUserType::Person, 'https://mastodon.tld/users/SomeAccount'); $mastodonUser->apPublicUrl = 'https://mastodon.tld/@SomeAccount'; $this->entityManager->persist($mastodonUser); } diff --git a/tests/Functional/Controller/Entry/EntryFrontControllerTest.php b/tests/Functional/Controller/Entry/EntryFrontControllerTest.php index ef61ae04f2..1e14be41e7 100644 --- a/tests/Functional/Controller/Entry/EntryFrontControllerTest.php +++ b/tests/Functional/Controller/Entry/EntryFrontControllerTest.php @@ -292,7 +292,7 @@ public function testCustomDefaultSort(): void self::assertGreaterThan($older->getRanking(), $newer->getRanking()); $user = $this->getUserByUsername('user'); - $user->frontDefaultSort = ESortOptions::Newest->value; + $user->frontDefaultSort = ESortOptions::Newest; $this->entityManager->flush(); $this->client->loginUser($user); @@ -311,7 +311,7 @@ public function testCustomDefaultSort(): void $secondId = $secondNode->attributes->getNamedItem('id')->nodeValue; self::assertEquals("entry-{$older->getId()}", $secondId); - $user->frontDefaultSort = ESortOptions::Commented->value; + $user->frontDefaultSort = ESortOptions::Commented; $this->entityManager->flush(); $crawler = $this->client->request('GET', '/'); diff --git a/tests/Functional/Controller/Entry/EntrySingleControllerTest.php b/tests/Functional/Controller/Entry/EntrySingleControllerTest.php index a47aae11c1..6023d04242 100644 --- a/tests/Functional/Controller/Entry/EntrySingleControllerTest.php +++ b/tests/Functional/Controller/Entry/EntrySingleControllerTest.php @@ -95,7 +95,7 @@ public function testCommentsDefaultSortOption(): void $older->createdAt = new \DateTimeImmutable('now - 1 day'); $newer = $this->createEntryComment('newer comment', entry: $entry); - $user->commentDefaultSort = ESortOptions::Oldest->value; + $user->commentDefaultSort = ESortOptions::Oldest; $this->entityManager->flush(); $this->client->loginUser($user); @@ -113,7 +113,7 @@ public function testCommentsDefaultSortOption(): void $secondId = $secondNode->attributes->getNamedItem('id')->nodeValue; self::assertEquals("entry-comment-{$newer->getId()}", $secondId); - $user->commentDefaultSort = ESortOptions::Newest->value; + $user->commentDefaultSort = ESortOptions::Newest; $this->entityManager->flush(); $crawler = $this->client->request('GET', "/m/{$entry->magazine->name}/t/{$entry->getId()}/-"); diff --git a/tests/Functional/Controller/Post/PostFrontControllerTest.php b/tests/Functional/Controller/Post/PostFrontControllerTest.php index 16a8807713..4c8659589d 100644 --- a/tests/Functional/Controller/Post/PostFrontControllerTest.php +++ b/tests/Functional/Controller/Post/PostFrontControllerTest.php @@ -167,7 +167,7 @@ public function testCustomDefaultSort(): void self::assertGreaterThan($older->getRanking(), $newer->getRanking()); $user = $this->getUserByUsername('user'); - $user->frontDefaultSort = ESortOptions::Newest->value; + $user->frontDefaultSort = ESortOptions::Newest; $this->entityManager->flush(); $this->client->loginUser($user); @@ -186,7 +186,7 @@ public function testCustomDefaultSort(): void $secondId = $secondNode->attributes->getNamedItem('id')->nodeValue; self::assertEquals("post-{$older->getId()}", $secondId); - $user->frontDefaultSort = ESortOptions::Commented->value; + $user->frontDefaultSort = ESortOptions::Commented; $this->entityManager->flush(); $crawler = $this->client->request('GET', '/microblog'); diff --git a/tests/Functional/Controller/Post/PostSingleControllerTest.php b/tests/Functional/Controller/Post/PostSingleControllerTest.php index 3dd0c6bdc1..473ebfcb1a 100644 --- a/tests/Functional/Controller/Post/PostSingleControllerTest.php +++ b/tests/Functional/Controller/Post/PostSingleControllerTest.php @@ -66,7 +66,7 @@ public function testCommentsDefaultSortOption(): void $older->createdAt = new \DateTimeImmutable('now - 1 day'); $newer = $this->createPostComment('newer comment', post: $post); - $user->commentDefaultSort = ESortOptions::Oldest->value; + $user->commentDefaultSort = ESortOptions::Oldest; $this->entityManager->flush(); $this->client->loginUser($user); @@ -84,7 +84,7 @@ public function testCommentsDefaultSortOption(): void $secondId = $secondNode->attributes->getNamedItem('id')->nodeValue; self::assertEquals("post-comment-{$newer->getId()}", $secondId); - $user->commentDefaultSort = ESortOptions::Newest->value; + $user->commentDefaultSort = ESortOptions::Newest; $this->entityManager->flush(); $crawler = $this->client->request('GET', "/m/{$post->magazine->name}/p/{$post->getId()}/-");