diff --git a/composer.json b/composer.json index 64fc9759..0e04d8d5 100644 --- a/composer.json +++ b/composer.json @@ -97,6 +97,7 @@ "Api\\User\\": "src/User/src/", "Core\\Admin\\": "src/Core/src/Admin/src/", "Core\\App\\": "src/Core/src/App/src/", + "Core\\Security\\": "src/Core/src/Security/src/", "Core\\User\\": "src/Core/src/User/src/" } }, diff --git a/config/autoload/cors.local.php.dist b/config/autoload/cors.local.php.dist index e7891518..782b4ccf 100644 --- a/config/autoload/cors.local.php.dist +++ b/config/autoload/cors.local.php.dist @@ -11,7 +11,7 @@ return [ * Leaving this line here makes your application accessible by any origin. * * To restrict, replace this line with a list of origins that should have access to your application. - * Example: "domain1.com", "domain2.com" + * Example: 'domain1.com', 'domain2.com' */ ConfigurationInterface::ANY_ORIGIN, ], diff --git a/config/autoload/dependencies.global.php b/config/autoload/dependencies.global.php index 1a4d71d2..55c3e89d 100644 --- a/config/autoload/dependencies.global.php +++ b/config/autoload/dependencies.global.php @@ -3,66 +3,53 @@ declare(strict_types=1); use Api\App\Factory\ErrorResponseGeneratorFactory; -use Api\App\Factory\OAuthAccessTokenRepositoryFactory; -use Api\App\Factory\OAuthAuthCodeRepositoryFactory; -use Api\App\Factory\OAuthClientRepositoryFactory; -use Api\App\Factory\OAuthRefreshTokenRepositoryFactory; -use Api\App\Factory\OAuthScopeRepositoryFactory; use Api\App\Factory\UserIdentityFactory; -use Api\App\Factory\UserRepositoryFactory; -use Core\App\Repository\OAuthAccessTokenRepository; -use Core\App\Repository\OAuthAuthCodeRepository; -use Core\App\Repository\OAuthClientRepository; -use Core\App\Repository\OAuthRefreshTokenRepository; -use Core\App\Repository\OAuthScopeRepository; +use Core\Security\Repository\OAuthAccessTokenRepository; +use Core\Security\Repository\OAuthAuthCodeRepository; +use Core\Security\Repository\OAuthClientRepository; +use Core\Security\Repository\OAuthRefreshTokenRepository; +use Core\Security\Repository\OAuthScopeRepository; use Core\User\Repository\UserRepository; use Core\User\UserIdentity; use Doctrine\Migrations\Tools\Console\Command\ExecuteCommand; -use Dot\ErrorHandler\ErrorHandlerInterface; -use Dot\ErrorHandler\LogErrorHandler; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; use League\OAuth2\Server\Repositories\UserRepositoryInterface; +use Mezzio\Authentication\UserInterface; +use Mezzio\Authorization\AuthorizationInterface; +use Mezzio\Authorization\Rbac\LaminasRbac; +use Mezzio\Middleware\ErrorResponseGenerator; use Roave\PsrContainerDoctrine\Migrations\CommandFactory; return [ // Provides application-wide services. - // We recommend using fully-qualified class names whenever possible as - // service names. + // We recommend using fully-qualified class names whenever possible as service names. 'dependencies' => [ - // Use 'aliases' to alias a service name to another service. The - // key is the alias name, the value is the service to which it points. + // Use 'aliases' to alias a service name to another service. + // The key is the alias name, the value is the service to which it points. 'aliases' => [ - AccessTokenRepositoryInterface::class => OAuthAccessTokenRepository::class, - AuthCodeRepositoryInterface::class => OAuthAuthCodeRepository::class, - ClientRepositoryInterface::class => OAuthClientRepository::class, - RefreshTokenRepositoryInterface::class => OAuthRefreshTokenRepository::class, - ScopeRepositoryInterface::class => OAuthScopeRepository::class, - Mezzio\Authentication\UserInterface::class => UserIdentity::class, - ErrorHandlerInterface::class => LogErrorHandler::class, - Mezzio\Authorization\AuthorizationInterface::class => Mezzio\Authorization\Rbac\LaminasRbac::class, - UserRepositoryInterface::class => UserRepository::class, + AccessTokenRepositoryInterface::class => OAuthAccessTokenRepository::class, + AuthCodeRepositoryInterface::class => OAuthAuthCodeRepository::class, + ClientRepositoryInterface::class => OAuthClientRepository::class, + UserInterface::class => UserIdentity::class, + AuthorizationInterface::class => LaminasRbac::class, + RefreshTokenRepositoryInterface::class => OAuthRefreshTokenRepository::class, + ScopeRepositoryInterface::class => OAuthScopeRepository::class, + UserRepositoryInterface::class => UserRepository::class, ], - // Use 'invokables' for constructor-less services, or services that do - // not require arguments to the constructor. Map a service name to the - // class name. + // Use 'invokables' for constructor-less services, or services that do not require arguments to the constructor. + // Map a service name to the class name. 'invokables' => [ // Fully\Qualified\InterfaceName::class => Fully\Qualified\ClassName::class, ], // Use 'factories' for services provided by callbacks/factory classes. 'factories' => [ - ExecuteCommand::class => CommandFactory::class, - Mezzio\Middleware\ErrorResponseGenerator::class => ErrorResponseGeneratorFactory::class, - OAuthAccessTokenRepository::class => OAuthAccessTokenRepositoryFactory::class, - OAuthAuthCodeRepository::class => OAuthAuthCodeRepositoryFactory::class, - OAuthClientRepository::class => OAuthClientRepositoryFactory::class, - OAuthRefreshTokenRepository::class => OAuthRefreshTokenRepositoryFactory::class, - OAuthScopeRepository::class => OAuthScopeRepositoryFactory::class, - UserRepository::class => UserRepositoryFactory::class, - UserIdentity::class => UserIdentityFactory::class, + ExecuteCommand::class => CommandFactory::class, + ErrorResponseGenerator::class => ErrorResponseGeneratorFactory::class, + UserIdentity::class => UserIdentityFactory::class, ], ], ]; diff --git a/config/autoload/mezzio-tooling-factories.global.php b/config/autoload/mezzio-tooling-factories.global.php deleted file mode 100644 index a81187f4..00000000 --- a/config/autoload/mezzio-tooling-factories.global.php +++ /dev/null @@ -1,17 +0,0 @@ - [ - 'factories' => [], - ], -]; diff --git a/config/autoload/templates.global.php b/config/autoload/templates.global.php index 411d20f6..91617926 100644 --- a/config/autoload/templates.global.php +++ b/config/autoload/templates.global.php @@ -17,6 +17,6 @@ 'globals' => [], 'optimizations' => -1, 'runtime_loaders' => [], -// 'timezone' => '', + 'timezone' => 'UTC', ], ]; diff --git a/config/config.php b/config/config.php index b0d7731a..ebd973a7 100644 --- a/config/config.php +++ b/config/config.php @@ -51,6 +51,7 @@ class_exists(Mezzio\Tooling\ConfigProvider::class) // Default App module config Core\Admin\ConfigProvider::class, Core\App\ConfigProvider::class, + Core\Security\ConfigProvider::class, Core\User\ConfigProvider::class, Api\Admin\ConfigProvider::class, Api\App\ConfigProvider::class, diff --git a/documentation/command/admin-create.md b/documentation/command/admin-create.md index 4a837434..02c1cdaf 100644 --- a/documentation/command/admin-create.md +++ b/documentation/command/admin-create.md @@ -23,9 +23,9 @@ after replacing: If the submitted data is valid, the outputted response is: -```text -Admin account has been created. -``` +> +> [INFO] Admin account has been created. +> The new admin account is ready to use. diff --git a/documentation/command/token-generate.md b/documentation/command/token-generate.md index 7f3322a6..0182c5dd 100644 --- a/documentation/command/token-generate.md +++ b/documentation/command/token-generate.md @@ -36,6 +36,10 @@ The output should look similar to this: Error reporting token: 0123456789abcdef0123456789abcdef01234567 + +* copy the generated token +* open config/autoload/error-handling.global.php +* paste the copied string inside the tokens array found under the ErrorReportServiceInterface::class key. ``` Copy the generated token. diff --git a/phpstan.neon b/phpstan.neon index 3503bcd5..653f71ea 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -4,6 +4,9 @@ includes: parameters: level: 5 paths: + - bin + - config + - public - src - test treatPhpDocTypesAsCertain: false diff --git a/src/Admin/src/Command/AdminCreateCommand.php b/src/Admin/src/Command/AdminCreateCommand.php index efcdac1e..0078c127 100644 --- a/src/Admin/src/Command/AdminCreateCommand.php +++ b/src/Admin/src/Command/AdminCreateCommand.php @@ -5,18 +5,20 @@ namespace Api\Admin\Command; use Api\Admin\InputFilter\CreateAdminInputFilter; -use Api\Admin\Service\AdminRoleService; -use Api\Admin\Service\AdminService; -use Api\App\Exception\BadRequestException; -use Api\App\Exception\ConflictException; -use Api\App\Exception\NotFoundException; use Core\Admin\Entity\AdminRole; +use Core\Admin\Service\AdminRoleServiceInterface; +use Core\Admin\Service\AdminServiceInterface; +use Core\App\Exception\BadRequestException; +use Core\App\Exception\ConflictException; +use Core\App\Exception\NotFoundException; use Core\App\Message; +use Dot\DependencyInjection\Attribute\Inject; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use function implode; use function sprintf; @@ -32,9 +34,13 @@ class AdminCreateCommand extends Command /** @var string $defaultName */ protected static $defaultName = 'admin:create'; + #[Inject( + AdminServiceInterface::class, + AdminRoleServiceInterface::class, + )] public function __construct( - protected AdminService $adminService, - protected AdminRoleService $adminRoleService, + protected AdminServiceInterface $adminService, + protected AdminRoleServiceInterface $adminRoleService, ) { parent::__construct(self::$defaultName); } @@ -74,7 +80,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $this->adminService->createAdmin($inputFilter->getValues()); - $output->writeln(Message::ADMIN_CREATED); + (new SymfonyStyle($input, $output))->info(Message::ADMIN_CREATED); return Command::SUCCESS; } @@ -84,7 +90,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int */ private function getData(InputInterface $input): array { - $role = $this->adminRoleService->findOneBy(['name' => AdminRole::ROLE_ADMIN]); + $adminRole = $this->adminRoleService->getAdminRoleRepository()->findOneBy(['name' => AdminRole::ROLE_ADMIN]); + if (! $adminRole instanceof AdminRole) { + throw new NotFoundException(Message::ROLE_NOT_FOUND); + } return [ 'identity' => $input->getOption('identity'), @@ -93,7 +102,7 @@ private function getData(InputInterface $input): array 'firstName' => $input->getOption('firstName'), 'lastName' => $input->getOption('lastName'), 'roles' => [ - ['uuid' => $role->getUuid()->toString()], + ['uuid' => $adminRole->getUuid()->toString()], ], ]; } diff --git a/src/Admin/src/ConfigProvider.php b/src/Admin/src/ConfigProvider.php index f9cac17f..a79ffe9e 100644 --- a/src/Admin/src/ConfigProvider.php +++ b/src/Admin/src/ConfigProvider.php @@ -7,7 +7,6 @@ use Api\Admin\Collection\AdminCollection; use Api\Admin\Collection\AdminRoleCollection; use Api\Admin\Command\AdminCreateCommand; -use Api\Admin\Factory\AdminCreateCommandFactory; use Api\Admin\Handler\Account\GetAdminAccountResourceHandler; use Api\Admin\Handler\Account\PatchAdminAccountResourceHandler; use Api\Admin\Handler\Admin\DeleteAdminResourceHandler; @@ -17,17 +16,10 @@ use Api\Admin\Handler\Admin\PostAdminResourceHandler; use Api\Admin\Handler\Admin\Role\GetAdminRoleCollectionHandler; use Api\Admin\Handler\Admin\Role\GetAdminRoleResourceHandler; -use Api\Admin\Service\AdminRoleService; -use Api\Admin\Service\AdminRoleServiceInterface; -use Api\Admin\Service\AdminService; -use Api\Admin\Service\AdminServiceInterface; use Api\App\ConfigProvider as AppConfigProvider; use Api\App\Factory\HandlerDelegatorFactory; use Core\Admin\Entity\Admin; use Core\Admin\Entity\AdminRole; -use Core\Admin\Repository\AdminRepository; -use Core\Admin\Repository\AdminRoleRepository; -use Dot\DependencyInjection\Factory\AttributedRepositoryFactory; use Dot\DependencyInjection\Factory\AttributedServiceFactory; use Mezzio\Application; use Mezzio\Hal\Metadata\MetadataMap; @@ -42,7 +34,7 @@ public function __invoke(): array ]; } - public function getDependencies(): array + private function getDependencies(): array { return [ 'delegators' => [ @@ -58,11 +50,7 @@ public function getDependencies(): array PostAdminResourceHandler::class => [HandlerDelegatorFactory::class], ], 'factories' => [ - AdminCreateCommand::class => AdminCreateCommandFactory::class, - AdminRepository::class => AttributedRepositoryFactory::class, - AdminRoleRepository::class => AttributedRepositoryFactory::class, - AdminRoleService::class => AttributedServiceFactory::class, - AdminService::class => AttributedServiceFactory::class, + AdminCreateCommand::class => AttributedServiceFactory::class, DeleteAdminResourceHandler::class => AttributedServiceFactory::class, GetAdminAccountResourceHandler::class => AttributedServiceFactory::class, GetAdminCollectionHandler::class => AttributedServiceFactory::class, @@ -73,14 +61,10 @@ public function getDependencies(): array PatchAdminResourceHandler::class => AttributedServiceFactory::class, PostAdminResourceHandler::class => AttributedServiceFactory::class, ], - 'aliases' => [ - AdminRoleServiceInterface::class => AdminRoleService::class, - AdminServiceInterface::class => AdminService::class, - ], ]; } - public function getHalConfig(): array + private function getHalConfig(): array { return [ AppConfigProvider::getCollection(AdminCollection::class, 'admin::list-admin', 'admins'), diff --git a/src/Admin/src/Factory/AdminCreateCommandFactory.php b/src/Admin/src/Factory/AdminCreateCommandFactory.php deleted file mode 100644 index 6d174f10..00000000 --- a/src/Admin/src/Factory/AdminCreateCommandFactory.php +++ /dev/null @@ -1,32 +0,0 @@ -get(AdminService::class); - assert($adminService instanceof AdminService); - - $adminRoleService = $container->get(AdminRoleService::class); - assert($adminRoleService instanceof AdminRoleService); - - return new AdminCreateCommand($adminService, $adminRoleService); - } -} diff --git a/src/Admin/src/Handler/Account/PatchAdminAccountResourceHandler.php b/src/Admin/src/Handler/Account/PatchAdminAccountResourceHandler.php index 8e27fa7e..6bc900f7 100644 --- a/src/Admin/src/Handler/Account/PatchAdminAccountResourceHandler.php +++ b/src/Admin/src/Handler/Account/PatchAdminAccountResourceHandler.php @@ -5,12 +5,12 @@ namespace Api\Admin\Handler\Account; use Api\Admin\InputFilter\UpdateAdminInputFilter; -use Api\Admin\Service\AdminServiceInterface; -use Api\App\Exception\BadRequestException; -use Api\App\Exception\ConflictException; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; use Core\Admin\Entity\Admin; +use Core\Admin\Service\AdminServiceInterface; +use Core\App\Exception\BadRequestException; +use Core\App\Exception\ConflictException; +use Core\App\Exception\NotFoundException; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; diff --git a/src/Admin/src/Handler/Admin/DeleteAdminResourceHandler.php b/src/Admin/src/Handler/Admin/DeleteAdminResourceHandler.php index 2374ec2d..2f55de23 100644 --- a/src/Admin/src/Handler/Admin/DeleteAdminResourceHandler.php +++ b/src/Admin/src/Handler/Admin/DeleteAdminResourceHandler.php @@ -4,9 +4,9 @@ namespace Api\Admin\Handler\Admin; -use Api\Admin\Service\AdminServiceInterface; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; +use Core\Admin\Service\AdminServiceInterface; +use Core\App\Exception\NotFoundException; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -26,9 +26,9 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - $admin = $this->adminService->findOneBy(['uuid' => $request->getAttribute('uuid')]); - - $this->adminService->deleteAdmin($admin); + $this->adminService->getAdminRepository()->deleteAdmin( + $this->adminService->find($request->getAttribute('uuid')) + ); return $this->noContentResponse(); } diff --git a/src/Admin/src/Handler/Admin/GetAdminCollectionHandler.php b/src/Admin/src/Handler/Admin/GetAdminCollectionHandler.php index 827d08ad..000f3d7c 100644 --- a/src/Admin/src/Handler/Admin/GetAdminCollectionHandler.php +++ b/src/Admin/src/Handler/Admin/GetAdminCollectionHandler.php @@ -4,9 +4,10 @@ namespace Api\Admin\Handler\Admin; -use Api\Admin\Service\AdminServiceInterface; -use Api\App\Exception\BadRequestException; +use Api\Admin\Collection\AdminCollection; use Api\App\Handler\AbstractHandler; +use Core\Admin\Service\AdminServiceInterface; +use Core\App\Exception\BadRequestException; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -26,6 +27,9 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - return $this->createResponse($request, $this->adminService->getAdmins($request->getQueryParams())); + return $this->createResponse( + $request, + new AdminCollection($this->adminService->getAdminRepository()->getAdmins($request->getQueryParams())) + ); } } diff --git a/src/Admin/src/Handler/Admin/GetAdminResourceHandler.php b/src/Admin/src/Handler/Admin/GetAdminResourceHandler.php index fff78ade..bbbbf2f8 100644 --- a/src/Admin/src/Handler/Admin/GetAdminResourceHandler.php +++ b/src/Admin/src/Handler/Admin/GetAdminResourceHandler.php @@ -4,9 +4,9 @@ namespace Api\Admin\Handler\Admin; -use Api\Admin\Service\AdminServiceInterface; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; +use Core\Admin\Service\AdminServiceInterface; +use Core\App\Exception\NotFoundException; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -26,8 +26,9 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - $admin = $this->adminService->findOneBy(['uuid' => $request->getAttribute('uuid')]); - - return $this->createResponse($request, $admin); + return $this->createResponse( + $request, + $this->adminService->find($request->getAttribute('uuid')) + ); } } diff --git a/src/Admin/src/Handler/Admin/PatchAdminResourceHandler.php b/src/Admin/src/Handler/Admin/PatchAdminResourceHandler.php index 8a08ecb2..0cc1e863 100644 --- a/src/Admin/src/Handler/Admin/PatchAdminResourceHandler.php +++ b/src/Admin/src/Handler/Admin/PatchAdminResourceHandler.php @@ -5,11 +5,11 @@ namespace Api\Admin\Handler\Admin; use Api\Admin\InputFilter\UpdateAdminInputFilter; -use Api\Admin\Service\AdminServiceInterface; -use Api\App\Exception\BadRequestException; -use Api\App\Exception\ConflictException; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; +use Core\Admin\Service\AdminServiceInterface; +use Core\App\Exception\BadRequestException; +use Core\App\Exception\ConflictException; +use Core\App\Exception\NotFoundException; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -38,7 +38,8 @@ public function handle(ServerRequestInterface $request): ResponseInterface throw (new BadRequestException())->setMessages($this->inputFilter->getMessages()); } - $admin = $this->adminService->findOneBy(['uuid' => $request->getAttribute('uuid')]); + $admin = $this->adminService->find($request->getAttribute('uuid')); + $this->adminService->updateAdmin($admin, (array) $this->inputFilter->getValues()); return $this->createResponse($request, $admin); diff --git a/src/Admin/src/Handler/Admin/PostAdminResourceHandler.php b/src/Admin/src/Handler/Admin/PostAdminResourceHandler.php index f21f0287..19059d24 100644 --- a/src/Admin/src/Handler/Admin/PostAdminResourceHandler.php +++ b/src/Admin/src/Handler/Admin/PostAdminResourceHandler.php @@ -5,11 +5,11 @@ namespace Api\Admin\Handler\Admin; use Api\Admin\InputFilter\CreateAdminInputFilter; -use Api\Admin\Service\AdminServiceInterface; -use Api\App\Exception\BadRequestException; -use Api\App\Exception\ConflictException; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; +use Core\Admin\Service\AdminServiceInterface; +use Core\App\Exception\BadRequestException; +use Core\App\Exception\ConflictException; +use Core\App\Exception\NotFoundException; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -38,8 +38,9 @@ public function handle(ServerRequestInterface $request): ResponseInterface throw (new BadRequestException())->setMessages($this->inputFilter->getMessages()); } - $admin = $this->adminService->createAdmin((array) $this->inputFilter->getValues()); - - return $this->createdResponse($request, $admin); + return $this->createdResponse( + $request, + $this->adminService->createAdmin((array) $this->inputFilter->getValues()) + ); } } diff --git a/src/Admin/src/Handler/Admin/Role/GetAdminRoleCollectionHandler.php b/src/Admin/src/Handler/Admin/Role/GetAdminRoleCollectionHandler.php index 78b75ef5..61730701 100644 --- a/src/Admin/src/Handler/Admin/Role/GetAdminRoleCollectionHandler.php +++ b/src/Admin/src/Handler/Admin/Role/GetAdminRoleCollectionHandler.php @@ -4,9 +4,10 @@ namespace Api\Admin\Handler\Admin\Role; -use Api\Admin\Service\AdminRoleServiceInterface; -use Api\App\Exception\BadRequestException; +use Api\Admin\Collection\AdminRoleCollection; use Api\App\Handler\AbstractHandler; +use Core\Admin\Service\AdminRoleServiceInterface; +use Core\App\Exception\BadRequestException; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -17,7 +18,7 @@ class GetAdminRoleCollectionHandler extends AbstractHandler AdminRoleServiceInterface::class, )] public function __construct( - protected AdminRoleServiceInterface $roleService, + protected AdminRoleServiceInterface $adminRoleService, ) { } @@ -26,6 +27,11 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - return $this->createResponse($request, $this->roleService->getAdminRoles($request->getQueryParams())); + return $this->createResponse( + $request, + new AdminRoleCollection( + $this->adminRoleService->getAdminRoleRepository()->getAdminRoles($request->getQueryParams()) + ) + ); } } diff --git a/src/Admin/src/Handler/Admin/Role/GetAdminRoleResourceHandler.php b/src/Admin/src/Handler/Admin/Role/GetAdminRoleResourceHandler.php index 5d026fd7..479247c8 100644 --- a/src/Admin/src/Handler/Admin/Role/GetAdminRoleResourceHandler.php +++ b/src/Admin/src/Handler/Admin/Role/GetAdminRoleResourceHandler.php @@ -4,9 +4,9 @@ namespace Api\Admin\Handler\Admin\Role; -use Api\Admin\Service\AdminRoleServiceInterface; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; +use Core\Admin\Service\AdminRoleServiceInterface; +use Core\App\Exception\NotFoundException; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -17,7 +17,7 @@ class GetAdminRoleResourceHandler extends AbstractHandler AdminRoleServiceInterface::class, )] public function __construct( - protected AdminRoleServiceInterface $roleService, + protected AdminRoleServiceInterface $adminRoleService, ) { } @@ -26,8 +26,9 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - $role = $this->roleService->findOneBy(['uuid' => $request->getAttribute('uuid')]); - - return $this->createResponse($request, $role); + return $this->createResponse( + $request, + $this->adminRoleService->find($request->getAttribute('uuid')) + ); } } diff --git a/src/Admin/src/Service/AdminRoleService.php b/src/Admin/src/Service/AdminRoleService.php deleted file mode 100644 index 971d9bac..00000000 --- a/src/Admin/src/Service/AdminRoleService.php +++ /dev/null @@ -1,62 +0,0 @@ -adminRoleRepository->findOneBy($params); - if (! $role instanceof AdminRole) { - throw new NotFoundException(Message::ROLE_NOT_FOUND); - } - - return $role; - } - - /** - * @throws BadRequestException - */ - public function getAdminRoles(array $params = []): AdminRoleCollection - { - $values = [ - 'role.name', - 'role.created', - 'role.updated', - ]; - - $params['order'] = $params['order'] ?? 'role.created'; - if (! in_array($params['order'], $values)) { - throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'order')]); - } - - $qb = $this->adminRoleRepository->getAdminRoles($params); - $qb->getQuery()->useQueryCache(true); - - return new AdminRoleCollection($qb, false); - } -} diff --git a/src/Admin/src/Service/AdminRoleServiceInterface.php b/src/Admin/src/Service/AdminRoleServiceInterface.php deleted file mode 100644 index 27671eff..00000000 --- a/src/Admin/src/Service/AdminRoleServiceInterface.php +++ /dev/null @@ -1,23 +0,0 @@ -exists($data['identity'])) { - throw new ConflictException(Message::DUPLICATE_IDENTITY); - } - - $admin = (new Admin()) - ->setIdentity($data['identity']) - ->usePassword($data['password']) - ->setFirstName($data['firstName']) - ->setLastName($data['lastName']) - ->setStatus($data['status'] ?? AdminStatusEnum::Active); - - foreach ($data['roles'] as $roleData) { - $admin->addRole( - $this->adminRoleService->findOneBy(['uuid' => $roleData['uuid']]) - ); - } - - return $this->adminRepository->saveAdmin($admin); - } - - public function deleteAdmin(Admin $admin): void - { - $this->adminRepository->deleteAdmin( - $admin->resetRoles()->deactivate() - ); - } - - public function exists(string $identity = ''): bool - { - return $this->adminRepository->findOneBy(['identity' => $identity]) instanceof Admin; - } - - public function existsOther(string $identity = '', string $uuid = ''): bool - { - try { - $admin = $this->findOneBy(['identity' => $identity]); - - return $admin->getUuid()->toString() !== $uuid; - } catch (NotFoundException) { - return false; - } - } - - /** - * @throws NotFoundException - */ - public function findOneBy(array $params = []): Admin - { - $admin = $this->adminRepository->findOneBy($params); - if (! $admin instanceof Admin) { - throw new NotFoundException(Message::ADMIN_NOT_FOUND); - } - - return $admin; - } - - /** - * @throws BadRequestException - */ - public function getAdmins(array $params = []): AdminCollection - { - $values = [ - 'admin.identity', - 'admin.firstName', - 'admin.lastName', - 'admin.status', - 'admin.created', - 'admin.updated', - ]; - - $params['order'] = $params['order'] ?? 'admin.created'; - if (! in_array($params['order'], $values)) { - throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'order')]); - } - - $qb = $this->adminRepository->getAdmins($params); - $qb->getQuery()->useQueryCache(true); - - return new AdminCollection($qb, false); - } - - /** - * @throws BadRequestException - * @throws ConflictException - * @throws NotFoundException - */ - public function updateAdmin(Admin $admin, array $data = []): Admin - { - if (isset($data['identity']) && $this->existsOther($data['identity'], $admin->getUuid()->toString())) { - throw new ConflictException(Message::DUPLICATE_IDENTITY); - } - - if (! empty($data['password'])) { - $admin->usePassword($data['password']); - } - - if (isset($data['firstName'])) { - $admin->setFirstName($data['firstName']); - } - - if (isset($data['lastName'])) { - $admin->setLastName($data['lastName']); - } - - if (isset($data['status'])) { - $admin->setStatus($data['status']); - } - - if (! empty($data['roles'])) { - $admin->resetRoles(); - foreach ($data['roles'] as $roleData) { - $admin->addRole( - $this->adminRoleService->findOneBy(['uuid' => $roleData['uuid']]) - ); - } - } - - if (! $admin->hasRoles()) { - throw (new BadRequestException())->setMessages([Message::RESTRICTION_ROLES]); - } - - return $this->adminRepository->saveAdmin($admin); - } -} diff --git a/src/App/src/Attribute/BaseDeprecation.php b/src/App/src/Attribute/BaseDeprecation.php index 64196d0d..3773697f 100644 --- a/src/App/src/Attribute/BaseDeprecation.php +++ b/src/App/src/Attribute/BaseDeprecation.php @@ -4,7 +4,7 @@ namespace Api\App\Attribute; -use Api\App\Exception\DeprecationSunsetException; +use Core\App\Exception\DeprecationSunsetException; use Core\App\Message; use Laminas\Validator\Date; diff --git a/src/App/src/Command/RouteListCommand.php b/src/App/src/Command/RouteListCommand.php index f61e7f74..8a7eab12 100644 --- a/src/App/src/Command/RouteListCommand.php +++ b/src/App/src/Command/RouteListCommand.php @@ -5,6 +5,7 @@ namespace Api\App\Command; use Api\App\RoutesDelegator; +use Dot\DependencyInjection\Attribute\Inject; use Fig\Http\Message\RequestMethodInterface; use Mezzio\Application; use Symfony\Component\Console\Attribute\AsCommand; @@ -32,6 +33,9 @@ class RouteListCommand extends Command /** @var string $defaultName */ protected static $defaultName = 'route:list'; + #[Inject( + Application::class, + )] public function __construct( protected Application $application, ) { diff --git a/src/App/src/Command/TokenGenerateCommand.php b/src/App/src/Command/TokenGenerateCommand.php index a2640757..5f34f861 100644 --- a/src/App/src/Command/TokenGenerateCommand.php +++ b/src/App/src/Command/TokenGenerateCommand.php @@ -4,16 +4,20 @@ namespace Api\App\Command; -use Api\App\Exception\NotFoundException; use Api\App\Service\ErrorReportServiceInterface; +use Core\App\Exception\NotFoundException; +use Dot\DependencyInjection\Attribute\Inject; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; use function sprintf; +use const PHP_EOL; + #[AsCommand( name: 'token:generate', description: 'Generic token generator.', @@ -24,6 +28,9 @@ class TokenGenerateCommand extends Command protected static $defaultName = 'token:generate'; private string $typeErrorReporting = 'error-reporting'; + #[Inject( + ErrorReportServiceInterface::class, + )] public function __construct( protected ErrorReportServiceInterface $errorReportService, ) { @@ -38,18 +45,14 @@ protected function configure(): void ->addArgument('type', InputArgument::REQUIRED, 'The type of token to be generated.') ->addUsage($this->typeErrorReporting) ->setHelp( - // @codingStandardsIgnoreStart - <<%command.name% is a multipurpose command that allows creating tokens required by different parts of the API. + sprintf( + '%%command.name%% is a multipurpose command' + . ' that allows creating tokens required by different parts of the API. Usage: -1. Create token for the error reporting endpoint: -* run: %command.full_name% $this->typeErrorReporting -* copy the generated token -* open config/autoload/error-handling.global.php -* paste the copied string inside the tokens array found under the ErrorReportServiceInterface::class key. -MSG - // @codingStandardsIgnoreEnd +- Create token for the error reporting endpoint: ./%%command.full_name%% %s', + $this->typeErrorReporting + ) ); } @@ -58,9 +61,11 @@ protected function configure(): void */ protected function execute(InputInterface $input, OutputInterface $output): int { + $io = new SymfonyStyle($input, $output); + $type = $input->getArgument('type'); match ($type) { - $this->typeErrorReporting => $this->generateErrorReportingToken($output), + $this->typeErrorReporting => $this->generateErrorReportingToken($io), default => throw new NotFoundException( sprintf('Unknown token type: %s', $type) ) @@ -69,22 +74,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int return Command::SUCCESS; } - private function generateErrorReportingToken(OutputInterface $output): void + private function generateErrorReportingToken(SymfonyStyle $io): void { $token = $this->errorReportService->generateToken(); - $output->writeln( - // @codingStandardsIgnoreStart - <<writeln('Error reporting token:' . PHP_EOL); + $io->writeln(' ' . $token . '' . PHP_EOL); - $token - -* copy the generated token + $io->writeln( + sprintf( + '* copy the generated token * open config/autoload/error-handling.global.php -* paste the copied string inside the tokens array found under the ErrorReportServiceInterface::class key. -MSG - // @codingStandardsIgnoreEnd +* paste the copied string inside the tokens array found under the %s key.', + 'ErrorReportServiceInterface::class' + ) ); } } diff --git a/src/App/src/ConfigProvider.php b/src/App/src/ConfigProvider.php index 8850fc46..bcafb02b 100644 --- a/src/App/src/ConfigProvider.php +++ b/src/App/src/ConfigProvider.php @@ -6,10 +6,7 @@ use Api\App\Command\RouteListCommand; use Api\App\Command\TokenGenerateCommand; -use Api\App\Factory\AuthenticationMiddlewareFactory; use Api\App\Factory\HandlerDelegatorFactory; -use Api\App\Factory\RouteListCommandFactory; -use Api\App\Factory\TokenGenerateCommandFactory; use Api\App\Handler\GetIndexResourceHandler; use Api\App\Handler\PostErrorReportResourceHandler; use Api\App\Middleware\AuthenticationMiddleware; @@ -17,17 +14,13 @@ use Api\App\Middleware\ContentNegotiationMiddleware; use Api\App\Middleware\DeprecationMiddleware; use Api\App\Middleware\ErrorReportPermissionMiddleware; -use Api\App\Middleware\ResponseMiddleware; use Api\App\Service\ErrorReportService; use Api\App\Service\ErrorReportServiceInterface; use Dot\DependencyInjection\Factory\AttributedServiceFactory; -use Dot\Mail\Factory\MailOptionsAbstractFactory; -use Dot\Mail\Factory\MailServiceAbstractFactory; -use Dot\Mail\Service\MailService; use Laminas\Hydrator\ArraySerializableHydrator; use Mezzio\Application; -use Mezzio\Authentication; -use Mezzio\Hal\Metadata\MetadataMap; +use Mezzio\Authentication\AuthenticationInterface; +use Mezzio\Authentication\OAuth2\OAuth2Adapter; use Mezzio\Hal\Metadata\RouteBasedCollectionMetadata; use Mezzio\Hal\Metadata\RouteBasedResourceMetadata; use Mezzio\Template\TemplateRendererInterface; @@ -43,12 +36,11 @@ class ConfigProvider public function __invoke(): array { return [ - 'dependencies' => $this->getDependencies(), - MetadataMap::class => $this->getHalConfig(), + 'dependencies' => $this->getDependencies(), ]; } - public function getDependencies(): array + private function getDependencies(): array { return [ 'delegators' => [ @@ -57,37 +49,28 @@ public function getDependencies(): array PostErrorReportResourceHandler::class => [HandlerDelegatorFactory::class], ], 'factories' => [ - 'dot-mail.options.default' => MailOptionsAbstractFactory::class, - 'dot-mail.service.default' => MailServiceAbstractFactory::class, - AuthenticationMiddleware::class => AuthenticationMiddlewareFactory::class, + AuthenticationMiddleware::class => AttributedServiceFactory::class, AuthorizationMiddleware::class => AttributedServiceFactory::class, ContentNegotiationMiddleware::class => AttributedServiceFactory::class, DeprecationMiddleware::class => AttributedServiceFactory::class, - Environment::class => TwigEnvironmentFactory::class, ErrorReportPermissionMiddleware::class => AttributedServiceFactory::class, GetIndexResourceHandler::class => AttributedServiceFactory::class, PostErrorReportResourceHandler::class => AttributedServiceFactory::class, ErrorReportService::class => AttributedServiceFactory::class, - ResponseMiddleware::class => AttributedServiceFactory::class, - RouteListCommand::class => RouteListCommandFactory::class, - TokenGenerateCommand::class => TokenGenerateCommandFactory::class, + RouteListCommand::class => AttributedServiceFactory::class, + TokenGenerateCommand::class => AttributedServiceFactory::class, + Environment::class => TwigEnvironmentFactory::class, TwigExtension::class => TwigExtensionFactory::class, TwigRenderer::class => TwigRendererFactory::class, ], 'aliases' => [ - Authentication\AuthenticationInterface::class => Authentication\OAuth2\OAuth2Adapter::class, - ErrorReportServiceInterface::class => ErrorReportService::class, - MailService::class => 'dot-mail.service.default', - TemplateRendererInterface::class => TwigRenderer::class, + AuthenticationInterface::class => OAuth2Adapter::class, + ErrorReportServiceInterface::class => ErrorReportService::class, + TemplateRendererInterface::class => TwigRenderer::class, ], ]; } - public function getHalConfig(): array - { - return []; - } - /** * @param class-string $collectionClass */ diff --git a/src/App/src/Factory/AuthenticationMiddlewareFactory.php b/src/App/src/Factory/AuthenticationMiddlewareFactory.php deleted file mode 100644 index dbb26c34..00000000 --- a/src/App/src/Factory/AuthenticationMiddlewareFactory.php +++ /dev/null @@ -1,33 +0,0 @@ -has(AuthenticationInterface::class)) { - throw new InvalidConfigException('AuthenticationInterface service is missing'); - } - - $authentication = $container->get(AuthenticationInterface::class); - assert($authentication instanceof AuthenticationInterface); - - return new AuthenticationMiddleware($authentication); - } -} diff --git a/src/App/src/Factory/HandlerDelegatorFactory.php b/src/App/src/Factory/HandlerDelegatorFactory.php index 13bbd0ac..9a196ced 100644 --- a/src/App/src/Factory/HandlerDelegatorFactory.php +++ b/src/App/src/Factory/HandlerDelegatorFactory.php @@ -4,8 +4,8 @@ namespace Api\App\Factory; -use Api\App\Exception\RuntimeException; use Api\App\Handler\AbstractHandler; +use Core\App\Exception\RuntimeException; use Core\App\Message; use Mezzio\Hal\HalResponseFactory; use Mezzio\Hal\ResourceGenerator; diff --git a/src/App/src/Factory/OAuthAccessTokenRepositoryFactory.php b/src/App/src/Factory/OAuthAccessTokenRepositoryFactory.php deleted file mode 100644 index aabf58a0..00000000 --- a/src/App/src/Factory/OAuthAccessTokenRepositoryFactory.php +++ /dev/null @@ -1,29 +0,0 @@ -get(EntityManagerInterface::class); - assert($entityManager instanceof EntityManagerInterface); - - return $entityManager->getRepository(OAuthAccessToken::class); - } -} diff --git a/src/App/src/Factory/OAuthAuthCodeRepositoryFactory.php b/src/App/src/Factory/OAuthAuthCodeRepositoryFactory.php deleted file mode 100644 index 20d71e99..00000000 --- a/src/App/src/Factory/OAuthAuthCodeRepositoryFactory.php +++ /dev/null @@ -1,29 +0,0 @@ -get(EntityManagerInterface::class); - assert($entityManager instanceof EntityManagerInterface); - - return $entityManager->getRepository(OAuthAuthCode::class); - } -} diff --git a/src/App/src/Factory/OAuthClientRepositoryFactory.php b/src/App/src/Factory/OAuthClientRepositoryFactory.php deleted file mode 100644 index 7d62571f..00000000 --- a/src/App/src/Factory/OAuthClientRepositoryFactory.php +++ /dev/null @@ -1,29 +0,0 @@ -get(EntityManagerInterface::class); - assert($entityManager instanceof EntityManagerInterface); - - return $entityManager->getRepository(OAuthClient::class); - } -} diff --git a/src/App/src/Factory/OAuthRefreshTokenRepositoryFactory.php b/src/App/src/Factory/OAuthRefreshTokenRepositoryFactory.php deleted file mode 100644 index f421f156..00000000 --- a/src/App/src/Factory/OAuthRefreshTokenRepositoryFactory.php +++ /dev/null @@ -1,29 +0,0 @@ -get(EntityManagerInterface::class); - assert($entityManager instanceof EntityManagerInterface); - - return $entityManager->getRepository(OAuthRefreshToken::class); - } -} diff --git a/src/App/src/Factory/OAuthScopeRepositoryFactory.php b/src/App/src/Factory/OAuthScopeRepositoryFactory.php deleted file mode 100644 index 61138015..00000000 --- a/src/App/src/Factory/OAuthScopeRepositoryFactory.php +++ /dev/null @@ -1,29 +0,0 @@ -get(EntityManagerInterface::class); - assert($entityManager instanceof EntityManagerInterface); - - return $entityManager->getRepository(OAuthScope::class); - } -} diff --git a/src/App/src/Factory/RouteListCommandFactory.php b/src/App/src/Factory/RouteListCommandFactory.php deleted file mode 100644 index bb8c4545..00000000 --- a/src/App/src/Factory/RouteListCommandFactory.php +++ /dev/null @@ -1,28 +0,0 @@ -get(Application::class); - assert($application instanceof Application); - - return new RouteListCommand($application); - } -} diff --git a/src/App/src/Factory/TokenGenerateCommandFactory.php b/src/App/src/Factory/TokenGenerateCommandFactory.php deleted file mode 100644 index a1dc8678..00000000 --- a/src/App/src/Factory/TokenGenerateCommandFactory.php +++ /dev/null @@ -1,28 +0,0 @@ -get(ErrorReportServiceInterface::class); - assert($errorReportService instanceof ErrorReportServiceInterface); - - return new TokenGenerateCommand($errorReportService); - } -} diff --git a/src/App/src/Factory/UserRepositoryFactory.php b/src/App/src/Factory/UserRepositoryFactory.php deleted file mode 100644 index 0180c041..00000000 --- a/src/App/src/Factory/UserRepositoryFactory.php +++ /dev/null @@ -1,29 +0,0 @@ -get(EntityManagerInterface::class); - assert($entityManager instanceof EntityManagerInterface); - - return $entityManager->getRepository(User::class); - } -} diff --git a/src/App/src/Handler/GetIndexResourceHandler.php b/src/App/src/Handler/GetIndexResourceHandler.php index a9238f08..3e0e9064 100644 --- a/src/App/src/Handler/GetIndexResourceHandler.php +++ b/src/App/src/Handler/GetIndexResourceHandler.php @@ -5,9 +5,12 @@ namespace Api\App\Handler; use Api\App\Attribute\ResourceDeprecation; +use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use function sprintf; + #[ResourceDeprecation( sunset: '2038-01-01', link: 'https://docs.dotkernel.org/api-documentation/v5/core-features/versioning', @@ -15,8 +18,16 @@ )] class GetIndexResourceHandler extends AbstractHandler { + #[Inject( + 'config.application.name', + )] + public function __construct( + private readonly string $applicationName, + ) { + } + public function handle(ServerRequestInterface $request): ResponseInterface { - return $this->jsonResponse(['message' => 'Dotkernel API version 5']); + return $this->jsonResponse(['message' => sprintf('%s version 5', $this->applicationName)]); } } diff --git a/src/App/src/Handler/PostErrorReportResourceHandler.php b/src/App/src/Handler/PostErrorReportResourceHandler.php index bc09d9ef..b58dc670 100644 --- a/src/App/src/Handler/PostErrorReportResourceHandler.php +++ b/src/App/src/Handler/PostErrorReportResourceHandler.php @@ -5,9 +5,9 @@ namespace Api\App\Handler; use Api\App\Attribute\MethodDeprecation; -use Api\App\Exception\BadRequestException; use Api\App\InputFilter\ErrorReportInputFilter; use Api\App\Service\ErrorReportServiceInterface; +use Core\App\Exception\BadRequestException; use Core\App\Message; use Dot\DependencyInjection\Attribute\Inject; use Fig\Http\Message\StatusCodeInterface; diff --git a/src/App/src/Middleware/AuthenticationMiddleware.php b/src/App/src/Middleware/AuthenticationMiddleware.php index cb6f72e2..152e0be8 100644 --- a/src/App/src/Middleware/AuthenticationMiddleware.php +++ b/src/App/src/Middleware/AuthenticationMiddleware.php @@ -6,6 +6,8 @@ use Core\User\Entity\UserRole; use Core\User\UserIdentity; +use Dot\DependencyInjection\Attribute\Inject; +use Mezzio\Authentication\AuthenticationInterface; use Mezzio\Authentication\UserInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -13,6 +15,15 @@ class AuthenticationMiddleware extends \Mezzio\Authentication\AuthenticationMiddleware { + #[Inject( + AuthenticationInterface::class, + )] + public function __construct( + AuthenticationInterface $auth, + ) { + parent::__construct($auth); + } + public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { $user = $this->auth->authenticate($request); diff --git a/src/App/src/Middleware/ContentNegotiationMiddleware.php b/src/App/src/Middleware/ContentNegotiationMiddleware.php index f12325fe..3135e62f 100644 --- a/src/App/src/Middleware/ContentNegotiationMiddleware.php +++ b/src/App/src/Middleware/ContentNegotiationMiddleware.php @@ -26,7 +26,7 @@ readonly class ContentNegotiationMiddleware implements MiddlewareInterface { #[Inject( - "config.content-negotiation", + 'config.content-negotiation', )] public function __construct( private array $config, diff --git a/src/App/src/Middleware/DeprecationMiddleware.php b/src/App/src/Middleware/DeprecationMiddleware.php index ac41a6a8..2bd0367f 100644 --- a/src/App/src/Middleware/DeprecationMiddleware.php +++ b/src/App/src/Middleware/DeprecationMiddleware.php @@ -6,7 +6,7 @@ use Api\App\Attribute\MethodDeprecation; use Api\App\Attribute\ResourceDeprecation; -use Api\App\Exception\DeprecationConflictException; +use Core\App\Exception\DeprecationConflictException; use Core\App\Message; use Dot\DependencyInjection\Attribute\Inject; use Dot\Router\Middleware\LazyLoadingMiddleware; @@ -43,10 +43,10 @@ class DeprecationMiddleware implements MiddlewareInterface ]; #[Inject( - "config.application.versioning", + 'config.application.versioning', )] public function __construct( - protected readonly array $config, + private readonly array $config, ) { } diff --git a/src/App/src/Middleware/ErrorReportPermissionMiddleware.php b/src/App/src/Middleware/ErrorReportPermissionMiddleware.php index 30794cae..32e0d1cc 100644 --- a/src/App/src/Middleware/ErrorReportPermissionMiddleware.php +++ b/src/App/src/Middleware/ErrorReportPermissionMiddleware.php @@ -4,28 +4,30 @@ namespace Api\App\Middleware; -use Api\App\Exception\ForbiddenException; -use Api\App\Exception\UnauthorizedException; use Api\App\Service\ErrorReportServiceInterface; +use Core\App\Exception\ForbiddenException; +use Core\App\Exception\RuntimeException; +use Core\App\Exception\UnauthorizedException; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; -class ErrorReportPermissionMiddleware implements MiddlewareInterface +readonly class ErrorReportPermissionMiddleware implements MiddlewareInterface { #[Inject( ErrorReportServiceInterface::class, )] public function __construct( - protected ErrorReportServiceInterface $errorReportService, + private ErrorReportServiceInterface $errorReportService, ) { } /** - * @throws UnauthorizedException * @throws ForbiddenException + * @throws RuntimeException + * @throws UnauthorizedException */ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { diff --git a/src/App/src/Middleware/ResponseMiddleware.php b/src/App/src/Middleware/ResponseMiddleware.php index 244f8766..9604a1a1 100644 --- a/src/App/src/Middleware/ResponseMiddleware.php +++ b/src/App/src/Middleware/ResponseMiddleware.php @@ -4,20 +4,18 @@ namespace Api\App\Middleware; -use Api\App\Exception\BadRequestException; -use Api\App\Exception\ConflictException; -use Api\App\Exception\ExpiredException; -use Api\App\Exception\ForbiddenException; -use Api\App\Exception\MethodNotAllowedException; -use Api\App\Exception\NotFoundException; -use Api\App\Exception\RuntimeException; -use Api\App\Exception\UnauthorizedException; -use Dot\DependencyInjection\Attribute\Inject; +use Core\App\Exception\BadRequestException; +use Core\App\Exception\ConflictException; +use Core\App\Exception\ExpiredException; +use Core\App\Exception\ForbiddenException; +use Core\App\Exception\MethodNotAllowedException; +use Core\App\Exception\NotFoundException; +use Core\App\Exception\RuntimeException; +use Core\App\Exception\UnauthorizedException; use Dot\Mail\Exception\MailException; use Exception; use Fig\Http\Message\StatusCodeInterface; use Laminas\Diactoros\Response\JsonResponse; -use Mezzio\Hal\HalResponseFactory; use Mezzio\Hal\ResourceGenerator\Exception\OutOfBoundsException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -28,14 +26,6 @@ class ResponseMiddleware implements MiddlewareInterface { - #[Inject( - HalResponseFactory::class, - )] - public function __construct( - protected HalResponseFactory $responseFactory, - ) { - } - public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { try { diff --git a/src/App/src/Service/ErrorReportService.php b/src/App/src/Service/ErrorReportService.php index ffc9a975..746b3426 100644 --- a/src/App/src/Service/ErrorReportService.php +++ b/src/App/src/Service/ErrorReportService.php @@ -4,12 +4,12 @@ namespace Api\App\Service; -use Api\App\Exception\ForbiddenException; -use Api\App\Exception\UnauthorizedException; +use Core\App\Exception\ForbiddenException; +use Core\App\Exception\RuntimeException; +use Core\App\Exception\UnauthorizedException; use Core\App\Message; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ServerRequestInterface; -use RuntimeException; use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Filesystem; @@ -32,7 +32,7 @@ class ErrorReportService implements ErrorReportServiceInterface private ?string $token = null; #[Inject( - "config", + 'config', )] public function __construct( protected array $config, diff --git a/src/App/src/Service/ErrorReportServiceInterface.php b/src/App/src/Service/ErrorReportServiceInterface.php index 5d9a20f0..8b8ec5d6 100644 --- a/src/App/src/Service/ErrorReportServiceInterface.php +++ b/src/App/src/Service/ErrorReportServiceInterface.php @@ -4,10 +4,10 @@ namespace Api\App\Service; -use Api\App\Exception\ForbiddenException; -use Api\App\Exception\UnauthorizedException; +use Core\App\Exception\ForbiddenException; +use Core\App\Exception\RuntimeException; +use Core\App\Exception\UnauthorizedException; use Psr\Http\Message\ServerRequestInterface; -use RuntimeException; use Symfony\Component\Filesystem\Exception\IOException; interface ErrorReportServiceInterface diff --git a/src/Core/src/Admin/src/ConfigProvider.php b/src/Core/src/Admin/src/ConfigProvider.php index 40bf6fbb..249d37d8 100644 --- a/src/Core/src/Admin/src/ConfigProvider.php +++ b/src/Core/src/Admin/src/ConfigProvider.php @@ -5,7 +5,15 @@ namespace Core\Admin; use Core\Admin\DBAL\Types\AdminStatusEnumType; +use Core\Admin\Repository\AdminRepository; +use Core\Admin\Repository\AdminRoleRepository; +use Core\Admin\Service\AdminRoleService; +use Core\Admin\Service\AdminRoleServiceInterface; +use Core\Admin\Service\AdminService; +use Core\Admin\Service\AdminServiceInterface; use Doctrine\ORM\Mapping\Driver\AttributeDriver; +use Dot\DependencyInjection\Factory\AttributedRepositoryFactory; +use Dot\DependencyInjection\Factory\AttributedServiceFactory; use function getcwd; @@ -14,7 +22,24 @@ class ConfigProvider public function __invoke(): array { return [ - 'doctrine' => $this->getDoctrineConfig(), + 'dependencies' => $this->getDependencies(), + 'doctrine' => $this->getDoctrineConfig(), + ]; + } + + private function getDependencies(): array + { + return [ + 'factories' => [ + AdminService::class => AttributedServiceFactory::class, + AdminRoleService::class => AttributedServiceFactory::class, + AdminRepository::class => AttributedRepositoryFactory::class, + AdminRoleRepository::class => AttributedRepositoryFactory::class, + ], + 'aliases' => [ + AdminServiceInterface::class => AdminService::class, + AdminRoleServiceInterface::class => AdminRoleService::class, + ], ]; } diff --git a/src/Core/src/Admin/src/Entity/Admin.php b/src/Core/src/Admin/src/Entity/Admin.php index 683af14e..ec46531d 100644 --- a/src/Core/src/Admin/src/Entity/Admin.php +++ b/src/Core/src/Admin/src/Entity/Admin.php @@ -16,32 +16,32 @@ use League\OAuth2\Server\Entities\UserEntityInterface; #[ORM\Entity(repositoryClass: AdminRepository::class)] -#[ORM\Table("admin")] +#[ORM\Table('admin')] #[ORM\HasLifecycleCallbacks] class Admin extends AbstractEntity implements UserEntityInterface { use PasswordTrait; use TimestampsTrait; - #[ORM\Column(name: "identity", type: "string", length: 100, unique: true)] + #[ORM\Column(name: 'identity', type: 'string', length: 100, unique: true)] protected string $identity = ''; - #[ORM\Column(name: "firstName", type: "string", length: 191)] + #[ORM\Column(name: 'firstName', type: 'string', length: 191)] protected string $firstName = ''; - #[ORM\Column(name: "lastName", type: "string", length: 191)] + #[ORM\Column(name: 'lastName', type: 'string', length: 191)] protected string $lastName = ''; - #[ORM\Column(name: "password", type: "string", length: 100)] + #[ORM\Column(name: 'password', type: 'string', length: 100)] protected string $password = ''; #[ORM\Column(type: 'admin_status_enum', options: ['default' => AdminStatusEnum::Active])] protected AdminStatusEnum $status = AdminStatusEnum::Active; #[ORM\ManyToMany(targetEntity: AdminRole::class)] - #[ORM\JoinTable(name: "admin_roles")] - #[ORM\JoinColumn(name: "userUuid", referencedColumnName: "uuid")] - #[ORM\InverseJoinColumn(name: "roleUuid", referencedColumnName: "uuid")] + #[ORM\JoinTable(name: 'admin_roles')] + #[ORM\JoinColumn(name: 'userUuid', referencedColumnName: 'uuid')] + #[ORM\InverseJoinColumn(name: 'roleUuid', referencedColumnName: 'uuid')] protected Collection $roles; public function __construct() diff --git a/src/Core/src/Admin/src/Entity/AdminRole.php b/src/Core/src/Admin/src/Entity/AdminRole.php index 79d384e1..c93fd261 100644 --- a/src/Core/src/Admin/src/Entity/AdminRole.php +++ b/src/Core/src/Admin/src/Entity/AdminRole.php @@ -11,7 +11,7 @@ use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: AdminRoleRepository::class)] -#[ORM\Table("admin_role")] +#[ORM\Table('admin_role')] #[ORM\HasLifecycleCallbacks] class AdminRole extends AbstractEntity implements RoleInterface { @@ -24,6 +24,9 @@ class AdminRole extends AbstractEntity implements RoleInterface self::ROLE_SUPERUSER, ]; + #[ORM\Column(name: 'name', type: 'string', length: 30, unique: true)] + protected string $name = ''; + public function __construct() { parent::__construct(); @@ -31,9 +34,6 @@ public function __construct() $this->created(); } - #[ORM\Column(name: "name", type: "string", length: 30, unique: true)] - protected string $name = ''; - public function getName(): string { return $this->name; diff --git a/src/Core/src/Admin/src/Repository/AdminRepository.php b/src/Core/src/Admin/src/Repository/AdminRepository.php index ed9c55ef..f4385a4a 100644 --- a/src/Core/src/Admin/src/Repository/AdminRepository.php +++ b/src/Core/src/Admin/src/Repository/AdminRepository.php @@ -5,11 +5,16 @@ namespace Core\Admin\Repository; use Core\Admin\Entity\Admin; +use Core\App\Exception\BadRequestException; use Core\App\Helper\PaginationHelper; +use Core\App\Message; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; use Dot\DependencyInjection\Attribute\Entity; +use function in_array; +use function sprintf; + /** * @extends EntityRepository */ @@ -22,25 +27,49 @@ public function deleteAdmin(Admin $admin): void $this->getEntityManager()->flush(); } - public function saveAdmin(Admin $admin): Admin + /** + * @throws BadRequestException + */ + public function getAdmins(array $params = []): QueryBuilder { - $this->getEntityManager()->persist($admin); - $this->getEntityManager()->flush(); + $page = PaginationHelper::getOffsetAndLimit($params); - return $admin; - } + $values = [ + 'admin.identity', + 'admin.firstName', + 'admin.lastName', + 'admin.status', + 'admin.created', + 'admin.updated', + ]; - public function getAdmins(array $filters = []): QueryBuilder - { - $page = PaginationHelper::getOffsetAndLimit($filters); + $params['order'] = $params['order'] ?? 'admin.created'; + if (! in_array($params['order'], $values)) { + throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'order')]); + } + $params['dir'] = $params['dir'] ?? 'desc'; + if (! in_array($params['dir'], ['asc', 'desc'])) { + throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'dir')]); + } - return $this + $queryBuilder = $this ->getEntityManager() ->createQueryBuilder() ->select(['admin']) ->from(Admin::class, 'admin') - ->orderBy($filters['order'] ?? 'admin.created', $filters['dir'] ?? 'desc') + ->orderBy($params['order'], $params['dir']) ->setFirstResult($page['offset']) ->setMaxResults($page['limit']); + $queryBuilder->getQuery()->useQueryCache(true); + + return $queryBuilder; + } + + public function saveAdmin(Admin $admin): Admin + { + $this->getEntityManager()->persist($admin); + $this->getEntityManager()->flush(); + + return $admin; } } diff --git a/src/Core/src/Admin/src/Repository/AdminRoleRepository.php b/src/Core/src/Admin/src/Repository/AdminRoleRepository.php index 6b10a2b9..47fe66b5 100644 --- a/src/Core/src/Admin/src/Repository/AdminRoleRepository.php +++ b/src/Core/src/Admin/src/Repository/AdminRoleRepository.php @@ -5,28 +5,54 @@ namespace Core\Admin\Repository; use Core\Admin\Entity\AdminRole; +use Core\App\Exception\BadRequestException; use Core\App\Helper\PaginationHelper; +use Core\App\Message; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; use Dot\DependencyInjection\Attribute\Entity; +use function in_array; +use function sprintf; + /** * @extends EntityRepository */ #[Entity(name: AdminRole::class)] class AdminRoleRepository extends EntityRepository { - public function getAdminRoles(array $filters = []): QueryBuilder + /** + * @throws BadRequestException + */ + public function getAdminRoles(array $params = []): QueryBuilder { - $page = PaginationHelper::getOffsetAndLimit($filters); + $page = PaginationHelper::getOffsetAndLimit($params); + + $values = [ + 'role.name', + 'role.created', + 'role.updated', + ]; - return $this + $params['order'] = $params['order'] ?? 'role.created'; + if (! in_array($params['order'], $values)) { + throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'order')]); + } + $params['dir'] = $params['dir'] ?? 'desc'; + if (! in_array($params['dir'], ['asc', 'desc'])) { + throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'dir')]); + } + + $queryBuilder = $this ->getEntityManager() ->createQueryBuilder() ->select(['role']) ->from(AdminRole::class, 'role') - ->orderBy($filters['order'] ?? 'role.created', $filters['dir'] ?? 'desc') + ->orderBy($params['order'], $params['dir']) ->setFirstResult($page['offset']) ->setMaxResults($page['limit']); + $queryBuilder->getQuery()->useQueryCache(true); + + return $queryBuilder; } } diff --git a/src/Core/src/Admin/src/Service/AdminRoleService.php b/src/Core/src/Admin/src/Service/AdminRoleService.php new file mode 100644 index 00000000..c352ac08 --- /dev/null +++ b/src/Core/src/Admin/src/Service/AdminRoleService.php @@ -0,0 +1,40 @@ +adminRoleRepository; + } + + /** + * @throws NotFoundException + */ + public function find(string $id): AdminRole + { + $adminRole = $this->adminRoleRepository->find($id); + if (! $adminRole instanceof AdminRole) { + throw new NotFoundException(Message::ROLE_NOT_FOUND); + } + + return $adminRole; + } +} diff --git a/src/Core/src/Admin/src/Service/AdminRoleServiceInterface.php b/src/Core/src/Admin/src/Service/AdminRoleServiceInterface.php new file mode 100644 index 00000000..bc7695c5 --- /dev/null +++ b/src/Core/src/Admin/src/Service/AdminRoleServiceInterface.php @@ -0,0 +1,19 @@ +adminRepository; + } + + /** + * @throws ConflictException + * @throws NotFoundException + */ + public function createAdmin(array $data = []): Admin + { + $admin = (new Admin()) + ->setIdentity($data['identity']) + ->usePassword($data['password']) + ->setFirstName($data['firstName']) + ->setLastName($data['lastName']) + ->setStatus($data['status'] ?? AdminStatusEnum::Active); + + $this->validateUniqueUser($admin->getIdentity()); + + foreach ($data['roles'] as $roleData) { + $adminRole = $this->adminRoleRepository->find($roleData['uuid']); + if (! $adminRole instanceof AdminRole) { + throw new NotFoundException(Message::ROLE_NOT_FOUND); + } + $admin->addRole($adminRole); + } + + return $this->adminRepository->saveAdmin($admin); + } + + public function deleteAdmin(Admin $admin): void + { + $this->adminRepository->deleteAdmin($admin); + } + + /** + * @throws NotFoundException + */ + public function find(string $id): Admin + { + $admin = $this->adminRepository->find($id); + if (! $admin instanceof Admin) { + throw new NotFoundException(Message::ADMIN_NOT_FOUND); + } + + return $admin; + } + + /** + * @throws BadRequestException + * @throws ConflictException + * @throws NotFoundException + */ + public function updateAdmin(Admin $admin, array $data = []): Admin + { + if (! empty($data['password'])) { + $admin->usePassword($data['password']); + } + + if (isset($data['firstName'])) { + $admin->setFirstName($data['firstName']); + } + + if (isset($data['lastName'])) { + $admin->setLastName($data['lastName']); + } + + if (isset($data['status'])) { + $admin->setStatus($data['status']); + } + + $this->validateUniqueUser($admin->getIdentity(), $admin->getUuid()); + + if (! empty($data['roles'])) { + $admin->resetRoles(); + foreach ($data['roles'] as $roleData) { + $adminRole = $this->adminRoleRepository->find($roleData['uuid']); + if (! $adminRole instanceof AdminRole) { + throw new NotFoundException(Message::ROLE_NOT_FOUND); + } + $admin->addRole($adminRole); + } + } + + if (! $admin->hasRoles()) { + throw (new BadRequestException())->setMessages([Message::RESTRICTION_ROLES]); + } + + return $this->adminRepository->saveAdmin($admin); + } + + /** + * @throws ConflictException + */ + public function validateUniqueUser(string $identity, ?UuidInterface $uuid = null): void + { + $admin = $this->adminRepository->findOneBy(['identity' => $identity]); + if ($admin instanceof Admin) { + if ($uuid === null) { + throw new ConflictException(Message::DUPLICATE_IDENTITY); + } + if ($admin->getUuid()->toString() !== $uuid->toString()) { + throw new ConflictException(Message::DUPLICATE_IDENTITY); + } + } + } +} diff --git a/src/Admin/src/Service/AdminServiceInterface.php b/src/Core/src/Admin/src/Service/AdminServiceInterface.php similarity index 55% rename from src/Admin/src/Service/AdminServiceInterface.php rename to src/Core/src/Admin/src/Service/AdminServiceInterface.php index 8deeed3f..1f1139c2 100644 --- a/src/Admin/src/Service/AdminServiceInterface.php +++ b/src/Core/src/Admin/src/Service/AdminServiceInterface.php @@ -2,16 +2,18 @@ declare(strict_types=1); -namespace Api\Admin\Service; +namespace Core\Admin\Service; -use Api\Admin\Collection\AdminCollection; -use Api\App\Exception\BadRequestException; -use Api\App\Exception\ConflictException; -use Api\App\Exception\NotFoundException; use Core\Admin\Entity\Admin; +use Core\Admin\Repository\AdminRepository; +use Core\App\Exception\BadRequestException; +use Core\App\Exception\ConflictException; +use Core\App\Exception\NotFoundException; interface AdminServiceInterface { + public function getAdminRepository(): AdminRepository; + /** * @throws ConflictException * @throws NotFoundException @@ -20,17 +22,10 @@ public function createAdmin(array $data = []): Admin; public function deleteAdmin(Admin $admin): void; - public function exists(string $identity = ''): bool; - /** * @throws NotFoundException */ - public function findOneBy(array $params = []): Admin; - - /** - * @throws BadRequestException - */ - public function getAdmins(array $params = []): AdminCollection; + public function find(string $id): Admin; /** * @throws BadRequestException diff --git a/src/Core/src/App/src/ConfigProvider.php b/src/Core/src/App/src/ConfigProvider.php index dfeffe5c..0ec10a15 100644 --- a/src/Core/src/App/src/ConfigProvider.php +++ b/src/Core/src/App/src/ConfigProvider.php @@ -4,14 +4,20 @@ namespace Core\App; -use Api\App\Factory\EntityListenerResolverFactory; -use Core\App\Entity\EntityListenerResolver; +use Core\App\Factory\EntityListenerResolverFactory; +use Core\App\Resolver\EntityListenerResolver; +use Core\App\Service\MailService; use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\Driver\AttributeDriver; use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; use Dot\Cache\Adapter\ArrayAdapter; use Dot\Cache\Adapter\FilesystemAdapter; +use Dot\DependencyInjection\Factory\AttributedServiceFactory; +use Dot\ErrorHandler\ErrorHandlerInterface; +use Dot\ErrorHandler\LogErrorHandler; +use Dot\Mail\Factory\MailOptionsAbstractFactory; +use Dot\Mail\Factory\MailServiceAbstractFactory; +use Dot\Mail\Service\MailService as DotMailService; use Ramsey\Uuid\Doctrine\UuidBinaryOrderedTimeType; use Ramsey\Uuid\Doctrine\UuidBinaryType; use Ramsey\Uuid\Doctrine\UuidType; @@ -30,16 +36,21 @@ public function __invoke(): array ]; } - public function getDependencies(): array + private function getDependencies(): array { return [ 'factories' => [ 'doctrine.entity_manager.orm_default' => EntityManagerFactory::class, + 'dot-mail.options.default' => MailOptionsAbstractFactory::class, + 'dot-mail.service.default' => MailServiceAbstractFactory::class, EntityListenerResolver::class => EntityListenerResolverFactory::class, + MailService::class => AttributedServiceFactory::class, ], 'aliases' => [ + DotMailService::class => 'dot-mail.service.default', EntityManager::class => 'doctrine.entity_manager.orm_default', EntityManagerInterface::class => 'doctrine.entity_manager.orm_default', + ErrorHandlerInterface::class => LogErrorHandler::class, ], ]; } @@ -84,15 +95,7 @@ private function getDoctrineConfig(): array ], 'driver' => [ 'orm_default' => [ - 'class' => MappingDriverChain::class, - 'drivers' => [ - 'Core\App\Entity' => 'AppEntities', - ], - ], - 'AppEntities' => [ - 'class' => AttributeDriver::class, - 'cache' => 'array', - 'paths' => getcwd() . '/src/Core/src/App/src/Entity', + 'class' => MappingDriverChain::class, ], ], 'fixtures' => getcwd() . '/src/Core/src/App/src/Fixture', diff --git a/src/Core/src/App/src/Entity/AbstractEntity.php b/src/Core/src/App/src/Entity/AbstractEntity.php index e685df8e..82e1dea0 100644 --- a/src/Core/src/App/src/Entity/AbstractEntity.php +++ b/src/Core/src/App/src/Entity/AbstractEntity.php @@ -17,7 +17,7 @@ abstract class AbstractEntity implements ArraySerializableInterface, EntityInterface { #[ORM\Id] - #[ORM\Column(name: 'uuid', type: "uuid_binary", unique: true)] + #[ORM\Column(name: 'uuid', type: 'uuid_binary', unique: true)] protected UuidInterface $uuid; public function __construct() diff --git a/src/Core/src/App/src/Entity/TimestampsTrait.php b/src/Core/src/App/src/Entity/TimestampsTrait.php index ca2ff2af..e08bd2a7 100644 --- a/src/Core/src/App/src/Entity/TimestampsTrait.php +++ b/src/Core/src/App/src/Entity/TimestampsTrait.php @@ -10,10 +10,10 @@ #[ORM\HasLifecycleCallbacks] trait TimestampsTrait { - #[ORM\Column(name: "created", type: "datetime_immutable")] + #[ORM\Column(name: 'created', type: 'datetime_immutable')] protected DateTimeImmutable $created; - #[ORM\Column(name: "updated", type: "datetime_immutable", nullable: true)] + #[ORM\Column(name: 'updated', type: 'datetime_immutable', nullable: true)] protected ?DateTimeImmutable $updated = null; public function getCreated(): ?DateTimeImmutable diff --git a/src/App/src/Exception/BadRequestException.php b/src/Core/src/App/src/Exception/BadRequestException.php similarity index 92% rename from src/App/src/Exception/BadRequestException.php rename to src/Core/src/App/src/Exception/BadRequestException.php index 69a06cc7..a001ae9c 100644 --- a/src/App/src/Exception/BadRequestException.php +++ b/src/Core/src/App/src/Exception/BadRequestException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Api\App\Exception; +namespace Core\App\Exception; use Exception; diff --git a/src/App/src/Exception/ConflictException.php b/src/Core/src/App/src/Exception/ConflictException.php similarity index 76% rename from src/App/src/Exception/ConflictException.php rename to src/Core/src/App/src/Exception/ConflictException.php index 15658958..09044a2d 100644 --- a/src/App/src/Exception/ConflictException.php +++ b/src/Core/src/App/src/Exception/ConflictException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Api\App\Exception; +namespace Core\App\Exception; use Exception; diff --git a/src/App/src/Exception/DeprecationConflictException.php b/src/Core/src/App/src/Exception/DeprecationConflictException.php similarity index 80% rename from src/App/src/Exception/DeprecationConflictException.php rename to src/Core/src/App/src/Exception/DeprecationConflictException.php index 4edf0d51..f54c1967 100644 --- a/src/App/src/Exception/DeprecationConflictException.php +++ b/src/Core/src/App/src/Exception/DeprecationConflictException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Api\App\Exception; +namespace Core\App\Exception; use RuntimeException; diff --git a/src/App/src/Exception/DeprecationSunsetException.php b/src/Core/src/App/src/Exception/DeprecationSunsetException.php similarity index 79% rename from src/App/src/Exception/DeprecationSunsetException.php rename to src/Core/src/App/src/Exception/DeprecationSunsetException.php index ad4b3288..e994cb96 100644 --- a/src/App/src/Exception/DeprecationSunsetException.php +++ b/src/Core/src/App/src/Exception/DeprecationSunsetException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Api\App\Exception; +namespace Core\App\Exception; use RuntimeException; diff --git a/src/App/src/Exception/ExpiredException.php b/src/Core/src/App/src/Exception/ExpiredException.php similarity index 76% rename from src/App/src/Exception/ExpiredException.php rename to src/Core/src/App/src/Exception/ExpiredException.php index ac36b776..712fc346 100644 --- a/src/App/src/Exception/ExpiredException.php +++ b/src/Core/src/App/src/Exception/ExpiredException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Api\App\Exception; +namespace Core\App\Exception; use Exception; diff --git a/src/App/src/Exception/ForbiddenException.php b/src/Core/src/App/src/Exception/ForbiddenException.php similarity index 76% rename from src/App/src/Exception/ForbiddenException.php rename to src/Core/src/App/src/Exception/ForbiddenException.php index 6aa88fc1..df3d4dd2 100644 --- a/src/App/src/Exception/ForbiddenException.php +++ b/src/Core/src/App/src/Exception/ForbiddenException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Api\App\Exception; +namespace Core\App\Exception; use Exception; diff --git a/src/App/src/Exception/MethodNotAllowedException.php b/src/Core/src/App/src/Exception/MethodNotAllowedException.php similarity index 77% rename from src/App/src/Exception/MethodNotAllowedException.php rename to src/Core/src/App/src/Exception/MethodNotAllowedException.php index e604c375..5e43aef1 100644 --- a/src/App/src/Exception/MethodNotAllowedException.php +++ b/src/Core/src/App/src/Exception/MethodNotAllowedException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Api\App\Exception; +namespace Core\App\Exception; use Exception; diff --git a/src/App/src/Exception/NotFoundException.php b/src/Core/src/App/src/Exception/NotFoundException.php similarity index 76% rename from src/App/src/Exception/NotFoundException.php rename to src/Core/src/App/src/Exception/NotFoundException.php index d4254200..e166d9d7 100644 --- a/src/App/src/Exception/NotFoundException.php +++ b/src/Core/src/App/src/Exception/NotFoundException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Api\App\Exception; +namespace Core\App\Exception; use Exception; diff --git a/src/App/src/Exception/RuntimeException.php b/src/Core/src/App/src/Exception/RuntimeException.php similarity index 74% rename from src/App/src/Exception/RuntimeException.php rename to src/Core/src/App/src/Exception/RuntimeException.php index 35c7e898..d2436a82 100644 --- a/src/App/src/Exception/RuntimeException.php +++ b/src/Core/src/App/src/Exception/RuntimeException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Api\App\Exception; +namespace Core\App\Exception; class RuntimeException extends \RuntimeException { diff --git a/src/App/src/Exception/UnauthorizedException.php b/src/Core/src/App/src/Exception/UnauthorizedException.php similarity index 76% rename from src/App/src/Exception/UnauthorizedException.php rename to src/Core/src/App/src/Exception/UnauthorizedException.php index 68fc283e..9ec10710 100644 --- a/src/App/src/Exception/UnauthorizedException.php +++ b/src/Core/src/App/src/Exception/UnauthorizedException.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Api\App\Exception; +namespace Core\App\Exception; use Exception; diff --git a/src/App/src/Factory/EntityListenerResolverFactory.php b/src/Core/src/App/src/Factory/EntityListenerResolverFactory.php similarity index 78% rename from src/App/src/Factory/EntityListenerResolverFactory.php rename to src/Core/src/App/src/Factory/EntityListenerResolverFactory.php index f2c8ea5b..c826995a 100644 --- a/src/App/src/Factory/EntityListenerResolverFactory.php +++ b/src/Core/src/App/src/Factory/EntityListenerResolverFactory.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Api\App\Factory; +namespace Core\App\Factory; -use Core\App\Entity\EntityListenerResolver; +use Core\App\Resolver\EntityListenerResolver; use Psr\Container\ContainerInterface; class EntityListenerResolverFactory diff --git a/src/Core/src/App/src/Fixture/OAuthClientLoader.php b/src/Core/src/App/src/Fixture/OAuthClientLoader.php index 642792f3..a971c411 100644 --- a/src/Core/src/App/src/Fixture/OAuthClientLoader.php +++ b/src/Core/src/App/src/Fixture/OAuthClientLoader.php @@ -4,7 +4,7 @@ namespace Core\App\Fixture; -use Core\App\Entity\OAuthClient; +use Core\Security\Entity\OAuthClient; use Doctrine\Common\DataFixtures\AbstractFixture; use Doctrine\Common\DataFixtures\FixtureInterface; use Doctrine\Persistence\ObjectManager; diff --git a/src/Core/src/App/src/Fixture/OAuthScopeLoader.php b/src/Core/src/App/src/Fixture/OAuthScopeLoader.php index e9bddda1..f87d2a7c 100644 --- a/src/Core/src/App/src/Fixture/OAuthScopeLoader.php +++ b/src/Core/src/App/src/Fixture/OAuthScopeLoader.php @@ -4,7 +4,7 @@ namespace Core\App\Fixture; -use Core\App\Entity\OAuthScope; +use Core\Security\Entity\OAuthScope; use Doctrine\Common\DataFixtures\FixtureInterface; use Doctrine\Persistence\ObjectManager; diff --git a/src/Core/src/App/src/Message.php b/src/Core/src/App/src/Message.php index 4987f46e..c8bee608 100644 --- a/src/Core/src/App/src/Message.php +++ b/src/Core/src/App/src/Message.php @@ -27,7 +27,7 @@ class Message public const METHOD_NOT_ALLOWED = 'The request method is not supported for the requested resource.'; public const MISSING_CONFIG = 'Missing configuration value: \'%s\'.'; public const RESET_PASSWORD_EXPIRED = 'Password reset request for hash: \'%s\' is invalid (expired).'; - public const RESET_PASSWORD_NOT_FOUND = 'Could not find password reset request identified by hash: \'%s\''; + public const RESET_PASSWORD_NOT_FOUND = 'Reset password not found.'; public const RESET_PASSWORD_OK = 'Password successfully modified.'; public const RESET_PASSWORD_USED = 'Password reset request for hash: \'%s\' is invalid (used).'; public const RESET_PASSWORD_VALID = 'Password reset request for hash: \'%s\' is valid.'; diff --git a/src/Core/src/App/src/Entity/EntityListenerResolver.php b/src/Core/src/App/src/Resolver/EntityListenerResolver.php similarity index 83% rename from src/Core/src/App/src/Entity/EntityListenerResolver.php rename to src/Core/src/App/src/Resolver/EntityListenerResolver.php index 093fd567..a842ee06 100644 --- a/src/Core/src/App/src/Entity/EntityListenerResolver.php +++ b/src/Core/src/App/src/Resolver/EntityListenerResolver.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Core\App\Entity; +namespace Core\App\Resolver; use Doctrine\ORM\Mapping\DefaultEntityListenerResolver; use Psr\Container\ContainerExceptionInterface; @@ -17,11 +17,10 @@ public function __construct( } /** - * @param string $className * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ - public function resolve($className): object + public function resolve(string $className): object { return $this->container->get($className); } diff --git a/src/Core/src/App/src/Service/MailService.php b/src/Core/src/App/src/Service/MailService.php new file mode 100644 index 00000000..510c28e8 --- /dev/null +++ b/src/Core/src/App/src/Service/MailService.php @@ -0,0 +1,152 @@ +isActive()) { + return false; + } + + $this->mailService->getMessage()->addTo($user->getDetail()->getEmail(), $user->getName()); + $this->mailService->setSubject('Welcome to ' . $this->config['application']['name']); + $this->mailService->setBody( + $this->templateRenderer->render('user::activate', [ + 'config' => $this->config, + 'user' => $user, + ]) + ); + + try { + return $this->mailService->send()->isValid(); + } catch (MailException | TransportExceptionInterface $exception) { + $this->logger->err($exception->getMessage()); + throw new MailException(sprintf(Message::MAIL_NOT_SENT_TO, $user->getDetail()->getEmail())); + } + } + + /** + * @throws MailException + */ + public function sendResetPasswordRequestedMail(User $user): bool + { + $this->mailService->getMessage()->addTo($user->getDetail()->getEmail(), $user->getName()); + $this->mailService->setSubject( + 'Reset password instructions for your ' . $this->config['application']['name'] . ' account' + ); + $this->mailService->setBody( + $this->templateRenderer->render('user::reset-password-requested', [ + 'config' => $this->config, + 'user' => $user, + ]) + ); + + try { + return $this->mailService->send()->isValid(); + } catch (MailException | TransportExceptionInterface $exception) { + $this->logger->err($exception->getMessage()); + throw new MailException(sprintf(Message::MAIL_NOT_SENT_TO, $user->getDetail()->getEmail())); + } + } + + /** + * @throws MailException + */ + public function sendResetPasswordCompletedMail(User $user): bool + { + $this->mailService->getMessage()->addTo($user->getDetail()->getEmail(), $user->getName()); + $this->mailService->setSubject( + 'You have successfully reset the password for your ' . $this->config['application']['name'] . ' account' + ); + $this->mailService->setBody( + $this->templateRenderer->render('user::reset-password-completed', [ + 'config' => $this->config, + 'user' => $user, + ]) + ); + + try { + return $this->mailService->send()->isValid(); + } catch (MailException | TransportExceptionInterface $exception) { + $this->logger->err($exception->getMessage()); + throw new MailException(sprintf(Message::MAIL_NOT_SENT_TO, $user->getDetail()->getEmail())); + } + } + + /** + * @throws MailException + */ + public function sendWelcomeMail(User $user): bool + { + $this->mailService->getMessage()->addTo($user->getDetail()->getEmail(), $user->getName()); + $this->mailService->setSubject('Welcome to ' . $this->config['application']['name']); + $this->mailService->setBody( + $this->templateRenderer->render('user::welcome', [ + 'config' => $this->config, + 'user' => $user, + ]) + ); + + try { + return $this->mailService->send()->isValid(); + } catch (MailException | TransportExceptionInterface $exception) { + $this->logger->err($exception->getMessage()); + throw new MailException(sprintf(Message::MAIL_NOT_SENT_TO, $user->getDetail()->getEmail())); + } + } + + /** + * @throws MailException + */ + public function sendRecoverIdentityMail(User $user): bool + { + $this->mailService->getMessage()->addTo($user->getDetail()->getEmail(), $user->getName()); + $this->mailService->setSubject( + 'Recover identity for your ' . $this->config['application']['name'] . ' account' + ); + $this->mailService->setBody( + $this->templateRenderer->render('user::recover-identity-requested', [ + 'config' => $this->config, + 'user' => $user, + ]) + ); + + try { + return $this->mailService->send()->isValid(); + } catch (MailException | TransportExceptionInterface $exception) { + $this->logger->err($exception->getMessage()); + throw new MailException(sprintf(Message::MAIL_NOT_SENT_TO, $user->getDetail()->getEmail())); + } + } +} diff --git a/src/Core/src/Security/src/ConfigProvider.php b/src/Core/src/Security/src/ConfigProvider.php new file mode 100644 index 00000000..cb8a9a7a --- /dev/null +++ b/src/Core/src/Security/src/ConfigProvider.php @@ -0,0 +1,57 @@ + $this->getDependencies(), + 'doctrine' => $this->getDoctrineConfig(), + ]; + } + + private function getDependencies(): array + { + return [ + 'factories' => [ + OAuthAccessTokenRepository::class => AttributedRepositoryFactory::class, + OAuthAuthCodeRepository::class => AttributedRepositoryFactory::class, + OAuthClientRepository::class => AttributedRepositoryFactory::class, + OAuthRefreshTokenRepository::class => AttributedRepositoryFactory::class, + OAuthScopeRepository::class => AttributedRepositoryFactory::class, + ], + ]; + } + + private function getDoctrineConfig(): array + { + return [ + 'driver' => [ + 'orm_default' => [ + 'drivers' => [ + 'Core\Security\Entity' => 'SecurityEntities', + ], + ], + 'SecurityEntities' => [ + 'class' => AttributeDriver::class, + 'cache' => 'array', + 'paths' => getcwd() . '/src/Core/src/Security/src/Entity', + ], + ], + ]; + } +} diff --git a/src/Core/src/App/src/Entity/OAuthAccessToken.php b/src/Core/src/Security/src/Entity/OAuthAccessToken.php similarity index 86% rename from src/Core/src/App/src/Entity/OAuthAccessToken.php rename to src/Core/src/Security/src/Entity/OAuthAccessToken.php index 73863569..2f5f2b17 100644 --- a/src/Core/src/App/src/Entity/OAuthAccessToken.php +++ b/src/Core/src/Security/src/Entity/OAuthAccessToken.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Core\App\Entity; +namespace Core\Security\Entity; -use Core\App\Repository\OAuthAccessTokenRepository; +use Core\Security\Repository\OAuthAccessTokenRepository; use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -21,34 +21,34 @@ use RuntimeException; #[ORM\Entity(repositoryClass: OAuthAccessTokenRepository::class)] -#[ORM\Table(name: "oauth_access_tokens")] +#[ORM\Table(name: 'oauth_access_tokens')] class OAuthAccessToken implements AccessTokenEntityInterface { #[ORM\Id] - #[ORM\Column(name: "id", type: "integer", options: ['unsigned' => true])] - #[ORM\GeneratedValue(strategy: "IDENTITY")] + #[ORM\Column(name: 'id', type: 'integer', options: ['unsigned' => true])] + #[ORM\GeneratedValue(strategy: 'IDENTITY')] private int $id; #[ORM\ManyToOne(targetEntity: OAuthClient::class)] - #[ORM\JoinColumn(name: "client_id", referencedColumnName: "id")] + #[ORM\JoinColumn(name: 'client_id', referencedColumnName: 'id')] private ClientEntityInterface $client; - #[ORM\Column(name: "user_id", type: "string", length: 25, nullable: true)] + #[ORM\Column(name: 'user_id', type: 'string', length: 25, nullable: true)] private ?string $userId; - #[ORM\Column(name: "token", type: "string", length: 100)] + #[ORM\Column(name: 'token', type: 'string', length: 100)] private string $token; - #[ORM\Column(name: "revoked", type: "boolean", options: ['default' => false])] + #[ORM\Column(name: 'revoked', type: 'boolean', options: ['default' => false])] private bool $isRevoked = false; - #[ORM\ManyToMany(targetEntity: OAuthScope::class, inversedBy: "accessTokens", indexBy: "id")] - #[ORM\JoinTable(name: "oauth_access_token_scopes")] - #[ORM\JoinColumn(name: "access_token_id", referencedColumnName: "id")] - #[ORM\InverseJoinColumn(name: "scope_id", referencedColumnName: "id")] + #[ORM\ManyToMany(targetEntity: OAuthScope::class, inversedBy: 'accessTokens', indexBy: 'id')] + #[ORM\JoinTable(name: 'oauth_access_token_scopes')] + #[ORM\JoinColumn(name: 'access_token_id', referencedColumnName: 'id')] + #[ORM\InverseJoinColumn(name: 'scope_id', referencedColumnName: 'id')] protected Collection $scopes; - #[ORM\Column(name: 'expires_at', type: "datetime_immutable")] + #[ORM\Column(name: 'expires_at', type: 'datetime_immutable')] private DateTimeImmutable $expiresAt; private ?CryptKey $privateKey = null; diff --git a/src/Core/src/App/src/Entity/OAuthAuthCode.php b/src/Core/src/Security/src/Entity/OAuthAuthCode.php similarity index 84% rename from src/Core/src/App/src/Entity/OAuthAuthCode.php rename to src/Core/src/Security/src/Entity/OAuthAuthCode.php index 32399f3f..5123cb7a 100644 --- a/src/Core/src/App/src/Entity/OAuthAuthCode.php +++ b/src/Core/src/Security/src/Entity/OAuthAuthCode.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Core\App\Entity; +namespace Core\Security\Entity; -use Core\App\Repository\OAuthAuthCodeRepository; +use Core\Security\Repository\OAuthAuthCodeRepository; use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -18,30 +18,30 @@ use function assert; #[ORM\Entity(repositoryClass: OAuthAuthCodeRepository::class)] -#[ORM\Table(name: "oauth_auth_codes")] +#[ORM\Table(name: 'oauth_auth_codes')] class OAuthAuthCode implements AuthCodeEntityInterface { use AuthCodeTrait; #[ORM\Id] - #[ORM\Column(name: "id", type: "integer", options: ['unsigned' => true])] - #[ORM\GeneratedValue(strategy: "IDENTITY")] + #[ORM\Column(name: 'id', type: 'integer', options: ['unsigned' => true])] + #[ORM\GeneratedValue(strategy: 'IDENTITY')] private int $id; #[ORM\ManyToOne(targetEntity: OAuthClient::class)] - #[ORM\JoinColumn(name: "client_id", referencedColumnName: "id")] + #[ORM\JoinColumn(name: 'client_id', referencedColumnName: 'id')] private ClientEntityInterface $client; - #[ORM\Column(name: "revoked", type: "boolean", options: ['default' => false])] + #[ORM\Column(name: 'revoked', type: 'boolean', options: ['default' => false])] private bool $isRevoked = false; - #[ORM\ManyToMany(targetEntity: OAuthScope::class, inversedBy: "authCodes", indexBy: "id")] - #[ORM\JoinTable(name: "oauth_auth_code_scopes")] - #[ORM\JoinColumn(name: "auth_code_id", referencedColumnName: "id")] - #[ORM\InverseJoinColumn(name: "scope_id", referencedColumnName: "id")] + #[ORM\ManyToMany(targetEntity: OAuthScope::class, inversedBy: 'authCodes', indexBy: 'id')] + #[ORM\JoinTable(name: 'oauth_auth_code_scopes')] + #[ORM\JoinColumn(name: 'auth_code_id', referencedColumnName: 'id')] + #[ORM\InverseJoinColumn(name: 'scope_id', referencedColumnName: 'id')] protected Collection $scopes; - #[ORM\Column(type: "datetime_immutable", nullable: true)] + #[ORM\Column(type: 'datetime_immutable', nullable: true)] private DateTimeImmutable $expiresDatetime; public function __construct() diff --git a/src/Core/src/App/src/Entity/OAuthClient.php b/src/Core/src/Security/src/Entity/OAuthClient.php similarity index 80% rename from src/Core/src/App/src/Entity/OAuthClient.php rename to src/Core/src/Security/src/Entity/OAuthClient.php index d1ca9c0c..ed4c2ba2 100644 --- a/src/Core/src/App/src/Entity/OAuthClient.php +++ b/src/Core/src/Security/src/Entity/OAuthClient.php @@ -2,42 +2,42 @@ declare(strict_types=1); -namespace Core\App\Entity; +namespace Core\Security\Entity; -use Core\App\Repository\OAuthClientRepository; +use Core\Security\Repository\OAuthClientRepository; use Core\User\Entity\User; use Doctrine\ORM\Mapping as ORM; use League\OAuth2\Server\Entities\ClientEntityInterface; #[ORM\Entity(repositoryClass: OAuthClientRepository::class)] -#[ORM\Table(name: "oauth_clients")] +#[ORM\Table(name: 'oauth_clients')] class OAuthClient implements ClientEntityInterface { public const NAME_ADMIN = 'admin'; public const NAME_FRONTEND = 'frontend'; #[ORM\Id] - #[ORM\Column(name: "id", type: "integer", options: ['unsigned' => true])] - #[ORM\GeneratedValue(strategy: "IDENTITY")] + #[ORM\Column(name: 'id', type: 'integer', options: ['unsigned' => true])] + #[ORM\GeneratedValue(strategy: 'IDENTITY')] private int $id; #[ORM\ManyToOne(targetEntity: User::class)] - #[ORM\JoinColumn(name: "user_id", referencedColumnName: "uuid", nullable: true)] + #[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'uuid', nullable: true)] private ?User $user = null; - #[ORM\Column(name: "name", type: "string", length: 40)] + #[ORM\Column(name: 'name', type: 'string', length: 40)] private string $name = ''; - #[ORM\Column(name: "secret", type: "string", length: 100, nullable: true)] + #[ORM\Column(name: 'secret', type: 'string', length: 100, nullable: true)] private ?string $secret = null; - #[ORM\Column(name: "redirect", type: "string", length: 191)] + #[ORM\Column(name: 'redirect', type: 'string', length: 191)] private string $redirect = ''; - #[ORM\Column(name: "revoked", type: "boolean", options: ['default' => false])] + #[ORM\Column(name: 'revoked', type: 'boolean', options: ['default' => false])] private bool $isRevoked = false; - #[ORM\Column(name: "isConfidential", type: "boolean", options: ['default' => false])] + #[ORM\Column(name: 'isConfidential', type: 'boolean', options: ['default' => false])] private bool $isConfidential = false; public function setId(int $id): self diff --git a/src/Core/src/App/src/Entity/OAuthRefreshToken.php b/src/Core/src/Security/src/Entity/OAuthRefreshToken.php similarity index 80% rename from src/Core/src/App/src/Entity/OAuthRefreshToken.php rename to src/Core/src/Security/src/Entity/OAuthRefreshToken.php index db987a55..db0ba618 100644 --- a/src/Core/src/App/src/Entity/OAuthRefreshToken.php +++ b/src/Core/src/Security/src/Entity/OAuthRefreshToken.php @@ -2,31 +2,31 @@ declare(strict_types=1); -namespace Core\App\Entity; +namespace Core\Security\Entity; -use Core\App\Repository\OAuthRefreshTokenRepository; +use Core\Security\Repository\OAuthRefreshTokenRepository; use DateTimeImmutable; use Doctrine\ORM\Mapping as ORM; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; #[ORM\Entity(repositoryClass: OAuthRefreshTokenRepository::class)] -#[ORM\Table(name: "oauth_refresh_tokens")] +#[ORM\Table(name: 'oauth_refresh_tokens')] class OAuthRefreshToken implements RefreshTokenEntityInterface { #[ORM\Id] - #[ORM\Column(name: "id", type: "integer", options: ['unsigned' => true])] - #[ORM\GeneratedValue(strategy: "IDENTITY")] + #[ORM\Column(name: 'id', type: 'integer', options: ['unsigned' => true])] + #[ORM\GeneratedValue(strategy: 'IDENTITY')] private int $id; #[ORM\ManyToOne(targetEntity: OAuthAccessToken::class)] - #[ORM\JoinColumn(name: "access_token_id", referencedColumnName: "id")] + #[ORM\JoinColumn(name: 'access_token_id', referencedColumnName: 'id')] private AccessTokenEntityInterface $accessToken; - #[ORM\Column(name: "revoked", type: "boolean", options: ['default' => false])] + #[ORM\Column(name: 'revoked', type: 'boolean', options: ['default' => false])] private bool $isRevoked = false; - #[ORM\Column(name: "expires_at", type: "datetime_immutable")] + #[ORM\Column(name: 'expires_at', type: 'datetime_immutable')] private DateTimeImmutable $expiresAt; public function setId(int $id): self diff --git a/src/Core/src/App/src/Entity/OAuthScope.php b/src/Core/src/Security/src/Entity/OAuthScope.php similarity index 89% rename from src/Core/src/App/src/Entity/OAuthScope.php rename to src/Core/src/Security/src/Entity/OAuthScope.php index 5a2fbf17..c642b119 100644 --- a/src/Core/src/App/src/Entity/OAuthScope.php +++ b/src/Core/src/Security/src/Entity/OAuthScope.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Core\App\Entity; +namespace Core\Security\Entity; -use Core\App\Repository\OAuthScopeRepository; +use Core\Security\Repository\OAuthScopeRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Criteria; @@ -13,23 +13,23 @@ use League\OAuth2\Server\Entities\Traits\ScopeTrait; #[ORM\Entity(repositoryClass: OAuthScopeRepository::class)] -#[ORM\Table(name: "oauth_scopes")] +#[ORM\Table(name: 'oauth_scopes')] class OAuthScope implements ScopeEntityInterface { use ScopeTrait; #[ORM\Id] - #[ORM\Column(name: "id", type: "integer", options: ['unsigned' => true])] - #[ORM\GeneratedValue(strategy: "IDENTITY")] + #[ORM\Column(name: 'id', type: 'integer', options: ['unsigned' => true])] + #[ORM\GeneratedValue(strategy: 'IDENTITY')] private int $id; - #[ORM\Column(name: "scope", type: "string", length: 191)] + #[ORM\Column(name: 'scope', type: 'string', length: 191)] private string $scope = ''; - #[ORM\ManyToMany(targetEntity: OAuthAccessToken::class, mappedBy: "scopes")] + #[ORM\ManyToMany(targetEntity: OAuthAccessToken::class, mappedBy: 'scopes')] protected Collection $accessTokens; - #[ORM\ManyToMany(targetEntity: OAuthAuthCode::class, mappedBy: "scopes")] + #[ORM\ManyToMany(targetEntity: OAuthAuthCode::class, mappedBy: 'scopes')] protected Collection $authCodes; public function __construct() diff --git a/src/Core/src/App/src/Repository/OAuthAccessTokenRepository.php b/src/Core/src/Security/src/Repository/OAuthAccessTokenRepository.php similarity index 96% rename from src/Core/src/App/src/Repository/OAuthAccessTokenRepository.php rename to src/Core/src/Security/src/Repository/OAuthAccessTokenRepository.php index b57eea4d..1a09230d 100644 --- a/src/Core/src/App/src/Repository/OAuthAccessTokenRepository.php +++ b/src/Core/src/Security/src/Repository/OAuthAccessTokenRepository.php @@ -2,11 +2,11 @@ declare(strict_types=1); -namespace Core\App\Repository; +namespace Core\Security\Repository; use Core\Admin\Entity\Admin; -use Core\App\Entity\OAuthAccessToken; -use Core\App\Entity\OAuthClient; +use Core\Security\Entity\OAuthAccessToken; +use Core\Security\Entity\OAuthClient; use Core\User\Entity\User; use Doctrine\ORM\EntityRepository; use Dot\DependencyInjection\Attribute\Entity; diff --git a/src/Core/src/App/src/Repository/OAuthAuthCodeRepository.php b/src/Core/src/Security/src/Repository/OAuthAuthCodeRepository.php similarity index 94% rename from src/Core/src/App/src/Repository/OAuthAuthCodeRepository.php rename to src/Core/src/Security/src/Repository/OAuthAuthCodeRepository.php index fddc5c8d..2628d3ad 100644 --- a/src/Core/src/App/src/Repository/OAuthAuthCodeRepository.php +++ b/src/Core/src/Security/src/Repository/OAuthAuthCodeRepository.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Core\App\Repository; +namespace Core\Security\Repository; -use Core\App\Entity\OAuthAuthCode; +use Core\Security\Entity\OAuthAuthCode; use Doctrine\ORM\EntityRepository; use Dot\DependencyInjection\Attribute\Entity; use League\OAuth2\Server\Entities\AuthCodeEntityInterface; diff --git a/src/Core/src/App/src/Repository/OAuthClientRepository.php b/src/Core/src/Security/src/Repository/OAuthClientRepository.php similarity index 96% rename from src/Core/src/App/src/Repository/OAuthClientRepository.php rename to src/Core/src/Security/src/Repository/OAuthClientRepository.php index be89f861..e7b4ad55 100644 --- a/src/Core/src/App/src/Repository/OAuthClientRepository.php +++ b/src/Core/src/Security/src/Repository/OAuthClientRepository.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Core\App\Repository; +namespace Core\Security\Repository; -use Core\App\Entity\OAuthClient; +use Core\Security\Entity\OAuthClient; use Doctrine\ORM\EntityRepository; use Dot\DependencyInjection\Attribute\Entity; use League\OAuth2\Server\Entities\ClientEntityInterface; diff --git a/src/Core/src/App/src/Repository/OAuthRefreshTokenRepository.php b/src/Core/src/Security/src/Repository/OAuthRefreshTokenRepository.php similarity index 94% rename from src/Core/src/App/src/Repository/OAuthRefreshTokenRepository.php rename to src/Core/src/Security/src/Repository/OAuthRefreshTokenRepository.php index 28b5af4e..c2b74c54 100644 --- a/src/Core/src/App/src/Repository/OAuthRefreshTokenRepository.php +++ b/src/Core/src/Security/src/Repository/OAuthRefreshTokenRepository.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Core\App\Repository; +namespace Core\Security\Repository; -use Core\App\Entity\OAuthRefreshToken; +use Core\Security\Entity\OAuthRefreshToken; use Doctrine\ORM\EntityRepository; use Dot\DependencyInjection\Attribute\Entity; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; diff --git a/src/Core/src/App/src/Repository/OAuthScopeRepository.php b/src/Core/src/Security/src/Repository/OAuthScopeRepository.php similarity index 93% rename from src/Core/src/App/src/Repository/OAuthScopeRepository.php rename to src/Core/src/Security/src/Repository/OAuthScopeRepository.php index 1ec6da85..ded71369 100644 --- a/src/Core/src/App/src/Repository/OAuthScopeRepository.php +++ b/src/Core/src/Security/src/Repository/OAuthScopeRepository.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Core\App\Repository; +namespace Core\Security\Repository; -use Core\App\Entity\OAuthScope; +use Core\Security\Entity\OAuthScope; use Doctrine\ORM\EntityRepository; use Dot\DependencyInjection\Attribute\Entity; use League\OAuth2\Server\Entities\ClientEntityInterface; diff --git a/src/Core/src/User/src/ConfigProvider.php b/src/Core/src/User/src/ConfigProvider.php index 295a864e..ec2caa2d 100644 --- a/src/Core/src/User/src/ConfigProvider.php +++ b/src/Core/src/User/src/ConfigProvider.php @@ -6,7 +6,23 @@ use Core\User\DBAL\Types\UserResetPasswordStatusEnumType; use Core\User\DBAL\Types\UserStatusEnumType; +use Core\User\EventListener\UserAvatarEventListener; +use Core\User\Repository\UserAvatarRepository; +use Core\User\Repository\UserDetailRepository; +use Core\User\Repository\UserRepository; +use Core\User\Repository\UserResetPasswordRepository; +use Core\User\Repository\UserRoleRepository; +use Core\User\Service\UserAvatarService; +use Core\User\Service\UserAvatarServiceInterface; +use Core\User\Service\UserResetPasswordService; +use Core\User\Service\UserResetPasswordServiceInterface; +use Core\User\Service\UserRoleService; +use Core\User\Service\UserRoleServiceInterface; +use Core\User\Service\UserService; +use Core\User\Service\UserServiceInterface; use Doctrine\ORM\Mapping\Driver\AttributeDriver; +use Dot\DependencyInjection\Factory\AttributedRepositoryFactory; +use Dot\DependencyInjection\Factory\AttributedServiceFactory; use function getcwd; @@ -15,7 +31,32 @@ class ConfigProvider public function __invoke(): array { return [ - 'doctrine' => $this->getDoctrineConfig(), + 'dependencies' => $this->getDependencies(), + 'doctrine' => $this->getDoctrineConfig(), + ]; + } + + private function getDependencies(): array + { + return [ + 'aliases' => [ + UserAvatarServiceInterface::class => UserAvatarService::class, + UserResetPasswordServiceInterface::class => UserResetPasswordService::class, + UserRoleServiceInterface::class => UserRoleService::class, + UserServiceInterface::class => UserService::class, + ], + 'factories' => [ + UserAvatarEventListener::class => AttributedServiceFactory::class, + UserAvatarService::class => AttributedServiceFactory::class, + UserResetPasswordService::class => AttributedServiceFactory::class, + UserRoleService::class => AttributedServiceFactory::class, + UserService::class => AttributedServiceFactory::class, + UserAvatarRepository::class => AttributedRepositoryFactory::class, + UserDetailRepository::class => AttributedRepositoryFactory::class, + UserRepository::class => AttributedRepositoryFactory::class, + UserResetPasswordRepository::class => AttributedRepositoryFactory::class, + UserRoleRepository::class => AttributedRepositoryFactory::class, + ], ]; } diff --git a/src/Core/src/User/src/Entity/User.php b/src/Core/src/User/src/Entity/User.php index c0bad08e..d46dc11b 100644 --- a/src/Core/src/User/src/Entity/User.php +++ b/src/Core/src/User/src/Entity/User.php @@ -20,38 +20,38 @@ use function uniqid; #[ORM\Entity(repositoryClass: UserRepository::class)] -#[ORM\Table(name: "user")] +#[ORM\Table(name: 'user')] #[ORM\HasLifecycleCallbacks] class User extends AbstractEntity implements UserEntityInterface { use PasswordTrait; use TimestampsTrait; - #[ORM\OneToOne(targetEntity: UserAvatar::class, mappedBy: "user", cascade: ['persist', 'remove'])] + #[ORM\OneToOne(targetEntity: UserAvatar::class, mappedBy: 'user', cascade: ['persist', 'remove'])] protected ?UserAvatar $avatar = null; - #[ORM\OneToOne(targetEntity: UserDetail::class, mappedBy: "user", cascade: ['persist', 'remove'])] + #[ORM\OneToOne(targetEntity: UserDetail::class, mappedBy: 'user', cascade: ['persist', 'remove'])] protected UserDetail $detail; - #[ORM\OneToMany(targetEntity: UserResetPassword::class, mappedBy: "user", cascade: ['persist', 'remove'])] + #[ORM\OneToMany(targetEntity: UserResetPassword::class, mappedBy: 'user', cascade: ['persist', 'remove'])] protected Collection $resetPasswords; #[ORM\ManyToMany(targetEntity: UserRole::class)] - #[ORM\JoinTable(name: "user_roles")] - #[ORM\JoinColumn(name: "userUuid", referencedColumnName: "uuid")] - #[ORM\InverseJoinColumn(name: "roleUuid", referencedColumnName: "uuid")] + #[ORM\JoinTable(name: 'user_roles')] + #[ORM\JoinColumn(name: 'userUuid', referencedColumnName: 'uuid')] + #[ORM\InverseJoinColumn(name: 'roleUuid', referencedColumnName: 'uuid')] protected Collection $roles; - #[ORM\Column(name: "identity", type: "string", length: 191, unique: true)] + #[ORM\Column(name: 'identity', type: 'string', length: 191, unique: true)] protected string $identity; - #[ORM\Column(name: "password", type: "string", length: 191)] + #[ORM\Column(name: 'password', type: 'string', length: 191)] protected string $password; #[ORM\Column(type: 'user_status_enum', options: ['default' => UserStatusEnum::Pending])] protected UserStatusEnum $status = UserStatusEnum::Pending; - #[ORM\Column(name: "hash", type: "string", length: 64, unique: true)] + #[ORM\Column(name: 'hash', type: 'string', length: 64, unique: true)] protected string $hash; public function __construct() diff --git a/src/Core/src/User/src/Entity/UserAvatar.php b/src/Core/src/User/src/Entity/UserAvatar.php index 83efa35f..e1b8c2b6 100644 --- a/src/Core/src/User/src/Entity/UserAvatar.php +++ b/src/Core/src/User/src/Entity/UserAvatar.php @@ -11,18 +11,18 @@ use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: UserAvatarRepository::class)] -#[ORM\Table(name: "user_avatar")] +#[ORM\Table(name: 'user_avatar')] #[ORM\HasLifecycleCallbacks] #[ORM\EntityListeners([UserAvatarEventListener::class])] class UserAvatar extends AbstractEntity { use TimestampsTrait; - #[ORM\OneToOne(targetEntity: User::class, inversedBy: "avatar")] - #[ORM\JoinColumn(name: "userUuid", referencedColumnName: "uuid")] + #[ORM\OneToOne(targetEntity: User::class, inversedBy: 'avatar')] + #[ORM\JoinColumn(name: 'userUuid', referencedColumnName: 'uuid')] protected User $user; - #[ORM\Column(name: "name", type: "string", length: 191)] + #[ORM\Column(name: 'name', type: 'string', length: 191)] protected string $name; protected ?string $url = null; diff --git a/src/Core/src/User/src/Entity/UserDetail.php b/src/Core/src/User/src/Entity/UserDetail.php index e45650e2..dcf61187 100644 --- a/src/Core/src/User/src/Entity/UserDetail.php +++ b/src/Core/src/User/src/Entity/UserDetail.php @@ -10,23 +10,23 @@ use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: UserDetailRepository::class)] -#[ORM\Table(name: "user_detail")] +#[ORM\Table(name: 'user_detail')] #[ORM\HasLifecycleCallbacks] class UserDetail extends AbstractEntity { use TimestampsTrait; - #[ORM\OneToOne(targetEntity: User::class, inversedBy: "detail")] - #[ORM\JoinColumn(name: "userUuid", referencedColumnName: "uuid")] + #[ORM\OneToOne(targetEntity: User::class, inversedBy: 'detail')] + #[ORM\JoinColumn(name: 'userUuid', referencedColumnName: 'uuid')] protected User $user; - #[ORM\Column(name: "firstName", type: "string", length: 191, nullable: true)] + #[ORM\Column(name: 'firstName', type: 'string', length: 191, nullable: true)] protected ?string $firstName = null; - #[ORM\Column(name: "lastName", type: "string", length: 191, nullable: true)] + #[ORM\Column(name: 'lastName', type: 'string', length: 191, nullable: true)] protected ?string $lastName = null; - #[ORM\Column(name: "email", type: "string", length: 191)] + #[ORM\Column(name: 'email', type: 'string', length: 191)] protected string $email; public function __construct() diff --git a/src/Core/src/User/src/Entity/UserResetPassword.php b/src/Core/src/User/src/Entity/UserResetPassword.php index 98cb90ce..8ee4ff7c 100644 --- a/src/Core/src/User/src/Entity/UserResetPassword.php +++ b/src/Core/src/User/src/Entity/UserResetPassword.php @@ -15,20 +15,20 @@ use Exception; #[ORM\Entity(repositoryClass: UserResetPasswordRepository::class)] -#[ORM\Table(name: "user_reset_password")] +#[ORM\Table(name: 'user_reset_password')] #[ORM\HasLifecycleCallbacks] class UserResetPassword extends AbstractEntity { use TimestampsTrait; - #[ORM\ManyToOne(targetEntity: User::class, cascade: ['persist', 'remove'], inversedBy: "resetPasswords")] - #[ORM\JoinColumn(name: "userUuid", referencedColumnName: "uuid")] + #[ORM\ManyToOne(targetEntity: User::class, cascade: ['persist', 'remove'], inversedBy: 'resetPasswords')] + #[ORM\JoinColumn(name: 'userUuid', referencedColumnName: 'uuid')] protected User $user; - #[ORM\Column(name: "expires", type: "datetime_immutable")] + #[ORM\Column(name: 'expires', type: 'datetime_immutable')] protected DateTimeImmutable $expires; - #[ORM\Column(name: "hash", type: "string", length: 64, unique: true)] + #[ORM\Column(name: 'hash', type: 'string', length: 64, unique: true)] protected string $hash; #[ORM\Column( diff --git a/src/Core/src/User/src/Entity/UserRole.php b/src/Core/src/User/src/Entity/UserRole.php index 450e5fae..dab89330 100644 --- a/src/Core/src/User/src/Entity/UserRole.php +++ b/src/Core/src/User/src/Entity/UserRole.php @@ -11,7 +11,7 @@ use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: UserRoleRepository::class)] -#[ORM\Table(name: "user_role")] +#[ORM\Table(name: 'user_role')] #[ORM\HasLifecycleCallbacks] class UserRole extends AbstractEntity implements RoleInterface { @@ -24,7 +24,7 @@ class UserRole extends AbstractEntity implements RoleInterface self::ROLE_USER, ]; - #[ORM\Column(name: "name", type: "string", length: 20, unique: true)] + #[ORM\Column(name: 'name', type: 'string', length: 20, unique: true)] protected ?string $name = null; public function __construct() diff --git a/src/Core/src/User/src/EventListener/UserAvatarEventListener.php b/src/Core/src/User/src/EventListener/UserAvatarEventListener.php index 8b623aff..9f199475 100644 --- a/src/Core/src/User/src/EventListener/UserAvatarEventListener.php +++ b/src/Core/src/User/src/EventListener/UserAvatarEventListener.php @@ -13,7 +13,7 @@ class UserAvatarEventListener { #[Inject( - "config", + 'config', )] public function __construct( protected array $config = [], diff --git a/src/Core/src/User/src/Repository/UserRepository.php b/src/Core/src/User/src/Repository/UserRepository.php index c0cd58d7..67ea88ba 100644 --- a/src/Core/src/User/src/Repository/UserRepository.php +++ b/src/Core/src/User/src/Repository/UserRepository.php @@ -5,9 +5,10 @@ namespace Core\User\Repository; use Core\Admin\Entity\Admin; -use Core\App\Entity\OAuthClient; +use Core\App\Exception\BadRequestException; use Core\App\Helper\PaginationHelper; use Core\App\Message; +use Core\Security\Entity\OAuthClient; use Core\User\Entity\User; use Core\User\Enum\UserStatusEnum; use Doctrine\ORM\EntityRepository; @@ -18,7 +19,9 @@ use League\OAuth2\Server\Repositories\UserRepositoryInterface; use Mezzio\Authentication\OAuth2\Entity\UserEntity; +use function in_array; use function password_verify; +use function sprintf; /** * @extends EntityRepository @@ -26,15 +29,28 @@ #[Entity(name: User::class)] class UserRepository extends EntityRepository implements UserRepositoryInterface { - public function deleteUser(User $user): void - { - $this->getEntityManager()->remove($user); - $this->getEntityManager()->flush(); - } - - public function getUsers(array $filters = []): QueryBuilder + /** + * @throws BadRequestException + */ + public function getUsers(array $params = []): QueryBuilder { - $page = PaginationHelper::getOffsetAndLimit($filters); + $page = PaginationHelper::getOffsetAndLimit($params); + + $values = [ + 'user.identity', + 'user.status', + 'user.created', + 'user.updated', + ]; + + $params['order'] = $params['order'] ?? 'user.created'; + if (! in_array($params['order'], $values)) { + throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'order')]); + } + $params['dir'] = $params['dir'] ?? 'desc'; + if (! in_array($params['dir'], ['asc', 'desc'])) { + throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'dir')]); + } $qb = $this ->getEntityManager() @@ -44,15 +60,18 @@ public function getUsers(array $filters = []): QueryBuilder ->leftJoin('user.avatar', 'avatar') ->leftJoin('user.detail', 'detail') ->leftJoin('user.roles', 'roles') - ->orderBy($filters['order'] ?? 'user.created', $filters['dir'] ?? 'desc') + ->andWhere('user.status != :status') + ->setParameter('status', UserStatusEnum::Deleted) + ->orderBy($params['order'], $params['dir']) ->setFirstResult($page['offset']) ->setMaxResults($page['limit']); + $qb->getQuery()->useQueryCache(true); - if (! empty($filters['status'])) { - $qb->andWhere('user.status = :status')->setParameter('status', $filters['status']); + if (! empty($params['status'])) { + $qb->andWhere('user.status = :status')->setParameter('status', $params['status']); } - if (! empty($filters['search'])) { + if (! empty($params['search'])) { $qb->andWhere( $qb->expr()->orX( $qb->expr()->like('user.identity', ':search'), @@ -60,17 +79,13 @@ public function getUsers(array $filters = []): QueryBuilder $qb->expr()->like('detail.lastName', ':search'), $qb->expr()->like('detail.email', ':search') ) - )->setParameter('search', '%' . $filters['search'] . '%'); + )->setParameter('search', '%' . $params['search'] . '%'); } - if (! empty($filters['role'])) { - $qb->andWhere('roles.name = :role')->setParameter('role', $filters['role']); + if (! empty($params['role'])) { + $qb->andWhere('roles.name = :role')->setParameter('role', $params['role']); } - //ignore deleted users - $qb->andWhere('user.status != :status')->setParameter('status', UserStatusEnum::Deleted); - $qb->getQuery()->useQueryCache(true); - return $qb; } diff --git a/src/Core/src/User/src/Repository/UserRoleRepository.php b/src/Core/src/User/src/Repository/UserRoleRepository.php index fef088c3..4d657e75 100644 --- a/src/Core/src/User/src/Repository/UserRoleRepository.php +++ b/src/Core/src/User/src/Repository/UserRoleRepository.php @@ -4,20 +4,44 @@ namespace Core\User\Repository; +use Core\App\Exception\BadRequestException; use Core\App\Helper\PaginationHelper; +use Core\App\Message; use Core\User\Entity\UserRole; use Doctrine\ORM\EntityRepository; -use Doctrine\ORM\QueryBuilder; +use Doctrine\ORM\Query; use Dot\DependencyInjection\Attribute\Entity; +use function in_array; +use function sprintf; + /** * @extends EntityRepository */ #[Entity(name: UserRole::class)] class UserRoleRepository extends EntityRepository { - public function getRoles(array $params = []): QueryBuilder + /** + * @throws BadRequestException + */ + public function getRoles(array $params = []): Query { + $values = [ + 'role.name', + 'role.created', + 'role.updated', + ]; + + $params['order'] = $params['order'] ?? 'role.created'; + if (! in_array($params['order'], $values)) { + throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'order')]); + } + + $params['dir'] = $params['dir'] ?? 'desc'; + if (! in_array($params['dir'], ['asc', 'desc'])) { + throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'dir')]); + } + $page = PaginationHelper::getOffsetAndLimit($params); return $this @@ -25,8 +49,10 @@ public function getRoles(array $params = []): QueryBuilder ->createQueryBuilder() ->select(['role']) ->from(UserRole::class, 'role') - ->orderBy($params['order'] ?? 'role.created', $params['dir'] ?? 'desc') + ->orderBy($params['order'], $params['dir']) ->setFirstResult($page['offset']) - ->setMaxResults($page['limit']); + ->setMaxResults($page['limit']) + ->getQuery() + ->useQueryCache(true); } } diff --git a/src/User/src/Service/UserAvatarService.php b/src/Core/src/User/src/Service/UserAvatarService.php similarity index 92% rename from src/User/src/Service/UserAvatarService.php rename to src/Core/src/User/src/Service/UserAvatarService.php index 4c446160..768fa157 100644 --- a/src/User/src/Service/UserAvatarService.php +++ b/src/Core/src/User/src/Service/UserAvatarService.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Api\User\Service; +namespace Core\User\Service; use Core\User\Entity\User; use Core\User\Entity\UserAvatar; @@ -30,7 +30,7 @@ class UserAvatarService implements UserAvatarServiceInterface #[Inject( UserAvatarRepository::class, - "config", + 'config', )] public function __construct( protected UserAvatarRepository $userAvatarRepository, @@ -38,6 +38,11 @@ public function __construct( ) { } + public function getUserAvatarRepository(): UserAvatarRepository + { + return $this->userAvatarRepository; + } + public function createAvatar(User $user, UploadedFile $uploadedFile): UserAvatar { $path = $this->getUserAvatarDirectoryPath($user); @@ -59,7 +64,7 @@ public function createAvatar(User $user, UploadedFile $uploadedFile): UserAvatar return $avatar; } - public function removeAvatar(User $user): void + public function deleteAvatar(User $user): void { if (! $user->hasAvatar()) { return; diff --git a/src/User/src/Service/UserAvatarServiceInterface.php b/src/Core/src/User/src/Service/UserAvatarServiceInterface.php similarity index 76% rename from src/User/src/Service/UserAvatarServiceInterface.php rename to src/Core/src/User/src/Service/UserAvatarServiceInterface.php index 336841de..7c54fa33 100644 --- a/src/User/src/Service/UserAvatarServiceInterface.php +++ b/src/Core/src/User/src/Service/UserAvatarServiceInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Api\User\Service; +namespace Core\User\Service; use Core\User\Entity\User; use Core\User\Entity\UserAvatar; @@ -12,5 +12,5 @@ interface UserAvatarServiceInterface { public function createAvatar(User $user, UploadedFile $uploadedFile): UserAvatar; - public function removeAvatar(User $user): void; + public function deleteAvatar(User $user): void; } diff --git a/src/Core/src/User/src/Service/UserResetPasswordService.php b/src/Core/src/User/src/Service/UserResetPasswordService.php new file mode 100644 index 00000000..8be58afd --- /dev/null +++ b/src/Core/src/User/src/Service/UserResetPasswordService.php @@ -0,0 +1,40 @@ +userResetPasswordRepository; + } + + /** + * @throws NotFoundException + */ + public function findOneBy(array $params): UserResetPassword + { + $userResetPassword = $this->userResetPasswordRepository->findOneBy($params); + if (! $userResetPassword instanceof UserResetPassword) { + throw new NotFoundException(Message::RESET_PASSWORD_NOT_FOUND); + } + + return $userResetPassword; + } +} diff --git a/src/Core/src/User/src/Service/UserResetPasswordServiceInterface.php b/src/Core/src/User/src/Service/UserResetPasswordServiceInterface.php new file mode 100644 index 00000000..a8272529 --- /dev/null +++ b/src/Core/src/User/src/Service/UserResetPasswordServiceInterface.php @@ -0,0 +1,19 @@ +userRoleRepository; + } + + /** + * @throws NotFoundException + */ + public function find(string $id): UserRole + { + $userRole = $this->userRoleRepository->find($id); + if (! $userRole instanceof UserRole) { + throw new NotFoundException(Message::ROLE_NOT_FOUND); + } + + return $userRole; + } +} diff --git a/src/Core/src/User/src/Service/UserRoleServiceInterface.php b/src/Core/src/User/src/Service/UserRoleServiceInterface.php new file mode 100644 index 00000000..b6fb8c76 --- /dev/null +++ b/src/Core/src/User/src/Service/UserRoleServiceInterface.php @@ -0,0 +1,19 @@ +userRepository; + } + + public function activateUser(User $user): User + { + return $this->userRepository->saveUser($user->activate()); + } + + public function deactivateUser(User $user): User + { + return $this->userRepository->saveUser($user->deactivate()); + } + + /** + * @throws ConflictException + * @throws NotFoundException + */ + public function createUser(array $data = []): User + { + $detail = (new UserDetail()) + ->setFirstName($data['detail']['firstName'] ?? null) + ->setLastName($data['detail']['lastName'] ?? null) + ->setEmail($data['detail']['email']); + + $user = (new User()) + ->setDetail($detail) + ->setIdentity($data['identity']) + ->usePassword($data['password']) + ->setStatus($data['status'] ?? UserStatusEnum::Pending); + $detail->setUser($user); + + $this->validateUniqueUser($user->getIdentity(), $user->getDetail()->getEmail()); + + if (isset($data['roles']) && is_array($data['roles']) && count($data['roles']) > 0) { + foreach ($data['roles'] as $roleData) { + $userRole = $this->userRoleRepository->find($roleData['uuid']); + if (! $userRole instanceof UserRole) { + throw new NotFoundException(Message::ROLE_NOT_FOUND); + } + $user->addRole($userRole); + } + } + + return $this->userRepository->saveUser($user); + } + + public function deleteUser(User $user): User + { + $this->revokeTokens($user); + + return $this->anonymizeUser($user); + } + + private function anonymizeUser(User $user): User + { + $placeholder = $this->getAnonymousPlaceholder(); + + $user + ->setStatus(UserStatusEnum::Deleted) + ->setIdentity($placeholder . $this->config['userAnonymizeAppend']) + ->getDetail() + ->setFirstName($placeholder) + ->setLastName($placeholder) + ->setEmail($placeholder); + + return $this->userRepository->saveUser($user); + } + + private function getAnonymousPlaceholder(): string + { + return 'anonymous' . date('dmYHis'); + } + + /** + * @throws ConflictException + */ + private function validateUniqueUser(string $identity, string $email, ?UuidInterface $uuid = null): void + { + $user = $this->userRepository->findOneBy(['identity' => $identity]); + if ($user instanceof User) { + if ($uuid === null) { + throw new ConflictException(Message::DUPLICATE_IDENTITY); + } + if ($user->getUuid()->toString() !== $uuid->toString()) { + throw new ConflictException(Message::DUPLICATE_IDENTITY); + } + } + + $userDetail = $this->userDetailRepository->findOneBy(['email' => $email]); + if ($userDetail instanceof UserDetail) { + if ($uuid === null) { + throw new ConflictException(Message::DUPLICATE_EMAIL); + } + if ($userDetail->getUser()->getUuid()->toString() !== $uuid->toString()) { + throw new ConflictException(Message::DUPLICATE_EMAIL); + } + } + } + + private function revokeTokens(User $user): void + { + $accessTokens = $this->oAuthAccessTokenRepository->findAccessTokens($user->getIdentity()); + foreach ($accessTokens as $accessToken) { + $this->oAuthAccessTokenRepository->revokeAccessToken($accessToken->getToken()); + $this->oAuthRefreshTokenRepository->revokeRefreshToken($accessToken->getToken()); + } + } + + /** + * @throws NotFoundException + */ + public function find(string $id): User + { + $user = $this->userRepository->find($id); + if (! $user instanceof User || $user->isDeleted()) { + throw new NotFoundException(Message::USER_NOT_FOUND); + } + + return $user; + } + + /** + * @throws NotFoundException + */ + public function findByEmail(string $email): User + { + $userDetail = $this->userDetailRepository->findOneBy(['email' => $email]); + if (! $userDetail instanceof UserDetail) { + throw new NotFoundException(Message::USER_NOT_FOUND); + } + + $user = $userDetail->getUser(); + if ($user->isDeleted()) { + throw new NotFoundException(Message::USER_NOT_FOUND); + } + + return $user; + } + + /** + * @throws NotFoundException + */ + public function findByIdentity(string $identity): ?User + { + return $this->findOneBy(['identity' => $identity]); + } + + /** + * @throws NotFoundException + */ + public function findOneBy(array $params): User + { + $user = $this->userRepository->findOneBy($params); + if (! $user instanceof User || $user->isDeleted()) { + throw new NotFoundException(Message::USER_NOT_FOUND); + } + + return $user; + } + + /** + * @throws BadRequestException + * @throws ConflictException + * @throws NotFoundException + */ + public function updateUser(User $user, array $data = []): User + { + if (isset($data['identity'])) { + $user->setIdentity($data['identity']); + } + + if (isset($data['password'])) { + $user->usePassword($data['password']); + } + + if (isset($data['status'])) { + $user->setStatus($data['status']); + } + + if (isset($data['hash'])) { + $user->setHash($data['hash']); + } + + if (isset($data['detail']['firstName'])) { + $user->getDetail()->setFirstname($data['detail']['firstName']); + } + + if (isset($data['detail']['lastName'])) { + $user->getDetail()->setLastName($data['detail']['lastName']); + } + + if (isset($data['detail']['email'])) { + $user->getDetail()->setEmail($data['detail']['email']); + } + + $this->validateUniqueUser($user->getIdentity(), $user->getDetail()->getEmail(), $user->getUuid()); + + if (isset($data['roles']) && is_array($data['roles']) && count($data['roles']) > 0) { + $user->resetRoles(); + foreach ($data['roles'] as $roleData) { + $userRole = $this->userRoleRepository->find($roleData['uuid']); + if (! $userRole instanceof UserRole) { + throw new NotFoundException(Message::ROLE_NOT_FOUND); + } + $user->addRole($userRole); + } + } + + if (! $user->hasRoles()) { + throw (new BadRequestException())->setMessages([Message::RESTRICTION_ROLES]); + } + + return $this->userRepository->saveUser($user); + } +} diff --git a/src/Core/src/User/src/Service/UserServiceInterface.php b/src/Core/src/User/src/Service/UserServiceInterface.php new file mode 100644 index 00000000..affc2f54 --- /dev/null +++ b/src/Core/src/User/src/Service/UserServiceInterface.php @@ -0,0 +1,52 @@ + [ diff --git a/src/Security/src/Middleware/ErrorResponseMiddleware.php b/src/Security/src/Middleware/ErrorResponseMiddleware.php index dac8fca1..0228cd9b 100644 --- a/src/Security/src/Middleware/ErrorResponseMiddleware.php +++ b/src/Security/src/Middleware/ErrorResponseMiddleware.php @@ -18,7 +18,7 @@ class ErrorResponseMiddleware implements MiddlewareInterface { #[Inject( - "config.authentication", + 'config.authentication', )] public function __construct( protected array $config, diff --git a/src/Security/src/RoutesDelegator.php b/src/Security/src/RoutesDelegator.php index fb7e6c08..726a5cf6 100644 --- a/src/Security/src/RoutesDelegator.php +++ b/src/Security/src/RoutesDelegator.php @@ -23,11 +23,8 @@ public function __invoke(ContainerInterface $container, string $serviceName, cal /** @var RouteCollectorInterface $routeCollector */ $routeCollector = $container->get(RouteCollectorInterface::class); - $routeCollector->post( - '/security/token', - [ErrorResponseMiddleware::class, TokenEndpointHandler::class], - 'security::token' - ); + $routeCollector->group('/security', ErrorResponseMiddleware::class) + ->post('/token', TokenEndpointHandler::class, 'security::token'); return $callback(); } diff --git a/src/User/src/ConfigProvider.php b/src/User/src/ConfigProvider.php index 8cb9ba26..fa54fb4b 100644 --- a/src/User/src/ConfigProvider.php +++ b/src/User/src/ConfigProvider.php @@ -33,22 +33,9 @@ use Api\User\Handler\User\PostUserResourceHandler; use Api\User\Handler\User\Role\GetUserRoleCollectionHandler; use Api\User\Handler\User\Role\GetUserRoleResourceHandler; -use Api\User\Service\UserAvatarService; -use Api\User\Service\UserAvatarServiceInterface; -use Api\User\Service\UserRoleService; -use Api\User\Service\UserRoleServiceInterface; -use Api\User\Service\UserService; -use Api\User\Service\UserServiceInterface; use Core\User\Entity\User; use Core\User\Entity\UserAvatar; use Core\User\Entity\UserRole; -use Core\User\EventListener\UserAvatarEventListener; -use Core\User\Repository\UserAvatarRepository; -use Core\User\Repository\UserDetailRepository; -use Core\User\Repository\UserRepository; -use Core\User\Repository\UserResetPasswordRepository; -use Core\User\Repository\UserRoleRepository; -use Dot\DependencyInjection\Factory\AttributedRepositoryFactory; use Dot\DependencyInjection\Factory\AttributedServiceFactory; use Mezzio\Application; use Mezzio\Hal\Metadata\MetadataMap; @@ -64,7 +51,7 @@ public function __invoke(): array ]; } - public function getDependencies(): array + private function getDependencies(): array { return [ 'delegators' => [ @@ -96,10 +83,6 @@ public function getDependencies(): array PostUserResourceHandler::class => [HandlerDelegatorFactory::class], ], 'factories' => [ - UserAvatarEventListener::class => AttributedServiceFactory::class, - UserService::class => AttributedServiceFactory::class, - UserRoleService::class => AttributedServiceFactory::class, - UserAvatarService::class => AttributedServiceFactory::class, DeleteUserAccountAvatarHandler::class => AttributedServiceFactory::class, DeleteUserAccountResourceHandler::class => AttributedServiceFactory::class, DeleteUserAvatarResourceHandler::class => AttributedServiceFactory::class, @@ -125,21 +108,11 @@ public function getDependencies(): array PostUserAccountResourceHandler::class => AttributedServiceFactory::class, PostUserAvatarResourceHandler::class => AttributedServiceFactory::class, PostUserResourceHandler::class => AttributedServiceFactory::class, - UserAvatarRepository::class => AttributedRepositoryFactory::class, - UserDetailRepository::class => AttributedRepositoryFactory::class, - UserRepository::class => AttributedRepositoryFactory::class, - UserResetPasswordRepository::class => AttributedRepositoryFactory::class, - UserRoleRepository::class => AttributedRepositoryFactory::class, - ], - 'aliases' => [ - UserAvatarServiceInterface::class => UserAvatarService::class, - UserRoleServiceInterface::class => UserRoleService::class, - UserServiceInterface::class => UserService::class, ], ]; } - public function getHalConfig(): array + private function getHalConfig(): array { return [ AppConfigProvider::getCollection(UserCollection::class, 'user::list-user', 'users'), @@ -150,7 +123,7 @@ public function getHalConfig(): array ]; } - public function getTemplates(): array + private function getTemplates(): array { return [ 'paths' => [ diff --git a/src/User/src/Handler/Account/Avatar/DeleteUserAccountAvatarHandler.php b/src/User/src/Handler/Account/Avatar/DeleteUserAccountAvatarHandler.php index e9d575db..9bc1ef6e 100644 --- a/src/User/src/Handler/Account/Avatar/DeleteUserAccountAvatarHandler.php +++ b/src/User/src/Handler/Account/Avatar/DeleteUserAccountAvatarHandler.php @@ -4,11 +4,11 @@ namespace Api\User\Handler\Account\Avatar; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; -use Api\User\Service\UserAvatarServiceInterface; +use Core\App\Exception\NotFoundException; use Core\App\Message; use Core\User\Entity\User; +use Core\User\Service\UserAvatarServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -33,7 +33,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface throw new NotFoundException(Message::AVATAR_MISSING); } - $this->userAvatarService->removeAvatar($user); + $this->userAvatarService->deleteAvatar($user); return $this->noContentResponse(); } diff --git a/src/User/src/Handler/Account/Avatar/GetUserAccountAvatarHandler.php b/src/User/src/Handler/Account/Avatar/GetUserAccountAvatarHandler.php index 1d3d5a1d..4770776d 100644 --- a/src/User/src/Handler/Account/Avatar/GetUserAccountAvatarHandler.php +++ b/src/User/src/Handler/Account/Avatar/GetUserAccountAvatarHandler.php @@ -4,11 +4,11 @@ namespace Api\User\Handler\Account\Avatar; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; -use Api\User\Service\UserAvatarServiceInterface; +use Core\App\Exception\NotFoundException; use Core\App\Message; use Core\User\Entity\User; +use Core\User\Service\UserAvatarServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; diff --git a/src/User/src/Handler/Account/Avatar/PostUserAccountAvatarHandler.php b/src/User/src/Handler/Account/Avatar/PostUserAccountAvatarHandler.php index 9e2e87c1..81c55ab0 100644 --- a/src/User/src/Handler/Account/Avatar/PostUserAccountAvatarHandler.php +++ b/src/User/src/Handler/Account/Avatar/PostUserAccountAvatarHandler.php @@ -4,11 +4,11 @@ namespace Api\User\Handler\Account\Avatar; -use Api\App\Exception\BadRequestException; use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\UpdateAvatarInputFilter; -use Api\User\Service\UserAvatarServiceInterface; +use Core\App\Exception\BadRequestException; use Core\User\Entity\User; +use Core\User\Service\UserAvatarServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -35,11 +35,12 @@ public function handle(ServerRequestInterface $request): ResponseInterface throw (new BadRequestException())->setMessages($this->inputFilter->getMessages()); } - $userAvatar = $this->userAvatarService->createAvatar( - $request->getAttribute(User::class), - $this->inputFilter->getValue('avatar') + return $this->createdResponse( + $request, + $this->userAvatarService->createAvatar( + $request->getAttribute(User::class), + $this->inputFilter->getValue('avatar') + ) ); - - return $this->createdResponse($request, $userAvatar); } } diff --git a/src/User/src/Handler/Account/DeleteUserAccountResourceHandler.php b/src/User/src/Handler/Account/DeleteUserAccountResourceHandler.php index 8f2e8b06..1bb61e0f 100644 --- a/src/User/src/Handler/Account/DeleteUserAccountResourceHandler.php +++ b/src/User/src/Handler/Account/DeleteUserAccountResourceHandler.php @@ -5,12 +5,11 @@ namespace Api\User\Handler\Account; use Api\App\Handler\AbstractHandler; -use Api\User\Service\UserServiceInterface; use Core\User\Entity\User; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use RuntimeException; class DeleteUserAccountResourceHandler extends AbstractHandler { @@ -22,9 +21,6 @@ public function __construct( ) { } - /** - * @throws RuntimeException - */ public function handle(ServerRequestInterface $request): ResponseInterface { $this->userService->deleteUser($request->getAttribute(User::class)); diff --git a/src/User/src/Handler/Account/GetUserAccountResourceHandler.php b/src/User/src/Handler/Account/GetUserAccountResourceHandler.php index 6f547bdd..2a6eb96b 100644 --- a/src/User/src/Handler/Account/GetUserAccountResourceHandler.php +++ b/src/User/src/Handler/Account/GetUserAccountResourceHandler.php @@ -5,8 +5,8 @@ namespace Api\User\Handler\Account; use Api\App\Handler\AbstractHandler; -use Api\User\Service\UserServiceInterface; use Core\User\Entity\User; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; diff --git a/src/User/src/Handler/Account/PatchUserAccountActivateHandler.php b/src/User/src/Handler/Account/PatchUserAccountActivateHandler.php index 7f13d826..b68417bf 100644 --- a/src/User/src/Handler/Account/PatchUserAccountActivateHandler.php +++ b/src/User/src/Handler/Account/PatchUserAccountActivateHandler.php @@ -4,11 +4,11 @@ namespace Api\User\Handler\Account; -use Api\App\Exception\ConflictException; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\ConflictException; +use Core\App\Exception\NotFoundException; use Core\App\Message; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; diff --git a/src/User/src/Handler/Account/PatchUserAccountResourceHandler.php b/src/User/src/Handler/Account/PatchUserAccountResourceHandler.php index 8c5ddea2..17db99ef 100644 --- a/src/User/src/Handler/Account/PatchUserAccountResourceHandler.php +++ b/src/User/src/Handler/Account/PatchUserAccountResourceHandler.php @@ -4,13 +4,13 @@ namespace Api\User\Handler\Account; -use Api\App\Exception\BadRequestException; -use Api\App\Exception\ConflictException; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\UpdateUserInputFilter; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\BadRequestException; +use Core\App\Exception\ConflictException; +use Core\App\Exception\NotFoundException; use Core\User\Entity\User; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; diff --git a/src/User/src/Handler/Account/PostUserAccountActivateHandler.php b/src/User/src/Handler/Account/PostUserAccountActivateHandler.php index 53d400a8..66f30aa1 100644 --- a/src/User/src/Handler/Account/PostUserAccountActivateHandler.php +++ b/src/User/src/Handler/Account/PostUserAccountActivateHandler.php @@ -4,13 +4,14 @@ namespace Api\User\Handler\Account; -use Api\App\Exception\BadRequestException; -use Api\App\Exception\ConflictException; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\ActivateAccountInputFilter; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\BadRequestException; +use Core\App\Exception\ConflictException; +use Core\App\Exception\NotFoundException; use Core\App\Message; +use Core\App\Service\MailService; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Dot\Mail\Exception\MailException; use Fig\Http\Message\StatusCodeInterface; @@ -22,10 +23,12 @@ class PostUserAccountActivateHandler extends AbstractHandler { #[Inject( + MailService::class, UserServiceInterface::class, ActivateAccountInputFilter::class, )] public function __construct( + protected MailService $mailService, protected UserServiceInterface $userService, protected ActivateAccountInputFilter $inputFilter, ) { @@ -50,7 +53,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface } $this->userService->activateUser($user); - $this->userService->sendActivationMail($user); + $this->mailService->sendActivationMail($user); return $this->infoResponse( sprintf(Message::MAIL_SENT_USER_ACTIVATION, $user->getDetail()->getEmail()), diff --git a/src/User/src/Handler/Account/PostUserAccountRecoverHandler.php b/src/User/src/Handler/Account/PostUserAccountRecoverHandler.php index 27b443ba..7edac54e 100644 --- a/src/User/src/Handler/Account/PostUserAccountRecoverHandler.php +++ b/src/User/src/Handler/Account/PostUserAccountRecoverHandler.php @@ -4,12 +4,13 @@ namespace Api\User\Handler\Account; -use Api\App\Exception\BadRequestException; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\RecoverIdentityInputFilter; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\BadRequestException; +use Core\App\Exception\NotFoundException; use Core\App\Message; +use Core\App\Service\MailService; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Dot\Mail\Exception\MailException; use Psr\Http\Message\ResponseInterface; @@ -18,10 +19,12 @@ class PostUserAccountRecoverHandler extends AbstractHandler { #[Inject( + MailService::class, UserServiceInterface::class, RecoverIdentityInputFilter::class, )] public function __construct( + protected MailService $mailService, protected UserServiceInterface $userService, protected RecoverIdentityInputFilter $inputFilter, ) { @@ -40,7 +43,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface } $user = $this->userService->findByEmail($this->inputFilter->getValue('email')); - $this->userService->sendRecoverIdentityMail($user); + $this->mailService->sendRecoverIdentityMail($user); return $this->infoResponse(Message::MAIL_SENT_RECOVER_IDENTITY); } diff --git a/src/User/src/Handler/Account/PostUserAccountResourceHandler.php b/src/User/src/Handler/Account/PostUserAccountResourceHandler.php index 4e393344..e973730c 100644 --- a/src/User/src/Handler/Account/PostUserAccountResourceHandler.php +++ b/src/User/src/Handler/Account/PostUserAccountResourceHandler.php @@ -4,12 +4,13 @@ namespace Api\User\Handler\Account; -use Api\App\Exception\BadRequestException; -use Api\App\Exception\ConflictException; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\CreateUserInputFilter; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\BadRequestException; +use Core\App\Exception\ConflictException; +use Core\App\Exception\NotFoundException; +use Core\App\Service\MailService; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Dot\Mail\Exception\MailException; use Psr\Http\Message\ResponseInterface; @@ -18,10 +19,12 @@ class PostUserAccountResourceHandler extends AbstractHandler { #[Inject( + MailService::class, UserServiceInterface::class, CreateUserInputFilter::class, )] public function __construct( + protected MailService $mailService, protected UserServiceInterface $userService, protected CreateUserInputFilter $inputFilter, ) { @@ -43,7 +46,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface } $user = $this->userService->createUser((array) $this->inputFilter->getValues()); - $this->userService->sendActivationMail($user); + $this->mailService->sendActivationMail($user); return $this->createdResponse($request, $user); } diff --git a/src/User/src/Handler/Account/ResetPassword/GetUserAccountResetPasswordHandler.php b/src/User/src/Handler/Account/ResetPassword/GetUserAccountResetPasswordHandler.php index 4af88caf..467a4d7b 100644 --- a/src/User/src/Handler/Account/ResetPassword/GetUserAccountResetPasswordHandler.php +++ b/src/User/src/Handler/Account/ResetPassword/GetUserAccountResetPasswordHandler.php @@ -4,11 +4,11 @@ namespace Api\User\Handler\Account\ResetPassword; -use Api\App\Exception\ExpiredException; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\ExpiredException; +use Core\App\Exception\NotFoundException; use Core\App\Message; +use Core\User\Service\UserResetPasswordServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -18,10 +18,10 @@ class GetUserAccountResetPasswordHandler extends AbstractHandler { #[Inject( - UserServiceInterface::class, + UserResetPasswordServiceInterface::class, )] public function __construct( - protected UserServiceInterface $userService, + protected UserResetPasswordServiceInterface $userResetPasswordService, ) { } @@ -33,7 +33,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface { $hash = $request->getAttribute('hash'); - $userResetPassword = $this->userService->findResetPasswordByHash($hash); + $userResetPassword = $this->userResetPasswordService->findOneBy(['hash' => $hash]); if (! $userResetPassword->isValid()) { throw new ExpiredException(sprintf(Message::RESET_PASSWORD_EXPIRED, $hash)); } diff --git a/src/User/src/Handler/Account/ResetPassword/PatchUserAccountResetPasswordHandler.php b/src/User/src/Handler/Account/ResetPassword/PatchUserAccountResetPasswordHandler.php index 7fd9454d..0d0b022d 100644 --- a/src/User/src/Handler/Account/ResetPassword/PatchUserAccountResetPasswordHandler.php +++ b/src/User/src/Handler/Account/ResetPassword/PatchUserAccountResetPasswordHandler.php @@ -4,14 +4,16 @@ namespace Api\User\Handler\Account\ResetPassword; -use Api\App\Exception\BadRequestException; -use Api\App\Exception\ConflictException; -use Api\App\Exception\ExpiredException; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\UpdatePasswordInputFilter; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\BadRequestException; +use Core\App\Exception\ConflictException; +use Core\App\Exception\ExpiredException; +use Core\App\Exception\NotFoundException; use Core\App\Message; +use Core\App\Service\MailService; +use Core\User\Service\UserResetPasswordServiceInterface; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Dot\Mail\Exception\MailException; use Psr\Http\Message\ResponseInterface; @@ -22,11 +24,15 @@ class PatchUserAccountResetPasswordHandler extends AbstractHandler { #[Inject( + MailService::class, UserServiceInterface::class, + UserResetPasswordServiceInterface::class, UpdatePasswordInputFilter::class, )] public function __construct( + protected MailService $mailService, protected UserServiceInterface $userService, + protected UserResetPasswordServiceInterface $userResetPasswordService, protected UpdatePasswordInputFilter $inputFilter, ) { } @@ -40,9 +46,14 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { + $this->inputFilter->setData((array) $request->getParsedBody()); + if (! $this->inputFilter->isValid()) { + throw (new BadRequestException())->setMessages($this->inputFilter->getMessages()); + } + $hash = $request->getAttribute('hash'); - $userResetPassword = $this->userService->findResetPasswordByHash($hash); + $userResetPassword = $this->userResetPasswordService->findOneBy(['hash' => $hash]); if (! $userResetPassword->isValid()) { throw new ExpiredException(sprintf(Message::RESET_PASSWORD_EXPIRED, $hash)); } @@ -50,17 +61,12 @@ public function handle(ServerRequestInterface $request): ResponseInterface throw new ConflictException(sprintf(Message::RESET_PASSWORD_USED, $hash)); } - $this->inputFilter->setData((array) $request->getParsedBody()); - if (! $this->inputFilter->isValid()) { - throw (new BadRequestException())->setMessages($this->inputFilter->getMessages()); - } - $this->userService->updateUser( $userResetPassword->markAsCompleted()->getUser(), (array) $this->inputFilter->getValues() ); - $this->userService->sendResetPasswordCompletedMail($userResetPassword->getUser()); + $this->mailService->sendResetPasswordCompletedMail($userResetPassword->getUser()); return $this->infoResponse(Message::RESET_PASSWORD_OK); } diff --git a/src/User/src/Handler/Account/ResetPassword/PostUserAccountResetPasswordHandler.php b/src/User/src/Handler/Account/ResetPassword/PostUserAccountResetPasswordHandler.php index 5d3b54d9..68b47829 100644 --- a/src/User/src/Handler/Account/ResetPassword/PostUserAccountResetPasswordHandler.php +++ b/src/User/src/Handler/Account/ResetPassword/PostUserAccountResetPasswordHandler.php @@ -4,14 +4,14 @@ namespace Api\User\Handler\Account\ResetPassword; -use Api\App\Exception\BadRequestException; -use Api\App\Exception\ConflictException; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\ResetPasswordInputFilter; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\BadRequestException; +use Core\App\Exception\ConflictException; +use Core\App\Exception\NotFoundException; use Core\App\Message; -use Core\User\Entity\User; +use Core\App\Service\MailService; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Dot\Mail\Exception\MailException; use Fig\Http\Message\StatusCodeInterface; @@ -21,10 +21,12 @@ class PostUserAccountResetPasswordHandler extends AbstractHandler { #[Inject( + MailService::class, UserServiceInterface::class, ResetPasswordInputFilter::class, )] public function __construct( + protected MailService $mailService, protected UserServiceInterface $userService, protected ResetPasswordInputFilter $inputFilter, ) { @@ -48,15 +50,11 @@ public function handle(ServerRequestInterface $request): ResponseInterface } elseif (! empty($this->inputFilter->getValue('identity'))) { $user = $this->userService->findByIdentity($this->inputFilter->getValue('identity')); } else { - $user = null; - } - - if (! $user instanceof User) { throw new NotFoundException(Message::USER_NOT_FOUND); } $this->userService->updateUser($user->createResetPassword()); - $this->userService->sendResetPasswordRequestedMail($user); + $this->mailService->sendResetPasswordRequestedMail($user); return $this->infoResponse(Message::MAIL_SENT_RESET_PASSWORD, StatusCodeInterface::STATUS_CREATED); } diff --git a/src/User/src/Handler/User/Avatar/DeleteUserAvatarResourceHandler.php b/src/User/src/Handler/User/Avatar/DeleteUserAvatarResourceHandler.php index c85e03f3..758ab30a 100644 --- a/src/User/src/Handler/User/Avatar/DeleteUserAvatarResourceHandler.php +++ b/src/User/src/Handler/User/Avatar/DeleteUserAvatarResourceHandler.php @@ -4,11 +4,12 @@ namespace Api\User\Handler\User\Avatar; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; -use Api\User\Service\UserAvatarServiceInterface; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\NotFoundException; use Core\App\Message; +use Core\User\Entity\User; +use Core\User\Service\UserAvatarServiceInterface; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -30,12 +31,15 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - $user = $this->userService->findOneBy(['uuid' => $request->getAttribute('uuid')]); + $user = $this->userService->getUserRepository()->find($request->getAttribute('uuid')); + if (! $user instanceof User) { + throw new NotFoundException(Message::USER_NOT_FOUND); + } if (! $user->hasAvatar()) { throw new NotFoundException(Message::AVATAR_MISSING); } - $this->userAvatarService->removeAvatar($user); + $this->userAvatarService->deleteAvatar($user); return $this->noContentResponse(); } diff --git a/src/User/src/Handler/User/Avatar/GetUserAvatarResourceHandler.php b/src/User/src/Handler/User/Avatar/GetUserAvatarResourceHandler.php index f53fe8ce..37eddbe1 100644 --- a/src/User/src/Handler/User/Avatar/GetUserAvatarResourceHandler.php +++ b/src/User/src/Handler/User/Avatar/GetUserAvatarResourceHandler.php @@ -4,10 +4,11 @@ namespace Api\User\Handler\User\Avatar; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\NotFoundException; use Core\App\Message; +use Core\User\Entity\User; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -27,7 +28,10 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - $user = $this->userService->findOneBy(['uuid' => $request->getAttribute('uuid')]); + $user = $this->userService->getUserRepository()->find($request->getAttribute('uuid')); + if (! $user instanceof User) { + throw new NotFoundException(Message::USER_NOT_FOUND); + } if (! $user->hasAvatar()) { throw new NotFoundException(Message::AVATAR_MISSING); } diff --git a/src/User/src/Handler/User/Avatar/PostUserAvatarResourceHandler.php b/src/User/src/Handler/User/Avatar/PostUserAvatarResourceHandler.php index cf771dbd..348e2268 100644 --- a/src/User/src/Handler/User/Avatar/PostUserAvatarResourceHandler.php +++ b/src/User/src/Handler/User/Avatar/PostUserAvatarResourceHandler.php @@ -4,12 +4,14 @@ namespace Api\User\Handler\User\Avatar; -use Api\App\Exception\BadRequestException; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\UpdateAvatarInputFilter; -use Api\User\Service\UserAvatarServiceInterface; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\BadRequestException; +use Core\App\Exception\NotFoundException; +use Core\App\Message; +use Core\User\Entity\User; +use Core\User\Service\UserAvatarServiceInterface; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -39,9 +41,14 @@ public function handle(ServerRequestInterface $request): ResponseInterface throw (new BadRequestException())->setMessages($this->inputFilter->getMessages()); } - $user = $this->userService->findOneBy(['uuid' => $request->getAttribute('uuid')]); - $userAvatar = $this->userAvatarService->createAvatar($user, $this->inputFilter->getValue('avatar')); + $user = $this->userService->getUserRepository()->find($request->getAttribute('uuid')); + if (! $user instanceof User) { + throw new NotFoundException(Message::USER_NOT_FOUND); + } - return $this->createdResponse($request, $userAvatar); + return $this->createdResponse( + $request, + $this->userAvatarService->createAvatar($user, $this->inputFilter->getValue('avatar')) + ); } } diff --git a/src/User/src/Handler/User/DeleteUserResourceHandler.php b/src/User/src/Handler/User/DeleteUserResourceHandler.php index 324b34a2..c952e7d1 100644 --- a/src/User/src/Handler/User/DeleteUserResourceHandler.php +++ b/src/User/src/Handler/User/DeleteUserResourceHandler.php @@ -4,13 +4,13 @@ namespace Api\User\Handler\User; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\NotFoundException; +use Core\App\Exception\RuntimeException; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use RuntimeException; class DeleteUserResourceHandler extends AbstractHandler { @@ -28,9 +28,9 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - $user = $this->userService->findOneBy(['uuid' => $request->getAttribute('uuid')]); - - $this->userService->deleteUser($user); + $this->userService->deleteUser( + $this->userService->find($request->getAttribute('uuid')) + ); return $this->noContentResponse(); } diff --git a/src/User/src/Handler/User/GetUserCollectionHandler.php b/src/User/src/Handler/User/GetUserCollectionHandler.php index 46fdc395..4a821874 100644 --- a/src/User/src/Handler/User/GetUserCollectionHandler.php +++ b/src/User/src/Handler/User/GetUserCollectionHandler.php @@ -4,9 +4,10 @@ namespace Api\User\Handler\User; -use Api\App\Exception\BadRequestException; use Api\App\Handler\AbstractHandler; -use Api\User\Service\UserServiceInterface; +use Api\User\Collection\UserCollection; +use Core\App\Exception\BadRequestException; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -26,6 +27,9 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - return $this->createResponse($request, $this->userService->getUsers($request->getQueryParams())); + return $this->createResponse( + $request, + new UserCollection($this->userService->getUserRepository()->getUsers($request->getQueryParams())) + ); } } diff --git a/src/User/src/Handler/User/GetUserResourceHandler.php b/src/User/src/Handler/User/GetUserResourceHandler.php index 8cab1dc8..30bde030 100644 --- a/src/User/src/Handler/User/GetUserResourceHandler.php +++ b/src/User/src/Handler/User/GetUserResourceHandler.php @@ -4,9 +4,9 @@ namespace Api\User\Handler\User; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\NotFoundException; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -26,8 +26,9 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - $user = $this->userService->findOneBy(['uuid' => $request->getAttribute('uuid')]); - - return $this->createResponse($request, $user); + return $this->createResponse( + $request, + $this->userService->find($request->getAttribute('uuid')) + ); } } diff --git a/src/User/src/Handler/User/PatchUserActivateHandler.php b/src/User/src/Handler/User/PatchUserActivateHandler.php index e92b68cc..8e10239f 100644 --- a/src/User/src/Handler/User/PatchUserActivateHandler.php +++ b/src/User/src/Handler/User/PatchUserActivateHandler.php @@ -4,11 +4,12 @@ namespace Api\User\Handler\User; -use Api\App\Exception\ConflictException; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\ConflictException; +use Core\App\Exception\NotFoundException; use Core\App\Message; +use Core\App\Service\MailService; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Dot\Mail\Exception\MailException; use Psr\Http\Message\ResponseInterface; @@ -17,9 +18,11 @@ class PatchUserActivateHandler extends AbstractHandler { #[Inject( + MailService::class, UserServiceInterface::class, )] public function __construct( + protected MailService $mailService, protected UserServiceInterface $userService, ) { } @@ -31,13 +34,13 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - $user = $this->userService->findOneBy(['uuid' => $request->getAttribute('uuid')]); + $user = $this->userService->find($request->getAttribute('uuid')); if ($user->isActive()) { throw new ConflictException(Message::USER_ALREADY_ACTIVATED); } $this->userService->activateUser($user); - $this->userService->sendActivationMail($user); + $this->mailService->sendActivationMail($user); return $this->infoResponse(Message::USER_ACTIVATED); } diff --git a/src/User/src/Handler/User/PatchUserDeactivateHandler.php b/src/User/src/Handler/User/PatchUserDeactivateHandler.php index 9aa37756..e309ee0b 100644 --- a/src/User/src/Handler/User/PatchUserDeactivateHandler.php +++ b/src/User/src/Handler/User/PatchUserDeactivateHandler.php @@ -4,11 +4,11 @@ namespace Api\User\Handler\User; -use Api\App\Exception\ConflictException; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\ConflictException; +use Core\App\Exception\NotFoundException; use Core\App\Message; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -29,7 +29,7 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - $user = $this->userService->findOneBy(['uuid' => $request->getAttribute('uuid')]); + $user = $this->userService->find($request->getAttribute('uuid')); if ($user->isPending()) { throw new ConflictException(Message::USER_ALREADY_DEACTIVATED); } diff --git a/src/User/src/Handler/User/PatchUserResourceHandler.php b/src/User/src/Handler/User/PatchUserResourceHandler.php index fe9159dc..bd535b70 100644 --- a/src/User/src/Handler/User/PatchUserResourceHandler.php +++ b/src/User/src/Handler/User/PatchUserResourceHandler.php @@ -4,12 +4,12 @@ namespace Api\User\Handler\User; -use Api\App\Exception\BadRequestException; -use Api\App\Exception\ConflictException; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\UpdateUserInputFilter; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\BadRequestException; +use Core\App\Exception\ConflictException; +use Core\App\Exception\NotFoundException; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -38,7 +38,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface throw (new BadRequestException())->setMessages($this->inputFilter->getMessages()); } - $user = $this->userService->findOneBy(['uuid' => $request->getAttribute('uuid')]); + $user = $this->userService->find($request->getAttribute('uuid')); $this->userService->updateUser($user, (array) $this->inputFilter->getValues()); return $this->createResponse($request, $user); diff --git a/src/User/src/Handler/User/PostUserResourceHandler.php b/src/User/src/Handler/User/PostUserResourceHandler.php index 7ae13343..6f52d311 100644 --- a/src/User/src/Handler/User/PostUserResourceHandler.php +++ b/src/User/src/Handler/User/PostUserResourceHandler.php @@ -4,12 +4,13 @@ namespace Api\User\Handler\User; -use Api\App\Exception\BadRequestException; -use Api\App\Exception\ConflictException; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\CreateUserInputFilter; -use Api\User\Service\UserServiceInterface; +use Core\App\Exception\BadRequestException; +use Core\App\Exception\ConflictException; +use Core\App\Exception\NotFoundException; +use Core\App\Service\MailService; +use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Dot\Mail\Exception\MailException; use Psr\Http\Message\ResponseInterface; @@ -18,10 +19,12 @@ class PostUserResourceHandler extends AbstractHandler { #[Inject( + MailService::class, UserServiceInterface::class, CreateUserInputFilter::class, )] public function __construct( + protected MailService $mailService, protected UserServiceInterface $userService, protected CreateUserInputFilter $inputFilter, ) { @@ -42,9 +45,9 @@ public function handle(ServerRequestInterface $request): ResponseInterface $user = $this->userService->createUser((array) $this->inputFilter->getValues()); if ($user->isPending()) { - $this->userService->sendActivationMail($user); + $this->mailService->sendActivationMail($user); } elseif ($user->isActive()) { - $this->userService->sendWelcomeMail($user); + $this->mailService->sendWelcomeMail($user); } return $this->createdResponse($request, $user); diff --git a/src/User/src/Handler/User/Role/GetUserRoleCollectionHandler.php b/src/User/src/Handler/User/Role/GetUserRoleCollectionHandler.php index 605e133c..805a8456 100644 --- a/src/User/src/Handler/User/Role/GetUserRoleCollectionHandler.php +++ b/src/User/src/Handler/User/Role/GetUserRoleCollectionHandler.php @@ -4,9 +4,10 @@ namespace Api\User\Handler\User\Role; -use Api\App\Exception\BadRequestException; use Api\App\Handler\AbstractHandler; -use Api\User\Service\UserRoleServiceInterface; +use Api\User\Collection\UserRoleCollection; +use Core\App\Exception\BadRequestException; +use Core\User\Service\UserRoleServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -17,7 +18,7 @@ class GetUserRoleCollectionHandler extends AbstractHandler UserRoleServiceInterface::class, )] public function __construct( - protected UserRoleServiceInterface $roleService, + protected UserRoleServiceInterface $userRoleService, ) { } @@ -26,6 +27,11 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - return $this->createResponse($request, $this->roleService->getRoles($request->getQueryParams())); + return $this->createResponse( + $request, + new UserRoleCollection( + $this->userRoleService->getUserRoleRepository()->getRoles($request->getQueryParams()) + ) + ); } } diff --git a/src/User/src/Handler/User/Role/GetUserRoleResourceHandler.php b/src/User/src/Handler/User/Role/GetUserRoleResourceHandler.php index 1756fd94..b9bdd27c 100644 --- a/src/User/src/Handler/User/Role/GetUserRoleResourceHandler.php +++ b/src/User/src/Handler/User/Role/GetUserRoleResourceHandler.php @@ -4,9 +4,9 @@ namespace Api\User\Handler\User\Role; -use Api\App\Exception\NotFoundException; use Api\App\Handler\AbstractHandler; -use Api\User\Service\UserRoleServiceInterface; +use Core\App\Exception\NotFoundException; +use Core\User\Service\UserRoleServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -17,7 +17,7 @@ class GetUserRoleResourceHandler extends AbstractHandler UserRoleServiceInterface::class, )] public function __construct( - protected UserRoleServiceInterface $roleService, + protected UserRoleServiceInterface $userRoleService, ) { } @@ -26,8 +26,9 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - $role = $this->roleService->findOneBy(['uuid' => $request->getAttribute('uuid')]); - - return $this->createResponse($request, $role); + return $this->createResponse( + $request, + $this->userRoleService->find($request->getAttribute('uuid')) + ); } } diff --git a/src/User/src/Service/UserRoleService.php b/src/User/src/Service/UserRoleService.php deleted file mode 100644 index 44048938..00000000 --- a/src/User/src/Service/UserRoleService.php +++ /dev/null @@ -1,62 +0,0 @@ -roleRepository->findOneBy($params); - if (! $role instanceof UserRole) { - throw new NotFoundException(Message::ROLE_NOT_FOUND); - } - - return $role; - } - - /** - * @throws BadRequestException - */ - public function getRoles(array $params = []): UserRoleCollection - { - $values = [ - 'role.name', - 'role.created', - 'role.updated', - ]; - - $params['order'] = $params['order'] ?? 'role.created'; - if (! in_array($params['order'], $values)) { - throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'order')]); - } - - $qb = $this->roleRepository->getRoles($params); - $qb->getQuery()->useQueryCache(true); - - return new UserRoleCollection($qb, false); - } -} diff --git a/src/User/src/Service/UserRoleServiceInterface.php b/src/User/src/Service/UserRoleServiceInterface.php deleted file mode 100644 index 76ab09ce..00000000 --- a/src/User/src/Service/UserRoleServiceInterface.php +++ /dev/null @@ -1,23 +0,0 @@ -userRepository->saveUser($user->activate()); - } - - public function deactivateUser(User $user): User - { - return $this->userRepository->saveUser($user->deactivate()); - } - - /** - * @throws ConflictException - * @throws NotFoundException - */ - public function createUser(array $data = []): User - { - if ($this->exists($data['identity'])) { - throw new ConflictException(Message::DUPLICATE_IDENTITY); - } - - if ($this->emailExists($data['detail']['email'])) { - throw new ConflictException(Message::DUPLICATE_EMAIL); - } - - $detail = (new UserDetail()) - ->setFirstName($data['detail']['firstName'] ?? null) - ->setLastName($data['detail']['lastName'] ?? null) - ->setEmail($data['detail']['email']); - - $user = (new User()) - ->setDetail($detail) - ->setIdentity($data['identity']) - ->usePassword($data['password']) - ->setStatus($data['status'] ?? UserStatusEnum::Pending); - $detail->setUser($user); - - if (! empty($data['roles'])) { - foreach ($data['roles'] as $roleData) { - $user->addRole( - $this->userRoleService->findOneBy(['uuid' => $roleData['uuid']]) - ); - } - } - - return $this->userRepository->saveUser($user); - } - - public function revokeTokens(User $user): void - { - $accessTokens = $this->oAuthAccessTokenRepository->findAccessTokens($user->getIdentity()); - foreach ($accessTokens as $accessToken) { - $this->oAuthAccessTokenRepository->revokeAccessToken($accessToken->getToken()); - $this->oAuthRefreshTokenRepository->revokeRefreshToken($accessToken->getToken()); - } - } - - /** - * @throws RuntimeException - */ - public function deleteUser(User $user): User - { - $this->revokeTokens($user); - - return $this->anonymizeUser($user->setStatus(UserStatusEnum::Deleted)); - } - - /** - * @throws RuntimeException - */ - public function anonymizeUser(User $user): User - { - $placeholder = $this->getAnonymousPlaceholder(); - - $user - ->setIdentity($placeholder . $this->config['userAnonymizeAppend']) - ->getDetail() - ->setFirstName($placeholder) - ->setLastName($placeholder) - ->setEmail($placeholder); - - return $this->userRepository->saveUser($user); - } - - public function exists(string $identity = ''): bool - { - try { - $this->findOneBy(['identity' => $identity]); - - return true; - } catch (NotFoundException) { - return false; - } - } - - public function existsOther(string $identity = '', string $uuid = ''): bool - { - try { - $user = $this->findOneBy(['identity' => $identity]); - - return $user->getUuid()->toString() !== $uuid; - } catch (NotFoundException) { - return false; - } - } - - public function emailExists(string $email = ''): bool - { - try { - $this->findByEmail($email); - - return true; - } catch (NotFoundException) { - return false; - } - } - - public function emailExistsOther(string $email = '', string $uuid = ''): bool - { - try { - $user = $this->findByEmail($email); - - return $user->getUuid()->toString() !== $uuid; - } catch (NotFoundException) { - return false; - } - } - - /** - * @throws NotFoundException - */ - public function findResetPasswordByHash(?string $hash): UserResetPassword - { - $userResetPassword = $this->userResetPasswordRepository->findOneBy(['hash' => $hash]); - if (! $userResetPassword instanceof UserResetPassword) { - throw new NotFoundException(sprintf(Message::RESET_PASSWORD_NOT_FOUND, (string) $hash)); - } - - return $userResetPassword; - } - - /** - * @throws NotFoundException - */ - public function findByEmail(string $email): User - { - $user = $this->userDetailRepository->findOneBy(['email' => $email])?->getUser(); - if (! $user instanceof User || $user->isDeleted()) { - throw new NotFoundException(Message::USER_NOT_FOUND); - } - - return $user; - } - - /** - * @throws NotFoundException - */ - public function findByIdentity(string $identity): ?User - { - return $this->findOneBy(['identity' => $identity]); - } - - /** - * @throws NotFoundException - */ - public function findOneBy(array $params = []): User - { - $user = $this->userRepository->findOneBy($params); - if (! $user instanceof User || $user->isDeleted()) { - throw new NotFoundException(Message::USER_NOT_FOUND); - } - - return $user; - } - - /** - * @throws BadRequestException - */ - public function getUsers(array $params = []): UserCollection - { - $values = [ - 'user.identity', - 'user.status', - 'user.created', - 'user.updated', - ]; - - $params['order'] = $params['order'] ?? 'user.created'; - if (! in_array($params['order'], $values)) { - throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'order')]); - } - - $qb = $this->userRepository->getUsers($params); - $qb->getQuery()->useQueryCache(true); - - return new UserCollection($qb, false); - } - - /** - * @throws MailException - */ - public function sendActivationMail(User $user): bool - { - if ($user->isActive()) { - return false; - } - - $this->mailService->getMessage()->addTo($user->getDetail()->getEmail(), $user->getName()); - $this->mailService->setSubject('Welcome to ' . $this->config['application']['name']); - $this->mailService->setBody( - $this->templateRenderer->render('user::activate', [ - 'config' => $this->config, - 'user' => $user, - ]) - ); - - try { - return $this->mailService->send()->isValid(); - } catch (MailException | TransportExceptionInterface $exception) { - $this->logger->err($exception->getMessage()); - throw new MailException(sprintf(Message::MAIL_NOT_SENT_TO, $user->getDetail()->getEmail())); - } - } - - /** - * @throws MailException - */ - public function sendResetPasswordRequestedMail(User $user): bool - { - $this->mailService->getMessage()->addTo($user->getDetail()->getEmail(), $user->getName()); - $this->mailService->setSubject( - 'Reset password instructions for your ' . $this->config['application']['name'] . ' account' - ); - $this->mailService->setBody( - $this->templateRenderer->render('user::reset-password-requested', [ - 'config' => $this->config, - 'user' => $user, - ]) - ); - - try { - return $this->mailService->send()->isValid(); - } catch (MailException | TransportExceptionInterface $exception) { - $this->logger->err($exception->getMessage()); - throw new MailException(sprintf(Message::MAIL_NOT_SENT_TO, $user->getDetail()->getEmail())); - } - } - - /** - * @throws MailException - */ - public function sendResetPasswordCompletedMail(User $user): bool - { - $this->mailService->getMessage()->addTo($user->getDetail()->getEmail(), $user->getName()); - $this->mailService->setSubject( - 'You have successfully reset the password for your ' . $this->config['application']['name'] . ' account' - ); - $this->mailService->setBody( - $this->templateRenderer->render('user::reset-password-completed', [ - 'config' => $this->config, - 'user' => $user, - ]) - ); - - try { - return $this->mailService->send()->isValid(); - } catch (MailException | TransportExceptionInterface $exception) { - $this->logger->err($exception->getMessage()); - throw new MailException(sprintf(Message::MAIL_NOT_SENT_TO, $user->getDetail()->getEmail())); - } - } - - /** - * @throws MailException - */ - public function sendWelcomeMail(User $user): bool - { - $this->mailService->getMessage()->addTo($user->getDetail()->getEmail(), $user->getName()); - $this->mailService->setSubject('Welcome to ' . $this->config['application']['name']); - $this->mailService->setBody( - $this->templateRenderer->render('user::welcome', [ - 'config' => $this->config, - 'user' => $user, - ]) - ); - - try { - return $this->mailService->send()->isValid(); - } catch (MailException | TransportExceptionInterface $exception) { - $this->logger->err($exception->getMessage()); - throw new MailException(sprintf(Message::MAIL_NOT_SENT_TO, $user->getDetail()->getEmail())); - } - } - - /** - * @throws BadRequestException - * @throws ConflictException - * @throws NotFoundException - */ - public function updateUser(User $user, array $data = []): User - { - if (isset($data['identity'])) { - if ($this->existsOther($data['identity'], $user->getUuid()->toString())) { - throw new ConflictException(Message::DUPLICATE_IDENTITY); - } - $user->setIdentity($data['identity']); - } - - if (isset($data['detail']['email'])) { - if ($this->emailExistsOther($data['detail']['email'], $user->getUuid()->toString())) { - throw new ConflictException(Message::DUPLICATE_EMAIL); - } - } - - if (isset($data['password'])) { - $user->usePassword($data['password']); - } - - if (isset($data['status'])) { - $user->setStatus($data['status']); - } - - if (isset($data['hash'])) { - $user->setHash($data['hash']); - } - - if (isset($data['detail']['firstName'])) { - $user->getDetail()->setFirstname($data['detail']['firstName']); - } - - if (isset($data['detail']['lastName'])) { - $user->getDetail()->setLastName($data['detail']['lastName']); - } - - if (isset($data['detail']['email'])) { - if (! $this->emailExists($data['detail']['email'])) { - $user->getDetail()->setEmail($data['detail']['email']); - } - } - - if (! empty($data['roles'])) { - $user->resetRoles(); - foreach ($data['roles'] as $roleData) { - $user->addRole( - $this->userRoleService->findOneBy(['uuid' => $roleData['uuid']]) - ); - } - } - - if (! $user->hasRoles()) { - throw (new BadRequestException())->setMessages([Message::RESTRICTION_ROLES]); - } - - return $this->userRepository->saveUser($user); - } - - /** - * @throws MailException - */ - public function sendRecoverIdentityMail(User $user): bool - { - $this->mailService->getMessage()->addTo($user->getDetail()->getEmail(), $user->getName()); - $this->mailService->setSubject( - 'Recover identity for your ' . $this->config['application']['name'] . ' account' - ); - $this->mailService->setBody( - $this->templateRenderer->render('user::recover-identity-requested', [ - 'config' => $this->config, - 'user' => $user, - ]) - ); - - try { - return $this->mailService->send()->isValid(); - } catch (MailException | TransportExceptionInterface $exception) { - $this->logger->err($exception->getMessage()); - throw new MailException(sprintf(Message::MAIL_NOT_SENT_TO, $user->getDetail()->getEmail())); - } - } - - private function getAnonymousPlaceholder(): string - { - return 'anonymous' . date('dmYHis'); - } -} diff --git a/src/User/src/Service/UserServiceInterface.php b/src/User/src/Service/UserServiceInterface.php deleted file mode 100644 index 596e6333..00000000 --- a/src/User/src/Service/UserServiceInterface.php +++ /dev/null @@ -1,101 +0,0 @@ -assertTrue($deletedUser?->isDeleted()); } - public function testRequestResetPasswordInvalidHash(): void + public function testRequestResetPasswordBadRequest(): void { $response = $this->patch('/user/account/reset-password/invalid_hash'); + $this->assertResponseBadRequest($response); + } + + public function testRequestResetPasswordInvalidHash(): void + { + $response = $this->patch('/user/account/reset-password/invalid_hash', [ + 'password' => 'password', + 'passwordConfirm' => 'password', + ]); $this->assertResponseNotFound($response); } diff --git a/test/Unit/Admin/Service/AdminServiceTest.php b/test/Unit/Admin/Service/AdminServiceTest.php index 303e08ed..6d25f90a 100644 --- a/test/Unit/Admin/Service/AdminServiceTest.php +++ b/test/Unit/Admin/Service/AdminServiceTest.php @@ -4,12 +4,13 @@ namespace ApiTest\Unit\Admin\Service; -use Api\Admin\Service\AdminRoleService; -use Api\Admin\Service\AdminService as Subject; use Core\Admin\Entity\Admin; use Core\Admin\Entity\AdminRole; use Core\Admin\Enum\AdminStatusEnum; use Core\Admin\Repository\AdminRepository; +use Core\Admin\Repository\AdminRoleRepository; +use Core\Admin\Service\AdminService; +use Core\App\Exception\ConflictException; use Core\App\Message; use Exception; use PHPUnit\Framework\MockObject\MockObject; @@ -20,26 +21,18 @@ class AdminServiceTest extends TestCase { - private Subject|MockObject $subject; - private AdminRoleService|MockObject $adminRoleService; + private AdminService $adminService; private AdminRepository|MockObject $adminRepository; + private AdminRoleRepository|MockObject $adminRoleRepository; /** * @throws \PHPUnit\Framework\MockObject\Exception */ public function setUp(): void { - $this->adminRoleService = $this->createMock(AdminRoleService::class); - $this->adminRepository = $this->createMock(AdminRepository::class); - $this->subject = $this->getMockBuilder(Subject::class) - ->setConstructorArgs([ - $this->adminRoleService, - $this->adminRepository, - ]) - ->onlyMethods([ - 'exists', - ]) - ->getMock(); + $this->adminRepository = $this->createMock(AdminRepository::class); + $this->adminRoleRepository = $this->createMock(AdminRoleRepository::class); + $this->adminService = new AdminService($this->adminRepository, $this->adminRoleRepository); } /** @@ -47,12 +40,15 @@ public function setUp(): void */ public function testCreateAdminThrowsDuplicateIdentity(): void { - $this->expectException(Exception::class); + $this->expectException(ConflictException::class); $this->expectExceptionMessage(Message::DUPLICATE_IDENTITY); - $this->subject->method('exists')->willReturn(true); + $request = $this->getAdmin(); + $this->adminRepository + ->method('findOneBy') + ->willReturn($this->getAdminEntity(['identity' => $request['identity']])); - $this->subject->createAdmin(['identity' => 'admin@dotkernel.com']); + $this->adminService->createAdmin($request); } /** @@ -71,12 +67,12 @@ public function testCreateAdminSuperAdminRole(): void $role = (new AdminRole())->setName(AdminRole::ROLE_SUPERUSER); - $this->adminRoleService->method('findOneBy')->willReturn($role); + $this->adminRoleRepository->method('find')->willReturn($role); $this->adminRepository->method('saveAdmin')->willReturn( $this->getAdminEntity($data) ); - $admin = $this->subject->createAdmin($data); + $admin = $this->adminService->createAdmin($data); $this->assertSame($data['identity'], $admin->getIdentity()); $this->assertTrue(Admin::verifyPassword($data['password'], $admin->getPassword())); diff --git a/test/Unit/App/Attribute/MethodDeprecationTest.php b/test/Unit/App/Attribute/MethodDeprecationTest.php index c7f08cd2..7050b3e2 100644 --- a/test/Unit/App/Attribute/MethodDeprecationTest.php +++ b/test/Unit/App/Attribute/MethodDeprecationTest.php @@ -5,8 +5,8 @@ namespace ApiTest\Unit\App\Attribute; use Api\App\Attribute\MethodDeprecation; -use Api\App\Exception\DeprecationSunsetException; use Api\App\Middleware\DeprecationMiddleware; +use Core\App\Exception\DeprecationSunsetException; use PHPUnit\Framework\TestCase; use ReflectionClass; diff --git a/test/Unit/App/Attribute/ResourceDeprecationTest.php b/test/Unit/App/Attribute/ResourceDeprecationTest.php index 34b6683b..aed76d49 100644 --- a/test/Unit/App/Attribute/ResourceDeprecationTest.php +++ b/test/Unit/App/Attribute/ResourceDeprecationTest.php @@ -5,8 +5,8 @@ namespace ApiTest\Unit\App\Attribute; use Api\App\Attribute\ResourceDeprecation; -use Api\App\Exception\DeprecationSunsetException; use Api\App\Middleware\DeprecationMiddleware; +use Core\App\Exception\DeprecationSunsetException; use PHPUnit\Framework\TestCase; use ReflectionClass; diff --git a/test/Unit/App/Middleware/AuthenticationMiddlewareTest.php b/test/Unit/App/Middleware/AuthenticationMiddlewareTest.php index 5e5121a9..fc205831 100644 --- a/test/Unit/App/Middleware/AuthenticationMiddlewareTest.php +++ b/test/Unit/App/Middleware/AuthenticationMiddlewareTest.php @@ -4,7 +4,7 @@ namespace ApiTest\Unit\App\Middleware; -use Api\App\Middleware\AuthenticationMiddleware as Subject; +use Api\App\Middleware\AuthenticationMiddleware; use Core\User\Entity\UserRole; use Laminas\Diactoros\ServerRequest; use Mezzio\Authentication\AuthenticationInterface; @@ -18,7 +18,7 @@ class AuthenticationMiddlewareTest extends TestCase { - private Subject $subject; + private AuthenticationMiddleware $authenticationMiddleware; private AuthenticationInterface|MockObject $auth; private ServerRequestInterface $request; private RequestHandlerInterface|MockObject $handler; @@ -33,7 +33,8 @@ public function setUp(): void $this->handler = $this->createMock(RequestHandlerInterface::class); $this->response = $this->createMock(ResponseInterface::class); $this->request = new ServerRequest(); - $this->subject = new Subject($this->auth); + + $this->authenticationMiddleware = new AuthenticationMiddleware($this->auth); } public function testAuthenticationFailsFallbackToGuestUser(): void @@ -42,15 +43,15 @@ public function testAuthenticationFailsFallbackToGuestUser(): void $this->handler->expects($this->once()) ->method('handle') - ->will($this->returnCallback(function (ServerRequestInterface $request) { + ->willReturnCallback(function (ServerRequestInterface $request) { $user = $request->getAttribute(UserInterface::class); $this->assertInstanceOf(UserInterface::class, $user); $this->assertSame(UserRole::ROLE_GUEST, $user->getIdentity()); $this->assertSame(['guest'], $user->getRoles()); $this->assertCount(1, $user->getRoles()); return $this->response; - })); + }); - $this->subject->process($this->request, $this->handler); + $this->authenticationMiddleware->process($this->request, $this->handler); } } diff --git a/test/Unit/App/Middleware/AuthorizationMiddlewareTest.php b/test/Unit/App/Middleware/AuthorizationMiddlewareTest.php index 9d866a38..8370a8e5 100644 --- a/test/Unit/App/Middleware/AuthorizationMiddlewareTest.php +++ b/test/Unit/App/Middleware/AuthorizationMiddlewareTest.php @@ -4,7 +4,7 @@ namespace ApiTest\Unit\App\Middleware; -use Api\App\Middleware\AuthorizationMiddleware as Subject; +use Api\App\Middleware\AuthorizationMiddleware; use Core\Admin\Entity\Admin; use Core\Admin\Entity\AdminRole; use Core\Admin\Enum\AdminStatusEnum; @@ -31,7 +31,7 @@ class AuthorizationMiddlewareTest extends TestCase { - private Subject $subject; + private AuthorizationMiddleware $authorizationMiddleware; private UserRepository|MockObject $userRepository; private AdminRepository|MockObject $adminRepository; private AuthorizationInterface|MockObject $authorization; @@ -50,7 +50,8 @@ public function setUp(): void $this->handler = $this->createMock(RequestHandlerInterface::class); $this->response = $this->createMock(ResponseInterface::class); $this->request = new ServerRequest(); - $this->subject = new Subject( + + $this->authorizationMiddleware = new AuthorizationMiddleware( $this->authorization, $this->userRepository, $this->adminRepository @@ -62,7 +63,7 @@ public function testAuthorizationInvalidClientIdProvided(): void $identity = new UserIdentity('test@dotkernel.com', ['user'], ['oauth_client_id' => 'invalid_client_id']); $this->request = $this->request->withAttribute(UserInterface::class, $identity); - $response = $this->subject->process($this->request, $this->handler); + $response = $this->authorizationMiddleware->process($this->request, $this->handler); $this->assertSame(StatusCodeInterface::STATUS_FORBIDDEN, $response->getStatusCode()); $data = json_decode($response->getBody()->getContents(), true); @@ -82,7 +83,7 @@ public function testAuthorizationInactiveAdmin(): void $identity = new UserIdentity('admin@dotkernel.com', ['admin'], ['oauth_client_id' => 'admin']); $this->request = $this->request->withAttribute(UserInterface::class, $identity); - $response = $this->subject->process($this->request, $this->handler); + $response = $this->authorizationMiddleware->process($this->request, $this->handler); $this->assertSame(StatusCodeInterface::STATUS_FORBIDDEN, $response->getStatusCode()); $data = json_decode($response->getBody()->getContents(), true); @@ -98,7 +99,7 @@ public function testAuthorizationInactiveUser(): void $identity = new UserIdentity('test@dotkernel.com', ['user'], ['oauth_client_id' => 'frontend']); $this->request = $this->request->withAttribute(UserInterface::class, $identity); - $response = $this->subject->process($this->request, $this->handler); + $response = $this->authorizationMiddleware->process($this->request, $this->handler); $this->assertSame(StatusCodeInterface::STATUS_FORBIDDEN, $response->getStatusCode()); $data = json_decode($response->getBody()->getContents(), true); @@ -116,7 +117,7 @@ public function testAuthorizationUserNotFoundOrDeleted(): void $identity = new UserIdentity('test@dotkernel.com', ['user'], ['oauth_client_id' => 'frontend']); $this->request = $this->request->withAttribute(UserInterface::class, $identity); - $response = $this->subject->process($this->request, $this->handler); + $response = $this->authorizationMiddleware->process($this->request, $this->handler); $this->assertSame(StatusCodeInterface::STATUS_FORBIDDEN, $response->getStatusCode()); $data = json_decode($response->getBody()->getContents(), true); @@ -139,7 +140,7 @@ public function testAuthorizationNotGranted(): void $identity = new UserIdentity('test@dotkernel.com', ['user'], ['oauth_client_id' => 'frontend']); $this->request = $this->request->withAttribute(UserInterface::class, $identity); - $response = $this->subject->process($this->request, $this->handler); + $response = $this->authorizationMiddleware->process($this->request, $this->handler); $this->assertSame(StatusCodeInterface::STATUS_FORBIDDEN, $response->getStatusCode()); $data = json_decode($response->getBody()->getContents(), true); @@ -166,14 +167,14 @@ public function testAuthorizationAccessGranted(): void $this->handler ->expects($this->once()) ->method('handle') - ->will($this->returnCallback(function (ServerRequestInterface $request) use ($identity) { + ->willReturnCallback(function (ServerRequestInterface $request) use ($identity) { $user = $request->getAttribute(UserInterface::class); $this->assertSame($identity->getIdentity(), $user->getIdentity()); $this->assertSame($identity->getDetails(), $user->getDetails()); $this->assertSame($identity->getRoles(), $user->getRoles()); return $this->response; - })); + }); - $this->subject->process($this->request, $this->handler); + $this->authorizationMiddleware->process($this->request, $this->handler); } } diff --git a/test/Unit/App/Middleware/ContentNegotiationMiddlewareTest.php b/test/Unit/App/Middleware/ContentNegotiationMiddlewareTest.php index 6c20efa5..d6eb383b 100644 --- a/test/Unit/App/Middleware/ContentNegotiationMiddlewareTest.php +++ b/test/Unit/App/Middleware/ContentNegotiationMiddlewareTest.php @@ -4,7 +4,7 @@ namespace ApiTest\Unit\App\Middleware; -use Api\App\Middleware\ContentNegotiationMiddleware as Subject; +use Api\App\Middleware\ContentNegotiationMiddleware; use Fig\Http\Message\StatusCodeInterface; use Laminas\Diactoros\ServerRequest; use Mezzio\Router\Route; @@ -17,7 +17,7 @@ class ContentNegotiationMiddlewareTest extends TestCase { - private Subject $subject; + private ContentNegotiationMiddleware $contentNegotiationMiddleware; private ServerRequestInterface $request; private RequestHandlerInterface $handler; private RouteResult $routeResult; @@ -55,7 +55,7 @@ protected function setUp(): void $this->request = new ServerRequest(); - $this->subject = new Subject(self::CONFIG); + $this->contentNegotiationMiddleware = new ContentNegotiationMiddleware(self::CONFIG); } public function testWrongAccept(): void @@ -67,7 +67,7 @@ public function testWrongAccept(): void $request = $request->withHeader('Accept', 'text/html'); $this->assertSame( StatusCodeInterface::STATUS_NOT_ACCEPTABLE, - $this->subject->process($request, $this->handler)->getStatusCode() + $this->contentNegotiationMiddleware->process($request, $this->handler)->getStatusCode() ); } @@ -81,7 +81,7 @@ public function testWrongContentType(): void $request = $request->withHeader('Content-Type', 'text/html'); $this->assertSame( StatusCodeInterface::STATUS_UNSUPPORTED_MEDIA_TYPE, - $this->subject->process($request, $this->handler)->getStatusCode() + $this->contentNegotiationMiddleware->process($request, $this->handler)->getStatusCode() ); } @@ -95,13 +95,13 @@ public function testCannotResolveRepresentation(): void $request = $request->withHeader('Content-Type', 'application/json'); $this->assertSame( StatusCodeInterface::STATUS_NOT_ACCEPTABLE, - $this->subject->process($request, $this->handler)->getStatusCode() + $this->contentNegotiationMiddleware->process($request, $this->handler)->getStatusCode() ); } public function testFormatAcceptRequest(): void { - $accept = $this->subject->formatAcceptRequest('application/json'); + $accept = $this->contentNegotiationMiddleware->formatAcceptRequest('application/json'); $this->assertNotEmpty($accept); $this->assertSame(['application/json'], $accept); @@ -110,36 +110,36 @@ public function testFormatAcceptRequest(): void public function testCheckAccept(): void { $this->assertTrue( - $this->subject->checkAccept( + $this->contentNegotiationMiddleware->checkAccept( self::ROUTE_NAME, ['*/*'] ) ); $this->assertTrue( - $this->subject->checkAccept( + $this->contentNegotiationMiddleware->checkAccept( self::ROUTE_NAME, ['application/json'] ) ); $this->assertFalse( - $this->subject->checkAccept(self::ROUTE_NAME, ['text/html']) + $this->contentNegotiationMiddleware->checkAccept(self::ROUTE_NAME, ['text/html']) ); } public function testCheckContentType(): void { $this->assertTrue( - $this->subject->checkContentType(self::ROUTE_NAME, '') + $this->contentNegotiationMiddleware->checkContentType(self::ROUTE_NAME, '') ); $this->assertTrue( - $this->subject->checkContentType( + $this->contentNegotiationMiddleware->checkContentType( self::ROUTE_NAME, 'application/json' ) ); $this->assertFalse( - $this->subject->checkContentType(self::ROUTE_NAME, 'text/html') + $this->contentNegotiationMiddleware->checkContentType(self::ROUTE_NAME, 'text/html') ); } } diff --git a/test/Unit/App/Middleware/DeprecationMiddlewareTest.php b/test/Unit/App/Middleware/DeprecationMiddlewareTest.php index 37a80e64..2ba181d1 100644 --- a/test/Unit/App/Middleware/DeprecationMiddlewareTest.php +++ b/test/Unit/App/Middleware/DeprecationMiddlewareTest.php @@ -6,8 +6,8 @@ use Api\App\Attribute\MethodDeprecation; use Api\App\Attribute\ResourceDeprecation; -use Api\App\Exception\DeprecationConflictException; -use Api\App\Middleware\DeprecationMiddleware as Subject; +use Api\App\Middleware\DeprecationMiddleware; +use Core\App\Exception\DeprecationConflictException; use Core\App\Message; use Fig\Http\Message\RequestMethodInterface; use Laminas\Diactoros\Response\EmptyResponse; @@ -31,7 +31,7 @@ class DeprecationMiddlewareTest extends TestCase { - private Subject $subject; + private DeprecationMiddleware $deprecationMiddleware; private ServerRequestInterface|MockObject $request; private RequestHandlerInterface|MockObject $handler; private ResponseInterface $response; @@ -48,7 +48,8 @@ protected function setUp(): void $this->handler = $this->createMock(RequestHandlerInterface::class); $this->request = $this->createMock(ServerRequestInterface::class); $this->response = new EmptyResponse(); - $this->subject = new Subject(self::VERSIONING_CONFIG); + + $this->deprecationMiddleware = new DeprecationMiddleware(self::VERSIONING_CONFIG); } /** @@ -89,11 +90,11 @@ public function handle(ServerRequestInterface $request): ResponseInterface $this->expectException(DeprecationConflictException::class); $this->expectExceptionMessage(sprintf( Message::RESTRICTION_DEPRECATION, - Subject::RESOURCE_DEPRECATION_ATTRIBUTE, - Subject::METHOD_DEPRECATION_ATTRIBUTE + DeprecationMiddleware::RESOURCE_DEPRECATION_ATTRIBUTE, + DeprecationMiddleware::METHOD_DEPRECATION_ATTRIBUTE )); - $this->subject->process($this->request, $this->handler); + $this->deprecationMiddleware->process($this->request, $this->handler); } /** @@ -129,7 +130,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface $this->request->method('getMethod')->willReturn(RequestMethodInterface::METHOD_GET); $this->handler->method('handle')->with($this->request)->willReturn($this->response); - $response = $this->subject->process($this->request, $this->handler); + $response = $this->deprecationMiddleware->process($this->request, $this->handler); $this->assertTrue($response->hasHeader('sunset')); $this->assertTrue($response->hasHeader('link')); @@ -192,7 +193,7 @@ public function process( $this->request->method('getMethod')->willReturn(RequestMethodInterface::METHOD_GET); $this->handler->method('handle')->with($this->request)->willReturn($this->response); - $response = $this->subject->process($this->request, $this->handler); + $response = $this->deprecationMiddleware->process($this->request, $this->handler); $this->assertTrue($response->hasHeader('sunset')); $this->assertTrue($response->hasHeader('link')); @@ -230,7 +231,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface $this->request->method('getMethod')->willReturn(RequestMethodInterface::METHOD_GET); $this->handler->method('handle')->with($this->request)->willReturn($this->response); - $response = $this->subject->process($this->request, $this->handler); + $response = $this->deprecationMiddleware->process($this->request, $this->handler); $this->assertTrue($response->hasHeader('link')); $this->assertFalse($response->hasHeader('sunset')); @@ -268,7 +269,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface $this->request->method('getMethod')->willReturn(RequestMethodInterface::METHOD_GET); $this->handler->method('handle')->with($this->request)->willReturn($this->response); - $response = $this->subject->process($this->request, $this->handler); + $response = $this->deprecationMiddleware->process($this->request, $this->handler); $this->assertTrue($response->hasHeader('link')); $this->assertFalse($response->hasHeader('sunset')); @@ -306,7 +307,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface $this->request->method('getMethod')->willReturn(RequestMethodInterface::METHOD_GET); $this->handler->method('handle')->with($this->request)->willReturn($this->response); - $response = (new Subject([]))->process($this->request, $this->handler); + $response = (new DeprecationMiddleware([]))->process($this->request, $this->handler); $this->assertFalse($response->hasHeader('link')); $this->assertTrue($response->hasHeader('sunset')); diff --git a/test/Unit/User/Service/UserAvatarServiceTest.php b/test/Unit/User/Service/UserAvatarServiceTest.php index e1bcbd39..7c5840e0 100644 --- a/test/Unit/User/Service/UserAvatarServiceTest.php +++ b/test/Unit/User/Service/UserAvatarServiceTest.php @@ -4,10 +4,10 @@ namespace ApiTest\Unit\User\Service; -use Api\User\Service\UserAvatarService; use Core\User\Entity\User; use Core\User\Entity\UserAvatar; use Core\User\Repository\UserAvatarRepository; +use Core\User\Service\UserAvatarService; use Laminas\Diactoros\UploadedFile; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; diff --git a/test/Unit/User/Service/UserServiceTest.php b/test/Unit/User/Service/UserServiceTest.php index dadb138f..5d5d7326 100644 --- a/test/Unit/User/Service/UserServiceTest.php +++ b/test/Unit/User/Service/UserServiceTest.php @@ -4,23 +4,20 @@ namespace ApiTest\Unit\User\Service; -use Api\App\Exception\ConflictException; -use Api\App\Exception\NotFoundException; -use Api\User\Service\UserRoleService; -use Api\User\Service\UserService as Subject; -use Core\App\Repository\OAuthAccessTokenRepository; -use Core\App\Repository\OAuthRefreshTokenRepository; +use Core\App\Exception\BadRequestException; +use Core\App\Exception\ConflictException; +use Core\App\Exception\NotFoundException; +use Core\Security\Repository\OAuthAccessTokenRepository; +use Core\Security\Repository\OAuthRefreshTokenRepository; use Core\User\Entity\User; use Core\User\Entity\UserDetail; use Core\User\Entity\UserRole; use Core\User\Enum\UserStatusEnum; use Core\User\Repository\UserDetailRepository; use Core\User\Repository\UserRepository; -use Core\User\Repository\UserResetPasswordRepository; -use Dot\Log\LoggerInterface; -use Dot\Mail\Service\MailService; +use Core\User\Repository\UserRoleRepository; +use Core\User\Service\UserService; use Exception; -use Mezzio\Template\TemplateRendererInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -29,31 +26,27 @@ class UserServiceTest extends TestCase { - private Subject $subject; - private UserRoleService|MockObject $userRoleService; - private UserRepository|MockObject $userRepository; - private UserDetailRepository|MockObject $userDetailRepository; - private UserResetPasswordRepository|MockObject $userResetPasswordRepository; + private UserService $subject; + private UserRepository&MockObject $userRepository; + private UserDetailRepository&MockObject $userDetailRepository; + private UserRoleRepository&MockObject $userRoleRepository; /** * @throws \PHPUnit\Framework\MockObject\Exception */ public function setUp(): void { - $this->userRoleService = $this->createMock(UserRoleService::class); - $this->userRepository = $this->createMock(UserRepository::class); - $this->userDetailRepository = $this->createMock(UserDetailRepository::class); - $this->userResetPasswordRepository = $this->createMock(UserResetPasswordRepository::class); - $this->subject = new Subject( - $this->userRoleService, - $this->createMock(MailService::class), - $this->createMock(TemplateRendererInterface::class), - $this->createMock(OAuthAccessTokenRepository::class), - $this->createMock(OAuthRefreshTokenRepository::class), + $oAuthAccessTokenRepository = $this->createMock(OAuthAccessTokenRepository::class); + $oAuthRefreshTokenRepository = $this->createMock(OAuthRefreshTokenRepository::class); + $this->userRepository = $this->createMock(UserRepository::class); + $this->userDetailRepository = $this->createMock(UserDetailRepository::class); + $this->userRoleRepository = $this->createMock(UserRoleRepository::class); + $this->subject = new UserService( + $oAuthAccessTokenRepository, + $oAuthRefreshTokenRepository, $this->userRepository, $this->userDetailRepository, - $this->userResetPasswordRepository, - $this->createMock(LoggerInterface::class), + $this->userRoleRepository, [] ); } @@ -64,15 +57,14 @@ public function setUp(): void */ public function testCreateUserThrowsExceptionDuplicateIdentity(): void { - $this->userRepository->method('findOneBy')->willReturn( - $this->getUserEntity($this->getUser()) - ); + $request = $this->getUser(); + $this->userRepository + ->method('findOneBy') + ->willReturn($this->getUserEntity(['identity' => $request['identity']])); $this->expectException(Exception::class); - $this->subject->createUser([ - 'identity' => 'test@dotkernel.com', - ]); + $this->subject->createUser($request); } /** @@ -93,7 +85,7 @@ public function testCreateUserWithMultipleRoles(): void ], ]); - $this->userRoleService->method('findOneBy')->willReturn(new UserRole()); + $this->userRoleRepository->method('find')->willReturn(new UserRole()); $this->userRepository->method('saveUser')->willReturn( $this->getUserEntity($data) ); @@ -117,7 +109,7 @@ public function testCreateUserWithDefaultRole(): void ]); $defaultRole = (new UserRole())->setName(UserRole::ROLE_USER); - $this->userRoleService->method('findOneBy')->willReturn($defaultRole); + $this->userRoleRepository->method('find')->willReturn($defaultRole); $this->userRepository->method('saveUser')->willReturn( $this->getUserEntity($data) ); @@ -133,7 +125,7 @@ public function testCreateUserWithDefaultRole(): void */ public function testCreateUser(): void { - $this->userRoleService->method('findOneBy')->willReturn(new UserRole()); + $this->userRoleRepository->method('find')->willReturn(new UserRole()); $this->userRepository->method('saveUser')->willReturn( $this->getUserEntity($this->getUser()) ); @@ -150,6 +142,11 @@ public function testCreateUser(): void $this->assertFalse($user->isActive()); } + /** + * @throws NotFoundException + * @throws ConflictException + * @throws BadRequestException + */ public function testUpdateUserThrowsExceptionDuplicateUserDetailEmail(): void { $this->userDetailRepository->method('findOneBy')->willReturn($this->getUserEntity()->getDetail());