From 5e6e930fb10a20b9fb99da0662ebfa25d6dd9bf9 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 1 Oct 2025 14:45:34 +0200 Subject: [PATCH 1/4] Added user and made entities blameable and timestampable --- composer.json | 2 + composer.lock | 215 +++++++++++++++++- config/bundles.php | 1 + config/packages/security.yaml | 8 +- config/packages/stof_doctrine_extensions.yaml | 8 + fixtures/user.yaml | 12 + migrations/Version20251001122515.php | 32 +++ migrations/Version20251001123027.php | 58 +++++ .../Admin/DataSourceCrudController.php | 7 + .../Admin/ProcessOverviewCrudController.php | 6 + .../ProcessOverviewGroupCrudController.php | 6 + src/DataFixtures/Faker/Provider/Provider.php | 35 +++ src/Entity/DataSource.php | 5 + src/Entity/ProcessOverview.php | 5 + src/Entity/ProcessOverviewGroup.php | 5 + src/Entity/User.php | 119 ++++++++++ src/Repository/UserRepository.php | 60 +++++ symfony.lock | 12 + 18 files changed, 593 insertions(+), 3 deletions(-) create mode 100644 config/packages/stof_doctrine_extensions.yaml create mode 100644 fixtures/user.yaml create mode 100644 migrations/Version20251001122515.php create mode 100644 migrations/Version20251001123027.php create mode 100644 src/DataFixtures/Faker/Provider/Provider.php create mode 100644 src/Entity/User.php create mode 100644 src/Repository/UserRepository.php diff --git a/composer.json b/composer.json index 07be0df..ba4bb4d 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ "easycorp/easyadmin-bundle": "^4.25.1", "league/uri-components": "^7.5.1", "runtime/frankenphp-symfony": "^0.2.0", + "stof/doctrine-extensions-bundle": "^1.14", "symfony/asset": "~7.3.0", "symfony/asset-mapper": "~7.3.4", "symfony/console": "~7.3.4", @@ -21,6 +22,7 @@ "symfony/framework-bundle": "~7.3.4", "symfony/mercure-bundle": "^0.3.9", "symfony/runtime": "~7.3.4", + "symfony/security-bundle": "~7.3.0", "symfony/translation": "~7.3.4", "symfony/twig-bundle": "~7.3.4", "symfony/yaml": "~7.3.3", diff --git a/composer.lock b/composer.lock index 15fd361..6858bdc 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": "44a19a2f82d0fda0f3d8f52583a8f081", + "content-hash": "c86c9f1ba9f64bd04b3d9ba4896749d0", "packages": [ { "name": "composer/semver", @@ -1301,6 +1301,138 @@ ], "time": "2025-09-10T05:00:12+00:00" }, + { + "name": "gedmo/doctrine-extensions", + "version": "v3.21.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine-extensions/DoctrineExtensions.git", + "reference": "eb53dfcb2b592327b76ac5226fbb003d32aea37e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine-extensions/DoctrineExtensions/zipball/eb53dfcb2b592327b76ac5226fbb003d32aea37e", + "reference": "eb53dfcb2b592327b76ac5226fbb003d32aea37e", + "shasum": "" + }, + "require": { + "doctrine/collections": "^1.2 || ^2.0", + "doctrine/deprecations": "^1.0", + "doctrine/event-manager": "^1.2 || ^2.0", + "doctrine/persistence": "^2.2 || ^3.0 || ^4.0", + "php": "^7.4 || ^8.0", + "psr/cache": "^1 || ^2 || ^3", + "psr/clock": "^1", + "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/string": "^5.4 || ^6.0 || ^7.0" + }, + "conflict": { + "behat/transliterator": "<1.2 || >=2.0", + "doctrine/annotations": "<1.13 || >=3.0", + "doctrine/common": "<2.13 || >=4.0", + "doctrine/dbal": "<3.7 || >=5.0", + "doctrine/mongodb-odm": "<2.3 || >=3.0", + "doctrine/orm": "<2.20 || >=3.0 <3.3 || >=4.0" + }, + "require-dev": { + "behat/transliterator": "^1.2", + "doctrine/annotations": "^1.13 || ^2.0", + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/common": "^2.13 || ^3.0", + "doctrine/dbal": "^3.7 || ^4.0", + "doctrine/doctrine-bundle": "^2.3", + "doctrine/mongodb-odm": "^2.3", + "doctrine/orm": "^2.20 || ^3.3", + "friendsofphp/php-cs-fixer": "^3.70", + "nesbot/carbon": "^2.71 || ^3.0", + "phpstan/phpstan": "^2.1.1", + "phpstan/phpstan-doctrine": "^2.0.1", + "phpstan/phpstan-phpunit": "^2.0.3", + "phpunit/phpunit": "^9.6", + "rector/rector": "^2.0.6", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/doctrine-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^6.4 || ^7.0", + "symfony/uid": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "doctrine/mongodb-odm": "to use the extensions with the MongoDB ODM", + "doctrine/orm": "to use the extensions with the ORM" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Gedmo\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gediminas Morkevicius", + "email": "gediminas.morkevicius@gmail.com" + }, + { + "name": "Gustavo Falco", + "email": "comfortablynumb84@gmail.com" + }, + { + "name": "David Buchmann", + "email": "david@liip.ch" + } + ], + "description": "Doctrine behavioral extensions", + "homepage": "http://gediminasm.org/", + "keywords": [ + "Blameable", + "behaviors", + "doctrine", + "extensions", + "gedmo", + "loggable", + "nestedset", + "odm", + "orm", + "sluggable", + "sortable", + "timestampable", + "translatable", + "tree", + "uploadable" + ], + "support": { + "docs": "https://github.com/doctrine-extensions/DoctrineExtensions/tree/main/doc", + "issues": "https://github.com/doctrine-extensions/DoctrineExtensions/issues", + "source": "https://github.com/doctrine-extensions/DoctrineExtensions/tree/v3.21.0" + }, + "funding": [ + { + "url": "https://github.com/l3pp4rd", + "type": "github" + }, + { + "url": "https://github.com/mbabker", + "type": "github" + }, + { + "url": "https://github.com/phansys", + "type": "github" + }, + { + "url": "https://github.com/stof", + "type": "github" + } + ], + "time": "2025-09-22T17:04:34+00:00" + }, { "name": "lcobucci/jwt", "version": "5.5.0", @@ -2096,6 +2228,87 @@ ], "time": "2023-12-12T12:06:11+00:00" }, + { + "name": "stof/doctrine-extensions-bundle", + "version": "v1.14.0", + "source": { + "type": "git", + "url": "https://github.com/stof/StofDoctrineExtensionsBundle.git", + "reference": "bdf3eb10baeb497ac5985b8f78a6cf55862c2662" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stof/StofDoctrineExtensionsBundle/zipball/bdf3eb10baeb497ac5985b8f78a6cf55862c2662", + "reference": "bdf3eb10baeb497ac5985b8f78a6cf55862c2662", + "shasum": "" + }, + "require": { + "gedmo/doctrine-extensions": "^3.20.0", + "php": "^8.1", + "symfony/cache": "^6.4 || ^7.0", + "symfony/config": "^6.4 || ^7.0", + "symfony/dependency-injection": "^6.4 || ^7.0", + "symfony/event-dispatcher": "^6.4 || ^7.0", + "symfony/http-kernel": "^6.4 || ^7.0", + "symfony/translation-contracts": "^2.5 || ^3.5" + }, + "require-dev": { + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-deprecation-rules": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpstan/phpstan-symfony": "^2.0", + "symfony/mime": "^6.4 || ^7.0", + "symfony/phpunit-bridge": "^v6.4.1 || ^7.0.1", + "symfony/security-core": "^6.4 || ^7.0" + }, + "suggest": { + "doctrine/doctrine-bundle": "to use the ORM extensions", + "doctrine/mongodb-odm-bundle": "to use the MongoDB ODM extensions", + "symfony/mime": "To use the Mime component integration for Uploadable" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Stof\\DoctrineExtensionsBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christophe Coevoet", + "email": "stof@notk.org" + } + ], + "description": "Integration of the gedmo/doctrine-extensions with Symfony", + "homepage": "https://github.com/stof/StofDoctrineExtensionsBundle", + "keywords": [ + "behaviors", + "doctrine2", + "extensions", + "gedmo", + "loggable", + "nestedset", + "sluggable", + "sortable", + "timestampable", + "translatable", + "tree" + ], + "support": { + "issues": "https://github.com/stof/StofDoctrineExtensionsBundle/issues", + "source": "https://github.com/stof/StofDoctrineExtensionsBundle/tree/v1.14.0" + }, + "time": "2025-05-01T08:00:32+00:00" + }, { "name": "symfony/asset", "version": "v7.3.0", diff --git a/config/bundles.php b/config/bundles.php index 023c1cd..900b6d0 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -18,4 +18,5 @@ Nelmio\CorsBundle\NelmioCorsBundle::class => ['dev' => true], Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true], Symfonycasts\TailwindBundle\SymfonycastsTailwindBundle::class => ['all' => true], + Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true], ]; diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 367af25..fbfb8ed 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -4,14 +4,18 @@ security: Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider providers: - users_in_memory: { memory: null } + # used to reload user from session & other features (e.g. switch_user) + app_user_provider: + entity: + class: App\Entity\User + property: email firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: lazy: true - provider: users_in_memory + provider: app_user_provider # activate different ways to authenticate # https://symfony.com/doc/current/security.html#the-firewall diff --git a/config/packages/stof_doctrine_extensions.yaml b/config/packages/stof_doctrine_extensions.yaml new file mode 100644 index 0000000..45fe1b3 --- /dev/null +++ b/config/packages/stof_doctrine_extensions.yaml @@ -0,0 +1,8 @@ +# Read the documentation: https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html +# See the official DoctrineExtensions documentation for more details: https://github.com/doctrine-extensions/DoctrineExtensions/tree/main/doc +stof_doctrine_extensions: + default_locale: '%env(DEFAULT_LOCALE)%' + orm: + default: + blameable: true + timestampable: true diff --git a/fixtures/user.yaml b/fixtures/user.yaml new file mode 100644 index 0000000..430772c --- /dev/null +++ b/fixtures/user.yaml @@ -0,0 +1,12 @@ +App\Entity\User: + admin: + email: admin@example.com + password: '' + roles: + - ROLE_ADMIN + + user: + email: user@example.com + password: '' + roles: + - ROLE_USER diff --git a/migrations/Version20251001122515.php b/migrations/Version20251001122515.php new file mode 100644 index 0000000..165de4b --- /dev/null +++ b/migrations/Version20251001122515.php @@ -0,0 +1,32 @@ +addSql('CREATE TABLE user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(180) NOT NULL, roles CLOB NOT NULL, password VARCHAR(255) NOT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL)'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON user (email)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE user'); + } +} diff --git a/migrations/Version20251001123027.php b/migrations/Version20251001123027.php new file mode 100644 index 0000000..8af73f0 --- /dev/null +++ b/migrations/Version20251001123027.php @@ -0,0 +1,58 @@ +addSql('ALTER TABLE data_source ADD COLUMN created_by VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE data_source ADD COLUMN updated_by VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE data_source ADD COLUMN created_at DATETIME NOT NULL'); + $this->addSql('ALTER TABLE data_source ADD COLUMN updated_at DATETIME NOT NULL'); + $this->addSql('ALTER TABLE rpa_process_overview_process_overview ADD COLUMN created_by VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE rpa_process_overview_process_overview ADD COLUMN updated_by VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE rpa_process_overview_process_overview ADD COLUMN created_at DATETIME NOT NULL'); + $this->addSql('ALTER TABLE rpa_process_overview_process_overview ADD COLUMN updated_at DATETIME NOT NULL'); + $this->addSql('ALTER TABLE rpa_process_overview_process_overview_group ADD COLUMN created_by VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE rpa_process_overview_process_overview_group ADD COLUMN updated_by VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE rpa_process_overview_process_overview_group ADD COLUMN created_at DATETIME NOT NULL'); + $this->addSql('ALTER TABLE rpa_process_overview_process_overview_group ADD COLUMN updated_at DATETIME NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TEMPORARY TABLE __temp__data_source AS SELECT id, label, options, url FROM data_source'); + $this->addSql('DROP TABLE data_source'); + $this->addSql('CREATE TABLE data_source (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, label VARCHAR(255) NOT NULL, options CLOB DEFAULT NULL, url VARCHAR(255) NOT NULL)'); + $this->addSql('INSERT INTO data_source (id, label, options, url) SELECT id, label, options, url FROM __temp__data_source'); + $this->addSql('DROP TABLE __temp__data_source'); + $this->addSql('CREATE TEMPORARY TABLE __temp__rpa_process_overview_process_overview AS SELECT id, label, options, process_id, group_id, data_source_id FROM rpa_process_overview_process_overview'); + $this->addSql('DROP TABLE rpa_process_overview_process_overview'); + $this->addSql('CREATE TABLE rpa_process_overview_process_overview (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, label VARCHAR(255) NOT NULL, options CLOB DEFAULT NULL, process_id VARCHAR(255) DEFAULT NULL, group_id INTEGER NOT NULL, data_source_id INTEGER NOT NULL, CONSTRAINT FK_437BDF18FE54D947 FOREIGN KEY (group_id) REFERENCES rpa_process_overview_process_overview_group (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_437BDF181A935C57 FOREIGN KEY (data_source_id) REFERENCES data_source (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO rpa_process_overview_process_overview (id, label, options, process_id, group_id, data_source_id) SELECT id, label, options, process_id, group_id, data_source_id FROM __temp__rpa_process_overview_process_overview'); + $this->addSql('DROP TABLE __temp__rpa_process_overview_process_overview'); + $this->addSql('CREATE INDEX IDX_437BDF18FE54D947 ON rpa_process_overview_process_overview (group_id)'); + $this->addSql('CREATE INDEX IDX_437BDF181A935C57 ON rpa_process_overview_process_overview (data_source_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__rpa_process_overview_process_overview_group AS SELECT id, label FROM rpa_process_overview_process_overview_group'); + $this->addSql('DROP TABLE rpa_process_overview_process_overview_group'); + $this->addSql('CREATE TABLE rpa_process_overview_process_overview_group (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, label VARCHAR(255) NOT NULL)'); + $this->addSql('INSERT INTO rpa_process_overview_process_overview_group (id, label) SELECT id, label FROM __temp__rpa_process_overview_process_overview_group'); + $this->addSql('DROP TABLE __temp__rpa_process_overview_process_overview_group'); + } +} diff --git a/src/Controller/Admin/DataSourceCrudController.php b/src/Controller/Admin/DataSourceCrudController.php index 3bda22d..801cfcc 100644 --- a/src/Controller/Admin/DataSourceCrudController.php +++ b/src/Controller/Admin/DataSourceCrudController.php @@ -5,6 +5,7 @@ use App\Entity\DataSource; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; use EasyCorp\Bundle\EasyAdminBundle\Field\CodeEditorField; +use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; use Symfony\Component\Form\Extension\Core\Type\UrlType; @@ -29,6 +30,12 @@ public function configureFields(string $pageName): iterable yield TextField::new('label', t('Label')); yield TextField::new('url', t('URL')) ->setFormType(UrlType::class); + + yield TextField::new('createdBy', t('Created by')) + ->hideOnForm(); + yield DateTimeField::new('createdAt', t('Created at')) + ->hideOnForm(); + yield CodeEditorField::new('options', t('Options')) ->hideOnIndex() ->setLanguage('yaml'); diff --git a/src/Controller/Admin/ProcessOverviewCrudController.php b/src/Controller/Admin/ProcessOverviewCrudController.php index d5cb0fd..06f86df 100644 --- a/src/Controller/Admin/ProcessOverviewCrudController.php +++ b/src/Controller/Admin/ProcessOverviewCrudController.php @@ -11,6 +11,7 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField; use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField; use EasyCorp\Bundle\EasyAdminBundle\Field\CodeEditorField; +use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField; use EasyCorp\Bundle\EasyAdminBundle\Field\FormField as EaFormField; use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; @@ -68,6 +69,11 @@ public function configureFields(string $pageName): iterable yield TextField::new('label', t('Label')); yield AssociationField::new('group', t('Group')); + yield TextField::new('createdBy', t('Created by')) + ->hideOnForm(); + yield DateTimeField::new('createdAt', t('Created at')) + ->hideOnForm(); + /** @var ProcessOverview $entity */ $entity = $this->getContext()->getEntity()->getInstance(); $dataSource = $entity?->getDataSource(); diff --git a/src/Controller/Admin/ProcessOverviewGroupCrudController.php b/src/Controller/Admin/ProcessOverviewGroupCrudController.php index a34de1e..4ab444a 100644 --- a/src/Controller/Admin/ProcessOverviewGroupCrudController.php +++ b/src/Controller/Admin/ProcessOverviewGroupCrudController.php @@ -6,6 +6,7 @@ use EasyCorp\Bundle\EasyAdminBundle\Config\Action; use EasyCorp\Bundle\EasyAdminBundle\Config\Actions; use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; +use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField; use EasyCorp\Bundle\EasyAdminBundle\Field\IdField; use EasyCorp\Bundle\EasyAdminBundle\Field\TextField; @@ -39,5 +40,10 @@ public function configureFields(string $pageName): iterable yield IdField::new('id', t('ID')) ->onlyOnDetail(); yield TextField::new('label', t('Label')); + + yield TextField::new('createdBy', t('Created by')) + ->hideOnForm(); + yield DateTimeField::new('createdAt', t('Created at')) + ->hideOnForm(); } } diff --git a/src/DataFixtures/Faker/Provider/Provider.php b/src/DataFixtures/Faker/Provider/Provider.php new file mode 100644 index 0000000..ef2cce9 --- /dev/null +++ b/src/DataFixtures/Faker/Provider/Provider.php @@ -0,0 +1,35 @@ +' + * + * @return string + */ + public function password(PasswordAuthenticatedUserInterface $user, string $plaintextPassword) + { + return $this->passwordHasher->hashPassword( + $user, + $plaintextPassword + ); + } +} diff --git a/src/Entity/DataSource.php b/src/Entity/DataSource.php index bbc5a3d..2ae1472 100644 --- a/src/Entity/DataSource.php +++ b/src/Entity/DataSource.php @@ -7,10 +7,15 @@ use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use Gedmo\Blameable\Traits\BlameableEntity; +use Gedmo\Timestampable\Traits\TimestampableEntity; #[ORM\Entity(repositoryClass: DataSourceRepository::class)] class DataSource { + use BlameableEntity; + use TimestampableEntity; + #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] diff --git a/src/Entity/ProcessOverview.php b/src/Entity/ProcessOverview.php index 02e1f7b..7157709 100644 --- a/src/Entity/ProcessOverview.php +++ b/src/Entity/ProcessOverview.php @@ -8,12 +8,17 @@ use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Event\PreUpdateEventArgs; use Doctrine\ORM\Mapping as ORM; +use Gedmo\Blameable\Traits\BlameableEntity; +use Gedmo\Timestampable\Traits\TimestampableEntity; #[ORM\Entity(repositoryClass: ProcessOverviewRepository::class)] #[ORM\Table(name: 'rpa_process_overview_process_overview')] #[ORM\HasLifecycleCallbacks] class ProcessOverview { + use BlameableEntity; + use TimestampableEntity; + #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] diff --git a/src/Entity/ProcessOverviewGroup.php b/src/Entity/ProcessOverviewGroup.php index 9269b17..9792716 100644 --- a/src/Entity/ProcessOverviewGroup.php +++ b/src/Entity/ProcessOverviewGroup.php @@ -6,11 +6,16 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Gedmo\Blameable\Traits\BlameableEntity; +use Gedmo\Timestampable\Traits\TimestampableEntity; #[ORM\Entity(repositoryClass: ProcessOverviewGroupRepository::class)] #[ORM\Table(name: 'rpa_process_overview_process_overview_group')] class ProcessOverviewGroup { + use BlameableEntity; + use TimestampableEntity; + #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] diff --git a/src/Entity/User.php b/src/Entity/User.php new file mode 100644 index 0000000..ce0e8b3 --- /dev/null +++ b/src/Entity/User.php @@ -0,0 +1,119 @@ + The user roles + */ + #[ORM\Column] + private array $roles = []; + + /** + * @var string The hashed password + */ + #[ORM\Column] + private ?string $password = null; + + public function getId(): ?int + { + return $this->id; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function setEmail(string $email): static + { + $this->email = $email; + + return $this; + } + + /** + * A visual identifier that represents this user. + * + * @see UserInterface + */ + public function getUserIdentifier(): string + { + return (string) $this->email; + } + + /** + * @see UserInterface + */ + public function getRoles(): array + { + $roles = $this->roles; + // guarantee every user at least has ROLE_USER + $roles[] = 'ROLE_USER'; + + return array_unique($roles); + } + + /** + * @param list $roles + */ + public function setRoles(array $roles): static + { + $this->roles = $roles; + + return $this; + } + + /** + * @see PasswordAuthenticatedUserInterface + */ + public function getPassword(): ?string + { + return $this->password; + } + + public function setPassword(string $password): static + { + $this->password = $password; + + return $this; + } + + /** + * Ensure the session doesn't contain actual password hashes by CRC32C-hashing them, as supported since Symfony 7.3. + */ + public function __serialize(): array + { + $data = (array) $this; + $data["\0".self::class."\0password"] = hash('crc32c', $this->password); + + return $data; + } + + #[\Deprecated] + public function eraseCredentials(): void + { + // @deprecated, to be removed when upgrading to Symfony 8 + } +} diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php new file mode 100644 index 0000000..4f2804e --- /dev/null +++ b/src/Repository/UserRepository.php @@ -0,0 +1,60 @@ + + */ +class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, User::class); + } + + /** + * Used to upgrade (rehash) the user's password automatically over time. + */ + public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void + { + if (!$user instanceof User) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class)); + } + + $user->setPassword($newHashedPassword); + $this->getEntityManager()->persist($user); + $this->getEntityManager()->flush(); + } + + // /** + // * @return User[] Returns an array of User objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('u') + // ->andWhere('u.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('u.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?User + // { + // return $this->createQueryBuilder('u') + // ->andWhere('u.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/symfony.lock b/symfony.lock index dd81013..46d475c 100644 --- a/symfony.lock +++ b/symfony.lock @@ -96,6 +96,18 @@ "config/packages/nelmio_cors.yaml" ] }, + "stof/doctrine-extensions-bundle": { + "version": "1.14", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.2", + "ref": "e805aba9eff5372e2d149a9ff56566769e22819d" + }, + "files": [ + "config/packages/stof_doctrine_extensions.yaml" + ] + }, "symfony/asset-mapper": { "version": "7.3", "recipe": { From 0904a02cb899a4f93d3f13f783f7742fe4155c42 Mon Sep 17 00:00:00 2001 From: Mikkel Ricky Date: Wed, 1 Oct 2025 14:50:55 +0200 Subject: [PATCH 2/4] Added login form --- config/packages/security.yaml | 13 +++++++-- src/Controller/SecurityController.php | 32 ++++++++++++++++++++++ templates/default/index.html.twig | 2 ++ templates/security/login.html.twig | 38 +++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 src/Controller/SecurityController.php create mode 100644 templates/security/login.html.twig diff --git a/config/packages/security.yaml b/config/packages/security.yaml index fbfb8ed..ed0dc77 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -16,6 +16,14 @@ security: main: lazy: true provider: app_user_provider + form_login: + login_path: app_login + check_path: app_login + enable_csrf: true + logout: + path: app_logout + # where to redirect after logout + # target: app_any_route # activate different ways to authenticate # https://symfony.com/doc/current/security.html#the-firewall @@ -26,8 +34,9 @@ security: # Easy way to control access for large sections of your site # Note: Only the *first* access control that matches will be used access_control: - # - { path: ^/admin, roles: ROLE_ADMIN } - # - { path: ^/profile, roles: ROLE_USER } + - { path: ^/admin, roles: ROLE_ADMIN } + - { path: ^/login, roles: PUBLIC_ACCESS } + - { path: ^/, roles: ROLE_USER } when@test: security: diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php new file mode 100644 index 0000000..76bf5c4 --- /dev/null +++ b/src/Controller/SecurityController.php @@ -0,0 +1,32 @@ +getLastAuthenticationError(); + + // last username entered by the user + $lastUsername = $authenticationUtils->getLastUsername(); + + return $this->render('security/login.html.twig', [ + 'last_username' => $lastUsername, + 'error' => $error, + ]); + } + + #[Route(path: '/logout', name: 'app_logout')] + public function logout(): void + { + throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.'); + } +} diff --git a/templates/default/index.html.twig b/templates/default/index.html.twig index 4b55bb7..7917e66 100644 --- a/templates/default/index.html.twig +++ b/templates/default/index.html.twig @@ -4,9 +4,11 @@ {% block content %}