first version, pre PR
This commit is contained in:
parent
1df825db2a
commit
cc989e96f7
2
.gitignore
vendored
2
.gitignore
vendored
@ -11,6 +11,8 @@
|
|||||||
/var/
|
/var/
|
||||||
/vendor/
|
/vendor/
|
||||||
|
|
||||||
|
docker/docker-compose.yml
|
||||||
|
|
||||||
*.env
|
*.env
|
||||||
composer.lock
|
composer.lock
|
||||||
composer.development.json
|
composer.development.json
|
||||||
|
|||||||
@ -201,8 +201,8 @@ use Psr\\Http\\Server\\RequestHandlerInterface;
|
|||||||
class {$apiHandlerName} implements RequestHandlerInterface
|
class {$apiHandlerName} implements RequestHandlerInterface
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly {$cqrsHandlerName} \${$cqrsHandlerVariableName},
|
private readonly {$cqrsHandlerName} \$handler,
|
||||||
private readonly {$cqrsBuilderName} \${$cqrsBuilderVariableName},
|
private readonly {$cqrsBuilderName} \$builder,
|
||||||
private readonly {$apiResponseFormatterName} \$responseFormatter,
|
private readonly {$apiResponseFormatterName} \$responseFormatter,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
@ -211,10 +211,10 @@ class {$apiHandlerName} implements RequestHandlerInterface
|
|||||||
{
|
{
|
||||||
\$data = \$request->getAttribute(AnalyzeBodyMiddleware::JSON_DATA);
|
\$data = \$request->getAttribute(AnalyzeBodyMiddleware::JSON_DATA);
|
||||||
|
|
||||||
\${$cqrsVariableName} = \$this->{$cqrsBuilderVariableName}->build(
|
\${$cqrsVariableName} = \$this->builder->build(
|
||||||
\$data
|
\$data
|
||||||
);
|
);
|
||||||
\$result = \$this->{$cqrsHandlerVariableName}->execute(\${$cqrsVariableName});
|
\$result = \$this->handler->execute(\${$cqrsVariableName});
|
||||||
|
|
||||||
return new SuccessResponse(\$this->responseFormatter->format(\$result));
|
return new SuccessResponse(\$this->responseFormatter->format(\$result));
|
||||||
}
|
}
|
||||||
@ -275,9 +275,7 @@ class {$cqrsHandlerName}
|
|||||||
|
|
||||||
public function execute({$cqrsName} \${$cqrsVariableName}): {$cqrsResultName}
|
public function execute({$cqrsName} \${$cqrsVariableName}): {$cqrsResultName}
|
||||||
{
|
{
|
||||||
return new {$cqrsResultName}(
|
return new {$cqrsResultName}();
|
||||||
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
";
|
";
|
||||||
|
|||||||
30
bin/script/firstRun
Executable file
30
bin/script/firstRun
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
PROJECT_DIR=$(realpath $SCRIPT_DIR/../../)
|
||||||
|
ENV_DIR=$(realpath $PROJECT_DIR/../../../)
|
||||||
|
|
||||||
|
source $ENV_DIR/bin/denv_msg
|
||||||
|
source $ENV_DIR/bin/drun
|
||||||
|
|
||||||
|
# Install PHP packages
|
||||||
|
denv_echo_msg "[backend]: Composer install"
|
||||||
|
drun backend composer install
|
||||||
|
denv_success_msg "[backend]: Composer install done"
|
||||||
|
|
||||||
|
# Dump autoload
|
||||||
|
denv_echo_msg "[backend]: Dump autoload"
|
||||||
|
drun backend composer da
|
||||||
|
denv_success_msg "[backend]: Dump autoload done"
|
||||||
|
|
||||||
|
# Migrate databases to current version
|
||||||
|
denv_echo_msg "[backend]: Migrate db"
|
||||||
|
drun backend composer dmm
|
||||||
|
drun backend composer dmlm
|
||||||
|
denv_success_msg "[backend]: Migrate db done"
|
||||||
|
|
||||||
|
# Insert setup for project after this line
|
||||||
|
denv_echo_msg "[backend]: Initialize data"
|
||||||
|
drun backend composer console rbac:update
|
||||||
|
drun backend composer console init:data
|
||||||
|
denv_success_msg "[backend]: Initialize data done"
|
||||||
@ -3,54 +3,16 @@
|
|||||||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
PROJECT_DIR=$(realpath $SCRIPT_DIR/../../)
|
PROJECT_DIR=$(realpath $SCRIPT_DIR/../../)
|
||||||
ENV_DIR=$(realpath $PROJECT_DIR/../../../)
|
ENV_DIR=$(realpath $PROJECT_DIR/../../../)
|
||||||
|
source $ENV_DIR/bin/denv_msg
|
||||||
EXIT=0
|
|
||||||
|
|
||||||
# Check .env file
|
# Check .env file
|
||||||
if [ ! -f "$PROJECT_DIR/.env" ]
|
if [ ! -f "$PROJECT_DIR/.env" ] ; then
|
||||||
then
|
|
||||||
cp "$PROJECT_DIR/.env.example" "$PROJECT_DIR/.env"
|
cp "$PROJECT_DIR/.env.example" "$PROJECT_DIR/.env"
|
||||||
EXIT=1
|
denv_info_msg "[backend] Created .env"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check docker-compose.yml file
|
# Check docker-compose.yml file
|
||||||
if [ ! -f "$PROJECT_DIR/docker/docker-compose.yml" ]
|
if [ ! -f "$PROJECT_DIR/docker/docker-compose.yml" ] ; then
|
||||||
then
|
|
||||||
cp "$PROJECT_DIR/docker/docker-compose.yml.dist" "$PROJECT_DIR/docker/docker-compose.yml"
|
cp "$PROJECT_DIR/docker/docker-compose.yml.dist" "$PROJECT_DIR/docker/docker-compose.yml"
|
||||||
EXIT=1
|
denv_info_msg "[backend] Created docker-compose.yml"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check docker-compose-mac.yml file
|
|
||||||
if [ ! -f "$PROJECT_DIR/docker/docker-compose-mac.yml" ]
|
|
||||||
then
|
|
||||||
cp "$PROJECT_DIR/docker/docker-compose-mac.yml.dist" "$PROJECT_DIR/docker/docker-compose-mac.yml"
|
|
||||||
EXIT=1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $EXIT -eq 1 ]
|
|
||||||
then
|
|
||||||
echo "docker-compose or env files created, please change variables and call init again"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Source key-scripts
|
|
||||||
source $ENV_DIR/bin/drun
|
|
||||||
source $ENV_DIR/bin/dexec
|
|
||||||
|
|
||||||
# Build and start docker containers
|
|
||||||
dexec template-backend build
|
|
||||||
dexec template-backend up -d
|
|
||||||
|
|
||||||
# Install PHP packages
|
|
||||||
drun template-backend composer install
|
|
||||||
|
|
||||||
# Dump autoload
|
|
||||||
drun template-backend composer da
|
|
||||||
|
|
||||||
# Migrate databases to current version
|
|
||||||
drun template-backend composer dmm
|
|
||||||
drun template-backend composer dmlm
|
|
||||||
|
|
||||||
# Insert setup for project after this line
|
|
||||||
drun template-backend composer console rbac:update
|
|
||||||
drun template-backend composer console init:data
|
|
||||||
39
bin/script/update
Executable file
39
bin/script/update
Executable file
@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
PROJECT_DIR=$(realpath $SCRIPT_DIR/../../)
|
||||||
|
ENV_DIR=$(realpath $PROJECT_DIR/../../../)
|
||||||
|
SYSTEM_ENV_FILE="$PROJECT_DIR/.env"
|
||||||
|
CWD=$(pwd)
|
||||||
|
|
||||||
|
source $ENV_DIR/bin/denv_msg
|
||||||
|
source $ENV_DIR/bin/drun
|
||||||
|
|
||||||
|
# Pull branch in project directory
|
||||||
|
denv_echo_msg "[backend]: Git pull"
|
||||||
|
cd "$PROJECT_DIR"
|
||||||
|
git pull
|
||||||
|
denv_success_msg "[backend]: Git pull done"
|
||||||
|
|
||||||
|
# Install PHP packages
|
||||||
|
denv_echo_msg "[backend]: Composer install"
|
||||||
|
drun backend composer install
|
||||||
|
denv_success_msg "[backend]: Composer install done"
|
||||||
|
|
||||||
|
# Dump autoload
|
||||||
|
denv_echo_msg "[backend]: Dump autoload"
|
||||||
|
drun backend composer da
|
||||||
|
denv_success_msg "[backend]: Dump autoload done"
|
||||||
|
|
||||||
|
# Migrate databases to current version
|
||||||
|
denv_echo_msg "[backend]: Migrate db"
|
||||||
|
drun backend composer dmm
|
||||||
|
drun backend composer dmlm
|
||||||
|
denv_success_msg "[backend]: Migrate db done"
|
||||||
|
|
||||||
|
# Insert setup for project after this line
|
||||||
|
denv_echo_msg "[backend]: Update roles and permissions"
|
||||||
|
drun backend composer console rbac:update
|
||||||
|
denv_success_msg "[backend]: Update roles and permissions done"
|
||||||
|
|
||||||
|
# Switch back to current working directory
|
||||||
|
cd "$CWD"
|
||||||
@ -10,7 +10,8 @@ return [
|
|||||||
'user' => [
|
'user' => [
|
||||||
],
|
],
|
||||||
'admin' => [
|
'admin' => [
|
||||||
'user.create-user',
|
'user.create',
|
||||||
|
'user.read-list',
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|||||||
17
config/autoload/mail.global.php
Normal file
17
config/autoload/mail.global.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'mail' => [
|
||||||
|
'default' => [
|
||||||
|
'sender' => $_ENV['MAIL_DEFAULT_SENDER'],
|
||||||
|
'senderName' => $_ENV['MAIL_DEFAULT_SENDER_NAME']
|
||||||
|
],
|
||||||
|
'smtp-server' => [
|
||||||
|
'host' => $_ENV['SMTP_HOST'],
|
||||||
|
'port' => (int)$_ENV['SMTP_PORT'] ?? 25,
|
||||||
|
'encryption' => $_ENV['SMTP_ENCRYPTION'],
|
||||||
|
'username' => $_ENV['SMTP_USERNAME'],
|
||||||
|
'password' => $_ENV['SMTP_PASSWORD'],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
9
config/autoload/notification.global.php
Normal file
9
config/autoload/notification.global.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
'notification' => [
|
||||||
|
'defaultTitle' => $_ENV['NOTIFICATION_DEFAULT_TITLE'] ?? 'Template',
|
||||||
|
'host' => $_ENV['NOTIFICATION_HOST'],
|
||||||
|
'id' => $_ENV['NOTIFICATION_ID'],
|
||||||
|
]
|
||||||
|
];
|
||||||
@ -52,6 +52,9 @@ $aggregator = new ConfigAggregator([
|
|||||||
\Template\Infrastructure\Rbac\ConfigProvider::class,
|
\Template\Infrastructure\Rbac\ConfigProvider::class,
|
||||||
\Template\Infrastructure\Request\ConfigProvider::class,
|
\Template\Infrastructure\Request\ConfigProvider::class,
|
||||||
\Template\Infrastructure\Session\ConfigProvider::class,
|
\Template\Infrastructure\Session\ConfigProvider::class,
|
||||||
|
\Template\Infrastructure\Mail\ConfigProvider::class,
|
||||||
|
\Template\Infrastructure\Notification\ConfigProvider::class,
|
||||||
|
\Template\Infrastructure\Schema\ConfigProvider::class,
|
||||||
|
|
||||||
// HandlingDomain
|
// HandlingDomain
|
||||||
\Template\Handling\User\ConfigProvider::class,
|
\Template\Handling\User\ConfigProvider::class,
|
||||||
@ -63,7 +66,7 @@ $aggregator = new ConfigAggregator([
|
|||||||
\Template\API\Console\ConfigProvider::class,
|
\Template\API\Console\ConfigProvider::class,
|
||||||
|
|
||||||
/// External
|
/// External
|
||||||
\Template\API\External\Health\ConfigProvider::class,
|
\Template\API\External\Api\ConfigProvider::class,
|
||||||
\Template\API\External\User\ConfigProvider::class,
|
\Template\API\External\User\ConfigProvider::class,
|
||||||
\Template\API\External\Authentication\ConfigProvider::class,
|
\Template\API\External\Authentication\ConfigProvider::class,
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Template\Infrastructure\Exception\Middleware\ExceptionHandlerMiddleware;
|
use Template\Infrastructure\Exception\Middleware\TemplateExceptionHandlerMiddleware;
|
||||||
use Template\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
|
use Template\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
|
||||||
use Template\Infrastructure\Request\Middleware\AnalyzeHeaderMiddleware;
|
use Template\Infrastructure\Request\Middleware\AnalyzeHeaderMiddleware;
|
||||||
use Template\Infrastructure\Session\Middleware\SessionMiddleware;
|
use Template\Infrastructure\Session\Middleware\SessionMiddleware;
|
||||||
@ -68,7 +68,7 @@ return function (Application $app, MiddlewareFactory $factory, ContainerInterfac
|
|||||||
|
|
||||||
|
|
||||||
//// Pre Template Space
|
//// Pre Template Space
|
||||||
$app->pipe(ExceptionHandlerMiddleware::class);
|
$app->pipe(TemplateExceptionHandlerMiddleware::class);
|
||||||
$app->pipe(AnalyzeHeaderMiddleware::class);
|
$app->pipe(AnalyzeHeaderMiddleware::class);
|
||||||
$app->pipe(AnalyzeBodyMiddleware::class);
|
$app->pipe(AnalyzeBodyMiddleware::class);
|
||||||
|
|
||||||
|
|||||||
BIN
data/mails/registration/assets/icon.png
Normal file
BIN
data/mails/registration/assets/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
66
data/mails/registration/template.latte
Normal file
66
data/mails/registration/template.latte
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Willkommen bei Template!</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
}
|
||||||
|
.message-block {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: rgb(21 128 61);
|
||||||
|
width: 100%;
|
||||||
|
height: 15%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.quote {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: x-small;
|
||||||
|
color: slategray;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="header" class="header">
|
||||||
|
<div class="wrapper">
|
||||||
|
<div id="title" class="title">
|
||||||
|
<img src="assets/icon.png" />
|
||||||
|
<h1>Template</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="message-block" class="message-block">
|
||||||
|
<p>Hallo {$username},</p>
|
||||||
|
<br />
|
||||||
|
<p>Herzlich willkommen beim Template!</p>
|
||||||
|
<br />
|
||||||
|
<p>Ich konnte deine Anmeldung nun bestätigen.</p>
|
||||||
|
<p>Bitte klicke auf <a href="{$confirmationLink}">diesen Link</a> um dein Passwort festzulegen.</p>
|
||||||
|
<p>Danach ist deine Registierung abgeschlossen und du kannst loslegen!</p>
|
||||||
|
<br />
|
||||||
|
<p>Mit grünen Grüßen,</p>
|
||||||
|
<p>{$growerName}</p>
|
||||||
|
<br />
|
||||||
|
<p class="quote">"Smoke weed everyday" - Snoop Dogg</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
data/mails/reset-password/assets/icon.png
Normal file
BIN
data/mails/reset-password/assets/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
63
data/mails/reset-password/template.latte
Normal file
63
data/mails/reset-password/template.latte
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Passwort zurücksetzen</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
}
|
||||||
|
.message-block {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: rgb(255, 199, 44);
|
||||||
|
width: 100%;
|
||||||
|
height: 15%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.quote {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: x-small;
|
||||||
|
color: slategray;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="header" class="header">
|
||||||
|
<div class="wrapper">
|
||||||
|
<div id="title" class="title">
|
||||||
|
<img src="assets/icon.png" />
|
||||||
|
<h1>Beekeeper</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="message-block" class="message-block">
|
||||||
|
<p>Hallo {$username},</p>
|
||||||
|
<br />
|
||||||
|
<p>Dein Passwort wurde zurückgesetzt.</p>
|
||||||
|
<p>Bitte klicke auf <a href="{$passwordResetLink}">diesen Link</a> um ein neues Passwort zu vergeben.</p>
|
||||||
|
<br />
|
||||||
|
<p>Wenn du dein Passwort nicht zurückgesetzt hast, kannst du diese Nachricht ignorieren!</p>
|
||||||
|
<br />
|
||||||
|
<p>Mit fleißigen Grüßen,</p>
|
||||||
|
<p>Der Beekeeper</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
37
data/migrations/template/Version20240827155813.php
Normal file
37
data/migrations/template/Version20240827155813.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Template\Migrations\Bee;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20240827155813 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return "Create Table 'user_password_token'";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$sql = "CREATE TABLE user_password_token (
|
||||||
|
id binary(16) NOT NULL,
|
||||||
|
user_id binary(16) NOT 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_password_token;");
|
||||||
|
}
|
||||||
|
}
|
||||||
41
data/migrations/template/Version20240911191301.php
Normal file
41
data/migrations/template/Version20240911191301.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Bee\Migrations\Bee;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20240911191301 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return "Create Table 'mail'";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$sql = "CREATE TABLE mail (
|
||||||
|
id binary(16) NOT NULL,
|
||||||
|
template varchar(255) NOT NULL,
|
||||||
|
data json NOT NULL,
|
||||||
|
recipient varchar(255) NOT NULL,
|
||||||
|
sender varchar(255) NULL,
|
||||||
|
sender_name varchar(255) NULL,
|
||||||
|
failed_at datetime NULL,
|
||||||
|
created_at datetime NOT NULL,
|
||||||
|
PRIMARY KEY (id)
|
||||||
|
);";
|
||||||
|
|
||||||
|
$this->addSql($sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql("DROP TABLE mail;");
|
||||||
|
}
|
||||||
|
}
|
||||||
31
data/migrations/template/Version20241130201314.php
Normal file
31
data/migrations/template/Version20241130201314.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Template\Migrations\Template;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20241130201314 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'rename status and thermometer to reference location';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql("RENAME TABLE `thermometer` TO `location_thermometer`");
|
||||||
|
$this->addSql("RENAME TABLE `status` TO `location_status`");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql("RENAME TABLE `location_thermometer` TO `thermometer`");
|
||||||
|
$this->addSql("RENAME TABLE `location_status` TO `status`");
|
||||||
|
}
|
||||||
|
}
|
||||||
77
data/migrations/template/Version20241130202949.php
Normal file
77
data/migrations/template/Version20241130202949.php
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Template\Migrations\Template;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20241130202949 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return 'update procedure to last migration';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql("DROP PROCEDURE pS_LOCATION_ThermometerHistoryGraphList");
|
||||||
|
$this->addSql("
|
||||||
|
CREATE PROCEDURE pS_LOCATION_ThermometerHistoryGraphList (
|
||||||
|
IN locaiton_id BINARY(16),
|
||||||
|
IN start_at DATETIME,
|
||||||
|
IN end_at DATETIME,
|
||||||
|
IN `interval` int
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
SELECT
|
||||||
|
FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(created_at) / (`interval` * 60)) * (`interval` * 60)) AS time_interval,
|
||||||
|
ROUND(AVG(temperature),1) AS avg_temperature,
|
||||||
|
ROUND(AVG(humidity)) AS avg_humidity
|
||||||
|
FROM
|
||||||
|
location_thermometer
|
||||||
|
WHERE
|
||||||
|
locaiton_id = locaiton_id AND
|
||||||
|
(created_at >= COALESCE(start_at, '1970-01-01 00:00:00')) AND
|
||||||
|
(created_at <= COALESCE(end_at, NOW()))
|
||||||
|
GROUP BY
|
||||||
|
time_interval
|
||||||
|
ORDER BY
|
||||||
|
time_interval ASC;
|
||||||
|
END;
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
$this->addSql("DROP PROCEDURE pS_LOCATION_ThermometerHistoryGraphList");
|
||||||
|
$this->addSql("
|
||||||
|
CREATE PROCEDURE pS_LOCATION_ThermometerHistoryGraphList (
|
||||||
|
IN locaiton_id BINARY(16),
|
||||||
|
IN start_at DATETIME,
|
||||||
|
IN end_at DATETIME,
|
||||||
|
IN `interval` int
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
SELECT
|
||||||
|
FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(created_at) / (`interval` * 60)) * (`interval` * 60)) AS time_interval,
|
||||||
|
ROUND(AVG(temperature),1) AS avg_temperature,
|
||||||
|
ROUND(AVG(humidity)) AS avg_humidity
|
||||||
|
FROM
|
||||||
|
thermometer
|
||||||
|
WHERE
|
||||||
|
locaiton_id = locaiton_id AND
|
||||||
|
(created_at >= COALESCE(start_at, '1970-01-01 00:00:00')) AND
|
||||||
|
(created_at <= COALESCE(end_at, NOW()))
|
||||||
|
GROUP BY
|
||||||
|
time_interval
|
||||||
|
ORDER BY
|
||||||
|
time_interval ASC;
|
||||||
|
END;
|
||||||
|
");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,52 +0,0 @@
|
|||||||
networks:
|
|
||||||
template:
|
|
||||||
external: true
|
|
||||||
|
|
||||||
services:
|
|
||||||
template-backend-mysql:
|
|
||||||
image: template-backend-mysql
|
|
||||||
networks:
|
|
||||||
- template
|
|
||||||
build:
|
|
||||||
context: ./../
|
|
||||||
dockerfile: ./docker/mysql/dockerfile
|
|
||||||
volumes:
|
|
||||||
- /Users/flo/dev/backend/template/var/db:/var/lib/mysql:z
|
|
||||||
environment:
|
|
||||||
MYSQL_USER: ${DB_USER}
|
|
||||||
MYSQL_PASSWORD: ${DB_PASSWORD}
|
|
||||||
ports:
|
|
||||||
- 3306:3306
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
|
|
||||||
timeout: 20s
|
|
||||||
retries: 10
|
|
||||||
|
|
||||||
template-backend-app:
|
|
||||||
image: template-backend-app
|
|
||||||
networks:
|
|
||||||
- template
|
|
||||||
build:
|
|
||||||
context: ./../
|
|
||||||
dockerfile: ./docker/php/dockerfile
|
|
||||||
volumes:
|
|
||||||
- /Users/flo/dev/backend/template/:/var/www/html:z
|
|
||||||
ports:
|
|
||||||
- 9000:9000
|
|
||||||
depends_on:
|
|
||||||
template-backend-mysql:
|
|
||||||
condition: service_healthy
|
|
||||||
|
|
||||||
template-backend-nginx:
|
|
||||||
image: template-backend-nginx
|
|
||||||
networks:
|
|
||||||
- template
|
|
||||||
build:
|
|
||||||
context: ./../
|
|
||||||
dockerfile: ./docker/nginx/dockerfile
|
|
||||||
labels:
|
|
||||||
- "traefik.http.routers.backend.rule=Host(`template.local`) && PathPrefix(`/api`)"
|
|
||||||
- "traefik.http.routers.backend.entrypoints=websecure"
|
|
||||||
- "traefik.http.routers.backend.tls.certresolver=le"
|
|
||||||
depends_on:
|
|
||||||
- template-backend-app
|
|
||||||
@ -46,7 +46,7 @@ return [
|
|||||||
|
|
||||||
'migrations_configuration' => [
|
'migrations_configuration' => [
|
||||||
'orm_template' => [
|
'orm_template' => [
|
||||||
'directory' => 'data/migrations/business',
|
'directory' => 'data/migrations/template',
|
||||||
'name' => 'Doctrine Database Migrations for Template',
|
'name' => 'Doctrine Database Migrations for Template',
|
||||||
'namespace' => 'Template\Migrations\Template',
|
'namespace' => 'Template\Migrations\Template',
|
||||||
'table' => 'migrations',
|
'table' => 'migrations',
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Database;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
|
use Laminas\ServiceManager\Factory\FactoryInterface;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
|
class AutowireRepositoryFactory implements FactoryInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $entityManagerClass,
|
||||||
|
private readonly string $entityClass,
|
||||||
|
) {
|
||||||
|
var_dump($this->entityClass, $this->entityClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke(
|
||||||
|
ContainerInterface $container,
|
||||||
|
$requestedName,
|
||||||
|
?array $options = null
|
||||||
|
): ObjectRepository|EntityRepository
|
||||||
|
{
|
||||||
|
/** @var EntityManager $em */
|
||||||
|
$em = $container->get($this->entityManagerClass);
|
||||||
|
|
||||||
|
return $em->getRepository($this->entityClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,23 +4,15 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Template\Infrastructure\Encryption\Client;
|
namespace Template\Infrastructure\Encryption\Client;
|
||||||
|
|
||||||
use Laminas\Crypt\Password\Bcrypt;
|
|
||||||
|
|
||||||
class EncryptionClient
|
class EncryptionClient
|
||||||
{
|
{
|
||||||
private readonly Bcrypt $bcrypt;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
) {
|
|
||||||
$this->bcrypt = new Bcrypt();
|
|
||||||
$this->bcrypt->setCost(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function encrypt(string $value): string {
|
public function encrypt(string $value): string {
|
||||||
return $this->bcrypt->create($value);
|
return password_hash($value, PASSWORD_BCRYPT, [
|
||||||
|
'cost' => 10
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function verify(string $value, string $encryptedValue): bool {
|
public function verify(string $value, string $encryptedValue): bool {
|
||||||
return $this->bcrypt->verify($value, $encryptedValue);
|
return hash_equals($encryptedValue, crypt($value, $encryptedValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Template\Infrastructure\Exception\Middleware\ExceptionHandlerMiddleware;
|
use Template\Infrastructure\Exception\Middleware\TemplateExceptionHandlerMiddleware;
|
||||||
use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
|
use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'factories' => [
|
'factories' => [
|
||||||
ExceptionHandlerMiddleware::class => AutoWiringFactory::class,
|
TemplateExceptionHandlerMiddleware::class => AutoWiringFactory::class,
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@ -3,10 +3,13 @@
|
|||||||
namespace Template\Infrastructure\Exception;
|
namespace Template\Infrastructure\Exception;
|
||||||
|
|
||||||
enum ErrorCode : string {
|
enum ErrorCode : string {
|
||||||
case SomethingWentWrong = 'SomethingWentWrong';
|
|
||||||
|
|
||||||
case NotFound = 'NotFound';
|
|
||||||
case AlreadyExists = 'AlreadyExists';
|
case AlreadyExists = 'AlreadyExists';
|
||||||
case WrongCredentials = 'WrongCredentials';
|
case Failed = 'Failed';
|
||||||
|
case Invalid = 'Invalid';
|
||||||
|
case Maintenance = 'Maintenance';
|
||||||
case Mismatch = 'Mismatch';
|
case Mismatch = 'Mismatch';
|
||||||
|
case NotFound = 'NotFound';
|
||||||
|
case SomethingWentWrong = 'SomethingWentWrong';
|
||||||
|
case WrongCredentials = 'WrongCredentials';
|
||||||
|
case ValidationFailed = 'ValidationFailed';
|
||||||
}
|
}
|
||||||
@ -3,11 +3,12 @@
|
|||||||
namespace Template\Infrastructure\Exception;
|
namespace Template\Infrastructure\Exception;
|
||||||
|
|
||||||
enum ErrorDomain : string {
|
enum ErrorDomain : string {
|
||||||
|
case Schema = 'Schema';
|
||||||
case Generic = 'Generic';
|
case Generic = 'Generic';
|
||||||
|
case Mail = 'Mail';
|
||||||
|
case Notification = 'Notification';
|
||||||
|
case Registration = 'Registration';
|
||||||
case Role = 'Role';
|
case Role = 'Role';
|
||||||
case User = 'User';
|
case User = 'User';
|
||||||
case UserPassword = 'UserPassword';
|
case UserPassword = 'UserPassword';
|
||||||
case Registration = 'Registration';
|
|
||||||
case Product = 'Product';
|
|
||||||
}
|
}
|
||||||
@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
namespace Template\Infrastructure\Exception\Exception;
|
namespace Template\Infrastructure\Exception\Exception;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
use Template\Infrastructure\Exception\ErrorCode;
|
use Template\Infrastructure\Exception\ErrorCode;
|
||||||
use Template\Infrastructure\Exception\ErrorDomain;
|
use Template\Infrastructure\Exception\ErrorDomain;
|
||||||
|
|
||||||
class Exception extends \Exception {
|
class TemplateException extends Exception {
|
||||||
|
|
||||||
private readonly ErrorDomain $errorDomain;
|
private readonly ErrorDomain $errorDomain;
|
||||||
private readonly ErrorCode $errorCode;
|
private readonly ErrorCode $errorCode;
|
||||||
@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Template\Infrastructure\Exception\Middleware;
|
|
||||||
|
|
||||||
use Template\Infrastructure\Exception\Exception\Exception;
|
|
||||||
use Template\Infrastructure\Logging\Logger\Logger;
|
|
||||||
use Template\Infrastructure\Response\ErrorResponse;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
use Psr\Http\Server\MiddlewareInterface;
|
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
|
||||||
|
|
||||||
class ExceptionHandlerMiddleware implements MiddlewareInterface
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private readonly Logger $logger
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public function process(
|
|
||||||
ServerRequestInterface $request,
|
|
||||||
RequestHandlerInterface $handler
|
|
||||||
): ResponseInterface
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return $handler->handle($request);
|
|
||||||
} catch (Exception $exception) {
|
|
||||||
$this->logger->exception($exception);
|
|
||||||
|
|
||||||
return new ErrorResponse(
|
|
||||||
$exception->getErrorDomain(),
|
|
||||||
$exception->getErrorCode(),
|
|
||||||
$exception->getMessage()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Exception\Middleware;
|
||||||
|
|
||||||
|
use League\OpenAPIValidation\PSR15\Exception\InvalidResponseMessage;
|
||||||
|
use League\OpenAPIValidation\PSR15\Exception\InvalidServerRequestMessage;
|
||||||
|
use Template\Infrastructure\Exception\ErrorCode;
|
||||||
|
use Template\Infrastructure\Exception\ErrorDomain;
|
||||||
|
use Template\Infrastructure\Exception\Exception\TemplateException;
|
||||||
|
use Template\Infrastructure\Logging\Logger\Logger;
|
||||||
|
use Template\Infrastructure\Response\ErrorResponse;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
|
||||||
|
class TemplateExceptionHandlerMiddleware implements MiddlewareInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly Logger $logger
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function process(
|
||||||
|
ServerRequestInterface $request,
|
||||||
|
RequestHandlerInterface $handler
|
||||||
|
): ResponseInterface
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return $handler->handle($request);
|
||||||
|
} catch (TemplateException $exception) {
|
||||||
|
$this->logger->exception($exception);
|
||||||
|
|
||||||
|
return new ErrorResponse(
|
||||||
|
$exception->getErrorDomain(),
|
||||||
|
$exception->getErrorCode(),
|
||||||
|
$exception->getMessage()
|
||||||
|
);
|
||||||
|
} catch (InvalidResponseMessage $exception) {
|
||||||
|
$this->logger->exception($exception);
|
||||||
|
|
||||||
|
|
||||||
|
$lines = [];
|
||||||
|
do {
|
||||||
|
$lines[] = $exception->getMessage();
|
||||||
|
$exception = $exception->getPrevious();
|
||||||
|
} while ($exception->getPrevious() !== null);
|
||||||
|
|
||||||
|
return new ErrorResponse(
|
||||||
|
ErrorDomain::Schema,
|
||||||
|
ErrorCode::ValidationFailed,
|
||||||
|
implode(PHP_EOL, $lines)
|
||||||
|
);
|
||||||
|
} catch (InvalidServerRequestMessage $exception) {
|
||||||
|
$this->logger->exception($exception);
|
||||||
|
|
||||||
|
|
||||||
|
$lines = [];
|
||||||
|
do {
|
||||||
|
$lines[] = $exception->getMessage();
|
||||||
|
$exception = $exception->getPrevious();
|
||||||
|
} while ($exception->getPrevious() !== null);
|
||||||
|
|
||||||
|
return new ErrorResponse(
|
||||||
|
ErrorDomain::Schema,
|
||||||
|
ErrorCode::ValidationFailed,
|
||||||
|
implode(PHP_EOL, $lines)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
namespace Template\Infrastructure\Logging\Logger;
|
namespace Template\Infrastructure\Logging\Logger;
|
||||||
|
|
||||||
use Template\Infrastructure\Exception\Exception\Exception;
|
use Template\Infrastructure\Exception\Exception\TemplateException;
|
||||||
|
use Template\Infrastructure\Logging\Handler\FileStreamHandler;
|
||||||
use Monolog\ErrorHandler;
|
use Monolog\ErrorHandler;
|
||||||
|
use Monolog\Handler\StreamHandler;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Stringable;
|
use Stringable;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
@ -24,7 +26,7 @@ class Logger implements LoggerInterface
|
|||||||
'trace' => $exception->getTraceAsString()
|
'trace' => $exception->getTraceAsString()
|
||||||
]*/;
|
]*/;
|
||||||
|
|
||||||
if ($exception instanceof Exception) {
|
if ($exception instanceof TemplateException) {
|
||||||
$exceptionContext = array_merge([
|
$exceptionContext = array_merge([
|
||||||
'errorDomain' => $exception->getErrorDomain()->value,
|
'errorDomain' => $exception->getErrorDomain()->value,
|
||||||
'errorCode' => $exception->getErrorCode()->value,
|
'errorCode' => $exception->getErrorCode()->value,
|
||||||
|
|||||||
12
src/Infrastructure/Mail/config/service_manager.php
Normal file
12
src/Infrastructure/Mail/config/service_manager.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
|
||||||
|
use Template\Infrastructure\Mail\Service\MailService;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'factories' => [
|
||||||
|
MailService::class => AutoWiringFactory::class,
|
||||||
|
],
|
||||||
|
];
|
||||||
15
src/Infrastructure/Mail/src/ConfigProvider.php
Normal file
15
src/Infrastructure/Mail/src/ConfigProvider.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Mail;
|
||||||
|
|
||||||
|
class ConfigProvider
|
||||||
|
{
|
||||||
|
public function __invoke(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'dependencies' => require __DIR__ . './../config/service_manager.php',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Mail\Exception;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
use Template\Infrastructure\Exception\ErrorCode;
|
||||||
|
use Template\Infrastructure\Exception\ErrorDomain;
|
||||||
|
use Template\Infrastructure\Exception\Exception\TemplateException;
|
||||||
|
|
||||||
|
class SendMailFailedException extends TemplateException
|
||||||
|
{
|
||||||
|
private const MESSAGE = 'Das Mail template %s konnte mit den Daten {%s} nicht an %s gesendet werden. Grund: %s';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
string $templateIdentification,
|
||||||
|
array $templateData,
|
||||||
|
string $recipient,
|
||||||
|
Throwable $reason,
|
||||||
|
) {
|
||||||
|
parent::__construct(
|
||||||
|
sprintf(
|
||||||
|
self::MESSAGE,
|
||||||
|
$templateIdentification,
|
||||||
|
json_encode($templateData),
|
||||||
|
$recipient,
|
||||||
|
$reason->getMessage(),
|
||||||
|
),
|
||||||
|
ErrorDomain::Mail,
|
||||||
|
ErrorCode::Failed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
176
src/Infrastructure/Mail/src/Service/MailService.php
Normal file
176
src/Infrastructure/Mail/src/Service/MailService.php
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Mail\Service;
|
||||||
|
|
||||||
|
use Template\Data\Business\Entity\Mail;
|
||||||
|
use Template\Data\Business\Manager\EntityManager;
|
||||||
|
use Template\Data\Business\Repository\MailRepository;
|
||||||
|
use DateTime;
|
||||||
|
use Exception;
|
||||||
|
use Latte\Engine;
|
||||||
|
use Nette\Mail\Mailer;
|
||||||
|
use Nette\Mail\Message;
|
||||||
|
use Nette\Mail\SmtpMailer;
|
||||||
|
use Reinfi\DependencyInjection\Service\ConfigService;
|
||||||
|
use Throwable;
|
||||||
|
use Template\Infrastructure\Logging\Logger\Logger;
|
||||||
|
use Template\Infrastructure\Mail\Exception\SendMailFailedException;
|
||||||
|
|
||||||
|
class MailService
|
||||||
|
{
|
||||||
|
private const TEMPLATE_PATH = APP_ROOT . '/data/mails/';
|
||||||
|
private readonly Engine $engine;
|
||||||
|
private readonly MailRepository $mailRepository;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly EntityManager $entityManager,
|
||||||
|
private readonly ConfigService $configService,
|
||||||
|
private readonly Logger $logger,
|
||||||
|
) {
|
||||||
|
$this->engine = new Engine();
|
||||||
|
|
||||||
|
$this->mailRepository = $this->entityManager->getRepository(Mail::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function enqueue(
|
||||||
|
string $template,
|
||||||
|
array $templateData,
|
||||||
|
string $recipient,
|
||||||
|
?string $sender = null,
|
||||||
|
?string $senderName = null
|
||||||
|
): void {
|
||||||
|
$mail = new Mail();
|
||||||
|
$mail->setTemplate($template);
|
||||||
|
$mail->setData($templateData);
|
||||||
|
$mail->setRecipient($recipient);
|
||||||
|
$mail->setSender($sender);
|
||||||
|
$mail->setSenderName($senderName);
|
||||||
|
|
||||||
|
$this->entityManager->persist($mail);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(?int $count = null): void {
|
||||||
|
$qb = $this->mailRepository->createQueryBuilder('m');
|
||||||
|
$qb->orderBy('m.createdAt', 'asc');
|
||||||
|
|
||||||
|
if ($count !== null) {
|
||||||
|
$qb->setMaxResults($count);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mailsToSend = $qb->getQuery()->execute();
|
||||||
|
|
||||||
|
/** @var Mail $mailToSend */
|
||||||
|
foreach ($mailsToSend as $mailToSend) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->sendMail(
|
||||||
|
$mailToSend->getTemplate(),
|
||||||
|
$mailToSend->getData(),
|
||||||
|
$mailToSend->getRecipient(),
|
||||||
|
$mailToSend->getSender(),
|
||||||
|
$mailToSend->getSenderName(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->entityManager->remove($mailToSend);
|
||||||
|
} catch (SendMailFailedException $e) {
|
||||||
|
// log is done withing sendMail
|
||||||
|
$mailToSend->setFailedAt(new DateTime());
|
||||||
|
$this->entityManager->persist($mailToSend);
|
||||||
|
}
|
||||||
|
$this->entityManager->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws SendMailFailedException
|
||||||
|
*/
|
||||||
|
private function sendMail(
|
||||||
|
string $template,
|
||||||
|
array $templateData,
|
||||||
|
string $recipient,
|
||||||
|
?string $sender = null,
|
||||||
|
?string $senderName = null
|
||||||
|
): void {
|
||||||
|
try {
|
||||||
|
$mail = $this->getMail(
|
||||||
|
template: $template,
|
||||||
|
templateData: $templateData,
|
||||||
|
recipient: $recipient,
|
||||||
|
sender: $sender,
|
||||||
|
senderName: $senderName
|
||||||
|
);
|
||||||
|
|
||||||
|
$mailer = $this->getMailer();
|
||||||
|
|
||||||
|
$mailer->send($mail);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
$this->logger->exception($e);
|
||||||
|
|
||||||
|
throw new SendMailFailedException(
|
||||||
|
$template,
|
||||||
|
$templateData,
|
||||||
|
$recipient,
|
||||||
|
$e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMailer(): Mailer
|
||||||
|
{
|
||||||
|
$smtpConfig = $this->configService->resolve('mail.smtp-server');
|
||||||
|
|
||||||
|
return new SmtpMailer(
|
||||||
|
host: $smtpConfig['host'],
|
||||||
|
username: $smtpConfig['username'],
|
||||||
|
password: $smtpConfig['password'],
|
||||||
|
encryption: $smtpConfig['encryption'],
|
||||||
|
port: $smtpConfig['port'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMail(
|
||||||
|
string $template,
|
||||||
|
array $templateData,
|
||||||
|
string $recipient,
|
||||||
|
?string $sender = null,
|
||||||
|
?string $senderName = null
|
||||||
|
): Message {
|
||||||
|
$templatePath = self::TEMPLATE_PATH . $template . '/template.latte';
|
||||||
|
$assetsPath = self::TEMPLATE_PATH . $template . "/";
|
||||||
|
|
||||||
|
if (!file_exists($templatePath)) {
|
||||||
|
throw new Exception("Template File does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_dir($assetsPath)) {
|
||||||
|
$assetsPath = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mail = new Message();
|
||||||
|
$mail->setFrom($this->getSenderName($sender, $senderName));
|
||||||
|
$mail->addTo($recipient);
|
||||||
|
$mail->setHtmlBody(
|
||||||
|
$this->engine->renderToString(
|
||||||
|
$templatePath,
|
||||||
|
$templateData,
|
||||||
|
),
|
||||||
|
$assetsPath
|
||||||
|
);
|
||||||
|
|
||||||
|
return $mail;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getSenderName(
|
||||||
|
?string $sender,
|
||||||
|
?string $senderName
|
||||||
|
): string {
|
||||||
|
$defaultConfig = $this->configService->resolve('mail.default');
|
||||||
|
|
||||||
|
$from = $sender ?? $defaultConfig['sender'] ?? throw new Exception('Could not determine Sender');
|
||||||
|
$name =$sender ?? $defaultConfig['senderName'] ?? throw new Exception('Could not determine Sender Name');
|
||||||
|
return sprintf('%s <%s>', $name, $from);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/Infrastructure/Notification/config/service_manager.php
Normal file
12
src/Infrastructure/Notification/config/service_manager.php
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
|
||||||
|
use Template\Infrastructure\Notification\Service\NotificationService;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'factories' => [
|
||||||
|
NotificationService::class => AutoWiringFactory::class,
|
||||||
|
],
|
||||||
|
];
|
||||||
15
src/Infrastructure/Notification/src/ConfigProvider.php
Normal file
15
src/Infrastructure/Notification/src/ConfigProvider.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Notification;
|
||||||
|
|
||||||
|
class ConfigProvider
|
||||||
|
{
|
||||||
|
public function __invoke(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'dependencies' => require __DIR__ . './../config/service_manager.php',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Notification\Exception;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
use Template\Infrastructure\Exception\ErrorCode;
|
||||||
|
use Template\Infrastructure\Exception\ErrorDomain;
|
||||||
|
use Template\Infrastructure\Exception\Exception\TemplateException;
|
||||||
|
|
||||||
|
class SendNotificationFailed extends TemplateException
|
||||||
|
{
|
||||||
|
private const MESSAGE = 'Notification could not be sent. Reason: %s';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
Throwable $reason,
|
||||||
|
) {
|
||||||
|
parent::__construct(
|
||||||
|
sprintf(
|
||||||
|
self::MESSAGE,
|
||||||
|
$reason->getMessage(),
|
||||||
|
),
|
||||||
|
ErrorDomain::Notification,
|
||||||
|
ErrorCode::Failed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Notification\Service;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Reinfi\DependencyInjection\Service\ConfigService;
|
||||||
|
use Throwable;
|
||||||
|
use Template\Infrastructure\Notification\Exception\SendNotificationFailed;
|
||||||
|
|
||||||
|
class NotificationService
|
||||||
|
{
|
||||||
|
private string $defaultTitle;
|
||||||
|
private ?string $host;
|
||||||
|
private ?string $id;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly ConfigService $configService,
|
||||||
|
) {
|
||||||
|
$config = $this->configService->resolve('notification');
|
||||||
|
$this->defaultTitle = $config['defaultTitle'];
|
||||||
|
$this->host = $config['host'] ?? '';
|
||||||
|
$this->id = $config['id'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws SendNotificationFailed
|
||||||
|
*/
|
||||||
|
public function push(
|
||||||
|
string $message,
|
||||||
|
string $title = null,
|
||||||
|
): void {
|
||||||
|
try {
|
||||||
|
if (($this->id ?? '') === '') {
|
||||||
|
throw new Exception('Notification ID is not set.');
|
||||||
|
}
|
||||||
|
if (($this->host ?? '') === '') {
|
||||||
|
throw new Exception('Notification Host is not set.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$command = sprintf(
|
||||||
|
'curl -H "Title: %s" -H "Tags: broccoli" -d "%s" %s/%s',
|
||||||
|
$title ?? $this->defaultTitle,
|
||||||
|
$message,
|
||||||
|
$this->host,
|
||||||
|
$this->id
|
||||||
|
);
|
||||||
|
|
||||||
|
shell_exec($command);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
throw new SendNotificationFailed($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -56,7 +56,8 @@ class EnsureAuthorizationMiddleware implements MiddlewareInterface
|
|||||||
): bool {
|
): bool {
|
||||||
$role = $user->getRole();
|
$role = $user->getRole();
|
||||||
$permissions = $role->getPermissions();
|
$permissions = $role->getPermissions();
|
||||||
$targetApi = $this->routes[$targetPath];
|
|
||||||
|
$targetApi = $this->routes[$targetPath] ?? null;
|
||||||
|
|
||||||
/** @var Permission $permission */
|
/** @var Permission $permission */
|
||||||
foreach($permissions as $permission) {
|
foreach($permissions as $permission) {
|
||||||
|
|||||||
@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
|
use Template\Infrastructure\Rbac\Middleware\EnsureAuthorizationMiddleware;
|
||||||
use Template\Infrastructure\Request\Factory\RequestServiceFactory;
|
use Template\Infrastructure\Request\Factory\RequestServiceFactory;
|
||||||
use Template\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
|
use Template\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
|
||||||
use Template\Infrastructure\Request\Middleware\AnalyzeHeaderMiddleware;
|
use Template\Infrastructure\Request\Middleware\AnalyzeHeaderMiddleware;
|
||||||
use Template\Infrastructure\Request\Middleware\InternalRequestMiddleware;
|
use Template\Infrastructure\Request\Middleware\InternalRequestMiddleware;
|
||||||
use Template\Infrastructure\Request\Service\RequestService;
|
use Template\Infrastructure\Request\Service\RequestService;
|
||||||
|
use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
|
||||||
|
use Reinfi\DependencyInjection\Factory\InjectionFactory;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'factories' => [
|
'factories' => [
|
||||||
|
|||||||
@ -18,7 +18,7 @@ class RequestServiceFactory implements FactoryInterface
|
|||||||
{
|
{
|
||||||
/** @var ConfigService $configService */
|
/** @var ConfigService $configService */
|
||||||
$configService = $container->get(ConfigService::class);
|
$configService = $container->get(ConfigService::class);
|
||||||
$apiKey = $configService->resolve("api.keys.template");
|
$apiKey = $configService->resolve("api.keys.Template");
|
||||||
|
|
||||||
return new RequestService(
|
return new RequestService(
|
||||||
$apiKey,
|
$apiKey,
|
||||||
|
|||||||
@ -2,16 +2,10 @@
|
|||||||
|
|
||||||
namespace Template\Infrastructure\Request\Middleware;
|
namespace Template\Infrastructure\Request\Middleware;
|
||||||
|
|
||||||
use Template\Data\Business\Entity\Permission;
|
|
||||||
use Template\Data\Business\Entity\User;
|
|
||||||
use Template\Infrastructure\Response\ForbiddenResponse;
|
|
||||||
use Template\Infrastructure\Session\Middleware\LoggedInUserMiddleware;
|
|
||||||
use Laminas\Config\Config;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Server\MiddlewareInterface;
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
use Reinfi\DependencyInjection\Annotation\InjectConfig;
|
|
||||||
|
|
||||||
class AnalyzeBodyMiddleware implements MiddlewareInterface
|
class AnalyzeBodyMiddleware implements MiddlewareInterface
|
||||||
{
|
{
|
||||||
@ -33,6 +27,8 @@ class AnalyzeBodyMiddleware implements MiddlewareInterface
|
|||||||
$request->getBody()->getContents(),
|
$request->getBody()->getContents(),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$request->getBody()->rewind();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $handler->handle($request->withAttribute(
|
return $handler->handle($request->withAttribute(
|
||||||
|
|||||||
@ -2,36 +2,47 @@
|
|||||||
|
|
||||||
namespace Template\Infrastructure\Request\Middleware;
|
namespace Template\Infrastructure\Request\Middleware;
|
||||||
|
|
||||||
use Template\Data\Business\Entity\Permission;
|
|
||||||
use Template\Data\Business\Entity\User;
|
|
||||||
use Template\Infrastructure\Response\ForbiddenResponse;
|
|
||||||
use Template\Infrastructure\Session\Middleware\LoggedInUserMiddleware;
|
|
||||||
use Laminas\Config\Config;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Server\MiddlewareInterface;
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
use Reinfi\DependencyInjection\Annotation\InjectConfig;
|
|
||||||
|
|
||||||
class AnalyzeHeaderMiddleware implements MiddlewareInterface
|
class AnalyzeHeaderMiddleware implements MiddlewareInterface
|
||||||
{
|
{
|
||||||
const HOST_ATTRIBUTE = 'host_attribute';
|
public const HOST_ATTRIBUTE = 'host_attribute';
|
||||||
|
public const CONTENT_TYPE_ATTRIBUTE = 'content_type_attribute';
|
||||||
|
|
||||||
|
private const CONTENT_TYPE_HEADER_KEY = 'Content-Type';
|
||||||
|
private const HOST_HEADER_KEY = 'host';
|
||||||
|
private const FORWARDED_HOST_HEADER_KEY = 'x-forwarded-host';
|
||||||
|
|
||||||
public function process(
|
public function process(
|
||||||
ServerRequestInterface $request,
|
ServerRequestInterface $request,
|
||||||
RequestHandlerInterface $handler
|
RequestHandlerInterface $handler
|
||||||
): ResponseInterface
|
): ResponseInterface
|
||||||
{
|
{
|
||||||
|
$headers = $request->getHeaders();
|
||||||
|
|
||||||
|
$contentType = $this->readHeader($headers, self::CONTENT_TYPE_HEADER_KEY) ?? null;
|
||||||
$host = explode(
|
$host = explode(
|
||||||
':',
|
':',
|
||||||
$request->getHeaders()['host'][0]
|
$this->readHeader($headers, self::HOST_HEADER_KEY) ??
|
||||||
?? $request->getHeaders()['x-forwarded-host'][0]
|
$this->readHeader($headers, self::FORWARDED_HOST_HEADER_KEY) ??
|
||||||
?? 'UNKNOWN'
|
'UNKNOWN'
|
||||||
)[0];
|
)[0];
|
||||||
|
|
||||||
return $handler->handle($request->withAttribute(
|
return $handler->handle(
|
||||||
self::HOST_ATTRIBUTE,
|
$request
|
||||||
$host
|
->withAttribute(self::HOST_ATTRIBUTE, $host)
|
||||||
));
|
->withAttribute(self::CONTENT_TYPE_ATTRIBUTE, $contentType)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function readHeader(array $header, string $key): mixed
|
||||||
|
{
|
||||||
|
return
|
||||||
|
($header[$key] ??
|
||||||
|
$header[strtolower($key)] ??
|
||||||
|
$header[strtoupper($key)] ?? [])[0] ?? null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7,9 +7,13 @@ use Laminas\Diactoros\Response\JsonResponse;
|
|||||||
class SuccessResponse extends JsonResponse
|
class SuccessResponse extends JsonResponse
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
mixed $data = 'OK'
|
mixed $data = 'Success'
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
if ($data === []) {
|
||||||
|
$data = 'Success';
|
||||||
|
}
|
||||||
|
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
$data
|
$data
|
||||||
);
|
);
|
||||||
|
|||||||
41
src/Infrastructure/Schema/config/service_manager.php
Normal file
41
src/Infrastructure/Schema/config/service_manager.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use cebe\openapi\spec\OpenApi;
|
||||||
|
use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Backend\Builder\ClassFileBuilder;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\BackendExporter;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Frontend\Builder\PropertyBuilder as FrontendPropertyBuilder;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Backend\Builder\PropertyBuilder as BackendPropertyBuilder;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Frontend\Builder\ServiceBuilder;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Frontend\Builder\TypeBuilder as FrontendTypeBuilder;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Backend\Builder\TypeBuilder as BackendTypeBuilder;;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\FrontendExporter;
|
||||||
|
use Template\Infrastructure\Schema\Factory\OpenApiFactory;
|
||||||
|
use Template\Infrastructure\Schema\Middleware\SchemaValidationMiddleware;
|
||||||
|
use Template\Infrastructure\Schema\Reader\ApiDefinitionReader;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'factories' => [
|
||||||
|
OpenApi::class => OpenApiFactory::class,
|
||||||
|
|
||||||
|
/// Reader
|
||||||
|
ApiDefinitionReader::class => AutoWiringFactory::class,
|
||||||
|
|
||||||
|
/// Middleware
|
||||||
|
SchemaValidationMiddleware::class => AutoWiringFactory::class,
|
||||||
|
|
||||||
|
/// Exporter
|
||||||
|
// Backend
|
||||||
|
BackendExporter::class => AutoWiringFactory::class,
|
||||||
|
ClassFileBuilder::class => AutoWiringFactory::class,
|
||||||
|
BackendPropertyBuilder::class => AutoWiringFactory::class,
|
||||||
|
BackendTypeBuilder::class => AutoWiringFactory::class,
|
||||||
|
// Frontend
|
||||||
|
FrontendExporter::class => AutoWiringFactory::class,
|
||||||
|
ServiceBuilder::class => AutoWiringFactory::class,
|
||||||
|
FrontendTypeBuilder::class => AutoWiringFactory::class,
|
||||||
|
FrontendPropertyBuilder::class => AutoWiringFactory::class,
|
||||||
|
],
|
||||||
|
];
|
||||||
15
src/Infrastructure/Schema/src/ConfigProvider.php
Normal file
15
src/Infrastructure/Schema/src/ConfigProvider.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema;
|
||||||
|
|
||||||
|
class ConfigProvider
|
||||||
|
{
|
||||||
|
public function __invoke(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'dependencies' => require __DIR__ . './../config/service_manager.php',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/Infrastructure/Schema/src/Enum/Method.php
Normal file
8
src/Infrastructure/Schema/src/Enum/Method.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Enum;
|
||||||
|
|
||||||
|
enum Method : string {
|
||||||
|
case post = 'post';
|
||||||
|
case get = 'get';
|
||||||
|
}
|
||||||
13
src/Infrastructure/Schema/src/Enum/SchemaType.php
Normal file
13
src/Infrastructure/Schema/src/Enum/SchemaType.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Enum;
|
||||||
|
|
||||||
|
enum SchemaType : string {
|
||||||
|
case object = 'object';
|
||||||
|
case array = 'array';
|
||||||
|
case string = 'string';
|
||||||
|
case number = 'number';
|
||||||
|
case integer = 'integer';
|
||||||
|
case boolean = 'boolean';
|
||||||
|
case any = 'any';
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Exception;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class FileNotFoundException extends Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Exception;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class UnsupportedContentTypeException extends Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Exporter\Backend\Builder;
|
||||||
|
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Backend\Model\ClassFile;
|
||||||
|
use Template\Infrastructure\Schema\Model\Api;
|
||||||
|
|
||||||
|
class ClassFileBuilder
|
||||||
|
{
|
||||||
|
public function build(Api $api, string $domain, string $type, string $namespace, string $directory): ClassFile
|
||||||
|
{
|
||||||
|
if(in_array($type, ["UseCase", "UseCaseResult", "UseCaseHandler", "UseCaseBuilder", "Request", "Response", "ResponseFormatter", "RequestHandler"])) {
|
||||||
|
$name = $api->getUseCase() . $type;
|
||||||
|
} else {
|
||||||
|
$name = $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_exists($directory)) {
|
||||||
|
mkdir(
|
||||||
|
directory: $directory,
|
||||||
|
recursive: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ClassFile(
|
||||||
|
$api,
|
||||||
|
$domain,
|
||||||
|
$name,
|
||||||
|
$namespace,
|
||||||
|
$directory,
|
||||||
|
$directory . $name . '.php',
|
||||||
|
$type,
|
||||||
|
[],
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Exporter\Backend\Builder;
|
||||||
|
|
||||||
|
use Laminas\ServiceManager\ServiceManager;
|
||||||
|
use Template\Infrastructure\Schema\Enum\SchemaType;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Backend\Model\ClassFile;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Backend\Model\Property;
|
||||||
|
use Template\Infrastructure\Schema\Model\Type;
|
||||||
|
|
||||||
|
class PropertyBuilder
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ServiceManager $serviceManager,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function build(ClassFile $parent, string $name, Type $type): Property {
|
||||||
|
$nullableFlag = ($type->getIsNullable() ?? false) ? '?' : '';
|
||||||
|
|
||||||
|
$normalizedType = $this->normalizeBackendType($parent, $name, $type);
|
||||||
|
$types = [$normalizedType['normalizedType']];
|
||||||
|
|
||||||
|
/*
|
||||||
|
if ($type->getEnum() !== null) {
|
||||||
|
$format = match ($type->getType()) {
|
||||||
|
SchemaType::string => '"%s"',
|
||||||
|
default => "%s"
|
||||||
|
};
|
||||||
|
|
||||||
|
$types = [];
|
||||||
|
foreach ($type->getEnum() as $enumValue) {
|
||||||
|
$types[] = sprintf($format, $enumValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return new Property(
|
||||||
|
$type,
|
||||||
|
$name,
|
||||||
|
sprintf("%s%s", $nullableFlag, implode('|', $types)),
|
||||||
|
$normalizedType['backendType'],
|
||||||
|
$normalizedType['useUsings'],
|
||||||
|
$normalizedType['createUsings'],
|
||||||
|
$normalizedType['format']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function normalizeBackendType(ClassFile $parent, string $name, Type $type) : array
|
||||||
|
{
|
||||||
|
$definition = null;
|
||||||
|
$createUsings = [];
|
||||||
|
$useUsings = [];
|
||||||
|
|
||||||
|
switch ($type->getType()) {
|
||||||
|
case SchemaType::boolean:
|
||||||
|
$normalizedType = 'bool';
|
||||||
|
break;
|
||||||
|
case SchemaType::integer:
|
||||||
|
$normalizedType = 'int';
|
||||||
|
break;
|
||||||
|
case SchemaType::number:
|
||||||
|
switch ($type->getFormat()) {
|
||||||
|
case 'float': $normalizedType = 'float'; break;
|
||||||
|
default: $normalizedType = 'NUMBER';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SchemaType::string:
|
||||||
|
switch ($type->getFormat()) {
|
||||||
|
case 'uuid':
|
||||||
|
$normalizedType = 'UuidInterface';
|
||||||
|
$createUsings = ['Ramsey\Uuid\Uuid'];
|
||||||
|
$useUsings = ['Ramsey\Uuid\UuidInterface'];
|
||||||
|
$format = "Uuid::fromString(%s)";
|
||||||
|
break;
|
||||||
|
case 'date-time':
|
||||||
|
$normalizedType = 'DateTimeImmutable';
|
||||||
|
$createUsings = ['DateTimeImmutable'];
|
||||||
|
$useUsings = ['DateTimeImmutable'];
|
||||||
|
$format = "new DateTimeImmutable(%s)";
|
||||||
|
break;
|
||||||
|
default: $normalizedType = 'string';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SchemaType::object:
|
||||||
|
/** @var TypeBuilder $typeBuilder */
|
||||||
|
$typeBuilder = $this->serviceManager->get(TypeBuilder::class);
|
||||||
|
//$backendType = $typeBuilder->build($parent, ucfirst($name), $type);
|
||||||
|
$normalizedType = "any";//$backendType->getName();
|
||||||
|
break;
|
||||||
|
case SchemaType::array:
|
||||||
|
$singular = ucfirst(substr($name, 0, strlen($name) - 1));
|
||||||
|
/** @var TypeBuilder $typeBuilder */
|
||||||
|
//$typeBuilder = $this->serviceManager->get(TypeBuilder::class);
|
||||||
|
//$backendType = $typeBuilder->build($parent, $singular, $type->getItemType());
|
||||||
|
$normalizedType = "array";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$normalizedType = $type->getType()->value;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
return [
|
||||||
|
'normalizedType' => $normalizedType,
|
||||||
|
'backendType' => $backendType ?? null,
|
||||||
|
'format' => $format ?? '%s',
|
||||||
|
'useUsings' => $useUsings ?? [],
|
||||||
|
'createUsings' => $createUsings ?? [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Exporter\Backend\Builder;
|
||||||
|
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Backend\Model\ClassFile;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Backend\Model\Property;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Backend\Model\Type;
|
||||||
|
use Template\Infrastructure\Schema\Model\Type as SchemaType;
|
||||||
|
|
||||||
|
class TypeBuilder
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly PropertyBuilder $propertyBuilder,
|
||||||
|
private readonly ClassFileBuilder $classFileBuilder,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function build(ClassFile $parent, string $name, SchemaType $type, ?ClassFile $root = null): Type
|
||||||
|
{
|
||||||
|
if ($root === null) {
|
||||||
|
$name = implode([$name]);
|
||||||
|
} else {
|
||||||
|
$name = implode([$parent->getName(), $name]);
|
||||||
|
}
|
||||||
|
$propertyDefinitions = [];
|
||||||
|
|
||||||
|
$newClassFile = $this->classFileBuilder->build(
|
||||||
|
$parent->getApi(),
|
||||||
|
$parent->getDomain(),
|
||||||
|
$name,
|
||||||
|
$parent->getNamespace() . (($root === null) ? ($parent->getType() . '\\') : ''),
|
||||||
|
$parent->getDirectory() . (($root === null) ? ($parent->getType() . '/') : ''),
|
||||||
|
);
|
||||||
|
|
||||||
|
$usings = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string $propertyName
|
||||||
|
* @var SchemaType $type
|
||||||
|
*/
|
||||||
|
foreach (($type->getProperties() ?? []) as $propertyName => $type) {
|
||||||
|
$propertyDefinition = $this->propertyBuilder->build($newClassFile, $propertyName, $type);
|
||||||
|
$usings = array_merge($usings, $propertyDefinition->getUseUsings());
|
||||||
|
$propertyDefinitions[] = $propertyDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = [];
|
||||||
|
$lines[] = "public function __construct(";
|
||||||
|
/** @var Property $propertyDefinition */
|
||||||
|
foreach ($propertyDefinitions as $propertyDefinition) {
|
||||||
|
$lines[] = "\tprivate readonly " . $propertyDefinition->getType() . " $" . $propertyDefinition->getName() . ',';
|
||||||
|
}
|
||||||
|
$lines[] = ") {";
|
||||||
|
$lines[] = "}";
|
||||||
|
/** @var Property $propertyDefinition */
|
||||||
|
foreach ($propertyDefinitions as $propertyDefinition) {
|
||||||
|
$lines[] = null;
|
||||||
|
$lines[] = "public function get" . ucfirst($propertyDefinition->getName()) . '(): ' .$propertyDefinition->getType();
|
||||||
|
$lines[] = "{";
|
||||||
|
$lines[] = "\treturn \$this->{$propertyDefinition->getName()};";
|
||||||
|
$lines[] = "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
$newClassFile->setDefinition(
|
||||||
|
implode(
|
||||||
|
PHP_EOL,
|
||||||
|
array_map(
|
||||||
|
function (?string $line) { return $line === null ? '' : "\t" . $line; },
|
||||||
|
$lines
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$newClassFile->setPredefinedUsings($usings);
|
||||||
|
|
||||||
|
return new Type(
|
||||||
|
$name,
|
||||||
|
$newClassFile,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Exporter\Backend\Model;
|
||||||
|
|
||||||
|
use Template\Infrastructure\Schema\Model\Api;
|
||||||
|
|
||||||
|
class ClassFile
|
||||||
|
{
|
||||||
|
private bool $finalized = false;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly Api $api,
|
||||||
|
private readonly string $domain,
|
||||||
|
private readonly string $name,
|
||||||
|
private readonly string $namespace,
|
||||||
|
private readonly string $directory,
|
||||||
|
private readonly string $path,
|
||||||
|
private readonly string $type,
|
||||||
|
private array $predefinedUsings,
|
||||||
|
private ?string $definition,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isFinalized(): bool
|
||||||
|
{
|
||||||
|
return $this->finalized;
|
||||||
|
}
|
||||||
|
public function setFinalized(bool $finalized): void
|
||||||
|
{
|
||||||
|
$this->finalized = $finalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function getApi(): Api
|
||||||
|
{
|
||||||
|
return $this->api;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDomain(): string
|
||||||
|
{
|
||||||
|
return $this->domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNamespace(): string
|
||||||
|
{
|
||||||
|
return $this->namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDirectory(): string
|
||||||
|
{
|
||||||
|
return $this->directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPath(): string
|
||||||
|
{
|
||||||
|
return $this->path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPredefinedUsings(): array
|
||||||
|
{
|
||||||
|
return $this->predefinedUsings;
|
||||||
|
}
|
||||||
|
public function setPredefinedUsings(array $pred): void
|
||||||
|
{
|
||||||
|
$this->predefinedUsings = $pred;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDefinition(): ?string
|
||||||
|
{
|
||||||
|
return $this->definition;
|
||||||
|
}
|
||||||
|
public function setDefinition(?string $definition): void
|
||||||
|
{
|
||||||
|
$this->definition = $definition;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Exporter\Backend\Model;
|
||||||
|
|
||||||
|
use Template\Infrastructure\Schema\Model\Type as SchemaType;
|
||||||
|
|
||||||
|
class Property
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly SchemaType $schemaType,
|
||||||
|
private readonly string $name,
|
||||||
|
private readonly string $type,
|
||||||
|
private readonly ?Type $subtypeDefinition,
|
||||||
|
private readonly array $useUsings,
|
||||||
|
private readonly array $createUsings,
|
||||||
|
private readonly string $format,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSchemaType(): SchemaType
|
||||||
|
{
|
||||||
|
return $this->schemaType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubtype(): ?Type
|
||||||
|
{
|
||||||
|
return $this->subtypeDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUseUsings(): array
|
||||||
|
{
|
||||||
|
return $this->useUsings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCreateUsings(): array
|
||||||
|
{
|
||||||
|
return $this->createUsings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFormat(): string
|
||||||
|
{
|
||||||
|
return $this->format;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Exporter\Backend\Model;
|
||||||
|
|
||||||
|
class Type
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $name,
|
||||||
|
private readonly ClassFile $file
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFile(): ClassFile
|
||||||
|
{
|
||||||
|
return $this->file;
|
||||||
|
}
|
||||||
|
}
|
||||||
581
src/Infrastructure/Schema/src/Exporter/BackendExporter.php
Normal file
581
src/Infrastructure/Schema/src/Exporter/BackendExporter.php
Normal file
@ -0,0 +1,581 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Exporter;
|
||||||
|
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Backend\Builder\ClassFileBuilder;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Backend\Builder\PropertyBuilder;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Backend\Builder\TypeBuilder;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Backend\Model\ClassFile;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Backend\Model\Property;
|
||||||
|
use Template\Infrastructure\Schema\Model\Api;
|
||||||
|
use Template\Infrastructure\Schema\Model\Type as SchemaType;
|
||||||
|
use Template\Infrastructure\Schema\Reader\ApiDefinitionReader;
|
||||||
|
|
||||||
|
class BackendExporter
|
||||||
|
{
|
||||||
|
private const BASE_NAMESPACE = 'Template';
|
||||||
|
|
||||||
|
private const AUTO_WIRING_FACTORY = 'Reinfi\DependencyInjection\Factory\AutoWiringFactory';
|
||||||
|
private const INJECTION_FACTORY = 'Reinfi\DependencyInjection\Factory\InjectionFactory';
|
||||||
|
|
||||||
|
private const ENSURE_AUTHORIZATION_MIDDLEWARE = 'Template\Infrastructure\Rbac\Middleware\EnsureAuthorizationMiddleware';
|
||||||
|
private const SCHEMA_VALIDATION_MIDDLEWARE = 'Template\Infrastructure\Schema\Middleware\SchemaValidationMiddleware';
|
||||||
|
private const LOGGED_IN_USER_MIDDLEWARE = 'Template\Infrastructure\Session\Middleware\LoggedInUserMiddleware';
|
||||||
|
|
||||||
|
private const SERVICE_MANAGER_USINGS = [
|
||||||
|
self::AUTO_WIRING_FACTORY,
|
||||||
|
self::INJECTION_FACTORY,
|
||||||
|
];
|
||||||
|
|
||||||
|
private const ROUTER_USINGS = [
|
||||||
|
self::ENSURE_AUTHORIZATION_MIDDLEWARE,
|
||||||
|
self::SCHEMA_VALIDATION_MIDDLEWARE,
|
||||||
|
self::LOGGED_IN_USER_MIDDLEWARE,
|
||||||
|
];
|
||||||
|
|
||||||
|
private const DOMAIN_TYPE_API = 'Api';
|
||||||
|
private const DOMAIN_TYPE_HANDLING = 'Handling';
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly ApiDefinitionReader $apiDefinitionReader,
|
||||||
|
private readonly ClassFileBuilder $fileBuilder,
|
||||||
|
private readonly PropertyBuilder $propertyBuilder,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function export(string $exportRootDirectory = APP_ROOT . "/var/export/schema/be-models/"): void
|
||||||
|
{
|
||||||
|
$namespaces = $this->apiDefinitionReader->read("/api/location");
|
||||||
|
|
||||||
|
$files = [];
|
||||||
|
foreach ($namespaces as $namespace => $apis) {
|
||||||
|
$namespaceFiles = [];
|
||||||
|
$apiDomainNamespace = self::BASE_NAMESPACE . '\\API\\External\\' . ucfirst($namespace) . '\\';
|
||||||
|
$apiDomainDirectory = $exportRootDirectory . 'ApiDomain/External/' . ucfirst($namespace) . '/';
|
||||||
|
$apiDomainDirectorySource = $apiDomainDirectory . 'src/';
|
||||||
|
$apiDomainDirectoryConfig = $apiDomainDirectory . 'config/';
|
||||||
|
|
||||||
|
$handlingDomainNamespace = self::BASE_NAMESPACE . '\\Handling\\' . ucfirst($namespace) . '\\';
|
||||||
|
$handlingDomainDirectory = $exportRootDirectory . 'HandlingDomain/' . ucfirst($namespace) . '/';
|
||||||
|
$handlingDomainDirectorySource = $handlingDomainDirectory . 'src/';
|
||||||
|
$handlingDomainDirectoryConfig = $handlingDomainDirectory . 'config/';
|
||||||
|
|
||||||
|
/** @var Api $api */
|
||||||
|
foreach ($apis as $api) {
|
||||||
|
$useCaseFiles = [];
|
||||||
|
$useCase = $api->getUseCase();
|
||||||
|
|
||||||
|
$useCaseHandlingNamespace = $handlingDomainNamespace . 'UseCase\\' . $useCase . '\\';
|
||||||
|
$useCaseHandlingDirectory = $handlingDomainDirectorySource . 'UseCase/' . $useCase . '/';
|
||||||
|
$useCaseFiles[] = $this->fileBuilder->build($api, self::DOMAIN_TYPE_HANDLING, 'UseCase', $useCaseHandlingNamespace, $useCaseHandlingDirectory);
|
||||||
|
$useCaseFiles[] = $this->fileBuilder->build($api, self::DOMAIN_TYPE_HANDLING, 'UseCaseResult', $useCaseHandlingNamespace, $useCaseHandlingDirectory);
|
||||||
|
$useCaseFiles[] = $this->fileBuilder->build($api, self::DOMAIN_TYPE_HANDLING, 'UseCaseHandler', $useCaseHandlingNamespace, $useCaseHandlingDirectory);
|
||||||
|
$useCaseFiles[] = $this->fileBuilder->build($api, self::DOMAIN_TYPE_HANDLING, 'UseCaseBuilder', $useCaseHandlingNamespace, $useCaseHandlingDirectory);
|
||||||
|
|
||||||
|
$useCaseSeparatedNamespace = $apiDomainNamespace . $useCase . '\\';
|
||||||
|
$useCaseSeparatedDirectory = $apiDomainDirectorySource . $useCase . '/';
|
||||||
|
|
||||||
|
$apisAreUseCaseSeparated = false;
|
||||||
|
|
||||||
|
$responseFormatterNamespace = $apisAreUseCaseSeparated ? $useCaseSeparatedNamespace : $apiDomainNamespace . 'ResponseFormatter\\';
|
||||||
|
$responseFormatterDirectory = $apisAreUseCaseSeparated ? $useCaseSeparatedDirectory : $apiDomainDirectorySource . 'ResponseFormatter/';
|
||||||
|
$handlerNamespace = $apisAreUseCaseSeparated ? $useCaseSeparatedNamespace : $apiDomainNamespace . 'Handler\\';
|
||||||
|
$handlerDirectory = $apisAreUseCaseSeparated ? $useCaseSeparatedDirectory : $apiDomainDirectorySource . 'Handler/';
|
||||||
|
|
||||||
|
$useCaseFiles[] = $this->fileBuilder->build($api, self::DOMAIN_TYPE_API, 'ResponseFormatter', $responseFormatterNamespace, $responseFormatterDirectory);
|
||||||
|
$useCaseFiles[] = $this->fileBuilder->build($api, self::DOMAIN_TYPE_API, 'RequestHandler', $handlerNamespace, $handlerDirectory);
|
||||||
|
|
||||||
|
foreach ($useCaseFiles as $useCaseFile) {
|
||||||
|
$this->generateFileContent($useCaseFile, $useCaseFiles);
|
||||||
|
$namespaceFiles[] = $useCaseFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$namespaceFiles[] = $this->fileBuilder->build($api, self::DOMAIN_TYPE_HANDLING, 'ConfigProvider', $handlingDomainNamespace, $handlingDomainDirectorySource);
|
||||||
|
$namespaceFiles[] = $this->fileBuilder->build($api, self::DOMAIN_TYPE_HANDLING, 'service_manager', $handlingDomainNamespace, $handlingDomainDirectoryConfig);
|
||||||
|
|
||||||
|
$namespaceFiles[] = $this->fileBuilder->build($api, self::DOMAIN_TYPE_API,'ConfigProvider', $apiDomainNamespace, $apiDomainDirectorySource);
|
||||||
|
$namespaceFiles[] = $this->fileBuilder->build($api, self::DOMAIN_TYPE_API,'service_manager', $apiDomainNamespace, $apiDomainDirectoryConfig);
|
||||||
|
$namespaceFiles[] = $this->fileBuilder->build($api, self::DOMAIN_TYPE_API,'routes', $apiDomainNamespace, $apiDomainDirectoryConfig);
|
||||||
|
|
||||||
|
|
||||||
|
foreach ($namespaceFiles as $namespaceFile) {
|
||||||
|
$this->generateFileContent($namespaceFile, $namespaceFiles);
|
||||||
|
$files[] = $namespaceFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($files as $file) {
|
||||||
|
file_put_contents(
|
||||||
|
$file->getPath(),
|
||||||
|
$file->getDefinition()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getFilesOfType(array $files, array $types) : array
|
||||||
|
{
|
||||||
|
$return = [];
|
||||||
|
|
||||||
|
/** @var ClassFile $file */
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if (in_array($file->getType(), $types)) {
|
||||||
|
$return[] = $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateFileContent(ClassFile $file, array $files): void
|
||||||
|
{
|
||||||
|
if ($file->isFinalized())
|
||||||
|
return;
|
||||||
|
|
||||||
|
$fileDefinition = $this->getFileDefinition($file, $files);
|
||||||
|
$usings = $fileDefinition['usings'];
|
||||||
|
$isClass = $fileDefinition['isClass'];
|
||||||
|
$inheritance = $fileDefinition['inheritance'];
|
||||||
|
$content = $fileDefinition['content'];
|
||||||
|
|
||||||
|
$namespace = substr($file->getNamespace(), 0, strlen($file->getNamespace())-1);
|
||||||
|
$definition = "<?php" . PHP_EOL . PHP_EOL;
|
||||||
|
$definition .= "declare(strict_types=1);" . PHP_EOL . PHP_EOL;
|
||||||
|
$definition .= "namespace {$namespace};" . PHP_EOL . PHP_EOL;
|
||||||
|
foreach (array_unique($usings) as $using) {
|
||||||
|
$definition .= $using . PHP_EOL;
|
||||||
|
}
|
||||||
|
if (count($usings) > 0){
|
||||||
|
$definition .= PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($isClass) {
|
||||||
|
$definition .= "class {$file->getName()}";
|
||||||
|
if($inheritance) {
|
||||||
|
$definition .= ' ' . $inheritance;
|
||||||
|
}
|
||||||
|
$definition .= PHP_EOL;
|
||||||
|
$definition .= "{" . PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$definition .= $content . PHP_EOL;
|
||||||
|
|
||||||
|
if ($isClass) {
|
||||||
|
$definition .= "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
$file->setDefinition($definition);
|
||||||
|
$file->setFinalized(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function getFileDefinition(ClassFile $file, array $files): array
|
||||||
|
{
|
||||||
|
$usingsDefault = [];
|
||||||
|
$usingsFiles = [];
|
||||||
|
$isClass = true;
|
||||||
|
$inheritance = null;
|
||||||
|
|
||||||
|
switch ($file->getType()) {
|
||||||
|
// HandlingDomain:
|
||||||
|
case 'UseCase':
|
||||||
|
$responseContent = $this->getUseCaseContent($file);
|
||||||
|
$content = $responseContent['definition'];
|
||||||
|
break;
|
||||||
|
case 'UseCaseBuilder':
|
||||||
|
$usingsFiles = ['Request'];
|
||||||
|
$content = $this->getUseCaseBuilderContent($files);
|
||||||
|
break;
|
||||||
|
case 'UseCaseHandler':
|
||||||
|
$content = $this->getUseCaseHandlerContent($files);
|
||||||
|
break;
|
||||||
|
case 'UseCaseResult':
|
||||||
|
$content = $this->getUseCaseResultContent($files);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// ApiDomain:
|
||||||
|
case 'RequestHandler':
|
||||||
|
$usingsDefault = [
|
||||||
|
"Psr\Http\Message\ResponseInterface",
|
||||||
|
"Psr\Http\Message\ServerRequestInterface",
|
||||||
|
"Psr\Http\Server\RequestHandlerInterface",
|
||||||
|
"Template\Infrastructure\Response\SuccessResponse",
|
||||||
|
"Template\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware",
|
||||||
|
];
|
||||||
|
$inheritance = "implements RequestHandlerInterface";
|
||||||
|
$usingsFiles = ['UseCaseBuilder', 'UseCaseHandler', 'ResponseFormatter'];
|
||||||
|
$content = $this->getRequestHandlerContent($files);
|
||||||
|
break;
|
||||||
|
case 'ResponseFormatter':
|
||||||
|
$usingsFiles = ['UseCaseResult'];
|
||||||
|
$content = $this->getResponseFormatterContent($files);
|
||||||
|
break;
|
||||||
|
case 'routes':
|
||||||
|
$isClass = false;
|
||||||
|
$usingsDefault = self::ROUTER_USINGS;
|
||||||
|
$usingsFiles = ['RequestHandler'];
|
||||||
|
$content = $this->getRouterContent($files);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// General:
|
||||||
|
case 'ConfigProvider':
|
||||||
|
$content = $this->getContentProviderContent($file->getDomain());
|
||||||
|
break;
|
||||||
|
case 'service_manager':
|
||||||
|
$isClass = false;
|
||||||
|
$usingsDefault = self::SERVICE_MANAGER_USINGS;
|
||||||
|
if ($file->getDomain() === 'Api') {
|
||||||
|
$usingsFiles = ['RequestHandler', 'ResponseFormatter'];
|
||||||
|
}
|
||||||
|
if ($file->getDomain() === 'Handling') {
|
||||||
|
$usingsFiles = ['UseCaseHandler', 'UseCaseBuilder'];
|
||||||
|
}
|
||||||
|
$content = $this->getServiceManagerContent(
|
||||||
|
$this->getFilesOfType($files, $usingsFiles)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Request / Response Types
|
||||||
|
default:
|
||||||
|
$isClass = true;
|
||||||
|
$content = $file->getDefinition();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'isClass' => $isClass,
|
||||||
|
'content' => $content,
|
||||||
|
'inheritance' => $inheritance,
|
||||||
|
'usings' => array_map(
|
||||||
|
function ($using) { return "use {$using};";},
|
||||||
|
array_merge(
|
||||||
|
$file->getPredefinedUsings(),
|
||||||
|
$usingsDefault,
|
||||||
|
array_map(
|
||||||
|
function (ClassFile $file) { return $file->getNamespace() . $file->getName(); },
|
||||||
|
$this->getFilesOfType($files, $usingsFiles)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUseCaseContent(ClassFile $file): array
|
||||||
|
{
|
||||||
|
$usings = [];
|
||||||
|
$properties = ($file->getApi()->getRequest()?->getProperties() ?? []);
|
||||||
|
|
||||||
|
$propertyDefinitions = [];
|
||||||
|
$propertyUsings = [];
|
||||||
|
/**
|
||||||
|
* @var string $name
|
||||||
|
* @var SchemaType $type
|
||||||
|
*/
|
||||||
|
foreach ($properties as $name => $type) {
|
||||||
|
$propertyDefinition = $this->propertyBuilder->build($file, $name, $type);
|
||||||
|
$propertyDefinitions[] = $propertyDefinition;
|
||||||
|
$propertyUsings = array_merge($propertyUsings, $propertyDefinition->getUseUsings());
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = [];
|
||||||
|
$lines[] = "public function __construct(";
|
||||||
|
/** @var Property $propertyDefinition */
|
||||||
|
foreach ($propertyDefinitions as $propertyDefinition) {
|
||||||
|
$lines[] = "\tprivate readonly " . $propertyDefinition->getType() . " $" . $propertyDefinition->getName() . ',';
|
||||||
|
}
|
||||||
|
$lines[] = ") {";
|
||||||
|
$lines[] = "}";
|
||||||
|
/** @var Property $propertyDefinition */
|
||||||
|
foreach ($propertyDefinitions as $propertyDefinition) {
|
||||||
|
$lines[] = null;
|
||||||
|
$lines[] = "public function get" . ucfirst($propertyDefinition->getName()) . '(): ' .$propertyDefinition->getType();
|
||||||
|
$lines[] = "{";
|
||||||
|
$lines[] = "\treturn \$this->{$propertyDefinition->getName()};";
|
||||||
|
$lines[] = "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
$file->setPredefinedUsings($propertyUsings);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'definition' => implode(
|
||||||
|
PHP_EOL,
|
||||||
|
array_map(
|
||||||
|
function (?string $line) { return $line === null ? '' : "\t" . $line; },
|
||||||
|
$lines
|
||||||
|
)
|
||||||
|
),
|
||||||
|
'usings' => $usings,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUseCaseBuilderContent(array $files): string
|
||||||
|
{
|
||||||
|
/** @var ClassFile $builder */
|
||||||
|
$builder = $this->getFilesOfType($files, ['UseCaseBuilder'])[0];
|
||||||
|
|
||||||
|
/** @var ClassFile $cqrs */
|
||||||
|
$cqrs = $this->getFilesOfType($files, ['UseCase'])[0];
|
||||||
|
|
||||||
|
|
||||||
|
$properties = ($cqrs->getApi()->getRequest()?->getProperties() ?? []);
|
||||||
|
|
||||||
|
$propertyDefinitions = [];
|
||||||
|
$usings = [];
|
||||||
|
/**
|
||||||
|
* @var string $name
|
||||||
|
* @var SchemaType $type
|
||||||
|
*/
|
||||||
|
foreach ($properties as $name => $type) {
|
||||||
|
$propertyDefinition = $this->propertyBuilder->build($cqrs, $name, $type);
|
||||||
|
$propertyDefinitions[] = $propertyDefinition;
|
||||||
|
|
||||||
|
$usings = array_merge($usings, $propertyDefinition->getUseUsings());
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = [];
|
||||||
|
$lines[] = "public function build(";
|
||||||
|
foreach ($propertyDefinitions as $propertyDefinition) {
|
||||||
|
$lines[] = "\t" . $propertyDefinition->getType() . " $" . $propertyDefinition->getName() . ',';
|
||||||
|
}
|
||||||
|
$lines[] = "): {$cqrs->getName()} {";
|
||||||
|
$lines[] = "\treturn new {$cqrs->getName()}(";
|
||||||
|
foreach ($propertyDefinitions as $propertyDefinition) {
|
||||||
|
$lines[] = "\t\t\${$propertyDefinition->getName()},";
|
||||||
|
}
|
||||||
|
$lines[] = "\t);";
|
||||||
|
$lines[] = "}";
|
||||||
|
|
||||||
|
$builder->setPredefinedUsings($usings);
|
||||||
|
|
||||||
|
return implode(
|
||||||
|
PHP_EOL,
|
||||||
|
array_map(
|
||||||
|
function (?string $line) { return $line === null ? '' : "\t" . $line; },
|
||||||
|
$lines
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUseCaseResultContent(array $files): string
|
||||||
|
{
|
||||||
|
$lines[] = "public function __construct(";
|
||||||
|
$lines[] = ") {";
|
||||||
|
$lines[] = "}";
|
||||||
|
|
||||||
|
return implode(
|
||||||
|
PHP_EOL,
|
||||||
|
array_map(
|
||||||
|
function (?string $line) { return $line === null ? '' : "\t" . $line; },
|
||||||
|
$lines
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUseCaseHandlerContent(array $files): string
|
||||||
|
{
|
||||||
|
/** @var ClassFile $result */
|
||||||
|
$result = $this->getFilesOfType($files, ['UseCaseResult'])[0];
|
||||||
|
|
||||||
|
/** @var ClassFile $cqrs */
|
||||||
|
$cqrs = $this->getFilesOfType($files, ['UseCase'])[0];
|
||||||
|
|
||||||
|
$lines = [];
|
||||||
|
|
||||||
|
$lines[] = "public function __construct(";
|
||||||
|
$lines[] = ") {";
|
||||||
|
$lines[] = "}";
|
||||||
|
$lines[] = null;
|
||||||
|
$lines[] = "public function handle({$cqrs->getName()} \$useCase): {$result->getName()}";
|
||||||
|
$lines[] = "{";
|
||||||
|
$lines[] = "\treturn new {$result->getName()}();";
|
||||||
|
$lines[] = "}";
|
||||||
|
|
||||||
|
return implode(
|
||||||
|
PHP_EOL,
|
||||||
|
array_map(
|
||||||
|
function (?string $line) { return $line === null ? '' : "\t" . $line; },
|
||||||
|
$lines
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getResponseFormatterContent(array $files): string
|
||||||
|
{
|
||||||
|
/** @var ClassFile $result */
|
||||||
|
$result = $this->getFilesOfType($files, ['UseCaseResult'])[0];
|
||||||
|
|
||||||
|
/** @var ClassFile $responseFormatter */
|
||||||
|
$responseFormatter = $this->getFilesOfType($files, ['ResponseFormatter'])[0];
|
||||||
|
|
||||||
|
$propertyDefinitions = [];
|
||||||
|
$propertyUsings = [];
|
||||||
|
/**
|
||||||
|
* @var string $name
|
||||||
|
* @var SchemaType $type
|
||||||
|
*/
|
||||||
|
foreach (($result->getApi()->getResponse()?->getProperties() ?? []) as $name => $type) {
|
||||||
|
$propertyDefinition = $this->propertyBuilder->build($result, $name, $type);
|
||||||
|
$propertyDefinitions[] = $propertyDefinition;
|
||||||
|
$propertyUsings = array_merge($propertyUsings, $propertyDefinition->getCreateUsings());
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = [];
|
||||||
|
|
||||||
|
$lines[] = "public function format({$result->getName()} \$result): array";
|
||||||
|
$lines[] = "{";
|
||||||
|
$lines[] = "\treturn [";
|
||||||
|
foreach ($propertyDefinitions as $propertyDefinition) {
|
||||||
|
$lines[] = "\t\t'{$propertyDefinition->getName()}' => ,";
|
||||||
|
}
|
||||||
|
$lines[] = "\t];";
|
||||||
|
$lines[] = "}";
|
||||||
|
|
||||||
|
$responseFormatter->setPredefinedUsings($propertyUsings);
|
||||||
|
|
||||||
|
return implode(
|
||||||
|
PHP_EOL,
|
||||||
|
array_map(
|
||||||
|
function (?string $line) { return $line === null ? '' : "\t" . $line; },
|
||||||
|
$lines
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getRequestHandlerContent(array $files): string
|
||||||
|
{
|
||||||
|
/** @var ClassFile $requestHandler */
|
||||||
|
$requestHandler = $this->getFilesOfType($files, ['RequestHandler'])[0];
|
||||||
|
|
||||||
|
/** @var ClassFile $useCaseHandler */
|
||||||
|
$useCaseHandler = $this->getFilesOfType($files, ['UseCaseHandler'])[0];
|
||||||
|
|
||||||
|
/** @var ClassFile $useCaseBuilder */
|
||||||
|
$useCaseBuilder = $this->getFilesOfType($files, ['UseCaseBuilder'])[0];
|
||||||
|
|
||||||
|
/** @var ClassFile $responseFormatter */
|
||||||
|
$responseFormatter = $this->getFilesOfType($files, ['ResponseFormatter'])[0];
|
||||||
|
|
||||||
|
$propertyDefinitions = [];
|
||||||
|
$propertyUsings = [];
|
||||||
|
/**
|
||||||
|
* @var string $name
|
||||||
|
* @var SchemaType $type
|
||||||
|
*/
|
||||||
|
foreach (($useCaseHandler->getApi()->getRequest()?->getProperties() ?? []) as $name => $type) {
|
||||||
|
$propertyDefinition = $this->propertyBuilder->build($useCaseHandler, $name, $type);
|
||||||
|
$propertyDefinitions[] = $propertyDefinition;
|
||||||
|
$propertyUsings = array_merge($propertyUsings, $propertyDefinition->getCreateUsings());
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines = [];
|
||||||
|
|
||||||
|
$lines[] = "public function __construct(";
|
||||||
|
$lines[] = "\tprivate readonly {$useCaseBuilder->getName()} \$builder,";
|
||||||
|
$lines[] = "\tprivate readonly {$useCaseHandler->getName()} \$handler,";
|
||||||
|
$lines[] = "\tprivate readonly {$responseFormatter->getName()} \$responseFormatter,";
|
||||||
|
$lines[] = ") {";
|
||||||
|
$lines[] = "}";
|
||||||
|
$lines[] = null;
|
||||||
|
|
||||||
|
$lines[] = "public function handle(ServerRequestInterface \$request): ResponseInterface";
|
||||||
|
$lines[] = "{";
|
||||||
|
$lines[] = "\t\$data = \$request->getAttribute(AnalyzeBodyMiddleware::JSON_DATA);";
|
||||||
|
$lines[] = null;
|
||||||
|
$lines[] = "\t\$useCase = \$this->builder->build(";
|
||||||
|
/** @var Property $propertyDefinition */
|
||||||
|
foreach ($propertyDefinitions as $propertyDefinition) {
|
||||||
|
$value = "\$data['{$propertyDefinition->getName()}']";
|
||||||
|
$lines[] = "\t\t" .sprintf($propertyDefinition->getFormat(), $value) . ',';
|
||||||
|
}
|
||||||
|
$lines[] = "\t);";
|
||||||
|
$lines[] = "\t\$result = \$this->handler->handle(\$useCase);";
|
||||||
|
$lines[] = null;
|
||||||
|
$lines[] = "\t\$response = \$this->responseFormatter->format(\$result);";
|
||||||
|
$lines[] = "\treturn new SuccessResponse(\$response);";
|
||||||
|
$lines[] = "}";
|
||||||
|
|
||||||
|
$requestHandler->setPredefinedUsings($propertyUsings);
|
||||||
|
|
||||||
|
return implode(
|
||||||
|
PHP_EOL,
|
||||||
|
array_map(
|
||||||
|
function (?string $line) { return $line === null ? '' : "\t" . $line; },
|
||||||
|
$lines
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getContentProviderContent(string $domain): string
|
||||||
|
{
|
||||||
|
$lines = [];
|
||||||
|
$lines[] = "public function __invoke(): array";
|
||||||
|
$lines[] = "{";
|
||||||
|
$lines[] = "\treturn [";
|
||||||
|
$lines[] = "\t\t'dependencies' => require __DIR__ . '/./../config/service_manager.php',";
|
||||||
|
|
||||||
|
if ($domain === 'Api') {
|
||||||
|
$lines[] = "\t\t'routes' => require __DIR__ . '/./../config/routes.php',";
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines[] = "\t];";
|
||||||
|
$lines[] = "}";
|
||||||
|
|
||||||
|
return implode(
|
||||||
|
PHP_EOL,
|
||||||
|
array_map(
|
||||||
|
function (?string $line) { return $line === null ? '' : "\t" . $line; },
|
||||||
|
$lines
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getServiceManagerContent(array $files): string
|
||||||
|
{
|
||||||
|
$definition = "return [" . PHP_EOL;
|
||||||
|
$definition .= "\t'factories' => [" . PHP_EOL;
|
||||||
|
|
||||||
|
$lastUseCase = '';
|
||||||
|
|
||||||
|
/** @var ClassFile $file */
|
||||||
|
foreach ($files as $file) {
|
||||||
|
if ($lastUseCase !== $file->getApi()->getUseCase()) {
|
||||||
|
$lastUseCase = $file->getApi()->getUseCase();
|
||||||
|
$definition .= "\t\t/// " . $lastUseCase . PHP_EOL;
|
||||||
|
}
|
||||||
|
$definition .= "\t\t" . $file->getName() . '::class => AutoWiringFactory::class,' . PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$definition .= "\t]" . PHP_EOL;
|
||||||
|
$definition .= "];";
|
||||||
|
return $definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getRouterContent(array $files) : string
|
||||||
|
{
|
||||||
|
$apiHandlerFiles = $this->getFilesOfType($files, ['RequestHandler']);
|
||||||
|
|
||||||
|
$definition = "return [" . PHP_EOL;
|
||||||
|
|
||||||
|
/** @var ClassFile $apiHandlerFile */
|
||||||
|
foreach ($apiHandlerFiles as $apiHandlerFile) {
|
||||||
|
$namespace = lcfirst($apiHandlerFile->getApi()->getNamespace());
|
||||||
|
$method = strtoupper($apiHandlerFile->getApi()->getMethod()->value);
|
||||||
|
$apiHandler = $apiHandlerFile->getName();
|
||||||
|
|
||||||
|
$definition .= "\t[" . PHP_EOL;
|
||||||
|
$definition .= "\t\t'name' => '{$namespace}.{$apiHandlerFile->getApi()->getEndpoint()}'," . PHP_EOL;
|
||||||
|
$definition .= "\t\t'path' => '{$apiHandlerFile->getApi()->getPath()}'," . PHP_EOL;
|
||||||
|
$definition .= "\t\t'allowed_methods' => ['{$method}']," . PHP_EOL;
|
||||||
|
$definition .= "\t\t'middleware' => [" . PHP_EOL;
|
||||||
|
$definition .= "\t\t\tSchemaValidationMiddleware::class," . PHP_EOL;
|
||||||
|
$definition .= "\t\t\tLoggedInUserMiddleware::class," . PHP_EOL;
|
||||||
|
$definition .= "\t\t\tEnsureAuthorizationMiddleware::class," . PHP_EOL;
|
||||||
|
$definition .= "\t\t\t{$apiHandler}::class," . PHP_EOL;
|
||||||
|
$definition .= "\t\t]," . PHP_EOL;
|
||||||
|
$definition .= "\t]," . PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$definition .= "];";
|
||||||
|
return $definition;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Exporter\Frontend\Builder;
|
||||||
|
|
||||||
|
use Laminas\ServiceManager\ServiceManager;
|
||||||
|
use Template\Infrastructure\Schema\Enum\SchemaType;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Frontend\Model\Property;
|
||||||
|
use Template\Infrastructure\Schema\Model\Type;
|
||||||
|
|
||||||
|
class PropertyBuilder
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ServiceManager $serviceManager,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function build(string $parentType, string $name, Type $type): Property {
|
||||||
|
$requiredFlag = ($type->getIsRequired() ?? true) ? '' : '?';
|
||||||
|
|
||||||
|
$normalizedType = $this->normalizeFrontendType($parentType, $name, $type);
|
||||||
|
$types = [$normalizedType['normalizedType']];
|
||||||
|
|
||||||
|
if ($type->getEnum() !== null) {
|
||||||
|
$format = match ($type->getType()) {
|
||||||
|
SchemaType::string => '"%s"',
|
||||||
|
default => "%s"
|
||||||
|
};
|
||||||
|
|
||||||
|
$types = [];
|
||||||
|
foreach ($type->getEnum() as $enumValue) {
|
||||||
|
$types[] = sprintf($format, $enumValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($type->getIsNullable() ?? false) {
|
||||||
|
$types = array_merge($types, ['null']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!($type->getIsRequired() ?? true)) {
|
||||||
|
$types = array_merge($types, ['undefined']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Property(
|
||||||
|
$name,
|
||||||
|
sprintf("%s%s: %s;", $name, $requiredFlag, implode('|', $types)),
|
||||||
|
$normalizedType['definition']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function normalizeFrontendType(string $parent, string $name, Type $type) : array
|
||||||
|
{
|
||||||
|
$definition = null;
|
||||||
|
|
||||||
|
switch ($type->getType()) {
|
||||||
|
case SchemaType::integer:
|
||||||
|
$normalizedType = 'number';
|
||||||
|
break;
|
||||||
|
case SchemaType::object:
|
||||||
|
/** @var TypeBuilder $typeBuilder */
|
||||||
|
$typeBuilder = $this->serviceManager->get(TypeBuilder::class);
|
||||||
|
$frontendType = $typeBuilder->build($parent, ucfirst($name), $type->getProperties());
|
||||||
|
$normalizedType = $frontendType->getName();
|
||||||
|
$definition = $frontendType->getDefinition();
|
||||||
|
break;
|
||||||
|
case SchemaType::array:
|
||||||
|
$singular = ucfirst(substr($name, 0, strlen($name) - 1));
|
||||||
|
$normalizedArrayItemType = $this->normalizeFrontendType($parent, $singular, $type->getItemType());
|
||||||
|
$normalizedType = $normalizedArrayItemType['normalizedType'] . '[]';
|
||||||
|
$definition = $normalizedArrayItemType['definition'];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$normalizedType = $type->getType()->value;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
return [
|
||||||
|
'normalizedType' => $normalizedType,
|
||||||
|
'definition' => $definition
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Exporter\Frontend\Builder;
|
||||||
|
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Frontend\Model\Service;
|
||||||
|
use Template\Infrastructure\Schema\Model\Api;
|
||||||
|
|
||||||
|
class ServiceBuilder
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function build(string $namespace, array $apis): Service {
|
||||||
|
$definition =
|
||||||
|
'import { Injectable } from "@angular/core";' . PHP_EOL .
|
||||||
|
'import { Observable } from "rxjs";' . PHP_EOL .
|
||||||
|
'import { RequestService } from "src/app/core/services/request.service";';
|
||||||
|
|
||||||
|
// build imports
|
||||||
|
/** @var Api $api */
|
||||||
|
foreach ($apis as $api) {
|
||||||
|
$definition = $definition. PHP_EOL . $this->buildFrontendServiceImport($api);
|
||||||
|
}
|
||||||
|
|
||||||
|
$definition = $definition . PHP_EOL . PHP_EOL .
|
||||||
|
"@Injectable()" . PHP_EOL .
|
||||||
|
sprintf("export class %sService {", $namespace) . PHP_EOL .
|
||||||
|
"\tconstructor(private requestService: RequestService) {" . PHP_EOL .
|
||||||
|
"\t}" . PHP_EOL;
|
||||||
|
|
||||||
|
// build methods
|
||||||
|
foreach ($apis as $api) {
|
||||||
|
$definition = $definition. PHP_EOL . $this->buildFrontendServiceMethod($api);
|
||||||
|
}
|
||||||
|
$definition = $definition . '}';
|
||||||
|
|
||||||
|
return new Service($definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildFrontendServiceImport(Api $api): string {
|
||||||
|
$endpoint = $api->getEndpoint();
|
||||||
|
$endpointName = implode(
|
||||||
|
array_map(
|
||||||
|
function ($item) {
|
||||||
|
return ucfirst($item);
|
||||||
|
},
|
||||||
|
explode('-', $endpoint)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return "import { {$endpointName}Request, {$endpointName}Response } from '../models/{$endpoint}.model';";
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildFrontendServiceMethod(Api $api): string {
|
||||||
|
$endpointName = implode(
|
||||||
|
array_map(
|
||||||
|
function ($item) {
|
||||||
|
return ucfirst($item);
|
||||||
|
},
|
||||||
|
explode('-', $api->getEndpoint())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$methodName = lcfirst($endpointName);
|
||||||
|
$path = str_replace('/api/', '', $api->getPath());
|
||||||
|
$apiMethod = $api->getMethod()->value;
|
||||||
|
|
||||||
|
$method = "\t" . "$methodName(body: {$endpointName}Request): Observable<{$endpointName}Response> {" . PHP_EOL;
|
||||||
|
$method = $method . "\t\t" . "return this.requestService.call<{$endpointName}Response>(" . PHP_EOL;
|
||||||
|
$method = $method . "\t\t\t" . "'{$apiMethod}'," . PHP_EOL;
|
||||||
|
$method = $method . "\t\t\t" . "'{$path}'," . PHP_EOL;
|
||||||
|
$method = $method . "\t\t\t" . "body" . PHP_EOL;
|
||||||
|
$method = $method . "\t\t" . ");" . PHP_EOL;
|
||||||
|
return $method . "\t" . "}" . PHP_EOL;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Exporter\Frontend\Builder;
|
||||||
|
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Frontend\Model\Type;
|
||||||
|
use Template\Infrastructure\Schema\Model\Type as SchemaType;
|
||||||
|
|
||||||
|
class TypeBuilder
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly PropertyBuilder $propertyBuilder
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function build(string $parent, string $name, array $properties): Type
|
||||||
|
{
|
||||||
|
$typeName = implode([$parent, $name]);
|
||||||
|
$typeDefinition = "export interface " . $typeName . " {" . PHP_EOL;
|
||||||
|
|
||||||
|
$subTypeDefinitions = [];
|
||||||
|
/**
|
||||||
|
* @var string $name
|
||||||
|
* @var SchemaType $type
|
||||||
|
*/
|
||||||
|
foreach ($properties as $name => $type) {
|
||||||
|
$frontendProperty = $this->propertyBuilder->build($typeName, $name, $type);
|
||||||
|
$typeDefinition = $typeDefinition . ' ' . $frontendProperty->getDefinition() . PHP_EOL;
|
||||||
|
|
||||||
|
if ($frontendProperty->getSubtypeDefinition() !== null)
|
||||||
|
$subTypeDefinitions[] = $frontendProperty->getSubtypeDefinition();
|
||||||
|
}
|
||||||
|
$typeDefinition = $typeDefinition . '}' . PHP_EOL;
|
||||||
|
|
||||||
|
return new Type(
|
||||||
|
$typeName,
|
||||||
|
implode(PHP_EOL, array_merge([$typeDefinition], $subTypeDefinitions))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Exporter\Frontend\Model;
|
||||||
|
|
||||||
|
class Property
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $name,
|
||||||
|
private readonly string $definition,
|
||||||
|
private readonly ?string $subtypeDefinition,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDefinition(): string
|
||||||
|
{
|
||||||
|
return $this->definition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubtypeDefinition(): ?string
|
||||||
|
{
|
||||||
|
return $this->subtypeDefinition;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Exporter\Frontend\Model;
|
||||||
|
|
||||||
|
class Service
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $definition
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDefinition(): string
|
||||||
|
{
|
||||||
|
return $this->definition;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Exporter\Frontend\Model;
|
||||||
|
|
||||||
|
class Type
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $name,
|
||||||
|
private readonly string $definition,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDefinition(): string
|
||||||
|
{
|
||||||
|
return $this->definition;
|
||||||
|
}
|
||||||
|
}
|
||||||
91
src/Infrastructure/Schema/src/Exporter/FrontendExporter.php
Normal file
91
src/Infrastructure/Schema/src/Exporter/FrontendExporter.php
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Exporter;
|
||||||
|
|
||||||
|
use Template\Infrastructure\Schema\Enum\SchemaType;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Frontend\Builder\ServiceBuilder;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Frontend\Builder\TypeBuilder;
|
||||||
|
use Template\Infrastructure\Schema\Exporter\Frontend\Model\Type;
|
||||||
|
use Template\Infrastructure\Schema\Model\Api;
|
||||||
|
use Template\Infrastructure\Schema\Model\Type as SchemaTypeModel;
|
||||||
|
use Template\Infrastructure\Schema\Reader\ApiDefinitionReader;
|
||||||
|
|
||||||
|
class FrontendExporter
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ApiDefinitionReader $apiDefinitionReader,
|
||||||
|
private readonly TypeBuilder $typeBuilder,
|
||||||
|
private readonly ServiceBuilder $serviceBuilder,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function export(string $exportRootDirectory = APP_ROOT . "/var/export/schema/fe-models/"): void
|
||||||
|
{
|
||||||
|
$namespaces = $this->apiDefinitionReader->read("");
|
||||||
|
|
||||||
|
foreach ($namespaces as $namespace => $apis) {
|
||||||
|
$namespaceDirectory = $exportRootDirectory . lcfirst($namespace);
|
||||||
|
$modelDirectory = $namespaceDirectory . '/models/';
|
||||||
|
$serviceDirectory = $namespaceDirectory . '/services/';
|
||||||
|
|
||||||
|
$this->assureDirectoryExists($namespaceDirectory);
|
||||||
|
$this->assureDirectoryExists($modelDirectory);
|
||||||
|
$this->assureDirectoryExists($serviceDirectory);
|
||||||
|
|
||||||
|
/** @var Api $api */
|
||||||
|
foreach ($apis as $api) {
|
||||||
|
$apiModels = [];
|
||||||
|
$apiModels[] = $this->buildFrontendModel($api, $api->getRequest(), "request");
|
||||||
|
$apiModels[] = $this->buildFrontendModel($api, $api->getResponse(), "response");
|
||||||
|
|
||||||
|
$modelFilePath = $modelDirectory . $api->getEndpoint() . '.model.ts';
|
||||||
|
file_put_contents(
|
||||||
|
$modelFilePath,
|
||||||
|
implode(
|
||||||
|
PHP_EOL,
|
||||||
|
array_map(
|
||||||
|
function (Type $apiModel) {
|
||||||
|
return $apiModel->getDefinition();
|
||||||
|
},
|
||||||
|
$apiModels
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$serviceFilePath = $serviceDirectory . lcfirst($namespace) . '.service.ts';
|
||||||
|
file_put_contents(
|
||||||
|
$serviceFilePath,
|
||||||
|
$this->serviceBuilder->build(
|
||||||
|
$namespace,
|
||||||
|
$apis
|
||||||
|
)->getDefinition()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildFrontendModel(Api $api, ?SchemaTypeModel $type, string $name): Type
|
||||||
|
{
|
||||||
|
$endpoint = $api->getUseCase();
|
||||||
|
$name = (ucfirst($name));
|
||||||
|
|
||||||
|
if ($type === null) {
|
||||||
|
return $this->typeBuilder->build($endpoint, $name, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
return match ($type->getType()) {
|
||||||
|
SchemaType::object => $this->typeBuilder->build($endpoint, $name, $type->getProperties()),
|
||||||
|
default => $this->typeBuilder->build($endpoint, $name, [lcfirst($name) => $type])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function assureDirectoryExists(string $path): void {
|
||||||
|
if (!file_exists($path)) {
|
||||||
|
mkdir(
|
||||||
|
directory: $path,
|
||||||
|
recursive: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/Infrastructure/Schema/src/Factory/OpenApiFactory.php
Normal file
71
src/Infrastructure/Schema/src/Factory/OpenApiFactory.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Factory;
|
||||||
|
|
||||||
|
use cebe\openapi\exceptions\UnresolvableReferenceException;
|
||||||
|
use cebe\openapi\Reader;
|
||||||
|
use cebe\openapi\ReferenceContext;
|
||||||
|
use cebe\openapi\spec\OpenApi;
|
||||||
|
use Laminas\ServiceManager\Factory\FactoryInterface;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use RuntimeException;
|
||||||
|
use Symfony\Component\Finder\Finder;
|
||||||
|
use Symfony\Component\Finder\SplFileInfo;
|
||||||
|
use Throwable;
|
||||||
|
use Template\Infrastructure\Schema\Exception\FileNotFoundException;
|
||||||
|
use Template\Infrastructure\Schema\Exception\UnsupportedContentTypeException;
|
||||||
|
|
||||||
|
class OpenApiFactory implements FactoryInterface
|
||||||
|
{
|
||||||
|
private const SCHEMA_PATH = APP_ROOT . '/src/ApiDomain/Schema';
|
||||||
|
|
||||||
|
private const SCHEMA_FILE_NAME = 'api.schema.json';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws UnresolvableReferenceException
|
||||||
|
* @throws RuntimeException
|
||||||
|
* @throws FileNotFoundException
|
||||||
|
* @throws UnsupportedContentTypeException
|
||||||
|
*/
|
||||||
|
public function __invoke(
|
||||||
|
ContainerInterface $container,
|
||||||
|
$requestedName,
|
||||||
|
?array $options = null
|
||||||
|
): OpenApi
|
||||||
|
{
|
||||||
|
$schemaFile = $this->findSchemaDefinitionFile();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$schema = Reader::readFromJson($schemaFile->getContents());
|
||||||
|
} catch (Throwable) {
|
||||||
|
throw new UnsupportedContentTypeException(sprintf(
|
||||||
|
'File %s does not contain valid JSON',
|
||||||
|
$schemaFile->getRealPath(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$schema->resolveReferences(new ReferenceContext($schema, self::SCHEMA_PATH . '/' . self::SCHEMA_FILE_NAME));
|
||||||
|
|
||||||
|
return $schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws FileNotFoundException
|
||||||
|
*/
|
||||||
|
private function findSchemaDefinitionFile(): SplFileInfo
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$finder = (new Finder())->files()->in(self::SCHEMA_PATH)->name(self::SCHEMA_FILE_NAME);
|
||||||
|
$iterator = $finder->getIterator();
|
||||||
|
$iterator->rewind();
|
||||||
|
|
||||||
|
return $iterator->current();
|
||||||
|
} catch (Throwable) {
|
||||||
|
throw new FileNotFoundException(sprintf(
|
||||||
|
'File with name "%s" not found in path: %s',
|
||||||
|
self::SCHEMA_FILE_NAME,
|
||||||
|
self::SCHEMA_PATH
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Middleware;
|
||||||
|
|
||||||
|
use cebe\openapi\spec\OpenApi;
|
||||||
|
use League\OpenAPIValidation\PSR15\Exception\InvalidResponseMessage;
|
||||||
|
use League\OpenAPIValidation\PSR15\Exception\InvalidServerRequestMessage;
|
||||||
|
use League\OpenAPIValidation\PSR15\ValidationMiddleware;
|
||||||
|
use League\OpenAPIValidation\PSR15\ValidationMiddlewareBuilder;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
|
||||||
|
class SchemaValidationMiddleware implements MiddlewareInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly OpenApi $schema
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws InvalidServerRequestMessage
|
||||||
|
* @throws InvalidResponseMessage
|
||||||
|
*/
|
||||||
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||||
|
{
|
||||||
|
/** @var ValidationMiddleware $validationMiddleware */
|
||||||
|
$validationMiddleware = (new ValidationMiddlewareBuilder())
|
||||||
|
->fromSchema($this->schema)
|
||||||
|
->getValidationMiddleware();
|
||||||
|
|
||||||
|
return $validationMiddleware->process($request, $handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
98
src/Infrastructure/Schema/src/Model/Api.php
Normal file
98
src/Infrastructure/Schema/src/Model/Api.php
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Model;
|
||||||
|
|
||||||
|
use Template\Infrastructure\Schema\Enum\Method;
|
||||||
|
|
||||||
|
class Api
|
||||||
|
{
|
||||||
|
private Method $method;
|
||||||
|
private ?Type $request = null;
|
||||||
|
private ?Type $response = null;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly string $path
|
||||||
|
) {
|
||||||
|
$this->method = Method::get;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPath(): string
|
||||||
|
{
|
||||||
|
return $this->path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMethod(): Method
|
||||||
|
{
|
||||||
|
return $this->method;
|
||||||
|
}
|
||||||
|
public function setMethod(Method $method): void
|
||||||
|
{
|
||||||
|
$this->method = $method;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRequest(): ?Type
|
||||||
|
{
|
||||||
|
return $this->request;
|
||||||
|
}
|
||||||
|
public function setRequest(?Type $request): void
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResponse(): ?Type
|
||||||
|
{
|
||||||
|
return $this->response;
|
||||||
|
}
|
||||||
|
public function setResponse(?Type $response): void
|
||||||
|
{
|
||||||
|
$this->response = $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Getter for path substrings
|
||||||
|
//
|
||||||
|
private function getPathParts(): array {
|
||||||
|
return array_map(
|
||||||
|
function (string $name) {
|
||||||
|
if ($name === 'api') // remove '/api'
|
||||||
|
return '';
|
||||||
|
if (str_starts_with($name, '{') || str_ends_with($name, '}')) // remove e.g. '{locationId}'
|
||||||
|
return '';
|
||||||
|
|
||||||
|
return $name;
|
||||||
|
},
|
||||||
|
explode("/", $this->path)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNamespace(): string {
|
||||||
|
$pathParts = $this->getPathParts();
|
||||||
|
$namespace = implode(
|
||||||
|
'',
|
||||||
|
array_map(
|
||||||
|
function (string $name) {
|
||||||
|
return ucfirst($name);
|
||||||
|
},
|
||||||
|
array_slice($pathParts, 0, count($pathParts) - 1)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return $namespace === '' ? 'Api' : $namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUseCase(): string {
|
||||||
|
return implode(
|
||||||
|
array_map(
|
||||||
|
function (string $name) {
|
||||||
|
return ucfirst($name);
|
||||||
|
},
|
||||||
|
explode('-', $this->getEndpoint())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEndpoint(): string {
|
||||||
|
$pathParts = $this->getPathParts();
|
||||||
|
return end($pathParts);
|
||||||
|
}
|
||||||
|
}
|
||||||
93
src/Infrastructure/Schema/src/Model/Type.php
Normal file
93
src/Infrastructure/Schema/src/Model/Type.php
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Model;
|
||||||
|
|
||||||
|
use Template\Infrastructure\Schema\Enum\SchemaType;
|
||||||
|
|
||||||
|
class Type
|
||||||
|
{
|
||||||
|
private ?bool $isRequired = null;
|
||||||
|
private ?bool $isNullable = null;
|
||||||
|
private ?array $enum = null;
|
||||||
|
private ?string $pattern = null;
|
||||||
|
private ?string $format = null;
|
||||||
|
|
||||||
|
// schemaType === object
|
||||||
|
private ?array $properties = null;
|
||||||
|
|
||||||
|
// schemaType === array
|
||||||
|
private ?Type $itemType = null;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly SchemaType $type
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): SchemaType
|
||||||
|
{
|
||||||
|
return $this->type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIsRequired(): ?bool
|
||||||
|
{
|
||||||
|
return $this->isRequired;
|
||||||
|
}
|
||||||
|
public function setIsRequired(?bool $isRequired): void
|
||||||
|
{
|
||||||
|
$this->isRequired = $isRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIsNullable(): ?bool
|
||||||
|
{
|
||||||
|
return $this->isNullable;
|
||||||
|
}
|
||||||
|
public function setIsNullable(?bool $isNullable): void
|
||||||
|
{
|
||||||
|
$this->isNullable = $isNullable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEnum(): ?array
|
||||||
|
{
|
||||||
|
return $this->enum;
|
||||||
|
}
|
||||||
|
public function setEnum(?array $enum): void
|
||||||
|
{
|
||||||
|
$this->enum = $enum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPattern(): ?string
|
||||||
|
{
|
||||||
|
return $this->pattern;
|
||||||
|
}
|
||||||
|
public function setPattern(?string $pattern): void
|
||||||
|
{
|
||||||
|
$this->pattern = $pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getFormat(): ?string
|
||||||
|
{
|
||||||
|
return $this->format;
|
||||||
|
}
|
||||||
|
public function setFormat(?string $format): void
|
||||||
|
{
|
||||||
|
$this->format = $format;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getProperties(): ?array
|
||||||
|
{
|
||||||
|
return $this->properties;
|
||||||
|
}
|
||||||
|
public function setProperties(?array $properties): void
|
||||||
|
{
|
||||||
|
$this->properties = $properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getItemType(): ?Type
|
||||||
|
{
|
||||||
|
return $this->itemType;
|
||||||
|
}
|
||||||
|
public function setItemType(?Type $itemType): void
|
||||||
|
{
|
||||||
|
$this->itemType = $itemType;
|
||||||
|
}
|
||||||
|
}
|
||||||
141
src/Infrastructure/Schema/src/Reader/ApiDefinitionReader.php
Normal file
141
src/Infrastructure/Schema/src/Reader/ApiDefinitionReader.php
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Template\Infrastructure\Schema\Reader;
|
||||||
|
|
||||||
|
use cebe\openapi\exceptions\UnresolvableReferenceException;
|
||||||
|
use cebe\openapi\Reader;
|
||||||
|
use cebe\openapi\ReferenceContext;
|
||||||
|
use cebe\openapi\spec\OpenApi;
|
||||||
|
use Exception;
|
||||||
|
use Laminas\ServiceManager\Factory\FactoryInterface;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use RuntimeException;
|
||||||
|
use Symfony\Component\Finder\Finder;
|
||||||
|
use Symfony\Component\Finder\SplFileInfo;
|
||||||
|
use Throwable;
|
||||||
|
use Template\Infrastructure\Schema\Enum\Method;
|
||||||
|
use Template\Infrastructure\Schema\Enum\SchemaType;
|
||||||
|
use Template\Infrastructure\Schema\Exception\FileNotFoundException;
|
||||||
|
use Template\Infrastructure\Schema\Exception\UnsupportedContentTypeException;
|
||||||
|
use Template\Infrastructure\Schema\Model\Api;
|
||||||
|
use Template\Infrastructure\Schema\Model\Type;
|
||||||
|
|
||||||
|
class ApiDefinitionReader
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly OpenApi $schema
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function read(?string $pathRegex): array
|
||||||
|
{
|
||||||
|
$namespaces = [];
|
||||||
|
$schemaDefinition = json_decode(
|
||||||
|
json_encode($this->schema->getSerializableData()),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($schemaDefinition['paths'] as $path => $definition) {
|
||||||
|
if (!$this->regexMatches($pathRegex, $path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$api = new Api($path);
|
||||||
|
|
||||||
|
if (key_exists('get', $definition)) {
|
||||||
|
$api->setMethod(Method::get);
|
||||||
|
$api->setRequest(null);
|
||||||
|
}
|
||||||
|
if (key_exists('post', $definition)) {
|
||||||
|
$requestSchema = $definition['post']['requestBody']['content']['application/json']['schema'] ?? [];
|
||||||
|
$api->setMethod(Method::post);
|
||||||
|
$api->setRequest($this->resolveSchema($requestSchema));
|
||||||
|
}
|
||||||
|
|
||||||
|
$responseSchema = $definition[$api->getMethod()->value]['responses']['200']['content']['application/json']['schema'] ?? [];
|
||||||
|
$api->setResponse($this->resolveSchema($responseSchema));
|
||||||
|
|
||||||
|
$namespaces[$api->getNamespace()][] = $api;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $namespaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function regexMatches(?string $pathRegex, $path): bool {
|
||||||
|
if($pathRegex === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return preg_match(
|
||||||
|
sprintf(
|
||||||
|
"~%s~",
|
||||||
|
$pathRegex
|
||||||
|
),
|
||||||
|
$path
|
||||||
|
) !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function resolveSchema(array $definition): ?Type {
|
||||||
|
if (empty($definition)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!key_exists('type', $definition)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($definition['type']) {
|
||||||
|
case 'string':
|
||||||
|
$type = new Type(SchemaType::string);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'number':
|
||||||
|
$type = new Type(SchemaType::number);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'integer':
|
||||||
|
$type = new Type(SchemaType::integer);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'boolean':
|
||||||
|
$type = new Type(SchemaType::boolean);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'array':
|
||||||
|
$type = new Type(SchemaType::array);
|
||||||
|
$type->setItemType($this->resolveSchema($definition['items']));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'object':
|
||||||
|
$type = new Type(SchemaType::object);
|
||||||
|
$properties = [];
|
||||||
|
|
||||||
|
foreach (($definition['properties'] ?? []) as $name => $definedProperty) {
|
||||||
|
$property = $this->resolveSchema($definedProperty);
|
||||||
|
$property->setIsRequired(in_array($name, $definition['required']));
|
||||||
|
$properties[$name] = $property;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($definition['required'] as $name) {
|
||||||
|
if (!key_exists($name, $properties)) {
|
||||||
|
$property = new Type(SchemaType::any);
|
||||||
|
$property->setIsRequired(true);
|
||||||
|
$property->setIsNullable(true);
|
||||||
|
$properties[$name] = $property;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$type->setProperties($properties);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$type = new Type(SchemaType::any);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$type->setIsNullable($definition['nullable'] ?? false);
|
||||||
|
$type->setEnum($definition['enum'] ?? null);
|
||||||
|
$type->setPattern($definition['pattern'] ?? null);
|
||||||
|
$type->setFormat($definition['format'] ?? null);
|
||||||
|
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user