diff --git a/.ddev/config.yaml b/.ddev/config.yaml index 85bd9b6..84a2edb 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -16,6 +16,7 @@ composer_version: "2" web_environment: [] nodejs_version: "16" disable_upload_dirs_warning: true +timezone: Europe/Berlin # Key features of ddev's config.yaml: diff --git a/composer.json b/composer.json index cc9c08f..5878d0d 100644 --- a/composer.json +++ b/composer.json @@ -37,6 +37,7 @@ "symfony/string": "6.3.*", "symfony/translation": "6.3.*", "symfony/twig-bundle": "6.3.*", + "symfony/uid": "6.3.*", "symfony/validator": "6.3.*", "symfony/web-link": "6.3.*", "symfony/yaml": "6.3.*", diff --git a/composer.lock b/composer.lock index fa403d1..f3beef0 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": "321e741fbc0979c63e04752cd30aba64", + "content-hash": "ef1d5f48771d4c4589b11cab4cc343fa", "packages": [ { "name": "doctrine/cache", @@ -5208,6 +5208,88 @@ ], "time": "2022-11-03T14:55:06+00:00" }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "9c44518a5aff8da565c8a55dbe85d2769e6f630e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/9c44518a5aff8da565c8a55dbe85d2769e6f630e", + "reference": "9c44518a5aff8da565c8a55dbe85d2769e6f630e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.28.0" + }, + "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": "2023-01-26T09:26:14+00:00" + }, { "name": "symfony/process", "version": "v6.3.0", @@ -6626,6 +6708,80 @@ ], "time": "2023-05-06T09:53:41+00:00" }, + { + "name": "symfony/uid", + "version": "v6.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "01b0f20b1351d997711c56f1638f7a8c3061e384" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/01b0f20b1351d997711c56f1638f7a8c3061e384", + "reference": "01b0f20b1351d997711c56f1638f7a8c3061e384", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v6.3.0" + }, + "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": "2023-04-08T07:25:02+00:00" + }, { "name": "symfony/validator", "version": "v6.3.1", @@ -9503,7 +9659,8 @@ "platform": { "php": ">=8.1", "ext-ctype": "*", - "ext-iconv": "*" + "ext-iconv": "*", + "ext-simplexml": "*" }, "platform-dev": [], "plugin-api-version": "2.3.0" diff --git a/config/packages/uid.yaml b/config/packages/uid.yaml new file mode 100644 index 0000000..0152094 --- /dev/null +++ b/config/packages/uid.yaml @@ -0,0 +1,4 @@ +framework: + uid: + default_uuid_version: 7 + time_based_uuid_version: 7 diff --git a/config/services.yaml b/config/services.yaml index c13812c..16c9545 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -19,6 +19,10 @@ services: - '../src/Entity/' - '../src/Kernel.php' + App\EventListener\TimestampableEntitySubscriber: + tags: + - { name: 'doctrine.event_subscriber', connection: 'default' } + App\BusProNet\ApiClient: arguments: $options: { 'bpn_username': '%bpn_username%', 'bpn_password': '%bpn_password%', 'bpn_url': '%bpn_url%' } diff --git a/migrations/Version20230707115553.php b/migrations/Version20230707115553.php deleted file mode 100644 index 92a7712..0000000 --- a/migrations/Version20230707115553.php +++ /dev/null @@ -1,33 +0,0 @@ -addSql('CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, bus_pro_address_id INT NOT NULL, email VARCHAR(255) NOT NULL, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, role VARCHAR(255) NOT NULL, last_login_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); - $this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP TABLE user'); - $this->addSql('DROP TABLE messenger_messages'); - } -} diff --git a/migrations/Version20230707135208.php b/migrations/Version20230707135208.php deleted file mode 100644 index b02614e..0000000 --- a/migrations/Version20230707135208.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE user ADD bus_pro_person_id INT NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE user DROP bus_pro_person_id'); - } -} diff --git a/migrations/Version20230902154310.php b/migrations/Version20230902154310.php deleted file mode 100644 index 5470e70..0000000 --- a/migrations/Version20230902154310.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE user ADD roles LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', DROP role'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE user ADD role VARCHAR(255) NOT NULL, DROP roles'); - } -} diff --git a/migrations/Version20230902155910.php b/migrations/Version20230902155910.php deleted file mode 100644 index 7a68998..0000000 --- a/migrations/Version20230902155910.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE user ADD address_street VARCHAR(255) DEFAULT NULL, ADD address_post_code VARCHAR(255) DEFAULT NULL, ADD address_city VARCHAR(255) DEFAULT NULL, ADD address_country VARCHAR(255) DEFAULT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE user DROP address_street, DROP address_post_code, DROP address_city, DROP address_country'); - } -} diff --git a/migrations/Version20230902163321.php b/migrations/Version20230902163321.php deleted file mode 100644 index 814218c..0000000 --- a/migrations/Version20230902163321.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE user ADD profile_bus_pro_address_id INT NOT NULL, ADD profile_bus_pro_person_id INT NOT NULL, ADD profile_title VARCHAR(255) NOT NULL, ADD profile_salutation VARCHAR(255) NOT NULL, ADD profile_first_name VARCHAR(255) NOT NULL, ADD profile_last_name VARCHAR(255) NOT NULL, ADD profile_gender VARCHAR(1) DEFAULT NULL, ADD profile_date_of_birth DATE DEFAULT NULL COMMENT \'(DC2Type:date_immutable)\', ADD profile_street VARCHAR(255) DEFAULT NULL, ADD profile_post_code VARCHAR(255) DEFAULT NULL, ADD profile_city VARCHAR(255) DEFAULT NULL, ADD profile_country VARCHAR(255) DEFAULT NULL, ADD profile_phone VARCHAR(255) DEFAULT NULL, ADD profile_mobile VARCHAR(255) DEFAULT NULL, DROP bus_pro_address_id, DROP first_name, DROP last_name, DROP bus_pro_person_id, DROP address_street, DROP address_post_code, DROP address_city, DROP address_country'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE user ADD bus_pro_address_id INT NOT NULL, ADD first_name VARCHAR(255) NOT NULL, ADD last_name VARCHAR(255) NOT NULL, ADD bus_pro_person_id INT NOT NULL, ADD address_street VARCHAR(255) DEFAULT NULL, ADD address_post_code VARCHAR(255) DEFAULT NULL, ADD address_city VARCHAR(255) DEFAULT NULL, ADD address_country VARCHAR(255) DEFAULT NULL, DROP profile_bus_pro_address_id, DROP profile_bus_pro_person_id, DROP profile_title, DROP profile_salutation, DROP profile_first_name, DROP profile_last_name, DROP profile_gender, DROP profile_date_of_birth, DROP profile_street, DROP profile_post_code, DROP profile_city, DROP profile_country, DROP profile_phone, DROP profile_mobile'); - } -} diff --git a/migrations/Version20230902164055.php b/migrations/Version20230902164055.php deleted file mode 100644 index 902d966..0000000 --- a/migrations/Version20230902164055.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE user ADD address_street VARCHAR(255) DEFAULT NULL, ADD address_post_code VARCHAR(255) DEFAULT NULL, ADD address_city VARCHAR(255) DEFAULT NULL, ADD address_country VARCHAR(255) DEFAULT NULL, ADD communication_phone VARCHAR(255) DEFAULT NULL, ADD communication_mobile VARCHAR(255) DEFAULT NULL, ADD communication_email VARCHAR(255) DEFAULT NULL, DROP profile_phone, DROP profile_mobile'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE user ADD profile_phone VARCHAR(255) DEFAULT NULL, ADD profile_mobile VARCHAR(255) DEFAULT NULL, DROP address_street, DROP address_post_code, DROP address_city, DROP address_country, DROP communication_phone, DROP communication_mobile, DROP communication_email'); - } -} diff --git a/migrations/Version20230912134406.php b/migrations/Version20230912134406.php new file mode 100644 index 0000000..0c93459 --- /dev/null +++ b/migrations/Version20230912134406.php @@ -0,0 +1,37 @@ +addSql('CREATE TABLE teamer (id INT AUTO_INCREMENT NOT NULL, first_name VARCHAR(255) NOT NULL, last_name VARCHAR(255) NOT NULL, gender VARCHAR(1) NOT NULL, date_of_birth DATE NOT NULL COMMENT \'(DC2Type:date_immutable)\', academic_title VARCHAR(255) DEFAULT NULL, salutation VARCHAR(255) DEFAULT NULL, nationality VARCHAR(255) DEFAULT NULL, tax_id VARCHAR(255) DEFAULT NULL, health_insurance_company VARCHAR(255) DEFAULT NULL, status VARCHAR(32) NOT NULL, remarks LONGTEXT DEFAULT NULL, address_street VARCHAR(255) DEFAULT NULL, address_post_code VARCHAR(255) DEFAULT NULL, address_city VARCHAR(255) DEFAULT NULL, address_country VARCHAR(255) DEFAULT NULL, communication_phone VARCHAR(255) DEFAULT NULL, communication_mobile VARCHAR(255) DEFAULT NULL, communication_email VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, teamer_id INT DEFAULT NULL, bus_pro_address_id INT NOT NULL, bus_pro_person_id INT NOT NULL, email VARCHAR(255) NOT NULL, roles LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', last_login_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_8D93D6494302FF75 (teamer_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE user ADD CONSTRAINT FK_8D93D6494302FF75 FOREIGN KEY (teamer_id) REFERENCES teamer (id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE user DROP FOREIGN KEY FK_8D93D6494302FF75'); + $this->addSql('DROP TABLE teamer'); + $this->addSql('DROP TABLE user'); + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/migrations/Version20230912134737.php b/migrations/Version20230912134737.php new file mode 100644 index 0000000..62dc629 --- /dev/null +++ b/migrations/Version20230912134737.php @@ -0,0 +1,37 @@ +addSql('ALTER TABLE teamer ADD uuid VARCHAR(36) NOT NULL'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_15B751AED17F50A6 ON teamer (uuid)'); + $this->addSql('ALTER TABLE user ADD uuid VARCHAR(36) NOT NULL'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649D17F50A6 ON user (uuid)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP INDEX UNIQ_8D93D649D17F50A6 ON user'); + $this->addSql('ALTER TABLE user DROP uuid'); + $this->addSql('DROP INDEX UNIQ_15B751AED17F50A6 ON teamer'); + $this->addSql('ALTER TABLE teamer DROP uuid'); + } +} diff --git a/migrations/Version20230902164150.php b/migrations/Version20230912140412.php similarity index 56% rename from migrations/Version20230902164150.php rename to migrations/Version20230912140412.php index 9e86426..79f7bc0 100644 --- a/migrations/Version20230902164150.php +++ b/migrations/Version20230912140412.php @@ -10,7 +10,7 @@ use Doctrine\Migrations\AbstractMigration; /** * Auto-generated Migration: Please modify to your needs! */ -final class Version20230902164150 extends AbstractMigration +final class Version20230912140412 extends AbstractMigration { public function getDescription(): string { @@ -20,12 +20,12 @@ final class Version20230902164150 extends AbstractMigration public function up(Schema $schema): void { // this up() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE user DROP profile_street, DROP profile_post_code, DROP profile_city, DROP profile_country'); + $this->addSql('ALTER TABLE teamer ADD bank_account_iban VARCHAR(255) DEFAULT NULL, ADD bank_account_bic VARCHAR(255) DEFAULT NULL, ADD bank_account_bank VARCHAR(255) DEFAULT NULL, ADD bank_account_holder VARCHAR(255) DEFAULT NULL'); } public function down(Schema $schema): void { // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE user ADD profile_street VARCHAR(255) DEFAULT NULL, ADD profile_post_code VARCHAR(255) DEFAULT NULL, ADD profile_city VARCHAR(255) DEFAULT NULL, ADD profile_country VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE teamer DROP bank_account_iban, DROP bank_account_bic, DROP bank_account_bank, DROP bank_account_holder'); } } diff --git a/migrations/Version20230912142633.php b/migrations/Version20230912142633.php new file mode 100644 index 0000000..fea2ac9 --- /dev/null +++ b/migrations/Version20230912142633.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE teamer ADD created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', ADD updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\''); + $this->addSql('ALTER TABLE user ADD created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', ADD updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE user DROP created_at, DROP updated_at'); + $this->addSql('ALTER TABLE teamer DROP created_at, DROP updated_at'); + } +} diff --git a/src/BusProNet/UserDataHandler.php b/src/BusProNet/UserDataHandler.php new file mode 100644 index 0000000..809900a --- /dev/null +++ b/src/BusProNet/UserDataHandler.php @@ -0,0 +1,99 @@ +isAdmin()) { + $roles[] = 'ROLE_ADMIN'; + } + if ($crmAttributes->isManager()) { + $roles[] = 'ROLE_MANAGER'; + } + if ($crmAttributes->isTeamer()) { + $roles[] = 'ROLE_TEAMER'; + } + + return $roles; + } + + public function findLocalUser(ProfileResponse $profileResponse): ?User + { + // Check if user is already present in local database + $repository = $this->entityManager->getRepository(User::class); + + return $repository->findOneBy([ + 'busProAddressId' => $profileResponse->getAddressId(), + 'busProPersonId' => $profileResponse->getPersonId(), + ]); + } + + public function createLocalUser(ProfileResponse $profileResponse, array $roles, bool $isTeamer = false): User + { + $user = new User(); + $user + ->setEmail($profileResponse->getCommunication()->getEmail()) + ->setBusProPersonId($profileResponse->getPersonId()) + ->setBusProAddressId($profileResponse->getAddressId()) + ->setRoles($roles) + ; + + if (true === $isTeamer) { + $teamer = Teamer::fromApiResponse($profileResponse); + $user->setTeamer($teamer); + $this->logger->info('Create teamer', [ + 'uuid' => $teamer->getUuid(), + ]); + } + + $this->entityManager->persist($user); + $this->entityManager->flush(); + + $this->logger->info('Create user', [ + 'uuid' => $user->getUuid(), + ]); + + return $user; + } + + public function updateLocalUser( + User $user, + ProfileResponse $profileResponse, + array $roles, + bool $isTeamer = false + ): void { + $user->setRoles($roles); + + if (true === $isTeamer) { + $address = Address::fromApiResponse($profileResponse); + $communication = Communication::fromApiResponse($profileResponse); + $user + ->getTeamer() + ->setAddress($address) + ->setCommunication($communication) + ; + } + + $this->entityManager->flush(); + } +} \ No newline at end of file diff --git a/src/Entity/Embeddable/BankAccount.php b/src/Entity/Embeddable/BankAccount.php new file mode 100644 index 0000000..c126e05 --- /dev/null +++ b/src/Entity/Embeddable/BankAccount.php @@ -0,0 +1,77 @@ +iban; + } + + if (null === $iban = $this->getIban()) { + return ''; + } + + return sprintf('%s*****%s', substr($iban, 0, 6), substr($iban, -4)); + } + + public function setIban(?string $iban): static + { + $this->iban = $iban; + + return $this; + } + + public function getBic(): ?string + { + return $this->bic; + } + + public function setBic(?string $bic): static + { + $this->bic = $bic; + + return $this; + } + + public function getBank(): ?string + { + return $this->bank; + } + + public function setBank(?string $bank): static + { + $this->bank = $bank; + + return $this; + } + + public function getHolder(): ?string + { + return $this->holder; + } + + public function setHolder(?string $holder): static + { + $this->holder = $holder; + + return $this; + } +} diff --git a/src/Entity/Embeddable/Profile.php b/src/Entity/Embeddable/Profile.php deleted file mode 100644 index d7c86c6..0000000 --- a/src/Entity/Embeddable/Profile.php +++ /dev/null @@ -1,149 +0,0 @@ -setBusProAddressId($profileResponse->getAddressId()) - ->setBusProPersonId($profileResponse->getPersonId()) - ->setTitle($profileResponse->getTitle()) - ->setSalutation($profileResponse->getSalutation()) - ->setFirstName($profileResponse->getFirstName()) - ->setLastName($profileResponse->getName()) - ->setGender($profileResponse->getGender()) - ->setDateOfBirth($profileResponse->getDateOfBirth()) - ; - - return $instance; - - } - - public function getBusProAddressId(): ?int - { - return $this->busProAddressId; - } - - public function setBusProAddressId(int $busProAddressId): static - { - $this->busProAddressId = $busProAddressId; - - return $this; - } - - public function getBusProPersonId(): ?int - { - return $this->busProPersonId; - } - - public function setBusProPersonId(int $busProPersonId): static - { - $this->busProPersonId = $busProPersonId; - - return $this; - } - - public function getSalutation(): ?string - { - return $this->salutation; - } - - public function setSalutation(?string $salutation): static - { - $this->salutation = $salutation; - - return $this; - } - - public function getTitle(): ?string - { - return $this->title; - } - - public function setTitle(?string $title): static - { - $this->title = $title; - - return $this; - } - - public function getFirstName(): ?string - { - return $this->firstName; - } - - public function setFirstName(string $firstName): static - { - $this->firstName = $firstName; - - return $this; - } - - public function getLastName(): ?string - { - return $this->lastName; - } - - public function setLastName(string $lastName): static - { - $this->lastName = $lastName; - - return $this; - } - - public function getGender(): ?string - { - return $this->gender; - } - - public function setGender(?string $gender): static - { - $this->gender = $gender; - - return $this; - } - - public function getDateOfBirth(): ?\DateTimeImmutable - { - return $this->dateOfBirth; - } - - public function setDateOfBirth(?\DateTimeImmutable $dateOfBirth): static - { - $this->dateOfBirth = $dateOfBirth; - - return $this; - } -} \ No newline at end of file diff --git a/src/Entity/Teamer.php b/src/Entity/Teamer.php new file mode 100644 index 0000000..abfb680 --- /dev/null +++ b/src/Entity/Teamer.php @@ -0,0 +1,284 @@ +uuid = Uuid::v4(); + $this->status = static::STATUS_NEW; + } + + public static function fromApiResponse(ProfileResponse $profileResponse): static + { + $instance = new static(); + + $instance + ->setAcademicTitle($profileResponse->getTitle()) + ->setSalutation($profileResponse->getSalutation()) + ->setFirstName($profileResponse->getFirstName()) + ->setLastName($profileResponse->getName()) + ->setGender($profileResponse->getGender()) + ->setDateOfBirth($profileResponse->getDateOfBirth()) + ; + + $address = Address::fromApiResponse($profileResponse); + $communication = Communication::fromApiResponse($profileResponse); + + $instance + ->setAddress($address) + ->setCommunication($communication) + ; + + return $instance; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getUuid(): string + { + return $this->uuid; + } + + public function getFirstName(): ?string + { + return $this->firstName; + } + + public function setFirstName(string $firstName): static + { + $this->firstName = $firstName; + + return $this; + } + + public function getLastName(): ?string + { + return $this->lastName; + } + + public function setLastName(string $lastName): static + { + $this->lastName = $lastName; + + return $this; + } + + public function getGender(): ?string + { + return $this->gender; + } + + public function setGender(string $gender): static + { + $this->gender = $gender; + + return $this; + } + + public function getDateOfBirth(): ?\DateTimeImmutable + { + return $this->dateOfBirth; + } + + public function setDateOfBirth(\DateTimeImmutable $dateOfBirth): static + { + $this->dateOfBirth = $dateOfBirth; + + return $this; + } + + public function getAcademicTitle(): ?string + { + return $this->academicTitle; + } + + public function setAcademicTitle(?string $academicTitle): static + { + $this->academicTitle = $academicTitle; + + return $this; + } + + public function getSalutation(): ?string + { + return $this->salutation; + } + + public function setSalutation(?string $salutation): static + { + $this->salutation = $salutation; + + return $this; + } + + public function getNationality(): ?string + { + return $this->nationality; + } + + public function setNationality(string $nationality): static + { + $this->nationality = $nationality; + + return $this; + } + + public function getAddress(): ?Address + { + return $this->address; + } + + public function setAddress(?Address $address): static + { + $this->address = $address; + + return $this; + } + + public function getCommunication(): ?Communication + { + return $this->communication; + } + + public function setCommunication(?Communication $communication): static + { + $this->communication = $communication; + + return $this; + } + + public function getBankAccount(): ?BankAccount + { + return $this->bankAccount; + } + + public function setBankAccount(?BankAccount $bankAccount): static + { + $this->bankAccount = $bankAccount; + + return $this; + } + + public function getTaxId(): ?string + { + return $this->taxId; + } + + public function setTaxId(?string $taxId): static + { + $this->taxId = $taxId; + + return $this; + } + + public function getHealthInsuranceCompany(): ?string + { + return $this->healthInsuranceCompany; + } + + public function setHealthInsuranceCompany(?string $healthInsuranceCompany): static + { + $this->healthInsuranceCompany = $healthInsuranceCompany; + + return $this; + } + + public function getStatus(): ?string + { + return $this->status; + } + + public function setStatus(string $status): static + { + $this->status = $status; + + return $this; + } + + public function getRemarks(): ?string + { + return $this->remarks; + } + + public function setRemarks(?string $remarks): static + { + $this->remarks = $remarks; + + return $this; + } +} diff --git a/src/Entity/TimestampableEntityInterface.php b/src/Entity/TimestampableEntityInterface.php new file mode 100644 index 0000000..0a3cb1e --- /dev/null +++ b/src/Entity/TimestampableEntityInterface.php @@ -0,0 +1,14 @@ +createdAt; + } + + public function setCreatedAt(\DateTimeImmutable $createdAt): static + { + $this->createdAt = $createdAt; + + return $this; + } + + public function getUpdatedAt(): ?\DateTimeImmutable + { + return $this->updatedAt; + } + + public function setUpdatedAt(\DateTimeImmutable $updatedAt): static + { + $this->updatedAt = $updatedAt; + + return $this; + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php index 5afaed7..1ac1b4a 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -2,48 +2,82 @@ namespace App\Entity; -use App\Entity\Embeddable\Address; -use App\Entity\Embeddable\Communication; -use App\Entity\Embeddable\Profile; +use App\Entity\Traits\TimestampableEntity; use App\Repository\UserRepository; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Security\Core\User\UserInterface; -use Symfony\Component\Validator\Constraints as Assert; +use Symfony\Component\Uid\Uuid; #[ORM\Entity(repositoryClass: UserRepository::class)] -class User implements UserInterface +class User implements UserInterface, TimestampableEntityInterface { + use TimestampableEntity; + #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] private ?int $id = null; + #[ORM\Column(type: 'string', length: 36, unique: true)] + private string $uuid; + + #[ORM\Column] + private ?int $busProAddressId = null; + + #[ORM\Column] + private ?int $busProPersonId = null; + #[ORM\Column(length: 255)] private ?string $email = null; - #[ORM\Embedded(class: Profile::class)] - #[Assert\Valid()] - private ?Profile $profile = null; - - #[ORM\Embedded(class: Address::class)] - #[Assert\Valid()] - private ?Address $address = null; - - #[ORM\Embedded(class: Communication::class)] - #[Assert\Valid()] - private ?Communication $communication = null; - #[ORM\Column(type: 'json')] private array $roles = []; #[ORM\Column(nullable: true)] private ?\DateTimeImmutable $lastLoginAt = null; + #[ORM\OneToOne(cascade: ['persist', 'remove'])] + private ?Teamer $teamer = null; + + public function __construct() + { + $this->uuid = Uuid::v4(); + } + public function getId(): ?int { return $this->id; } + public function getUuid(): string + { + return $this->uuid; + } + + public function getBusProAddressId(): ?int + { + return $this->busProAddressId; + } + + public function setBusProAddressId(int $busProAddressId): static + { + $this->busProAddressId = $busProAddressId; + + return $this; + } + + public function getBusProPersonId(): ?int + { + return $this->busProPersonId; + } + + public function setBusProPersonId(int $busProPersonId): static + { + $this->busProPersonId = $busProPersonId; + + return $this; + } + public function getEmail(): ?string { return $this->email; @@ -56,42 +90,6 @@ class User implements UserInterface return $this; } - public function getProfile(): ?Profile - { - return $this->profile; - } - - public function setProfile(?Profile $profile): static - { - $this->profile = $profile; - - return $this; - } - - public function getAddress(): ?Address - { - return $this->address; - } - - public function setAddress(?Address $address): static - { - $this->address = $address; - - return $this; - } - - public function getCommunication(): ?Communication - { - return $this->communication; - } - - public function setCommunication(?Communication $communication): static - { - $this->communication = $communication; - - return $this; - } - public function getRoles(): array { $roles = ['ROLE_USER', ...$this->roles]; @@ -142,4 +140,16 @@ class User implements UserInterface { return $this->email; } + + public function getTeamer(): ?Teamer + { + return $this->teamer; + } + + public function setTeamer(?Teamer $teamer): static + { + $this->teamer = $teamer; + + return $this; + } } diff --git a/src/EventListener/TimestampableEntitySubscriber.php b/src/EventListener/TimestampableEntitySubscriber.php new file mode 100644 index 0000000..08b14f8 --- /dev/null +++ b/src/EventListener/TimestampableEntitySubscriber.php @@ -0,0 +1,41 @@ +getObject(); + + if ($entity instanceof TimestampableEntityInterface) { + $now = new \DateTimeImmutable('now'); + $entity->setCreatedAt($now); + $entity->setUpdatedAt($now); + } + } + + public function preUpdate(PreUpdateEventArgs $args): void + { + $entity = $args->getObject(); + + if ($entity instanceof TimestampableEntityInterface) { + $now = new \DateTimeImmutable('now'); + $entity->setUpdatedAt($now); + } + } +} diff --git a/src/Repository/TeamerRepository.php b/src/Repository/TeamerRepository.php new file mode 100644 index 0000000..23338e6 --- /dev/null +++ b/src/Repository/TeamerRepository.php @@ -0,0 +1,66 @@ + + * + * @method Teamer|null find($id, $lockMode = null, $lockVersion = null) + * @method Teamer|null findOneBy(array $criteria, array $orderBy = null) + * @method Teamer[] findAll() + * @method Teamer[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) + */ +class TeamerRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Teamer::class); + } + + public function save(Teamer $entity, bool $flush = false): void + { + $this->getEntityManager()->persist($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function remove(Teamer $entity, bool $flush = false): void + { + $this->getEntityManager()->remove($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + +// /** +// * @return Teamer[] Returns an array of Teamer objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('t') +// ->andWhere('t.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('t.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?Teamer +// { +// return $this->createQueryBuilder('t') +// ->andWhere('t.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Security/BpnAuthenticator.php b/src/Security/BpnAuthenticator.php index 9db48ee..e49cc84 100644 --- a/src/Security/BpnAuthenticator.php +++ b/src/Security/BpnAuthenticator.php @@ -6,9 +6,7 @@ use App\BusProNet\ApiClient; use App\BusProNet\ApiClientException; use App\BusProNet\Model\CrmAttributesResponse; use App\BusProNet\Model\ProfileResponse; -use App\Entity\Embeddable\Address; -use App\Entity\Embeddable\Communication; -use App\Entity\Embeddable\Profile; +use App\BusProNet\UserDataHandler; use App\Entity\User; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; @@ -33,7 +31,8 @@ class BpnAuthenticator extends AbstractLoginFormAuthenticator implements Authent private readonly UrlGeneratorInterface $urlGenerator, private readonly EntityManagerInterface $entityManager, private readonly ApiClient $apiClient, - private readonly LoggerInterface $logger + private readonly LoggerInterface $logger, + private readonly UserDataHandler $userDataHandler ) { } @@ -72,6 +71,9 @@ class BpnAuthenticator extends AbstractLoginFormAuthenticator implements Authent $user = $token->getUser(); $user->setLastLoginAt(new \DateTimeImmutable()); + $this->logger->info('Login', [ + 'user' => $user->getUserIdentifier(), + ]); $this->entityManager->flush(); @@ -95,56 +97,33 @@ class BpnAuthenticator extends AbstractLoginFormAuthenticator implements Authent } // Collect user's roles from CRM attributes - $roles = []; + $roles = $this + ->userDataHandler + ->collectRoles($crmAttributes) + ; - if ($crmAttributes->isAdmin()) { - $roles[] = 'ROLE_ADMIN'; - } - if ($crmAttributes->isManager()) { - $roles[] = 'ROLE_MANAGER'; - } - if ($crmAttributes->isTeamer()) { - $roles[] = 'ROLE_TEAMER'; - } - - // Get profile data from API response - $profile = Profile::fromApiResponse($profileResponse); - $address = Address::fromApiResponse($profileResponse); - $communication = Communication::fromApiResponse($profileResponse); + // Determine teamer status from CRM attributes + $isTeamer = $crmAttributes->isTeamer(); // Check if user is already present in local database - $repository = $this->entityManager->getRepository(User::class); + $user = $this + ->userDataHandler + ->findLocalUser($profileResponse) + ; - $user = $repository->findOneBy([ - 'profile.busProAddressId' => $profileResponse->getAddressId(), - 'profile.busProPersonId' => $profileResponse->getPersonId(), - ]); - - // Update existing user's roles and address and return it + // Update existing user's roles and teamer data and return it if (null !== $user) { - $user - ->setProfile($profile) - ->setAddress($address) - ->setCommunication($communication) - ->setRoles($roles) + $this + ->userDataHandler + ->updateLocalUser($user, $profileResponse, $roles, $isTeamer) ; return $user; } - // Create new user entity to persist locally otherwise - $user = new User(); - $user - ->setEmail($profileResponse->getCommunication()->getEmail()) - ->setProfile($profile) - ->setAddress($address) - ->setCommunication($communication) - ->setRoles($roles) + return $this + ->userDataHandler + ->createLocalUser($profileResponse, $roles, $isTeamer) ; - - $this->entityManager->persist($user); - $this->entityManager->flush(); - - return $user; } } diff --git a/symfony.lock b/symfony.lock index d020c1b..bc30400 100644 --- a/symfony.lock +++ b/symfony.lock @@ -230,6 +230,18 @@ "templates/base.html.twig" ] }, + "symfony/uid": { + "version": "6.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.2", + "ref": "d294ad4add3e15d7eb1bae0221588ca89b38e558" + }, + "files": [ + "config/packages/uid.yaml" + ] + }, "symfony/validator": { "version": "6.3", "recipe": { diff --git a/tests/BusProNet/ResponseParserTest.php b/tests/BusProNet/ResponseParserTest.php index 08166a0..aecab1f 100644 --- a/tests/BusProNet/ResponseParserTest.php +++ b/tests/BusProNet/ResponseParserTest.php @@ -13,7 +13,7 @@ class ResponseParserTest extends TestCase { $content = '853ID, EMail oder Passwort falsch'; - $parser = new ResponseParser(); + $parser = $this->getParserInstance(); $response = $parser->parseXmlString($content); $this->assertEquals(853, $response->getCode()); @@ -25,7 +25,7 @@ class ResponseParserTest extends TestCase { $content = 'Adressdaten141747224526FrommeBjörnHerrM16.04.1972D119447Emilienstraße 5742853RemscheidDmail@fromme.orgFalse02191-4615837'; - $parser = new ResponseParser(); + $parser = $this->getParserInstance(); $response = $parser->parseXmlString($content); $this->assertInstanceOf(ProfileResponse::class, $response); @@ -38,7 +38,7 @@ class ResponseParserTest extends TestCase { $content = 'SelektionCRM141747224526'; - $parser = new ResponseParser(); + $parser = $this->getParserInstance(); $response = $parser->parseXmlString($content); $this->assertInstanceOf(CrmAttributesResponse::class, $response); @@ -48,4 +48,13 @@ class ResponseParserTest extends TestCase $this->assertCount(3, $response->getAttributeGroups()); $this->assertTrue($response->isTeamer()); } + + private function getParserInstance(): ResponseParser + { + return new ResponseParser([ + 'bpn_crm_id_admin' => 1292, + 'bpn_crm_id_manager' => 1293, + 'bpn_crm_id_teamer' => 1070, + ]); + } } \ No newline at end of file