From 08f7836d4167cebacdd0f9ba30938ff9d8d81d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Fromme?= Date: Sat, 2 Sep 2023 18:47:42 +0200 Subject: [PATCH] WIP --- config/packages/security.yaml | 3 + migrations/Version20230902154310.php | 31 ++++ migrations/Version20230902155910.php | 31 ++++ migrations/Version20230902163321.php | 31 ++++ migrations/Version20230902164055.php | 31 ++++ migrations/Version20230902164150.php | 31 ++++ src/BusProNet/Model/CrmAttribute.php | 4 + src/BusProNet/Model/CrmAttributesResponse.php | 26 +++ src/BusProNet/ResponseParser.php | 18 ++- src/Controller/Admin/IndexController.php | 18 +++ src/Controller/Manager/IndexController.php | 18 +++ src/Controller/Security/LoginController.php | 4 + src/Controller/Teamer/IndexController.php | 18 +++ src/Entity/Embeddable/Address.php | 100 ++++++++++++ src/Entity/Embeddable/Communication.php | 71 +++++++++ src/Entity/Embeddable/Profile.php | 149 ++++++++++++++++++ src/Entity/User.php | 109 +++++++------ src/Security/BpnAuthenticator.php | 70 +++++--- templates/admin/index.html.twig | 4 + templates/manager/index.html.twig | 4 + templates/teamer/index.html.twig | 4 + 21 files changed, 693 insertions(+), 82 deletions(-) create mode 100644 migrations/Version20230902154310.php create mode 100644 migrations/Version20230902155910.php create mode 100644 migrations/Version20230902163321.php create mode 100644 migrations/Version20230902164055.php create mode 100644 migrations/Version20230902164150.php create mode 100644 src/Controller/Admin/IndexController.php create mode 100644 src/Controller/Manager/IndexController.php create mode 100644 src/Controller/Teamer/IndexController.php create mode 100644 src/Entity/Embeddable/Address.php create mode 100644 src/Entity/Embeddable/Communication.php create mode 100644 src/Entity/Embeddable/Profile.php create mode 100644 templates/admin/index.html.twig create mode 100644 templates/manager/index.html.twig create mode 100644 templates/teamer/index.html.twig diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 54747e3..bbacc0e 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -17,6 +17,9 @@ security: provider: bpn_user_provider custom_authenticators: - App\Security\BpnAuthenticator + logout: + path: app_security_logout + target: app_security_login access_control: - { path: ^/profile, roles: ROLE_USER } diff --git a/migrations/Version20230902154310.php b/migrations/Version20230902154310.php new file mode 100644 index 0000000..5470e70 --- /dev/null +++ b/migrations/Version20230902154310.php @@ -0,0 +1,31 @@ +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 new file mode 100644 index 0000000..7a68998 --- /dev/null +++ b/migrations/Version20230902155910.php @@ -0,0 +1,31 @@ +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 new file mode 100644 index 0000000..814218c --- /dev/null +++ b/migrations/Version20230902163321.php @@ -0,0 +1,31 @@ +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 new file mode 100644 index 0000000..902d966 --- /dev/null +++ b/migrations/Version20230902164055.php @@ -0,0 +1,31 @@ +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/Version20230902164150.php b/migrations/Version20230902164150.php new file mode 100644 index 0000000..9e86426 --- /dev/null +++ b/migrations/Version20230902164150.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE user DROP profile_street, DROP profile_post_code, DROP profile_city, DROP profile_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 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'); + } +} diff --git a/src/BusProNet/Model/CrmAttribute.php b/src/BusProNet/Model/CrmAttribute.php index 22b6900..7f05e7f 100644 --- a/src/BusProNet/Model/CrmAttribute.php +++ b/src/BusProNet/Model/CrmAttribute.php @@ -4,6 +4,10 @@ namespace App\BusProNet\Model; class CrmAttribute { + public const ATTR_ID_ADMIN = 1071; + public const ATTR_ID_MANAGER = 1072; + public const ATTR_ID_TEAMER = 1070; + private ?int $id = null; private ?string $label = null; private bool $selected = false; diff --git a/src/BusProNet/Model/CrmAttributesResponse.php b/src/BusProNet/Model/CrmAttributesResponse.php index 73a7809..bec74c1 100644 --- a/src/BusProNet/Model/CrmAttributesResponse.php +++ b/src/BusProNet/Model/CrmAttributesResponse.php @@ -5,6 +5,8 @@ namespace App\BusProNet\Model; class CrmAttributesResponse extends BaseResponse { private ?array $attributeGroups = null; + private bool $admin = false; + private bool $manager = false; private bool $teamer = false; public function getAttributeGroups(): ?array @@ -29,4 +31,28 @@ class CrmAttributesResponse extends BaseResponse return $this; } + + public function isAdmin(): bool + { + return $this->admin; + } + + public function setAdmin(bool $admin): static + { + $this->admin = $admin; + + return $this; + } + + public function isManager(): bool + { + return $this->manager; + } + + public function setManager(bool $manager): static + { + $this->manager = $manager; + + return $this; + } } \ No newline at end of file diff --git a/src/BusProNet/ResponseParser.php b/src/BusProNet/ResponseParser.php index 304a1f8..8ac0fc1 100644 --- a/src/BusProNet/ResponseParser.php +++ b/src/BusProNet/ResponseParser.php @@ -12,8 +12,6 @@ use App\BusProNet\Model\BaseResponse; class ResponseParser { - public const ATTR_ID_TEAMER = 1070; - /** * @throws ResponseParserException */ @@ -108,7 +106,7 @@ class ResponseParser public function createCrmAttributesResponse(\SimpleXMLElement $xml): CrmAttributesResponse { $groups = []; - $isTeam = false; + $isAdmin = $isManager = $isTeamer = false; foreach ($xml->xpath('selektionsmerkmale/selektionsgruppe') as $item) { $group = new CrmAttributeGroup(); @@ -124,8 +122,14 @@ class ResponseParser ; $attributes[] = $attribute; - if (static::ATTR_ID_TEAMER === $attribute->getId() && true === $attribute->isSelected()) { - $isTeam = true; + if (CrmAttribute::ATTR_ID_ADMIN === $attribute->getId() && true === $attribute->isSelected()) { + $isAdmin = true; + } + if (CrmAttribute::ATTR_ID_MANAGER === $attribute->getId() && true === $attribute->isSelected()) { + $isManager = true; + } + if (CrmAttribute::ATTR_ID_TEAMER === $attribute->getId() && true === $attribute->isSelected()) { + $isTeamer = true; } } $group->setAttributes($attributes); @@ -135,7 +139,9 @@ class ResponseParser $response = new CrmAttributesResponse(); $response ->setAttributeGroups($groups) - ->setTeamer($isTeam) + ->setAdmin($isAdmin) + ->setManager($isManager) + ->setTeamer($isTeamer) ; return $response; diff --git a/src/Controller/Admin/IndexController.php b/src/Controller/Admin/IndexController.php new file mode 100644 index 0000000..b976336 --- /dev/null +++ b/src/Controller/Admin/IndexController.php @@ -0,0 +1,18 @@ +render('admin/index.html.twig'); + } +} \ No newline at end of file diff --git a/src/Controller/Manager/IndexController.php b/src/Controller/Manager/IndexController.php new file mode 100644 index 0000000..5d94057 --- /dev/null +++ b/src/Controller/Manager/IndexController.php @@ -0,0 +1,18 @@ +render('manager/index.html.twig'); + } +} \ No newline at end of file diff --git a/src/Controller/Security/LoginController.php b/src/Controller/Security/LoginController.php index a0cf172..365ecc6 100644 --- a/src/Controller/Security/LoginController.php +++ b/src/Controller/Security/LoginController.php @@ -23,4 +23,8 @@ class LoginController extends AbstractController 'error' => $error, ]); } + + #[Route('/logout', name: 'app_security_logout')] + public function logout(): void + {} } \ No newline at end of file diff --git a/src/Controller/Teamer/IndexController.php b/src/Controller/Teamer/IndexController.php new file mode 100644 index 0000000..9956818 --- /dev/null +++ b/src/Controller/Teamer/IndexController.php @@ -0,0 +1,18 @@ +render('teamer/index.html.twig'); + } +} \ No newline at end of file diff --git a/src/Entity/Embeddable/Address.php b/src/Entity/Embeddable/Address.php new file mode 100644 index 0000000..233ccd0 --- /dev/null +++ b/src/Entity/Embeddable/Address.php @@ -0,0 +1,100 @@ + $this->getStreet(), + 'post_code' => $this->getPostCode(), + 'city' => $this->getCity(), + 'country' => $this->getCountry(), + ]; + } + + public static function fromApiResponse(ProfileResponse $profileResponse): static + { + $address = $profileResponse->getAddress(); + + $instance = new static(); + $instance + ->setStreet($address->getStreet()) + ->setPostCode($address->getPostCode()) + ->setCity($address->getCity()) + ->setCountry($address->getCountry() ?? 'DE') + ; + + return $instance; + } + + public function getStreet(): ?string + { + return $this->street; + } + + public function setStreet(?string $street): static + { + $this->street = $street; + + return $this; + } + + public function getPostCode(): ?string + { + return $this->postCode; + } + + public function setPostCode(?string $postCode): static + { + $this->postCode = $postCode; + + return $this; + } + + public function getCity(): ?string + { + return $this->city; + } + + public function setCity(?string $city): static + { + $this->city = $city; + + return $this; + } + + public function getCountry(): ?string + { + return $this->country; + } + + public function setCountry(?string $country): static + { + $this->country = $country; + + return $this; + } +} diff --git a/src/Entity/Embeddable/Communication.php b/src/Entity/Embeddable/Communication.php new file mode 100644 index 0000000..c925757 --- /dev/null +++ b/src/Entity/Embeddable/Communication.php @@ -0,0 +1,71 @@ +getCommunication(); + + $instance = new static(); + $instance + ->setPhone($communication->getPhone()) + ->setMobile($communication->getMobile()) + ->setEmail($communication->getEmail()) + ; + + return $instance; + } + + public function getPhone(): ?string + { + return $this->phone; + } + + public function setPhone(?string $phone): static + { + $this->phone = $phone; + + return $this; + } + + public function getMobile(): ?string + { + return $this->mobile; + } + + public function setMobile(?string $mobile): static + { + $this->mobile = $mobile; + + return $this; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function setEmail(?string $email): static + { + $this->email = $email; + + return $this; + } +} diff --git a/src/Entity/Embeddable/Profile.php b/src/Entity/Embeddable/Profile.php new file mode 100644 index 0000000..d7c86c6 --- /dev/null +++ b/src/Entity/Embeddable/Profile.php @@ -0,0 +1,149 @@ +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/User.php b/src/Entity/User.php index 9d2efcd..5afaed7 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -2,10 +2,13 @@ namespace App\Entity; +use App\Entity\Embeddable\Address; +use App\Entity\Embeddable\Communication; +use App\Entity\Embeddable\Profile; use App\Repository\UserRepository; use Doctrine\ORM\Mapping as ORM; -use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Validator\Constraints as Assert; #[ORM\Entity(repositoryClass: UserRepository::class)] class User implements UserInterface @@ -15,23 +18,23 @@ class User implements UserInterface #[ORM\Column] private ?int $id = null; - #[ORM\Column] - private ?int $busProAddressId = null; - - #[ORM\Column] - private ?int $busProPersonId = null; - #[ORM\Column(length: 255)] private ?string $email = null; - #[ORM\Column(length: 255)] - private ?string $firstName = null; + #[ORM\Embedded(class: Profile::class)] + #[Assert\Valid()] + private ?Profile $profile = null; - #[ORM\Column(length: 255)] - private ?string $lastName = null; + #[ORM\Embedded(class: Address::class)] + #[Assert\Valid()] + private ?Address $address = null; - #[ORM\Column(length: 255)] - private ?string $role = 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; @@ -41,30 +44,6 @@ class User implements UserInterface return $this->id; } - 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; @@ -77,42 +56,61 @@ class User implements UserInterface return $this; } - public function getFirstName(): ?string + public function getProfile(): ?Profile { - return $this->firstName; + return $this->profile; } - public function setFirstName(string $firstName): static + public function setProfile(?Profile $profile): static { - $this->firstName = $firstName; + $this->profile = $profile; return $this; } - public function getLastName(): ?string + public function getAddress(): ?Address { - return $this->lastName; + return $this->address; } - public function setLastName(string $lastName): static + public function setAddress(?Address $address): static { - $this->lastName = $lastName; + $this->address = $address; return $this; } - public function getRole(): ?string + public function getCommunication(): ?Communication { - return $this->role; + return $this->communication; } - public function setRole(string $role): static + public function setCommunication(?Communication $communication): static { - $this->role = $role; + $this->communication = $communication; return $this; } + public function getRoles(): array + { + $roles = ['ROLE_USER', ...$this->roles]; + + return array_unique($roles); + } + + public function setRoles(array $roles): static + { + $this->roles = $roles; + + return $this; + } + + public function hasRole(string $role): bool + { + return in_array($role, $this->getRoles()); + } + public function getLastLoginAt(): ?\DateTimeImmutable { return $this->lastLoginAt; @@ -127,12 +125,13 @@ class User implements UserInterface public function getDefaultRoute(): string { - return 'app_index'; - } - - public function getRoles(): array - { - return ['ROLE_USER', $this->getRole()]; + if ($this->hasRole('ROLE_ADMIN')) { + return 'app_admin_index'; + } elseif ($this->hasRole('ROLE_MANAGER')) { + return 'app_manager_index'; + } else { + return 'app_teamer_index'; + } } public function eraseCredentials(): void diff --git a/src/Security/BpnAuthenticator.php b/src/Security/BpnAuthenticator.php index fab56da..9db48ee 100644 --- a/src/Security/BpnAuthenticator.php +++ b/src/Security/BpnAuthenticator.php @@ -6,6 +6,9 @@ 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\Entity\User; use Doctrine\ORM\EntityManagerInterface; use Psr\Log\LoggerInterface; @@ -81,19 +84,9 @@ class BpnAuthenticator extends AbstractLoginFormAuthenticator implements Authent return new RedirectResponse($url); } - private function getOrCreateLocalUser(ProfileResponse $profile, string $email, string $password): ?User + private function getOrCreateLocalUser(ProfileResponse $profileResponse, string $email, string $password): ?User { - $repository = $this->entityManager->getRepository(User::class); - - $user = $repository->findOneBy([ - 'busProAddressId' => $profile->getAddressId(), - 'busProPersonId' => $profile->getPersonId(), - ]); - - if (null !== $user) { - return $user; - } - + // Fetch CRM attributes, early return in case of an API arror try { /** @var CrmAttributesResponse $crmAttributes */ $crmAttributes = $this->apiClient->getCrmAttributes($email, $password); @@ -101,19 +94,54 @@ class BpnAuthenticator extends AbstractLoginFormAuthenticator implements Authent return null; } + // Collect user's roles from CRM attributes + $roles = []; + + 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); + + // Check if user is already present in local database + $repository = $this->entityManager->getRepository(User::class); + + $user = $repository->findOneBy([ + 'profile.busProAddressId' => $profileResponse->getAddressId(), + 'profile.busProPersonId' => $profileResponse->getPersonId(), + ]); + + // Update existing user's roles and address and return it + if (null !== $user) { + $user + ->setProfile($profile) + ->setAddress($address) + ->setCommunication($communication) + ->setRoles($roles) + ; + + return $user; + } + + // Create new user entity to persist locally otherwise $user = new User(); $user - ->setBusProAddressId($profile->getAddressId()) - ->setBusProPersonId($profile->getPersonId()) - ->setEmail($profile->getCommunication()->getEmail()) - ->setFirstName($profile->getFirstName()) - ->setLastName($profile->getName()) + ->setEmail($profileResponse->getCommunication()->getEmail()) + ->setProfile($profile) + ->setAddress($address) + ->setCommunication($communication) + ->setRoles($roles) ; - if ($crmAttributes->isTeamer()) { - $user->setRole('ROLE_TEAMER'); - } - $this->entityManager->persist($user); $this->entityManager->flush(); diff --git a/templates/admin/index.html.twig b/templates/admin/index.html.twig new file mode 100644 index 0000000..bd6c454 --- /dev/null +++ b/templates/admin/index.html.twig @@ -0,0 +1,4 @@ +{% extends 'base.html.twig' %} + +{% block body %} +{% endblock %} \ No newline at end of file diff --git a/templates/manager/index.html.twig b/templates/manager/index.html.twig new file mode 100644 index 0000000..bd6c454 --- /dev/null +++ b/templates/manager/index.html.twig @@ -0,0 +1,4 @@ +{% extends 'base.html.twig' %} + +{% block body %} +{% endblock %} \ No newline at end of file diff --git a/templates/teamer/index.html.twig b/templates/teamer/index.html.twig new file mode 100644 index 0000000..bd6c454 --- /dev/null +++ b/templates/teamer/index.html.twig @@ -0,0 +1,4 @@ +{% extends 'base.html.twig' %} + +{% block body %} +{% endblock %} \ No newline at end of file