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,167 @@
<?php
declare(strict_types=1);
namespace App\Application\Api\Images;
use App\Domain\Media\Image;
use App\Framework\Attributes\Route;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Http\Responses\JsonResponse;
use App\Framework\Http\Status;
use App\Framework\Pagination\PaginationService;
use App\Framework\Pagination\ValueObjects\Direction;
final readonly class ListImagesController
{
public function __construct(
private PaginationService $paginationService
) {
}
#[Route(path: '/api/images', method: Method::GET)]
public function __invoke(HttpRequest $request): JsonResponse
{
// Parse query parameters
$limit = $this->parseLimit($request);
$page = $this->parsePage($request);
$sortField = $this->parseSortField($request);
$direction = $this->parseDirection($request);
$cursor = $this->parseCursor($request);
// Create pagination request
if ($cursor !== null) {
// Cursor-based pagination
$paginationRequest = $this->paginationService->cursorRequest(
limit: $limit,
cursorValue: $cursor,
sortField: $sortField,
direction: $direction
);
} else {
// Offset-based pagination
$offset = ($page - 1) * $limit;
$paginationRequest = $this->paginationService->offsetRequest(
limit: $limit,
offset: $offset,
sortField: $sortField,
direction: $direction
);
}
// Get paginated results
$paginator = $this->paginationService->forEntity(Image::class);
$paginationResponse = $paginator->paginate($paginationRequest);
// Transform images to API format
$transformedData = array_map([$this, 'transformImage'], $paginationResponse->data);
// Create response with transformed data
$responseData = [
'data' => $transformedData,
'meta' => $paginationResponse->meta->toArray(),
];
return new JsonResponse($responseData, Status::OK);
}
/**
* Parse limit parameter with validation
*/
private function parseLimit(HttpRequest $request): int
{
$limit = $request->query->getInt('limit', 20);
// Validate limit bounds
if ($limit < 1) {
$limit = 1;
} elseif ($limit > 100) {
$limit = 100;
}
return $limit;
}
/**
* Parse page parameter with validation
*/
private function parsePage(HttpRequest $request): int
{
$page = $request->query->getInt('page', 1);
return max(1, $page);
}
/**
* Parse sort field parameter
*/
private function parseSortField(HttpRequest $request): ?string
{
$sortField = $request->query->getString('sort');
// Allow only specific fields for security
$allowedFields = ['ulid', 'filename', 'width', 'height', 'fileSize'];
if ($sortField && in_array($sortField, $allowedFields)) {
return $sortField;
}
// Default sort by creation time (via ULID)
return 'ulid';
}
/**
* Parse direction parameter
*/
private function parseDirection(HttpRequest $request): string
{
$direction = $request->query->getString('direction', 'desc');
return in_array($direction, ['asc', 'desc']) ? $direction : 'desc';
}
/**
* Parse cursor parameter
*/
private function parseCursor(HttpRequest $request): ?string
{
return $request->query->getString('cursor');
}
/**
* Transform Image entity to API representation
*/
private function transformImage(Image $image): array
{
return [
'ulid' => $image->getUlidString(),
'filename' => $image->filename,
'original_filename' => $image->originalFilename,
'url' => '/images/' . $image->filename,
'thumbnail_url' => '/images/' . str_replace('_original.', '_thumbnail.', $image->filename),
'alt_text' => $image->altText,
'dimensions' => [
'width' => $image->width,
'height' => $image->height,
'aspect_ratio' => $image->getAspectRatio(),
'orientation' => $image->getDimensions()->getOrientation()->value,
],
'mime_type' => $image->mimeType->value,
'file_size' => [
'bytes' => $image->fileSize->toBytes(),
'human_readable' => $image->getHumanReadableFileSize(),
],
'hash' => $image->hash->toString(),
'is_image' => $image->isImageFile(),
'created_at' => $image->ulid->getDateTime()->format('c'),
'variants' => array_map(fn ($variant) => [
'type' => $variant->variantType,
'width' => $variant->width,
'height' => $variant->height,
'path' => $variant->path,
'url' => '/images/' . $variant->filename,
], $image->variants ?? []),
];
}
}