Files
michaelschiemer/src/Application/Api/Images/UpdateImageController.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

145 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Application\Api\Images;
use App\Domain\Media\ImageRepository;
use App\Framework\Attributes\Route;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\FrameworkException;
use App\Framework\Http\Exception\NotFound;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Http\Responses\JsonResponse;
use App\Framework\Http\Status;
final readonly class UpdateImageController
{
public function __construct(
private ImageRepository $imageRepository
) {
}
#[Route(path: '/api/images/{ulid}', method: Method::PUT)]
public function __invoke(HttpRequest $request): JsonResponse
{
$ulid = $request->routeParameters->getString('ulid');
// Find image by ULID
$image = $this->imageRepository->findByUlid($ulid);
if (! $image) {
throw NotFound::create(
ErrorCode::ENTITY_NOT_FOUND,
"Image with ULID {$ulid} not found"
)->withData(['ulid' => $ulid]);
}
// Parse request body
$updateData = $this->parseUpdateData($request);
// Update only allowed fields
$updatedImage = $image;
if (isset($updateData['alt_text'])) {
$updatedImage = $updatedImage->withAltText($updateData['alt_text']);
}
if (isset($updateData['filename'])) {
$this->validateFilename($updateData['filename']);
$updatedImage = $updatedImage->withFilename($updateData['filename']);
}
// Save updated image
$this->imageRepository->save($updatedImage);
// Return updated image data
return new JsonResponse([
'ulid' => $updatedImage->getUlidString(),
'filename' => $updatedImage->filename,
'original_filename' => $updatedImage->originalFilename,
'url' => '/media/images/' . $updatedImage->path->toString(),
'thumbnail_url' => '/media/images/thumbnails/' . $updatedImage->path->toString(),
'alt_text' => $updatedImage->altText,
'dimensions' => [
'width' => $updatedImage->width,
'height' => $updatedImage->height,
'aspect_ratio' => $updatedImage->getAspectRatio(),
'orientation' => $updatedImage->getDimensions()->getOrientation()->value,
],
'mime_type' => $updatedImage->mimeType->value,
'file_size' => [
'bytes' => $updatedImage->fileSize->toBytes(),
'human_readable' => $updatedImage->getHumanReadableFileSize(),
],
'hash' => $updatedImage->hash->toString(),
'is_image' => $updatedImage->isImageFile(),
'created_at' => $updatedImage->ulid->getDateTime()->format('c'),
'variants' => array_map(fn ($variant) => [
'type' => $variant->variantType,
'width' => $variant->width,
'height' => $variant->height,
'path' => $variant->path,
'url' => '/media/images/' . $variant->path,
], $updatedImage->variants ?? []),
], Status::OK);
}
/**
* Parse update data from request body
*/
private function parseUpdateData(HttpRequest $request): array
{
$body = $request->parsedBody->toArray();
// Only allow specific fields to be updated
$allowedFields = ['alt_text', 'filename'];
$updateData = [];
foreach ($allowedFields as $field) {
if (array_key_exists($field, $body)) {
$updateData[$field] = $body[$field];
}
}
return $updateData;
}
/**
* Validate filename
*/
private function validateFilename(string $filename): void
{
if (empty(trim($filename))) {
throw FrameworkException::create(
ErrorCode::VAL_INVALID_FORMAT,
'Filename cannot be empty'
)->withData(['field' => 'filename']);
}
// Check for invalid characters
if (preg_match('/[\/\\\\:*?"<>|]/', $filename)) {
throw FrameworkException::create(
ErrorCode::VAL_INVALID_FORMAT,
'Filename contains invalid characters'
)->withData([
'field' => 'filename',
'value' => $filename,
'invalid_chars' => ['/', '\\', ':', '*', '?', '"', '<', '>', '|'],
]);
}
// Check length
if (strlen($filename) > 255) {
throw FrameworkException::create(
ErrorCode::VAL_INVALID_FORMAT,
'Filename is too long (maximum 255 characters)'
)->withData([
'field' => 'filename',
'length' => strlen($filename),
'max_length' => 255,
]);
}
}
}