first nice version

This commit is contained in:
Flo 2024-02-23 14:06:49 +01:00
parent fa5375e04d
commit 7c6bd31a6c
44 changed files with 903 additions and 38 deletions

View File

@ -20,6 +20,12 @@ $apiHandlerName = $apiName . 'Handler';
$apiHandlerNamespace = $projectNamespace . '\\API\\' . $apiType . '\\' . $apiNamespace . '\\Handler'; $apiHandlerNamespace = $projectNamespace . '\\API\\' . $apiType . '\\' . $apiNamespace . '\\Handler';
$apiHandlerFilePath = $projectSourceDirectory . 'ApiDomain/' . $apiType . '/' . $apiNamespace . '/src/Handler/' . $apiHandlerName . '.php'; $apiHandlerFilePath = $projectSourceDirectory . 'ApiDomain/' . $apiType . '/' . $apiNamespace . '/src/Handler/' . $apiHandlerName . '.php';
# Response Formatter
$apiResponseFormatterName = $apiName . 'ResponseFormatter';
$apiResponseFormatterNamespace = $projectNamespace . '\\API\\' . $apiType . '\\' . $apiNamespace . '\\ResponseFormatter';
$apiResponseFormatterUsingNamespace = $apiResponseFormatterNamespace . '\\' . $apiResponseFormatterName;
$apiResponseFormatterFilePath = $projectSourceDirectory . 'ApiDomain/' . $apiType . '/' . $apiNamespace . '/src/ResponseFormatter/' . $apiResponseFormatterName . '.php';
# CQRS # CQRS
$cqrsFilePath = $projectSourceDirectory . 'HandlingDomain/' . $cqrsNamespace . '/src/Handler/' . $cqrsType . '/' . $apiName . '/'; $cqrsFilePath = $projectSourceDirectory . 'HandlingDomain/' . $cqrsNamespace . '/src/Handler/' . $cqrsType . '/' . $apiName . '/';
$cqrsName = $apiName . $cqrsType; $cqrsName = $apiName . $cqrsType;
@ -30,6 +36,12 @@ $cqrsNamespace = $projectNamespace . '\\Handling\\' . $cqrsNamespace . '\\Handle
$cqrsPath = $cqrsFilePath . $cqrsName . '.php'; $cqrsPath = $cqrsFilePath . $cqrsName . '.php';
$cqrsUsingNamespace = $cqrsNamespace . '\\' . $cqrsName; $cqrsUsingNamespace = $cqrsNamespace . '\\' . $cqrsName;
# Result
$cqrsResultName = $cqrsName . 'Result';
$cqrsResultVariableName = lcfirst($cqrsResultName);
$cqrsResultPath = $cqrsFilePath . $cqrsResultName . '.php';
$cqrsResultUsingNamespace = $cqrsNamespace . '\\' . $cqrsResultName;
# Handler # Handler
$cqrsHandlerName = $cqrsName . 'Handler'; $cqrsHandlerName = $cqrsName . 'Handler';
$cqrsHandlerVariableName = lcfirst($cqrsHandlerName); $cqrsHandlerVariableName = lcfirst($cqrsHandlerName);
@ -66,16 +78,18 @@ namespace {$apiHandlerNamespace};
use {$cqrsHandlerUsingNamespace}; use {$cqrsHandlerUsingNamespace};
use {$cqrsBuilderUsingNamespace}; use {$cqrsBuilderUsingNamespace};
use {$apiResponseFormatterUsingNamespace};
use Psr\\Http\\Message\\ResponseInterface; use Psr\\Http\\Message\\ResponseInterface;
use Psr\\Http\\Message\\ServerRequestInterface; use Psr\\Http\\Message\\ServerRequestInterface;
use Psr\\Http\\Server\\RequestHandlerInterface; use Psr\\Http\\Server\\RequestHandlerInterface;
use Bee\\Infrastructure\\Response\\SuccessResponse; use {$projectNamespace}\\Infrastructure\\Response\\SuccessResponse;
class {$apiHandlerName} implements RequestHandlerInterface class {$apiHandlerName} implements RequestHandlerInterface
{ {
public function __construct( public function __construct(
private readonly {$cqrsHandlerName} \${$cqrsHandlerVariableName}, private readonly {$cqrsHandlerName} \${$cqrsHandlerVariableName},
private readonly {$cqrsBuilderName} \${$cqrsBuilderVariableName}, private readonly {$cqrsBuilderName} \${$cqrsBuilderVariableName},
private readonly {$apiResponseFormatterName} \$responseFormatter,
) { ) {
} }
@ -91,12 +105,30 @@ class {$apiHandlerName} implements RequestHandlerInterface
); );
\$result = \$this->{$cqrsHandlerVariableName}->execute(\${$cqrsVariableName}); \$result = \$this->{$cqrsHandlerVariableName}->execute(\${$cqrsVariableName});
return new SuccessResponse('OK'); return new SuccessResponse(\$this->responseFormatter->format(\$result));
} }
} }
"; ";
writeToFile($apiHandlerFilePath, $apiHandlerFileContent); 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 $cqrsFileContent = "<?php
declare(strict_types=1); declare(strict_types=1);
@ -109,12 +141,26 @@ class {$cqrsName}
#TODO #TODO
) { ) {
} }
#TODO
} }
"; ";
writeToFile($cqrsPath, $cqrsFileContent); writeToFile($cqrsPath, $cqrsFileContent);
$cqrsResultFileContent = "<?php
declare(strict_types=1);
namespace {$cqrsNamespace};
class {$cqrsResultName}
{
public function __construct(
#TODO
) {
}
}
";
writeToFile($cqrsResultPath, $cqrsResultFileContent);
$cqrsHandlerFileContent = "<?php $cqrsHandlerFileContent = "<?php
declare(strict_types=1); declare(strict_types=1);
@ -128,8 +174,9 @@ class {$cqrsHandlerName}
) { ) {
} }
public function execute({$cqrsName} \${$cqrsVariableName}): void public function execute({$cqrsName} \${$cqrsVariableName}): {$cqrsResultName}
{ {
return new {$cqrsResultName}();
} }
} }
"; ";

View File

@ -58,6 +58,7 @@ $aggregator = new ConfigAggregator([
\MyTube\Handling\UserSession\ConfigProvider::class, \MyTube\Handling\UserSession\ConfigProvider::class,
\MyTube\Handling\Registration\ConfigProvider::class, \MyTube\Handling\Registration\ConfigProvider::class,
\MyTube\Handling\Video\ConfigProvider::class, \MyTube\Handling\Video\ConfigProvider::class,
\MyTube\Handling\VideoList\ConfigProvider::class,
// API // API
/// Command /// Command
@ -68,6 +69,7 @@ $aggregator = new ConfigAggregator([
\MyTube\API\External\User\ConfigProvider::class, \MyTube\API\External\User\ConfigProvider::class,
\MyTube\API\External\Authentication\ConfigProvider::class, \MyTube\API\External\Authentication\ConfigProvider::class,
\MyTube\API\External\Video\ConfigProvider::class, \MyTube\API\External\Video\ConfigProvider::class,
\MyTube\API\External\VideoList\ConfigProvider::class,
/// Internal /// Internal

View File

@ -22,7 +22,7 @@ final class Version20240214194156 extends AbstractMigration
$sql = "CREATE TABLE video ( $sql = "CREATE TABLE video (
id binary(16) NOT NULL, id binary(16) NOT NULL,
title varchar(255) NOT NULL, title varchar(255) NOT NULL,
file_path varchar(255) NOT NULL, directory_path varchar(255) NOT NULL,
created_at datetime NOT NULL, created_at datetime NOT NULL,
updated_at datetime NOT NULL, updated_at datetime NOT NULL,
PRIMARY KEY (id) PRIMARY KEY (id)

View File

@ -0,0 +1,31 @@
<?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 '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
}
}

View File

@ -5,6 +5,8 @@ upstream host-backend-app {
server { server {
listen 80 default_server; listen 80 default_server;
client_max_body_size 1000M;
location / { location / {
fastcgi_pass host-backend-app; fastcgi_pass host-backend-app;
fastcgi_index index.php; fastcgi_index index.php;

View File

@ -0,0 +1,5 @@
file_uploads = On
memory_limit = 1000M
upload_max_filesize = 1000M
post_max_size = 1000M
max_execution_time = 600

View File

@ -2,6 +2,7 @@ FROM php:8.1-fpm
RUN apt-get -y update RUN apt-get -y update
RUN apt-get -y install git RUN apt-get -y install git
RUN apt-get install -y ffmpeg
RUN chown -R www-data:www-data /var/www/html RUN chown -R www-data:www-data /var/www/html
WORKDIR /var/www/html WORKDIR /var/www/html
@ -12,4 +13,6 @@ RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local
RUN useradd -m docker && echo "docker:docker" | chpasswd && adduser docker sudo RUN useradd -m docker && echo "docker:docker" | chpasswd && adduser docker sudo
USER docker USER docker
COPY docker/php/config/uploads.ini /usr/local/etc/php/conf.d
CMD ["php-fpm"] CMD ["php-fpm"]

View File

@ -1,22 +1,41 @@
<?php <?php
use MyTube\API\External\Video\Handler\ReadDetailsHandler;
use MyTube\API\External\Video\Handler\StreamHandler; use MyTube\API\External\Video\Handler\StreamHandler;
use MyTube\API\External\Video\Handler\ThumbnailHandler;
use MyTube\API\External\Video\Handler\UploadHandler;
return [ return [
[ [
'name' => 'video.stream', 'name' => 'video.stream',
'path' => '/api/video/stream[/]', 'path' => '/api/video/stream/:videoUuid[/]',
'allowed_methods' => ['GET'], 'allowed_methods' => ['GET'],
'middleware' => [ 'middleware' => [
StreamHandler::class StreamHandler::class
], ],
], ],
[
'name' => 'video.thumbnail',
'path' => '/api/video/thumbnail/:videoUuid[/]',
'allowed_methods' => ['GET'],
'middleware' => [
ThumbnailHandler::class
],
],
[ [
'name' => 'video.upload', 'name' => 'video.upload',
'path' => '/api/video/upload[/]', 'path' => '/api/video/upload[/]',
'allowed_methods' => ['POST'], 'allowed_methods' => ['POST'],
'middleware' => [ 'middleware' => [
StreamHandler::class UploadHandler::class
],
],
[
'name' => 'video.read-details',
'path' => '/api/video/read-details[/]',
'allowed_methods' => ['POST'],
'middleware' => [
ReadDetailsHandler::class
], ],
], ],
]; ];

View File

@ -1,10 +1,19 @@
<?php <?php
use MyTube\API\External\Video\Handler\ReadDetailsHandler;
use MyTube\API\External\Video\Handler\StreamHandler; use MyTube\API\External\Video\Handler\StreamHandler;
use MyTube\API\External\Video\Handler\ThumbnailHandler;
use MyTube\API\External\Video\Handler\UploadHandler;
use MyTube\API\External\Video\ResponseFormatter\ReadDetailsResponseFormatter;
use Reinfi\DependencyInjection\Factory\AutoWiringFactory; use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
return [ return [
'factories' => [ 'factories' => [
StreamHandler::class => AutoWiringFactory::class ThumbnailHandler::class => AutoWiringFactory::class,
StreamHandler::class => AutoWiringFactory::class,
UploadHandler::class => AutoWiringFactory::class,
ReadDetailsHandler::class => AutoWiringFactory::class,
ReadDetailsResponseFormatter::class => AutoWiringFactory::class,
], ],
]; ];

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\Video\Handler;
use MyTube\Handling\Video\Exception\VideoNotFoundByIdException;
use MyTube\Handling\Video\Handler\Query\ReadDetails\ReadDetailsQueryHandler;
use MyTube\Handling\Video\Handler\Query\ReadDetails\ReadDetailsQueryBuilder;
use MyTube\API\External\Video\ResponseFormatter\ReadDetailsResponseFormatter;
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 ReadDetailsHandler implements RequestHandlerInterface
{
public function __construct(
private readonly ReadDetailsQueryHandler $readDetailsQueryHandler,
private readonly ReadDetailsQueryBuilder $readDetailsQueryBuilder,
private readonly ReadDetailsResponseFormatter $responseFormatter,
) {
}
/**
* @throws VideoNotFoundByIdException
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
$data = json_decode(
$request->getBody()->getContents(),
true
);
$readDetailsQuery = $this->readDetailsQueryBuilder->build(
Uuid::fromString($data['videoId'])
);
$result = $this->readDetailsQueryHandler->execute($readDetailsQuery);
return new SuccessResponse($this->responseFormatter->format($result));
}
}

View File

@ -12,6 +12,7 @@ use MyTube\Handling\Video\Handler\Query\Stream\StreamQueryHandler;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Server\RequestHandlerInterface;
use Ramsey\Uuid\Uuid;
class StreamHandler implements RequestHandlerInterface class StreamHandler implements RequestHandlerInterface
{ {
@ -23,19 +24,24 @@ class StreamHandler implements RequestHandlerInterface
public function handle(ServerRequestInterface $request): ResponseInterface public function handle(ServerRequestInterface $request): ResponseInterface
{ {
$videoPath = APP_ROOT . '/var/filestore/video.mp4'; $videoUuid = $request->getAttribute('videoUuid') ?? null;
if ($videoUuid === null) {
return new JsonResponse('No videoId provided');
}
$streamQuery = $this->streamQueryBuilder->build(
Uuid::fromString($videoUuid)
);
$video = $this->streamQueryHandler->execute($streamQuery);
if (file_exists($videoPath)) {
$stream = new VideoStream(); $stream = new VideoStream();
return $stream->streamVideo( return $stream->streamVideo(
$videoPath, $video->getDirectoryPath() . 'video.mp4',
[ [
'is_localPath' => true, 'is_localPath' => true,
'content_type' => 'video/mp4' 'content_type' => 'video/mp4'
] ]
); );
} else {
return new JsonResponse("Video not found", Response::STATUS_CODE_404);
}
} }
} }

View File

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\Video\Handler;
use Laminas\Diactoros\Response\JsonResponse;
use Laminas\Diactoros\Response\TextResponse;
use MyTube\Handling\Video\Handler\Query\Thumbnail\ThumbnailQueryHandler;
use MyTube\Handling\Video\Handler\Query\Thumbnail\ThumbnailQueryBuilder;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Ramsey\Uuid\Uuid;
class ThumbnailHandler implements RequestHandlerInterface
{
public function __construct(
private readonly ThumbnailQueryHandler $thumbnailQueryHandler,
private readonly ThumbnailQueryBuilder $thumbnailQueryBuilder,
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$videoUuid = $request->getAttribute('videoUuid') ?? null;
if ($videoUuid === null) {
return new JsonResponse('No videoId provided');
}
$thumbnailQuery = $this->thumbnailQueryBuilder->build(
Uuid::fromString($videoUuid)
);
$result = $this->thumbnailQueryHandler->execute($thumbnailQuery);
$filePath = $result->getVideo()->getDirectoryPath() . 'thumbnail.png';
if (!file_exists($filePath)) {
return new JsonResponse('Not Found', 404);
}
$fileContent = file_get_contents($filePath);
$response = new TextResponse($fileContent);
return $response->withHeader('Content-Type', 'image/png');
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\Video\Handler;
use Laminas\Diactoros\Response\JsonResponse;
use Laminas\Http\Response;
use MyTube\Handling\Video\Handler\Command\Upload\UploadCommandHandler;
use MyTube\Handling\Video\Handler\Command\Upload\UploadCommandBuilder;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use MyTube\Infrastructure\Response\SuccessResponse;
class UploadHandler implements RequestHandlerInterface
{
public function __construct(
private readonly UploadCommandHandler $uploadCommandHandler,
private readonly UploadCommandBuilder $uploadCommandBuilder,
) {
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$uploadedFiles = $request->getUploadedFiles();
if (count($uploadedFiles) !== 1) {
return new JsonResponse('Uploaded more than one file', Response::STATUS_CODE_400);
}
$uploadCommand = $this->uploadCommandBuilder->build(
$request->getUploadedFiles()['file']
);
$result = $this->uploadCommandHandler->execute($uploadCommand);
return new SuccessResponse([
'id' => $result->getId()->toString()
]);
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\Video\ResponseFormatter;
use MyTube\Handling\Video\Handler\Query\ReadDetails\ReadDetailsQueryResult;
class ReadDetailsResponseFormatter
{
public function format(ReadDetailsQueryResult $readDetailsQueryResult): array
{
$video = $readDetailsQueryResult->getVideo();
return [
'id' => $video->getId(),
'title' => $video->getTitle()
];
}
}

View File

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\Video\ResponseFormatter;
use MyTube\Handling\Video\Handler\Query\Thumbnail\ThumbnailQueryResult;
class ThumbnailResponseFormatter
{
public function format(ThumbnailQueryResult $thumbnailQueryResult): array
{
return [];
}
}

View File

@ -0,0 +1,14 @@
<?php
use MyTube\API\External\VideoList\Handler\ReadListHandler;
return [
[
'name' => 'video-list.read-list',
'path' => '/api/video-list/read-list[/]',
'allowed_methods' => ['POST'],
'middleware' => [
ReadListHandler::class
],
],
];

View File

@ -0,0 +1,15 @@
<?php
use MyTube\API\External\Video\Handler\StreamHandler;
use MyTube\API\External\Video\Handler\UploadHandler;
use MyTube\API\External\VideoList\Handler\ReadListHandler;
use MyTube\API\External\VideoList\ResponseFormatter\ReadListResponseFormatter;
use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
return [
'factories' => [
ReadListHandler::class => AutoWiringFactory::class,
ReadListResponseFormatter::class => AutoWiringFactory::class,
],
];

View File

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

View File

@ -4,18 +4,20 @@ declare(strict_types=1);
namespace MyTube\API\External\VideoList\Handler; namespace MyTube\API\External\VideoList\Handler;
use MyTube\API\External\VideoList\ResponseFormatter\ReadListResponseFormatter;
use MyTube\Handling\VideoList\Handler\Query\ReadList\ReadListQueryHandler; use MyTube\Handling\VideoList\Handler\Query\ReadList\ReadListQueryHandler;
use MyTube\Handling\VideoList\Handler\Query\ReadList\ReadListQueryBuilder; use MyTube\Handling\VideoList\Handler\Query\ReadList\ReadListQueryBuilder;
use MyTube\Infrastructure\Response\SuccessResponse;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface; use Psr\Http\Server\RequestHandlerInterface;
use Bee\Infrastructure\Response\SuccessResponse;
class ReadListHandler implements RequestHandlerInterface class ReadListHandler implements RequestHandlerInterface
{ {
public function __construct( public function __construct(
private readonly ReadListQueryHandler $readListQueryHandler, private readonly ReadListQueryHandler $readListQueryHandler,
private readonly ReadListQueryBuilder $readListQueryBuilder, private readonly ReadListQueryBuilder $readListQueryBuilder,
private readonly ReadListResponseFormatter $readListResponseFormatter,
) { ) {
} }
@ -31,6 +33,6 @@ class ReadListHandler implements RequestHandlerInterface
); );
$result = $this->readListQueryHandler->execute($readListQuery); $result = $this->readListQueryHandler->execute($readListQuery);
return new SuccessResponse('OK'); return new SuccessResponse($this->readListResponseFormatter->format($result));
} }
} }

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace MyTube\API\External\VideoList\ResponseFormatter;
use MyTube\Data\Business\Entity\Video;
class ReadListResponseFormatter
{
public function format(array $videos): array
{
$result = [];
/** @var Video $video */
foreach ($videos as $video) {
$result[] = [
'title' => $video->getTitle(),
'id' => $video->getId()->toString(),
];
}
return $result;
}
}

View File

@ -21,8 +21,8 @@ class Video {
/** @ORM\Column(name="title", type="string") */ /** @ORM\Column(name="title", type="string") */
private string $title; private string $title;
/** @ORM\Column(name="file_path", type="string") */ /** @ORM\Column(name="directory_path", type="string") */
private string $filePath; private ?string $directoryPath;
/** @ORM\Column(name="created_at", type="datetime") */ /** @ORM\Column(name="created_at", type="datetime") */
private DateTime $createdAt; private DateTime $createdAt;
@ -36,6 +36,7 @@ class Video {
$now = new DateTime(); $now = new DateTime();
$this->setCreatedAt($now); $this->setCreatedAt($now);
$this->setUpdatedAt($now);
} }
@ -51,12 +52,12 @@ class Video {
$this->title = $title; $this->title = $title;
} }
public function getFilePath(): ?string { public function getDirectoryPath(): ?string {
return $this->filePath; return $this->directoryPath;
} }
public function setFilePath(?string $filePath): void { public function setDirectoryPath(?string $directoryPath): void {
$this->filePath = $filePath; $this->directoryPath = $directoryPath;
} }
public function getCreatedAt(): DateTime { public function getCreatedAt(): DateTime {

View File

@ -1,16 +1,41 @@
<?php <?php
use MyTube\Handling\Video\Builder\VideoBuilder;
use MyTube\Handling\Video\Builder\VideoImageBuilder;
use MyTube\Handling\Video\Handler\Command\Upload\UploadCommandBuilder;
use MyTube\Handling\Video\Handler\Command\Upload\UploadCommandHandler;
use MyTube\Handling\Video\Handler\Query\ReadDetails\ReadDetailsQueryBuilder;
use MyTube\Handling\Video\Handler\Query\ReadDetails\ReadDetailsQueryHandler;
use MyTube\Handling\Video\Handler\Query\Stream\StreamQueryBuilder; use MyTube\Handling\Video\Handler\Query\Stream\StreamQueryBuilder;
use MyTube\Handling\Video\Handler\Query\Stream\StreamQueryHandler; use MyTube\Handling\Video\Handler\Query\Stream\StreamQueryHandler;
use MyTube\Handling\Video\Handler\Query\Thumbnail\ThumbnailQueryBuilder;
use MyTube\Handling\Video\Handler\Query\Thumbnail\ThumbnailQueryHandler;
use MyTube\Handling\Video\Uploader\VideoUploader;
use Reinfi\DependencyInjection\Factory\AutoWiringFactory; use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
use Reinfi\DependencyInjection\Factory\InjectionFactory; use Reinfi\DependencyInjection\Factory\InjectionFactory;
return [ return [
'factories' => [ 'factories' => [
/// Uploader
VideoUploader::class => AutoWiringFactory::class,
/// Builder
VideoBuilder::class => AutoWiringFactory::class,
VideoImageBuilder::class => AutoWiringFactory::class,
/// CQRS /// CQRS
// Stream Query // Stream Query
StreamQueryHandler::class => InjectionFactory::class, StreamQueryHandler::class => InjectionFactory::class,
StreamQueryBuilder::class => AutoWiringFactory::class, StreamQueryBuilder::class => AutoWiringFactory::class,
// Thumbnail Query
ThumbnailQueryHandler::class => InjectionFactory::class,
ThumbnailQueryBuilder::class => AutoWiringFactory::class,
// Upload Command
UploadCommandHandler::class => AutoWiringFactory::class,
UploadCommandBuilder::class => AutoWiringFactory::class,
// Read Details
ReadDetailsQueryHandler::class => InjectionFactory::class,
ReadDetailsQueryBuilder::class => AutoWiringFactory::class,
], ],
]; ];

View File

@ -0,0 +1,20 @@
<?php
namespace MyTube\Handling\Video\Builder;
use MyTube\Data\Business\Entity\Video;
class VideoBuilder
{
public function build(
string $title,
?string $directoryPath = null,
): Video
{
$video = new Video();
$video->setTitle($title);
$video->setDirectoryPath($directoryPath);
return $video;
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace MyTube\Handling\Video\Builder;
use MyTube\Data\Business\Entity\Video;
class VideoImageBuilder
{
private const FILE_NAME = 'thumbnail.png';
public function build(
Video $video,
string $timestamp = '00:00:10'
): string|null
{
$videoId = $video->getId()->toString();
$targetPath = sprintf(
'%s/%s/%s/%s',
APP_ROOT,
'var/filestore',
$videoId,
self::FILE_NAME
);
if (file_exists($targetPath)) {
return null;
}
$command = "cd /var/www/html/var/filestore/" . $videoId . "/" .
" && " .
"ffmpeg -i video.mp4 -ss " . $timestamp . " -vframes 1 " . self::FILE_NAME;
$output = shell_exec($command);
return $targetPath;
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace MyTube\Handling\Video\Exception;
use MyTube\Infrastructure\Exception\ErrorCode;
use MyTube\Infrastructure\Exception\ErrorDomain;
use MyTube\Infrastructure\Exception\Exception\MyTubeException;
use Ramsey\Uuid\UuidInterface;
class VideoNotFoundByIdException extends MyTubeException {
private const MESSAGE = 'The user with the Id %s was not found!';
public function __construct(UuidInterface $id)
{
parent::__construct(
sprintf(
self::MESSAGE,
$id->toString()
),
ErrorDomain::Video,
ErrorCode::NotFound
);
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace MyTube\Handling\Video\Handler\Command\Upload;
use Psr\Http\Message\UploadedFileInterface;
class UploadCommand
{
public function __construct(
private readonly UploadedFileInterface $uploadedFile,
) {
}
public function getUploadedFile(): UploadedFileInterface
{
return $this->uploadedFile;
}
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace MyTube\Handling\Video\Handler\Command\Upload;
use Psr\Http\Message\UploadedFileInterface;
class UploadCommandBuilder
{
public function build(
UploadedFileInterface $uploadedFile,
): UploadCommand {
return new UploadCommand(
$uploadedFile,
);
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace MyTube\Handling\Video\Handler\Command\Upload;
use MyTube\Data\Business\Entity\Video;
use MyTube\Data\Business\Manager\MyTubeEntityManager;
use MyTube\Handling\Video\Builder\VideoBuilder;
use MyTube\Handling\Video\Builder\VideoImageBuilder;
use MyTube\Handling\Video\Uploader\VideoUploader;
use Psr\Http\Message\UploadedFileInterface;
use Exception;
class UploadCommandHandler
{
public function __construct(
private readonly VideoBuilder $videoBuilder,
private readonly MyTubeEntityManager $entityManager,
private readonly VideoUploader $uploader,
) {
}
/**
* @throws Exception
*/
public function execute(UploadCommand $uploadCommand): Video
{
$uploadedFile = $uploadCommand->getUploadedFile();
$video = $this->videoBuilder->build(
$uploadedFile->getClientFilename()
);
$this->uploader->upload(
$uploadedFile,
$video
);
$this->entityManager->persist($video);
$this->entityManager->flush();
return $video;
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace MyTube\Handling\Video\Handler\Query\ReadDetails;
use Ramsey\Uuid\UuidInterface;
class ReadDetailsQuery
{
public function __construct(
private readonly UuidInterface $videoId
) {
}
public function getVideoId(): UuidInterface
{
return $this->videoId;
}
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace MyTube\Handling\Video\Handler\Query\ReadDetails;
use Ramsey\Uuid\UuidInterface;
class ReadDetailsQueryBuilder
{
public function build(
UuidInterface $videoId
): ReadDetailsQuery {
return new ReadDetailsQuery(
$videoId
);
}
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace MyTube\Handling\Video\Handler\Query\ReadDetails;
use MyTube\Data\Business\Entity\Video;
use MyTube\Data\Business\Repository\VideoRepository;
use MyTube\Handling\Video\Builder\VideoImageBuilder;
use MyTube\Handling\Video\Exception\VideoNotFoundByIdException;
use Reinfi\DependencyInjection\Annotation\Inject;
use Reinfi\DependencyInjection\Annotation\InjectDoctrineRepository;
class ReadDetailsQueryHandler
{
/**
* @InjectDoctrineRepository(
* entityManager="MyTube\Data\Business\Manager\MyTubeEntityManager",
* entity="MyTube\Data\Business\Entity\Video"
* )
*/
public function __construct(
private readonly VideoRepository $videoRepository,
) {
}
/**
* @throws VideoNotFoundByIdException
*/
public function execute(ReadDetailsQuery $readDetailsQuery): ReadDetailsQueryResult
{
$videoId = $readDetailsQuery->getVideoId();
/** @var Video $video */
$video = $this->videoRepository->findOneBy(['id' => $videoId]);
if ($video === null) {
throw new VideoNotFoundByIdException($videoId);
}
return new ReadDetailsQueryResult($video);
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace MyTube\Handling\Video\Handler\Query\ReadDetails;
use MyTube\Data\Business\Entity\Video;
class ReadDetailsQueryResult
{
public function __construct(
private readonly Video $video
) {
}
public function getVideo(): Video
{
return $this->video;
}
}

View File

@ -4,12 +4,17 @@ declare(strict_types=1);
namespace MyTube\Handling\Video\Handler\Query\Stream; namespace MyTube\Handling\Video\Handler\Query\Stream;
use Ramsey\Uuid\UuidInterface;
class StreamQuery class StreamQuery
{ {
public function __construct( public function __construct(
#TODO private readonly UuidInterface $videoUuid,
) { ) {
} }
#TODO public function getVideoUuid(): UuidInterface
{
return $this->videoUuid;
}
} }

View File

@ -4,13 +4,15 @@ declare(strict_types=1);
namespace MyTube\Handling\Video\Handler\Query\Stream; namespace MyTube\Handling\Video\Handler\Query\Stream;
use Ramsey\Uuid\UuidInterface;
class StreamQueryBuilder class StreamQueryBuilder
{ {
public function build( public function build(
#TODO UuidInterface $videoUuid,
): StreamQuery { ): StreamQuery {
return new StreamQuery( return new StreamQuery(
#TODO $videoUuid,
); );
} }
} }

View File

@ -4,14 +4,34 @@ declare(strict_types=1);
namespace MyTube\Handling\Video\Handler\Query\Stream; namespace MyTube\Handling\Video\Handler\Query\Stream;
use Exception;
use MyTube\Data\Business\Entity\Video;
use MyTube\Data\Business\Repository\VideoRepository;
use Reinfi\DependencyInjection\Annotation\InjectDoctrineRepository;
class StreamQueryHandler class StreamQueryHandler
{ {
/**
* @InjectDoctrineRepository(
* entityManager="MyTube\Data\Business\Manager\MyTubeEntityManager",
* entity="MyTube\Data\Business\Entity\Video"
* )
*/
public function __construct( public function __construct(
#TODO private readonly VideoRepository $videoRepository,
) { ) {
} }
public function execute(StreamQuery $streamQuery): void public function execute(StreamQuery $streamQuery): Video
{ {
$video = $this->videoRepository->findOneBy([
'id' => $streamQuery->getVideoUuid()
]);
if ($video === null) {
throw new Exception('Video not found');
}
return $video;
} }
} }

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace MyTube\Handling\Video\Handler\Query\Thumbnail;
use Ramsey\Uuid\UuidInterface;
class ThumbnailQuery
{
public function __construct(
private readonly UuidInterface $videoUuid,
) {
}
public function getVideoUuid(): UuidInterface
{
return $this->videoUuid;
}
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace MyTube\Handling\Video\Handler\Query\Thumbnail;
use Ramsey\Uuid\UuidInterface;
class ThumbnailQueryBuilder
{
public function build(
UuidInterface $videoUuid,
): ThumbnailQuery {
return new ThumbnailQuery(
$videoUuid,
);
}
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace MyTube\Handling\Video\Handler\Query\Thumbnail;
use Exception;
use MyTube\Data\Business\Entity\Video;
use MyTube\Data\Business\Repository\VideoRepository;
use Reinfi\DependencyInjection\Annotation\InjectDoctrineRepository;
class ThumbnailQueryHandler
{
/**
* @InjectDoctrineRepository(
* entityManager="MyTube\Data\Business\Manager\MyTubeEntityManager",
* entity="MyTube\Data\Business\Entity\Video"
* )
*/
public function __construct(
private readonly VideoRepository $videoRepository,
) {
}
public function execute(ThumbnailQuery $streamQuery): ThumbnailQueryResult
{
/** @var Video $video */
$video = $this->videoRepository->findOneBy([
'id' => $streamQuery->getVideoUuid()
]);
if ($video === null) {
throw new Exception('Video not found');
}
return new ThumbnailQueryResult($video);
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace MyTube\Handling\Video\Handler\Query\Thumbnail;
use MyTube\Data\Business\Entity\Video;
class ThumbnailQueryResult
{
public function __construct(
private readonly Video $video
) {
}
public function getVideo(): Video
{
return $this->video;
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace MyTube\Handling\Video\Uploader;
use Exception;
use MyTube\Data\Business\Entity\Video;
use MyTube\Handling\Video\Builder\VideoImageBuilder;
use Psr\Http\Message\UploadedFileInterface;
class VideoUploader
{
public function __construct(
private readonly VideoImageBuilder $imageBuilder,
) {
}
public function upload(
UploadedFileInterface $file,
Video $video,
): void {
$targetPath = sprintf(
'%s/%s/%s/',
APP_ROOT,
'var/filestore',
$video->getId()->toString(),
);
if (file_exists($targetPath)) {
throw new Exception('File already exists');
}
mkdir($targetPath, 0777, true);
$video->setDirectoryPath($targetPath);
$targetPath = $targetPath . 'video.' . substr(strrchr($file->getClientFilename(),'.'),1);
$file->moveTo($targetPath);
$this->imageBuilder->build($video);
}
}

View File

@ -0,0 +1,21 @@
<?php
use MyTube\Handling\Video\Builder\VideoBuilder;
use MyTube\Handling\Video\Handler\Command\Upload\UploadCommandBuilder;
use MyTube\Handling\Video\Handler\Command\Upload\UploadCommandHandler;
use MyTube\Handling\Video\Handler\Query\Stream\StreamQueryBuilder;
use MyTube\Handling\Video\Handler\Query\Stream\StreamQueryHandler;
use MyTube\Handling\VideoList\Handler\Query\ReadList\ReadListQueryBuilder;
use MyTube\Handling\VideoList\Handler\Query\ReadList\ReadListQueryHandler;
use Reinfi\DependencyInjection\Factory\AutoWiringFactory;
use Reinfi\DependencyInjection\Factory\InjectionFactory;
return [
'factories' => [
/// CQRS
// Read List
ReadListQueryHandler::class => InjectionFactory::class,
ReadListQueryBuilder::class => AutoWiringFactory::class,
],
];

View File

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

View File

@ -4,14 +4,24 @@ declare(strict_types=1);
namespace MyTube\Handling\VideoList\Handler\Query\ReadList; namespace MyTube\Handling\VideoList\Handler\Query\ReadList;
use MyTube\Data\Business\Repository\VideoRepository;
use Reinfi\DependencyInjection\Annotation\InjectDoctrineRepository;
class ReadListQueryHandler class ReadListQueryHandler
{ {
/**
* @InjectDoctrineRepository(
* entityManager="MyTube\Data\Business\Manager\MyTubeEntityManager",
* entity="MyTube\Data\Business\Entity\Video"
* )
*/
public function __construct( public function __construct(
#TODO private readonly VideoRepository $videoRepository
) { ) {
} }
public function execute(ReadListQuery $readListQuery): void public function execute(ReadListQuery $readListQuery): array
{ {
return $this->videoRepository->findAll();
} }
} }

View File

@ -10,4 +10,5 @@ enum ErrorDomain : string {
case UserPassword = 'UserPassword'; case UserPassword = 'UserPassword';
case Registration = 'Registration'; case Registration = 'Registration';
case Product = 'Product'; case Product = 'Product';
case Video = 'Video';
} }