first commit

This commit is contained in:
Flo 2024-08-04 14:23:33 +00:00
commit cb8ac3a08e
352 changed files with 11695 additions and 0 deletions

13
.env.example Normal file
View File

@ -0,0 +1,13 @@
# DB Configuration
DB_DRIVER=pdo_mysql
DB_HOST=myTube-backend-mysql
DB_PORT=3306
DB_USER=myTube
DB_PASSWORD=pass
DB_NAME=myTube
DB_NAME_LOG=log
# MyTube Setup
INIT_USER_NAME=admin
INIT_USER_PASSWORD=password
INIT_USER_MAIL=admin@test.com

7
.gitattributes vendored Normal file
View File

@ -0,0 +1,7 @@
/.gitattributes export-ignore
/.github/ export-ignore
/.laminas-ci.json export-ignore
/phpcs.xml.dist export-ignore
/psalm.xml.dist export-ignore
/psalm-baseline.xml export-ignore
/renovate.json export-ignore

View File

@ -0,0 +1,40 @@
name: Create Branch on Issue Transition to In Progress
on:
issues:
types:
- edited
jobs:
create-branch:
if: github.event.changes.issue.labels
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Get issue details
run: |
issue_title=$(jq -r ".issue.number" "$GITHUB_EVENT_PATH")
previous_labels=$(jq -r ".changes.labels.from" "$GITHUB_EVENT_PATH")
current_labels=$(jq -r ".issue.labels | map(.name) | join(\", \")" "$GITHUB_EVENT_PATH")
branch_name=""
# Check if the issue transitioned from 'Todo' to 'In Progress'
if [[ "$previous_labels" == *"Todo"* && "$current_labels" == *"In Progress"* ]]; then
project_name="hp-be" # Verwende "hp-be" als Projektnamen
branch_name="${project_name}-${issue_title}"
echo "Branch name: $branch_name"
fi
echo "::set-output name=branch_name::$branch_name"
shell: bash
- name: Create and switch to new branch
run: |
branch_name="${{ steps.create-branch.outputs.branch_name }}"
if [ -n "$branch_name" ]; then
git checkout -b "$branch_name"
git push origin "$branch_name"
fi
working-directory: ${{ github.workspace }}

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

@ -0,0 +1,39 @@
name: PHP Composer
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Validate composer.json and composer.lock
run: composer validate --strict
- name: Cache Composer packages
id: composer-cache
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-
- name: Install dependencies
run: composer install --prefer-dist --no-progress
# Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit"
# Docs: https://getcomposer.org/doc/articles/scripts.md
# - name: Run test suite
# run: composer run-script test

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
/.phpcs-cache
/.phpunit.result.cache
/.idea
/clover.xml
/coveralls-upload.json
/phpunit.xml
/data/cache/
/data/db/
/public/data/
/var/
/vendor/
*.env
composer.lock

3
.htaccess Normal file
View File

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

1
COPYRIGHT.md Normal file
View File

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

26
LICENSE.md Normal file
View File

@ -0,0 +1,26 @@
Copyright (c) 2020 Laminas Project a Series of LF Projects, LLC.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
- Neither the name of Laminas Foundation nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

0
README.md Normal file
View File

View File

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

30
bin/console.php Normal file
View File

@ -0,0 +1,30 @@
<?php
use MyTube\Infrastructure\Logging\Logger\Logger;
use Symfony\Component\Console\Application;
require_once __DIR__ . '/../config/autoload/defines.php';
require APP_ROOT . '/vendor/autoload.php';
call_user_func(function() {
$container = require APP_ROOT . '/config/container.php';
$config = $container->get('config');
$commands = $config['console']['commands'];
$app = new Application();
foreach ($commands as $command) {
$app->add($container->get($command));
}
try {
$app->setCatchExceptions(false);
$app->run();
} catch (Throwable $e) {
$logger = new Logger();
$logger->error(
$e->getMessage(),
['exception' => $e]
);
}
});

312
bin/createApi.php Normal file
View File

@ -0,0 +1,312 @@
<?php
if (count($argv) !== 6) {
echo 'Use of this Command:' . PHP_EOL
. 'createApi API_IsExternal API_Namespace API_Name CQRS_IsCommand CQRS_Namespace' . PHP_EOL;
die;
}
$projectSourceDirectory = 'src/';
$projectNamespace = 'MyTube';
$apiType = strtolower($argv[1]) === 'true' ? 'External' : 'Internal';
$apiNamespace = $argv[2];
$apiName = $argv[3];
$cqrsType = strtolower($argv[4]) === 'true' ? 'Command' : 'Query';
$cqrsNamespace = $argv[5];
$originalCqrsNamespace = $cqrsNamespace;
$apiDirectoryPath = $projectSourceDirectory . 'ApiDomain/' . $apiType . '/' . $apiNamespace . '/';
$cqrsDirectoryPath = $projectSourceDirectory . 'HandlingDomain/' . $cqrsNamespace . '/';
# API Handler
$apiHandlerName = $apiName . 'Handler';
$apiHandlerNamespace = $projectNamespace . '\\API\\' . $apiType . '\\' . $apiNamespace . '\\Handler';
$apiHandlerUsingNamespace = $apiHandlerNamespace . '\\' . $apiHandlerName;
$apiHandlerFilePath = $apiDirectoryPath . 'src/Handler/' . $apiHandlerName . '.php';
# Response Formatter
$apiResponseFormatterName = $apiName . 'ResponseFormatter';
$apiResponseFormatterNamespace = $projectNamespace . '\\API\\' . $apiType . '\\' . $apiNamespace . '\\ResponseFormatter';
$apiResponseFormatterUsingNamespace = $apiResponseFormatterNamespace . '\\' . $apiResponseFormatterName;
$apiResponseFormatterFilePath = $apiDirectoryPath . 'src/ResponseFormatter/' . $apiResponseFormatterName . '.php';
# CQRS
$cqrsFilePath = $cqrsDirectoryPath . 'src/Handler/' . $cqrsType . '/' . $apiName . '/';
$cqrsName = $apiName . $cqrsType;
$cqrsVariableName = lcfirst($cqrsName);
$cqrsNamespace = $projectNamespace . '\\Handling\\' . $cqrsNamespace . '\\Handler\\' . $cqrsType . '\\' . $apiName;
# Command / Query
$cqrsPath = $cqrsFilePath . $cqrsName . '.php';
$cqrsUsingNamespace = $cqrsNamespace . '\\' . $cqrsName;
# Result
$cqrsResultName = $cqrsName . 'Result';
$cqrsResultVariableName = lcfirst($cqrsResultName);
$cqrsResultPath = $cqrsFilePath . $cqrsResultName . '.php';
$cqrsResultUsingNamespace = $cqrsNamespace . '\\' . $cqrsResultName;
# Handler
$cqrsHandlerName = $cqrsName . 'Handler';
$cqrsHandlerVariableName = lcfirst($cqrsHandlerName);
$cqrsHandlerPath = $cqrsFilePath . $cqrsName . 'Handler' . '.php';
$cqrsHandlerUsingNamespace = $cqrsNamespace . '\\' . $cqrsHandlerName;
# Builder
$cqrsBuilderName = $cqrsName . 'Builder';
$cqrsBuilderVariableName = lcfirst($cqrsBuilderName);
$cqrsBuilderPath = $cqrsFilePath . $cqrsName . 'Builder' . '.php';
$cqrsBuilderUsingNamespace = $cqrsNamespace . '\\' . $cqrsBuilderName;
########## WRITE FILES ###############
function writeToFile($path, $content) {
echo 'Writing contents to file ' . $path . PHP_EOL;
$directory = pathinfo($path, PATHINFO_DIRNAME);
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
file_put_contents($path, $content);
}
if (!file_exists($apiDirectoryPath)) {
$routesFileContent = "<?php
declare(strict_types=1);
use {$apiHandlerUsingNamespace};
return [
[
'name' => 'TODO.TODO',
'path' => '/api/TODO/TODO[/]',
'allowed_methods' => ['POST'],
'middleware' => [
{$apiHandlerName}::class,
],
],
];
";
$routesFilePath = $apiDirectoryPath . 'config/routes.php';
writeToFile($routesFilePath, $routesFileContent);
$serviceManagerFileContent = "<?php
declare(strict_types=1);
use {$apiHandlerUsingNamespace};
use {$apiResponseFormatterUsingNamespace};
use Reinfi\\DependencyInjection\\Factory\\AutoWiringFactory;
return [
'factories' => [
// Handler
{$apiHandlerName}::class => AutoWiringFactory::class,
// Response Formatter
{$apiResponseFormatterName}::class => AutoWiringFactory::class,
],
];
";
$serviceManagerFilePath = $apiDirectoryPath . 'config/service_manager.php';
writeToFile($serviceManagerFilePath, $serviceManagerFileContent);
$configProviderFileContent = "<?php
declare(strict_types=1);
namespace MyTube\\API\\{$apiType}\\{$apiNamespace};
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => require __DIR__ . './../config/service_manager.php',
'routes' => require __DIR__ . '/./../config/routes.php',
];
}
}
";
$configProviderFilePath = $apiDirectoryPath . 'src/ConfigProvider.php';
writeToFile($configProviderFilePath, $configProviderFileContent);
}
if (!file_exists($cqrsDirectoryPath)) {
$serviceManagerFileContent = "<?php
declare(strict_types=1);
use {$cqrsHandlerUsingNamespace};
use {$cqrsBuilderUsingNamespace};
use Reinfi\\DependencyInjection\\Factory\\AutoWiringFactory;
use Reinfi\\DependencyInjection\\Factory\\InjectionFactory;
return [
'factories' => [
/// CQRS
// {$apiName}
{$cqrsBuilderName}::class => AutoWiringFactory::class,
{$cqrsHandlerName}::class => AutoWiringFactory::class,
],
];
";
$serviceManagerFilePath = $cqrsDirectoryPath . 'config/service_manager.php';
writeToFile($serviceManagerFilePath, $serviceManagerFileContent);
$configProviderFileContent = "<?php
declare(strict_types=1);
namespace MyTube\\Handling\\{$originalCqrsNamespace};
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => require __DIR__ . './../config/service_manager.php',
];
}
}
";
$configProviderFilePath = $cqrsDirectoryPath . 'src/ConfigProvider.php';
writeToFile($configProviderFilePath, $configProviderFileContent);
}
$apiHandlerFileContent = "<?php
declare(strict_types=1);
namespace {$apiHandlerNamespace};
use {$cqrsHandlerUsingNamespace};
use {$cqrsBuilderUsingNamespace};
use {$apiResponseFormatterUsingNamespace};
use {$projectNamespace}\\Infrastructure\\Request\\Middleware\\AnalyzeBodyMiddleware;
use {$projectNamespace}\\Infrastructure\\Response\\SuccessResponse;
use Psr\\Http\\Message\\ResponseInterface;
use Psr\\Http\\Message\\ServerRequestInterface;
use Psr\\Http\\Server\\RequestHandlerInterface;
class {$apiHandlerName} implements RequestHandlerInterface
{
public function __construct(
private readonly {$cqrsHandlerName} \${$cqrsHandlerVariableName},
private readonly {$cqrsBuilderName} \${$cqrsBuilderVariableName},
private readonly {$apiResponseFormatterName} \$responseFormatter,
) {
}
public function handle(ServerRequestInterface \$request): ResponseInterface
{
\$data = \$request->getAttribute(AnalyzeBodyMiddleware::JSON_DATA);
\${$cqrsVariableName} = \$this->{$cqrsBuilderVariableName}->build(
\$data
);
\$result = \$this->{$cqrsHandlerVariableName}->execute(\${$cqrsVariableName});
return new SuccessResponse(\$this->responseFormatter->format(\$result));
}
}
";
writeToFile($apiHandlerFilePath, $apiHandlerFileContent);
$apiResponseFormatterFileContent = "<?php
declare(strict_types=1);
namespace {$apiResponseFormatterNamespace};
use {$cqrsResultUsingNamespace};
class {$apiResponseFormatterName}
{
public function format({$cqrsResultName} \${$cqrsResultVariableName}): array
{
return [];
}
}
";
writeToFile($apiResponseFormatterFilePath, $apiResponseFormatterFileContent);
$cqrsFileContent = "<?php
declare(strict_types=1);
namespace {$cqrsNamespace};
class {$cqrsName}
{
public function __construct(
#TODO
) {
}
}
";
writeToFile($cqrsPath, $cqrsFileContent);
$cqrsResultFileContent = "<?php
declare(strict_types=1);
namespace {$cqrsNamespace};
class {$cqrsResultName}
{
public function __construct(
#TODO
) {
}
}
";
writeToFile($cqrsResultPath, $cqrsResultFileContent);
$cqrsHandlerFileContent = "<?php
declare(strict_types=1);
namespace {$cqrsNamespace};
class {$cqrsHandlerName}
{
public function __construct(
#TODO
) {
}
public function execute({$cqrsName} \${$cqrsVariableName}): {$cqrsResultName}
{
return new {$cqrsResultName}();
}
}
";
writeToFile($cqrsHandlerPath, $cqrsHandlerFileContent);
$cqrsBuilderFileContent = "<?php
declare(strict_types=1);
namespace {$cqrsNamespace};
class {$cqrsBuilderName}
{
public function build(
#TODO
): {$cqrsName} {
return new {$cqrsName}(
#TODO
);
}
}
";
writeToFile($cqrsBuilderPath, $cqrsBuilderFileContent);

194
bin/createPipeline.php Normal file
View File

@ -0,0 +1,194 @@
<?php
if (count($argv) < 4) {
echo 'Use of this Command:' . PHP_EOL
. 'createPipeline Handling_Namespace Pipeline_Name Step_1_Name Step_2_Name' . PHP_EOL;
die;
}
$projectSourceDirectory = 'src/';
$projectNamespace = 'MyTube';
$pipelineNamespace = $argv[1];
$pipelineName = $argv[2];
$stepNames = array_slice($argv, 3);
# Pipeline
$pipelineClassName = $pipelineName . 'Pipeline';
$pipelineVariableName = lcfirst($pipelineClassName);
$pipelineDirectoryPath = $projectSourceDirectory . 'HandlingDomain/' . $pipelineNamespace . '/';
$pipelineFilePath = $pipelineDirectoryPath . 'src/Pipeline/' . $pipelineName . '/' . $pipelineClassName . '.php';
$pipelineFullNamespace = $projectNamespace . '\\Handling\\' . $pipelineNamespace . '\\Pipeline\\' . $pipelineName;
$pipelineUsingNamespace = $pipelineFullNamespace . '\\' . $pipelineClassName;
# Payload
$payloadClassName = $pipelineName . 'Payload';
$payloadVariableName = lcfirst($payloadClassName);
$payloadFilePath = $projectSourceDirectory . 'HandlingDomain/' . $pipelineNamespace . '/src/Pipeline/' . $pipelineName . '/' . $payloadClassName . '.php';
$payloadFullNamespace = $projectNamespace . '\\Handling\\' . $pipelineNamespace . '\\Pipeline\\' . $pipelineName;
$payloadUsingNamespace = $payloadFullNamespace . '\\' . $payloadClassName;
# Step
$stepsFilePath = $projectSourceDirectory . 'HandlingDomain/' . $pipelineNamespace . '/src/Pipeline/' . $pipelineName . '/Step/';
$stepsFullNamespace = $projectNamespace . '\\Handling\\' . $pipelineNamespace . '\\Pipeline\\' . $pipelineName . '\\Step';
$steps = [];
foreach ($stepNames as $stepName) {
$stepClassName = $stepName . 'Step';
$steps[] = [
'stepClassName' => $stepClassName,
'stepVariableName' => lcfirst($stepClassName),
'stepFilePath' => $stepsFilePath . $stepClassName . '.php',
'stepUsingNamespace' => 'use ' . $payloadFullNamespace . '\\Step\\' . $stepClassName,
];
}
########## WRITE FILES ###############
function writeToFile($path, $content) {
echo 'Writing contents to file ' . $path . PHP_EOL;
$directory = pathinfo($path, PATHINFO_DIRNAME);
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
file_put_contents($path, $content);
}
$createNamespaceFiles = !file_exists($pipelineDirectoryPath);
$stepsUsingNamespaces = [];
$stepsDeclarations = [];
$stepsAutoWirings = [];
$stepsReferences = [];
foreach ($steps as $step) {
$stepClassName = $step['stepClassName'];
$stepVariableName = $step['stepVariableName'];
$stepFilePath = $step['stepFilePath'];
$stepUsingNamespace = $step['stepUsingNamespace'];
$stepsUsingNamespaces[] = $stepUsingNamespace . ';';
$stepsDeclarations[] = 'private readonly ' . $stepClassName . ' $' . $stepVariableName . ',';
$stepsAutoWirings[] = $stepClassName . '::class => AutoWiringFactory::class,';
$stepsReferences[] = '$this->' . $stepVariableName . ',';
$stepFileContent = "<?php
declare(strict_types=1);
namespace {$stepsFullNamespace};
use {$payloadUsingNamespace};
use teewurst\\Pipeline\\PipelineInterface;
use teewurst\\Pipeline\\TaskInterface;
class {$stepClassName} implements TaskInterface
{
public function __construct(
#TODO
) {
}
public function __invoke(\$payload, PipelineInterface \$pipeline): void
{
/** @var {$payloadClassName} \${$payloadVariableName} */
\${$payloadVariableName} = \$payload;
\$pipeline->next()(\$payload, \$pipeline);
}
}
";
writeToFile($stepFilePath, $stepFileContent);
}
$stepsUsingNamespace = implode(PHP_EOL, $stepsUsingNamespaces);
$stepsDeclaration = implode(PHP_EOL . ' ', $stepsDeclarations);
$stepsAutoWiring = implode(PHP_EOL . ' ', $stepsAutoWirings);
$stepsReference = implode(PHP_EOL . ' ', $stepsReferences);
$pipelineFileContent = "<?php
declare(strict_types=1);
namespace {$pipelineFullNamespace};
{$stepsUsingNamespace}
use teewurst\\Pipeline\\Pipeline;
class {$pipelineClassName} extends Pipeline
{
public function __construct(
{$stepsDeclaration}
) {
parent::__construct([
{$stepsReference}
]);
}
}
";
writeToFile($pipelineFilePath, $pipelineFileContent);
$payloadFileContent = "<?php
declare(strict_types=1);
namespace {$payloadFullNamespace};
class {$payloadClassName}
{
#TODO
}
";
writeToFile($payloadFilePath, $payloadFileContent);
if ($createNamespaceFiles) {
$serviceManagerFileContent = "<?php
declare(strict_types=1);
use {$pipelineUsingNamespace};
{$stepsUsingNamespace}
use Reinfi\\DependencyInjection\\Factory\\AutoWiringFactory;
use Reinfi\\DependencyInjection\\Factory\\InjectionFactory;
return [
'factories' => [
/// Pipeline
// {$pipelineName}
{$pipelineClassName}::class => AutoWiringFactory::class,
{$stepsAutoWiring}
],
];
";
$serviceManagerFilePath = $pipelineDirectoryPath . 'config/service_manager.php';
writeToFile($serviceManagerFilePath, $serviceManagerFileContent);
$configProviderFileContent = "<?php
declare(strict_types=1);
namespace MyTube\\Handling\\{$pipelineNamespace};
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => require __DIR__ . './../config/service_manager.php',
];
}
}
";
$configProviderFilePath = $pipelineDirectoryPath . 'src/ConfigProvider.php';
writeToFile($configProviderFilePath, $configProviderFileContent);
}

View File

@ -0,0 +1,91 @@
<?php
require_once __DIR__ . '/../config/autoload/defines.php';
require APP_ROOT . '/vendor/autoload.php';
use MyTube\Data\Business\Manager\MyTubeEntityManager;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\DBAL\DriverManager;
use Doctrine\Migrations\Configuration\Configuration;
use Doctrine\Migrations\Configuration\Connection\ExistingConnection;
use Doctrine\Migrations\Configuration\Migration\ExistingConfiguration;
use Doctrine\Migrations\DependencyFactory;
use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration;
use Doctrine\Migrations\Tools\Console\Command\DumpSchemaCommand;
use Doctrine\Migrations\Tools\Console\Command\ExecuteCommand;
use Doctrine\Migrations\Tools\Console\Command\GenerateCommand;
use Doctrine\Migrations\Tools\Console\Command\LatestCommand;
use Doctrine\Migrations\Tools\Console\Command\ListCommand;
use Doctrine\Migrations\Tools\Console\Command\MigrateCommand;
use Doctrine\Migrations\Tools\Console\Command\RollupCommand;
use Doctrine\Migrations\Tools\Console\Command\StatusCommand;
use Doctrine\Migrations\Tools\Console\Command\SyncMetadataCommand;
use Doctrine\Migrations\Tools\Console\Command\VersionCommand;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Tools\Setup;
use Symfony\Component\Console\Application;
$isDevMode = true;
$container = require APP_ROOT . '/config/container.php';
$config = $container->get('config');
$doctrineConfig = $config['doctrine'];
$paths = $doctrineConfig['driver']['orm_log_annotation_driver']['paths'];
$dbParams = $doctrineConfig['connection']['orm_log']['params'];
$migrationsConf = $doctrineConfig['migrations_configuration']['orm_log'];
$reader = new AnnotationReader();
$driver = new AnnotationDriver($reader, $paths);
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$config->setMetadataDriverImpl($driver);
$entityManager = $container->get(MyTubeEntityManager::class);
try {
$connection = DriverManager::getConnection($dbParams);
} catch (\Doctrine\DBAL\Exception $e) {
echo $e->getMessage();
exit;
}
$configuration = new Configuration($connection);
$configuration->addMigrationsDirectory(
$migrationsConf['namespace'],
$migrationsConf['directory']
);
$configuration->setAllOrNothing(true);
$configuration->setCheckDatabasePlatform(false);
$storageConfiguration = new TableMetadataStorageConfiguration();
$storageConfiguration->setTableName($migrationsConf['table']);
$configuration->setMetadataStorageConfiguration($storageConfiguration);
$dependencyFactory = DependencyFactory::fromConnection(
new ExistingConfiguration($configuration),
new ExistingConnection($connection)
);
$cli = new Application('Doctrine Migrations');
$cli->setCatchExceptions(true);
$cli->addCommands([
new DumpSchemaCommand($dependencyFactory),
new ExecuteCommand($dependencyFactory),
new GenerateCommand($dependencyFactory),
new LatestCommand($dependencyFactory),
new ListCommand($dependencyFactory),
new MigrateCommand($dependencyFactory),
new RollupCommand($dependencyFactory),
new StatusCommand($dependencyFactory),
new SyncMetadataCommand($dependencyFactory),
new VersionCommand($dependencyFactory),
]);
try {
$cli->run();
} catch (Exception $e) {
echo $e->getMessage();
}

View File

@ -0,0 +1,91 @@
<?php
require_once __DIR__ . '/../config/autoload/defines.php';
require APP_ROOT . '/vendor/autoload.php';
use MyTube\Data\Business\Manager\MyTubeEntityManager;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\DBAL\DriverManager;
use Doctrine\Migrations\Configuration\Configuration;
use Doctrine\Migrations\Configuration\Connection\ExistingConnection;
use Doctrine\Migrations\Configuration\Migration\ExistingConfiguration;
use Doctrine\Migrations\DependencyFactory;
use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration;
use Doctrine\Migrations\Tools\Console\Command\DumpSchemaCommand;
use Doctrine\Migrations\Tools\Console\Command\ExecuteCommand;
use Doctrine\Migrations\Tools\Console\Command\GenerateCommand;
use Doctrine\Migrations\Tools\Console\Command\LatestCommand;
use Doctrine\Migrations\Tools\Console\Command\ListCommand;
use Doctrine\Migrations\Tools\Console\Command\MigrateCommand;
use Doctrine\Migrations\Tools\Console\Command\RollupCommand;
use Doctrine\Migrations\Tools\Console\Command\StatusCommand;
use Doctrine\Migrations\Tools\Console\Command\SyncMetadataCommand;
use Doctrine\Migrations\Tools\Console\Command\VersionCommand;
use Doctrine\ORM\Mapping\Driver\AnnotationDriver;
use Doctrine\ORM\Tools\Setup;
use Symfony\Component\Console\Application;
$isDevMode = true;
$container = require APP_ROOT . '/config/container.php';
$config = $container->get('config');
$doctrineConfig = $config['doctrine'];
$paths = $doctrineConfig['driver']['orm_myTube_annotation_driver']['paths'];
$dbParams = $doctrineConfig['connection']['orm_myTube']['params'];
$migrationsConf = $doctrineConfig['migrations_configuration']['orm_myTube'];
$reader = new AnnotationReader();
$driver = new AnnotationDriver($reader, $paths);
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$config->setMetadataDriverImpl($driver);
$entityManager = $container->get(MyTubeEntityManager::class);
try {
$connection = DriverManager::getConnection($dbParams);
} catch (\Doctrine\DBAL\Exception $e) {
echo $e->getMessage();
exit;
}
$configuration = new Configuration($connection);
$configuration->addMigrationsDirectory(
$migrationsConf['namespace'],
$migrationsConf['directory']
);
$configuration->setAllOrNothing(true);
$configuration->setCheckDatabasePlatform(false);
$storageConfiguration = new TableMetadataStorageConfiguration();
$storageConfiguration->setTableName($migrationsConf['table']);
$configuration->setMetadataStorageConfiguration($storageConfiguration);
$dependencyFactory = DependencyFactory::fromConnection(
new ExistingConfiguration($configuration),
new ExistingConnection($connection)
);
$cli = new Application('Doctrine Migrations');
$cli->setCatchExceptions(true);
$cli->addCommands([
new DumpSchemaCommand($dependencyFactory),
new ExecuteCommand($dependencyFactory),
new GenerateCommand($dependencyFactory),
new LatestCommand($dependencyFactory),
new ListCommand($dependencyFactory),
new MigrateCommand($dependencyFactory),
new RollupCommand($dependencyFactory),
new StatusCommand($dependencyFactory),
new SyncMetadataCommand($dependencyFactory),
new VersionCommand($dependencyFactory),
]);
try {
$cli->run();
} catch (Exception $e) {
echo $e->getMessage();
}

3
bin/script/build Executable file
View File

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

3
bin/script/down Executable file
View File

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

23
bin/script/exec Executable file
View File

@ -0,0 +1,23 @@
#!/bin/bash
COMMAND="$@"
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
#LOAD ENV VARIABLES FROM .ENV
export $(grep -v '^#' "${SCRIPT_DIR}/../../.env" | xargs)
#MAC
if [[ "$OSTYPE" == "darwin"* ]]; then
docker compose -f "${SCRIPT_DIR}/../../docker/docker-compose-mac.yml" $COMMAND
#LINUX
elif [[ "$OSTYPE" == "linux-gnu" ]]; then
docker compose -f "${SCRIPT_DIR}/../../docker/docker-compose.yml" $COMMAND
else
echo "Dieses Skript wird auf deinem Gerät nicht unterstützt"
exit 1
fi
#UNSET ENV VARIABLES FROM .ENV
unset $(grep -v '^#' "${SCRIPT_DIR}/../../.env" | sed -E 's/(.*)=.*/\1/' | xargs)

32
bin/script/init Executable file
View File

@ -0,0 +1,32 @@
#!/bin/bash
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
PROJECT_DIR=$(realpath $SCRIPT_DIR/../../)
ENV_DIR=$(realpath $PROJECT_DIR/../../)
# Check .env file
if [ ! -f "$PROJECT_DIR/.env" ]
then
echo "Create .env file from example..."
cp "$PROJECT_DIR/.env.example" "$PROJECT_DIR/.env"
echo ".env file created, please change variables and call me again"
exit 1
fi
# Build and start docker containers
$SCRIPT_DIR/exec build
$SCRIPT_DIR/exec up -d
# Source drun
source $ENV_DIR/bin/script/drun
# Install PHP packages
drun myTube-backend composer install
# Migrate databases to current version
drun myTube-backend composer dmm
drun myTube-backend composer dmlm
# Insert setup for project after this line
drun myTube-backend composer console rbac:update
drun myTube-backend composer console init:data

3
bin/script/stop Executable file
View File

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

3
bin/script/up Executable file
View File

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

130
composer.development.json Normal file
View File

@ -0,0 +1,130 @@
{
"require": {
"laminas\/laminas-servicemanager": "^3.21",
"mezzio\/mezzio-laminasrouter": "^3.8",
"reinfi\/zf-dependency-injection": "^5.4",
"laminas\/laminas-config-aggregator": "^1.13",
"doctrine\/doctrine-orm-module": "5.0.*",
"doctrine\/migrations": "^3.5",
"doctrine\/orm": "2.11.3",
"doctrine\/persistence": "2.2.*",
"ramsey\/uuid-doctrine": "^1.8",
"roave\/psr-container-doctrine": "^3.1",
"mezzio\/mezzio": "^3.17",
"mezzio\/mezzio-helpers": "^5.15",
"symfony\/cache": "5.4.8",
"doctrine\/dbal": "^3.6",
"teewurst\/psr4-advanced-wildcard-composer-plugin": "^3.0",
"laminas\/laminas-crypt": "^3.10",
"monolog\/monolog": "^3.4",
"laminas\/laminas-mail": "^2.23",
"teewurst\/pipeline": "^3.0",
"guzzlehttp\/guzzle": "^7.8",
"micilini\/video-stream": "^1.0",
"nesbot\/carbon": "^3.0"
},
"autoload": {
"psr-4": {
"MyTube\\API\\Console\\": "src\/ApiDomain\/Console\/src",
"MyTube\\API\\External\\Authentication\\": "src\/ApiDomain\/External\/Authentication\/src",
"MyTube\\API\\External\\Health\\": "src\/ApiDomain\/External\/Health\/src",
"MyTube\\API\\External\\Tag\\": "src\/ApiDomain\/External\/Tag\/src",
"MyTube\\API\\External\\TagList\\": "src\/ApiDomain\/External\/TagList\/src",
"MyTube\\API\\External\\User\\": "src\/ApiDomain\/External\/User\/src",
"MyTube\\API\\External\\Video\\": "src\/ApiDomain\/External\/Video\/src",
"MyTube\\API\\External\\VideoList\\": "src\/ApiDomain\/External\/VideoList\/src",
"MyTube\\Data\\Business\\": "src\/DataDomain\/Business\/src",
"MyTube\\Data\\Log\\": "src\/DataDomain\/Log\/src",
"MyTube\\Handling\\Registration\\": "src\/HandlingDomain\/Registration\/src",
"MyTube\\Handling\\Role\\": "src\/HandlingDomain\/Role\/src",
"MyTube\\Handling\\Tag\\": "src\/HandlingDomain\/Tag\/src",
"MyTube\\Handling\\TagList\\": "src\/HandlingDomain\/TagList\/src",
"MyTube\\Handling\\User\\": "src\/HandlingDomain\/User\/src",
"MyTube\\Handling\\UserSession\\": "src\/HandlingDomain\/UserSession\/src",
"MyTube\\Handling\\Video\\": "src\/HandlingDomain\/Video\/src",
"MyTube\\Handling\\VideoList\\": "src\/HandlingDomain\/VideoList\/src",
"MyTube\\Infrastructure\\Database\\": "src\/Infrastructure\/Database\/src",
"MyTube\\Infrastructure\\DependencyInjection\\": "src\/Infrastructure\/DependencyInjection\/src",
"MyTube\\Infrastructure\\Encryption\\": "src\/Infrastructure\/Encryption\/src",
"MyTube\\Infrastructure\\Exception\\": "src\/Infrastructure\/Exception\/src",
"MyTube\\Infrastructure\\Logging\\": "src\/Infrastructure\/Logging\/src",
"MyTube\\Infrastructure\\Rbac\\": "src\/Infrastructure\/Rbac\/src",
"MyTube\\Infrastructure\\Request\\": "src\/Infrastructure\/Request\/src",
"MyTube\\Infrastructure\\Response\\": "src\/Infrastructure\/Response\/src",
"MyTube\\Infrastructure\\Session\\": "src\/Infrastructure\/Session\/src",
"MyTube\\Infrastructure\\UuidGenerator\\": "src\/Infrastructure\/UuidGenerator\/src"
}
},
"extra": {
"teewurst\/psr4-advanced-wildcard-composer-plugin": {
"autoload": {
"psr-4": {
"MyTube\\API\\%s\\%s\\": "src\/ApiDomain\/{*}\/{*}\/src\/",
"MyTube\\Data\\%s\\": "src\/DataDomain\/{*}\/src\/",
"MyTube\\Handling\\%s\\": "src\/HandlingDomain\/{*}\/src\/",
"MyTube\\Infrastructure\\%s\\": "src\/Infrastructure\/{*}\/src\/"
}
},
"autoload-dev": {
"psr-4": {
"MyTube\\API\\%s\\%s\\": "src\/ApiDomain\/{*}\/{*}\/src\/",
"MyTube\\Data\\%s\\": "src\/DataDomain\/{*}\/src\/",
"MyTube\\Handling\\%s\\": "src\/HandlingDomain\/{*}\/src\/",
"MyTube\\Infrastructure\\%s\\": "src\/Infrastructure\/{*}\/src\/"
}
}
}
},
"scripts": {
"dmg": "php bin\/doctrine-migrations.php migrations:generate",
"dmm": "php bin\/doctrine-migrations.php migrations:migrate --no-interaction",
"dmlg": "php bin\/doctrine-migrations-log.php migrations:generate",
"dmlm": "php bin\/doctrine-migrations-log.php migrations:migrate --no-interaction",
"console": "php bin\/console.php",
"createApi": "php bin\/createApi.php",
"createPipeline": "php bin\/createPipeline.php",
"da": [
"composer dump-autoload",
"composer dump-autoload --dev"
]
},
"config": {
"allow-plugins": {
"teewurst\/psr4-advanced-wildcard-composer-plugin": true
}
},
"require-dev": {
"symfony\/dotenv": "^6.3"
},
"autoload-dev": {
"psr-4": {
"MyTube\\API\\External\\Authentication\\": "src\/ApiDomain\/External\/Authentication\/src",
"MyTube\\API\\External\\Health\\": "src\/ApiDomain\/External\/Health\/src",
"MyTube\\API\\External\\Tag\\": "src\/ApiDomain\/External\/Tag\/src",
"MyTube\\API\\External\\TagList\\": "src\/ApiDomain\/External\/TagList\/src",
"MyTube\\API\\External\\User\\": "src\/ApiDomain\/External\/User\/src",
"MyTube\\API\\External\\Video\\": "src\/ApiDomain\/External\/Video\/src",
"MyTube\\API\\External\\VideoList\\": "src\/ApiDomain\/External\/VideoList\/src",
"MyTube\\Data\\Business\\": "src\/DataDomain\/Business\/src",
"MyTube\\Data\\Log\\": "src\/DataDomain\/Log\/src",
"MyTube\\Handling\\Registration\\": "src\/HandlingDomain\/Registration\/src",
"MyTube\\Handling\\Role\\": "src\/HandlingDomain\/Role\/src",
"MyTube\\Handling\\Tag\\": "src\/HandlingDomain\/Tag\/src",
"MyTube\\Handling\\TagList\\": "src\/HandlingDomain\/TagList\/src",
"MyTube\\Handling\\User\\": "src\/HandlingDomain\/User\/src",
"MyTube\\Handling\\UserSession\\": "src\/HandlingDomain\/UserSession\/src",
"MyTube\\Handling\\Video\\": "src\/HandlingDomain\/Video\/src",
"MyTube\\Handling\\VideoList\\": "src\/HandlingDomain\/VideoList\/src",
"MyTube\\Infrastructure\\Database\\": "src\/Infrastructure\/Database\/src",
"MyTube\\Infrastructure\\DependencyInjection\\": "src\/Infrastructure\/DependencyInjection\/src",
"MyTube\\Infrastructure\\Encryption\\": "src\/Infrastructure\/Encryption\/src",
"MyTube\\Infrastructure\\Exception\\": "src\/Infrastructure\/Exception\/src",
"MyTube\\Infrastructure\\Logging\\": "src\/Infrastructure\/Logging\/src",
"MyTube\\Infrastructure\\Rbac\\": "src\/Infrastructure\/Rbac\/src",
"MyTube\\Infrastructure\\Request\\": "src\/Infrastructure\/Request\/src",
"MyTube\\Infrastructure\\Response\\": "src\/Infrastructure\/Response\/src",
"MyTube\\Infrastructure\\Session\\": "src\/Infrastructure\/Session\/src",
"MyTube\\Infrastructure\\UuidGenerator\\": "src\/Infrastructure\/UuidGenerator\/src"
}
}
}

72
composer.json Normal file
View File

@ -0,0 +1,72 @@
{
"require": {
"laminas/laminas-servicemanager": "^3.21",
"mezzio/mezzio-laminasrouter": "^3.8",
"reinfi/zf-dependency-injection": "^5.4",
"laminas/laminas-config-aggregator": "^1.13",
"doctrine/doctrine-orm-module": "5.0.*",
"doctrine/migrations": "^3.5",
"doctrine/orm": "2.11.3",
"doctrine/persistence": "2.2.*",
"ramsey/uuid-doctrine": "^1.8",
"roave/psr-container-doctrine": "^3.1",
"mezzio/mezzio": "^3.17",
"mezzio/mezzio-helpers": "^5.15",
"symfony/cache": "5.4.8",
"doctrine/dbal": "^3.6",
"teewurst/psr4-advanced-wildcard-composer-plugin": "^3.0",
"laminas/laminas-crypt": "^3.10",
"monolog/monolog": "^3.4",
"laminas/laminas-mail": "^2.23",
"teewurst/pipeline": "^3.0",
"guzzlehttp/guzzle": "^7.8",
"micilini/video-stream": "^1.0",
"nesbot/carbon": "^3.0"
},
"autoload": {
"psr-4": {
"MyTube\\API\\Console\\": "src/ApiDomain/Console/src/"
}
},
"extra": {
"teewurst/psr4-advanced-wildcard-composer-plugin": {
"autoload": {
"psr-4": {
"MyTube\\API\\%s\\%s\\": "src/ApiDomain/{*}/{*}/src/",
"MyTube\\Data\\%s\\": "src/DataDomain/{*}/src/",
"MyTube\\Handling\\%s\\": "src/HandlingDomain/{*}/src/",
"MyTube\\Infrastructure\\%s\\": "src/Infrastructure/{*}/src/"
}
},
"autoload-dev": {
"psr-4": {
"MyTube\\API\\%s\\%s\\": "src/ApiDomain/{*}/{*}/src/",
"MyTube\\Data\\%s\\": "src/DataDomain/{*}/src/",
"MyTube\\Handling\\%s\\": "src/HandlingDomain/{*}/src/",
"MyTube\\Infrastructure\\%s\\": "src/Infrastructure/{*}/src/"
}
}
}
},
"scripts": {
"dmg": "php bin/doctrine-migrations.php migrations:generate",
"dmm": "php bin/doctrine-migrations.php migrations:migrate --no-interaction",
"dmlg": "php bin/doctrine-migrations-log.php migrations:generate",
"dmlm": "php bin/doctrine-migrations-log.php migrations:migrate --no-interaction",
"console": "php bin/console.php",
"createApi": "php bin/createApi.php",
"createPipeline": "php bin/createPipeline.php",
"da": [
"composer dump-autoload",
"composer dump-autoload --dev"
]
},
"config": {
"allow-plugins": {
"teewurst/psr4-advanced-wildcard-composer-plugin": true
}
},
"require-dev": {
"symfony/dotenv": "^6.3"
}
}

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

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

View File

@ -0,0 +1,29 @@
<?php
return [
'api' => [
'keys' => [
'myTube' => $_ENV['HOMEPAGE_API_KEY'],
'notification' => $_ENV['NOTIFICATION_API_KEY'],
],
'services' => [
'myTube' => [
'host' => 'myTube-backend-nginx',
'apis' => [
]
],
'notification' => [
'host' => 'notification-backend-nginx',
'apis' => [
'send-mail' => [
'path' => '/api/mail/send',
'method' => 'POST'
],
],
],
],
],
];

View File

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

View File

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

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
return [
// Provides application-wide services.
// We recommend using fully-qualified class names whenever possible as
// service names.
'dependencies' => [
// Use 'aliases' to alias a service name to another service. The
// key is the alias name, the value is the service to which it points.
'aliases' => [
// Fully\Qualified\ClassOrInterfaceName::class => Fully\Qualified\ClassName::class,
],
// Use 'invokables' for constructor-less services, or services that do
// not require arguments to the constructor. Map a service name to the
// class name.
'invokables' => [
// Fully\Qualified\InterfaceName::class => Fully\Qualified\ClassName::class,
],
// Use 'factories' for services provided by callbacks/factory classes.
'factories' => [
// Fully\Qualified\ClassName::class => Fully\Qualified\FactoryName::class,
],
],
];

View File

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

View File

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

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
use Laminas\ConfigAggregator\ConfigAggregator;
return [
// Toggle the configuration cache. Set this to boolean false, or remove the
// directive, to disable configuration caching. Toggling development mode
// will also disable it by default; clear the configuration cache using
// `composer clear-config-cache`.
ConfigAggregator::ENABLE_CACHE => false,
// Enable debugging; typically used to provide debugging information within myTubes.
'debug' => false,
'mezzio' => [
// Provide myTubes for the error handling middleware to use when
// generating responses.
'error_handler' => [
'myTube_404' => 'error::404',
'myTube_error' => 'error::error',
],
],
];

97
config/config.php Normal file
View File

@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
use Laminas\ConfigAggregator\ArrayProvider;
use Laminas\ConfigAggregator\ConfigAggregator;
use Laminas\ConfigAggregator\PhpFileProvider;
// To enable or disable caching, set the `ConfigAggregator::ENABLE_CACHE` boolean in
// `config/autoload/local.php`.
$cacheConfig = [
'config_cache_path' => 'data/cache/config-cache.php',
];
$aggregator = new ConfigAggregator([
// Include cache configuration
new ArrayProvider($cacheConfig),
\Mezzio\Helper\ConfigProvider::class,
\Mezzio\ConfigProvider::class,
\Mezzio\Router\ConfigProvider::class,
\Mezzio\Router\LaminasRouter\ConfigProvider::class,
\Laminas\Diactoros\ConfigProvider::class,
\Laminas\HttpHandlerRunner\ConfigProvider::class,
\Laminas\Validator\ConfigProvider::class,
\Laminas\Router\ConfigProvider::class,
\Reinfi\DependencyInjection\ConfigProvider::class,
\DoctrineORMModule\ConfigProvider::class,
\DoctrineModule\ConfigProvider::class,
// Swoole config to overwrite some services (if installed)
class_exists(\Mezzio\Swoole\ConfigProvider::class)
? \Mezzio\Swoole\ConfigProvider::class
: function (): array {
return [];
},
// Data
\MyTube\Data\Business\ConfigProvider::class,
\MyTube\Data\Log\ConfigProvider::class,
// Infrastructure
\MyTube\Infrastructure\Database\ConfigProvider::class,
\MyTube\Infrastructure\DependencyInjection\ConfigProvider::class,
\MyTube\Infrastructure\Encryption\ConfigProvider::class,
\MyTube\Infrastructure\Exception\ConfigProvider::class,
\MyTube\Infrastructure\Logging\ConfigProvider::class,
\MyTube\Infrastructure\Rbac\ConfigProvider::class,
\MyTube\Infrastructure\Request\ConfigProvider::class,
\MyTube\Infrastructure\Session\ConfigProvider::class,
// HandlingDomain
\MyTube\Handling\User\ConfigProvider::class,
\MyTube\Handling\UserSession\ConfigProvider::class,
\MyTube\Handling\Registration\ConfigProvider::class,
\MyTube\Handling\Video\ConfigProvider::class,
\MyTube\Handling\VideoList\ConfigProvider::class,
\MyTube\Handling\Tag\ConfigProvider::class,
\MyTube\Handling\TagList\ConfigProvider::class,
// API
/// Command
\MyTube\API\Console\ConfigProvider::class,
/// External
\MyTube\API\External\Health\ConfigProvider::class,
\MyTube\API\External\User\ConfigProvider::class,
\MyTube\API\External\Authentication\ConfigProvider::class,
\MyTube\API\External\Video\ConfigProvider::class,
\MyTube\API\External\VideoList\ConfigProvider::class,
\MyTube\API\External\Tag\ConfigProvider::class,
\MyTube\API\External\TagList\ConfigProvider::class,
/// Internal
// Load application config in a pre-defined order in such a way that local settings
// overwrite global settings. (Loaded as first to last):
// - `global.php`
// - `*.global.php`
// - `local.php`
// - `*.local.php`
new PhpFileProvider(realpath(__DIR__) . '/autoload/{{,*.}global,{,*.}local}.php'),
// Load development config if it exists
new PhpFileProvider(realpath(__DIR__) . '/development.config.php'),
], $cacheConfig['config_cache_path']);
return $aggregator->getMergedConfig();

21
config/container.php Normal file
View File

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

View File

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

94
config/pipeline.php Normal file
View File

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
use MyTube\Infrastructure\Exception\Middleware\MyTubeExceptionHandlerMiddleware;
use MyTube\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
use MyTube\Infrastructure\Request\Middleware\AnalyzeHeaderMiddleware;
use MyTube\Infrastructure\Session\Middleware\SessionMiddleware;
use Laminas\Stratigility\Middleware\ErrorHandler;
use Mezzio\Application;
use Mezzio\Handler\NotFoundHandler;
use Mezzio\Helper\ServerUrlMiddleware;
use Mezzio\Helper\UrlHelperMiddleware;
use Mezzio\MiddlewareFactory;
use Mezzio\Router\Middleware\DispatchMiddleware;
use Mezzio\Router\Middleware\ImplicitHeadMiddleware;
use Mezzio\Router\Middleware\ImplicitOptionsMiddleware;
use Mezzio\Router\Middleware\MethodNotAllowedMiddleware;
use Mezzio\Router\Middleware\RouteMiddleware;
use Psr\Container\ContainerInterface;
/**
* Setup middleware pipeline:
*/
return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container): void {
// The error handler should be the first (most outer) middleware to catch
// all Exceptions.
$app->pipe(ErrorHandler::class);
$app->pipe(ServerUrlMiddleware::class);
// Pipe more middleware here that you want to execute on every request:
// - bootstrapping
// - pre-conditions
// - modifications to outgoing responses
//
// Piped Middleware may be either callables or service names. Middleware may
// also be passed as an array; each item in the array must resolve to
// middleware eventually (i.e., callable or service name).
//
// Middleware can be attached to specific paths, allowing you to mix and match
// applications under a common domain. The handlers in each middleware
// attached this way will see a URI with the matched path segment removed.
//
// i.e., path of "/api/member/profile" only passes "/member/profile" to $apiMiddleware
// - $app->pipe('/api', $apiMiddleware);
// - $app->pipe('/docs', $apiDocMiddleware);
// - $app->pipe('/files', $filesMiddleware);
// Register the routing middleware in the middleware pipeline.
// This middleware registers the Mezzio\Router\RouteResult request attribute.
$app->pipe(RouteMiddleware::class);
// The following handle routing failures for common conditions:
// - HEAD request but no routes answer that method
// - OPTIONS request but no routes answer that method
// - method not allowed
// Order here matters; the MethodNotAllowedMiddleware should be placed
// after the Implicit*Middleware.
$app->pipe(ImplicitHeadMiddleware::class);
$app->pipe(ImplicitOptionsMiddleware::class);
$app->pipe(MethodNotAllowedMiddleware::class);
// Seed the UrlHelper with the routing results:
$app->pipe(UrlHelperMiddleware::class);
//// Pre MyTube Space
$app->pipe(MyTubeExceptionHandlerMiddleware::class);
$app->pipe(AnalyzeHeaderMiddleware::class);
$app->pipe(AnalyzeBodyMiddleware::class);
//// MyTube Space
$app->pipe(SessionMiddleware::class);
// Add more middleware here that needs to introspect the routing results; this
// might include:
//
// - route-based authentication
// - route-based validation
// - etc.
// Register the dispatch middleware in the middleware pipeline
$app->pipe(DispatchMiddleware::class);
// At this point, if no Response is returned by any middleware, the
// NotFoundHandler kicks in; alternately, you can provide other fallback
// middleware to execute.
$app->pipe(NotFoundHandler::class);
};

52
config/routes.php Normal file
View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
use Mezzio\Application;
use Mezzio\MiddlewareFactory;
use Psr\Container\ContainerInterface;
use MyTube\Infrastructure\Session\Middleware\LoggedInUserMiddleware;
/**
* laminas-router route configuration
*
* @see https://docs.laminas.dev/laminas-router/
*
* Setup routes with a single request method:
*
* $app->get('/', App\Handler\HomePageHandler::class, 'home');
* $app->post('/album', App\Handler\AlbumCreateHandler::class, 'album.create');
* $app->put('/album/:id', App\Handler\AlbumUpdateHandler::class, 'album.put');
* $app->patch('/album/:id', App\Handler\AlbumUpdateHandler::class, 'album.patch');
* $app->delete('/album/:id', App\Handler\AlbumDeleteHandler::class, 'album.delete');
*
* Or with multiple request methods:
*
* $app->route('/contact', App\Handler\ContactHandler::class, ['GET', 'POST', ...], 'contact');
*
* Or handling all request methods:
*
* $app->route('/contact', App\Handler\ContactHandler::class)->setName('contact');
*
* or:
*
* $app->route(
* '/contact',
* App\Handler\ContactHandler::class,
* Mezzio\Router\Route::HTTP_METHOD_ANY,
* 'contact'
* );
*/
return static function (Application $app, MiddlewareFactory $factory, ContainerInterface $container): void {
$config = $container->get('config');
foreach ($config['routes'] as $routeConfig) {
$app->route(
name: $routeConfig['name'],
path: $routeConfig['path'],
middleware: $routeConfig['middleware'],
methods: $routeConfig['allowed_methods']
);
}
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace MyTube\Migrations\MyTube;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240323095155 extends AbstractMigration
{
public function getDescription(): string
{
return 'add duration to video';
}
public function up(Schema $schema): void
{
$sql = "ALTER TABLE video
ADD COLUMN duration int NULL after title
;";
$this->addSql($sql);
}
public function down(Schema $schema): void
{
$this->addSql("ALTER TABLE video
DROP COLUMN duration
;");
}
}

View File

@ -0,0 +1,51 @@
version: '3'
networks:
myTube:
external: true
services:
myTube-backend-mysql:
image: myTube-backend-mysql
networks:
- myTube
build:
context: ./../
dockerfile: ./docker/mysql/dockerfile
volumes:
- /Users/flo/dev/backend/myTube/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
myTube-backend-app:
image: myTube-backend-app
networks:
- myTube
build:
context: ./../
dockerfile: ./docker/php/dockerfile
volumes:
- /Users/flo/dev/backend/myTube/:/var/www/html:z
ports:
- 9000:9000
depends_on:
myTube-backend-mysql:
condition: service_healthy
myTube-backend-nginx:
image: myTube-backend-nginx
networks:
- myTube
build:
context: ./../
dockerfile: ./docker/nginx/dockerfile
ports:
- 8080:80
depends_on:
- myTube-backend-app

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

@ -0,0 +1,51 @@
version: '3'
networks:
mytube:
external: true
services:
mytube-backend-mysql:
image: mytube-backend-mysql
networks:
- mytube
build:
context: ./../
dockerfile: ./docker/mysql/dockerfile
volumes:
- ./../var/db:/var/lib/mysql:z
environment:
MYSQL_USER: ${DB_USER}
MYSQL_PASSWORD: ${DB_PASSWORD}
ports:
- 3306:3306
healthcheck:
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
timeout: 20s
retries: 10
mytube-backend-app:
image: mytube-backend-app
networks:
- mytube
build:
context: ./../
dockerfile: ./docker/php/dockerfile
volumes:
- ./../:/var/www/html:z
ports:
- 9000:9000
depends_on:
mytube-backend-mysql:
condition: service_healthy
mytube-backend-nginx:
image: mytube-backend-nginx
networks:
- mytube
build:
context: ./../
dockerfile: ./docker/nginx/dockerfile
ports:
- 8080:80
depends_on:
- mytube-backend-app

7
docker/mysql/dockerfile Normal file
View File

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

View File

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

View File

@ -0,0 +1,17 @@
upstream host-backend-app {
server myTube-backend-app:9000;
}
server {
listen 80 default_server;
client_max_body_size 10000M;
location / {
fastcgi_pass host-backend-app;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/html/public/index.php;
}
}

View File

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

5
docker/nginx/dockerfile Normal file
View File

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

View File

@ -0,0 +1,6 @@
file_uploads = On
max_file_uploads = 1000
memory_limit = 10000M
upload_max_filesize = 10000M
post_max_size = 10000M
max_execution_time = 1000

18
docker/php/dockerfile Normal file
View File

@ -0,0 +1,18 @@
FROM php:8.1-fpm
RUN apt-get -y update
RUN apt-get -y install git
RUN apt-get install -y ffmpeg
RUN chown -R www-data:www-data /var/www/html
WORKDIR /var/www/html
RUN docker-php-ext-install pdo pdo_mysql
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN useradd -m docker && echo "docker:docker" | chpasswd && adduser docker sudo
USER docker
COPY docker/php/config/uploads.ini /usr/local/etc/php/conf.d
CMD ["php-fpm"]

26
phpunit.xml.dist Normal file
View File

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

19
public/.htaccess Normal file
View File

@ -0,0 +1,19 @@
RewriteEngine On
# The following rule allows authentication to work with fast-cgi
RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# The following rule tells Apache that if the requested filename
# exists, simply serve it.
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [NC,L]
# The following rewrites all other queries to index.php. The
# condition ensures that if you are using Apache aliases to do
# mass virtual hosting, the base path will be prepended to
# allow proper resolution of the index.php file; it will work
# in non-aliased environments as well, providing a safe, one-size
# fits all solution.
RewriteCond $0::%{REQUEST_URI} ^([^:]*+(?::[^:]*+)*?)::(/.+?)\1$
RewriteRule .+ - [E=BASE:%2]
RewriteRule .* %{ENV:BASE}index.php [NC,L]

48
public/index.php Normal file
View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
use Mezzio\Application;
use Mezzio\MiddlewareFactory;
if (PHP_SAPI === 'cli-server' && $_SERVER['SCRIPT_FILENAME'] !== __FILE__) {
return false;
}
require './../vendor/autoload.php';
require './../config/autoload/defines.php';
function throwableToMessageArray(Throwable $e): array {
$messageArray = [];
while($e !== null) {
$messageArray[] = $e->getMessage();
$e = $e->getPrevious();
}
return $messageArray;
}
function throwableToMessageArrayString(Throwable $e): string {
return implode( PHP_EOL, throwableToMessageArray($e) );
}
/**
* Self-called anonymous function that creates its own scope and keeps the global namespace clean.
*/
(function () {
/** @var \Psr\Container\ContainerInterface $container */
$container = require './../config/container.php';
/** @var Application $app */
$app = $container->get(Application::class);
$factory = $container->get(MiddlewareFactory::class);
// Execute programmatic/declarative middleware pipeline and routing
// configuration statements
(require './../config/pipeline.php')($app, $factory, $container);
(require './../config/routes.php')($app, $factory, $container);
$app->run();
})();

View File

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

View File

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

View File

@ -0,0 +1,64 @@
<?php
namespace MyTube\API\Console\Command;
use MyTube\Data\Business\Entity\Tag;
use MyTube\Data\Business\Entity\Video;
use MyTube\Data\Business\Manager\MyTubeEntityManager;
use MyTube\Data\Business\Repository\VideoRepository;
use MyTube\Handling\Video\Analyzer\VideoDurationAnalyzer;
use MyTube\Handling\Video\Analyzer\VideoTitleAnalyzer;
use MyTube\Infrastructure\Logging\Logger\Logger;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'analyze:video', description: 'Analyzes video titles and add tags')]
class AnalyzeVideoTitlesCommand extends Command
{
private readonly VideoRepository $videoRepository;
public function __construct(
private readonly MyTubeEntityManager $entityManager,
private readonly VideoTitleAnalyzer $titleAnalyzer,
private readonly VideoDurationAnalyzer $durationAnalyzer,
private readonly Logger $logger,
) {
parent::__construct($this->getName());
$this->videoRepository = $this->entityManager->getRepository(Video::class);
}
protected function execute(
InputInterface $input,
OutputInterface $output
): int {
$io = new SymfonyStyle($input, $output);
try {
$videos = $this->videoRepository->findAll();
/** @var Video $video */
foreach ($videos as $video) {
$io->info($video->getTitle());
$this->titleAnalyzer->analyze($video);
$this->durationAnalyzer->analyze($video);
$this->entityManager->persist($video);
$this->entityManager->flush();
}
$io->success('OK!');
} catch (\Throwable $e) {
$io->error($e->getMessage());
$io->error($e->getTraceAsString());
$this->logger->error($e->getMessage(), ['exception' => $e]);
return Command::FAILURE;
}
return Command::SUCCESS;
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace MyTube\API\Console\Command;
use MyTube\Data\Business\Entity\Role;
use MyTube\Data\Business\Entity\User;
use MyTube\Data\Business\Repository\RoleRepository;
use MyTube\Data\Business\Repository\UserRepository;
use MyTube\Data\Business\Manager\MyTubeEntityManager;
use MyTube\Infrastructure\Encryption\Client\EncryptionClient;
use MyTube\Infrastructure\Logging\Logger\Logger;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'init:data', description: 'Initializes the applications data')]
class InitializeDataCommand extends Command
{
private readonly RoleRepository $roleRepository;
private readonly UserRepository $userRepository;
public function __construct(
private readonly EncryptionClient $encryptionClient,
private readonly MyTubeEntityManager $entityManager,
private readonly Logger $logger,
) {
parent::__construct($this->getName());
$this->roleRepository = $this->entityManager->getRepository(Role::class);
$this->userRepository = $this->entityManager->getRepository(User::class);
}
protected function execute(
InputInterface $input,
OutputInterface $output
): int {
$io = new SymfonyStyle($input, $output);
try {
$admin = $this->roleRepository->findOneBy([
'identifier' => 'admin'
]) ?? null;
if ($admin === null) {
throw new \Exception('Admin role does not exist!');
}
$adminUser = $this->userRepository->findOneBy([
'roleId' => $admin->getId()
]) ?? null;
if ($adminUser === null) {
$io->info('Create admin');
$adminUser = new User();
$adminUser->setRole($admin);
$adminUser->setUsername($_ENV['INIT_USER_NAME']);
$adminUser->setMail($_ENV['INIT_USER_MAIL']);
$adminUser->setPassword(
$this->encryptionClient->encrypt(
$_ENV['INIT_USER_PASSWORD']
)
);
$this->entityManager->persist($adminUser);
$this->entityManager->flush();
}
$io->success('OK!');
} catch (\Throwable $e) {
$io->error($e->getMessage());
$io->error($e->getTraceAsString());
$this->logger->error($e->getMessage(), ['exception' => $e]);
return Command::FAILURE;
}
return Command::SUCCESS;
}
}

View File

@ -0,0 +1,98 @@
<?php
namespace MyTube\API\Console\Command;
use MyTube\Data\Business\Entity\Permission;
use MyTube\Data\Business\Entity\Role;
use MyTube\Data\Business\Repository\PermissionRepository;
use MyTube\Data\Business\Repository\RoleRepository;
use MyTube\Data\Business\Manager\MyTubeEntityManager;
use MyTube\Infrastructure\Logging\Logger\Logger;
use Reinfi\DependencyInjection\Service\ConfigService;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'rbac:update', description: 'Updates RBAC entries in DB')]
class RbacUpdateCommand extends Command
{
private array $rbacConfig;
private RoleRepository $roleRepository;
private PermissionRepository $permissionRepository;
public function __construct(
private readonly ConfigService $configService,
private readonly MyTubeEntityManager $entityManager,
private readonly Logger $logger,
) {
parent::__construct($this->getName());
$this->roleRepository = $this->entityManager->getRepository(Role::class);
$this->permissionRepository = $this->entityManager->getRepository(Permission::class);
$this->rbacConfig = $this->configService->resolve('myTube-rbac')->toArray();
}
protected function execute(
InputInterface $input,
OutputInterface $output
): int {
$io = new SymfonyStyle($input, $output);
try {
$roles = [];
foreach ($this->rbacConfig['roles'] as $roleIdentifier) {
$role = $this->roleRepository->findOneBy([
'identifier' => $roleIdentifier
]) ?? null;
if ($role === null) {
$role = new Role();
$role->setIdentifier($roleIdentifier);
$this->entityManager->persist($role);
$this->entityManager->flush();
}
$roles[$roleIdentifier] = $role;
}
foreach ($roles as $role) {
$roleIdentifier = $role->getIdentifier();
foreach ($this->rbacConfig['permissions'][$roleIdentifier] as $permissionIdentifier) {
$io->info($role->getIdentifier() . '.' . $permissionIdentifier);
$permission = $this->permissionRepository->findOneBy([
'identifier' => $permissionIdentifier
]) ?? null;
if ($permission === null) {
$permission = new Permission();
$permission->setIdentifier($permissionIdentifier);
$this->entityManager->persist($permission);
$this->entityManager->flush();
}
$role->addPermission($permission);
$this->entityManager->persist($role);
$this->entityManager->flush();
}
}
$io->success('OK!');
} catch (\Throwable $e) {
$io->error($e->getMessage());
$io->error($e->getTraceAsString());
$this->logger->error($e->getMessage(), ['exception' => $e]);
return Command::FAILURE;
}
return Command::SUCCESS;
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace MyTube\API\Console\Command;
use MyTube\Data\Business\Entity\Tag;
use MyTube\Data\Business\Entity\Video;
use MyTube\Data\Business\Manager\MyTubeEntityManager;
use MyTube\Data\Business\Repository\VideoRepository;
use MyTube\Handling\Video\Analyzer\VideoTitleAnalyzer;
use MyTube\Infrastructure\Logging\Logger\Logger;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'analyze:untagged', description: 'Returns the names of all untagged videos')]
class ReadUntaggedVideosCommand extends Command
{
private readonly VideoRepository $videoRepository;
public function __construct(
private readonly MyTubeEntityManager $entityManager,
private readonly Logger $logger,
) {
parent::__construct($this->getName());
$this->videoRepository = $this->entityManager->getRepository(Video::class);
}
protected function execute(
InputInterface $input,
OutputInterface $output
): int {
$io = new SymfonyStyle($input, $output);
try {
$qb = $this->videoRepository
->createQueryBuilder('v')
->leftJoin('v.tags', 't')
->groupBy('v.id')
->having('COUNT(t.id) = 0')
->orderBy('v.title');
$videos = $qb->getQuery()->execute();
/** @var Video $video */
foreach ($videos as $video) {
echo $video->getTitle() . PHP_EOL;
}
$io->info('total: ' . count($videos));
$io->success('OK!');
} catch (\Throwable $e) {
$io->error($e->getMessage());
$io->error($e->getTraceAsString());
$this->logger->error($e->getMessage(), ['exception' => $e]);
return Command::FAILURE;
}
return Command::SUCCESS;
}
}

View File

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

View File

@ -0,0 +1,43 @@
<?php
use MyTube\API\External\Authentication\Handler\ConfirmRegistrationHandler;
use MyTube\API\External\Authentication\Handler\LoginUserHandler;
use MyTube\API\External\Authentication\Handler\LogoutUserHandler;
use MyTube\API\External\Authentication\Handler\RegisterUserHandler;
use MyTube\Infrastructure\Session\Middleware\LoggedInUserMiddleware;
return [
[
'name' => 'auth.login-user',
'path' => '/api/auth/login-user',
'allowed_methods' => ['POST'],
'middleware' => [
LoginUserHandler::class
],
],
[
'name' => 'auth.logout-user',
'path' => '/api/auth/logout-user',
'allowed_methods' => ['POST'],
'middleware' => [
LoggedInUserMiddleware::class,
LogoutUserHandler::class
],
],
[
'name' => 'auth.confirm-registration',
'path' => '/api/auth/confirm-registration',
'allowed_methods' => ['POST'],
'middleware' => [
ConfirmRegistrationHandler::class
],
],
[
'name' => 'auth.register-user',
'path' => '/api/auth/register-user',
'allowed_methods' => ['POST'],
'middleware' => [
RegisterUserHandler::class
],
],
];

View File

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

View File

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

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\Authentication\Handler;
use MyTube\API\External\User\Formatter\UserFormatter;
use MyTube\Handling\Registration\Handler\Command\ConfirmRegistration\ConfirmRegistrationCommandBuilder;
use MyTube\Handling\Registration\Handler\Command\ConfirmRegistration\ConfirmRegistrationCommandHandler;
use MyTube\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
use MyTube\Infrastructure\Session\Middleware\SessionMiddleware;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Ramsey\Uuid\Uuid;
class ConfirmRegistrationHandler implements RequestHandlerInterface
{
public function __construct(
private readonly ConfirmRegistrationCommandHandler $handler,
private readonly ConfirmRegistrationCommandBuilder $builder,
private readonly UserFormatter $userFormatter
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$data = $request->getAttribute(AnalyzeBodyMiddleware::JSON_DATA);
$query = $this->builder->build(
Uuid::fromString($data['id']),
$data['password'],
$data['passwordConfirmation'],
);
$result = $this->handler->execute($query);
return new JsonResponse(
$this->userFormatter->format($result)
);
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\Authentication\Handler;
use MyTube\Handling\UserSession\Handler\Command\LoginUser\LoginUserCommandBuilder;
use MyTube\Handling\UserSession\Handler\Command\LoginUser\LoginUserCommandHandler;
use MyTube\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
use MyTube\Infrastructure\Session\Middleware\SessionMiddleware;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class LoginUserHandler implements RequestHandlerInterface
{
public function __construct(
private readonly LoginUserCommandHandler $handler,
private readonly LoginUserCommandBuilder $builder
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$session = $request->getAttribute(SessionMiddleware::SESSION_ATTRIBUTE);
$data = $request->getAttribute(AnalyzeBodyMiddleware::JSON_DATA);
$query = $this->builder->build(
$session,
$data['identifier'],
$data['password'],
);
$result = $this->handler->execute($query);
return new JsonResponse([
'sessionId' => $result->getId()->toString()
]);
}
}

View File

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

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\Authentication\Handler;
use MyTube\Handling\Registration\Handler\Command\RegisterUser\RegisterUserCommandBuilder;
use MyTube\Handling\Registration\Handler\Command\RegisterUser\RegisterUserCommandHandler;
use MyTube\Infrastructure\Exception\Middleware\MyTubeExceptionHandlerMiddleware;
use MyTube\Infrastructure\Logging\Logger\Logger;
use MyTube\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
use MyTube\Infrastructure\Request\Middleware\AnalyzeHeaderMiddleware;
use MyTube\Infrastructure\Response\SuccessResponse;
use MyTube\Infrastructure\Session\Middleware\SessionMiddleware;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class RegisterUserHandler implements RequestHandlerInterface
{
public function __construct(
private readonly RegisterUserCommandHandler $handler,
private readonly RegisterUserCommandBuilder $builder,
private readonly Logger $logger
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$host = $request->getAttribute(AnalyzeHeaderMiddleware::HOST_ATTRIBUTE);
$data = $request->getAttribute(AnalyzeBodyMiddleware::JSON_DATA);
$query = $this->builder->build(
$data['username'],
$data['mail'],
$host
);
$this->handler->execute($query);
return new SuccessResponse();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,41 @@
<?php
use MyTube\API\External\Tag\Handler\AddAliasHandler;
use MyTube\API\External\Tag\Handler\CreateHandler;
use MyTube\API\External\Tag\Handler\ReadDetailsHandler;
use MyTube\API\External\Tag\Handler\ReadVideoListHandler;
return [
[
'name' => 'tag.create',
'path' => '/api/tag/create[/]',
'allowed_methods' => ['POST'],
'middleware' => [
CreateHandler::class,
],
],
[
'name' => 'tag.add-alias',
'path' => '/api/tag/add-alias[/]',
'allowed_methods' => ['POST'],
'middleware' => [
AddAliasHandler::class,
],
],
[
'name' => 'tag.read-details',
'path' => '/api/tag/read-details[/]',
'allowed_methods' => ['POST'],
'middleware' => [
ReadDetailsHandler::class,
],
],
[
'name' => 'tag.read-video-list',
'path' => '/api/tag/read-video-list[/]',
'allowed_methods' => ['POST'],
'middleware' => [
ReadVideoListHandler::class,
],
],
];

View File

@ -0,0 +1,27 @@
<?php
use MyTube\API\External\Tag\Handler\AddAliasHandler;
use MyTube\API\External\Tag\Handler\CreateHandler;
use MyTube\API\External\Tag\Handler\ReadDetailsHandler;
use MyTube\API\External\Tag\Handler\ReadVideoListHandler;
use MyTube\API\External\Tag\ResponseFormatter\AddAliasResponseFormatter;
use MyTube\API\External\Tag\ResponseFormatter\CreateResponseFormatter;
use MyTube\API\External\Tag\ResponseFormatter\ReadDetailsResponseFormatter;
use MyTube\API\External\Tag\ResponseFormatter\ReadVideoListResponseFormatter;
use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
return [
'factories' => [
// Handler
AddAliasHandler::class => AutoWiringFactory::class,
CreateHandler::class => AutoWiringFactory::class,
ReadDetailsHandler::class => AutoWiringFactory::class,
ReadVideoListHandler::class => AutoWiringFactory::class,
// Response Formatter
AddAliasResponseFormatter::class => AutoWiringFactory::class,
CreateResponseFormatter::class => AutoWiringFactory::class,
ReadDetailsResponseFormatter::class => AutoWiringFactory::class,
ReadVideoListResponseFormatter::class => AutoWiringFactory::class,
],
];

View File

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

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\Tag\Handler;
use MyTube\Handling\Tag\Exception\TagAliasAlreadyExistsException;
use MyTube\Handling\Tag\Exception\TagNotFoundByIdException;
use MyTube\Handling\Tag\Handler\Command\AddAlias\AddAliasCommandHandler;
use MyTube\Handling\Tag\Handler\Command\AddAlias\AddAliasCommandBuilder;
use MyTube\API\External\Tag\ResponseFormatter\AddAliasResponseFormatter;
use MyTube\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use MyTube\Infrastructure\Response\SuccessResponse;
use Ramsey\Uuid\Uuid;
class AddAliasHandler implements RequestHandlerInterface
{
public function __construct(
private readonly AddAliasCommandHandler $addAliasCommandHandler,
private readonly AddAliasCommandBuilder $addAliasCommandBuilder,
private readonly AddAliasResponseFormatter $responseFormatter,
) {
}
/**
* @throws TagNotFoundByIdException
* @throws TagAliasAlreadyExistsException
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
$data = $request->getAttribute(AnalyzeBodyMiddleware::JSON_DATA);
$addAliasCommand = $this->addAliasCommandBuilder->build(
Uuid::fromString($data['tagId']),
$data['description']
);
$result = $this->addAliasCommandHandler->execute($addAliasCommand);
return new SuccessResponse($this->responseFormatter->format($result));
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\Tag\Handler;
use MyTube\Handling\Tag\Exception\TagAlreadyExistsException;
use MyTube\Handling\Tag\Handler\Command\Create\CreateCommandHandler;
use MyTube\Handling\Tag\Handler\Command\Create\CreateCommandBuilder;
use MyTube\API\External\Tag\ResponseFormatter\CreateResponseFormatter;
use MyTube\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use MyTube\Infrastructure\Response\SuccessResponse;
class CreateHandler implements RequestHandlerInterface
{
public function __construct(
private readonly CreateCommandHandler $createCommandHandler,
private readonly CreateCommandBuilder $createCommandBuilder,
private readonly CreateResponseFormatter $responseFormatter,
) {
}
/**
* @throws TagAlreadyExistsException
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
$data = $request->getAttribute(AnalyzeBodyMiddleware::JSON_DATA);
$createCommand = $this->createCommandBuilder->build(
$data['description']
);
$result = $this->createCommandHandler->execute($createCommand);
return new SuccessResponse($this->responseFormatter->format($result));
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\Tag\Handler;
use MyTube\Handling\Tag\Exception\TagNotFoundByIdException;
use MyTube\Handling\Tag\Handler\Query\ReadDetails\ReadDetailsQueryHandler;
use MyTube\Handling\Tag\Handler\Query\ReadDetails\ReadDetailsQueryBuilder;
use MyTube\API\External\Tag\ResponseFormatter\ReadDetailsResponseFormatter;
use MyTube\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
use MyTube\Infrastructure\Response\SuccessResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Ramsey\Uuid\Uuid;
class ReadDetailsHandler implements RequestHandlerInterface
{
public function __construct(
private readonly ReadDetailsQueryHandler $readDetailsQueryHandler,
private readonly ReadDetailsQueryBuilder $readDetailsQueryBuilder,
private readonly ReadDetailsResponseFormatter $responseFormatter,
) {
}
/**
* @throws TagNotFoundByIdException
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
$data = $request->getAttribute(AnalyzeBodyMiddleware::JSON_DATA);
$readDetailsQuery = $this->readDetailsQueryBuilder->build(
Uuid::fromString($data['tagId']),
);
$result = $this->readDetailsQueryHandler->execute($readDetailsQuery);
return new SuccessResponse($this->responseFormatter->format($result));
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\Tag\Handler;
use MyTube\Handling\Tag\Handler\Query\ReadVideoList\ReadVideoListQueryHandler;
use MyTube\Handling\Tag\Handler\Query\ReadVideoList\ReadVideoListQueryBuilder;
use MyTube\API\External\Tag\ResponseFormatter\ReadVideoListResponseFormatter;
use MyTube\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
use MyTube\Infrastructure\Response\SuccessResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Ramsey\Uuid\Uuid;
class ReadVideoListHandler implements RequestHandlerInterface
{
public function __construct(
private readonly ReadVideoListQueryHandler $readVideoListQueryHandler,
private readonly ReadVideoListQueryBuilder $readVideoListQueryBuilder,
private readonly ReadVideoListResponseFormatter $responseFormatter,
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$data = $request->getAttribute(AnalyzeBodyMiddleware::JSON_DATA);
$readVideoListQuery = $this->readVideoListQueryBuilder->build(
Uuid::fromString($data['tagId']),
$data['query'] ?? null,
$data['page'],
$data['perPage'],
);
$result = $this->readVideoListQueryHandler->execute($readVideoListQuery);
return new SuccessResponse($this->responseFormatter->format($result));
}
}

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\Tag\ResponseFormatter;
use MyTube\Handling\Tag\Handler\Command\AddAlias\AddAliasCommandResult;
class AddAliasResponseFormatter
{
public function format(AddAliasCommandResult $addAliasCommandResult): array
{
$alias = $addAliasCommandResult->getAlias();
$tag = $alias->getTag();
return [
'id' => $alias->getId()->toString(),
'description' => $alias->getDescription(),
'tag' => [
'id' => $tag->getId()->toString(),
'description' => $tag->getDescription()
],
];
}
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\Tag\ResponseFormatter;
use MyTube\Handling\Tag\Handler\Command\Create\CreateCommandResult;
class CreateResponseFormatter
{
public function format(CreateCommandResult $createCommandResult): array
{
$tag = $createCommandResult->getTag();
return [
'id' => $tag->getId()->toString(),
'description' => $tag->getDescription(),
];
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\Tag\ResponseFormatter;
use MyTube\Data\Business\Entity\TagAlias;
use MyTube\Handling\Tag\Handler\Query\ReadDetails\ReadDetailsQueryResult;
class ReadDetailsResponseFormatter
{
public function format(ReadDetailsQueryResult $readDetailsQueryResult): array
{
$tag = $readDetailsQueryResult->getTag();
$aliases = [];
/** @var TagAlias $alias */
foreach ($tag->getAliases() as $alias) {
$aliases[] = [
'id' => $alias->getId(),
'description' => $alias->getDescription()
];
}
return [
'id' => $tag->getId()->toString(),
'description' => $tag->getDescription(),
'aliases' => $aliases
];
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\Tag\ResponseFormatter;
use MyTube\Data\Business\Entity\Tag;
use MyTube\Data\Business\Entity\Video;
use MyTube\Handling\Tag\Handler\Query\ReadVideoList\ReadVideoListQueryResult;
class ReadVideoListResponseFormatter
{
public function format(ReadVideoListQueryResult $readVideoListQueryResult): array
{
$paginator = $readVideoListQueryResult->getPaginator();
$videos = $paginator->getIterator();
$items = [];
/** @var Video $video */
foreach ($videos as $video) {
$tags = [];
/** @var Tag $tag */
foreach ($video->getTags() as $tag) {
$tags[] = [
'id' => $tag->getId(),
'description' => $tag->getDescription()
];
}
$items[] = [
'title' => $video->getTitle(),
'id' => $video->getId(),
'duration' => $video->getDuration() === null ? 'n. def' : gmdate("H:i:s", $video->getDuration()),
'tags' => $tags
];
}
return [
'total' => $paginator->count(),
'items' => $items,
];
}
}

View File

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
use MyTube\API\External\TagList\Handler\ReadListHandler;
return [
[
'name' => 'tag-list.read-list',
'path' => '/api/tag-list/read-list[/]',
'allowed_methods' => ['POST'],
'middleware' => [
ReadListHandler::class,
],
],
];

View File

@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
use MyTube\API\External\TagList\Handler\ReadListHandler;
use MyTube\API\External\TagList\ResponseFormatter\ReadListResponseFormatter;
use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
return [
'factories' => [
// Handler
ReadListHandler::class => AutoWiringFactory::class,
// Response Formatter
ReadListResponseFormatter::class => AutoWiringFactory::class,
],
];

View File

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

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\TagList\Handler;
use MyTube\Handling\TagList\Handler\Query\ReadList\ReadListQueryHandler;
use MyTube\Handling\TagList\Handler\Query\ReadList\ReadListQueryBuilder;
use MyTube\API\External\TagList\ResponseFormatter\ReadListResponseFormatter;
use MyTube\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
use MyTube\Infrastructure\Response\SuccessResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ReadListHandler implements RequestHandlerInterface
{
public function __construct(
private readonly ReadListQueryHandler $readListQueryHandler,
private readonly ReadListQueryBuilder $readListQueryBuilder,
private readonly ReadListResponseFormatter $responseFormatter,
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$data = $request->getAttribute(AnalyzeBodyMiddleware::JSON_DATA);
$readListQuery = $this->readListQueryBuilder->build(
$data['query'] ?? null,
);
$result = $this->readListQueryHandler->execute($readListQuery);
return new SuccessResponse($this->responseFormatter->format($result));
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\TagList\ResponseFormatter;
use MyTube\Data\Business\Entity\Tag;
use MyTube\Handling\TagList\Handler\Query\ReadList\ReadListQueryResult;
class ReadListResponseFormatter
{
public function format(ReadListQueryResult $readListQueryResult): array
{
$tags = $readListQueryResult->getItems();
$items = [];
/** @var Tag $tag */
foreach ($tags as $tag) {
$items[] = [
'id' => $tag->getId()->toString(),
'description' => $tag->getDescription(),
'videoCount' => $tag->getVideos()->count()
];
}
return [
'total' => count($items),
'items' => $items,
];
}
}

View File

@ -0,0 +1,48 @@
<?php
use MyTube\API\External\User\Handler\ChangePasswordHandler;
use MyTube\API\External\User\Handler\ChangeUsernameHandler;
use MyTube\API\External\User\Handler\CreateUserHandler;
use MyTube\API\External\User\Handler\UserStateHandler;
use MyTube\Infrastructure\Rbac\Middleware\EnsureAuthorizationMiddleware;
use MyTube\Infrastructure\Session\Middleware\LoggedInUserMiddleware;
return [
[
'name' => 'user.create-user',
'path' => '/api/user/create-user',
'allowed_methods' => ['POST'],
'middleware' => [
LoggedInUserMiddleware::class,
EnsureAuthorizationMiddleware::class,
CreateUserHandler::class
],
],
[
'name' => 'user.change-password',
'path' => '/api/user/change-password',
'allowed_methods' => ['POST'],
'middleware' => [
LoggedInUserMiddleware::class,
ChangePasswordHandler::class
],
],
[
'name' => 'user.change-username',
'path' => '/api/user/change-username',
'allowed_methods' => ['POST'],
'middleware' => [
LoggedInUserMiddleware::class,
ChangeUsernameHandler::class
],
],
[
'name' => 'user.state',
'path' => '/api/user/state',
'allowed_methods' => ['GET'],
'middleware' => [
LoggedInUserMiddleware::class,
UserStateHandler::class
],
],
];

View File

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

View File

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

View File

@ -0,0 +1,27 @@
<?php
namespace MyTube\API\External\User\Formatter;
use DateTime;
use MyTube\Data\Business\Entity\User;
class UserFormatter {
public function format(User $user): array {
$userArray = [
'id' => $user->getId()->toString(),
'username' => $user->getUsername(),
'role' => $user->getRole()->getIdentifier(),
'created' => $user->getCreatedAt()->format(DateTime::ATOM),
'updated' => $user->getUpdatedAt()->format(DateTime::ATOM)
];
$userArray['permissions'] = [];
foreach ($user->getRole()->getPermissions()->toArray() as $permission) {
$userArray['permissions'][] = $permission->getIdentifier();
}
return $userArray;
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\User\Handler;
use MyTube\Data\Business\Entity\User;
use MyTube\Handling\User\Handler\Command\ChangePassword\ChangePasswordCommandBuilder;
use MyTube\Handling\User\Handler\Command\ChangePassword\ChangePasswordCommandHandler;
use MyTube\Infrastructure\Response\SuccessResponse;
use MyTube\Infrastructure\Session\Middleware\LoggedInUserMiddleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
class ChangePasswordHandler implements RequestHandlerInterface
{
public function __construct(
private readonly ChangePasswordCommandHandler $handler,
private readonly ChangePasswordCommandBuilder $builder,
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
/** @var User $user */
$user = $request->getAttribute(LoggedInUserMiddleware::USER_KEY);
$data = $request->getAttribute(AnalyzeBodyMiddleware::JSON_DATA);
$query = $this->builder->build(
$user,
$data['password'],
$data['newPassword'],
);
$this->handler->execute($query);
return new SuccessResponse();
}
}

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