commit c0c461a844d48e931bb91632d7a0f79ef703329e
Author: Flo <35579573+TheFlawww@users.noreply.github.com>
Date: Wed Feb 14 20:08:01 2024 +0100
Initial commit
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..53d7ce1
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,20 @@
+# DB Configuration
+DB_DRIVER=pdo_mysql
+DB_HOST=homepage-backend-mysql
+DB_PORT=3306
+DB_USER=homepage
+DB_PASSWORD=pass
+DB_NAME=homepage
+DB_NAME_LOG=log
+
+# API Keys
+AUTH_API_KEY=
+NOTIFICATION_API_KEY=
+FILE_API_KEY=
+HOMEPAGE_API_KEY=
+BEE_API_KEY=
+
+# Homepage 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/.github/workflows/create branch on issue.yml b/.github/workflows/create branch on issue.yml
new file mode 100644
index 0000000..789cd5a
--- /dev/null
+++ b/.github/workflows/create branch on issue.yml
@@ -0,0 +1,40 @@
+name: Create Branch on Issue Transition to In Progress
+
+on:
+ issues:
+ types:
+ - edited
+
+jobs:
+ create-branch:
+ if: github.event.changes.issue.labels
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ - name: Get issue details
+ run: |
+ issue_title=$(jq -r ".issue.number" "$GITHUB_EVENT_PATH")
+ previous_labels=$(jq -r ".changes.labels.from" "$GITHUB_EVENT_PATH")
+ current_labels=$(jq -r ".issue.labels | map(.name) | join(\", \")" "$GITHUB_EVENT_PATH")
+ branch_name=""
+
+ # Check if the issue transitioned from 'Todo' to 'In Progress'
+ if [[ "$previous_labels" == *"Todo"* && "$current_labels" == *"In Progress"* ]]; then
+ project_name="hp-be" # Verwende "hp-be" als Projektnamen
+ branch_name="${project_name}-${issue_title}"
+ echo "Branch name: $branch_name"
+ fi
+
+ echo "::set-output name=branch_name::$branch_name"
+ shell: bash
+
+ - name: Create and switch to new branch
+ run: |
+ branch_name="${{ steps.create-branch.outputs.branch_name }}"
+ if [ -n "$branch_name" ]; then
+ git checkout -b "$branch_name"
+ git push origin "$branch_name"
+ fi
+ working-directory: ${{ github.workspace }}
diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml
new file mode 100644
index 0000000..1ef7894
--- /dev/null
+++ b/.github/workflows/php.yml
@@ -0,0 +1,39 @@
+name: PHP Composer
+
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "main" ]
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Validate composer.json and composer.lock
+ run: composer validate --strict
+
+ - name: Cache Composer packages
+ id: composer-cache
+ uses: actions/cache@v3
+ with:
+ path: vendor
+ key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
+ restore-keys: |
+ ${{ runner.os }}-php-
+
+ - name: Install dependencies
+ run: composer install --prefer-dist --no-progress
+
+ # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit"
+ # Docs: https://getcomposer.org/doc/articles/scripts.md
+
+ # - name: Run test suite
+ # run: composer run-script test
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..810eee8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+/.phpcs-cache
+/.phpunit.result.cache
+/.idea
+/clover.xml
+/coveralls-upload.json
+/phpunit.xml
+
+/data/cache/
+/data/db/
+/public/data/
+/var/
+/vendor/
+
+*.env
+composer.lock
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/doctrine-migrations-log.php b/bin/doctrine-migrations-log.php
new file mode 100644
index 0000000..7a25153
--- /dev/null
+++ b/bin/doctrine-migrations-log.php
@@ -0,0 +1,91 @@
+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(HomepageEntityManager::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..2f8b013
--- /dev/null
+++ b/bin/doctrine-migrations.php
@@ -0,0 +1,91 @@
+get('config');
+$doctrineConfig = $config['doctrine'];
+$paths = $doctrineConfig['driver']['orm_homepage_annotation_driver']['paths'];
+
+$dbParams = $doctrineConfig['connection']['orm_homepage']['params'];
+$migrationsConf = $doctrineConfig['migrations_configuration']['orm_homepage'];
+
+$reader = new AnnotationReader();
+$driver = new AnnotationDriver($reader, $paths);
+
+$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
+$config->setMetadataDriverImpl($driver);
+$entityManager = $container->get(HomepageEntityManager::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/build b/bin/script/build
new file mode 100755
index 0000000..0cf812a
--- /dev/null
+++ b/bin/script/build
@@ -0,0 +1,3 @@
+#!/bin/bash
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+$SCRIPT_DIR/exec build
\ No newline at end of file
diff --git a/bin/script/down b/bin/script/down
new file mode 100755
index 0000000..7b6b977
--- /dev/null
+++ b/bin/script/down
@@ -0,0 +1,3 @@
+#!/bin/bash
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+$SCRIPT_DIR/exec down
\ No newline at end of file
diff --git a/bin/script/exec b/bin/script/exec
new file mode 100755
index 0000000..e57c87c
--- /dev/null
+++ b/bin/script/exec
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+COMMAND="$@"
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+
+#LOAD ENV VARIABLES FROM .ENV
+export $(grep -v '^#' "${SCRIPT_DIR}/../../.env" | xargs)
+
+#MAC
+if [[ "$OSTYPE" == "darwin"* ]]; then
+ docker-compose -f "${SCRIPT_DIR}/../../docker/docker-compose-mac.yml" $COMMAND
+
+#LINUX
+elif [[ "$OSTYPE" == "linux-gnu" ]]; then
+ docker-compose -f "${SCRIPT_DIR}/../../docker/docker-compose.yml" $COMMAND
+
+else
+ echo "Dieses Skript wird auf deinem Gerät nicht unterstützt"
+ exit 1
+fi
+
+#UNSET ENV VARIABLES FROM .ENV
+unset $(grep -v '^#' "${SCRIPT_DIR}/../../.env" | sed -E 's/(.*)=.*/\1/' | xargs)
diff --git a/bin/script/init b/bin/script/init
new file mode 100755
index 0000000..a41c9ab
--- /dev/null
+++ b/bin/script/init
@@ -0,0 +1,32 @@
+#!/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 me again"
+ exit 1
+fi
+
+# Build and start docker containers
+$SCRIPT_DIR/exec build
+$SCRIPT_DIR/exec up -d
+
+# Source drun
+source $ENV_DIR/bin/script/drun
+
+# Install PHP packages
+drun homepage-backend composer install
+
+# Migrate databases to current version
+drun homepage-backend composer dmm
+drun homepage-backend composer dmlm
+
+# Insert setup for project after this line
+drun homepage-backend composer console rbac:update
+drun homepage-backend composer console init:data
\ No newline at end of file
diff --git a/bin/script/stop b/bin/script/stop
new file mode 100755
index 0000000..64393e9
--- /dev/null
+++ b/bin/script/stop
@@ -0,0 +1,3 @@
+#!/bin/bash
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+$SCRIPT_DIR/exec stop
\ No newline at end of file
diff --git a/bin/script/up b/bin/script/up
new file mode 100755
index 0000000..7bd3164
--- /dev/null
+++ b/bin/script/up
@@ -0,0 +1,3 @@
+#!/bin/bash
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+$SCRIPT_DIR/exec up -d
\ No newline at end of file
diff --git a/composer.development.json b/composer.development.json
new file mode 100644
index 0000000..e2367b8
--- /dev/null
+++ b/composer.development.json
@@ -0,0 +1,122 @@
+{
+ "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": {
+ "Homepage\\API\\Console\\": "src\/ApiDomain\/Console\/src",
+ "Homepage\\API\\External\\Authentication\\": "src\/ApiDomain\/External\/Authentication\/src",
+ "Homepage\\API\\External\\Health\\": "src\/ApiDomain\/External\/Health\/src",
+ "Homepage\\API\\External\\Product\\": "src\/ApiDomain\/External\/Product\/src",
+ "Homepage\\API\\External\\User\\": "src\/ApiDomain\/External\/User\/src",
+ "Homepage\\Data\\Business\\": "src\/DataDomain\/Business\/src",
+ "Homepage\\Data\\Log\\": "src\/DataDomain\/Log\/src",
+ "Homepage\\Handling\\Product\\": "src\/HandlingDomain\/Product\/src",
+ "Homepage\\Handling\\Registration\\": "src\/HandlingDomain\/Registration\/src",
+ "Homepage\\Handling\\Role\\": "src\/HandlingDomain\/Role\/src",
+ "Homepage\\Handling\\User\\": "src\/HandlingDomain\/User\/src",
+ "Homepage\\Handling\\UserSession\\": "src\/HandlingDomain\/UserSession\/src",
+ "Homepage\\Infrastructure\\Database\\": "src\/Infrastructure\/Database\/src",
+ "Homepage\\Infrastructure\\DependencyInjection\\": "src\/Infrastructure\/DependencyInjection\/src",
+ "Homepage\\Infrastructure\\Encryption\\": "src\/Infrastructure\/Encryption\/src",
+ "Homepage\\Infrastructure\\Exception\\": "src\/Infrastructure\/Exception\/src",
+ "Homepage\\Infrastructure\\Logging\\": "src\/Infrastructure\/Logging\/src",
+ "Homepage\\Infrastructure\\Rbac\\": "src\/Infrastructure\/Rbac\/src",
+ "Homepage\\Infrastructure\\Request\\": "src\/Infrastructure\/Request\/src",
+ "Homepage\\Infrastructure\\Response\\": "src\/Infrastructure\/Response\/src",
+ "Homepage\\Infrastructure\\Session\\": "src\/Infrastructure\/Session\/src",
+ "Homepage\\Infrastructure\\UuidGenerator\\": "src\/Infrastructure\/UuidGenerator\/src"
+ }
+ },
+ "extra": {
+ "teewurst\/psr4-advanced-wildcard-composer-plugin": {
+ "autoload": {
+ "psr-4": {
+ "Homepage\\API\\%s\\%s\\": "src\/ApiDomain\/{*}\/{*}\/src\/",
+ "Homepage\\Data\\%s\\": "src\/DataDomain\/{*}\/src\/",
+ "Homepage\\Handling\\%s\\": "src\/HandlingDomain\/{*}\/src\/",
+ "Homepage\\Infrastructure\\%s\\": "src\/Infrastructure\/{*}\/src\/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Homepage\\API\\%s\\%s\\": "src\/ApiDomain\/{*}\/{*}\/src\/",
+ "Homepage\\Data\\%s\\": "src\/DataDomain\/{*}\/src\/",
+ "Homepage\\Handling\\%s\\": "src\/HandlingDomain\/{*}\/src\/",
+ "Homepage\\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",
+ "serve": "php -S 0.0.0.0:8080 -t public\/",
+ "unserve": "killall -9 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"
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Homepage\\API\\External\\Authentication\\": "src\/ApiDomain\/External\/Authentication\/src",
+ "Homepage\\API\\External\\Health\\": "src\/ApiDomain\/External\/Health\/src",
+ "Homepage\\API\\External\\Product\\": "src\/ApiDomain\/External\/Product\/src",
+ "Homepage\\API\\External\\User\\": "src\/ApiDomain\/External\/User\/src",
+ "Homepage\\Data\\Business\\": "src\/DataDomain\/Business\/src",
+ "Homepage\\Data\\Log\\": "src\/DataDomain\/Log\/src",
+ "Homepage\\Handling\\Product\\": "src\/HandlingDomain\/Product\/src",
+ "Homepage\\Handling\\Registration\\": "src\/HandlingDomain\/Registration\/src",
+ "Homepage\\Handling\\Role\\": "src\/HandlingDomain\/Role\/src",
+ "Homepage\\Handling\\User\\": "src\/HandlingDomain\/User\/src",
+ "Homepage\\Handling\\UserSession\\": "src\/HandlingDomain\/UserSession\/src",
+ "Homepage\\Infrastructure\\Database\\": "src\/Infrastructure\/Database\/src",
+ "Homepage\\Infrastructure\\DependencyInjection\\": "src\/Infrastructure\/DependencyInjection\/src",
+ "Homepage\\Infrastructure\\Encryption\\": "src\/Infrastructure\/Encryption\/src",
+ "Homepage\\Infrastructure\\Exception\\": "src\/Infrastructure\/Exception\/src",
+ "Homepage\\Infrastructure\\Logging\\": "src\/Infrastructure\/Logging\/src",
+ "Homepage\\Infrastructure\\Rbac\\": "src\/Infrastructure\/Rbac\/src",
+ "Homepage\\Infrastructure\\Request\\": "src\/Infrastructure\/Request\/src",
+ "Homepage\\Infrastructure\\Response\\": "src\/Infrastructure\/Response\/src",
+ "Homepage\\Infrastructure\\Session\\": "src\/Infrastructure\/Session\/src",
+ "Homepage\\Infrastructure\\UuidGenerator\\": "src\/Infrastructure\/UuidGenerator\/src"
+ }
+ }
+}
\ No newline at end of file
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..6d4d578
--- /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": {
+ "Homepage\\API\\Console\\": "src/ApiDomain/Console/src/"
+ }
+ },
+ "extra": {
+ "teewurst/psr4-advanced-wildcard-composer-plugin": {
+ "autoload": {
+ "psr-4": {
+ "Homepage\\API\\%s\\%s\\": "src/ApiDomain/{*}/{*}/src/",
+ "Homepage\\Data\\%s\\": "src/DataDomain/{*}/src/",
+ "Homepage\\Handling\\%s\\": "src/HandlingDomain/{*}/src/",
+ "Homepage\\Infrastructure\\%s\\": "src/Infrastructure/{*}/src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Homepage\\API\\%s\\%s\\": "src/ApiDomain/{*}/{*}/src/",
+ "Homepage\\Data\\%s\\": "src/DataDomain/{*}/src/",
+ "Homepage\\Handling\\%s\\": "src/HandlingDomain/{*}/src/",
+ "Homepage\\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",
+ "serve": "php -S 0.0.0.0:8080 -t public/",
+ "unserve": "killall -9 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..553b280
--- /dev/null
+++ b/config/autoload/api.global.php
@@ -0,0 +1,29 @@
+ [
+ 'keys' => [
+ 'homepage' => $_ENV['HOMEPAGE_API_KEY'],
+ 'notification' => $_ENV['NOTIFICATION_API_KEY'],
+ ],
+
+ 'services' => [
+ 'homepage' => [
+ 'host' => 'homepage-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..48c188f
--- /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..62e86ef
--- /dev/null
+++ b/config/autoload/logger.global.php
@@ -0,0 +1,14 @@
+ [
+ 'name' => 'homepage.backend',
+ 'path' => APP_ROOT . '/var/log/homepage.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..162c3f8
--- /dev/null
+++ b/config/config.php
@@ -0,0 +1,91 @@
+ '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
+ \Homepage\Data\Business\ConfigProvider::class,
+ \Homepage\Data\Log\ConfigProvider::class,
+
+ // Infrastructure
+ \Homepage\Infrastructure\Database\ConfigProvider::class,
+ \Homepage\Infrastructure\DependencyInjection\ConfigProvider::class,
+ \Homepage\Infrastructure\Encryption\ConfigProvider::class,
+ \Homepage\Infrastructure\Exception\ConfigProvider::class,
+ \Homepage\Infrastructure\Logging\ConfigProvider::class,
+ \Homepage\Infrastructure\Rbac\ConfigProvider::class,
+ \Homepage\Infrastructure\Request\ConfigProvider::class,
+ \Homepage\Infrastructure\Session\ConfigProvider::class,
+
+ // HandlingDomain
+ \Homepage\Handling\Product\ConfigProvider::class,
+ \Homepage\Handling\User\ConfigProvider::class,
+ \Homepage\Handling\UserSession\ConfigProvider::class,
+ \Homepage\Handling\Registration\ConfigProvider::class,
+
+ // API
+ /// Command
+ \Homepage\API\Console\ConfigProvider::class,
+
+ /// External
+ \Homepage\API\External\Health\ConfigProvider::class,
+ \Homepage\API\External\Product\ConfigProvider::class,
+ \Homepage\API\External\User\ConfigProvider::class,
+ \Homepage\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..a6f193a
--- /dev/null
+++ b/config/pipeline.php
@@ -0,0 +1,92 @@
+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 Homepage Space
+ $app->pipe(HomepageExceptionHandlerMiddleware::class);
+ $app->pipe(AnalyzeHeaderMiddleware::class);
+
+ //// Homepage 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..8ea95f5
--- /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/homepage/Version20230922085011.php b/data/migrations/homepage/Version20230922085011.php
new file mode 100644
index 0000000..2af462e
--- /dev/null
+++ b/data/migrations/homepage/Version20230922085011.php
@@ -0,0 +1,33 @@
+addSql($sql);
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql("DROP TABLE role;");
+ }
+}
diff --git a/data/migrations/homepage/Version20230922092351.php b/data/migrations/homepage/Version20230922092351.php
new file mode 100644
index 0000000..4bba491
--- /dev/null
+++ b/data/migrations/homepage/Version20230922092351.php
@@ -0,0 +1,36 @@
+addSql($sql);
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql("DROP TABLE permission;");
+ }
+}
diff --git a/data/migrations/homepage/Version20230922092754.php b/data/migrations/homepage/Version20230922092754.php
new file mode 100644
index 0000000..1af30a8
--- /dev/null
+++ b/data/migrations/homepage/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/homepage/Version20230922101352.php b/data/migrations/homepage/Version20230922101352.php
new file mode 100644
index 0000000..c9d3fa3
--- /dev/null
+++ b/data/migrations/homepage/Version20230922101352.php
@@ -0,0 +1,38 @@
+addSql($sql);
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql("DROP TABLE product;");
+ }
+}
diff --git a/data/migrations/homepage/Version20230922101354.php b/data/migrations/homepage/Version20230922101354.php
new file mode 100644
index 0000000..246eef3
--- /dev/null
+++ b/data/migrations/homepage/Version20230922101354.php
@@ -0,0 +1,41 @@
+addSql($sql);
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql("DROP TABLE user;");
+ }
+}
diff --git a/data/migrations/homepage/Version20230922101355.php b/data/migrations/homepage/Version20230922101355.php
new file mode 100644
index 0000000..4922356
--- /dev/null
+++ b/data/migrations/homepage/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/homepage/Version20230924113403.php b/data/migrations/homepage/Version20230924113403.php
new file mode 100644
index 0000000..6f76185
--- /dev/null
+++ b/data/migrations/homepage/Version20230924113403.php
@@ -0,0 +1,38 @@
+addSql($sql);
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->addSql("DROP TABLE registration;");
+ }
+}
diff --git a/data/migrations/homepage/Version20231021103120.php b/data/migrations/homepage/Version20231021103120.php
new file mode 100644
index 0000000..e7a3cc7
--- /dev/null
+++ b/data/migrations/homepage/Version20231021103120.php
@@ -0,0 +1,33 @@
+addSql($sql);
+ }
+
+ public function down(Schema $schema): void
+ {
+ $sql = "ALTER TABLE registration ADD COLUMN password varchar(255) NOT NULL after username";
+
+ $this->addSql($sql);
+ }
+}
diff --git a/data/migrations/homepage/Version20231021112654.php b/data/migrations/homepage/Version20231021112654.php
new file mode 100644
index 0000000..619ea3b
--- /dev/null
+++ b/data/migrations/homepage/Version20231021112654.php
@@ -0,0 +1,33 @@
+addSql($sql);
+ }
+
+ public function down(Schema $schema): void
+ {
+ $sql = "ALTER TABLE product DROP COLUMN url;";
+
+ $this->addSql($sql);
+ }
+}
diff --git a/data/migrations/log/Version20230922150649.php b/data/migrations/log/Version20230922150649.php
new file mode 100644
index 0000000..4e5f169
--- /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..c696adf
--- /dev/null
+++ b/docker/docker-compose-mac.yml
@@ -0,0 +1,51 @@
+version: '3'
+networks:
+ homepage:
+ external: true
+
+services:
+ homepage-backend-mysql:
+ image: homepage-backend-mysql
+ networks:
+ - homepage
+ build:
+ context: ./../
+ dockerfile: ./docker/mysql/dockerfile
+ volumes:
+ - /Users/flo/dev/backend/homepage/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
+
+ homepage-backend-app:
+ image: homepage-backend-app
+ networks:
+ - homepage
+ build:
+ context: ./../
+ dockerfile: ./docker/php/dockerfile
+ volumes:
+ - /Users/flo/dev/backend/homepage/:/var/www/html:z
+ ports:
+ - 9000:9000
+ depends_on:
+ homepage-backend-mysql:
+ condition: service_healthy
+
+ homepage-backend-nginx:
+ image: homepage-backend-nginx
+ networks:
+ - homepage
+ build:
+ context: ./../
+ dockerfile: ./docker/nginx/dockerfile
+ ports:
+ - 8080:80
+ depends_on:
+ - homepage-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..b47faa1
--- /dev/null
+++ b/docker/docker-compose.yml
@@ -0,0 +1,51 @@
+version: '3'
+networks:
+ homepage:
+ external: true
+
+services:
+ homepage-backend-mysql:
+ image: homepage-backend-mysql
+ networks:
+ - homepage
+ 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
+
+ homepage-backend-app:
+ image: homepage-backend-app
+ networks:
+ - homepage
+ build:
+ context: ./../
+ dockerfile: ./docker/php/dockerfile
+ volumes:
+ - ./../:/var/www/html:z
+ ports:
+ - 9000:9000
+ depends_on:
+ homepage-backend-mysql:
+ condition: service_healthy
+
+ homepage-backend-nginx:
+ image: homepage-backend-nginx
+ networks:
+ - homepage
+ build:
+ context: ./../
+ dockerfile: ./docker/nginx/dockerfile
+ ports:
+ - 8080:80
+ depends_on:
+ - homepage-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..3ce24b1
--- /dev/null
+++ b/docker/mysql/scripts/initdb.sql
@@ -0,0 +1,4 @@
+CREATE DATABASE IF NOT EXISTS `log`;
+CREATE DATABASE IF NOT EXISTS `homepage`;
+
+GRANT ALL PRIVILEGES on *.* to 'homepage'@'%';
\ 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..e915e62
--- /dev/null
+++ b/docker/nginx/config/nginx.conf
@@ -0,0 +1,15 @@
+upstream host-backend-app {
+ server homepage-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..0c04f3e
--- /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..8cc9544
--- /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..bc8a179
--- /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..94b3f05
--- /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('homepage-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..8e145c3
--- /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..764ea59
--- /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..0f5e190
--- /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..a30fe7d
--- /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..d9525ba
--- /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..37f7947
--- /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..81f34f7
--- /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..2e96321
--- /dev/null
+++ b/src/ApiDomain/External/Authentication/src/Handler/RegisterUserHandler.php
@@ -0,0 +1,45 @@
+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..183c3ce
--- /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..bed1b3f
--- /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..2575cf1
--- /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..5139048
--- /dev/null
+++ b/src/ApiDomain/External/Health/src/Handler/HealthHandler.php
@@ -0,0 +1,23 @@
+
diff --git a/src/ApiDomain/External/Product/config/routes.php b/src/ApiDomain/External/Product/config/routes.php
new file mode 100644
index 0000000..df11262
--- /dev/null
+++ b/src/ApiDomain/External/Product/config/routes.php
@@ -0,0 +1,51 @@
+ 'product.product-list',
+ 'path' => '/api/product/product-list',
+ 'allowed_methods' => ['GET'],
+ 'middleware' => [
+ LoggedInUserMiddleware::class,
+ EnsureAuthorizationMiddleware::class,
+ ProductListHandler::class
+ ],
+ ],
+ [
+ 'name' => 'product.create-product',
+ 'path' => '/api/product/create-product',
+ 'allowed_methods' => ['POST'],
+ 'middleware' => [
+ LoggedInUserMiddleware::class,
+ EnsureAuthorizationMiddleware::class,
+ CreateProductHandler::class
+ ],
+ ],
+ [
+ 'name' => 'product.delete-product',
+ 'path' => '/api/product/delete-product',
+ 'allowed_methods' => ['POST'],
+ 'middleware' => [
+ LoggedInUserMiddleware::class,
+ EnsureAuthorizationMiddleware::class,
+ DeleteProductHandler::class
+ ],
+ ],
+ [
+ 'name' => 'product.update-product',
+ 'path' => '/api/product/update-product',
+ 'allowed_methods' => ['POST'],
+ 'middleware' => [
+ LoggedInUserMiddleware::class,
+ EnsureAuthorizationMiddleware::class,
+ UpdateProductHandler::class
+ ],
+ ],
+];
diff --git a/src/ApiDomain/External/Product/config/service_manager.php b/src/ApiDomain/External/Product/config/service_manager.php
new file mode 100644
index 0000000..8c380b1
--- /dev/null
+++ b/src/ApiDomain/External/Product/config/service_manager.php
@@ -0,0 +1,25 @@
+ [
+ // Formatter
+ ProductFormatter::class => AutoWiringFactory::class,
+ ProductListFormatter::class => AutoWiringFactory::class,
+
+ // Handler
+ ProductListHandler::class => AutoWiringFactory::class,
+ CreateProductHandler::class => AutoWiringFactory::class,
+ DeleteProductHandler::class => AutoWiringFactory::class,
+ UpdateProductHandler::class => AutoWiringFactory::class,
+ ],
+]
+
+?>
\ No newline at end of file
diff --git a/src/ApiDomain/External/Product/src/ConfigProvider.php b/src/ApiDomain/External/Product/src/ConfigProvider.php
new file mode 100644
index 0000000..4f5360e
--- /dev/null
+++ b/src/ApiDomain/External/Product/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/Product/src/Formatter/ProductFormatter.php b/src/ApiDomain/External/Product/src/Formatter/ProductFormatter.php
new file mode 100644
index 0000000..90fce79
--- /dev/null
+++ b/src/ApiDomain/External/Product/src/Formatter/ProductFormatter.php
@@ -0,0 +1,20 @@
+ $product->getId()->toString(),
+ 'identifier' => $product->getIdentifier(),
+ 'name' => $product->getName(),
+ 'url' => $product->getUrl(),
+ 'created' => $product->getCreatedAt()->format(DateTime::ATOM),
+ 'updated' => $product->getUpdatedAt()->format(DateTime::ATOM)
+ ];
+ }
+}
diff --git a/src/ApiDomain/External/Product/src/Formatter/ProductListFormatter.php b/src/ApiDomain/External/Product/src/Formatter/ProductListFormatter.php
new file mode 100644
index 0000000..9348af2
--- /dev/null
+++ b/src/ApiDomain/External/Product/src/Formatter/ProductListFormatter.php
@@ -0,0 +1,28 @@
+productFormatter->format(
+ $product
+ );
+ }
+
+ return $list;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/src/ApiDomain/External/Product/src/Handler/CreateProductHandler.php b/src/ApiDomain/External/Product/src/Handler/CreateProductHandler.php
new file mode 100644
index 0000000..9268e62
--- /dev/null
+++ b/src/ApiDomain/External/Product/src/Handler/CreateProductHandler.php
@@ -0,0 +1,41 @@
+getBody()->getContents(),
+ true
+ );
+
+ $query = $this->builder->build(
+ $data['identifier'],
+ $data['name'] ?? null
+ );
+ $result = $this->handler->execute($query);
+
+ return new JsonResponse(
+ $this->productFormatter->format($result)
+ );
+ }
+}
diff --git a/src/ApiDomain/External/Product/src/Handler/DeleteProductHandler.php b/src/ApiDomain/External/Product/src/Handler/DeleteProductHandler.php
new file mode 100644
index 0000000..cadd773
--- /dev/null
+++ b/src/ApiDomain/External/Product/src/Handler/DeleteProductHandler.php
@@ -0,0 +1,38 @@
+getBody()->getContents(),
+ true
+ );
+
+ $query = $this->builder->build(
+ $data['id']
+ );
+ $result = $this->handler->execute($query);
+
+ return new JsonResponse("OK");
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/src/ApiDomain/External/Product/src/Handler/ProductListHandler.php b/src/ApiDomain/External/Product/src/Handler/ProductListHandler.php
new file mode 100644
index 0000000..5147ffd
--- /dev/null
+++ b/src/ApiDomain/External/Product/src/Handler/ProductListHandler.php
@@ -0,0 +1,35 @@
+builder->build( '*' );
+ $result = $this->handler->execute($query);
+
+ return new JsonResponse(
+ $this->productListFormatter->format($result)
+ );
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/src/ApiDomain/External/Product/src/Handler/UpdateProductHandler.php b/src/ApiDomain/External/Product/src/Handler/UpdateProductHandler.php
new file mode 100644
index 0000000..e5a11f8
--- /dev/null
+++ b/src/ApiDomain/External/Product/src/Handler/UpdateProductHandler.php
@@ -0,0 +1,43 @@
+getBody()->getContents(),
+ true
+ );
+
+ $query = $this->builder->build(
+ $data['id'],
+ $data['identifier'],
+ $data['name'] ?? null,
+ $data['url'] ?? null
+ );
+ $result = $this->handler->execute($query);
+
+ return new JsonResponse(
+ $this->productFormatter->format($result)
+ );
+ }
+}
diff --git a/src/ApiDomain/External/User/config/routes.php b/src/ApiDomain/External/User/config/routes.php
new file mode 100644
index 0000000..ce171c0
--- /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..f3db770
--- /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..7f78168
--- /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..4710773
--- /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..3090822
--- /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..fa64996
--- /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..82cfd95
--- /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..36eb896
--- /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..fc036f9
--- /dev/null
+++ b/src/DataDomain/Business/config/doctrine.php
@@ -0,0 +1,55 @@
+ [
+ 'orm_homepage' => [
+ 'second_level_cache' => [
+ 'enabled' => false,
+ ]
+ ],
+ ],
+
+ 'driver' => [
+ 'orm_homepage_annotation_driver' => [
+ 'class' => AnnotationDriver::class,
+ 'cache' => 'array',
+ 'paths' => [
+ realpath(APP_ROOT . '/src/DataDomain/Business/Entity/')
+ ],
+ ],
+
+ 'orm_homepage' => [
+ 'class' => MappingDriverChain::class,
+ 'drivers' => [
+ 'Homepage\Data\Business\Entity' => 'orm_homepage_annotation_driver',
+ ],
+ ],
+ ],
+
+ 'connection' => [
+ 'orm_homepage' => [
+ '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_homepage' => [
+ 'directory' => 'data/migrations/homepage',
+ 'name' => 'Doctrine Database Migrations for Homepage',
+ 'namespace' => 'Homepage\Migrations\Homepage',
+ '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..fe73c8e
--- /dev/null
+++ b/src/DataDomain/Business/config/service_manager.php
@@ -0,0 +1,21 @@
+ [
+ 'doctrine.entity_manager.orm_homepage' => [EntityManagerFactory::class, 'orm_homepage'],
+ 'doctrine.configuration.orm_homepage' => [ConfigurationFactory::class, 'orm_homepage'],
+ 'doctrine.connection.orm_homepage' => [ConnectionFactory::class, 'orm_homepage'],
+ HomepageEntityManager::class => HomepageEntityManagerFactory::class,
+
+ UserRepository::class => [AutowireRepositoryFactory::class, HomepageEntityManager::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..eed4122
--- /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..b6c762a
--- /dev/null
+++ b/src/DataDomain/Business/src/Entity/Permission.php
@@ -0,0 +1,100 @@
+id = UuidGenerator::generate();
+ $this->roles = new ArrayCollection();
+ }
+
+
+ public function getId(): UuidInterface {
+ return $this->id;
+ }
+
+ public function getProductId(): ?UuidInterface {
+ return $this->productId;
+ }
+
+ public function setProductId(?UuidInterface $productId): void {
+ $this->productId = $productId;
+ }
+
+ public function getProduct(): ?Product {
+ return $this->product;
+ }
+
+ public function setProduct(?Product $product): void {
+ $this->product = $product;
+ }
+
+ 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/Product.php b/src/DataDomain/Business/src/Entity/Product.php
new file mode 100644
index 0000000..f797c51
--- /dev/null
+++ b/src/DataDomain/Business/src/Entity/Product.php
@@ -0,0 +1,99 @@
+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 getIdentifier(): string {
+ return $this->identifier;
+ }
+
+ public function setIdentifier(string $identifier): void {
+ $this->identifier = $identifier;
+ }
+
+ public function getName(): ?string {
+ return $this->name;
+ }
+
+ public function setName(?string $name): void {
+ $this->name = $name;
+ }
+
+ public function getUrl(): ?string {
+ return $this->url;
+ }
+
+ public function setUrl(?string $url): void {
+ $this->url = $url;
+ }
+
+ 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;
+ }
+}
diff --git a/src/DataDomain/Business/src/Entity/Registration.php b/src/DataDomain/Business/src/Entity/Registration.php
new file mode 100644
index 0000000..b8c525b
--- /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..4bb96e9
--- /dev/null
+++ b/src/DataDomain/Business/src/Entity/Role.php
@@ -0,0 +1,101 @@
+id = UuidGenerator::generate();
+ $this->permissions = new ArrayCollection();
+ }
+
+
+ public function getId(): UuidInterface {
+ return $this->id;
+ }
+
+ public function getProductId(): ?UuidInterface {
+ return $this->productId;
+ }
+
+ public function setProductId(?UuidInterface $productId): void {
+ $this->productId = $productId;
+ }
+
+ public function getProduct(): ?Product {
+ return $this->product;
+ }
+
+ public function setProduct(?Product $product): void {
+ $this->product = $product;
+ }
+
+ 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..dce192d
--- /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..a1aa75e
--- /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/HomepageEntityManagerFactory.php b/src/DataDomain/Business/src/Factory/HomepageEntityManagerFactory.php
new file mode 100644
index 0000000..bb288e1
--- /dev/null
+++ b/src/DataDomain/Business/src/Factory/HomepageEntityManagerFactory.php
@@ -0,0 +1,19 @@
+get('doctrine.entity_manager.orm_homepage')
+ );
+ }
+}
diff --git a/src/DataDomain/Business/src/Manager/HomepageEntityManager.php b/src/DataDomain/Business/src/Manager/HomepageEntityManager.php
new file mode 100644
index 0000000..1488509
--- /dev/null
+++ b/src/DataDomain/Business/src/Manager/HomepageEntityManager.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..7c98dbf
--- /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..8a0a9c4
--- /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..5efb67a
--- /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..7893fd7
--- /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..d25011a
--- /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..66d819f
--- /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' => [
+ 'Homepage\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' => 'Homepage\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..ed24846
--- /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..c75f14c
--- /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..e955ca1
--- /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..1d12fe1
--- /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..0654b6d
--- /dev/null
+++ b/src/DataDomain/Log/src/Manager/LogEntityManager.php
@@ -0,0 +1,11 @@
+ [
+
+ /// Builder
+ ProductBuilder::class => AutoWiringFactory::class,
+
+ /// Rule
+ ProductIdentifierAlreadyExistsRule::class => InjectionFactory::class,
+
+ /// CQRS
+ // Product List
+ ProductListQueryHandler::class => InjectionFactory::class,
+ ProductListQueryBuilder::class => AutoWiringFactory::class,
+
+ // Create Product
+ CreateProductCommandHandler::class => AutoWiringFactory::class,
+ CreateProductCommandBuilder::class => AutoWiringFactory::class,
+
+ // Delete Product
+ DeleteProductCommandHandler::class => InjectionFactory::class,
+ DeleteProductCommandBuilder::class => AutoWiringFactory::class,
+
+ // Update Product
+ UpdateProductCommandHandler::class => InjectionFactory::class,
+ UpdateProductCommandBuilder::class => AutoWiringFactory::class,
+
+ ],
+];
\ No newline at end of file
diff --git a/src/HandlingDomain/Product/src/Builder/ProductBuilder.php b/src/HandlingDomain/Product/src/Builder/ProductBuilder.php
new file mode 100644
index 0000000..df17385
--- /dev/null
+++ b/src/HandlingDomain/Product/src/Builder/ProductBuilder.php
@@ -0,0 +1,23 @@
+setIdentifier($identifier);
+ $product->setName($name);
+ $product->setUrl($url);
+
+ return $product;
+ }
+}
\ No newline at end of file
diff --git a/src/HandlingDomain/Product/src/ConfigProvider.php b/src/HandlingDomain/Product/src/ConfigProvider.php
new file mode 100644
index 0000000..b9db183
--- /dev/null
+++ b/src/HandlingDomain/Product/src/ConfigProvider.php
@@ -0,0 +1,15 @@
+ require __DIR__ . '/./../config/service_manager.php',
+ ];
+ }
+}
diff --git a/src/HandlingDomain/Product/src/Exception/ProductNotFoundByIdException.php b/src/HandlingDomain/Product/src/Exception/ProductNotFoundByIdException.php
new file mode 100644
index 0000000..9c2fbdf
--- /dev/null
+++ b/src/HandlingDomain/Product/src/Exception/ProductNotFoundByIdException.php
@@ -0,0 +1,27 @@
+toString()
+ ),
+ ErrorDomain::Product,
+ ErrorCode::NotFound
+ );
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/src/HandlingDomain/Product/src/Exception/ProductWithIdentifierAlreadyExists.php b/src/HandlingDomain/Product/src/Exception/ProductWithIdentifierAlreadyExists.php
new file mode 100644
index 0000000..e7a2a1a
--- /dev/null
+++ b/src/HandlingDomain/Product/src/Exception/ProductWithIdentifierAlreadyExists.php
@@ -0,0 +1,24 @@
+identifier;
+ }
+
+ public function getName(): ?string {
+ return $this->name;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/src/HandlingDomain/Product/src/Handler/Command/CreateProduct/CreateProductCommandBuilder.php b/src/HandlingDomain/Product/src/Handler/Command/CreateProduct/CreateProductCommandBuilder.php
new file mode 100644
index 0000000..048a8c5
--- /dev/null
+++ b/src/HandlingDomain/Product/src/Handler/Command/CreateProduct/CreateProductCommandBuilder.php
@@ -0,0 +1,17 @@
+productIdentifierAlreadyExistsRule->appliesTo($command->getIdentifier());
+
+ $product = $this->builder->build(
+ identifier: $command->getIdentifier(),
+ name: $command->getName(),
+ url: null,
+ );
+
+ $this->entityManager->persist($product);
+ $this->entityManager->flush();
+
+ return $product;
+ }
+}
diff --git a/src/HandlingDomain/Product/src/Handler/Command/DeleteProduct/DeleteProductCommand.php b/src/HandlingDomain/Product/src/Handler/Command/DeleteProduct/DeleteProductCommand.php
new file mode 100644
index 0000000..3453e5b
--- /dev/null
+++ b/src/HandlingDomain/Product/src/Handler/Command/DeleteProduct/DeleteProductCommand.php
@@ -0,0 +1,19 @@
+id;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/src/HandlingDomain/Product/src/Handler/Command/DeleteProduct/DeleteProductCommandBuilder.php b/src/HandlingDomain/Product/src/Handler/Command/DeleteProduct/DeleteProductCommandBuilder.php
new file mode 100644
index 0000000..3f43f7a
--- /dev/null
+++ b/src/HandlingDomain/Product/src/Handler/Command/DeleteProduct/DeleteProductCommandBuilder.php
@@ -0,0 +1,19 @@
+
\ No newline at end of file
diff --git a/src/HandlingDomain/Product/src/Handler/Command/DeleteProduct/DeleteProductCommandHandler.php b/src/HandlingDomain/Product/src/Handler/Command/DeleteProduct/DeleteProductCommandHandler.php
new file mode 100644
index 0000000..6a92583
--- /dev/null
+++ b/src/HandlingDomain/Product/src/Handler/Command/DeleteProduct/DeleteProductCommandHandler.php
@@ -0,0 +1,39 @@
+repository->find(
+ $command->getId()
+ ) ?? null;
+
+ if ($product === null) {
+ throw new ProductNotFoundByIdException($command->getId());
+ }
+
+ $this->entityManager->remove($product);
+ $this->entityManager->flush();
+ }
+}
diff --git a/src/HandlingDomain/Product/src/Handler/Command/UpdateProduct/UpdateProductCommand.php b/src/HandlingDomain/Product/src/Handler/Command/UpdateProduct/UpdateProductCommand.php
new file mode 100644
index 0000000..63a265e
--- /dev/null
+++ b/src/HandlingDomain/Product/src/Handler/Command/UpdateProduct/UpdateProductCommand.php
@@ -0,0 +1,32 @@
+id;
+ }
+
+ public function getIdentifier(): string {
+ return $this->identifier;
+ }
+
+ public function getName(): ?string {
+ return $this->name;
+ }
+
+ public function getUrl(): ?string {
+ return $this->url;
+ }
+}
diff --git a/src/HandlingDomain/Product/src/Handler/Command/UpdateProduct/UpdateProductCommandBuilder.php b/src/HandlingDomain/Product/src/Handler/Command/UpdateProduct/UpdateProductCommandBuilder.php
new file mode 100644
index 0000000..d7b94b0
--- /dev/null
+++ b/src/HandlingDomain/Product/src/Handler/Command/UpdateProduct/UpdateProductCommandBuilder.php
@@ -0,0 +1,23 @@
+getId();
+
+ /** @var Product $product */
+ $product = $this->repository->find($id) ?? null;
+ if ($product === null) {
+ throw new ProductNotFoundByIdException($id);
+ }
+
+ if ($product->getId()->toString() !== $command->getId()->toString()) {
+ $this->productIdentifierAlreadyExistsRule->appliesTo($command->getIdentifier());
+ }
+
+ $product->setIdentifier($command->getIdentifier());
+ $product->setName($command->getName());
+ $product->setUrl($command->getUrl());
+ $product->setUpdatedAt(new DateTime());
+
+ $this->entityManager->persist($product);
+ $this->entityManager->flush();
+
+ return $product;
+ }
+}
diff --git a/src/HandlingDomain/Product/src/Handler/Query/ProductList/ProductListQuery.php b/src/HandlingDomain/Product/src/Handler/Query/ProductList/ProductListQuery.php
new file mode 100644
index 0000000..5379bbf
--- /dev/null
+++ b/src/HandlingDomain/Product/src/Handler/Query/ProductList/ProductListQuery.php
@@ -0,0 +1,17 @@
+query;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/src/HandlingDomain/Product/src/Handler/Query/ProductList/ProductListQueryBuilder.php b/src/HandlingDomain/Product/src/Handler/Query/ProductList/ProductListQueryBuilder.php
new file mode 100644
index 0000000..f932725
--- /dev/null
+++ b/src/HandlingDomain/Product/src/Handler/Query/ProductList/ProductListQueryBuilder.php
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/src/HandlingDomain/Product/src/Handler/Query/ProductList/ProductListQueryHandler.php b/src/HandlingDomain/Product/src/Handler/Query/ProductList/ProductListQueryHandler.php
new file mode 100644
index 0000000..a01e8a7
--- /dev/null
+++ b/src/HandlingDomain/Product/src/Handler/Query/ProductList/ProductListQueryHandler.php
@@ -0,0 +1,29 @@
+repository->findAll();
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/src/HandlingDomain/Product/src/Rule/ProductIdentifierAlreadyExistsRule.php b/src/HandlingDomain/Product/src/Rule/ProductIdentifierAlreadyExistsRule.php
new file mode 100644
index 0000000..7336e61
--- /dev/null
+++ b/src/HandlingDomain/Product/src/Rule/ProductIdentifierAlreadyExistsRule.php
@@ -0,0 +1,33 @@
+productRepository->findOneBy([
+ 'identifier' => $identifier
+ ]) ?? null;
+
+ if ($product !== null) {
+ throw new ProductWithIdentifierAlreadyExists($identifier);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/HandlingDomain/Registration/config/service_manager.php b/src/HandlingDomain/Registration/config/service_manager.php
new file mode 100644
index 0000000..3fdb317
--- /dev/null
+++ b/src/HandlingDomain/Registration/config/service_manager.php
@@ -0,0 +1,53 @@
+ [
+ /// 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..6142fc0
--- /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..e9d9a3a
--- /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..192d073
--- /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..ad689ab
--- /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..bfb4e6d
--- /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..1b087ec
--- /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..d102318
--- /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..b2e87be
--- /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..0bb25f6
--- /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..bfac7fc
--- /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..7e0e6ac
--- /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..2354f30
--- /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..130ab0e
--- /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..1f635c7
--- /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..830a10f
--- /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..6c43bb1
--- /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..c2e3f77
--- /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..ff7198d
--- /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..f83ace5
--- /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..e4baf65
--- /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..a8c683a
--- /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..bd8a832
--- /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..4453935
--- /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..3019220
--- /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..74a6b6a
--- /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..e2130df
--- /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..38abe6d
--- /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..abdd77b
--- /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..eb4464c
--- /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..770c7c8
--- /dev/null
+++ b/src/HandlingDomain/User/src/Rule/UserPasswordMatchRule.php
@@ -0,0 +1,15 @@
+userRepository->findByIdentifier($identifier);
+
+ if ($user !== null) {
+ throw new UserWithIdentifierAlreadyExists($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..302fcb2
--- /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..71ab0a4
--- /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..ee79fd9
--- /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..9c440f5
--- /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..ec65068
--- /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..9c7dfcc
--- /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..7ebce04
--- /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..c21acad
--- /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..76683ae
--- /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..bb4a0b6
--- /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..a31e353
--- /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..e1a5093
--- /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..5f48a4b
--- /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..d59c1ff
--- /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..cf4a84a
--- /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..03ec996
--- /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..1ff55dd
--- /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..661915d
--- /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..348f8c8
--- /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..ac61df9
--- /dev/null
+++ b/src/Infrastructure/Exception/config/service_manager.php
@@ -0,0 +1,12 @@
+ [
+ HomepageExceptionHandlerMiddleware::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..61fddc9
--- /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..e052ff0
--- /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/HomepageExceptionHandlerMiddleware.php b/src/Infrastructure/Exception/src/Middleware/HomepageExceptionHandlerMiddleware.php
new file mode 100644
index 0000000..717e64f
--- /dev/null
+++ b/src/Infrastructure/Exception/src/Middleware/HomepageExceptionHandlerMiddleware.php
@@ -0,0 +1,37 @@
+handle($request);
+ } catch (HomepageException $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..56cff25
--- /dev/null
+++ b/src/Infrastructure/Logging/config/service_manager.php
@@ -0,0 +1,23 @@
+ [
+ HomepageLogger::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..976efc4
--- /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..e037dfe
--- /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..7d87af6
--- /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..0605f6b
--- /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..1baaeea
--- /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..84884ec
--- /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..66f8314
--- /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..2f2df4c
--- /dev/null
+++ b/src/Infrastructure/Logging/src/Logger/Logger.php
@@ -0,0 +1,91 @@
+ $exception->getTraceAsString()
+ ]*/;
+
+ if ($exception instanceof HomepageException) {
+ $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..a677c9d
--- /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..29f2bf6
--- /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..ad5fa20
--- /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..901c4f6
--- /dev/null
+++ b/src/Infrastructure/Request/config/service_manager.php
@@ -0,0 +1,22 @@
+ [
+ /// Service
+ RequestService::class => RequestServiceFactory::class,
+
+ /// Middleware
+ AnalyzeHeaderMiddleware::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..6e79313
--- /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..d3dc29a
--- /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..431dd3e
--- /dev/null
+++ b/src/Infrastructure/Request/src/Exception/ApiServiceUnknownException.php
@@ -0,0 +1,20 @@
+get(ConfigService::class);
+ $apiKey = $configService->resolve("api.keys.homepage");
+
+ return new RequestService(
+ $apiKey,
+ $configService
+ );
+ }
+}
\ 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..64d7220
--- /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..c1083cc
--- /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..a1365d5
--- /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..8c4e637
--- /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..379efc1
--- /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..235fb12
--- /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..88c2029
--- /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..0b0a6a3
--- /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..3e85b2b
--- /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