From 3d60d4b9dc570ff76964f5f45a6298cab9545739 Mon Sep 17 00:00:00 2001 From: flo Date: Sat, 24 Aug 2024 20:33:05 +0000 Subject: [PATCH] Initial commit --- .env.example | 20 ++ .gitattributes | 7 + .gitignore | 16 + .htaccess | 3 + COPYRIGHT.md | 1 + LICENSE.md | 26 ++ README.md | 0 bin/clear-config-cache.php | 39 +++ bin/console.php | 30 ++ bin/createApi.php | 319 ++++++++++++++++++ bin/createPipeline.php | 151 +++++++++ bin/doctrine-migrations-log.php | 91 +++++ bin/doctrine-migrations.php | 91 +++++ bin/script/init | 36 ++ composer.json | 76 +++++ config/autoload/.gitignore | 2 + config/autoload/api.global.php | 29 ++ config/autoload/authorization.global.php | 22 ++ config/autoload/defines.php | 6 + config/autoload/dependencies.global.php | 26 ++ config/autoload/doctrine.global.php | 14 + config/autoload/logger.global.php | 14 + config/autoload/mezzio.global.php | 24 ++ config/config.php | 89 +++++ config/container.php | 21 ++ config/development.config.php | 31 ++ config/pipeline.php | 94 ++++++ config/routes.php | 52 +++ .../business/Version20230922085011.php | 32 ++ .../business/Version20230922092351.php | 35 ++ .../business/Version20230922092754.php | 36 ++ .../business/Version20230922101354.php | 41 +++ .../business/Version20230922101355.php | 38 +++ .../business/Version20230924113403.php | 37 ++ data/migrations/log/Version20230922150649.php | 40 +++ docker/docker-compose-mac.yml | 50 +++ docker/docker-compose.yml | 50 +++ docker/mysql/dockerfile | 7 + docker/mysql/scripts/initdb.sql | 4 + docker/nginx/config/nginx.conf | 15 + docker/nginx/config/upstream.conf | 3 + docker/nginx/dockerfile | 5 + docker/php/dockerfile | 15 + phpunit.xml.dist | 26 ++ public/.htaccess | 19 ++ public/index.php | 48 +++ src/ApiDomain/Console/config/console.php | 11 + .../Console/config/service_manager.php | 12 + .../src/Command/InitializeDataCommand.php | 80 +++++ .../Console/src/Command/RbacUpdateCommand.php | 98 ++++++ src/ApiDomain/Console/src/ConfigProvider.php | 16 + .../External/Authentication/config/routes.php | 43 +++ .../Authentication/config/service_manager.php | 17 + .../Authentication/src/ConfigProvider.php | 16 + .../Handler/ConfirmRegistrationHandler.php | 44 +++ .../src/Handler/LoginUserHandler.php | 42 +++ .../src/Handler/LogoutUserHandler.php | 35 ++ .../src/Handler/RegisterUserHandler.php | 41 +++ .../External/Health/config/routes.php | 16 + .../Health/config/service_manager.php | 12 + .../External/Health/src/ConfigProvider.php | 16 + .../Health/src/Handler/HealthHandler.php | 23 ++ src/ApiDomain/External/User/config/routes.php | 48 +++ .../External/User/config/service_manager.php | 21 ++ .../External/User/src/ConfigProvider.php | 16 + .../User/src/Formatter/UserFormatter.php | 27 ++ .../src/Handler/ChangePasswordHandler.php | 43 +++ .../src/Handler/ChangeUsernameHandler.php | 43 +++ .../User/src/Handler/CreateUserHandler.php | 44 +++ .../User/src/Handler/UserStateHandler.php | 34 ++ src/DataDomain/Business/config/doctrine.php | 55 +++ .../Business/config/service_manager.php | 21 ++ .../Business/src/ConfigProvider.php | 16 + .../Business/src/Entity/Permission.php | 75 ++++ .../Business/src/Entity/Registration.php | 66 ++++ src/DataDomain/Business/src/Entity/Role.php | 76 +++++ src/DataDomain/Business/src/Entity/User.php | 148 ++++++++ .../Business/src/Entity/UserSession.php | 105 ++++++ .../src/Factory/EntityManagerFactory.php | 19 ++ .../Business/src/Manager/EntityManager.php | 11 + .../src/Repository/PermissionRepository.php | 10 + .../src/Repository/ProductRepository.php | 10 + .../src/Repository/RegistrationRepository.php | 24 ++ .../src/Repository/RoleRepository.php | 10 + .../src/Repository/UserRepository.php | 26 ++ .../src/Repository/UserSessionRepository.php | 22 ++ src/DataDomain/Log/config/doctrine.php | 55 +++ src/DataDomain/Log/config/service_manager.php | 16 + src/DataDomain/Log/src/ConfigProvider.php | 16 + src/DataDomain/Log/src/Entity/Log.php | 129 +++++++ .../src/Factory/LogEntityManagerFactory.php | 21 ++ .../Log/src/Manager/LogEntityManager.php | 11 + .../Log/src/Repository/LogRepository.php | 8 + .../Registration/config/service_manager.php | 53 +++ .../src/Builder/RegistrationBuilder.php | 19 ++ .../Registration/src/ConfigProvider.php | 15 + .../RegistrationNotFoundByIdException.php | 25 ++ ...onWithIdentifierAlreadyExistsException.php | 25 ++ .../ConfirmRegistrationCommand.php | 28 ++ .../ConfirmRegistrationCommandBuilder.php | 21 ++ .../ConfirmRegistrationCommandHandler.php | 23 ++ .../RegisterUser/RegisterUserCommand.php | 25 ++ .../RegisterUserCommandBuilder.php | 19 ++ .../RegisterUserCommandHandler.php | 23 ++ .../ConfirmRegistrationPayload.php | 46 +++ .../ConfirmRegistrationPipeline.php | 26 ++ .../Step/CreateUserStep.php | 60 ++++ .../Step/LoadRegistrationStep.php | 47 +++ .../Step/SaveRegistrationAndUserStep.php | 32 ++ .../RegisterUser/RegisterUserPayload.php | 33 ++ .../RegisterUser/RegisterUserPipeline.php | 29 ++ .../Step/BuildRegistrationStep.php | 34 ++ .../RegisterUser/Step/CheckIdentifierStep.php | 41 +++ .../Step/SaveRegistrationStep.php | 30 ++ .../RegisterUser/Step/SendMailStep.php | 49 +++ ...trationWithIdentifierAlreadyExistsRule.php | 29 ++ .../RoleNotFoundByIdentifierException.php | 24 ++ .../User/config/service_manager.php | 39 +++ .../User/src/Builder/UserBuilder.php | 53 +++ .../User/src/ConfigProvider.php | 15 + .../UserNotFoundByIdentifierException.php | 26 ++ .../UserPasswordMismatchException.php | 21 ++ ...erWithIdentifierAlreadyExistsException.php | 24 ++ .../Exception/UserWrongPasswordException.php | 21 ++ .../ChangePassword/ChangePasswordCommand.php | 27 ++ .../ChangePasswordCommandBuilder.php | 21 ++ .../ChangePasswordCommandHandler.php | 39 +++ .../ChangeUsername/ChangeUsernameCommand.php | 28 ++ .../ChangeUsernameCommandBuilder.php | 21 ++ .../ChangeUsernameCommandHandler.php | 30 ++ .../Command/CreateUser/CreateUserCommand.php | 25 ++ .../CreateUser/CreateUserCommandBuilder.php | 19 ++ .../CreateUser/CreateUserCommandHandler.php | 39 +++ .../User/src/Rule/UserPasswordMatchRule.php | 15 + .../UserWithIdentifierAlreadyExistsRule.php | 29 ++ .../UserSession/config/service_manager.php | 31 ++ .../src/Builder/UserSessionBuilder.php | 17 + .../UserSession/src/ConfigProvider.php | 15 + .../Command/LoginUser/LoginUserCommand.php | 29 ++ .../LoginUser/LoginUserCommandBuilder.php | 23 ++ .../LoginUser/LoginUserCommandHandler.php | 80 +++++ .../Command/LogoutUser/LogoutUserCommand.php | 19 ++ .../LogoutUser/LogoutUserCommandBuilder.php | 19 ++ .../LogoutUser/LogoutUserCommandHandler.php | 37 ++ .../src/Rule/UserPasswordMatchesRule.php | 24 ++ .../Database/config/service_manager.php | 11 + .../Database/src/ConfigProvider.php | 15 + .../Database/src/ConfigServiceFactory.php | 33 ++ .../config/service_manager.php | 12 + .../src/ConfigProvider.php | 15 + .../src/ConfigServiceFactory.php | 21 ++ .../Encryption/config/service_manager.php | 12 + .../src/Client/EncryptionClient.php | 26 ++ .../Encryption/src/ConfigProvider.php | 15 + .../Exception/config/service_manager.php | 12 + .../Exception/src/ConfigProvider.php | 15 + .../Exception/src/ErrorCode.php | 12 + .../Exception/src/ErrorDomain.php | 13 + .../Exception/src/Exception/Exception.php | 36 ++ .../Middleware/ExceptionHandlerMiddleware.php | 37 ++ .../Logging/config/service_manager.php | 23 ++ .../Logging/src/ConfigProvider.php | 15 + .../src/Factory/FileStreamHandlerFactory.php | 29 ++ .../Logging/src/Factory/LoggerFactory.php | 22 ++ .../src/Factory/MonologLoggerFactory.php | 34 ++ .../src/Formatter/PrettyFileLogLines.php | 77 +++++ .../src/Handler/DoctrineLogHandler.php | 37 ++ .../Logging/src/Handler/FileStreamHandler.php | 21 ++ .../Logging/src/Logger/Logger.php | 89 +++++ .../Rbac/config/service_manager.php | 12 + .../Rbac/src/ConfigProvider.php | 15 + .../EnsureAuthorizationMiddleware.php | 70 ++++ .../Request/config/service_manager.php | 22 ++ .../Request/src/ConfigProvider.php | 15 + .../ApiIdentifierUnknownException.php | 22 ++ .../ApiPropertyUndefinedException.php | 24 ++ .../Exception/ApiRequestFailedException.php | 26 ++ .../Exception/ApiServiceUnknownException.php | 20 ++ .../src/Factory/RequestServiceFactory.php | 28 ++ .../src/Middleware/AnalyzeBodyMiddleware.php | 43 +++ .../Middleware/AnalyzeHeaderMiddleware.php | 37 ++ .../Middleware/InternalRequestMiddleware.php | 47 +++ .../Request/src/Service/RequestService.php | 95 ++++++ .../Response/src/ErrorResponse.php | 37 ++ .../Response/src/ForbiddenResponse.php | 16 + .../Response/src/SuccessResponse.php | 17 + .../Response/src/UnauthorizedResponse.php | 16 + .../Session/config/service_manager.php | 14 + .../Session/src/ConfigProvider.php | 15 + .../src/Middleware/LoggedInUserMiddleware.php | 41 +++ .../src/Middleware/SessionMiddleware.php | 81 +++++ .../UuidGenerator/src/UuidGenerator.php | 18 + 192 files changed, 6484 insertions(+) create mode 100644 .env.example create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .htaccess create mode 100644 COPYRIGHT.md create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 bin/clear-config-cache.php create mode 100644 bin/console.php create mode 100644 bin/createApi.php create mode 100644 bin/createPipeline.php create mode 100644 bin/doctrine-migrations-log.php create mode 100644 bin/doctrine-migrations.php create mode 100755 bin/script/init create mode 100644 composer.json create mode 100644 config/autoload/.gitignore create mode 100644 config/autoload/api.global.php create mode 100644 config/autoload/authorization.global.php create mode 100644 config/autoload/defines.php create mode 100644 config/autoload/dependencies.global.php create mode 100644 config/autoload/doctrine.global.php create mode 100644 config/autoload/logger.global.php create mode 100644 config/autoload/mezzio.global.php create mode 100644 config/config.php create mode 100644 config/container.php create mode 100644 config/development.config.php create mode 100644 config/pipeline.php create mode 100644 config/routes.php create mode 100644 data/migrations/business/Version20230922085011.php create mode 100644 data/migrations/business/Version20230922092351.php create mode 100644 data/migrations/business/Version20230922092754.php create mode 100644 data/migrations/business/Version20230922101354.php create mode 100644 data/migrations/business/Version20230922101355.php create mode 100644 data/migrations/business/Version20230924113403.php create mode 100644 data/migrations/log/Version20230922150649.php create mode 100644 docker/docker-compose-mac.yml create mode 100644 docker/docker-compose.yml create mode 100644 docker/mysql/dockerfile create mode 100644 docker/mysql/scripts/initdb.sql create mode 100644 docker/nginx/config/nginx.conf create mode 100644 docker/nginx/config/upstream.conf create mode 100644 docker/nginx/dockerfile create mode 100644 docker/php/dockerfile create mode 100644 phpunit.xml.dist create mode 100644 public/.htaccess create mode 100644 public/index.php create mode 100644 src/ApiDomain/Console/config/console.php create mode 100644 src/ApiDomain/Console/config/service_manager.php create mode 100644 src/ApiDomain/Console/src/Command/InitializeDataCommand.php create mode 100644 src/ApiDomain/Console/src/Command/RbacUpdateCommand.php create mode 100644 src/ApiDomain/Console/src/ConfigProvider.php create mode 100644 src/ApiDomain/External/Authentication/config/routes.php create mode 100644 src/ApiDomain/External/Authentication/config/service_manager.php create mode 100644 src/ApiDomain/External/Authentication/src/ConfigProvider.php create mode 100644 src/ApiDomain/External/Authentication/src/Handler/ConfirmRegistrationHandler.php create mode 100644 src/ApiDomain/External/Authentication/src/Handler/LoginUserHandler.php create mode 100644 src/ApiDomain/External/Authentication/src/Handler/LogoutUserHandler.php create mode 100644 src/ApiDomain/External/Authentication/src/Handler/RegisterUserHandler.php create mode 100644 src/ApiDomain/External/Health/config/routes.php create mode 100644 src/ApiDomain/External/Health/config/service_manager.php create mode 100644 src/ApiDomain/External/Health/src/ConfigProvider.php create mode 100644 src/ApiDomain/External/Health/src/Handler/HealthHandler.php create mode 100644 src/ApiDomain/External/User/config/routes.php create mode 100644 src/ApiDomain/External/User/config/service_manager.php create mode 100644 src/ApiDomain/External/User/src/ConfigProvider.php create mode 100644 src/ApiDomain/External/User/src/Formatter/UserFormatter.php create mode 100644 src/ApiDomain/External/User/src/Handler/ChangePasswordHandler.php create mode 100644 src/ApiDomain/External/User/src/Handler/ChangeUsernameHandler.php create mode 100644 src/ApiDomain/External/User/src/Handler/CreateUserHandler.php create mode 100644 src/ApiDomain/External/User/src/Handler/UserStateHandler.php create mode 100644 src/DataDomain/Business/config/doctrine.php create mode 100644 src/DataDomain/Business/config/service_manager.php create mode 100644 src/DataDomain/Business/src/ConfigProvider.php create mode 100644 src/DataDomain/Business/src/Entity/Permission.php create mode 100644 src/DataDomain/Business/src/Entity/Registration.php create mode 100644 src/DataDomain/Business/src/Entity/Role.php create mode 100644 src/DataDomain/Business/src/Entity/User.php create mode 100644 src/DataDomain/Business/src/Entity/UserSession.php create mode 100644 src/DataDomain/Business/src/Factory/EntityManagerFactory.php create mode 100644 src/DataDomain/Business/src/Manager/EntityManager.php create mode 100644 src/DataDomain/Business/src/Repository/PermissionRepository.php create mode 100644 src/DataDomain/Business/src/Repository/ProductRepository.php create mode 100644 src/DataDomain/Business/src/Repository/RegistrationRepository.php create mode 100644 src/DataDomain/Business/src/Repository/RoleRepository.php create mode 100644 src/DataDomain/Business/src/Repository/UserRepository.php create mode 100644 src/DataDomain/Business/src/Repository/UserSessionRepository.php create mode 100644 src/DataDomain/Log/config/doctrine.php create mode 100644 src/DataDomain/Log/config/service_manager.php create mode 100644 src/DataDomain/Log/src/ConfigProvider.php create mode 100644 src/DataDomain/Log/src/Entity/Log.php create mode 100644 src/DataDomain/Log/src/Factory/LogEntityManagerFactory.php create mode 100644 src/DataDomain/Log/src/Manager/LogEntityManager.php create mode 100644 src/DataDomain/Log/src/Repository/LogRepository.php create mode 100644 src/HandlingDomain/Registration/config/service_manager.php create mode 100644 src/HandlingDomain/Registration/src/Builder/RegistrationBuilder.php create mode 100644 src/HandlingDomain/Registration/src/ConfigProvider.php create mode 100644 src/HandlingDomain/Registration/src/Exception/RegistrationNotFoundByIdException.php create mode 100644 src/HandlingDomain/Registration/src/Exception/RegistrationWithIdentifierAlreadyExistsException.php create mode 100644 src/HandlingDomain/Registration/src/Handler/Command/ConfirmRegistration/ConfirmRegistrationCommand.php create mode 100644 src/HandlingDomain/Registration/src/Handler/Command/ConfirmRegistration/ConfirmRegistrationCommandBuilder.php create mode 100644 src/HandlingDomain/Registration/src/Handler/Command/ConfirmRegistration/ConfirmRegistrationCommandHandler.php create mode 100644 src/HandlingDomain/Registration/src/Handler/Command/RegisterUser/RegisterUserCommand.php create mode 100644 src/HandlingDomain/Registration/src/Handler/Command/RegisterUser/RegisterUserCommandBuilder.php create mode 100644 src/HandlingDomain/Registration/src/Handler/Command/RegisterUser/RegisterUserCommandHandler.php create mode 100644 src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/ConfirmRegistrationPayload.php create mode 100644 src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/ConfirmRegistrationPipeline.php create mode 100644 src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/Step/CreateUserStep.php create mode 100644 src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/Step/LoadRegistrationStep.php create mode 100644 src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/Step/SaveRegistrationAndUserStep.php create mode 100644 src/HandlingDomain/Registration/src/Pipeline/RegisterUser/RegisterUserPayload.php create mode 100644 src/HandlingDomain/Registration/src/Pipeline/RegisterUser/RegisterUserPipeline.php create mode 100644 src/HandlingDomain/Registration/src/Pipeline/RegisterUser/Step/BuildRegistrationStep.php create mode 100644 src/HandlingDomain/Registration/src/Pipeline/RegisterUser/Step/CheckIdentifierStep.php create mode 100644 src/HandlingDomain/Registration/src/Pipeline/RegisterUser/Step/SaveRegistrationStep.php create mode 100644 src/HandlingDomain/Registration/src/Pipeline/RegisterUser/Step/SendMailStep.php create mode 100644 src/HandlingDomain/Registration/src/Rule/RegistrationWithIdentifierAlreadyExistsRule.php create mode 100644 src/HandlingDomain/Role/src/Exception/RoleNotFoundByIdentifierException.php create mode 100644 src/HandlingDomain/User/config/service_manager.php create mode 100644 src/HandlingDomain/User/src/Builder/UserBuilder.php create mode 100644 src/HandlingDomain/User/src/ConfigProvider.php create mode 100644 src/HandlingDomain/User/src/Exception/UserNotFoundByIdentifierException.php create mode 100644 src/HandlingDomain/User/src/Exception/UserPasswordMismatchException.php create mode 100644 src/HandlingDomain/User/src/Exception/UserWithIdentifierAlreadyExistsException.php create mode 100644 src/HandlingDomain/User/src/Exception/UserWrongPasswordException.php create mode 100644 src/HandlingDomain/User/src/Handler/Command/ChangePassword/ChangePasswordCommand.php create mode 100644 src/HandlingDomain/User/src/Handler/Command/ChangePassword/ChangePasswordCommandBuilder.php create mode 100644 src/HandlingDomain/User/src/Handler/Command/ChangePassword/ChangePasswordCommandHandler.php create mode 100644 src/HandlingDomain/User/src/Handler/Command/ChangeUsername/ChangeUsernameCommand.php create mode 100644 src/HandlingDomain/User/src/Handler/Command/ChangeUsername/ChangeUsernameCommandBuilder.php create mode 100644 src/HandlingDomain/User/src/Handler/Command/ChangeUsername/ChangeUsernameCommandHandler.php create mode 100644 src/HandlingDomain/User/src/Handler/Command/CreateUser/CreateUserCommand.php create mode 100644 src/HandlingDomain/User/src/Handler/Command/CreateUser/CreateUserCommandBuilder.php create mode 100644 src/HandlingDomain/User/src/Handler/Command/CreateUser/CreateUserCommandHandler.php create mode 100644 src/HandlingDomain/User/src/Rule/UserPasswordMatchRule.php create mode 100644 src/HandlingDomain/User/src/Rule/UserWithIdentifierAlreadyExistsRule.php create mode 100644 src/HandlingDomain/UserSession/config/service_manager.php create mode 100644 src/HandlingDomain/UserSession/src/Builder/UserSessionBuilder.php create mode 100644 src/HandlingDomain/UserSession/src/ConfigProvider.php create mode 100644 src/HandlingDomain/UserSession/src/Handler/Command/LoginUser/LoginUserCommand.php create mode 100644 src/HandlingDomain/UserSession/src/Handler/Command/LoginUser/LoginUserCommandBuilder.php create mode 100644 src/HandlingDomain/UserSession/src/Handler/Command/LoginUser/LoginUserCommandHandler.php create mode 100644 src/HandlingDomain/UserSession/src/Handler/Command/LogoutUser/LogoutUserCommand.php create mode 100644 src/HandlingDomain/UserSession/src/Handler/Command/LogoutUser/LogoutUserCommandBuilder.php create mode 100644 src/HandlingDomain/UserSession/src/Handler/Command/LogoutUser/LogoutUserCommandHandler.php create mode 100644 src/HandlingDomain/UserSession/src/Rule/UserPasswordMatchesRule.php create mode 100644 src/Infrastructure/Database/config/service_manager.php create mode 100644 src/Infrastructure/Database/src/ConfigProvider.php create mode 100644 src/Infrastructure/Database/src/ConfigServiceFactory.php create mode 100644 src/Infrastructure/DependencyInjection/config/service_manager.php create mode 100644 src/Infrastructure/DependencyInjection/src/ConfigProvider.php create mode 100644 src/Infrastructure/DependencyInjection/src/ConfigServiceFactory.php create mode 100644 src/Infrastructure/Encryption/config/service_manager.php create mode 100644 src/Infrastructure/Encryption/src/Client/EncryptionClient.php create mode 100644 src/Infrastructure/Encryption/src/ConfigProvider.php create mode 100644 src/Infrastructure/Exception/config/service_manager.php create mode 100644 src/Infrastructure/Exception/src/ConfigProvider.php create mode 100644 src/Infrastructure/Exception/src/ErrorCode.php create mode 100644 src/Infrastructure/Exception/src/ErrorDomain.php create mode 100644 src/Infrastructure/Exception/src/Exception/Exception.php create mode 100644 src/Infrastructure/Exception/src/Middleware/ExceptionHandlerMiddleware.php create mode 100644 src/Infrastructure/Logging/config/service_manager.php create mode 100644 src/Infrastructure/Logging/src/ConfigProvider.php create mode 100644 src/Infrastructure/Logging/src/Factory/FileStreamHandlerFactory.php create mode 100644 src/Infrastructure/Logging/src/Factory/LoggerFactory.php create mode 100644 src/Infrastructure/Logging/src/Factory/MonologLoggerFactory.php create mode 100644 src/Infrastructure/Logging/src/Formatter/PrettyFileLogLines.php create mode 100644 src/Infrastructure/Logging/src/Handler/DoctrineLogHandler.php create mode 100644 src/Infrastructure/Logging/src/Handler/FileStreamHandler.php create mode 100644 src/Infrastructure/Logging/src/Logger/Logger.php create mode 100644 src/Infrastructure/Rbac/config/service_manager.php create mode 100644 src/Infrastructure/Rbac/src/ConfigProvider.php create mode 100644 src/Infrastructure/Rbac/src/Middleware/EnsureAuthorizationMiddleware.php create mode 100644 src/Infrastructure/Request/config/service_manager.php create mode 100644 src/Infrastructure/Request/src/ConfigProvider.php create mode 100644 src/Infrastructure/Request/src/Exception/ApiIdentifierUnknownException.php create mode 100644 src/Infrastructure/Request/src/Exception/ApiPropertyUndefinedException.php create mode 100644 src/Infrastructure/Request/src/Exception/ApiRequestFailedException.php create mode 100644 src/Infrastructure/Request/src/Exception/ApiServiceUnknownException.php create mode 100644 src/Infrastructure/Request/src/Factory/RequestServiceFactory.php create mode 100644 src/Infrastructure/Request/src/Middleware/AnalyzeBodyMiddleware.php create mode 100644 src/Infrastructure/Request/src/Middleware/AnalyzeHeaderMiddleware.php create mode 100644 src/Infrastructure/Request/src/Middleware/InternalRequestMiddleware.php create mode 100644 src/Infrastructure/Request/src/Service/RequestService.php create mode 100644 src/Infrastructure/Response/src/ErrorResponse.php create mode 100644 src/Infrastructure/Response/src/ForbiddenResponse.php create mode 100644 src/Infrastructure/Response/src/SuccessResponse.php create mode 100644 src/Infrastructure/Response/src/UnauthorizedResponse.php create mode 100644 src/Infrastructure/Session/config/service_manager.php create mode 100644 src/Infrastructure/Session/src/ConfigProvider.php create mode 100644 src/Infrastructure/Session/src/Middleware/LoggedInUserMiddleware.php create mode 100644 src/Infrastructure/Session/src/Middleware/SessionMiddleware.php create mode 100644 src/Infrastructure/UuidGenerator/src/UuidGenerator.php diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..892e582 --- /dev/null +++ b/.env.example @@ -0,0 +1,20 @@ +# DB Configuration +DB_DRIVER=pdo_mysql +DB_HOST=template-backend-mysql +DB_PORT=3306 +DB_USER=template +DB_PASSWORD=pass +DB_NAME=template +DB_NAME_LOG=log + +# API Keys +AUTH_API_KEY= +NOTIFICATION_API_KEY= +FILE_API_KEY= +HOMEPAGE_API_KEY= +BEE_API_KEY= + +# Template Setup +INIT_USER_NAME=admin +INIT_USER_PASSWORD=password +INIT_USER_MAIL=admin@test.com \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..88df8ad --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +/.gitattributes export-ignore +/.github/ export-ignore +/.laminas-ci.json export-ignore +/phpcs.xml.dist export-ignore +/psalm.xml.dist export-ignore +/psalm-baseline.xml export-ignore +/renovate.json export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23a615a --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +/.phpcs-cache +/.phpunit.result.cache +/.idea +/clover.xml +/coveralls-upload.json +/phpunit.xml + +/data/cache/ +/data/db/ +/public/data/ +/var/ +/vendor/ + +*.env +composer.lock +composer.development.json diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..562d1fb --- /dev/null +++ b/.htaccess @@ -0,0 +1,3 @@ +RewriteEngine On +RewriteBase / +RewriteRule ^pages/([^/]*)/(.*)$ pages.php?$1=$2 diff --git a/COPYRIGHT.md b/COPYRIGHT.md new file mode 100644 index 0000000..0a8cccc --- /dev/null +++ b/COPYRIGHT.md @@ -0,0 +1 @@ +Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/) diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..10b40f1 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,26 @@ +Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +- Neither the name of Laminas Foundation nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/bin/clear-config-cache.php b/bin/clear-config-cache.php new file mode 100644 index 0000000..b1c27ca --- /dev/null +++ b/bin/clear-config-cache.php @@ -0,0 +1,39 @@ +get('config'); + $commands = $config['console']['commands']; + + $app = new Application(); + foreach ($commands as $command) { + $app->add($container->get($command)); + } + + try { + $app->setCatchExceptions(false); + $app->run(); + } catch (Throwable $e) { + $logger = new Logger(); + $logger->error( + $e->getMessage(), + ['exception' => $e] + ); + } +}); \ No newline at end of file diff --git a/bin/createApi.php b/bin/createApi.php new file mode 100644 index 0000000..8c52026 --- /dev/null +++ b/bin/createApi.php @@ -0,0 +1,319 @@ + require __DIR__ . '/./../config/service_manager.php', + 'routes' => require __DIR__ . '/./../config/routes.php', + ]; + } +} +"; + writeToFile($configProviderFilePath, $configProviderFileContent); + + // Service Manager + $serviceManagerFilePath = $apiNamespacePath . '/config/service_manager.php'; + $serviceManagerFileContent = " [ + // Handler + {$apiHandlerName}::class => AutoWiringFactory::class, + + // Response Formatter + {$apiResponseFormatterName}::class => AutoWiringFactory::class, + ], +]; +"; + writeToFile($serviceManagerFilePath, $serviceManagerFileContent); + + // Routes + $routeName = strtolower($argv[2]) . '.' . strtolower($argv[3]); + $routePath = strtolower($argv[2]) . '/' . strtolower($argv[3]); + $routesFilePath = $apiNamespacePath . '/config/routes.php'; + $routesFileContent = " '{$routeName}', + 'path' => '/api/{$routePath}/', + 'allowed_methods' => ['POST'], + 'middleware' => [ + {$apiHandlerName}::class, + ], + ], +]; +"; + writeToFile($routesFilePath, $routesFileContent); +} + +if (!is_dir($cqrsNamespacePath)) { + // CQRS Namespace is new, create Configs + // API Namespace is new, create Configs + // Config Provider + $configProviderFilePath = $cqrsNamespacePath . '/src/ConfigProvider.php'; + $configProviderFileContent = " require __DIR__ . '/./../config/service_manager.php', + ]; + } +} +"; + writeToFile($configProviderFilePath, $configProviderFileContent); + + // Service Manager + $serviceManagerFilePath = $cqrsNamespacePath . '/config/service_manager.php'; + $serviceManagerFileContent = " [ + /// CQRS + // {$apiName} + {$cqrsBuilderName}::class => AutoWiringFactory::class, + {$cqrsHandlerName}::class => AutoWiringFactory::class, + ], +]; +"; + writeToFile($serviceManagerFilePath, $serviceManagerFileContent); +} + +########## WRITE FILES ############### +$apiHandlerFileContent = "getAttribute(AnalyzeBodyMiddleware::JSON_DATA); + + \${$cqrsVariableName} = \$this->{$cqrsBuilderVariableName}->build( + \$data + ); + \$result = \$this->{$cqrsHandlerVariableName}->execute(\${$cqrsVariableName}); + + return new SuccessResponse(\$this->responseFormatter->format(\$result)); + } +} +"; +writeToFile($apiHandlerFilePath, $apiHandlerFileContent); + +$apiResponseFormatterFileContent = " $stepClassName, + 'stepVariableName' => lcfirst($stepClassName), + 'stepFilePath' => $stepsFilePath . $stepClassName . '.php', + 'stepUsingNamespace' => 'use ' . $payloadFullNamespace . '\\Step\\' . $stepClassName, + ]; +} + + + + +########## WRITE FILES ############### +function writeToFile($path, $content) { + echo 'Writing contents to file ' . $path . PHP_EOL; + + $directory = pathinfo($path, PATHINFO_DIRNAME); + if (!is_dir($directory)) { + mkdir($directory, 0755, true); + } + + file_put_contents($path, $content); +} + +$stepsUsingNamespaces = []; +$stepsDeclarations = []; +$stepsReferences = []; + +foreach ($steps as $step) { + $stepClassName = $step['stepClassName']; + $stepVariableName = $step['stepVariableName']; + $stepFilePath = $step['stepFilePath']; + $stepUsingNamespace = $step['stepUsingNamespace']; + + $stepsUsingNamespaces[] = $stepUsingNamespace . ';'; + $stepsDeclarations[] = 'private readonly ' . $stepClassName . ' $' . $stepVariableName . ','; + $stepsReferences[] = '$this->' . $stepVariableName . ','; + + $stepFileContent = "next()(\$payload, \$pipeline); + } +} +"; + writeToFile($stepFilePath, $stepFileContent); +} + +$stepsUsingNamespace = implode(PHP_EOL, $stepsUsingNamespaces); +$stepsDeclaration = implode(PHP_EOL . ' ', $stepsDeclarations); +$stepsReference = implode(PHP_EOL . ' ', $stepsReferences); + + + +$pipelineFileContent = "get('config'); +$doctrineConfig = $config['doctrine']; +$paths = $doctrineConfig['driver']['orm_log_annotation_driver']['paths']; + +$dbParams = $doctrineConfig['connection']['orm_log']['params']; +$migrationsConf = $doctrineConfig['migrations_configuration']['orm_log']; + +$reader = new AnnotationReader(); +$driver = new AnnotationDriver($reader, $paths); + +$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode); +$config->setMetadataDriverImpl($driver); +$entityManager = $container->get(EntityManager::class); + +try { + $connection = DriverManager::getConnection($dbParams); +} catch (\Doctrine\DBAL\Exception $e) { + echo $e->getMessage(); + exit; +} + +$configuration = new Configuration($connection); + +$configuration->addMigrationsDirectory( + $migrationsConf['namespace'], + $migrationsConf['directory'] +); +$configuration->setAllOrNothing(true); +$configuration->setCheckDatabasePlatform(false); + +$storageConfiguration = new TableMetadataStorageConfiguration(); +$storageConfiguration->setTableName($migrationsConf['table']); + +$configuration->setMetadataStorageConfiguration($storageConfiguration); + +$dependencyFactory = DependencyFactory::fromConnection( + new ExistingConfiguration($configuration), + new ExistingConnection($connection) +); + +$cli = new Application('Doctrine Migrations'); +$cli->setCatchExceptions(true); + +$cli->addCommands([ + new DumpSchemaCommand($dependencyFactory), + new ExecuteCommand($dependencyFactory), + new GenerateCommand($dependencyFactory), + new LatestCommand($dependencyFactory), + new ListCommand($dependencyFactory), + new MigrateCommand($dependencyFactory), + new RollupCommand($dependencyFactory), + new StatusCommand($dependencyFactory), + new SyncMetadataCommand($dependencyFactory), + new VersionCommand($dependencyFactory), +]); + +try { + $cli->run(); +} catch (Exception $e) { + echo $e->getMessage(); +} diff --git a/bin/doctrine-migrations.php b/bin/doctrine-migrations.php new file mode 100644 index 0000000..879eb04 --- /dev/null +++ b/bin/doctrine-migrations.php @@ -0,0 +1,91 @@ +get('config'); +$doctrineConfig = $config['doctrine']; +$paths = $doctrineConfig['driver']['orm_template_annotation_driver']['paths']; + +$dbParams = $doctrineConfig['connection']['orm_template']['params']; +$migrationsConf = $doctrineConfig['migrations_configuration']['orm_template']; + +$reader = new AnnotationReader(); +$driver = new AnnotationDriver($reader, $paths); + +$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode); +$config->setMetadataDriverImpl($driver); +$entityManager = $container->get(EntityManager::class); + +try { + $connection = DriverManager::getConnection($dbParams); +} catch (\Doctrine\DBAL\Exception $e) { + echo $e->getMessage(); + exit; +} + +$configuration = new Configuration($connection); + +$configuration->addMigrationsDirectory( + $migrationsConf['namespace'], + $migrationsConf['directory'] +); +$configuration->setAllOrNothing(true); +$configuration->setCheckDatabasePlatform(false); + +$storageConfiguration = new TableMetadataStorageConfiguration(); +$storageConfiguration->setTableName($migrationsConf['table']); + +$configuration->setMetadataStorageConfiguration($storageConfiguration); + +$dependencyFactory = DependencyFactory::fromConnection( + new ExistingConfiguration($configuration), + new ExistingConnection($connection) +); + +$cli = new Application('Doctrine Migrations'); +$cli->setCatchExceptions(true); + +$cli->addCommands([ + new DumpSchemaCommand($dependencyFactory), + new ExecuteCommand($dependencyFactory), + new GenerateCommand($dependencyFactory), + new LatestCommand($dependencyFactory), + new ListCommand($dependencyFactory), + new MigrateCommand($dependencyFactory), + new RollupCommand($dependencyFactory), + new StatusCommand($dependencyFactory), + new SyncMetadataCommand($dependencyFactory), + new VersionCommand($dependencyFactory), +]); + +try { + $cli->run(); +} catch (Exception $e) { + echo $e->getMessage(); +} diff --git a/bin/script/init b/bin/script/init new file mode 100755 index 0000000..f2baa98 --- /dev/null +++ b/bin/script/init @@ -0,0 +1,36 @@ +#!/bin/bash + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +PROJECT_DIR=$(realpath $SCRIPT_DIR/../../) +ENV_DIR=$(realpath $PROJECT_DIR/../../../) + +# Check .env file +if [ ! -f "$PROJECT_DIR/.env" ] +then + echo "Create .env file from example..." + cp "$PROJECT_DIR/.env.example" "$PROJECT_DIR/.env" + echo ".env file created, please change variables and call init again" + exit 1 +fi + +# Source key-scripts +source $ENV_DIR/bin/drun +source $ENV_DIR/bin/dexec + +# Build and start docker containers +dexec template-backend build +dexec template-backend up -d + +# Install PHP packages +drun template-backend composer install + +# Dump autoload +drun template-backend composer da + +# Migrate databases to current version +drun template-backend composer dmm +drun template-backend composer dmlm + +# Insert setup for project after this line +drun template-backend composer console rbac:update +drun template-backend composer console init:data \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..6413085 --- /dev/null +++ b/composer.json @@ -0,0 +1,76 @@ +{ + "require": { + "laminas/laminas-servicemanager": "^3.21", + "mezzio/mezzio-laminasrouter": "^3.8", + "reinfi/zf-dependency-injection": "^5.4", + "laminas/laminas-config-aggregator": "^1.13", + "doctrine/doctrine-orm-module": "5.0.*", + "doctrine/migrations": "^3.5", + "doctrine/orm": "2.11.3", + "doctrine/persistence": "2.2.*", + "ramsey/uuid-doctrine": "^1.8", + "roave/psr-container-doctrine": "^3.1", + "mezzio/mezzio": "^3.17", + "mezzio/mezzio-helpers": "^5.15", + "symfony/cache": "5.4.8", + "doctrine/dbal": "^3.6", + "teewurst/psr4-advanced-wildcard-composer-plugin": "^3.0", + "laminas/laminas-crypt": "^3.10", + "monolog/monolog": "^3.4", + "laminas/laminas-mail": "^2.23", + "teewurst/pipeline": "^3.0", + "guzzlehttp/guzzle": "^7.8" + }, + "autoload": { + "psr-4": { + "Template\\API\\Console\\": "src/ApiDomain/Console/src/" + } + }, + "extra": { + "teewurst/psr4-advanced-wildcard-composer-plugin": { + "autoload": { + "psr-4": { + "Template\\API\\%s\\%s\\": "src/ApiDomain/{*}/{*}/src/", + "Template\\Data\\%s\\": "src/DataDomain/{*}/src/", + "Template\\Handling\\%s\\": "src/HandlingDomain/{*}/src/", + "Template\\Infrastructure\\%s\\": "src/Infrastructure/{*}/src/" + } + }, + "autoload-dev": { + "psr-4": { + "Template\\API\\%s\\%s\\": "src/ApiDomain/{*}/{*}/src/", + "Template\\Data\\%s\\": "src/DataDomain/{*}/src/", + "Template\\Handling\\%s\\": "src/HandlingDomain/{*}/src/", + "Template\\Infrastructure\\%s\\": "src/Infrastructure/{*}/src/" + } + } + } + }, + "scripts": { + "dmg": "php bin/doctrine-migrations.php migrations:generate --no-interaction", + "dmm": "php bin/doctrine-migrations.php migrations:migrate --no-interaction", + "dmlg": "php bin/doctrine-migrations-log.php migrations:generate --no-interaction", + "dmlm": "php bin/doctrine-migrations-log.php migrations:migrate --no-interaction", + "console": "php bin/console.php", + "createApi": "php bin/createApi.php", + "createPipeline": "php bin/createPipeline.php", + "da": [ + "composer dump-autoload", + "composer dump-autoload --dev" + ], + "initdb": [ + "@composer dmm", + "@composer dmlm", + "@composer console rbac:update", + "@composer console init:data" + ] + }, + "config": { + "allow-plugins": { + "teewurst/psr4-advanced-wildcard-composer-plugin": true + } + }, + "require-dev": { + "symfony/dotenv": "^6.3" + } +} diff --git a/config/autoload/.gitignore b/config/autoload/.gitignore new file mode 100644 index 0000000..1a83fda --- /dev/null +++ b/config/autoload/.gitignore @@ -0,0 +1,2 @@ +local.php +*.local.php diff --git a/config/autoload/api.global.php b/config/autoload/api.global.php new file mode 100644 index 0000000..35e85c3 --- /dev/null +++ b/config/autoload/api.global.php @@ -0,0 +1,29 @@ + [ + 'keys' => [ + 'template' => $_ENV['HOMEPAGE_API_KEY'], + 'notification' => $_ENV['NOTIFICATION_API_KEY'], + ], + + 'services' => [ + 'template' => [ + 'host' => 'template-backend-nginx', + 'apis' => [ + + ] + ], + 'notification' => [ + 'host' => 'notification-backend-nginx', + 'apis' => [ + 'send-mail' => [ + 'path' => '/api/mail/send', + 'method' => 'POST' + ], + ], + ], + ], + ], +]; + diff --git a/config/autoload/authorization.global.php b/config/autoload/authorization.global.php new file mode 100644 index 0000000..d1b8b23 --- /dev/null +++ b/config/autoload/authorization.global.php @@ -0,0 +1,22 @@ + [ + 'roles' => [ + 'admin', + 'user', + ], + 'permissions' => [ + 'user' => [ + 'product.product-list', + ], + 'admin' => [ + 'product.create-product', + 'product.delete-product', + 'product.product-list', + 'product.update-product', + 'user.create-user', + ], + ] + ] +]; \ No newline at end of file diff --git a/config/autoload/defines.php b/config/autoload/defines.php new file mode 100644 index 0000000..85b3a2a --- /dev/null +++ b/config/autoload/defines.php @@ -0,0 +1,6 @@ + [ + // 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' => [ + // Fully\Qualified\ClassOrInterfaceName::class => Fully\Qualified\ClassName::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. + 'invokables' => [ + // Fully\Qualified\InterfaceName::class => Fully\Qualified\ClassName::class, + ], + // Use 'factories' for services provided by callbacks/factory classes. + 'factories' => [ + // Fully\Qualified\ClassName::class => Fully\Qualified\FactoryName::class, + ], + ], +]; diff --git a/config/autoload/doctrine.global.php b/config/autoload/doctrine.global.php new file mode 100644 index 0000000..c32e202 --- /dev/null +++ b/config/autoload/doctrine.global.php @@ -0,0 +1,14 @@ + [ + 'types' => [ + UuidBinaryOrderedTimeType::NAME => UuidBinaryOrderedTimeType::class, + ], + ], +]; diff --git a/config/autoload/logger.global.php b/config/autoload/logger.global.php new file mode 100644 index 0000000..3a3fee7 --- /dev/null +++ b/config/autoload/logger.global.php @@ -0,0 +1,14 @@ + [ + 'name' => 'template.backend', + 'path' => APP_ROOT . '/var/log/template.backend.log', + 'level' => Level::Debug, + 'pretty' => true, + ] +]; diff --git a/config/autoload/mezzio.global.php b/config/autoload/mezzio.global.php new file mode 100644 index 0000000..b6b10d1 --- /dev/null +++ b/config/autoload/mezzio.global.php @@ -0,0 +1,24 @@ + false, + + // Enable debugging; typically used to provide debugging information within templates. + 'debug' => false, + 'mezzio' => [ + // Provide templates for the error handling middleware to use when + // generating responses. + 'error_handler' => [ + 'template_404' => 'error::404', + 'template_error' => 'error::error', + ], + ], +]; diff --git a/config/config.php b/config/config.php new file mode 100644 index 0000000..814b150 --- /dev/null +++ b/config/config.php @@ -0,0 +1,89 @@ + 'data/cache/config-cache.php', +]; + +$aggregator = new ConfigAggregator([ + // Include cache configuration + new ArrayProvider($cacheConfig), + \Mezzio\Helper\ConfigProvider::class, + \Mezzio\ConfigProvider::class, + \Mezzio\Router\ConfigProvider::class, + \Mezzio\Router\LaminasRouter\ConfigProvider::class, + + \Laminas\Diactoros\ConfigProvider::class, + \Laminas\HttpHandlerRunner\ConfigProvider::class, + \Laminas\Validator\ConfigProvider::class, + \Laminas\Router\ConfigProvider::class, + + \Reinfi\DependencyInjection\ConfigProvider::class, + + \DoctrineORMModule\ConfigProvider::class, + \DoctrineModule\ConfigProvider::class, + + + // Swoole config to overwrite some services (if installed) + class_exists(\Mezzio\Swoole\ConfigProvider::class) + ? \Mezzio\Swoole\ConfigProvider::class + : function (): array { + return []; + }, + + + // Data + \Template\Data\Business\ConfigProvider::class, + \Template\Data\Log\ConfigProvider::class, + + // Infrastructure + \Template\Infrastructure\Database\ConfigProvider::class, + \Template\Infrastructure\DependencyInjection\ConfigProvider::class, + \Template\Infrastructure\Encryption\ConfigProvider::class, + \Template\Infrastructure\Exception\ConfigProvider::class, + \Template\Infrastructure\Logging\ConfigProvider::class, + \Template\Infrastructure\Rbac\ConfigProvider::class, + \Template\Infrastructure\Request\ConfigProvider::class, + \Template\Infrastructure\Session\ConfigProvider::class, + + // HandlingDomain + \Template\Handling\User\ConfigProvider::class, + \Template\Handling\UserSession\ConfigProvider::class, + \Template\Handling\Registration\ConfigProvider::class, + + // API + /// Command + \Template\API\Console\ConfigProvider::class, + + /// External + \Template\API\External\Health\ConfigProvider::class, + \Template\API\External\User\ConfigProvider::class, + \Template\API\External\Authentication\ConfigProvider::class, + + /// Internal + + + + + + + // Load application config in a pre-defined order in such a way that local settings + // overwrite global settings. (Loaded as first to last): + // - `global.php` + // - `*.global.php` + // - `local.php` + // - `*.local.php` + new PhpFileProvider(realpath(__DIR__) . '/autoload/{{,*.}global,{,*.}local}.php'), + + // Load development config if it exists + new PhpFileProvider(realpath(__DIR__) . '/development.config.php'), +], $cacheConfig['config_cache_path']); + +return $aggregator->getMergedConfig(); diff --git a/config/container.php b/config/container.php new file mode 100644 index 0000000..e13f484 --- /dev/null +++ b/config/container.php @@ -0,0 +1,21 @@ +load(__DIR__ . '/../.env'); +} + +// Load configuration +$config = require __DIR__ . '/config.php'; + +$dependencies = $config['dependencies']; +$dependencies['services']['config'] = $config; + +// Build container +return new ServiceManager($dependencies); diff --git a/config/development.config.php b/config/development.config.php new file mode 100644 index 0000000..94b06d2 --- /dev/null +++ b/config/development.config.php @@ -0,0 +1,31 @@ + true, + ConfigAggregator::ENABLE_CACHE => false, +]; diff --git a/config/pipeline.php b/config/pipeline.php new file mode 100644 index 0000000..4d19afa --- /dev/null +++ b/config/pipeline.php @@ -0,0 +1,94 @@ +pipe(ErrorHandler::class); + $app->pipe(ServerUrlMiddleware::class); + + // Pipe more middleware here that you want to execute on every request: + // - bootstrapping + // - pre-conditions + // - modifications to outgoing responses + // + // Piped Middleware may be either callables or service names. Middleware may + // also be passed as an array; each item in the array must resolve to + // middleware eventually (i.e., callable or service name). + // + // Middleware can be attached to specific paths, allowing you to mix and match + // applications under a common domain. The handlers in each middleware + // attached this way will see a URI with the matched path segment removed. + // + // i.e., path of "/api/member/profile" only passes "/member/profile" to $apiMiddleware + // - $app->pipe('/api', $apiMiddleware); + // - $app->pipe('/docs', $apiDocMiddleware); + // - $app->pipe('/files', $filesMiddleware); + + // Register the routing middleware in the middleware pipeline. + // This middleware registers the Mezzio\Router\RouteResult request attribute. + $app->pipe(RouteMiddleware::class); + + // The following handle routing failures for common conditions: + // - HEAD request but no routes answer that method + // - OPTIONS request but no routes answer that method + // - method not allowed + // Order here matters; the MethodNotAllowedMiddleware should be placed + // after the Implicit*Middleware. + $app->pipe(ImplicitHeadMiddleware::class); + $app->pipe(ImplicitOptionsMiddleware::class); + $app->pipe(MethodNotAllowedMiddleware::class); + + // Seed the UrlHelper with the routing results: + $app->pipe(UrlHelperMiddleware::class); + + + + + //// Pre Template Space + $app->pipe(ExceptionHandlerMiddleware::class); + $app->pipe(AnalyzeHeaderMiddleware::class); + $app->pipe(AnalyzeBodyMiddleware::class); + + //// Template Space + $app->pipe(SessionMiddleware::class); + + + + // Add more middleware here that needs to introspect the routing results; this + // might include: + // + // - route-based authentication + // - route-based validation + // - etc. + + // Register the dispatch middleware in the middleware pipeline + $app->pipe(DispatchMiddleware::class); + + // At this point, if no Response is returned by any middleware, the + // NotFoundHandler kicks in; alternately, you can provide other fallback + // middleware to execute. + $app->pipe(NotFoundHandler::class); +}; diff --git a/config/routes.php b/config/routes.php new file mode 100644 index 0000000..a2523a1 --- /dev/null +++ b/config/routes.php @@ -0,0 +1,52 @@ +get('/', App\Handler\HomePageHandler::class, 'home'); + * $app->post('/album', App\Handler\AlbumCreateHandler::class, 'album.create'); + * $app->put('/album/:id', App\Handler\AlbumUpdateHandler::class, 'album.put'); + * $app->patch('/album/:id', App\Handler\AlbumUpdateHandler::class, 'album.patch'); + * $app->delete('/album/:id', App\Handler\AlbumDeleteHandler::class, 'album.delete'); + * + * Or with multiple request methods: + * + * $app->route('/contact', App\Handler\ContactHandler::class, ['GET', 'POST', ...], 'contact'); + * + * Or handling all request methods: + * + * $app->route('/contact', App\Handler\ContactHandler::class)->setName('contact'); + * + * or: + * + * $app->route( + * '/contact', + * App\Handler\ContactHandler::class, + * Mezzio\Router\Route::HTTP_METHOD_ANY, + * 'contact' + * ); + */ + +return static function (Application $app, MiddlewareFactory $factory, ContainerInterface $container): void { + $config = $container->get('config'); + + foreach ($config['routes'] as $routeConfig) { + $app->route( + name: $routeConfig['name'], + path: $routeConfig['path'], + middleware: $routeConfig['middleware'], + methods: $routeConfig['allowed_methods'] + ); + } +}; diff --git a/data/migrations/business/Version20230922085011.php b/data/migrations/business/Version20230922085011.php new file mode 100644 index 0000000..de5b8ec --- /dev/null +++ b/data/migrations/business/Version20230922085011.php @@ -0,0 +1,32 @@ +addSql($sql); + } + + public function down(Schema $schema): void + { + $this->addSql("DROP TABLE role;"); + } +} diff --git a/data/migrations/business/Version20230922092351.php b/data/migrations/business/Version20230922092351.php new file mode 100644 index 0000000..a759ef4 --- /dev/null +++ b/data/migrations/business/Version20230922092351.php @@ -0,0 +1,35 @@ +addSql($sql); + } + + public function down(Schema $schema): void + { + $this->addSql("DROP TABLE permission;"); + } +} diff --git a/data/migrations/business/Version20230922092754.php b/data/migrations/business/Version20230922092754.php new file mode 100644 index 0000000..2bbcb56 --- /dev/null +++ b/data/migrations/business/Version20230922092754.php @@ -0,0 +1,36 @@ +addSql($sql); + } + + public function down(Schema $schema): void + { + $this->addSql("DROP TABLE role_permission;"); + } +} diff --git a/data/migrations/business/Version20230922101354.php b/data/migrations/business/Version20230922101354.php new file mode 100644 index 0000000..51cfcd8 --- /dev/null +++ b/data/migrations/business/Version20230922101354.php @@ -0,0 +1,41 @@ +addSql($sql); + } + + public function down(Schema $schema): void + { + $this->addSql("DROP TABLE user;"); + } +} diff --git a/data/migrations/business/Version20230922101355.php b/data/migrations/business/Version20230922101355.php new file mode 100644 index 0000000..296a9de --- /dev/null +++ b/data/migrations/business/Version20230922101355.php @@ -0,0 +1,38 @@ +addSql($sql); + } + + public function down(Schema $schema): void + { + $this->addSql("DROP TABLE role_permission;"); + } +} diff --git a/data/migrations/business/Version20230924113403.php b/data/migrations/business/Version20230924113403.php new file mode 100644 index 0000000..fbd6512 --- /dev/null +++ b/data/migrations/business/Version20230924113403.php @@ -0,0 +1,37 @@ +addSql($sql); + } + + public function down(Schema $schema): void + { + $this->addSql("DROP TABLE registration;"); + } +} diff --git a/data/migrations/log/Version20230922150649.php b/data/migrations/log/Version20230922150649.php new file mode 100644 index 0000000..bbc7eae --- /dev/null +++ b/data/migrations/log/Version20230922150649.php @@ -0,0 +1,40 @@ +addSql($query); + } + + public function down(Schema $schema): void + { + $this->addSql('DROP TABLE log'); + } +} diff --git a/docker/docker-compose-mac.yml b/docker/docker-compose-mac.yml new file mode 100644 index 0000000..2b93ebd --- /dev/null +++ b/docker/docker-compose-mac.yml @@ -0,0 +1,50 @@ +networks: + template: + external: true + +services: + template-backend-mysql: + image: template-backend-mysql + networks: + - template + build: + context: ./../ + dockerfile: ./docker/mysql/dockerfile + volumes: + - /Users/flo/dev/backend/template/var/db:/var/lib/mysql:z + environment: + MYSQL_USER: ${DB_USER} + MYSQL_PASSWORD: ${DB_PASSWORD} + ports: + - 3306:3306 + healthcheck: + test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] + timeout: 20s + retries: 10 + + template-backend-app: + image: template-backend-app + networks: + - template + build: + context: ./../ + dockerfile: ./docker/php/dockerfile + volumes: + - /Users/flo/dev/backend/template/:/var/www/html:z + ports: + - 9000:9000 + depends_on: + template-backend-mysql: + condition: service_healthy + + template-backend-nginx: + image: template-backend-nginx + networks: + - template + build: + context: ./../ + dockerfile: ./docker/nginx/dockerfile + ports: + - 8080:80 + depends_on: + - template-backend-app \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..4f0f616 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,50 @@ +networks: + template: + external: true + +services: + template-backend-mysql: + image: template-backend-mysql + networks: + - template + build: + context: ./../ + dockerfile: ./docker/mysql/dockerfile + volumes: + - ./../var/db:/var/lib/mysql:z + environment: + MYSQL_USER: ${DB_USER} + MYSQL_PASSWORD: ${DB_PASSWORD} + ports: + - 3306:3306 + healthcheck: + test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"] + timeout: 20s + retries: 10 + + template-backend-app: + image: template-backend-app + networks: + - template + build: + context: ./../ + dockerfile: ./docker/php/dockerfile + volumes: + - ./../:/var/www/html:z + ports: + - 9000:9000 + depends_on: + template-backend-mysql: + condition: service_healthy + + template-backend-nginx: + image: template-backend-nginx + networks: + - template + build: + context: ./../ + dockerfile: ./docker/nginx/dockerfile + ports: + - 8080:80 + depends_on: + - template-backend-app \ No newline at end of file diff --git a/docker/mysql/dockerfile b/docker/mysql/dockerfile new file mode 100644 index 0000000..8261387 --- /dev/null +++ b/docker/mysql/dockerfile @@ -0,0 +1,7 @@ +FROM mysql:latest + +ENV MYSQL_RANDOM_ROOT_PASSWORD=1 + +COPY docker/mysql/scripts /docker-entrypoint-initdb.d/ + +CMD ["mysqld"] \ No newline at end of file diff --git a/docker/mysql/scripts/initdb.sql b/docker/mysql/scripts/initdb.sql new file mode 100644 index 0000000..206f6e9 --- /dev/null +++ b/docker/mysql/scripts/initdb.sql @@ -0,0 +1,4 @@ +CREATE DATABASE IF NOT EXISTS `log`; +CREATE DATABASE IF NOT EXISTS `template`; + +GRANT ALL PRIVILEGES on *.* to 'template'@'%'; \ No newline at end of file diff --git a/docker/nginx/config/nginx.conf b/docker/nginx/config/nginx.conf new file mode 100644 index 0000000..16cb672 --- /dev/null +++ b/docker/nginx/config/nginx.conf @@ -0,0 +1,15 @@ +upstream host-backend-app { + server template-backend-app:9000; +} + +server { + listen 80 default_server; + + location / { + fastcgi_pass host-backend-app; + fastcgi_index index.php; + + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME /var/www/html/public/index.php; + } +} \ No newline at end of file diff --git a/docker/nginx/config/upstream.conf b/docker/nginx/config/upstream.conf new file mode 100644 index 0000000..e12c63a --- /dev/null +++ b/docker/nginx/config/upstream.conf @@ -0,0 +1,3 @@ +upstream backend-dev { + server backend-app:9000; +} \ No newline at end of file diff --git a/docker/nginx/dockerfile b/docker/nginx/dockerfile new file mode 100644 index 0000000..f2aba1c --- /dev/null +++ b/docker/nginx/dockerfile @@ -0,0 +1,5 @@ +FROM nginx:alpine + +COPY docker/nginx/config/nginx.conf /etc/nginx/conf.d/default.conf + +CMD ["nginx", "-g", "daemon off;"] diff --git a/docker/php/dockerfile b/docker/php/dockerfile new file mode 100644 index 0000000..de3ffa5 --- /dev/null +++ b/docker/php/dockerfile @@ -0,0 +1,15 @@ +FROM php:8.1-fpm + +RUN apt-get -y update +RUN apt-get -y install git + +RUN chown -R www-data:www-data /var/www/html +WORKDIR /var/www/html + +RUN docker-php-ext-install pdo pdo_mysql +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +RUN useradd -m docker && echo "docker:docker" | chpasswd && adduser docker sudo +USER docker + +CMD ["php-fpm"] \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..f0e7212 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,26 @@ + + + + + test + + + + + + src + + + diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..b43deeb --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,19 @@ +RewriteEngine On +# The following rule allows authentication to work with fast-cgi +RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] +# The following rule tells Apache that if the requested filename +# exists, simply serve it. +RewriteCond %{REQUEST_FILENAME} -f [OR] +RewriteCond %{REQUEST_FILENAME} -l [OR] +RewriteCond %{REQUEST_FILENAME} -d +RewriteRule ^ - [NC,L] + +# The following rewrites all other queries to index.php. The +# condition ensures that if you are using Apache aliases to do +# mass virtual hosting, the base path will be prepended to +# allow proper resolution of the index.php file; it will work +# in non-aliased environments as well, providing a safe, one-size +# fits all solution. +RewriteCond $0::%{REQUEST_URI} ^([^:]*+(?::[^:]*+)*?)::(/.+?)\1$ +RewriteRule .+ - [E=BASE:%2] +RewriteRule .* %{ENV:BASE}index.php [NC,L] diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..0323e39 --- /dev/null +++ b/public/index.php @@ -0,0 +1,48 @@ +getMessage(); + $e = $e->getPrevious(); + } + return $messageArray; +} + +function throwableToMessageArrayString(Throwable $e): string { + return implode( PHP_EOL, throwableToMessageArray($e) ); +} + +/** + * Self-called anonymous function that creates its own scope and keeps the global namespace clean. + */ +(function () { + + /** @var \Psr\Container\ContainerInterface $container */ + $container = require './../config/container.php'; + + /** @var Application $app */ + $app = $container->get(Application::class); + $factory = $container->get(MiddlewareFactory::class); + + // Execute programmatic/declarative middleware pipeline and routing + // configuration statements + (require './../config/pipeline.php')($app, $factory, $container); + (require './../config/routes.php')($app, $factory, $container); + + $app->run(); + +})(); diff --git a/src/ApiDomain/Console/config/console.php b/src/ApiDomain/Console/config/console.php new file mode 100644 index 0000000..20093d4 --- /dev/null +++ b/src/ApiDomain/Console/config/console.php @@ -0,0 +1,11 @@ + [ + InitializeDataCommand::class, + RbacUpdateCommand::class, + ] +]; diff --git a/src/ApiDomain/Console/config/service_manager.php b/src/ApiDomain/Console/config/service_manager.php new file mode 100644 index 0000000..9aa3afc --- /dev/null +++ b/src/ApiDomain/Console/config/service_manager.php @@ -0,0 +1,12 @@ + [ + InitializeDataCommand::class => AutoWiringFactory::class, + RbacUpdateCommand::class => AutoWiringFactory::class, + ], +]; diff --git a/src/ApiDomain/Console/src/Command/InitializeDataCommand.php b/src/ApiDomain/Console/src/Command/InitializeDataCommand.php new file mode 100644 index 0000000..0d402ba --- /dev/null +++ b/src/ApiDomain/Console/src/Command/InitializeDataCommand.php @@ -0,0 +1,80 @@ +getName()); + + $this->roleRepository = $this->entityManager->getRepository(Role::class); + $this->userRepository = $this->entityManager->getRepository(User::class); + } + + protected function execute( + InputInterface $input, + OutputInterface $output + ): int { + $io = new SymfonyStyle($input, $output); + + try { + $admin = $this->roleRepository->findOneBy([ + 'identifier' => 'admin' + ]) ?? null; + if ($admin === null) { + throw new \Exception('Admin role does not exist!'); + } + + $adminUser = $this->userRepository->findOneBy([ + 'roleId' => $admin->getId() + ]) ?? null; + + if ($adminUser === null) { + $io->info('Create admin'); + + $adminUser = new User(); + $adminUser->setRole($admin); + $adminUser->setUsername($_ENV['INIT_USER_NAME']); + $adminUser->setMail($_ENV['INIT_USER_MAIL']); + $adminUser->setPassword( + $this->encryptionClient->encrypt( + $_ENV['INIT_USER_PASSWORD'] + ) + ); + + $this->entityManager->persist($adminUser); + $this->entityManager->flush(); + } + + $io->success('OK!'); + } catch (\Throwable $e) { + $io->error($e->getMessage()); + $io->error($e->getTraceAsString()); + $this->logger->error($e->getMessage(), ['exception' => $e]); + return Command::FAILURE; + } + + return Command::SUCCESS; + } +} diff --git a/src/ApiDomain/Console/src/Command/RbacUpdateCommand.php b/src/ApiDomain/Console/src/Command/RbacUpdateCommand.php new file mode 100644 index 0000000..bb4a2be --- /dev/null +++ b/src/ApiDomain/Console/src/Command/RbacUpdateCommand.php @@ -0,0 +1,98 @@ +getName()); + + $this->roleRepository = $this->entityManager->getRepository(Role::class); + $this->permissionRepository = $this->entityManager->getRepository(Permission::class); + + $this->rbacConfig = $this->configService->resolve('template-rbac')->toArray(); + } + + protected function execute( + InputInterface $input, + OutputInterface $output + ): int { + $io = new SymfonyStyle($input, $output); + + try { + + $roles = []; + + foreach ($this->rbacConfig['roles'] as $roleIdentifier) { + $role = $this->roleRepository->findOneBy([ + 'identifier' => $roleIdentifier + ]) ?? null; + + if ($role === null) { + $role = new Role(); + $role->setIdentifier($roleIdentifier); + + $this->entityManager->persist($role); + $this->entityManager->flush(); + } + + $roles[$roleIdentifier] = $role; + } + + foreach ($roles as $role) { + $roleIdentifier = $role->getIdentifier(); + + foreach ($this->rbacConfig['permissions'][$roleIdentifier] as $permissionIdentifier) { + $io->info($role->getIdentifier() . '.' . $permissionIdentifier); + + $permission = $this->permissionRepository->findOneBy([ + 'identifier' => $permissionIdentifier + ]) ?? null; + + if ($permission === null) { + $permission = new Permission(); + $permission->setIdentifier($permissionIdentifier); + + $this->entityManager->persist($permission); + $this->entityManager->flush(); + } + + $role->addPermission($permission); + $this->entityManager->persist($role); + $this->entityManager->flush(); + } + } + + $io->success('OK!'); + } catch (\Throwable $e) { + $io->error($e->getMessage()); + $io->error($e->getTraceAsString()); + $this->logger->error($e->getMessage(), ['exception' => $e]); + return Command::FAILURE; + } + + return Command::SUCCESS; + } +} diff --git a/src/ApiDomain/Console/src/ConfigProvider.php b/src/ApiDomain/Console/src/ConfigProvider.php new file mode 100644 index 0000000..e9a5efc --- /dev/null +++ b/src/ApiDomain/Console/src/ConfigProvider.php @@ -0,0 +1,16 @@ + require __DIR__ . './../config/service_manager.php', + 'console' => require __DIR__ . '/./../config/console.php', + ]; + } +} diff --git a/src/ApiDomain/External/Authentication/config/routes.php b/src/ApiDomain/External/Authentication/config/routes.php new file mode 100644 index 0000000..b776574 --- /dev/null +++ b/src/ApiDomain/External/Authentication/config/routes.php @@ -0,0 +1,43 @@ + 'auth.login-user', + 'path' => '/api/auth/login-user', + 'allowed_methods' => ['POST'], + 'middleware' => [ + LoginUserHandler::class + ], + ], + [ + 'name' => 'auth.logout-user', + 'path' => '/api/auth/logout-user', + 'allowed_methods' => ['POST'], + 'middleware' => [ + LoggedInUserMiddleware::class, + LogoutUserHandler::class + ], + ], + [ + 'name' => 'auth.confirm-registration', + 'path' => '/api/auth/confirm-registration', + 'allowed_methods' => ['POST'], + 'middleware' => [ + ConfirmRegistrationHandler::class + ], + ], + [ + 'name' => 'auth.register-user', + 'path' => '/api/auth/register-user', + 'allowed_methods' => ['POST'], + 'middleware' => [ + RegisterUserHandler::class + ], + ], +]; \ No newline at end of file diff --git a/src/ApiDomain/External/Authentication/config/service_manager.php b/src/ApiDomain/External/Authentication/config/service_manager.php new file mode 100644 index 0000000..fc34f4e --- /dev/null +++ b/src/ApiDomain/External/Authentication/config/service_manager.php @@ -0,0 +1,17 @@ + [ + // Handler + LoginUserHandler::class => AutoWiringFactory::class, + LogoutUserHandler::class => AutoWiringFactory::class, + ConfirmRegistrationHandler::class => AutoWiringFactory::class, + RegisterUserHandler::class => AutoWiringFactory::class + ], +]; diff --git a/src/ApiDomain/External/Authentication/src/ConfigProvider.php b/src/ApiDomain/External/Authentication/src/ConfigProvider.php new file mode 100644 index 0000000..5a5446d --- /dev/null +++ b/src/ApiDomain/External/Authentication/src/ConfigProvider.php @@ -0,0 +1,16 @@ + require __DIR__ . '/./../config/service_manager.php', + 'routes' => require __DIR__ . '/./../config/routes.php', + ]; + } +} diff --git a/src/ApiDomain/External/Authentication/src/Handler/ConfirmRegistrationHandler.php b/src/ApiDomain/External/Authentication/src/Handler/ConfirmRegistrationHandler.php new file mode 100644 index 0000000..62521c8 --- /dev/null +++ b/src/ApiDomain/External/Authentication/src/Handler/ConfirmRegistrationHandler.php @@ -0,0 +1,44 @@ +getBody()->getContents(), + true + ); + + $query = $this->builder->build( + Uuid::fromString($data['id']), + $data['password'], + $data['passwordConfirmation'], + ); + + $result = $this->handler->execute($query); + return new JsonResponse( + $this->userFormatter->format($result) + ); + } +} diff --git a/src/ApiDomain/External/Authentication/src/Handler/LoginUserHandler.php b/src/ApiDomain/External/Authentication/src/Handler/LoginUserHandler.php new file mode 100644 index 0000000..9582ec8 --- /dev/null +++ b/src/ApiDomain/External/Authentication/src/Handler/LoginUserHandler.php @@ -0,0 +1,42 @@ +getAttribute(SessionMiddleware::SESSION_ATTRIBUTE); + $data = json_decode( + $request->getBody()->getContents(), + true + ); + + $query = $this->builder->build( + $session, + $data['identifier'], + $data['password'], + ); + + $result = $this->handler->execute($query); + return new JsonResponse([ + 'sessionId' => $result->getId()->toString() + ]); + } +} diff --git a/src/ApiDomain/External/Authentication/src/Handler/LogoutUserHandler.php b/src/ApiDomain/External/Authentication/src/Handler/LogoutUserHandler.php new file mode 100644 index 0000000..0ab7328 --- /dev/null +++ b/src/ApiDomain/External/Authentication/src/Handler/LogoutUserHandler.php @@ -0,0 +1,35 @@ +getAttribute(SessionMiddleware::SESSION_ATTRIBUTE); + + $query = $this->builder->build( + $session + ); + $this->handler->execute($query); + + return new JsonResponse('OK'); + } +} diff --git a/src/ApiDomain/External/Authentication/src/Handler/RegisterUserHandler.php b/src/ApiDomain/External/Authentication/src/Handler/RegisterUserHandler.php new file mode 100644 index 0000000..7a330c2 --- /dev/null +++ b/src/ApiDomain/External/Authentication/src/Handler/RegisterUserHandler.php @@ -0,0 +1,41 @@ +getAttribute(AnalyzeHeaderMiddleware::HOST_ATTRIBUTE); + $data = json_decode( + $request->getBody()->getContents(), + true + ); + + $query = $this->builder->build( + $data['username'], + $data['mail'], + $host + ); + + $this->handler->execute($query); + return new SuccessResponse(); + } +} diff --git a/src/ApiDomain/External/Health/config/routes.php b/src/ApiDomain/External/Health/config/routes.php new file mode 100644 index 0000000..98f2f2d --- /dev/null +++ b/src/ApiDomain/External/Health/config/routes.php @@ -0,0 +1,16 @@ + 'health', + 'path' => '/api/health', + 'allowed_methods' => ['GET'], + 'middleware' => [ + HealthHandler::class + ], + ], +]; + +?> \ No newline at end of file diff --git a/src/ApiDomain/External/Health/config/service_manager.php b/src/ApiDomain/External/Health/config/service_manager.php new file mode 100644 index 0000000..abdefca --- /dev/null +++ b/src/ApiDomain/External/Health/config/service_manager.php @@ -0,0 +1,12 @@ + [ + HealthHandler::class => AutoWiringFactory::class + ], +] + +?> \ No newline at end of file diff --git a/src/ApiDomain/External/Health/src/ConfigProvider.php b/src/ApiDomain/External/Health/src/ConfigProvider.php new file mode 100644 index 0000000..217c876 --- /dev/null +++ b/src/ApiDomain/External/Health/src/ConfigProvider.php @@ -0,0 +1,16 @@ + require __DIR__ . './../config/service_manager.php', + 'routes' => require __DIR__ . '/./../config/routes.php', + ]; + } +} diff --git a/src/ApiDomain/External/Health/src/Handler/HealthHandler.php b/src/ApiDomain/External/Health/src/Handler/HealthHandler.php new file mode 100644 index 0000000..673e26f --- /dev/null +++ b/src/ApiDomain/External/Health/src/Handler/HealthHandler.php @@ -0,0 +1,23 @@ + diff --git a/src/ApiDomain/External/User/config/routes.php b/src/ApiDomain/External/User/config/routes.php new file mode 100644 index 0000000..cdb3833 --- /dev/null +++ b/src/ApiDomain/External/User/config/routes.php @@ -0,0 +1,48 @@ + 'user.create-user', + 'path' => '/api/user/create-user', + 'allowed_methods' => ['POST'], + 'middleware' => [ + LoggedInUserMiddleware::class, + EnsureAuthorizationMiddleware::class, + CreateUserHandler::class + ], + ], + [ + 'name' => 'user.change-password', + 'path' => '/api/user/change-password', + 'allowed_methods' => ['POST'], + 'middleware' => [ + LoggedInUserMiddleware::class, + ChangePasswordHandler::class + ], + ], + [ + 'name' => 'user.change-username', + 'path' => '/api/user/change-username', + 'allowed_methods' => ['POST'], + 'middleware' => [ + LoggedInUserMiddleware::class, + ChangeUsernameHandler::class + ], + ], + [ + 'name' => 'user.state', + 'path' => '/api/user/state', + 'allowed_methods' => ['GET'], + 'middleware' => [ + LoggedInUserMiddleware::class, + UserStateHandler::class + ], + ], +]; diff --git a/src/ApiDomain/External/User/config/service_manager.php b/src/ApiDomain/External/User/config/service_manager.php new file mode 100644 index 0000000..3d7e7c4 --- /dev/null +++ b/src/ApiDomain/External/User/config/service_manager.php @@ -0,0 +1,21 @@ + [ + // Formatter + UserFormatter::class => AutoWiringFactory::class, + + // Handler + CreateUserHandler::class => AutoWiringFactory::class, + UserStateHandler::class => AutoWiringFactory::class, + ChangePasswordHandler::class => AutoWiringFactory::class, + ChangeUsernameHandler::class => AutoWiringFactory::class, + ], +]; diff --git a/src/ApiDomain/External/User/src/ConfigProvider.php b/src/ApiDomain/External/User/src/ConfigProvider.php new file mode 100644 index 0000000..dd16400 --- /dev/null +++ b/src/ApiDomain/External/User/src/ConfigProvider.php @@ -0,0 +1,16 @@ + require __DIR__ . '/./../config/service_manager.php', + 'routes' => require __DIR__ . '/./../config/routes.php', + ]; + } +} diff --git a/src/ApiDomain/External/User/src/Formatter/UserFormatter.php b/src/ApiDomain/External/User/src/Formatter/UserFormatter.php new file mode 100644 index 0000000..920c57c --- /dev/null +++ b/src/ApiDomain/External/User/src/Formatter/UserFormatter.php @@ -0,0 +1,27 @@ + $user->getId()->toString(), + 'username' => $user->getUsername(), + 'role' => $user->getRole()->getIdentifier(), + 'created' => $user->getCreatedAt()->format(DateTime::ATOM), + 'updated' => $user->getUpdatedAt()->format(DateTime::ATOM) + ]; + + $userArray['permissions'] = []; + + foreach ($user->getRole()->getPermissions()->toArray() as $permission) { + $userArray['permissions'][] = $permission->getIdentifier(); + } + + return $userArray; + } +} diff --git a/src/ApiDomain/External/User/src/Handler/ChangePasswordHandler.php b/src/ApiDomain/External/User/src/Handler/ChangePasswordHandler.php new file mode 100644 index 0000000..f4d4062 --- /dev/null +++ b/src/ApiDomain/External/User/src/Handler/ChangePasswordHandler.php @@ -0,0 +1,43 @@ +getAttribute(LoggedInUserMiddleware::USER_KEY); + + $data = json_decode( + $request->getBody()->getContents(), + true + ); + + $query = $this->builder->build( + $user, + $data['password'], + $data['newPassword'], + ); + $this->handler->execute($query); + + return new SuccessResponse(); + } +} diff --git a/src/ApiDomain/External/User/src/Handler/ChangeUsernameHandler.php b/src/ApiDomain/External/User/src/Handler/ChangeUsernameHandler.php new file mode 100644 index 0000000..012bf71 --- /dev/null +++ b/src/ApiDomain/External/User/src/Handler/ChangeUsernameHandler.php @@ -0,0 +1,43 @@ +getAttribute(LoggedInUserMiddleware::USER_KEY); + + $data = json_decode( + $request->getBody()->getContents(), + true + ); + + $query = $this->builder->build( + $user, + $data['password'], + $data['newUsername'], + ); + $this->handler->execute($query); + + return new SuccessResponse(); + } +} diff --git a/src/ApiDomain/External/User/src/Handler/CreateUserHandler.php b/src/ApiDomain/External/User/src/Handler/CreateUserHandler.php new file mode 100644 index 0000000..5096705 --- /dev/null +++ b/src/ApiDomain/External/User/src/Handler/CreateUserHandler.php @@ -0,0 +1,44 @@ +getBody()->getContents(), + true + ); + + $query = $this->builder->build( + $data['username'], + $data['mail'], + $data['password'], + ); + $result = $this->handler->execute($query); + + return new JsonResponse( + $this->userFormatter->format($result) + ); + } +} + +?> \ No newline at end of file diff --git a/src/ApiDomain/External/User/src/Handler/UserStateHandler.php b/src/ApiDomain/External/User/src/Handler/UserStateHandler.php new file mode 100644 index 0000000..02bd37b --- /dev/null +++ b/src/ApiDomain/External/User/src/Handler/UserStateHandler.php @@ -0,0 +1,34 @@ +getAttribute(LoggedInUserMiddleware::USER_KEY); + + $response = $this->userFormatter->format($user); + $response['sessionId'] = $user->getSession()->getId()->toString(); + + return new JsonResponse($response); + } +} diff --git a/src/DataDomain/Business/config/doctrine.php b/src/DataDomain/Business/config/doctrine.php new file mode 100644 index 0000000..07bff44 --- /dev/null +++ b/src/DataDomain/Business/config/doctrine.php @@ -0,0 +1,55 @@ + [ + 'orm_template' => [ + 'second_level_cache' => [ + 'enabled' => false, + ] + ], + ], + + 'driver' => [ + 'orm_template_annotation_driver' => [ + 'class' => AnnotationDriver::class, + 'cache' => 'array', + 'paths' => [ + realpath(APP_ROOT . '/src/DataDomain/Business/Entity/') + ], + ], + + 'orm_template' => [ + 'class' => MappingDriverChain::class, + 'drivers' => [ + 'Template\Data\Business\Entity' => 'orm_template_annotation_driver', + ], + ], + ], + + 'connection' => [ + 'orm_template' => [ + 'driverClass' => Driver::class, + 'params' => [ + 'driver' => $_ENV['DB_DRIVER'], + 'host' => $_ENV['DB_HOST'], + 'port' => $_ENV['DB_PORT'], + 'user' => $_ENV['DB_USER'], + 'password' => $_ENV['DB_PASSWORD'], + 'dbname' => $_ENV['DB_NAME'], + ], + ], + ], + + 'migrations_configuration' => [ + 'orm_template' => [ + 'directory' => 'data/migrations/business', + 'name' => 'Doctrine Database Migrations for Template', + 'namespace' => 'Template\Migrations\Template', + 'table' => 'migrations', + ], + ], +]; \ No newline at end of file diff --git a/src/DataDomain/Business/config/service_manager.php b/src/DataDomain/Business/config/service_manager.php new file mode 100644 index 0000000..c0faf40 --- /dev/null +++ b/src/DataDomain/Business/config/service_manager.php @@ -0,0 +1,21 @@ + [ + 'doctrine.entity_manager.orm_template' => [BaseEntityManagerFactory::class, 'orm_template'], + 'doctrine.configuration.orm_template' => [ConfigurationFactory::class, 'orm_template'], + 'doctrine.connection.orm_template' => [ConnectionFactory::class, 'orm_template'], + EntityManager::class => EntityManagerFactory::class, + + UserRepository::class => [AutowireRepositoryFactory::class, EntityManager::class, User::class], + ], +]; diff --git a/src/DataDomain/Business/src/ConfigProvider.php b/src/DataDomain/Business/src/ConfigProvider.php new file mode 100644 index 0000000..e3b2c68 --- /dev/null +++ b/src/DataDomain/Business/src/ConfigProvider.php @@ -0,0 +1,16 @@ + require __DIR__ . './../config/service_manager.php', + 'doctrine' => require __DIR__ . './../config/doctrine.php', + ]; + } +} diff --git a/src/DataDomain/Business/src/Entity/Permission.php b/src/DataDomain/Business/src/Entity/Permission.php new file mode 100644 index 0000000..96f3258 --- /dev/null +++ b/src/DataDomain/Business/src/Entity/Permission.php @@ -0,0 +1,75 @@ +id = UuidGenerator::generate(); + $this->roles = new ArrayCollection(); + } + + + public function getId(): UuidInterface { + return $this->id; + } + + public function getIdentifier(): string { + return $this->identifier; + } + + public function setIdentifier(string $identifier): void { + $this->identifier = $identifier; + } + + public function getRoles(): Collection { + return $this->roles; + } + + public function setRoles(Collection $roles): void { + $this->roles = $roles; + } + + public function addRole(Role $role): void { + if (!$this->roles->contains($role)) { + $this->roles->add($role); + $role->addPermission($this); + } + } + + public function removeRole(Role $role): void { + if ($this->roles->contains($role)) { + $this->roles->remove($role); + $role->removePermission($this); + } + } +} diff --git a/src/DataDomain/Business/src/Entity/Registration.php b/src/DataDomain/Business/src/Entity/Registration.php new file mode 100644 index 0000000..08dace6 --- /dev/null +++ b/src/DataDomain/Business/src/Entity/Registration.php @@ -0,0 +1,66 @@ +id = UuidGenerator::generate(); + + $now = new DateTime(); + $this->setCreatedAt($now); + } + + + public function getId(): UuidInterface { + return $this->id; + } + + public function getUsername(): string { + return $this->username; + } + + public function setUsername(string $username): void { + $this->username = $username; + } + + public function getMail(): ?string { + return $this->mail; + } + + public function setMail(?string $mail): void { + $this->mail = $mail; + } + + public function getCreatedAt(): DateTime { + return $this->createdAt; + } + + public function setCreatedAt(DateTime $createdAt): void { + $this->createdAt = $createdAt; + } +} diff --git a/src/DataDomain/Business/src/Entity/Role.php b/src/DataDomain/Business/src/Entity/Role.php new file mode 100644 index 0000000..f1a2428 --- /dev/null +++ b/src/DataDomain/Business/src/Entity/Role.php @@ -0,0 +1,76 @@ +id = UuidGenerator::generate(); + $this->permissions = new ArrayCollection(); + } + + + public function getId(): UuidInterface { + return $this->id; + } + + public function getIdentifier(): string { + return $this->identifier; + } + + public function setIdentifier(string $identifier): void { + $this->identifier = $identifier; + } + + public function getPermissions(): Collection { + return $this->permissions; + } + + public function setPermissions(Collection $permissions): void { + $this->permissions = $permissions; + } + + public function addPermission(Permission $permission): void { + if (!$this->permissions->contains($permission)) { + $this->permissions->add($permission); + $permission->addRole($this); + } + } + + public function removePermission(Permission $permission): void { + if ($this->permissions->contains($permission)) { + $this->permissions->remove($permission); + $permission->removeRole($this); + } + } +} diff --git a/src/DataDomain/Business/src/Entity/User.php b/src/DataDomain/Business/src/Entity/User.php new file mode 100644 index 0000000..a753968 --- /dev/null +++ b/src/DataDomain/Business/src/Entity/User.php @@ -0,0 +1,148 @@ +id = UuidGenerator::generate(); + + $now = new DateTime(); + $this->setCreatedAt($now); + $this->setUpdatedAt($now); + } + + + /** + * @ORM\PrePersist + * @ORM\PreUpdate + */ + public function updateTimestamps(): void { + $now = new DateTime(); + $this->setUpdatedAt($now); + } + + + public function getId(): UuidInterface { + return $this->id; + } + + public function getRoleId(): UuidInterface { + return $this->roleId; + } + + public function setRoleId(UuidInterface $roleId): void { + $this->roleId = $roleId; + } + + public function getRole(): Role { + return $this->role; + } + + public function setRole(Role $role): void { + $this->role = $role; + } + + public function getUsername(): string { + return $this->username; + } + + public function setUsername(string $username): void { + $this->username = $username; + } + + public function getMail(): ?string { + return $this->mail; + } + + public function setMail(?string $mail): void { + $this->mail = $mail; + } + + public function getPassword(): ?string { + return $this->password; + } + + public function setPassword(?string $password): void { + $this->password = $password; + } + + public function getSession(): ?UserSession { + return $this->session; + } + + public function setSession(?UserSession $session): void { + $this->session = $session; + } + + public function getLastLoginAt(): ?DateTime { + return $this->lastLoginAt; + } + + public function setLastLoginAt(?DateTime $lastLoginAt): void { + $this->lastLoginAt = $lastLoginAt; + } + + public function getCreatedAt(): DateTime { + return $this->createdAt; + } + + public function setCreatedAt(DateTime $createdAt): void { + $this->createdAt = $createdAt; + } + + public function getUpdatedAt(): DateTime { + return $this->updatedAt; + } + + public function setUpdatedAt(DateTime $updatedAt): void { + $this->updatedAt = $updatedAt; + } +} + +?> \ No newline at end of file diff --git a/src/DataDomain/Business/src/Entity/UserSession.php b/src/DataDomain/Business/src/Entity/UserSession.php new file mode 100644 index 0000000..e1686d3 --- /dev/null +++ b/src/DataDomain/Business/src/Entity/UserSession.php @@ -0,0 +1,105 @@ +id = UuidGenerator::generate(); + + $now = new DateTime(); + $this->setCreatedAt($now); + $this->setUpdatedAt($now); + } + + + /** + * @ORM\PrePersist + * @ORM\PreUpdate + */ + public function updateTimestamps(): void { + $now = new DateTime(); + $this->setUpdatedAt($now); + } + + + public function getId(): UuidInterface { + return $this->id; + } + + public function getUserId(): ?UuidInterface { + return $this->userId; + } + + public function setUserId(?UuidInterface $userId): void { + $this->userId = $userId; + } + + public function getUser(): ?User { + return $this->user; + } + + public function setUser(?User $user): void { + $this->user = $user; + } + + public function getCsrf(): ?UuidInterface { + return $this->csrf; + } + + public function setCsrf(?UuidInterface $csrf): void { + $this->csrf = $csrf; + } + + + public function getCreatedAt(): DateTime { + return $this->createdAt; + } + + public function setCreatedAt(DateTime $createdAt): void { + $this->createdAt = $createdAt; + } + + public function getUpdatedAt(): DateTime { + return $this->updatedAt; + } + + public function setUpdatedAt(DateTime $updatedAt): void { + $this->updatedAt = $updatedAt; + } +} + +?> \ No newline at end of file diff --git a/src/DataDomain/Business/src/Factory/EntityManagerFactory.php b/src/DataDomain/Business/src/Factory/EntityManagerFactory.php new file mode 100644 index 0000000..0ce0465 --- /dev/null +++ b/src/DataDomain/Business/src/Factory/EntityManagerFactory.php @@ -0,0 +1,19 @@ +get('doctrine.entity_manager.orm_template') + ); + } +} diff --git a/src/DataDomain/Business/src/Manager/EntityManager.php b/src/DataDomain/Business/src/Manager/EntityManager.php new file mode 100644 index 0000000..75f4baa --- /dev/null +++ b/src/DataDomain/Business/src/Manager/EntityManager.php @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/src/DataDomain/Business/src/Repository/ProductRepository.php b/src/DataDomain/Business/src/Repository/ProductRepository.php new file mode 100644 index 0000000..621d277 --- /dev/null +++ b/src/DataDomain/Business/src/Repository/ProductRepository.php @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/src/DataDomain/Business/src/Repository/RegistrationRepository.php b/src/DataDomain/Business/src/Repository/RegistrationRepository.php new file mode 100644 index 0000000..c1e0d62 --- /dev/null +++ b/src/DataDomain/Business/src/Repository/RegistrationRepository.php @@ -0,0 +1,24 @@ +createQueryBuilder('r'); + $queryBuilder->where( + $queryBuilder->expr()->orX( + $queryBuilder->expr()->eq('r.username', ':identifier'), + $queryBuilder->expr()->eq('r.mail', ':identifier') + ) + ) + ->setParameter('identifier', $identifier); + $query = $queryBuilder->getQuery(); + + /** @var ?Registration $registration */ + $registration = $query->execute()[0] ?? null; + return $registration; + } +} \ No newline at end of file diff --git a/src/DataDomain/Business/src/Repository/RoleRepository.php b/src/DataDomain/Business/src/Repository/RoleRepository.php new file mode 100644 index 0000000..55f3a3a --- /dev/null +++ b/src/DataDomain/Business/src/Repository/RoleRepository.php @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/src/DataDomain/Business/src/Repository/UserRepository.php b/src/DataDomain/Business/src/Repository/UserRepository.php new file mode 100644 index 0000000..b2ac22b --- /dev/null +++ b/src/DataDomain/Business/src/Repository/UserRepository.php @@ -0,0 +1,26 @@ +createQueryBuilder('u'); + $queryBuilder->where( + $queryBuilder->expr()->orX( + $queryBuilder->expr()->eq('u.username', ':identifier'), + $queryBuilder->expr()->eq('u.mail', ':identifier') + ) + ) + ->setParameter('identifier', $identifier); + $query = $queryBuilder->getQuery(); + + /** @var ?User $user */ + $user = $query->execute()[0] ?? null; + return $user; + } +} + +?> \ No newline at end of file diff --git a/src/DataDomain/Business/src/Repository/UserSessionRepository.php b/src/DataDomain/Business/src/Repository/UserSessionRepository.php new file mode 100644 index 0000000..0c7488a --- /dev/null +++ b/src/DataDomain/Business/src/Repository/UserSessionRepository.php @@ -0,0 +1,22 @@ +createQueryBuilder('us'); + $queryBuilder + ->where("us.userId = :userId") + ->setParameter('userId', $user->getId()); + + /** @var ?UserSession $userSession */ + $userSession = $queryBuilder->getQuery()->execute()[0] ?? null; + return $userSession; + } +} + +?> \ No newline at end of file diff --git a/src/DataDomain/Log/config/doctrine.php b/src/DataDomain/Log/config/doctrine.php new file mode 100644 index 0000000..c2c1d6b --- /dev/null +++ b/src/DataDomain/Log/config/doctrine.php @@ -0,0 +1,55 @@ + [ + 'orm_log' => [ + 'second_level_cache' => [ + 'enabled' => false, + ] + ], + ], + + 'driver' => [ + 'orm_log_annotation_driver' => [ + 'class' => AnnotationDriver::class, + 'cache' => 'array', + 'paths' => [ + realpath(APP_ROOT . '/src/DataDomain/Log/Entity/') + ], + ], + + 'orm_log' => [ + 'class' => MappingDriverChain::class, + 'drivers' => [ + 'Template\Data\Log\Entity' => 'orm_log_annotation_driver', + ], + ], + ], + + 'connection' => [ + 'orm_log' => [ + 'driverClass' => Driver::class, + 'params' => [ + 'driver' => $_ENV['DB_DRIVER'], + 'host' => $_ENV['DB_HOST'], + 'port' => $_ENV['DB_PORT'], + 'user' => $_ENV['DB_USER'], + 'password' => $_ENV['DB_PASSWORD'], + 'dbname' => $_ENV['DB_NAME_LOG'], + ], + ], + ], + + 'migrations_configuration' => [ + 'orm_log' => [ + 'directory' => 'data/migrations/log', + 'name' => 'Doctrine Database Migrations for Log', + 'namespace' => 'Template\Migrations\Log', + 'table' => 'migrations', + ], + ], +]; \ No newline at end of file diff --git a/src/DataDomain/Log/config/service_manager.php b/src/DataDomain/Log/config/service_manager.php new file mode 100644 index 0000000..3357574 --- /dev/null +++ b/src/DataDomain/Log/config/service_manager.php @@ -0,0 +1,16 @@ + [ + 'doctrine.entity_manager.orm_log' => [EntityManagerFactory::class, 'orm_log'], + 'doctrine.configuration.orm_log' => [ConfigurationFactory::class, 'orm_log'], + 'doctrine.connection.orm_log' => [ConnectionFactory::class, 'orm_log'], + LogEntityManager::class => LogEntityManagerFactory::class, + ], +]; diff --git a/src/DataDomain/Log/src/ConfigProvider.php b/src/DataDomain/Log/src/ConfigProvider.php new file mode 100644 index 0000000..8040657 --- /dev/null +++ b/src/DataDomain/Log/src/ConfigProvider.php @@ -0,0 +1,16 @@ + require __DIR__ . './../config/service_manager.php', + 'doctrine' => require __DIR__ . './../config/doctrine.php', + ]; + } +} diff --git a/src/DataDomain/Log/src/Entity/Log.php b/src/DataDomain/Log/src/Entity/Log.php new file mode 100644 index 0000000..bd9b690 --- /dev/null +++ b/src/DataDomain/Log/src/Entity/Log.php @@ -0,0 +1,129 @@ +id = UuidGenerator::generate(); + } + + + public function getId(): UuidInterface + { + return $this->id; + } + + public function setId(UuidInterface $id): void + { + $this->id = $id; + } + + public function getMessage(): string + { + return $this->message; + } + + public function setMessage(string $message): void + { + $this->message = $message; + } + + public function getContext(): ?array + { + return $this->context; + } + + public function setContext(?array $context): void + { + $this->context = $context; + } + + public function getLevel(): int + { + return $this->level; + } + + public function setLevel(int $level): void + { + $this->level = $level; + } + + public function getLevelName(): string + { + return $this->levelName; + } + + public function setLevelName(string $levelName): void + { + $this->levelName = $levelName; + } + + public function getExtra(): ?array + { + return $this->extra; + } + + public function setExtra(?array $extra): void + { + $this->extra = $extra; + } + + public function getCreatedAt(): DateTime + { + return $this->createdAt; + } + + public function setCreatedAt(DateTime $createdAt): void + { + $this->createdAt = $createdAt; + } +} diff --git a/src/DataDomain/Log/src/Factory/LogEntityManagerFactory.php b/src/DataDomain/Log/src/Factory/LogEntityManagerFactory.php new file mode 100644 index 0000000..02c4ebb --- /dev/null +++ b/src/DataDomain/Log/src/Factory/LogEntityManagerFactory.php @@ -0,0 +1,21 @@ +get('doctrine.entity_manager.orm_log') + ); + + return $lem; + } +} diff --git a/src/DataDomain/Log/src/Manager/LogEntityManager.php b/src/DataDomain/Log/src/Manager/LogEntityManager.php new file mode 100644 index 0000000..a1fd378 --- /dev/null +++ b/src/DataDomain/Log/src/Manager/LogEntityManager.php @@ -0,0 +1,11 @@ + [ + /// Builder + RegistrationBuilder::class => AutoWiringFactory::class, + + /// Rule + RegistrationWithIdentifierAlreadyExistsRule::class => InjectionFactory::class, + + /// Pipeline + // Register User + RegisterUserPipeline::class => AutoWiringFactory::class, + CheckIdentifierStep::class => AutoWiringFactory::class, + BuildRegistrationStep::class => AutoWiringFactory::class, + SendMailStep::class => AutoWiringFactory::class, + SaveRegistrationStep::class => AutoWiringFactory::class, + + // Confirm Registration + ConfirmRegistrationPipeline::class => AutoWiringFactory::class, + LoadRegistrationStep::class => InjectionFactory::class, + CreateUserStep::class => AutoWiringFactory::class, + SaveRegistrationAndUserStep::class => AutoWiringFactory::class, + + /// CQRS + // Register User + RegisterUserCommandHandler::class => AutoWiringFactory::class, + RegisterUserCommandBuilder::class => AutoWiringFactory::class, + + // Confirm Registration + ConfirmRegistrationCommandHandler::class => AutoWiringFactory::class, + ConfirmRegistrationCommandBuilder::class => AutoWiringFactory::class, + ], +]; diff --git a/src/HandlingDomain/Registration/src/Builder/RegistrationBuilder.php b/src/HandlingDomain/Registration/src/Builder/RegistrationBuilder.php new file mode 100644 index 0000000..7bf2870 --- /dev/null +++ b/src/HandlingDomain/Registration/src/Builder/RegistrationBuilder.php @@ -0,0 +1,19 @@ +setUsername($username); + $registration->setMail($mail); + return $registration; + } +} diff --git a/src/HandlingDomain/Registration/src/ConfigProvider.php b/src/HandlingDomain/Registration/src/ConfigProvider.php new file mode 100644 index 0000000..b8d580d --- /dev/null +++ b/src/HandlingDomain/Registration/src/ConfigProvider.php @@ -0,0 +1,15 @@ + require __DIR__ . '/./../config/service_manager.php', + ]; + } +} diff --git a/src/HandlingDomain/Registration/src/Exception/RegistrationNotFoundByIdException.php b/src/HandlingDomain/Registration/src/Exception/RegistrationNotFoundByIdException.php new file mode 100644 index 0000000..e1f9ba7 --- /dev/null +++ b/src/HandlingDomain/Registration/src/Exception/RegistrationNotFoundByIdException.php @@ -0,0 +1,25 @@ +toString() + ), + ErrorDomain::Registration, + ErrorCode::NotFound + ); + } +} \ No newline at end of file diff --git a/src/HandlingDomain/Registration/src/Exception/RegistrationWithIdentifierAlreadyExistsException.php b/src/HandlingDomain/Registration/src/Exception/RegistrationWithIdentifierAlreadyExistsException.php new file mode 100644 index 0000000..c86afc5 --- /dev/null +++ b/src/HandlingDomain/Registration/src/Exception/RegistrationWithIdentifierAlreadyExistsException.php @@ -0,0 +1,25 @@ +id; + } + + public function getPassword(): string { + return $this->password; + } + + + public function getPasswordConfirmation(): string { + return $this->passwordConfirmation; + } +} diff --git a/src/HandlingDomain/Registration/src/Handler/Command/ConfirmRegistration/ConfirmRegistrationCommandBuilder.php b/src/HandlingDomain/Registration/src/Handler/Command/ConfirmRegistration/ConfirmRegistrationCommandBuilder.php new file mode 100644 index 0000000..4c3d0c3 --- /dev/null +++ b/src/HandlingDomain/Registration/src/Handler/Command/ConfirmRegistration/ConfirmRegistrationCommandBuilder.php @@ -0,0 +1,21 @@ +pipeline->handle($payload); + + return $payload->getUser(); + } +} diff --git a/src/HandlingDomain/Registration/src/Handler/Command/RegisterUser/RegisterUserCommand.php b/src/HandlingDomain/Registration/src/Handler/Command/RegisterUser/RegisterUserCommand.php new file mode 100644 index 0000000..dae1cde --- /dev/null +++ b/src/HandlingDomain/Registration/src/Handler/Command/RegisterUser/RegisterUserCommand.php @@ -0,0 +1,25 @@ +username; + } + + public function getMail(): string { + return $this->mail; + } + + public function getHost(): string { + return $this->host; + } +} diff --git a/src/HandlingDomain/Registration/src/Handler/Command/RegisterUser/RegisterUserCommandBuilder.php b/src/HandlingDomain/Registration/src/Handler/Command/RegisterUser/RegisterUserCommandBuilder.php new file mode 100644 index 0000000..45122cf --- /dev/null +++ b/src/HandlingDomain/Registration/src/Handler/Command/RegisterUser/RegisterUserCommandBuilder.php @@ -0,0 +1,19 @@ +pipeline->handle($payload); + + return $payload->getRegistration(); + } +} diff --git a/src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/ConfirmRegistrationPayload.php b/src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/ConfirmRegistrationPayload.php new file mode 100644 index 0000000..f43c99b --- /dev/null +++ b/src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/ConfirmRegistrationPayload.php @@ -0,0 +1,46 @@ +command; + } + + public function getRegistration(): ?Registration + { + return $this->registration; + } + + public function setRegistration(?Registration $registration) + { + $this->registration = $registration; + } + + public function getUser(): ?User + { + return $this->user; + } + + public function setUser(?User $user) + { + $this->user = $user; + } +} \ No newline at end of file diff --git a/src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/ConfirmRegistrationPipeline.php b/src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/ConfirmRegistrationPipeline.php new file mode 100644 index 0000000..0da093d --- /dev/null +++ b/src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/ConfirmRegistrationPipeline.php @@ -0,0 +1,26 @@ +loadRegistrationStep, + $this->createUserStep, + $this->saveRegistrationStep + ]); + } +} \ No newline at end of file diff --git a/src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/Step/CreateUserStep.php b/src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/Step/CreateUserStep.php new file mode 100644 index 0000000..00f9ad4 --- /dev/null +++ b/src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/Step/CreateUserStep.php @@ -0,0 +1,60 @@ +getCommand(); + $registration = $payload->getRegistration(); + + if ( + !$this->passwordMatchRule->appliesTo( + $command->getPassword(), + $command->getPasswordConfirmation() + ) + ) { + throw new UserPasswordMismatchException(); + } + + $createUserCommand = $this->createUserCommandBuilder->build( + username: $registration->getUsername(), + mail: $registration->getMail(), + password: $command->getPassword(), + ); + $user = $this->createUserCommandHandler->execute($createUserCommand); + + $payload->setUser($user); + + $pipeline->next()($payload, $pipeline); + } +} \ No newline at end of file diff --git a/src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/Step/LoadRegistrationStep.php b/src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/Step/LoadRegistrationStep.php new file mode 100644 index 0000000..0452172 --- /dev/null +++ b/src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/Step/LoadRegistrationStep.php @@ -0,0 +1,47 @@ +getCommand(); + + /** @var Registration $registration */ + $registration = $this->registrationRepository->findOneBy([ + 'id' => $command->getId() + ]) ?? null; + + if ($registration === null) { + throw new RegistrationNotFoundByIdException($command->getId()); + } + + $payload->setRegistration($registration); + + $pipeline->next()($payload, $pipeline); + } +} \ No newline at end of file diff --git a/src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/Step/SaveRegistrationAndUserStep.php b/src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/Step/SaveRegistrationAndUserStep.php new file mode 100644 index 0000000..15c0e13 --- /dev/null +++ b/src/HandlingDomain/Registration/src/Pipeline/ConfirmRegistration/Step/SaveRegistrationAndUserStep.php @@ -0,0 +1,32 @@ +getRegistration(); + $user = $payload->getUser(); + + $this->entityManager->remove($registration); + $this->entityManager->persist($user); + $this->entityManager->flush(); + + return $payload; + } +} \ No newline at end of file diff --git a/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/RegisterUserPayload.php b/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/RegisterUserPayload.php new file mode 100644 index 0000000..389bf91 --- /dev/null +++ b/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/RegisterUserPayload.php @@ -0,0 +1,33 @@ +command; + } + + public function getRegistration(): ?Registration + { + return $this->registration; + } + + public function setRegistration(?Registration $registration) + { + $this->registration = $registration; + } +} \ No newline at end of file diff --git a/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/RegisterUserPipeline.php b/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/RegisterUserPipeline.php new file mode 100644 index 0000000..ec86aeb --- /dev/null +++ b/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/RegisterUserPipeline.php @@ -0,0 +1,29 @@ +checkIdentifierStep, + $this->buildRegistrationStep, + $this->sendMailStep, + $this->saveRegistrationStep + ]); + } +} \ No newline at end of file diff --git a/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/Step/BuildRegistrationStep.php b/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/Step/BuildRegistrationStep.php new file mode 100644 index 0000000..8ac652f --- /dev/null +++ b/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/Step/BuildRegistrationStep.php @@ -0,0 +1,34 @@ +getCommand(); + + $registration = $this->builder->build( + username: $command->getUsername(), + mail: $command->getMail() + ); + + $payload->setRegistration($registration); + + $pipeline->next()($payload, $pipeline); + } +} \ No newline at end of file diff --git a/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/Step/CheckIdentifierStep.php b/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/Step/CheckIdentifierStep.php new file mode 100644 index 0000000..cab145f --- /dev/null +++ b/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/Step/CheckIdentifierStep.php @@ -0,0 +1,41 @@ +getCommand(); + + $this->userWithIdentifierAlreadyExistsRule->appliesTo($command->getMail()); + $this->userWithIdentifierAlreadyExistsRule->appliesTo($command->getUsername()); + + $this->registrationWithIdentifierAlreadyExistsRule->appliesTo($command->getMail()); + $this->registrationWithIdentifierAlreadyExistsRule->appliesTo($command->getUsername()); + + $pipeline->next()($payload, $pipeline); + } +} \ No newline at end of file diff --git a/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/Step/SaveRegistrationStep.php b/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/Step/SaveRegistrationStep.php new file mode 100644 index 0000000..e2bfcd2 --- /dev/null +++ b/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/Step/SaveRegistrationStep.php @@ -0,0 +1,30 @@ +getRegistration(); + + $this->entityManager->persist($registration); + $this->entityManager->flush(); + + return $payload; + } +} \ No newline at end of file diff --git a/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/Step/SendMailStep.php b/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/Step/SendMailStep.php new file mode 100644 index 0000000..aef6cc2 --- /dev/null +++ b/src/HandlingDomain/Registration/src/Pipeline/RegisterUser/Step/SendMailStep.php @@ -0,0 +1,49 @@ +getCommand(); + $registration = $payload->getRegistration(); + + $this->requestService->request( + 'notification', + 'send-mail', + [ + 'template-identifier' => 'new-account', + 'sender' => 'info@stack-up.de', + 'recipient' => $command->getMail(), + 'data' => [ + 'username' => $command->getUsername(), + 'confirmationLink' => + sprintf( + self::CONFIRM_LINK, + $command->getHost(), + $registration->getId()->toString() + ) + ] + ] + ); + + $pipeline->next()($payload, $pipeline); + } +} \ No newline at end of file diff --git a/src/HandlingDomain/Registration/src/Rule/RegistrationWithIdentifierAlreadyExistsRule.php b/src/HandlingDomain/Registration/src/Rule/RegistrationWithIdentifierAlreadyExistsRule.php new file mode 100644 index 0000000..43b82e6 --- /dev/null +++ b/src/HandlingDomain/Registration/src/Rule/RegistrationWithIdentifierAlreadyExistsRule.php @@ -0,0 +1,29 @@ +registrationRepository->findByIdentifier($identifier); + + if ($registration !== null) { + throw new RegistrationWithIdentifierAlreadyExistsException($identifier); + } + } +} \ No newline at end of file diff --git a/src/HandlingDomain/Role/src/Exception/RoleNotFoundByIdentifierException.php b/src/HandlingDomain/Role/src/Exception/RoleNotFoundByIdentifierException.php new file mode 100644 index 0000000..c4675c5 --- /dev/null +++ b/src/HandlingDomain/Role/src/Exception/RoleNotFoundByIdentifierException.php @@ -0,0 +1,24 @@ + [ + + /// Builder + UserBuilder::class => InjectionFactory::class, + + /// Rule + UserWithIdentifierAlreadyExistsRule::class => InjectionFactory::class, + UserPasswordMatchRule::class => AutoWiringFactory::class, + + /// CQRS + // Create User + CreateUserCommandHandler::class => AutoWiringFactory::class, + CreateUserCommandBuilder::class => AutoWiringFactory::class, + + // Change Password + ChangePasswordCommandHandler::class => AutoWiringFactory::class, + ChangePasswordCommandBuilder::class => AutoWiringFactory::class, + + // Change Username + ChangeUsernameCommandHandler::class => AutoWiringFactory::class, + ChangeUsernameCommandBuilder::class => AutoWiringFactory::class, + ], +]; diff --git a/src/HandlingDomain/User/src/Builder/UserBuilder.php b/src/HandlingDomain/User/src/Builder/UserBuilder.php new file mode 100644 index 0000000..a5c6f5b --- /dev/null +++ b/src/HandlingDomain/User/src/Builder/UserBuilder.php @@ -0,0 +1,53 @@ +roleRepository->findOneBy([ + 'identifier' => $roleIdentifier + ]) ?? null; + + if ($role === null) { + throw new RoleNotFoundByIdentifierException($roleIdentifier); + } + + $encryptedPassword = $this->encryptionClient->encrypt($password); + + $user = new User(); + $user->setRole($role); + $user->setUsername($username); + $user->setMail($mail); + $user->setPassword($encryptedPassword); + + return $user; + } +} diff --git a/src/HandlingDomain/User/src/ConfigProvider.php b/src/HandlingDomain/User/src/ConfigProvider.php new file mode 100644 index 0000000..6fd8eb6 --- /dev/null +++ b/src/HandlingDomain/User/src/ConfigProvider.php @@ -0,0 +1,15 @@ + require __DIR__ . '/./../config/service_manager.php', + ]; + } +} diff --git a/src/HandlingDomain/User/src/Exception/UserNotFoundByIdentifierException.php b/src/HandlingDomain/User/src/Exception/UserNotFoundByIdentifierException.php new file mode 100644 index 0000000..09e56f0 --- /dev/null +++ b/src/HandlingDomain/User/src/Exception/UserNotFoundByIdentifierException.php @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/src/HandlingDomain/User/src/Exception/UserPasswordMismatchException.php b/src/HandlingDomain/User/src/Exception/UserPasswordMismatchException.php new file mode 100644 index 0000000..844e2c7 --- /dev/null +++ b/src/HandlingDomain/User/src/Exception/UserPasswordMismatchException.php @@ -0,0 +1,21 @@ +user; + } + + public function getNewPassword(): string { + return $this->newPassword; + } + + public function getPassword(): string { + return $this->password; + } +} diff --git a/src/HandlingDomain/User/src/Handler/Command/ChangePassword/ChangePasswordCommandBuilder.php b/src/HandlingDomain/User/src/Handler/Command/ChangePassword/ChangePasswordCommandBuilder.php new file mode 100644 index 0000000..1ac93b8 --- /dev/null +++ b/src/HandlingDomain/User/src/Handler/Command/ChangePassword/ChangePasswordCommandBuilder.php @@ -0,0 +1,21 @@ +getUser(); + + if (!$this->encryptionClient->verify($command->getPassword(), $user->getPassword())) { + throw new UserWrongPasswordException(); + } + + $encryptedPassword = $this->encryptionClient->encrypt( + $command->getNewPassword() + ); + $user->setPassword($encryptedPassword); + + $this->entityManager->persist($user); + $this->entityManager->flush(); + } +} diff --git a/src/HandlingDomain/User/src/Handler/Command/ChangeUsername/ChangeUsernameCommand.php b/src/HandlingDomain/User/src/Handler/Command/ChangeUsername/ChangeUsernameCommand.php new file mode 100644 index 0000000..7a9350d --- /dev/null +++ b/src/HandlingDomain/User/src/Handler/Command/ChangeUsername/ChangeUsernameCommand.php @@ -0,0 +1,28 @@ +user; + } + + public function getNewUsername(): string { + return $this->newUsername; + } + + public function getPassword(): string { + return $this->password; + } +} + diff --git a/src/HandlingDomain/User/src/Handler/Command/ChangeUsername/ChangeUsernameCommandBuilder.php b/src/HandlingDomain/User/src/Handler/Command/ChangeUsername/ChangeUsernameCommandBuilder.php new file mode 100644 index 0000000..50e90e9 --- /dev/null +++ b/src/HandlingDomain/User/src/Handler/Command/ChangeUsername/ChangeUsernameCommandBuilder.php @@ -0,0 +1,21 @@ +getUser(); + + if (!$this->encryptionClient->verify($command->getPassword(), $user->getPassword())) { + throw new UserWrongPasswordException(); + } + + $user->setUsername($command->getNewUsername()); + + $this->entityManager->persist($user); + $this->entityManager->flush(); + } +} diff --git a/src/HandlingDomain/User/src/Handler/Command/CreateUser/CreateUserCommand.php b/src/HandlingDomain/User/src/Handler/Command/CreateUser/CreateUserCommand.php new file mode 100644 index 0000000..5f42338 --- /dev/null +++ b/src/HandlingDomain/User/src/Handler/Command/CreateUser/CreateUserCommand.php @@ -0,0 +1,25 @@ +username; + } + + public function getMail(): string { + return $this->mail; + } + + public function getPassword(): string { + return $this->password; + } +} \ No newline at end of file diff --git a/src/HandlingDomain/User/src/Handler/Command/CreateUser/CreateUserCommandBuilder.php b/src/HandlingDomain/User/src/Handler/Command/CreateUser/CreateUserCommandBuilder.php new file mode 100644 index 0000000..eebec74 --- /dev/null +++ b/src/HandlingDomain/User/src/Handler/Command/CreateUser/CreateUserCommandBuilder.php @@ -0,0 +1,19 @@ +userWithIdentifierAlreadyExistsRule->appliesTo($command->getUsername()); + $this->userWithIdentifierAlreadyExistsRule->appliesTo($command->getMail()); + + $user = $this->builder->build( + username: $command->getUsername(), + roleIdentifier: 'user', + mail: $command->getMail(), + password: $command->getPassword() + ); + + $this->entityManager->persist($user); + $this->entityManager->flush(); + + return $user; + } +} + +?> \ No newline at end of file diff --git a/src/HandlingDomain/User/src/Rule/UserPasswordMatchRule.php b/src/HandlingDomain/User/src/Rule/UserPasswordMatchRule.php new file mode 100644 index 0000000..e49dcca --- /dev/null +++ b/src/HandlingDomain/User/src/Rule/UserPasswordMatchRule.php @@ -0,0 +1,15 @@ +userRepository->findByIdentifier($identifier); + + if ($user !== null) { + throw new UserWithIdentifierAlreadyExistsException($identifier); + } + } +} \ No newline at end of file diff --git a/src/HandlingDomain/UserSession/config/service_manager.php b/src/HandlingDomain/UserSession/config/service_manager.php new file mode 100644 index 0000000..2731442 --- /dev/null +++ b/src/HandlingDomain/UserSession/config/service_manager.php @@ -0,0 +1,31 @@ + [ + + /// Rule + UserPasswordMatchesRule::class => AutoWiringFactory::class, + + /// Builder + UserSessionBuilder::class => AutoWiringFactory::class, + + /// CQRS + // Login User + LoginUserCommandHandler::class => InjectionFactory::class, + LoginUserCommandBuilder::class => AutoWiringFactory::class, + + // Logout User + LogoutUserCommandHandler::class => InjectionFactory::class, + LogoutUserCommandBuilder::class => AutoWiringFactory::class, + ], +]; diff --git a/src/HandlingDomain/UserSession/src/Builder/UserSessionBuilder.php b/src/HandlingDomain/UserSession/src/Builder/UserSessionBuilder.php new file mode 100644 index 0000000..7c1df62 --- /dev/null +++ b/src/HandlingDomain/UserSession/src/Builder/UserSessionBuilder.php @@ -0,0 +1,17 @@ +setUser(null); + $userSession->setUserId(null); + $userSession->setCsrf(null); + return $userSession; + } +} diff --git a/src/HandlingDomain/UserSession/src/ConfigProvider.php b/src/HandlingDomain/UserSession/src/ConfigProvider.php new file mode 100644 index 0000000..ad22134 --- /dev/null +++ b/src/HandlingDomain/UserSession/src/ConfigProvider.php @@ -0,0 +1,15 @@ + require __DIR__ . '/./../config/service_manager.php', + ]; + } +} diff --git a/src/HandlingDomain/UserSession/src/Handler/Command/LoginUser/LoginUserCommand.php b/src/HandlingDomain/UserSession/src/Handler/Command/LoginUser/LoginUserCommand.php new file mode 100644 index 0000000..a289c83 --- /dev/null +++ b/src/HandlingDomain/UserSession/src/Handler/Command/LoginUser/LoginUserCommand.php @@ -0,0 +1,29 @@ +session; + } + + public function getIdentifier(): string { + return $this->identifier; + } + + public function getPassword(): string { + return $this->password; + } +} + +?> \ No newline at end of file diff --git a/src/HandlingDomain/UserSession/src/Handler/Command/LoginUser/LoginUserCommandBuilder.php b/src/HandlingDomain/UserSession/src/Handler/Command/LoginUser/LoginUserCommandBuilder.php new file mode 100644 index 0000000..826e121 --- /dev/null +++ b/src/HandlingDomain/UserSession/src/Handler/Command/LoginUser/LoginUserCommandBuilder.php @@ -0,0 +1,23 @@ + \ No newline at end of file diff --git a/src/HandlingDomain/UserSession/src/Handler/Command/LoginUser/LoginUserCommandHandler.php b/src/HandlingDomain/UserSession/src/Handler/Command/LoginUser/LoginUserCommandHandler.php new file mode 100644 index 0000000..142b636 --- /dev/null +++ b/src/HandlingDomain/UserSession/src/Handler/Command/LoginUser/LoginUserCommandHandler.php @@ -0,0 +1,80 @@ +userRepository->findByIdentifier( + $command->getIdentifier() + ); + + if ($user === null) { + throw new UserNotFoundByIdentifierException($command->getIdentifier()); + } + + if ( + !$this->passwordMatchesRule->appliesTo( + $command->getPassword(), + $user + ) + ) { + throw new UserWrongPasswordException(); + } + + $oldUserSessions = $this->userSessionRepository->findBy([ + 'userId' => $user->getId() + ]); + + foreach ($oldUserSessions as $oldUserSession) { + $this->entityManager->remove($oldUserSession); + } + + $session = $command->getSession(); + $session->setUser($user); + $user->setLastLoginAt(new DateTime('now')); + + $this->entityManager->persist($session); + $this->entityManager->flush(); + + return $session; + } +} + +?> \ No newline at end of file diff --git a/src/HandlingDomain/UserSession/src/Handler/Command/LogoutUser/LogoutUserCommand.php b/src/HandlingDomain/UserSession/src/Handler/Command/LogoutUser/LogoutUserCommand.php new file mode 100644 index 0000000..6d4f9e2 --- /dev/null +++ b/src/HandlingDomain/UserSession/src/Handler/Command/LogoutUser/LogoutUserCommand.php @@ -0,0 +1,19 @@ +session; + } +} + +?> \ No newline at end of file diff --git a/src/HandlingDomain/UserSession/src/Handler/Command/LogoutUser/LogoutUserCommandBuilder.php b/src/HandlingDomain/UserSession/src/Handler/Command/LogoutUser/LogoutUserCommandBuilder.php new file mode 100644 index 0000000..32b6bba --- /dev/null +++ b/src/HandlingDomain/UserSession/src/Handler/Command/LogoutUser/LogoutUserCommandBuilder.php @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/src/HandlingDomain/UserSession/src/Handler/Command/LogoutUser/LogoutUserCommandHandler.php b/src/HandlingDomain/UserSession/src/Handler/Command/LogoutUser/LogoutUserCommandHandler.php new file mode 100644 index 0000000..319e0af --- /dev/null +++ b/src/HandlingDomain/UserSession/src/Handler/Command/LogoutUser/LogoutUserCommandHandler.php @@ -0,0 +1,37 @@ +getSession(); + $this->entityManager->remove($session); + $this->entityManager->flush(); + + return $session; + } +} + +?> \ No newline at end of file diff --git a/src/HandlingDomain/UserSession/src/Rule/UserPasswordMatchesRule.php b/src/HandlingDomain/UserSession/src/Rule/UserPasswordMatchesRule.php new file mode 100644 index 0000000..6ea7280 --- /dev/null +++ b/src/HandlingDomain/UserSession/src/Rule/UserPasswordMatchesRule.php @@ -0,0 +1,24 @@ +encryptionClient->verify( + $password, + $user->getPassword() + ); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Database/config/service_manager.php b/src/Infrastructure/Database/config/service_manager.php new file mode 100644 index 0000000..ebff039 --- /dev/null +++ b/src/Infrastructure/Database/config/service_manager.php @@ -0,0 +1,11 @@ + [ + ], +]; diff --git a/src/Infrastructure/Database/src/ConfigProvider.php b/src/Infrastructure/Database/src/ConfigProvider.php new file mode 100644 index 0000000..6a03c8f --- /dev/null +++ b/src/Infrastructure/Database/src/ConfigProvider.php @@ -0,0 +1,15 @@ + require __DIR__ . './../config/service_manager.php', + ]; + } +} diff --git a/src/Infrastructure/Database/src/ConfigServiceFactory.php b/src/Infrastructure/Database/src/ConfigServiceFactory.php new file mode 100644 index 0000000..fa4caa9 --- /dev/null +++ b/src/Infrastructure/Database/src/ConfigServiceFactory.php @@ -0,0 +1,33 @@ +entityClass, $this->entityClass); + } + + public function __invoke( + ContainerInterface $container, + $requestedName, + ?array $options = null + ): ObjectRepository|EntityRepository + { + /** @var EntityManager $em */ + $em = $container->get($this->entityManagerClass); + + return $em->getRepository($this->entityClass); + } +} diff --git a/src/Infrastructure/DependencyInjection/config/service_manager.php b/src/Infrastructure/DependencyInjection/config/service_manager.php new file mode 100644 index 0000000..9d5c0b1 --- /dev/null +++ b/src/Infrastructure/DependencyInjection/config/service_manager.php @@ -0,0 +1,12 @@ + [ + ConfigService::class => ConfigServiceFactory::class, + ], +]; diff --git a/src/Infrastructure/DependencyInjection/src/ConfigProvider.php b/src/Infrastructure/DependencyInjection/src/ConfigProvider.php new file mode 100644 index 0000000..b0c5848 --- /dev/null +++ b/src/Infrastructure/DependencyInjection/src/ConfigProvider.php @@ -0,0 +1,15 @@ + require __DIR__ . './../config/service_manager.php', + ]; + } +} diff --git a/src/Infrastructure/DependencyInjection/src/ConfigServiceFactory.php b/src/Infrastructure/DependencyInjection/src/ConfigServiceFactory.php new file mode 100644 index 0000000..a9d60b7 --- /dev/null +++ b/src/Infrastructure/DependencyInjection/src/ConfigServiceFactory.php @@ -0,0 +1,21 @@ +get('config') + ) + ); + } +} diff --git a/src/Infrastructure/Encryption/config/service_manager.php b/src/Infrastructure/Encryption/config/service_manager.php new file mode 100644 index 0000000..d159a7e --- /dev/null +++ b/src/Infrastructure/Encryption/config/service_manager.php @@ -0,0 +1,12 @@ + [ + EncryptionClient::class => AutoWiringFactory::class, + ], +]; diff --git a/src/Infrastructure/Encryption/src/Client/EncryptionClient.php b/src/Infrastructure/Encryption/src/Client/EncryptionClient.php new file mode 100644 index 0000000..ef73e3a --- /dev/null +++ b/src/Infrastructure/Encryption/src/Client/EncryptionClient.php @@ -0,0 +1,26 @@ +bcrypt = new Bcrypt(); + $this->bcrypt->setCost(10); + } + + public function encrypt(string $value): string { + return $this->bcrypt->create($value); + } + + public function verify(string $value, string $encryptedValue): bool { + return $this->bcrypt->verify($value, $encryptedValue); + } +} diff --git a/src/Infrastructure/Encryption/src/ConfigProvider.php b/src/Infrastructure/Encryption/src/ConfigProvider.php new file mode 100644 index 0000000..cbbed15 --- /dev/null +++ b/src/Infrastructure/Encryption/src/ConfigProvider.php @@ -0,0 +1,15 @@ + require __DIR__ . './../config/service_manager.php', + ]; + } +} diff --git a/src/Infrastructure/Exception/config/service_manager.php b/src/Infrastructure/Exception/config/service_manager.php new file mode 100644 index 0000000..060c7c0 --- /dev/null +++ b/src/Infrastructure/Exception/config/service_manager.php @@ -0,0 +1,12 @@ + [ + ExceptionHandlerMiddleware::class => AutoWiringFactory::class, + ], +]; diff --git a/src/Infrastructure/Exception/src/ConfigProvider.php b/src/Infrastructure/Exception/src/ConfigProvider.php new file mode 100644 index 0000000..96b4859 --- /dev/null +++ b/src/Infrastructure/Exception/src/ConfigProvider.php @@ -0,0 +1,15 @@ + require __DIR__ . './../config/service_manager.php', + ]; + } +} diff --git a/src/Infrastructure/Exception/src/ErrorCode.php b/src/Infrastructure/Exception/src/ErrorCode.php new file mode 100644 index 0000000..72ad4d4 --- /dev/null +++ b/src/Infrastructure/Exception/src/ErrorCode.php @@ -0,0 +1,12 @@ +errorCode = $code; + $this->errorDomain = $domain; + + parent::__construct( + $message, + 0, + $previous + ); + } + + public function getErrorDomain(): ErrorDomain { + return $this->errorDomain; + } + + public function getErrorCode(): ErrorCode { + return $this->errorCode; + } +} \ No newline at end of file diff --git a/src/Infrastructure/Exception/src/Middleware/ExceptionHandlerMiddleware.php b/src/Infrastructure/Exception/src/Middleware/ExceptionHandlerMiddleware.php new file mode 100644 index 0000000..a3c715f --- /dev/null +++ b/src/Infrastructure/Exception/src/Middleware/ExceptionHandlerMiddleware.php @@ -0,0 +1,37 @@ +handle($request); + } catch (Exception $exception) { + $this->logger->exception($exception); + + return new ErrorResponse( + $exception->getErrorDomain(), + $exception->getErrorCode(), + $exception->getMessage() + ); + } + } +} diff --git a/src/Infrastructure/Logging/config/service_manager.php b/src/Infrastructure/Logging/config/service_manager.php new file mode 100644 index 0000000..e31c445 --- /dev/null +++ b/src/Infrastructure/Logging/config/service_manager.php @@ -0,0 +1,23 @@ + [ + TemplateLogger::class => LoggerFactory::class, + Logger::class => MonologLoggerFactory::class, + FileStreamHandler::class => FileStreamHandlerFactory::class, + DoctrineLogHandler::class => AutoWiringFactory::class, + PsrLogMessageProcessor::class => AutoWiringFactory::class, + ], +]; diff --git a/src/Infrastructure/Logging/src/ConfigProvider.php b/src/Infrastructure/Logging/src/ConfigProvider.php new file mode 100644 index 0000000..7272673 --- /dev/null +++ b/src/Infrastructure/Logging/src/ConfigProvider.php @@ -0,0 +1,15 @@ + require __DIR__ . './../config/service_manager.php', + ]; + } +} diff --git a/src/Infrastructure/Logging/src/Factory/FileStreamHandlerFactory.php b/src/Infrastructure/Logging/src/Factory/FileStreamHandlerFactory.php new file mode 100644 index 0000000..ff50634 --- /dev/null +++ b/src/Infrastructure/Logging/src/Factory/FileStreamHandlerFactory.php @@ -0,0 +1,29 @@ +get(ConfigService::class)->resolve('logger'); + + return new FileStreamHandler( + $config['path'] ?? '', + $config['level'] ?? Level::Debug, + $config['pretty'] ?? false, + ); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Logging/src/Factory/LoggerFactory.php b/src/Infrastructure/Logging/src/Factory/LoggerFactory.php new file mode 100644 index 0000000..163b449 --- /dev/null +++ b/src/Infrastructure/Logging/src/Factory/LoggerFactory.php @@ -0,0 +1,22 @@ +get(Logger::class) + ); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Logging/src/Factory/MonologLoggerFactory.php b/src/Infrastructure/Logging/src/Factory/MonologLoggerFactory.php new file mode 100644 index 0000000..a493539 --- /dev/null +++ b/src/Infrastructure/Logging/src/Factory/MonologLoggerFactory.php @@ -0,0 +1,34 @@ +get(ConfigService::class)->resolve('logger'); + + $logfileHandler = $container->get(FileStreamHandler::class); + $databaseHandler = $container->get(DoctrineLogHandler::class); + + return new Logger( + $config['name'] ?? '', + [$logfileHandler, $databaseHandler], + [$container->get(PsrLogMessageProcessor::class)], + ); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Logging/src/Formatter/PrettyFileLogLines.php b/src/Infrastructure/Logging/src/Formatter/PrettyFileLogLines.php new file mode 100644 index 0000000..45cfd03 --- /dev/null +++ b/src/Infrastructure/Logging/src/Formatter/PrettyFileLogLines.php @@ -0,0 +1,77 @@ +level; + $normalizedRecord = parent::format($record); + $timeStamp = (new DateTimeImmutable())->format(DateTimeInterface::ATOM); + + $lines = ["\n[" . $timeStamp . '] ' . $level->getName() . ' >> ' . $normalizedRecord['message']]; + + foreach ($normalizedRecord['context'] ?? [] as $name => $payload) { + if (is_string($payload) && in_array(substr($payload, 0, 1), ['[', '{'])) { + $json = json_decode($payload, true); + } elseif (is_array($payload) || is_object($payload)) { + $json = $payload; + } else { + if (!$payload) { + $payload = ''; + } + $lines[] = "\t" . $name . ':' . $payload; + continue; + } + + try { + $secondLevelLines = []; + + $i = 0; + foreach ((array) $json as $secondLevelName => $secondLevelPayload) { + if (!is_string($secondLevelPayload)) { + $secondLevelPayload = json_encode($secondLevelPayload); + } + + $secondLevelLines[] = $i == 0 ? + "\t" . $name . ":\t" . $secondLevelName . ': ' . $secondLevelPayload : + "\t\t" . $secondLevelName . ': ' . $secondLevelPayload; + + $i++; + } + + foreach ($secondLevelLines as $line) { + $lines[] = $line; + } + } catch (Exception) { + $lines[] = '\t' . $name . ': ' . print_r($json, true); + } + } + + $string = implode(PHP_EOL, $lines); + + return "\n\033[" . $this->colors($level) . 'm' . $string . "\033[0m"; + } + + private function colors(Level $level): string + { + return match($level) { + Level::Debug => '0;2', + Level::Info => '0;32', + Level::Notice => '0;33', + Level::Warning => '0;33', + Level::Error => '0;31', + Level::Critical => '0;31', + Level::Alert => '0;31', + Level::Emergency => '0;31', + }; + } +} diff --git a/src/Infrastructure/Logging/src/Handler/DoctrineLogHandler.php b/src/Infrastructure/Logging/src/Handler/DoctrineLogHandler.php new file mode 100644 index 0000000..4352371 --- /dev/null +++ b/src/Infrastructure/Logging/src/Handler/DoctrineLogHandler.php @@ -0,0 +1,37 @@ +logEntityManager = $logEntityManager; + parent::__construct(); + } + + public function write(LogRecord $record): void + { + $log = new Log(); + + $log->setMessage($record->message); + $log->setLevel($record->level->value); + $log->setLevelName($record->level->getName()); + $log->setExtra($record->extra); + $log->setContext($record->context); + $log->setCreatedAt(new DateTime('now')); + + $this->logEntityManager->persist($log); + $this->logEntityManager->flush(); + } +} diff --git a/src/Infrastructure/Logging/src/Handler/FileStreamHandler.php b/src/Infrastructure/Logging/src/Handler/FileStreamHandler.php new file mode 100644 index 0000000..fca7c38 --- /dev/null +++ b/src/Infrastructure/Logging/src/Handler/FileStreamHandler.php @@ -0,0 +1,21 @@ +setFormatter(new PrettyFileLogLines('y-m-D')); + } + } +} diff --git a/src/Infrastructure/Logging/src/Logger/Logger.php b/src/Infrastructure/Logging/src/Logger/Logger.php new file mode 100644 index 0000000..a34c867 --- /dev/null +++ b/src/Infrastructure/Logging/src/Logger/Logger.php @@ -0,0 +1,89 @@ + $exception->getTraceAsString() + ]*/; + + if ($exception instanceof Exception) { + $exceptionContext = array_merge([ + 'errorDomain' => $exception->getErrorDomain()->value, + 'errorCode' => $exception->getErrorCode()->value, + ], $exceptionContext); + } + + $this->monologLogger->error( + $message ?: $exception->getMessage(), + $this->getMergedContexts($exceptionContext) + ); + } + + private function getMergedContexts(array $context): array + { + return array_merge($this->defaultContext, $context); + } + + public function emergency(Stringable|string $message, array $context = []): void + { + $this->monologLogger->emergency($message, $this->getMergedContexts($context)); + } + + public function alert(Stringable|string $message, array $context = []): void + { + $this->monologLogger->alert($message, $this->getMergedContexts($context)); + } + + public function critical(Stringable|string $message, array $context = []): void + { + $this->monologLogger->critical($message, $this->getMergedContexts($context)); + } + + public function error(Stringable|string $message, array $context = []): void + { + $this->monologLogger->error($message, $this->getMergedContexts($context)); + } + + public function warning(Stringable|string $message, array $context = []): void + { + $this->monologLogger->warning($message, $this->getMergedContexts($context)); + } + + public function notice(Stringable|string $message, array $context = []): void + { + $this->monologLogger->notice($message, $this->getMergedContexts($context)); + } + + public function info(Stringable|string $message, array $context = []): void + { + $this->monologLogger->info($message, $this->getMergedContexts($context)); + } + + public function debug(Stringable|string $message, array $context = []): void + { + $this->monologLogger->debug($message, $this->getMergedContexts($context)); + } + + public function log($level, Stringable|string $message, array $context = []): void + { + $this->monologLogger->log($level, $message, $this->getMergedContexts($context)); + } +} diff --git a/src/Infrastructure/Rbac/config/service_manager.php b/src/Infrastructure/Rbac/config/service_manager.php new file mode 100644 index 0000000..b231742 --- /dev/null +++ b/src/Infrastructure/Rbac/config/service_manager.php @@ -0,0 +1,12 @@ + [ + EnsureAuthorizationMiddleware::class => InjectionFactory::class + ], +]; diff --git a/src/Infrastructure/Rbac/src/ConfigProvider.php b/src/Infrastructure/Rbac/src/ConfigProvider.php new file mode 100644 index 0000000..582ca7d --- /dev/null +++ b/src/Infrastructure/Rbac/src/ConfigProvider.php @@ -0,0 +1,15 @@ + require __DIR__ . './../config/service_manager.php', + ]; + } +} diff --git a/src/Infrastructure/Rbac/src/Middleware/EnsureAuthorizationMiddleware.php b/src/Infrastructure/Rbac/src/Middleware/EnsureAuthorizationMiddleware.php new file mode 100644 index 0000000..d3b9581 --- /dev/null +++ b/src/Infrastructure/Rbac/src/Middleware/EnsureAuthorizationMiddleware.php @@ -0,0 +1,70 @@ +toArray() as $route) { + $path = $route['path']; + $permission = $route['name']; + $this->routes[$path] = $permission; + } + } + + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface + { + /** @var User $user */ + $user = $request->getAttribute(LoggedInUserMiddleware::USER_KEY); + + if ( + !$this->checkRights( + $request->getRequestTarget(), + $user + ) + ) { + return new ForbiddenResponse(); + } + + return $handler->handle($request); + } + + private function checkRights( + string $targetPath, + User $user + ): bool { + $role = $user->getRole(); + $permissions = $role->getPermissions(); + $targetApi = $this->routes[$targetPath]; + + /** @var Permission $permission */ + foreach($permissions as $permission) { + if ($permission->getIdentifier() === $targetApi) { + return true; + } + } + + return false; + } +} \ No newline at end of file diff --git a/src/Infrastructure/Request/config/service_manager.php b/src/Infrastructure/Request/config/service_manager.php new file mode 100644 index 0000000..92b91fa --- /dev/null +++ b/src/Infrastructure/Request/config/service_manager.php @@ -0,0 +1,22 @@ + [ + /// Service + RequestService::class => RequestServiceFactory::class, + + /// Middleware + AnalyzeHeaderMiddleware::class => AutoWiringFactory::class, + AnalyzeBodyMiddleware::class => AutoWiringFactory::class, + InternalRequestMiddleware::class => AutoWiringFactory::class, + ], +]; diff --git a/src/Infrastructure/Request/src/ConfigProvider.php b/src/Infrastructure/Request/src/ConfigProvider.php new file mode 100644 index 0000000..212986c --- /dev/null +++ b/src/Infrastructure/Request/src/ConfigProvider.php @@ -0,0 +1,15 @@ + require __DIR__ . './../config/service_manager.php', + ]; + } +} diff --git a/src/Infrastructure/Request/src/Exception/ApiIdentifierUnknownException.php b/src/Infrastructure/Request/src/Exception/ApiIdentifierUnknownException.php new file mode 100644 index 0000000..eebb07b --- /dev/null +++ b/src/Infrastructure/Request/src/Exception/ApiIdentifierUnknownException.php @@ -0,0 +1,22 @@ +getMessage() + ), + 0, + $previous + ); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Request/src/Exception/ApiServiceUnknownException.php b/src/Infrastructure/Request/src/Exception/ApiServiceUnknownException.php new file mode 100644 index 0000000..e349a56 --- /dev/null +++ b/src/Infrastructure/Request/src/Exception/ApiServiceUnknownException.php @@ -0,0 +1,20 @@ +get(ConfigService::class); + $apiKey = $configService->resolve("api.keys.template"); + + return new RequestService( + $apiKey, + $configService + ); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Request/src/Middleware/AnalyzeBodyMiddleware.php b/src/Infrastructure/Request/src/Middleware/AnalyzeBodyMiddleware.php new file mode 100644 index 0000000..a732d08 --- /dev/null +++ b/src/Infrastructure/Request/src/Middleware/AnalyzeBodyMiddleware.php @@ -0,0 +1,43 @@ +getHeaderLine(self::CONTENT_TYPE_HEADER); + + if (str_contains($contentType, self::CONTENT_TYPE_APPLICATION_JSON)) { + $jsonData = json_decode( + $request->getBody()->getContents(), + true + ); + } + + return $handler->handle($request->withAttribute( + self::JSON_DATA, + $jsonData + )); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Request/src/Middleware/AnalyzeHeaderMiddleware.php b/src/Infrastructure/Request/src/Middleware/AnalyzeHeaderMiddleware.php new file mode 100644 index 0000000..129efc5 --- /dev/null +++ b/src/Infrastructure/Request/src/Middleware/AnalyzeHeaderMiddleware.php @@ -0,0 +1,37 @@ +getHeaders()['host'][0] + ?? $request->getHeaders()['x-forwarded-host'][0] + ?? 'UNKNOWN' + )[0]; + + return $handler->handle($request->withAttribute( + self::HOST_ATTRIBUTE, + $host + )); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Request/src/Middleware/InternalRequestMiddleware.php b/src/Infrastructure/Request/src/Middleware/InternalRequestMiddleware.php new file mode 100644 index 0000000..fe92760 --- /dev/null +++ b/src/Infrastructure/Request/src/Middleware/InternalRequestMiddleware.php @@ -0,0 +1,47 @@ +apiKeys = $this->configService->resolve('apiKeys')->toArray(); + } + + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface { + $requestApiKey = $request->getHeader(self::API_KEY_HEADER)[0] ?? null; + + if ($requestApiKey === null) { + return new UnauthorizedResponse(); + } + + foreach ($this->apiKeys as $application => $apiKey) { + if ($apiKey === $requestApiKey) + return $handler->handle( + $request->withAttribute( + self::APPLICATION_KEY, + $application + ) + ); + } + + return new UnauthorizedResponse(); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Request/src/Service/RequestService.php b/src/Infrastructure/Request/src/Service/RequestService.php new file mode 100644 index 0000000..46d6539 --- /dev/null +++ b/src/Infrastructure/Request/src/Service/RequestService.php @@ -0,0 +1,95 @@ +client = new Client(); + $this->services = $this->configService->resolve('api.services')->toArray(); + } + + /** + * @throws ApiRequestFailedException + * @throws ApiIdentifierUnknownException + * @throws ApiPropertyUndefinedException + * @throws ApiServiceUnknownException + */ + public function request( + string $serviceIdentifier, + string $apiIdentifier, + ?array $data + ): ResponseInterface { + $serviceConfig = $this->services[$serviceIdentifier] ?? throw new ApiServiceUnknownException($serviceIdentifier); + $apiHost = $serviceConfig['host']; + $apiConfig = $serviceConfig['apis'][$apiIdentifier] ?? throw new ApiIdentifierUnknownException($serviceIdentifier, $apiIdentifier); + + + $method = $apiConfig['method'] ?? + throw new ApiPropertyUndefinedException( + $serviceIdentifier, + $apiIdentifier, + 'method' + ); + + $url = $apiHost . $apiConfig['path'] ?? + throw new ApiPropertyUndefinedException( + $serviceIdentifier, + $apiIdentifier, + 'path' + ); + + return $this->sendRequest( + $method, + $url, + $data + ); + } + + private function sendRequest( + string $method, + string $url, + ?array $data + ): ResponseInterface { + $options = [ + 'headers' => [ + InternalRequestMiddleware::API_KEY_HEADER => $this->apiKey + ] + ]; + + if (!empty($data)) { + $options['body'] = json_encode($data); + $options['headers']['Content-Type'] = 'application/json'; + } + + try { + return $this->client->request( + $method, + $url, + $options + ); + } catch (\Throwable $e) { + throw new ApiRequestFailedException( + $url, + $method, + $e + ); + } + } +} diff --git a/src/Infrastructure/Response/src/ErrorResponse.php b/src/Infrastructure/Response/src/ErrorResponse.php new file mode 100644 index 0000000..8413cc6 --- /dev/null +++ b/src/Infrastructure/Response/src/ErrorResponse.php @@ -0,0 +1,37 @@ + [ + 'code' => sprintf( + self::ERROR_CODE, + $domain->value, + $code->value + ) + ] + ]; + + if ($message !== null) { + $response['error']['message'] = $message; + } + + parent::__construct( + $response, + 400 + ); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Response/src/ForbiddenResponse.php b/src/Infrastructure/Response/src/ForbiddenResponse.php new file mode 100644 index 0000000..a1c7107 --- /dev/null +++ b/src/Infrastructure/Response/src/ForbiddenResponse.php @@ -0,0 +1,16 @@ + [ + SessionMiddleware::class => AutoWiringFactory::class, + LoggedInUserMiddleware::class => AutoWiringFactory::class + ], +]; diff --git a/src/Infrastructure/Session/src/ConfigProvider.php b/src/Infrastructure/Session/src/ConfigProvider.php new file mode 100644 index 0000000..2d6189c --- /dev/null +++ b/src/Infrastructure/Session/src/ConfigProvider.php @@ -0,0 +1,15 @@ + require __DIR__ . './../config/service_manager.php', + ]; + } +} diff --git a/src/Infrastructure/Session/src/Middleware/LoggedInUserMiddleware.php b/src/Infrastructure/Session/src/Middleware/LoggedInUserMiddleware.php new file mode 100644 index 0000000..72c17b8 --- /dev/null +++ b/src/Infrastructure/Session/src/Middleware/LoggedInUserMiddleware.php @@ -0,0 +1,41 @@ +getAttribute( + SessionMiddleware::SESSION_ATTRIBUTE + ) ?? null; + + if ($session === null) { + return new UnauthorizedResponse(); + } + + if ($session->getUser() === null) { + return new UnauthorizedResponse(); + } + + return $handler->handle( + $request->withAttribute( + self::USER_KEY, + $session->getUser() + ) + ); + } +} \ No newline at end of file diff --git a/src/Infrastructure/Session/src/Middleware/SessionMiddleware.php b/src/Infrastructure/Session/src/Middleware/SessionMiddleware.php new file mode 100644 index 0000000..7ff4a47 --- /dev/null +++ b/src/Infrastructure/Session/src/Middleware/SessionMiddleware.php @@ -0,0 +1,81 @@ +userSessionRepository = $this->entityManager->getRepository(UserSession::class); + } + + public function process( + ServerRequestInterface $request, + RequestHandlerInterface $handler + ): ResponseInterface + { + /** @var ?UserSession $session */ + $session = null; + $sessionId = null; + $cookies = []; + $cookieHeaders = $request->getHeaders()['cookie'] ?? []; + + foreach ($cookieHeaders as $cookie) { + $properties = explode(';', $cookie); + + foreach ($properties as $property) { + $keyValuePair = explode('=', $property); + $cookies[trim($keyValuePair[0])] = trim($keyValuePair[1]); + } + } + + if (isset($cookies[self::SESSION_COOKIE_NAME])) { + $sessionId = $cookies[self::SESSION_COOKIE_NAME]; + } + + if ($sessionId !== null) { + $session = $this->userSessionRepository->findOneBy([ + 'id' => $sessionId + ]) ?? null; + } + + if($session === null) { + $session = $this->userSessionBuilder->build(); + + $this->entityManager->persist($session); + $this->entityManager->flush(); + } + + $response = $handler->handle( + $request->withAttribute( + self::SESSION_ATTRIBUTE, + $session + ) + ); + + return $response->withAddedHeader( + 'Set-Cookie', + sprintf( + "%s=%s; Path=/", + self::SESSION_COOKIE_NAME, + $session->getId()->toString() + ) + ); + } +} \ No newline at end of file diff --git a/src/Infrastructure/UuidGenerator/src/UuidGenerator.php b/src/Infrastructure/UuidGenerator/src/UuidGenerator.php new file mode 100644 index 0000000..bde82b3 --- /dev/null +++ b/src/Infrastructure/UuidGenerator/src/UuidGenerator.php @@ -0,0 +1,18 @@ +getUuidBuilder()); + $factory->setCodec($codec); + return $factory->uuid1(); + } +} + +?> \ No newline at end of file