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.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -4,11 +4,10 @@ declare(strict_types=1);
namespace App\Framework\Pagination;
use App\Framework\Pagination\ValueObjects\PaginationRequest;
use App\Framework\Pagination\ValueObjects\PaginationResponse;
use App\Framework\Pagination\ValueObjects\Cursor;
use App\Framework\Pagination\ValueObjects\Direction;
use App\Framework\Serializer\Json\JsonSerializer;
use App\Framework\Pagination\ValueObjects\PaginationRequest;
use App\Framework\Pagination\ValueObjects\PaginationResponse;
/**
* Array-based paginator for in-memory pagination
@@ -213,6 +212,7 @@ final readonly class ArrayPaginator implements Paginator
}
$oppositeDirection = $request->direction->opposite();
return Cursor::fromField($request->sortField, $firstValue, $oppositeDirection);
}
}
}

View File

@@ -6,10 +6,9 @@ namespace App\Framework\Pagination;
use App\Framework\Database\EntityManager;
use App\Framework\Database\QueryBuilder\SelectQueryBuilder;
use App\Framework\Pagination\ValueObjects\Cursor;
use App\Framework\Pagination\ValueObjects\PaginationRequest;
use App\Framework\Pagination\ValueObjects\PaginationResponse;
use App\Framework\Pagination\ValueObjects\Cursor;
use App\Framework\Pagination\ValueObjects\Direction;
use InvalidArgumentException;
/**
@@ -112,13 +111,13 @@ final readonly class DatabasePaginator implements Paginator
$nextCursor = null;
$previousCursor = null;
if ($hasMore && !empty($results)) {
if ($hasMore && ! empty($results)) {
$lastItem = end($results);
$lastValue = $this->getFieldValue($lastItem, $request->sortField);
$nextCursor = Cursor::fromField($request->sortField, $lastValue, $request->direction);
}
if ($request->cursor !== null && !empty($results)) {
if ($request->cursor !== null && ! empty($results)) {
$firstItem = reset($results);
$firstValue = $this->getFieldValue($firstItem, $request->sortField);
$oppositeDirection = $request->direction->opposite();
@@ -197,4 +196,4 @@ final readonly class DatabasePaginator implements Paginator
entityClass: $entityClass
);
}
}
}

View File

@@ -60,7 +60,7 @@ final readonly class PaginationService
{
$paginator = $this->resolvePaginator($source);
if (!$paginator->supports($request)) {
if (! $paginator->supports($request)) {
throw new InvalidArgumentException(
'Paginator ' . get_class($paginator) . ' does not support the given request type'
);
@@ -148,4 +148,4 @@ final readonly class PaginationService
\App\Framework\Pagination\ValueObjects\Direction::ASC
);
}
}
}

View File

@@ -4,9 +4,9 @@ declare(strict_types=1);
namespace App\Framework\Pagination;
use App\Framework\Database\EntityManager;
use App\Framework\DI\Container;
use App\Framework\DI\Initializer;
use App\Framework\Database\EntityManager;
/**
* Initializer for the Pagination service
@@ -31,4 +31,4 @@ final readonly class PaginationServiceInitializer
return $paginationService;
}
}
}

View File

@@ -21,4 +21,4 @@ interface Paginator
* Check if paginator supports the given request type
*/
public function supports(PaginationRequest $request): bool;
}
}

View File

@@ -4,9 +4,9 @@ declare(strict_types=1);
namespace App\Framework\Pagination\ValueObjects;
use App\Framework\Serializer\Json\JsonSerializer;
use App\Framework\Serializer\Exception\SerializeException;
use App\Framework\Serializer\Exception\DeserializeException;
use App\Framework\Serializer\Exception\SerializeException;
use App\Framework\Serializer\Json\JsonSerializer;
use InvalidArgumentException;
/**
@@ -73,7 +73,7 @@ final readonly class Cursor
throw new InvalidArgumentException('Cursor value is not valid JSON: ' . $e->getMessage(), 0, $e);
}
if (!is_array($parsed)) {
if (! is_array($parsed)) {
throw new InvalidArgumentException('Cursor value is not an array');
}
@@ -86,6 +86,7 @@ final readonly class Cursor
public function getFieldValue(string $field): mixed
{
$parsed = $this->parseValue();
return $parsed[$field] ?? null;
}
@@ -122,4 +123,4 @@ final readonly class Cursor
{
return $this->toString();
}
}
}

View File

@@ -46,4 +46,4 @@ enum Direction: string
{
return strtoupper($this->value);
}
}
}

View File

@@ -119,7 +119,7 @@ final readonly class PaginationMeta
*/
public function getNavigationInfo(): array
{
if (!$this->isOffsetBased()) {
if (! $this->isOffsetBased()) {
return [];
}
@@ -137,7 +137,7 @@ final readonly class PaginationMeta
*/
public function getCursorInfo(): array
{
if (!$this->isCursorBased()) {
if (! $this->isCursorBased()) {
return [];
}
@@ -192,4 +192,4 @@ final readonly class PaginationMeta
{
return $this->type;
}
}
}

View File

@@ -166,4 +166,4 @@ final readonly class PaginationRequest
sortField: $sortField
);
}
}
}

View File

@@ -109,6 +109,7 @@ final readonly class PaginationResponse
public function toJson(): string
{
$serializer = new JsonSerializer();
return $serializer->serialize($this->toArray());
}
@@ -158,4 +159,4 @@ final readonly class PaginationResponse
return new self($filteredData, $newMeta);
}
}
}

View File

@@ -49,4 +49,4 @@ enum PaginationType: string
self::CURSOR => 'Large datasets or real-time data where performance is critical',
};
}
}
}