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

@@ -5,115 +5,63 @@ declare(strict_types=1);
namespace App\Framework\View\Components;
use App\Framework\View\Attributes\ComponentName;
use App\Framework\View\ValueObjects\HtmlAttributes;
use App\Framework\View\ValueObjects\HtmlElement;
use App\Framework\View\ValueObjects\HtmlTag;
use App\Framework\View\ValueObjects\StandardHtmlElement;
use App\Framework\View\ValueObjects\TagName;
use App\Framework\View\Contracts\StaticComponent;
use App\Framework\View\Dom\ElementNode;
use App\Framework\View\Dom\Node;
use App\Framework\View\Dom\TextNode;
#[ComponentName('card')]
final readonly class Card implements HtmlElement
final readonly class Card implements StaticComponent
{
public HtmlTag $tag;
public HtmlAttributes $attributes;
public string $content;
private string $bodyContent;
private ?string $title;
private ?string $subtitle;
private ?string $footer;
private ?string $imageSrc;
private ?string $imageAlt;
private string $variant;
public function __construct(
string $bodyContent,
public ?string $title = null,
public ?string $subtitle = null,
public ?string $footer = null,
public ?string $imageSrc = null,
public ?string $imageAlt = null,
public string $variant = 'default',
public HtmlAttributes $additionalAttributes = new HtmlAttributes()
string $content = '',
array $attributes = []
) {
$this->tag = new HtmlTag(TagName::DIV);
// Content is the card body
$this->bodyContent = $content;
// Extract attributes with defaults
$this->title = $attributes['title'] ?? null;
$this->subtitle = $attributes['subtitle'] ?? null;
$this->footer = $attributes['footer'] ?? null;
$this->imageSrc = $attributes['image-src'] ?? null;
$this->imageAlt = $attributes['image-alt'] ?? null;
$this->variant = $attributes['variant'] ?? 'default';
}
public function getRootNode(): Node
{
$div = new ElementNode('div');
// Build CSS classes
$classes = ['card', "card--{$this->variant}"];
$this->attributes = HtmlAttributes::empty()
->withClass(implode(' ', $classes));
$div->setAttribute('class', implode(' ', $classes));
foreach ($this->additionalAttributes->attributes as $name => $value) {
$this->attributes = $this->attributes->with($name, $value);
}
// Build complex nested content as HTML string
$contentHtml = $this->buildContent();
$div->appendChild(new TextNode($contentHtml));
$this->content = $this->buildContent($bodyContent);
return $div;
}
public static function create(string $bodyContent): self
{
return new self(bodyContent: $bodyContent);
}
public static function withTitle(string $title, string $bodyContent): self
{
return new self(bodyContent: $bodyContent, title: $title);
}
public static function withImage(string $imageSrc, string $bodyContent, ?string $imageAlt = null): self
{
return new self(bodyContent: $bodyContent, imageSrc: $imageSrc, imageAlt: $imageAlt);
}
public function withSubtitle(string $subtitle): self
{
// Note: Can't access bodyContent from constructor, need to rebuild from content
return new self(
bodyContent: '', // Will be rebuilt in buildContent
title: $this->title,
subtitle: $subtitle,
footer: $this->footer,
imageSrc: $this->imageSrc,
imageAlt: $this->imageAlt,
variant: $this->variant,
additionalAttributes: $this->additionalAttributes
);
}
public function withFooter(string|HtmlElement $footer): self
{
return new self(
bodyContent: '', // Will be rebuilt in buildContent
title: $this->title,
subtitle: $this->subtitle,
footer: (string) $footer,
imageSrc: $this->imageSrc,
imageAlt: $this->imageAlt,
variant: $this->variant,
additionalAttributes: $this->additionalAttributes
);
}
public function withVariant(string $variant): self
{
return new self(
bodyContent: '', // Will be rebuilt in buildContent
title: $this->title,
subtitle: $this->subtitle,
footer: $this->footer,
imageSrc: $this->imageSrc,
imageAlt: $this->imageAlt,
variant: $variant,
additionalAttributes: $this->additionalAttributes
);
}
private function buildContent(string $bodyContent): string
private function buildContent(): string
{
$elements = [];
// Image
if ($this->imageSrc !== null) {
$imgAttrs = HtmlAttributes::empty()
->with('src', $this->imageSrc)
->withClass('card__image');
if ($this->imageAlt !== null) {
$imgAttrs = $imgAttrs->with('alt', $this->imageAlt);
}
$elements[] = StandardHtmlElement::create(TagName::IMG, $imgAttrs);
$altAttr = $this->imageAlt !== null
? ' alt="' . htmlspecialchars($this->imageAlt) . '"'
: '';
$elements[] = '<img src="' . htmlspecialchars($this->imageSrc) . '"' . $altAttr . ' class="card__image" />';
}
// Header (title + subtitle)
@@ -121,53 +69,44 @@ final readonly class Card implements HtmlElement
$headerElements = [];
if ($this->title !== null) {
$headerElements[] = StandardHtmlElement::create(
TagName::H3,
HtmlAttributes::empty()->withClass('card__title'),
$this->title
);
$headerElements[] = '<h3 class="card__title">' . htmlspecialchars($this->title) . '</h3>';
}
if ($this->subtitle !== null) {
$headerElements[] = StandardHtmlElement::create(
TagName::P,
HtmlAttributes::empty()->withClass('card__subtitle'),
$this->subtitle
);
$headerElements[] = '<p class="card__subtitle">' . htmlspecialchars($this->subtitle) . '</p>';
}
$elements[] = StandardHtmlElement::create(
TagName::DIV,
HtmlAttributes::empty()->withClass('card__header'),
implode('', array_map('strval', $headerElements))
);
$elements[] = '<div class="card__header">' . implode('', $headerElements) . '</div>';
}
// Body
$elements[] = StandardHtmlElement::create(
TagName::DIV,
HtmlAttributes::empty()->withClass('card__body'),
$bodyContent
);
$elements[] = '<div class="card__body">' . htmlspecialchars($this->bodyContent) . '</div>';
// Footer
if ($this->footer !== null) {
$elements[] = StandardHtmlElement::create(
TagName::DIV,
HtmlAttributes::empty()->withClass('card__footer'),
$this->footer
);
$elements[] = '<div class="card__footer">' . htmlspecialchars($this->footer) . '</div>';
}
return implode('', array_map('strval', $elements));
return implode('', $elements);
}
public function __toString(): string
// Factory methods for programmatic usage
public static function create(string $bodyContent): self
{
return (string) StandardHtmlElement::create(
$this->tag->name,
$this->attributes,
$this->content
);
return new self($bodyContent);
}
public static function withTitle(string $title, string $bodyContent): self
{
return new self($bodyContent, ['title' => $title]);
}
public static function withImage(string $imageSrc, string $bodyContent, ?string $imageAlt = null): self
{
$attributes = ['image-src' => $imageSrc];
if ($imageAlt !== null) {
$attributes['image-alt'] = $imageAlt;
}
return new self($bodyContent, $attributes);
}
}