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

@@ -0,0 +1,201 @@
<?php
declare(strict_types=1);
namespace App\Framework\View\Components;
use App\Framework\Core\ValueObjects\AccessibleLink;
use App\Framework\Core\ValueObjects\HtmlLink;
use App\Framework\Core\ValueObjects\LinkRel;
use App\Framework\Core\ValueObjects\LinkTarget;
use App\Framework\View\Attributes\ComponentName;
use App\Framework\View\Contracts\StaticComponent;
use App\Framework\View\Dom\ElementNode;
use App\Framework\View\Dom\Node;
use App\Framework\View\Dom\TextNode;
/**
* <x-a> Component
*
* Wrapper around HtmlLink and AccessibleLink Value Objects for template usage.
* Provides consistent, accessible link rendering throughout the application.
*
* Usage in templates:
* <x-a href="/dashboard">Dashboard</x-a>
* <x-a href="https://example.com" external="true">External Link</x-a>
* <x-a href="/current" aria-current="true">Current Page</x-a>
* <x-a href="/downloads/file.pdf" download="true">Download PDF</x-a>
* <x-a href="/page" class="custom-link" title="Tooltip">Link with custom class</x-a>
*/
#[ComponentName('a')]
final readonly class A implements StaticComponent
{
private AccessibleLink $link;
private string $text;
public function __construct(
string $content = '',
array $attributes = []
) {
// Content is the link text
$this->text = $content;
// Extract href from attributes (default to '#' if not provided)
$href = $attributes['href'] ?? '#';
// Extract other link attributes
$title = $attributes['title'] ?? null;
$class = $attributes['class'] ?? null;
$target = $attributes['target'] ?? null;
$rel = $attributes['rel'] ?? null;
$external = isset($attributes['external']);
$download = $attributes['download'] ?? null;
// Extract ARIA attributes
$ariaLabel = $attributes['aria-label'] ?? null;
$ariaCurrent = $attributes['aria-current'] ?? null;
$ariaDescribedBy = $attributes['aria-described-by'] ?? null;
// Build HtmlLink
$htmlLink = HtmlLink::create($href, $this->text);
if ($title !== null) {
$htmlLink = $htmlLink->withTitle($title);
}
if ($class !== null) {
$htmlLink = $htmlLink->withClass($class);
}
// Handle target attribute
if ($target !== null) {
$linkTarget = match ($target) {
'_blank' => LinkTarget::BLANK,
'_self' => LinkTarget::SELF,
'_parent' => LinkTarget::PARENT,
'_top' => LinkTarget::TOP,
default => null
};
if ($linkTarget !== null) {
$htmlLink = $htmlLink->withTarget($linkTarget);
}
}
// Handle rel attribute
if ($rel !== null) {
$relValues = array_map(
fn(string $r) => match (trim($r)) {
'noopener' => LinkRel::NOOPENER,
'noreferrer' => LinkRel::NOREFERRER,
'nofollow' => LinkRel::NOFOLLOW,
'external' => LinkRel::EXTERNAL,
default => null
},
explode(' ', $rel)
);
$relValues = array_filter($relValues);
if (!empty($relValues)) {
$htmlLink = $htmlLink->withRel(...$relValues);
}
}
// Handle external links (auto-adds security attributes)
if ($external) {
$htmlLink = HtmlLink::external($href, $this->text);
if ($class !== null) {
$htmlLink = $htmlLink->withClass($class);
}
if ($title !== null) {
$htmlLink = $htmlLink->withTitle($title);
}
}
// Handle downloads
if ($download !== null) {
$htmlLink = HtmlLink::download($href, $this->text, $download !== '' ? $download : null);
if ($class !== null) {
$htmlLink = $htmlLink->withClass($class);
}
if ($title !== null) {
$htmlLink = $htmlLink->withTitle($title);
}
}
// Wrap in AccessibleLink for ARIA support
$link = AccessibleLink::fromHtmlLink($htmlLink);
if ($ariaLabel !== null) {
$link = $link->withAriaLabel($ariaLabel);
}
if ($ariaCurrent !== null) {
$link = $link->withAriaCurrent(true, $ariaCurrent);
}
if ($ariaDescribedBy !== null) {
$link = new AccessibleLink(
baseLink: $link->baseLink,
ariaLabel: $link->ariaLabel,
ariaDescribedBy: $ariaDescribedBy,
ariaCurrent: $link->ariaCurrent,
ariaCurrentValue: $link->ariaCurrentValue,
);
}
// Store the AccessibleLink
$this->link = $link;
}
public function getRootNode(): Node
{
$a = new ElementNode('a');
// Add href
$a->setAttribute('href', (string) $this->link->baseLink->href);
// Add class if present
if ($this->link->baseLink->cssClass !== null) {
$a->setAttribute('class', $this->link->baseLink->cssClass);
}
// Add title if present
if ($this->link->baseLink->title !== null) {
$a->setAttribute('title', $this->link->baseLink->title);
}
// Add target if present
if ($this->link->baseLink->target !== null) {
$a->setAttribute('target', $this->link->baseLink->target->value);
}
// Add rel if present
if (!empty($this->link->baseLink->rel)) {
$relValues = array_map(fn($r) => $r->value, $this->link->baseLink->rel);
$a->setAttribute('rel', implode(' ', $relValues));
}
// Add download if present
if ($this->link->baseLink->download !== null) {
$a->setAttribute('download', $this->link->baseLink->download);
}
// Add ARIA attributes
if ($this->link->ariaLabel !== null) {
$a->setAttribute('aria-label', $this->link->ariaLabel);
}
if ($this->link->ariaCurrent) {
$a->setAttribute('aria-current', $this->link->ariaCurrentValue ?? 'true');
}
if ($this->link->ariaDescribedBy !== null) {
$a->setAttribute('aria-describedby', $this->link->ariaDescribedBy);
}
// Add text content
$textNode = new TextNode($this->text);
$a->appendChild($textNode);
return $a;
}
}