docs: consolidate documentation into organized structure

- Move 12 markdown files from root to docs/ subdirectories
- Organize documentation by category:
  • docs/troubleshooting/ (1 file)  - Technical troubleshooting guides
  • docs/deployment/      (4 files) - Deployment and security documentation
  • docs/guides/          (3 files) - Feature-specific guides
  • docs/planning/        (4 files) - Planning and improvement proposals

Root directory cleanup:
- Reduced from 16 to 4 markdown files in root
- Only essential project files remain:
  • CLAUDE.md (AI instructions)
  • README.md (Main project readme)
  • CLEANUP_PLAN.md (Current cleanup plan)
  • SRC_STRUCTURE_IMPROVEMENTS.md (Structure improvements)

This improves:
 Documentation discoverability
 Logical organization by purpose
 Clean root directory
 Better maintainability
This commit is contained in:
2025-10-05 11:05:04 +02:00
parent 887847dde6
commit 5050c7d73a
36686 changed files with 196456 additions and 12398919 deletions

View File

@@ -0,0 +1,144 @@
<?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
]);
}
}
}