commit 3d60d4b9dc570ff76964f5f45a6298cab9545739 Author: flo Date: Sat Aug 24 20:33:05 2024 +0000 Initial commit 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