Initial commit

This commit is contained in:
Flo 2024-02-14 20:08:01 +01:00 committed by GitHub
commit c0c461a844
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
228 changed files with 7256 additions and 0 deletions

20
.env.example Normal file
View File

@ -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

7
.gitattributes vendored Normal file
View File

@ -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

View File

@ -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 }}

39
.github/workflows/php.yml vendored Normal file
View File

@ -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

15
.gitignore vendored Normal file
View File

@ -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

3
.htaccess Normal file
View File

@ -0,0 +1,3 @@
RewriteEngine On
RewriteBase /
RewriteRule ^pages/([^/]*)/(.*)$ pages.php?$1=$2

1
COPYRIGHT.md Normal file
View File

@ -0,0 +1 @@
Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC. (https://getlaminas.org/)

26
LICENSE.md Normal file
View File

@ -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.

0
README.md Normal file
View File

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
chdir(__DIR__ . '/../');
require 'vendor/autoload.php';
$config = include 'config/config.php';
if (! isset($config['config_cache_path'])) {
echo "No configuration cache path found" . PHP_EOL;
exit(0);
}
if (! file_exists($config['config_cache_path'])) {
printf(
"Configured config cache file '%s' not found%s",
$config['config_cache_path'],
PHP_EOL
);
exit(0);
}
if (false === unlink($config['config_cache_path'])) {
printf(
"Error removing config cache file '%s'%s",
$config['config_cache_path'],
PHP_EOL
);
exit(1);
}
printf(
"Removed configured config cache file '%s'%s",
$config['config_cache_path'],
PHP_EOL
);
exit(0);

30
bin/console.php Normal file
View File

@ -0,0 +1,30 @@
<?php
use Homepage\Infrastructure\Logging\Logger\Logger;
use Symfony\Component\Console\Application;
require_once __DIR__ . '/../config/autoload/defines.php';
require APP_ROOT . '/vendor/autoload.php';
call_user_func(function() {
$container = require APP_ROOT . '/config/container.php';
$config = $container->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]
);
}
});

View File

@ -0,0 +1,91 @@
<?php
require_once __DIR__ . '/../config/autoload/defines.php';
require APP_ROOT . '/vendor/autoload.php';
use Homepage\Data\Business\Manager\HomepageEntityManager;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\DBAL\DriverManager;
use Doctrine\Migrations\Configuration\Configuration;
use Doctrine\Migrations\Configuration\Connection\ExistingConnection;
use Doctrine\Migrations\Configuration\Migration\ExistingConfiguration;
use Doctrine\Migrations\DependencyFactory;
use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration;
use Doctrine\Migrations\Tools\Console\Command\DumpSchemaCommand;
use Doctrine\Migrations\Tools\Console\Command\ExecuteCommand;
use Doctrine\Migrations\Tools\Console\Command\GenerateCommand;
use Doctrine\Migrations\Tools\Console\Command\LatestCommand;
use Doctrine\Migrations\Tools\Console\Command\ListCommand;
use Doctrine\Migrations\Tools\Console\Command\MigrateCommand;
use Doctrine\Migrations\Tools\Console\Command\RollupCommand;
use Doctrine\Migrations\Tools\Console\Command\StatusCommand;
use Doctrine\Migrations\Tools\Console\Command\SyncMetadataCommand;
use Doctrine\Migrations\Tools\Console\Command\VersionCommand;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Tools\Setup;
use Symfony\Component\Console\Application;
$isDevMode = true;
$container = require APP_ROOT . '/config/container.php';
$config = $container->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();
}

View File

@ -0,0 +1,91 @@
<?php
require_once __DIR__ . '/../config/autoload/defines.php';
require APP_ROOT . '/vendor/autoload.php';
use Homepage\Data\Business\Manager\HomepageEntityManager;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\DBAL\DriverManager;
use Doctrine\Migrations\Configuration\Configuration;
use Doctrine\Migrations\Configuration\Connection\ExistingConnection;
use Doctrine\Migrations\Configuration\Migration\ExistingConfiguration;
use Doctrine\Migrations\DependencyFactory;
use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration;
use Doctrine\Migrations\Tools\Console\Command\DumpSchemaCommand;
use Doctrine\Migrations\Tools\Console\Command\ExecuteCommand;
use Doctrine\Migrations\Tools\Console\Command\GenerateCommand;
use Doctrine\Migrations\Tools\Console\Command\LatestCommand;
use Doctrine\Migrations\Tools\Console\Command\ListCommand;
use Doctrine\Migrations\Tools\Console\Command\MigrateCommand;
use Doctrine\Migrations\Tools\Console\Command\RollupCommand;
use Doctrine\Migrations\Tools\Console\Command\StatusCommand;
use Doctrine\Migrations\Tools\Console\Command\SyncMetadataCommand;
use Doctrine\Migrations\Tools\Console\Command\VersionCommand;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Tools\Setup;
use Symfony\Component\Console\Application;
$isDevMode = true;
$container = require APP_ROOT . '/config/container.php';
$config = $container->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();
}

3
bin/script/build Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
$SCRIPT_DIR/exec build

3
bin/script/down Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
$SCRIPT_DIR/exec down

23
bin/script/exec Executable file
View File

@ -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)

32
bin/script/init Executable file
View File

@ -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

3
bin/script/stop Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
$SCRIPT_DIR/exec stop

3
bin/script/up Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
$SCRIPT_DIR/exec up -d

122
composer.development.json Normal file
View File

@ -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"
}
}
}

76
composer.json Normal file
View File

@ -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"
}
}

2
config/autoload/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
local.php
*.local.php

View File

@ -0,0 +1,29 @@
<?php
return [
'api' => [
'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'
],
],
],
],
],
];

View File

@ -0,0 +1,22 @@
<?php
return [
'homepage-rbac' => [
'roles' => [
'admin',
'user',
],
'permissions' => [
'user' => [
'product.product-list',
],
'admin' => [
'product.create-product',
'product.delete-product',
'product.product-list',
'product.update-product',
'user.create-user',
],
]
]
];

View File

@ -0,0 +1,6 @@
<?php
if (! defined('APP_ROOT')) {
define('APP_ROOT', realpath(__DIR__ . '/../../'));
}

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
return [
// Provides application-wide services.
// We recommend using fully-qualified class names whenever possible as
// service names.
'dependencies' => [
// Use 'aliases' to alias a service name to another service. The
// key is the alias name, the value is the service to which it points.
'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,
],
],
];

View File

@ -0,0 +1,14 @@
<?php
use Doctrine\DBAL\Driver\PDO\MySQL\Driver;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
use Ramsey\Uuid\Doctrine\UuidBinaryOrderedTimeType;
return [
'doctrine' => [
'types' => [
UuidBinaryOrderedTimeType::NAME => UuidBinaryOrderedTimeType::class,
],
],
];

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
use Monolog\Level;
return [
'logger' => [
'name' => 'homepage.backend',
'path' => APP_ROOT . '/var/log/homepage.backend.log',
'level' => Level::Debug,
'pretty' => true,
]
];

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
use Laminas\ConfigAggregator\ConfigAggregator;
return [
// Toggle the configuration cache. Set this to boolean false, or remove the
// directive, to disable configuration caching. Toggling development mode
// will also disable it by default; clear the configuration cache using
// `composer clear-config-cache`.
ConfigAggregator::ENABLE_CACHE => 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',
],
],
];

91
config/config.php Normal file
View File

@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
use Laminas\ConfigAggregator\ArrayProvider;
use Laminas\ConfigAggregator\ConfigAggregator;
use Laminas\ConfigAggregator\PhpFileProvider;
// To enable or disable caching, set the `ConfigAggregator::ENABLE_CACHE` boolean in
// `config/autoload/local.php`.
$cacheConfig = [
'config_cache_path' => '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();

21
config/container.php Normal file
View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
use Laminas\ServiceManager\ServiceManager;
use Symfony\Component\Dotenv\Dotenv;
$dotenv = new Dotenv();
if (file_exists(__DIR__ . '/../.env')) {
$dotenv->load(__DIR__ . '/../.env');
}
// Load configuration
$config = require __DIR__ . '/config.php';
$dependencies = $config['dependencies'];
$dependencies['services']['config'] = $config;
// Build container
return new ServiceManager($dependencies);

View File

@ -0,0 +1,31 @@
<?php
/**
* File required to allow enablement of development mode.
*
* For use with the laminas-development-mode tool.
*
* Usage:
* $ composer development-disable
* $ composer development-enable
* $ composer development-status
*
* DO NOT MODIFY THIS FILE.
*
* Provide your own development-mode settings by editing the file
* `config/autoload/development.local.php.dist`.
*
* Because this file is aggregated last, it simply ensures:
*
* - The `debug` flag is _enabled_.
* - Configuration caching is _disabled_.
*/
declare(strict_types=1);
use Laminas\ConfigAggregator\ConfigAggregator;
return [
'debug' => true,
ConfigAggregator::ENABLE_CACHE => false,
];

92
config/pipeline.php Normal file
View File

@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
use Homepage\Infrastructure\Exception\Middleware\HomepageExceptionHandlerMiddleware;
use Homepage\Infrastructure\Request\Middleware\AnalyzeHeaderMiddleware;
use Homepage\Infrastructure\Session\Middleware\SessionMiddleware;
use Laminas\Stratigility\Middleware\ErrorHandler;
use Mezzio\Application;
use Mezzio\Handler\NotFoundHandler;
use Mezzio\Helper\ServerUrlMiddleware;
use Mezzio\Helper\UrlHelperMiddleware;
use Mezzio\MiddlewareFactory;
use Mezzio\Router\Middleware\DispatchMiddleware;
use Mezzio\Router\Middleware\ImplicitHeadMiddleware;
use Mezzio\Router\Middleware\ImplicitOptionsMiddleware;
use Mezzio\Router\Middleware\MethodNotAllowedMiddleware;
use Mezzio\Router\Middleware\RouteMiddleware;
use Psr\Container\ContainerInterface;
/**
* Setup middleware pipeline:
*/
return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container): void {
// The error handler should be the first (most outer) middleware to catch
// all Exceptions.
$app->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);
};

52
config/routes.php Normal file
View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
use Mezzio\Application;
use Mezzio\MiddlewareFactory;
use Psr\Container\ContainerInterface;
use Homepage\Infrastructure\Session\Middleware\LoggedInUserMiddleware;
/**
* laminas-router route configuration
*
* @see https://docs.laminas.dev/laminas-router/
*
* Setup routes with a single request method:
*
* $app->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']
);
}
};

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Homepage\Migrations\Homepage;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20230922085011 extends AbstractMigration
{
public function getDescription(): string
{
return "Create Table 'role'";
}
public function up(Schema $schema): void
{
$sql = "CREATE TABLE role (
id binary(16) NOT NULL,
product_id binary(16) DEFAULT NULL,
identifier varchar(255) UNIQUE NOT NULL,
PRIMARY KEY (id)
);";
$this->addSql($sql);
}
public function down(Schema $schema): void
{
$this->addSql("DROP TABLE role;");
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Homepage\Migrations\Homepage;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20230922092351 extends AbstractMigration
{
public function getDescription(): string
{
return "Create Table 'permission'";
}
public function up(Schema $schema): void
{
$sql = "CREATE TABLE permission (
id binary(16) NOT NULL,
product_id binary(16) DEFAULT NULL,
identifier varchar(255) UNIQUE NOT NULL,
PRIMARY KEY (id)
);";
$this->addSql($sql);
}
public function down(Schema $schema): void
{
$this->addSql("DROP TABLE permission;");
}
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Homepage\Migrations\Homepage;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20230922092754 extends AbstractMigration
{
public function getDescription(): string
{
return "Create Table 'role_permission'";
}
public function up(Schema $schema): void
{
$sql = "CREATE TABLE role_permission (
id binary(16) NOT NULL DEFAULT (uuid_to_bin(uuid())),
role_id binary(16) NOT NULL,
permission_id binary(16) NOT NULL,
PRIMARY KEY (id)
);";
$this->addSql($sql);
}
public function down(Schema $schema): void
{
$this->addSql("DROP TABLE role_permission;");
}
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Homepage\Migrations\Homepage;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20230922101352 extends AbstractMigration
{
public function getDescription(): string
{
return "Create Table 'product'";
}
public function up(Schema $schema): void
{
$sql = "CREATE TABLE product (
id binary(16) NOT NULL,
identifier varchar(255) UNIQUE NOT NULL,
name varchar(255) DEFAULT NULL,
updated_at datetime NOT NULL,
created_at datetime NOT NULL,
PRIMARY KEY (id)
);";
$this->addSql($sql);
}
public function down(Schema $schema): void
{
$this->addSql("DROP TABLE product;");
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Homepage\Migrations\Homepage;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20230922101354 extends AbstractMigration
{
public function getDescription(): string
{
return "Create Table 'user'";
}
public function up(Schema $schema): void
{
$sql = "CREATE TABLE user (
id binary(16) NOT NULL,
role_id binary(16) NOT NULL,
username varchar(255) UNIQUE NOT NULL,
mail varchar(255) UNIQUE NOT NULL,
password varchar(255) UNIQUE NOT NULL,
last_login_at datetime DEFAULT NULL,
updated_at datetime NOT NULL,
created_at datetime NOT NULL,
PRIMARY KEY (id)
);";
$this->addSql($sql);
}
public function down(Schema $schema): void
{
$this->addSql("DROP TABLE user;");
}
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Homepage\Migrations\Homepage;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20230922101355 extends AbstractMigration
{
public function getDescription(): string
{
return "Create Table 'user_session'";
}
public function up(Schema $schema): void
{
$sql = "CREATE TABLE user_session (
id binary(16) NOT NULL,
user_id binary(16) DEFAULT NULL,
csrf binary(16) DEFAULT NULL,
updated_at datetime NOT NULL,
created_at datetime NOT NULL,
PRIMARY KEY (id)
);";
$this->addSql($sql);
}
public function down(Schema $schema): void
{
$this->addSql("DROP TABLE role_permission;");
}
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Homepage\Migrations\Homepage;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20230924113403 extends AbstractMigration
{
public function getDescription(): string
{
return "Create Table 'registration'";
}
public function up(Schema $schema): void
{
$sql = "CREATE TABLE registration (
id binary(16) NOT NULL,
mail varchar(255) NOT NULL,
username varchar(255) NOT NULL,
password varchar(255) NOT NULL,
created_at datetime NOT NULL,
PRIMARY KEY (id)
);";
$this->addSql($sql);
}
public function down(Schema $schema): void
{
$this->addSql("DROP TABLE registration;");
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Homepage\Migrations\Homepage;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20231021103120 extends AbstractMigration
{
public function getDescription(): string
{
return 'drop password from registration';
}
public function up(Schema $schema): void
{
$sql = "ALTER TABLE registration DROP COLUMN password;";
$this->addSql($sql);
}
public function down(Schema $schema): void
{
$sql = "ALTER TABLE registration ADD COLUMN password varchar(255) NOT NULL after username";
$this->addSql($sql);
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Homepage\Migrations\Homepage;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20231021112654 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add url to product';
}
public function up(Schema $schema): void
{
$sql = "ALTER TABLE product ADD COLUMN url varchar(255) NULL after name";
$this->addSql($sql);
}
public function down(Schema $schema): void
{
$sql = "ALTER TABLE product DROP COLUMN url;";
$this->addSql($sql);
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Homepage\Migrations\Log;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20230922150649 extends AbstractMigration
{
public function getDescription(): string
{
return "Create Table 'log'";
}
public function up(Schema $schema): void
{
$query = 'CREATE TABLE log (
id BINARY(16) NOT NULL,
message TEXT NOT NULL,
context JSON DEFAULT NULL,
level INT NOT NULL ,
level_name VARCHAR(50) NOT NULL,
extra JSON DEFAULT NULL,
created_at datetime NOT NULL,
PRIMARY KEY (id)
)';
$this->addSql($query);
}
public function down(Schema $schema): void
{
$this->addSql('DROP TABLE log');
}
}

View File

@ -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

51
docker/docker-compose.yml Normal file
View File

@ -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

7
docker/mysql/dockerfile Normal file
View File

@ -0,0 +1,7 @@
FROM mysql:latest
ENV MYSQL_RANDOM_ROOT_PASSWORD=1
COPY docker/mysql/scripts /docker-entrypoint-initdb.d/
CMD ["mysqld"]

View File

@ -0,0 +1,4 @@
CREATE DATABASE IF NOT EXISTS `log`;
CREATE DATABASE IF NOT EXISTS `homepage`;
GRANT ALL PRIVILEGES on *.* to 'homepage'@'%';

View File

@ -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;
}
}

View File

@ -0,0 +1,3 @@
upstream backend-dev {
server backend-app:9000;
}

5
docker/nginx/dockerfile Normal file
View File

@ -0,0 +1,5 @@
FROM nginx:alpine
COPY docker/nginx/config/nginx.conf /etc/nginx/conf.d/default.conf
CMD ["nginx", "-g", "daemon off;"]

15
docker/php/dockerfile Normal file
View File

@ -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"]

26
phpunit.xml.dist Normal file
View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
beStrictAboutCoversAnnotation="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTodoAnnotatedTests="true"
bootstrap="vendor/autoload.php"
convertDeprecationsToExceptions="true"
executionOrder="depends,defects"
failOnRisky="true"
failOnWarning="true"
verbose="true"
colors="true">
<testsuites>
<testsuite name="default">
<directory suffix="Test.php">test</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
</phpunit>

19
public/.htaccess Normal file
View File

@ -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]

48
public/index.php Normal file
View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
use Mezzio\Application;
use Mezzio\MiddlewareFactory;
if (PHP_SAPI === 'cli-server' && $_SERVER['SCRIPT_FILENAME'] !== __FILE__) {
return false;
}
require './../vendor/autoload.php';
require './../config/autoload/defines.php';
function throwableToMessageArray(Throwable $e): array {
$messageArray = [];
while($e !== null) {
$messageArray[] = $e->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();
})();

View File

@ -0,0 +1,11 @@
<?php
use Homepage\API\Console\Command\InitializeDataCommand;
use Homepage\API\Console\Command\RbacUpdateCommand;
return [
'commands' => [
InitializeDataCommand::class,
RbacUpdateCommand::class,
]
];

View File

@ -0,0 +1,12 @@
<?php
use Homepage\API\Console\Command\InitializeDataCommand;
use Homepage\API\Console\Command\RbacUpdateCommand;
use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
return [
'factories' => [
InitializeDataCommand::class => AutoWiringFactory::class,
RbacUpdateCommand::class => AutoWiringFactory::class,
],
];

View File

@ -0,0 +1,80 @@
<?php
namespace Homepage\API\Console\Command;
use Homepage\Data\Business\Entity\Role;
use Homepage\Data\Business\Entity\User;
use Homepage\Data\Business\Repository\RoleRepository;
use Homepage\Data\Business\Repository\UserRepository;
use Homepage\Data\Business\Manager\HomepageEntityManager;
use Homepage\Infrastructure\Encryption\Client\EncryptionClient;
use Homepage\Infrastructure\Logging\Logger\Logger;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'init:data', description: 'Initializes the applications data')]
class InitializeDataCommand extends Command
{
private readonly RoleRepository $roleRepository;
private readonly UserRepository $userRepository;
public function __construct(
private readonly EncryptionClient $encryptionClient,
private readonly HomepageEntityManager $entityManager,
private readonly Logger $logger,
) {
parent::__construct($this->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;
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace Homepage\API\Console\Command;
use Homepage\Data\Business\Entity\Permission;
use Homepage\Data\Business\Entity\Role;
use Homepage\Data\Business\Repository\PermissionRepository;
use Homepage\Data\Business\Repository\RoleRepository;
use Homepage\Data\Business\Manager\HomepageEntityManager;
use Homepage\Infrastructure\Logging\Logger\Logger;
use Reinfi\DependencyInjection\Service\ConfigService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'rbac:update', description: 'Updates RBAC entries in DB')]
class RbacUpdateCommand extends Command
{
private array $rbacConfig;
private RoleRepository $roleRepository;
private PermissionRepository $permissionRepository;
public function __construct(
private readonly ConfigService $configService,
private readonly HomepageEntityManager $entityManager,
private readonly Logger $logger,
) {
parent::__construct($this->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;
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Homepage\API\Console;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => require __DIR__ . './../config/service_manager.php',
'console' => require __DIR__ . '/./../config/console.php',
];
}
}

View File

@ -0,0 +1,43 @@
<?php
use Homepage\API\External\Authentication\Handler\ConfirmRegistrationHandler;
use Homepage\API\External\Authentication\Handler\LoginUserHandler;
use Homepage\API\External\Authentication\Handler\LogoutUserHandler;
use Homepage\API\External\Authentication\Handler\RegisterUserHandler;
use Homepage\Infrastructure\Session\Middleware\LoggedInUserMiddleware;
return [
[
'name' => '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
],
],
];

View File

@ -0,0 +1,17 @@
<?php
use Homepage\API\External\Authentication\Handler\ConfirmRegistrationHandler;
use Homepage\API\External\Authentication\Handler\LoginUserHandler;
use Homepage\API\External\Authentication\Handler\LogoutUserHandler;
use Homepage\API\External\Authentication\Handler\RegisterUserHandler;
use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
return [
'factories' => [
// Handler
LoginUserHandler::class => AutoWiringFactory::class,
LogoutUserHandler::class => AutoWiringFactory::class,
ConfirmRegistrationHandler::class => AutoWiringFactory::class,
RegisterUserHandler::class => AutoWiringFactory::class
],
];

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\Authentication;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => require __DIR__ . '/./../config/service_manager.php',
'routes' => require __DIR__ . '/./../config/routes.php',
];
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\Authentication\Handler;
use Homepage\API\External\User\Formatter\UserFormatter;
use Homepage\Handling\Registration\Handler\Command\ConfirmRegistration\ConfirmRegistrationCommandBuilder;
use Homepage\Handling\Registration\Handler\Command\ConfirmRegistration\ConfirmRegistrationCommandHandler;
use Homepage\Infrastructure\Session\Middleware\SessionMiddleware;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Ramsey\Uuid\Uuid;
class ConfirmRegistrationHandler implements RequestHandlerInterface
{
public function __construct(
private readonly ConfirmRegistrationCommandHandler $handler,
private readonly ConfirmRegistrationCommandBuilder $builder,
private readonly UserFormatter $userFormatter
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$data = json_decode(
$request->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)
);
}
}

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\Authentication\Handler;
use Homepage\Handling\UserSession\Handler\Command\LoginUser\LoginUserCommandBuilder;
use Homepage\Handling\UserSession\Handler\Command\LoginUser\LoginUserCommandHandler;
use Homepage\Infrastructure\Session\Middleware\SessionMiddleware;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class LoginUserHandler implements RequestHandlerInterface
{
public function __construct(
private readonly LoginUserCommandHandler $handler,
private readonly LoginUserCommandBuilder $builder
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$session = $request->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()
]);
}
}

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\Authentication\Handler;
use Homepage\Handling\UserSession\Handler\Command\LogoutUser\LogoutUserCommandBuilder;
use Homepage\Handling\UserSession\Handler\Command\LogoutUser\LogoutUserCommandHandler;
use Homepage\Infrastructure\Response\SuccessResponse;
use Homepage\Infrastructure\Session\Middleware\SessionMiddleware;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class LogoutUserHandler implements RequestHandlerInterface
{
public function __construct(
private readonly LogoutUserCommandHandler $handler,
private readonly LogoutUserCommandBuilder $builder
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
$query = $this->builder->build(
$session
);
$this->handler->execute($query);
return new JsonResponse('OK');
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\Authentication\Handler;
use Homepage\Handling\Registration\Handler\Command\RegisterUser\RegisterUserCommandBuilder;
use Homepage\Handling\Registration\Handler\Command\RegisterUser\RegisterUserCommandHandler;
use Homepage\Infrastructure\Exception\Middleware\HomepageExceptionHandlerMiddleware;
use Homepage\Infrastructure\Logging\Logger\Logger;
use Homepage\Infrastructure\Request\Middleware\AnalyzeHeaderMiddleware;
use Homepage\Infrastructure\Response\SuccessResponse;
use Homepage\Infrastructure\Session\Middleware\SessionMiddleware;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class RegisterUserHandler implements RequestHandlerInterface
{
public function __construct(
private readonly RegisterUserCommandHandler $handler,
private readonly RegisterUserCommandBuilder $builder,
private readonly Logger $logger
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$host = $request->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();
}
}

View File

@ -0,0 +1,16 @@
<?php
use Homepage\API\External\Health\Handler\HealthHandler;
return [
[
'name' => 'health',
'path' => '/api/health',
'allowed_methods' => ['GET'],
'middleware' => [
HealthHandler::class
],
],
];
?>

View File

@ -0,0 +1,12 @@
<?php
use Homepage\API\External\Health\Handler\HealthHandler;
use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
return [
'factories' => [
HealthHandler::class => AutoWiringFactory::class
],
]
?>

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\Health;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => require __DIR__ . './../config/service_manager.php',
'routes' => require __DIR__ . '/./../config/routes.php',
];
}
}

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\Health\Handler;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class HealthHandler implements RequestHandlerInterface
{
public function __construct() {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
return new JsonResponse("I'm fine. :)");
}
}
?>

View File

@ -0,0 +1,51 @@
<?php
use Homepage\API\External\Product\Handler\CreateProductHandler;
use Homepage\API\External\Product\Handler\DeleteProductHandler;
use Homepage\API\External\Product\Handler\ProductListHandler;
use Homepage\API\External\Product\Handler\UpdateProductHandler;
use Homepage\Infrastructure\Rbac\Middleware\EnsureAuthorizationMiddleware;
use Homepage\Infrastructure\Session\Middleware\LoggedInUserMiddleware;
return [
[
'name' => '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
],
],
];

View File

@ -0,0 +1,25 @@
<?php
use Homepage\API\External\Product\Formatter\ProductFormatter;
use Homepage\API\External\Product\Formatter\ProductListFormatter;
use Homepage\API\External\Product\Handler\CreateProductHandler;
use Homepage\API\External\Product\Handler\DeleteProductHandler;
use Homepage\API\External\Product\Handler\ProductListHandler;
use Homepage\API\External\Product\Handler\UpdateProductHandler;
use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
return [
'factories' => [
// 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,
],
]
?>

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\Product;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => require __DIR__ . '/./../config/service_manager.php',
'routes' => require __DIR__ . '/./../config/routes.php',
];
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Homepage\API\External\Product\Formatter;
use DateTime;
use Homepage\Data\Business\Entity\Product;
class ProductFormatter {
public function format(Product $product): array {
return [
'id' => $product->getId()->toString(),
'identifier' => $product->getIdentifier(),
'name' => $product->getName(),
'url' => $product->getUrl(),
'created' => $product->getCreatedAt()->format(DateTime::ATOM),
'updated' => $product->getUpdatedAt()->format(DateTime::ATOM)
];
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Homepage\API\External\Product\Formatter;
use Homepage\Data\Business\Entity\Product;
class ProductListFormatter {
public function __construct(
private readonly ProductFormatter $productFormatter
) {
}
public function format(array $products): array {
$list = [];
/** @var Product $product */
foreach ($products as $product) {
$list[] = $this->productFormatter->format(
$product
);
}
return $list;
}
}
?>

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\Product\Handler;
use Homepage\API\External\Product\Formatter\ProductFormatter;
use Homepage\Handling\Product\Handler\Command\CreateProduct\CreateProductCommandBuilder;
use Homepage\Handling\Product\Handler\Command\CreateProduct\CreateProductCommandHandler;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class CreateProductHandler implements RequestHandlerInterface
{
public function __construct(
private readonly CreateProductCommandHandler $handler,
private readonly CreateProductCommandBuilder $builder,
private readonly ProductFormatter $productFormatter,
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$data = json_decode(
$request->getBody()->getContents(),
true
);
$query = $this->builder->build(
$data['identifier'],
$data['name'] ?? null
);
$result = $this->handler->execute($query);
return new JsonResponse(
$this->productFormatter->format($result)
);
}
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\Product\Handler;
use Homepage\Handling\Product\Handler\Command\DeleteProduct\DeleteProductCommandBuilder;
use Homepage\Handling\Product\Handler\Command\DeleteProduct\DeleteProductCommandHandler;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class DeleteProductHandler implements RequestHandlerInterface
{
public function __construct(
private readonly DeleteProductCommandHandler $handler,
private readonly DeleteProductCommandBuilder $builder,
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$data = json_decode(
$request->getBody()->getContents(),
true
);
$query = $this->builder->build(
$data['id']
);
$result = $this->handler->execute($query);
return new JsonResponse("OK");
}
}
?>

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\Product\Handler;
use Homepage\API\External\Product\Formatter\ProductListFormatter;
use Homepage\Handling\Product\Handler\Query\ProductList\ProductListQueryBuilder;
use Homepage\Handling\Product\Handler\Query\ProductList\ProductListQueryHandler;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ProductListHandler implements RequestHandlerInterface
{
public function __construct(
private readonly ProductListQueryHandler $handler,
private readonly ProductListQueryBuilder $builder,
private readonly ProductListFormatter $productListFormatter
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$query = $this->builder->build( '*' );
$result = $this->handler->execute($query);
return new JsonResponse(
$this->productListFormatter->format($result)
);
}
}
?>

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\Product\Handler;
use Homepage\API\External\Product\Formatter\ProductFormatter;
use Homepage\Handling\Product\Handler\Command\UpdateProduct\UpdateProductCommandBuilder;
use Homepage\Handling\Product\Handler\Command\UpdateProduct\UpdateProductCommandHandler;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class UpdateProductHandler implements RequestHandlerInterface
{
public function __construct(
private readonly UpdateProductCommandHandler $handler,
private readonly UpdateProductCommandBuilder $builder,
private readonly ProductFormatter $productFormatter,
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$data = json_decode(
$request->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)
);
}
}

View File

@ -0,0 +1,48 @@
<?php
use Homepage\API\External\User\Handler\ChangePasswordHandler;
use Homepage\API\External\User\Handler\ChangeUsernameHandler;
use Homepage\API\External\User\Handler\CreateUserHandler;
use Homepage\API\External\User\Handler\UserStateHandler;
use Homepage\Infrastructure\Rbac\Middleware\EnsureAuthorizationMiddleware;
use Homepage\Infrastructure\Session\Middleware\LoggedInUserMiddleware;
return [
[
'name' => '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
],
],
];

View File

@ -0,0 +1,21 @@
<?php
use Homepage\API\External\User\Formatter\UserFormatter;
use Homepage\API\External\User\Handler\ChangePasswordHandler;
use Homepage\API\External\User\Handler\ChangeUsernameHandler;
use Homepage\API\External\User\Handler\CreateUserHandler;
use Homepage\API\External\User\Handler\UserStateHandler;
use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
return [
'factories' => [
// Formatter
UserFormatter::class => AutoWiringFactory::class,
// Handler
CreateUserHandler::class => AutoWiringFactory::class,
UserStateHandler::class => AutoWiringFactory::class,
ChangePasswordHandler::class => AutoWiringFactory::class,
ChangeUsernameHandler::class => AutoWiringFactory::class,
],
];

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\User;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => require __DIR__ . '/./../config/service_manager.php',
'routes' => require __DIR__ . '/./../config/routes.php',
];
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Homepage\API\External\User\Formatter;
use DateTime;
use Homepage\Data\Business\Entity\User;
class UserFormatter {
public function format(User $user): array {
$userArray = [
'id' => $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;
}
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\User\Handler;
use Homepage\Data\Business\Entity\User;
use Homepage\Handling\User\Handler\Command\ChangePassword\ChangePasswordCommandBuilder;
use Homepage\Handling\User\Handler\Command\ChangePassword\ChangePasswordCommandHandler;
use Homepage\Infrastructure\Response\SuccessResponse;
use Homepage\Infrastructure\Session\Middleware\LoggedInUserMiddleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ChangePasswordHandler implements RequestHandlerInterface
{
public function __construct(
private readonly ChangePasswordCommandHandler $handler,
private readonly ChangePasswordCommandBuilder $builder,
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
/** @var User $user */
$user = $request->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();
}
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\User\Handler;
use Homepage\Data\Business\Entity\User;
use Homepage\Handling\User\Handler\Command\ChangeUsername\ChangeUsernameCommandBuilder;
use Homepage\Handling\User\Handler\Command\ChangeUsername\ChangeUsernameCommandHandler;
use Homepage\Infrastructure\Response\SuccessResponse;
use Homepage\Infrastructure\Session\Middleware\LoggedInUserMiddleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ChangeUsernameHandler implements RequestHandlerInterface
{
public function __construct(
private readonly ChangeUsernameCommandHandler $handler,
private readonly ChangeUsernameCommandBuilder $builder,
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
/** @var User $user */
$user = $request->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();
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\User\Handler;
use Homepage\API\External\User\Formatter\UserFormatter;
use Homepage\Handling\User\Handler\Command\CreateUser\CreateUserCommandBuilder;
use Homepage\Handling\User\Handler\Command\CreateUser\CreateUserCommandHandler;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class CreateUserHandler implements RequestHandlerInterface
{
public function __construct(
private readonly CreateUserCommandHandler $handler,
private readonly CreateUserCommandBuilder $builder,
private readonly UserFormatter $userFormatter,
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$data = json_decode(
$request->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)
);
}
}
?>

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Homepage\API\External\User\Handler;
use Homepage\API\External\User\Formatter\UserFormatter;
use Homepage\Data\Business\Entity\User;
use Homepage\Handling\User\Handler\Command\CreateUser\CreateUserCommandBuilder;
use Homepage\Handling\User\Handler\Command\CreateUser\ChangePasswordCommandHandler;
use Homepage\Infrastructure\Session\Middleware\LoggedInUserMiddleware;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class UserStateHandler implements RequestHandlerInterface
{
public function __construct(
private readonly UserFormatter $userFormatter,
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
/** @var User $user */
$user = $request->getAttribute(LoggedInUserMiddleware::USER_KEY);
$response = $this->userFormatter->format($user);
$response['sessionId'] = $user->getSession()->getId()->toString();
return new JsonResponse($response);
}
}

View File

@ -0,0 +1,55 @@
<?php
use Doctrine\DBAL\Driver\PDO\MySQL\Driver;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\Persistence\Mapping\Driver\MappingDriverChain;
return [
'configuration' => [
'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',
],
],
];

View File

@ -0,0 +1,21 @@
<?php
use Homepage\Data\Business\Entity\User;
use Homepage\Data\Business\Factory\HomepageEntityManagerFactory;
use Homepage\Data\Business\Manager\HomepageEntityManager;
use Homepage\Data\Business\Repository\UserRepository;
use Homepage\Infrastructure\Database\AutowireRepositoryFactory;
use Roave\PsrContainerDoctrine\ConfigurationFactory;
use Roave\PsrContainerDoctrine\ConnectionFactory;
use Roave\PsrContainerDoctrine\EntityManagerFactory;
return [
'factories' => [
'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],
],
];

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace Homepage\Data\Business;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => require __DIR__ . './../config/service_manager.php',
'doctrine' => require __DIR__ . './../config/doctrine.php',
];
}
}

View File

@ -0,0 +1,100 @@
<?php
namespace Homepage\Data\Business\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Homepage\Infrastructure\UuidGenerator\UuidGenerator;
use Ramsey\Uuid\UuidInterface;
/**
* @ORM\Entity(repositoryClass="Homepage\Data\Business\Repository\PermissionRepository")
* @ORM\Table(name="permission")
*/
class Permission {
/**
* @ORM\Id
* @ORM\Column(name="id", type="uuid_binary_ordered_time")
*/
private UuidInterface $id;
/** @ORM\Column(name="product_id", type="uuid_binary_ordered_time", nullable=true) */
private ?UuidInterface $productId;
/**
* @ORM\OneToOne(targetEntity="Homepage\Data\Business\Entity\Product")
* @ORM\JoinColumn(name="product_id", referencedColumnName="id", nullable=true)
*/
private ?Product $product;
/** @ORM\Column(name="identifier", type="string") */
private string $identifier;
/**
* @ORM\ManyToMany(targetEntity="Homepage\Data\Business\Entity\Role", inversedBy="permissions")
* @ORM\JoinTable(
* name="role_permission",
* joinColumns={@ORM\JoinColumn(name="permission_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")}
* )
*/
private Collection $roles;
public function __construct() {
$this->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);
}
}
}

View File

@ -0,0 +1,99 @@
<?php
namespace Homepage\Data\Business\Entity;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
use Homepage\Infrastructure\UuidGenerator\UuidGenerator;
use Ramsey\Uuid\UuidInterface;
/**
* @ORM\Entity(repositoryClass="Homepage\Data\Business\Repository\ProductRepository")
* @ORM\Table(name="product")
*/
class Product {
/**
* @ORM\Id
* @ORM\Column(name="id", type="uuid_binary_ordered_time")
*/
private UuidInterface $id;
/** @ORM\Column(name="identifier", type="string") */
private string $identifier;
/** @ORM\Column(name="name", type="string", nullable="true") */
private ?string $name;
/** @ORM\Column(name="url", type="string", nullable="true") */
private ?string $url;
/** @ORM\Column(name="created_at", type="datetime") */
private DateTime $createdAt;
/** @ORM\Column(name="updated_at", type="datetime") */
private DateTime $updatedAt;
public function __construct() {
$this->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;
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace Homepage\Data\Business\Entity;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
use Homepage\Infrastructure\UuidGenerator\UuidGenerator;
use Ramsey\Uuid\UuidInterface;
/**
* @ORM\Entity(repositoryClass="Homepage\Data\Business\Repository\RegistrationRepository")
* @ORM\Table(name="registration")
*/
class Registration {
/**
* @ORM\Id
* @ORM\Column(name="id", type="uuid_binary_ordered_time")
*/
private UuidInterface $id;
/** @ORM\Column(name="username", type="string") */
private string $username;
/** @ORM\Column(name="mail", type="string") */
private string $mail;
/** @ORM\Column(name="created_at", type="datetime") */
private DateTime $createdAt;
public function __construct() {
$this->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;
}
}

View File

@ -0,0 +1,101 @@
<?php
namespace Homepage\Data\Business\Entity;
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Homepage\Infrastructure\UuidGenerator\UuidGenerator;
use Ramsey\Uuid\UuidInterface;
/**
* @ORM\Entity(repositoryClass="Homepage\Data\Business\Repository\RoleRepository")
* @ORM\Table(name="role")
*/
class Role {
/**
* @ORM\Id
* @ORM\Column(name="id", type="uuid_binary_ordered_time")
*/
private UuidInterface $id;
/** @ORM\Column(name="product_id", type="uuid_binary_ordered_time", nullable=true) */
private ?UuidInterface $productId;
/**
* @ORM\OneToOne(targetEntity="Homepage\Data\Business\Entity\Product")
* @ORM\JoinColumn(name="product_id", referencedColumnName="id", nullable=true)
*/
private ?Product $product;
/** @ORM\Column(name="identifier", type="string") */
private string $identifier;
/**
* @ORM\ManyToMany(targetEntity="Homepage\Data\Business\Entity\Permission", mappedBy="roles")
* @ORM\JoinTable(
* name="role_permission",
* joinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")},
* inverseJoinColumns={@ORM\JoinColumn(name="permission_id", referencedColumnName="id")}
* )
*/
private Collection $permissions;
public function __construct() {
$this->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);
}
}
}

View File

@ -0,0 +1,148 @@
<?php
namespace Homepage\Data\Business\Entity;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
use Homepage\Infrastructure\UuidGenerator\UuidGenerator;
use Ramsey\Uuid\UuidInterface;
/**
* @ORM\Entity(repositoryClass="Homepage\Data\Business\Repository\UserRepository")
* @ORM\Table(name="user")
*/
class User {
/**
* @ORM\Id
* @ORM\Column(name="id", type="uuid_binary_ordered_time")
*/
private UuidInterface $id;
/** @ORM\Column(name="role_id", type="uuid_binary_ordered_time") */
private UuidInterface $roleId;
/**
* @ORM\OneToOne(targetEntity="Homepage\Data\Business\Entity\Role")
* @ORM\JoinColumn(name="role_id", referencedColumnName="id")
*/
private Role $role;
/** @ORM\Column(name="username", type="string") */
private string $username;
/** @ORM\Column(name="mail", type="string") */
private string $mail;
/** @ORM\Column(name="password", type="string") */
private string $password;
/** @ORM\OneToOne(targetEntity="Homepage\Data\Business\Entity\UserSession", mappedBy="user") */
private ?UserSession $session;
/** @ORM\Column(name="last_login_at", type="datetime", nullable=true) */
private DateTime $lastLoginAt;
/** @ORM\Column(name="created_at", type="datetime") */
private DateTime $createdAt;
/** @ORM\Column(name="updated_at", type="datetime") */
private DateTime $updatedAt;
public function __construct() {
$this->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;
}
}
?>

View File

@ -0,0 +1,105 @@
<?php
namespace Homepage\Data\Business\Entity;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
use Homepage\Infrastructure\UuidGenerator\UuidGenerator;
use Ramsey\Uuid\UuidInterface;
/**
* @ORM\Entity(repositoryClass="Homepage\Data\Business\Repository\UserSessionRepository")
* @ORM\Table(name="user_session")
*/
class UserSession {
/**
* @ORM\Id
* @ORM\Column(name="id", type="uuid_binary_ordered_time")
*/
private UuidInterface $id;
/** @ORM\Column(name="user_id", type="uuid_binary_ordered_time", nullable=true) */
private ?UuidInterface $userId;
/**
* @ORM\OneToOne(targetEntity="Homepage\Data\Business\Entity\User", mappedBy="session")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=true)
*/
private ?User $user;
/** @ORM\Column(name="csrf", type="uuid_binary_ordered_time", nullable=true) */
private ?UuidInterface $csrf;
/** @ORM\Column(name="created_at", type="datetime") */
private DateTime $createdAt;
/** @ORM\Column(name="updated_at", type="datetime") */
private DateTime $updatedAt;
public function __construct() {
$this->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;
}
}
?>

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Homepage\Data\Business\Factory;
use Homepage\Data\Business\Manager\HomepageEntityManager;
use Laminas\ServiceManager\Factory\FactoryInterface;
use Psr\Container\ContainerInterface;
class HomepageEntityManagerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, ?array $options = null): HomepageEntityManager
{
return new HomepageEntityManager(
$container->get('doctrine.entity_manager.orm_homepage')
);
}
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace Homepage\Data\Business\Manager;
use Doctrine\ORM\Decorator\EntityManagerDecorator;
class HomepageEntityManager extends EntityManagerDecorator
{
}

View File

@ -0,0 +1,10 @@
<?php
namespace Homepage\Data\Business\Repository;
use Doctrine\ORM\EntityRepository;
class PermissionRepository extends EntityRepository {
}
?>

Some files were not shown because too many files have changed in this diff Show More