-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a874c3b
commit f1d8e17
Showing
8 changed files
with
203 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,3 +4,5 @@ log_errors=On | |
expose_php=Off | ||
short_open_tag=Off | ||
xdebug.mode=coverage | ||
upload_max_filesize=2M | ||
post_max_size=64M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace HexagonalPlayground\Infrastructure\API\Logos; | ||
|
||
use HexagonalPlayground\Infrastructure\API\ActionInterface; | ||
use HexagonalPlayground\Infrastructure\API\JsonResponseWriter; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
|
||
class GetStatsAction implements ActionInterface | ||
{ | ||
private JsonResponseWriter $responseWriter; | ||
|
||
public function __construct(JsonResponseWriter $responseWriter) | ||
{ | ||
$this->responseWriter = $responseWriter; | ||
} | ||
|
||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface | ||
{ | ||
return $this->responseWriter->write($response, [ | ||
'maxFileSize' => 0, | ||
'totalFileSize' => 0, | ||
'totalFileCount' => 0 | ||
]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace HexagonalPlayground\Infrastructure\API\Logos; | ||
|
||
use HexagonalPlayground\Infrastructure\API\RouteProviderInterface; | ||
use Slim\Interfaces\RouteCollectorProxyInterface; | ||
|
||
class RouteProvider implements RouteProviderInterface | ||
{ | ||
public function register(RouteCollectorProxyInterface $routeCollectorProxy): void | ||
{ | ||
$routeCollectorProxy->get('/logos', GetStatsAction::class); | ||
$routeCollectorProxy->post('/logos', UploadAction::class); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace HexagonalPlayground\Infrastructure\API\Logos; | ||
|
||
use HexagonalPlayground\Application\ServiceProviderInterface; | ||
use DI; | ||
|
||
class ServiceProvider implements ServiceProviderInterface | ||
{ | ||
public function getDefinitions(): array | ||
{ | ||
return [ | ||
UploadAction::class => DI\autowire() | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
<?php | ||
declare(strict_types=1); | ||
|
||
namespace HexagonalPlayground\Infrastructure\API\Logos; | ||
|
||
use HexagonalPlayground\Domain\Exception\InternalException; | ||
use HexagonalPlayground\Domain\Exception\InvalidInputException; | ||
use HexagonalPlayground\Domain\Util\Uuid; | ||
use HexagonalPlayground\Infrastructure\API\ActionInterface; | ||
use Psr\Http\Message\ResponseInterface; | ||
use Psr\Http\Message\ServerRequestInterface; | ||
use Psr\Http\Message\UploadedFileInterface; | ||
use Psr\Log\LoggerInterface; | ||
|
||
class UploadAction implements ActionInterface | ||
{ | ||
private string $storageBasePath; | ||
private string $publicBasePath; | ||
private LoggerInterface $logger; | ||
|
||
public function __construct(LoggerInterface $logger) | ||
{ | ||
$this->storageBasePath = '/mnt/logos'; | ||
$this->publicBasePath = '/logos'; | ||
$this->logger = $logger; | ||
} | ||
|
||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface | ||
{ | ||
$file = $this->getUploadedFile($request); | ||
$this->checkForUploadError($file); | ||
$fileId = $this->generateFileId(); | ||
|
||
$fileSize = (int)$file->getSize(); | ||
if ($fileSize === 0) { | ||
throw new InvalidInputException("Uploaded file is empty"); | ||
} | ||
|
||
$mediaType = $file->getClientMediaType(); | ||
if ($mediaType !== 'image/webp') { | ||
throw new InvalidInputException("Invalid media type. Expected: image/webp. Got: $mediaType"); | ||
} | ||
|
||
$file->moveTo($this->buildStoragePath($fileId)); | ||
|
||
return $response->withHeader('Location', $this->buildPublicPath($fileId)); | ||
} | ||
|
||
private function buildStoragePath(string $fileId): string | ||
{ | ||
return join(DIRECTORY_SEPARATOR, [$this->storageBasePath, "$fileId.webp"]); | ||
} | ||
|
||
private function buildPublicPath(string $fileId): string | ||
{ | ||
return join('/', [$this->publicBasePath, "$fileId.webp"]); | ||
} | ||
|
||
private function generateFileId(): string | ||
{ | ||
return Uuid::create(); | ||
} | ||
|
||
private function getMaxFileSize(): int | ||
{ | ||
$value = ini_get('upload_max_filesize'); | ||
$value = trim($value); | ||
|
||
if (is_numeric($value)) { | ||
return (int)$value; | ||
} | ||
|
||
$unit = substr($value, -1); | ||
$value = substr($value, 0, -1); | ||
|
||
switch (strtolower($unit)) { | ||
case 'g': | ||
$value *= 2**30; | ||
break; | ||
case 'm': | ||
$value *= 2**20; | ||
break; | ||
case 'k': | ||
$value *= 2**10; | ||
break; | ||
default: | ||
throw new InternalException('Invalid unit suffix in "upload_max_filesize"'); | ||
} | ||
|
||
return (int)$value; | ||
} | ||
|
||
private function getUploadedFile(ServerRequestInterface $request): UploadedFileInterface | ||
{ | ||
$files = $request->getUploadedFiles(); | ||
$count = count($files); | ||
|
||
if ($count !== 1) { | ||
throw new InvalidInputException("Invalid upload file count. Expected: 1. Got: $count"); | ||
} | ||
|
||
/** @var UploadedFileInterface $file */ | ||
$file = array_shift($files); | ||
|
||
return $file; | ||
} | ||
|
||
private function checkForUploadError(UploadedFileInterface $uploadedFile): void | ||
{ | ||
$errorCode = $uploadedFile->getError(); | ||
switch ($errorCode) { | ||
case UPLOAD_ERR_OK: | ||
return; | ||
case UPLOAD_ERR_INI_SIZE: | ||
$maxSize = ini_get('upload_max_filesize'); | ||
throw new InvalidInputException("Invalid file upload: File exceeds max size of $maxSize"); | ||
default: | ||
$this->logger->error("Unexpected file upload error", [ | ||
'errorCode' => $errorCode, | ||
'files' => $_FILES | ||
]); | ||
throw new InternalException("Invalid file upload: Code $errorCode"); | ||
} | ||
} | ||
} |