fix: Gitea Traefik routing and connection pool optimization
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
This commit is contained in:
193
src/Domain/Cms/README.md
Normal file
193
src/Domain/Cms/README.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# Headless CMS Modul
|
||||
|
||||
Ein Block-basiertes Headless CMS System für das Custom PHP Framework.
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das CMS Modul ermöglicht die Verwaltung von Content durch ein flexibles Block-System, ähnlich WordPress Blocks, Storyblok oder Contentful. Content besteht aus einer geordneten Liste von Content-Blöcken, die als JSON in der Datenbank gespeichert werden.
|
||||
|
||||
## Architektur
|
||||
|
||||
### Block-basiertes System
|
||||
|
||||
Content besteht aus einer **geordneten Liste von Content-Blöcken**. Jeder Block hat:
|
||||
- **Block Type**: HERO, TEXT, IMAGE, GALLERY, CTA, VIDEO, etc.
|
||||
- **Block Data**: Typed Value Object mit block-spezifischen Daten
|
||||
- **Block ID**: Eindeutige ID innerhalb des Contents
|
||||
- **Block Settings**: Optionale Konfiguration (Styling, Visibility)
|
||||
|
||||
### Beispiel: Landing Page
|
||||
|
||||
```php
|
||||
use App\Domain\Cms\Entities\Content;
|
||||
use App\Domain\Cms\Enums\BlockType;
|
||||
use App\Domain\Cms\ValueObjects\*;
|
||||
|
||||
$content = Content::create(
|
||||
clock: $clock,
|
||||
contentTypeId: ContentTypeId::fromString('landing_page'),
|
||||
slug: ContentSlug::fromString('homepage'),
|
||||
title: 'Welcome to Our Product',
|
||||
blocks: ContentBlocks::fromArray([
|
||||
ContentBlock::create(
|
||||
blockType: BlockType::HERO,
|
||||
blockId: 'hero-1',
|
||||
data: BlockData::fromArray([
|
||||
'title' => 'Revolutionary Product',
|
||||
'subtitle' => 'Transform your workflow',
|
||||
'backgroundImage' => 'img-123', // MediaId als String
|
||||
'ctaText' => 'Get Started',
|
||||
'ctaLink' => '/signup'
|
||||
])
|
||||
),
|
||||
ContentBlock::create(
|
||||
blockType: BlockType::TEXT,
|
||||
blockId: 'text-1',
|
||||
data: BlockData::fromArray([
|
||||
'content' => '<p>Our product is...</p>',
|
||||
'alignment' => 'center'
|
||||
])
|
||||
)
|
||||
])
|
||||
);
|
||||
```
|
||||
|
||||
## Datenbank-Schema
|
||||
|
||||
### content_types
|
||||
|
||||
Speichert Content Type Definitionen:
|
||||
- `id` (VARCHAR) - Content Type Identifier
|
||||
- `name` (VARCHAR) - Display Name
|
||||
- `slug` (VARCHAR) - URL-freundlicher Slug
|
||||
- `description` (TEXT) - Beschreibung
|
||||
- `is_system` (BOOLEAN) - System Content Type
|
||||
|
||||
### contents
|
||||
|
||||
Speichert Content Instanzen:
|
||||
- `id` (VARCHAR) - ULID Identifier
|
||||
- `content_type_id` (VARCHAR) - Foreign Key zu content_types
|
||||
- `slug` (VARCHAR) - URL-freundlicher Slug (unique)
|
||||
- `title` (VARCHAR) - Content Titel
|
||||
- `blocks` (JSON) - Array von Block-Objekten
|
||||
- `meta_data` (JSON) - SEO, Open Graph Metadaten
|
||||
- `status` (ENUM) - draft, published, archived
|
||||
- `author_id` (VARCHAR) - Author User ID
|
||||
- `published_at` (TIMESTAMP) - Veröffentlichungsdatum
|
||||
- `created_at`, `updated_at` (TIMESTAMP)
|
||||
|
||||
## Verwendung
|
||||
|
||||
### ContentService
|
||||
|
||||
```php
|
||||
use App\Domain\Cms\Services\ContentService;
|
||||
use App\Domain\Cms\ValueObjects\*;
|
||||
|
||||
// Content erstellen
|
||||
$content = $contentService->create(
|
||||
contentTypeId: ContentTypeId::fromString('landing_page'),
|
||||
title: 'Welcome Page',
|
||||
blocks: ContentBlocks::fromArray([...]),
|
||||
slug: ContentSlug::fromString('homepage')
|
||||
);
|
||||
|
||||
// Content finden
|
||||
$content = $contentService->findBySlug(ContentSlug::fromString('homepage'));
|
||||
|
||||
// Content veröffentlichen
|
||||
$published = $contentService->publish($content->id);
|
||||
|
||||
// Content aktualisieren
|
||||
$updated = $contentService->updateBlocks(
|
||||
$content->id,
|
||||
ContentBlocks::fromArray([...])
|
||||
);
|
||||
```
|
||||
|
||||
## REST API
|
||||
|
||||
### Endpoints
|
||||
|
||||
- `GET /api/v1/cms/contents` - Liste aller Contents
|
||||
- `GET /api/v1/cms/contents/{id}` - Content Details
|
||||
- `POST /api/v1/cms/contents` - Content erstellen
|
||||
- `PUT /api/v1/cms/contents/{id}` - Content aktualisieren
|
||||
- `DELETE /api/v1/cms/contents/{id}` - Content löschen
|
||||
- `POST /api/v1/cms/contents/{id}/publish` - Content veröffentlichen
|
||||
- `POST /api/v1/cms/contents/{id}/unpublish` - Content zurückziehen
|
||||
|
||||
### Beispiel Request
|
||||
|
||||
```json
|
||||
POST /api/v1/cms/contents
|
||||
{
|
||||
"content_type_id": "landing_page",
|
||||
"slug": "homepage",
|
||||
"title": "Welcome Page",
|
||||
"blocks": [
|
||||
{
|
||||
"id": "hero-1",
|
||||
"type": "hero",
|
||||
"data": {
|
||||
"title": "Revolutionary Product",
|
||||
"subtitle": "Transform your workflow",
|
||||
"backgroundImage": "img-123",
|
||||
"ctaText": "Get Started",
|
||||
"ctaLink": "/signup"
|
||||
},
|
||||
"settings": {
|
||||
"fullWidth": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"status": "draft"
|
||||
}
|
||||
```
|
||||
|
||||
## Block Types
|
||||
|
||||
- `HERO` - Hero Section mit Titel, Bild, CTA
|
||||
- `TEXT` - Rich Text Block
|
||||
- `IMAGE` - Einzelnes Bild mit Caption
|
||||
- `GALLERY` - Bildergalerie
|
||||
- `CTA` - Call-to-Action Block
|
||||
- `VIDEO` - Video Embed
|
||||
- `FORM` - Formular Integration
|
||||
- `COLUMNS` - Multi-Column Layout
|
||||
- `QUOTE` - Zitat Block
|
||||
- `SEPARATOR` - Trenner/Divider
|
||||
|
||||
## Integration mit anderen Domains
|
||||
|
||||
### Media Domain
|
||||
|
||||
Block Data kann MediaId als String enthalten:
|
||||
```php
|
||||
BlockData::fromArray([
|
||||
'imageId' => 'img-123', // MediaId als String
|
||||
'caption' => 'Product Screenshot'
|
||||
])
|
||||
```
|
||||
|
||||
### Meta Domain
|
||||
|
||||
SEO-Metadaten können in `meta_data` gespeichert werden:
|
||||
```php
|
||||
$content = $content->withMetaData(BlockData::fromArray([
|
||||
'title' => 'SEO Title',
|
||||
'description' => 'SEO Description',
|
||||
'og_title' => 'OG Title',
|
||||
'og_image' => 'og-image.jpg'
|
||||
]));
|
||||
```
|
||||
|
||||
## Migration
|
||||
|
||||
```bash
|
||||
php console.php db:migrate
|
||||
```
|
||||
|
||||
Die Migrations erstellen die Tabellen `content_types` und `contents` mit JSON-Spalten für Blocks.
|
||||
|
||||
Reference in New Issue
Block a user