Initial commit: Docker infrastructure for fullstack Vollversammlung app

This commit is contained in:
2026-06-28 15:37:48 +02:00
commit 2960855bca
21 changed files with 1434 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
# Architekt (Phase 1 & 3)
## Rolle
Zuständig für Systemarchitektur, Datenfluss, API-Konventionen, Auth-Fluss und Architektur-Conformance im Review.
## Aufgaben — Phase 1: Design
- Gesamtsystemarchitektur erarbeiten (abgeschlossen → s. `Doku/architektur.md`)
- Datenfluss zwischen Diensten definieren (Provider/Processor-Pattern)
- API-Konventionen festlegen (REST `/api/*`, PascalCase Feature-Namen Deutsch)
- OAuth-Flow designen (Vonova Auth Integration)
- Multi-Tenant Scoping Konzept finalisieren (SQL Builder Trait, implizites Scoping)
## Aufgaben — Phase 3: Architektur-Review
- Konformance zu `Doku/architektur.md` prüfen (Provider/Processor-Pattern, UI/Logic/Data-Schichten)
- Entwurfsentscheidungen validieren (Feature-Namen PascalCase Deutsch?, MVC-Aufteilung korrekt?)
- Architekturvorschläge für Codeänderungen geben
## Kontext
- Projekt: Veranstaltungs-Tool mit Symfony Backend, React Frontend
- OAuth-Anbieter: Vonova
- Datenbank: PostgreSQL (Multi-Tenant-fähig)
- Auth: JWT/Session-basiert nach OAuth-Flow
- Logging: Mercure
- Architektur-Doku: `Doku/architektur.md`
## Regeln & Konventionen
- Immer Multi-Mandanten-Architektur beachten (implizites SQL Builder Scoping pro Repository)
- Alle API-Konventionen müssen konsistent mit `Doku/architektur.md` sein
- Feature-Namen sind PascalCase auf Deutsch (`Veranstaltungen`, nicht `Events`)
- Struktur: UI → Logic → Data, keine Schichten-Überschreitungen (zB keine DB-Zugriffe in Controllern)
- Architekturänderungen müssen dokumentiert und begründet werden
- Keine lokalen Fixes ohne architekturweite Auswirkungsprüfung
## Erwartete Deliverables
- Architektur-Dokumentation (`Doku/architektur.md`)
- API-Spezifikation (`/api/*` REST, Request/Response DTOs)
- OAuth-Architektur-Konzept
- Provider/Processor-Pattern Dokumentation
- Review-Berichte zur Architektur-Conformance
+57
View File
@@ -0,0 +1,57 @@
# Backend-Coder (Phase 2)
## Rolle
Zuständig für Symfony Boilerplate, Domain-Schichten nach Provider/Processor-Pattern, CRUD-Controller und OAuth-Integration (Vonova).
## Aufgaben — Struktur
Die Codebasis folgt der in `Doku/architektur.md` definierten Dreier-Schichten-Architektur:
- `UI/{Client}/{Feature}/` Controller, Request DTOs, Response DTOs
- `Logic/{Feature}/` Manager, Calculator, Model, Provider/Processor Interfaces, Shared
- `Data/{Feature}/` Doctrine Entities, Repository-Interfaces und Implementierungen
- `Shared/` Entitätsübergreifende Basisklassen (`BaseEntity`, `UuidGenerator`)
## Aufgaben — Logic-Schicht (Provider & Processor)
- **Entities** nach `Doku/plan.md` als Doctrine Entity-Attribute implementieren
- **Repository-Schicht**: Alle Queries über den SQL Builder Trait mit Mandant-Scoping implizit, keine expliziten Filter im Provider
- **Provider** (`*ProviderInterface`): Lesender Zugriff auf Data-Schicht; Mappen von Entities → Models über separate Mapper-Klassen in `Data/`
- **Processor** (`*ProcessorInterface`): Schreibende Prozesse; gleichen Mapper-Pattern für Models → Entities nutzen
- **Manager**: Cache, Workflow, Aggregate pro Use-Case
- **Calculator**: Berechnungen (Statistiken)
## Aufgaben — UI-Schicht
- **Feature-Namenskonvention**: PascalCase auf Deutsch (`Veranstaltungen`, `Personen`, `Anwesenheit`)
- Beispiel-Pfad: `UI/API/Veranstaltungsmanagement/Controller/VeranstaltungsmanagementController.php`
- **REST-Endpunkte** unter `/api/*` mit request/response DTOs
- Validierung auf Request-Level (DTO-Attribute)
## Aufgaben — OAuth & Infrastruktur
- Vonova OAuth-Flow implementieren (Login / Callback / Token Handling)
- JWT/Session Handhabung (HttpOnly, Secure, SameSite=lax Cookies)
- Mandantenisolation via Auth-Token validieren und an Mandanten-Scoping durchreichen
- Mercure als Logging-Transport einrichten
## Kontext
- Projekt: Veranstaltungs-Tool mit Symfony Backend, React Frontend
- OAuth-Anbieter: Vonova
- Datenbank: PostgreSQL (Multi-Tenant-fähig erforderlich)
- Auth: JWT/Session-basiert nach OAuth-Flow
- Architektur-Konventionen: `Doku/architektur.md`
## Regeln & Konventionen
- Feature-Namen sind PascalCase auf Deutsch, use-case-basiert (zB `Veranstaltungen`, nicht `Events`)
- Alle Controller müssen PSR-12 konform sein
- Doctrine Entities müssen vollständig als Attribute annotiert sein
- Validierungsregeln pro Feld
- Provider/Processor-Pattern strikt einhalten keine DB-Zugriffe in Controllern
- Mandant-Scoping ausschließlich über SQL Builder Trait manuelle WHERE-Klauseln in Repositories nicht erlaubt
- Request/Response DTOs für alle API-Endpunkte verwenden
## Erwartete Deliverables
- Symfony Boilerplate nach `Doku/architektur.md` Struktur
- Entities, Repository-Interface + Implementierung (mit Scalar Builder Traits)
- Provider/Processor-Schicht pro Feature
- CRUD-Controller mit Request/Response DTOs (REST `/api/*`)
- Vonova OAuth Integration
- Login, Callback, Token Handling
- JWT/Session Handhabung
- Mercure Logging-Anbindung
+32
View File
@@ -0,0 +1,32 @@
# Datenbank-Architekt (Phase 1)
## Rolle
Zuständig für Datenbank-Design, Entities, Beziehungen, Indizes und Multi-Mandanten-Fähigkeit auf DB-Ebene.
## Aufgaben
- Datenbankschema entwerfen (PostgreSQL)
- Entities und Relationen definieren (s. `Doku/plan.md` und `Doku/architektur.md`)
- Indizes für performante Abfragen planen
- Mandanten-Scoping auf DB-Ebene implementieren über SQL Builder Trait (implizites Scoping alle Queries werden automatisch mit `mandant_id` gefiltert)
- Migration-Konzept erarbeiten
## Kontext
- Projekt: Veranstaltungs-Tool mit Symfony Backend, React Frontend
- OAuth-Anbieter: Vonova
- Datenbank: PostgreSQL (Multi-Tenant-fähig erforderlich)
- Auth: JWT/Session-basiert nach OAuth-Flow
- Scoping-Konvention: `Doku/architektur.md` → implizites SQL Builder Trait Scoping
## Regeln & Konventionen
- Alle Entities müssen Mandant-ID als Foreign Key enthalten
- **Implizites Scoping**: Alle Repository-Queries über den Doctrine SQL Builder Trait mit automatisch angehängtem `mandant_id` keine manuellen WHERE-Klauseln in Repositories
- Tenant-übergreifende Operationen sind technisch nicht möglich (alle Repos implicit scoped)
- Indizes für häufige Queries planen (Fremdschlüssel, Unique Constraints, WHERE clauses)
- Soft-Delete über `deleted_at` Feld statt physischem Löschen implementieren
- Migrationen immer versioniert und rollback-fähig halten
## Erwartete Deliverables
- ER-Diagramm mit allen Entitäten und Beziehungen
- Entity-Definitionen (Symfony Doctrine-Konform, s. `Doku/plan.md`)
- Indexierungsplan
- Mandant-Scoping Konzept (SQL Builder Trait-basiert)
+33
View File
@@ -0,0 +1,33 @@
# DevOps-Coder (Phase 2 & 4)
## Rolle
Zuständig für Containerisierung, Service-Setup, Konnektivität zwischen Diensten und Environment-Konfiguration mit Health-Checks.
## Aufgaben — Phase 2: Implementierung
- docker-compose.yaml strukturieren (Symfony/PHP, PostgreSQL, React, Mercure)
- Services konfigurieren und untereinander vernetzen (Networks)
- Service-Mesh/Konnektivität sicherstellen (Docker Networks, Volume-Mounts, Ports)
## Aufgaben — Phase 4: Finale Integration
- Environment-Konfiguration für verschiedene Umgebungen (Dev/UAT/Prod)
- Health-Checks pro Service implementieren (docker-compose healthcheck / Procfile-basiert)
- Deployment-Szenarien definieren
## Kontext
- Projekt: Veranstaltungs-Tool mit symfony Backend, React Frontend
- OAuth-Anbieter: Vonova
- Datenbank: PostgreSQL (Multi-Tenant-fähig erforderlich)
- Auth: JWT/Session-basiert nach OAuth-Flow
- Additional Services: Mercure (Logging)
## Regeln & Konventionen
- docker-compose für lokale Entwicklung verwenden
- Environment-Variablen nicht hardcoden (.env.local, .env.example Vorlage)
- Health-Checks müssen alle kritischen Services abdecken (PostgreSQL, Symfony API, React dev-server, Mercure)
- Docker volumes für Symfony cache/logs konfigurieren
## Erwartete Deliverables
- docker-compose.yml mit allen Services
- Network & Volume-Konfiguration (inkl. Mercure)
- .env.example Vorlage mit allen erforderlichen Variablen
- Health-checks pro Service (inkl. Mercure)
+47
View File
@@ -0,0 +1,47 @@
# Frontend-Coder (Phase 2)
## Rolle
Zuständig für React Boilerplate, Router, State-Management, View-Komponenten und Statistikseite.
## Aufgaben
- React boilerplate aufsetzen (Projektstruktur, Verzeichnisstruktur, Konfiguration)
- Router konfigurieren (React Router)
- State-Management implementieren (z.B., Zustand / Redux Toolkit)
- View-Komponenten für alle Seiten bauen:
- OAuth Login-Seite mit Vonova Redirect → keine Token im Client-Speicher!
- Tabellen-/Listen-Ansichten für Anwesenheitsdaten
- Detailseiten für Entitäten
- Statistikseite implementieren
- Übersicht über Anwesenheit, Statistiken pro Mandant/Kategorie
## Aufgaben — API-Kommunikation
- Alle API-Calls nach `/api/*` REST Endpunkten (s. `Doku/architektur.md`)
- Session-Cookies (HttpOnly) für Authentication verwenden
- Request/Response DTO-Schema der Backend-API kennen und einhalten
- Mandantenspezifische Daten im State trennen
## Aufgaben — Logging/Mercure
- Mercure als Echtzeit-Logging-Transport im Frontend abonnieren/anbinden
## Kontext
- Projekt: Veranstaltungs-Tool mit Symfony Backend, React Frontend
- OAuth-Anbieter: Vonova
- Datenbank: PostgreSQL (Multi-Tenant-fähig erforderlich)
- Auth: JWT/Session-basiert nach OAuth-Flow
- Architektur-Konventionen: `Doku/architektur.md`
## Regeln & Konventionen
- Alle Komponenten müssen funktional sein und Hooks verwenden
- State-Management muss mandantenspezifische Daten separat halten
- Keine Hardcoded URLs API-Basis-URL aus env Variablen lesen (`VITE_API_URL`)
- TypeScript für alle Komponenten verwenden
- Responsive Design: Alle Views müssen auf verschiedenen Bildschirmgrößen funktionieren
- OAuth-Tokens niemals in localStorage/sessionStorage speichern!
## Erwartete Deliverables
- React boilerplate mit aller notwendiger Konfiguration
- Router mit allen benötigten Routes (Login, Protokoll, Statistik, etc.)
- State-Management Setup
- View-Komponenten für alle Seiten
- Statistikseite mit Datenvisualisierung
- Anbindung an `/api/*` REST Endpunkte nach `Doku/architektur.md`
+44
View File
@@ -0,0 +1,44 @@
# Security-Reviewer (Phase 3)
## Rolle
Zuständig für OAuth-Integrationssicherheit, Datenisolation, JWT/Session-Handling und SQL-Injection-Prüfung.
## Aufgaben
- OAuth-Integration reviewen:
- Token-Speicherung sicher? (nicht im localStorage!)
- Refresh-Token-Handhabung korrekt implementiert?
- PKCE-Flow für Vonova OAuth eingehalten?
- Datenisolation prüfen:
- Alle Queries mandantenbezogen über SQL Builder Trait gefiltert?
- Keine IDOR-Lücken in REST `/api/*` Endpunkten?
- Subquery-Injection gegen Mandantengrenzen möglich?
- JWT/Session-Handling auditieren:
- Tokens kurzlebig und sicher gespeichert? (HttpOnly Cookies?)
- Session-Fixierung verhindert?
- Token-Revocation implementiert?
- SQL-Injection prüfen:
- Alle Doctrine Queries verwenden parametrisierte Werte?
- Native/Query-Builder-Nutzung korrekt?
- Keine raw-sql ohne Parameterisierung?
## Kontext
- Projekt: Veranstaltungs-Tool mit Symfony Backend, React Frontend
- OAuth-Anbieter: Vonova
- Datenbank: PostgreSQL (Multi-Tenant-fähig erforderlich)
- Auth: JWT/Session-basiert nach OAuth-Flow
- Architektur-Konventionen: `Doku/architektur.md`
## Regeln & Konventionen
- Keine hardcoded secrets oder tokens in code
- Alle credentials über env vars beziehen
- OAuth-Tokens niemals im Client-Speicher (localStorage, sessionStorage) speichern
- Session-Cookies müssen HttpOnly, Secure und SameSite=lax sein
- Input validation auf jeder Schicht: Frontend + API + DB-Level
- Regelmäßige dependency-security-checks durchführen
## Erwartete Deliverables
- OAuth-Security-Review-Bericht (Token Handling, PKCE, Refresh-Token)
- Datenisolation-Audit (IDOR, SQL Builder Trait Scoping, Mandantengrenzen)
- JWT/Session-Handling Audit
- SQL-Injection-Analyse
- Security-Recommendations mit Priorisierung
+45
View File
@@ -0,0 +1,45 @@
# Tester (Phase 3 & 4)
## Rolle
Zuständig für Unit Tests, Functional Tests, E2E-Szenarien und Integrationstests sowie globale Workflow-Validierung.
## Aufgaben — Phase 3: Qualitätssicherung
- Unit Tests schreiben für alle Services und Business Logic
- Doctrine Entities (Validation, Relationships)
- Symfony Services (OAuth Logic, Mandanten-Scoping per SQL Builder Trait)
- Provider/Processor-Koordination pro Use-Case
- Functional Tests schreiben (Symfony PHPUnit / functional testing)
- CRUD-Operationen via REST `/api/*` testen (Request/Response DTOs validieren)
- OAuth-Integration testen
- Mandantenisolisation testen (Daten pro Mandant isoliert?)
- E2E-Szenarien vorbereiten:
- Anwesenheitsprotokollierung (vollständiger Workflow vom Login bis zur Protokolleintragung)
- Mandantenisolation (echter Multi-Tenant-Szenario via API-Aufrufe)
## Aufgaben — Phase 4: Finale Integration
- Integrationstests durchführen (End-to-end workflows validieren)
- Gesamt-Workflow von OAuth Login bis Anwesenheitsprotokollierung
- Environment-spezifische Tests (Dev/UAT/Prod Konfigurationen prüfen)
- Alle Testlücken dokumentieren
## Kontext
- Projekt: Veranstaltungs-Tool mit Symfony Backend, React Frontend
- OAuth-Anbieter: Vonova
- Datenbank: PostgreSQL (Multi-Tenant-fähig erforderlich)
- Auth: JWT/Session-basiert nach OAuth-Flow
- Architektur-Konventionen: `Doku/architektur.md`
## Regeln & Konventionen
- Testabdeckung für alle kritischen Pfade > 80% (Anwesenheitsprotokollierung, Mandantenisolation, OAuth-Flows)
- Functional Tests nutzen Symfony WebTestCase / functional testing framework
- E2E-Szenarien in playwright/cypress implementieren (Login → API-Aufruf → DB-Validierung)
- Pro Test: Ein einziger klarer Szenario mit assertierbaren Ergebnissen
- Tests müssen Mandantenisolation explizit prüfen
- Provider/Processor-Koordination testen
## Erwartete Deliverables
- Unit Tests für alle Services, Controller und Entities
- Functional Tests mit allen CRUD- und OAuth-Szenarien
- E2E-Szenarien (Anwesenheitsprotokoll Workflow, Mandantendisolation)
- Integrationstests
- Testabdeckungsreport
+33
View File
@@ -0,0 +1,33 @@
{
"$schema": "https://opencode.ai/config.json",
"agent": {
"architekt": {
"description": "Architektur-Design und Architektur-Review",
"path": ".opencode/agents/architekt.md"
},
"db-architekt": {
"description": "Datenbank-Design, Entities, Indizes und Mandanten-Scoping",
"path": ".opencode/agents/db-architekt.md"
},
"devops-coder": {
"description": "Docker-Konfiguration, Services, Health-Checks und Environment-Einrichtung",
"path": ".opencode/agents/devops-coder.md"
},
"backend-coder": {
"description": "Symfony Boilerplate, Entities, CRUD-Controller und OAuth-Integration",
"path": ".opencode/agents/backend-coder.md"
},
"frontend-coder": {
"description": "React boilerplate, Router, State-Management und View-Komponenten",
"path": ".opencode/agents/frontend-coder.md"
},
"tester": {
"description": "Unit-, Functional-, E2E- und Integrationstests",
"path": ".opencode/agents/tester.md"
},
"security-reviewer": {
"description": "OAuth-Sicherheit, Datenisolation, JWT/Session-Handling und SQL-Injection-Prüfung",
"path": ".opencode/agents/security-reviewer.md"
}
}
}
+40
View File
@@ -0,0 +1,40 @@
# Vollversammlung Agent Instructions
## Project
Mandantenfähiges Veranstaltungs-Tool mit Anwesenheitsverwaltung und Statistik.
- Backend: Symfony (manuelle Controller, keine API Platform)
- Frontend: React
- DB: PostgreSQL (Multi-Tenant, Scoping per MandantID)
- Auth: externer OAuth (Vonova), kein lokaler Login
- Logging: Mercure
- Infrastruktur: Docker / docker-compose
## Structure (planned)
```
vollversammlung/
├── .opencode/ ← OpenCode agents + config (opencode.json)
├── Doku/ ← Planungsdokumente, Architektur
├── api/ ← Symfony Backend
└── frontend/ ← React Frontend
```
## Key constraints the repo doesn't make obvious
- Personen haben **keine** email/benutzername-Felder OAuth liefert Identität extern
- Mandantenisolation ist core: alle Daten pro MandantID scoped, keine Überschneidung
- Auth-Tokens niemals im Client-Speicher (localStorage/sessionStorage)
- Session-Cookies: HttpOnly, Secure, SameSite=lax
- Input-Validierung auf jeder Schicht: Frontend + API + DB-Level
## Agent config
Agents are defined in `.opencode/opencode.json` and `.opencode/agents/*.md`. They cover six roles: architekt, db-architekt, backend-coder, frontend-coder, tester, devops-coder. Loading agent instructions from `.opencode/` is the primary configuration surface no other OpenCode config files exist at root level.
## Referenzen
- `Doku/architektur.md` → Backend Architektur mit struktur & Konventionen (Provider/Processor-Pattern, Multi-Tenant Scoping)
- `Doku/plan.md` → Domain-Details und Entity-Spezifikationen (Veranstaltung, Personen, Anwesenheit)
- `Doku/agents_planung.md` → 4-Phasen-Agent-Arbeitsverteilung
## What to do on first session in api/ or frontend/
1. Read `.opencode/opencode.json` for agent definitions
2. Read `Doku/architektur.md` und `Doku/plan.md` für Architektur und Domain-Anforderungen
3. Read the relevant agent file from `.opencode/agents/` fur implementierung-Konventionen
4. Prüfe ob Infrastruktur eingerichtet ist (docker-compose.yaml, .env.example) vor Code-Schreiben
+31
View File
@@ -0,0 +1,31 @@
# Agents-Planung Veranstaltungs-Tool
## Phase 1: Design
| Rolle | Aufgaben |
|---|---|
| **Architekt** | Systemarchitektur, Datenfluss, API-Konventionen, Auth-Fluss |
| **Datenbank-Architekt** | Entities, Beziehungen, Indizes, Mandanten-Scoping auf DB-Ebene |
## Phase 2: Implementierung
| Rolle | Aufgaben |
|---|---|
| **DevOps-Coder** | docker-compose.yaml, Services, Konnektivitat |
| **Backend-Coder** | Symfony boilerplate, Entities, CRUD-Controller, OAuth-Integration |
| **Frontend-Coder** | React boilerplate, Router, State-Management, View-Komponenten, Statistikseite |
## Phase 3: Qualität
| Rolle | Aufgaben |
|---|---|
| **Tester** | Unit Tests, Functional Tests, E2E-Szenarien (Anwesenheitsprotokollierung, Mandantenisolation) |
| **Security-Reviewer** | OAuth-Integration, Datenisolation, JWT/Session handling, SQL-Injection Check |
| **Architekt (Review)** | Architektur-Conformance, Entwurfsentscheidungen nachprufen |
## Phase 4: Finale Integration
| Rolle | Aufgaben |
|---|---|
| **DevOps-Coder** | Environment-Konfiguration, Health-Checks |
| **Tester** | Integrationstests, Gesamt-Workflow validieren |
+68
View File
@@ -0,0 +1,68 @@
# Backend-Architektur Veranstaltungs-Tool (Konzept)
## Ziel
Mandantenfähiges Backend mit 3 Schichten pro fachlicher Domäne und mehreren Clients/Entry-Points.
## Struktur
```
api/
├── public/index.php ← Entrypoint
└── src/
├── UI/{ClientName}/{FeatureName}/ ← pro Client + Use-Case (PascalCase, deutsch)
│ ├── Controller/ REST-Endpunkte (/api/*), Commands etc.
│ ├── Request/ Request-DTOs
│ └── Response/ Response-DTOs
├── Logic/{FeatureName}/ ← Business Rules pro Use-Case
│ ├── Manager/ Cache, Workflow, Aggregate
│ ├── Calculator/ Berechnungen (Statistiken)
│ ├── Model/ Feature-Modelle
│ ├── Provider/*ProviderInterface Interfaces für Data-Layer
│ ├── Processor/*ProcessorInterface Interface für Verarbeitungslogik
│ └── Shared/ Feature-spezifisches Shared (wenn notwendig)
├── Data/{FeatureName}/ ← Domain-Objects, DB-Zugriff pro Use-Case
│ ├── Entity/ Doctrine Entities
│ └── Repository/ Repo-Interfaces + Implementierungen
└── Shared/ ← Nur wirklich entitätsübergreifende Basics
├── BaseEntity.php
└── UuidGenerator.php
```
## Client-Namenskonvention
- `API` → REST-Schnittstelle (UI/API/*)
- `Console` → Symfony Commands (UI/Console/*)
## Feature-Namenskonvention
Use-Case-basiert, PascalCode auf Deutsch. ZB:
- `Personen` (nicht Personenliste / PersonController)
- `Veranstaltungen` (nicht VeranstaltungenIndex)
- `Anwesenheit` (nicht AnwesenheitsstatusErfassung)
Beispiel-Pfad: `UI/API/Personen/Controller/PersonenController.php`
**Model/** pro Feature Ordner mit ggf. mehreren Models definiert die Feature-Grenze.
## Entry-Points
Mehrere Clients via Router-basierte Routing:
- `/api/*` → REST-Controller (`UI/API/{Feature}/Controller/`)
- Console → Symfony Commands (`UI/Console/{Feature}/Command/`)
- Messages → Messenger Handler
## Entscheidungen
### Provider und Processoren
**Provider** implementieren die `*ProviderInterface`. Sie rufen Daten ab per Doctrine Repositories/Entities oder über API-Aufrufe. Das Mapping zwischen Entities und Model-Objekten erfolgt in separaten **Mapper**-Klassen, die in **Data/** liegen. Provider binden so die Data-Schicht an die Logic-Schicht.
**Processoren** funktionieren analog für schreibende Prozesse (`*ProcessorInterface`).
### Shared-Klassen
Echt entitätsbergreifende Basis-Klassen (`BaseEntity`, `UuidGenerator`) leben in `src/Shared/`. Feature-spezifisches Shared liegt innerhalb des Features, zB:
```
src/Logic/Veranstaltungsmanagement/Shared/JsonSerializer.php
```
### Multi-Tenant Scoping
Alle Repository-Queries werden per **Doctrine SQL Builder Trait** mit `mandant_id` gefiltert. Der Scope wird automatisch angehängt sowohl lesend als auch schreibend. Tenantübergreifende Operationen sind technisch nicht möglich, da alle Repos implicit scopen. Das schützt vor unbeabsichtigten Mandanten-Überschneidungen. Bei Bedarf kann eine explizite WithoutScope-Schicht eingeführt werden.
### Dependency Injection
Nur explizit über `services.yaml`, wenn notwendig. Standardmäßig wird Auto-Wiring mit Konventionen verwendet (`*ProviderInterface`, `*ProcessorInterface`).
+339
View File
@@ -0,0 +1,339 @@
# Infrastruktur-Planung — Vollversammlung
## Inhaltsverzeichnis
1. [docker-compose.yaml Services](#1-dockercomposeyaml--services)
2. [Netzwerk & Volumes](#2-netzwerk--volumes)
3. [api/ Verzeichnis Vorbereitungen](#3-apiverzeichnis--vorbereitungen)
4. [frontend/ Verzeichnis Vorbereitungen](#4-frontend-verzeichnis--vorbereitungen)
5. [Multi-Tenant Scoping Infrastruktur-Ansatz](#6-multi-tenant-scoping--infrastruktur-ansatz)
6. [OAuth (Vonova) Infrastruktur](#7-oauth-vonova--konfiguration)
7. [Lokaler Start](#8-lokaler-start)
8. [Health Checks](#9-health-checks)
9. [.env.example Konfigurationen](#10-envexample-konfigurationen)
10. [Checkliste Nächste Schritte](#11-checkliste--nächste-schritte-zur-implementierung)
---
## 1. docker-compose.yaml Services
Sechs Services, alle über `vollversammlung_net` vernetzt:
| Service | Image | Externer Port | Internes Ziel | Rolle |
|---------|-------|---------------|--------------|-------|
| **postgres-db** | postgres:16-alpine | kein (intern) | :5432 | PostgreSQL-Datenbank, MandantID-gescoped |
| **symfony-fpm** | php:8.3-fpm-alpine (custom) | kein (über nginx) | :9000 | PHP-FPM Backend-Runtime |
| **nginx** | nginx:alpine | **8080** → :80 | Reverse Proxy, routet /api/* zu symfony-fpm, statisch zu frontend |
| **mercure-hub** | dunglas/mercure | **3000** → :80 | Event-Streaming für Logging in Echtzeit |
| **mailpit** | axllent/mailpit | **8025** (UI), intern :1025 (SMTP) | E-Mail Testing |
| **vite-dev** | node:22-alpine | **5173** → :5173 | React Dev Server mit Hot Module Replacement |
---
## 2. Netzwerk & Volumes
### Netzwerk
```yaml
networks:
vollversammlung_net:
driver: bridge
```
Alle Services joined auf `vollversammlung_net`. Nur nginx, mercure-hub, vite-dev und mailpit haben externe Port-Mappings. postgres-db und symfony-fpm sind ausschließlich intern erreichbar.
### Volumes
**Named Volumes:**
| Name | Mount-Punkt (symfony-fpm) / Ziel | Zweck |
|------|-----------------------------------|-------|
| `pg_data` | `/var/lib/postgresql/data` (post-db) | Persistente PostgreSQL-Daten |
| `symfony_var` | `/app/var/` | Symfony cache, logs |
**Bind-mounts (Development only):**
| Host-Pfad | Container | Mount-Pfad | Zweck |
|-----------|-----------|-----------|-------|
| `./api/src` | symfony-fpm | `/app/src` | Live-Coding PHP-Code |
| `./frontend/src` | vite-dev | `/app/src` | Live-Coding React-Code |
---
## 3. api/ Verzeichnis Vorbereitungen
### Dateistruktur
```
api/
├── .env.example (Commit-Sicherheit, keine Secrets)
├── .env.local (gitignored - lokale Secrets)
├── .dockerignore (Docker Build-Kontext filtering)
├── composer.json (Symfony Flex setup)
├── Dockerfile.dev (php:8.3-fpm-alpine Base)
├── Dockerfile.prod (Multi-stage build für Produktion)
├── docker-entrypoint.sh (Healthcheck + Init-Script)
├── public/
│ └── index.php (Symfony Entry Point über FPM)
├── config/
│ ├── packages/ (framework.yaml, mercure.yaml, doctrine.yaml etc.)
│ └── routes/ (API Routes YAML)
├── migrations/ (Doctrine Migration-Dateien)
├── src/
│ ├── Controller/ (Manuelle Controller kein API Platform!)
│ ├── Entity/ (Domain Entities mit MandantID-Eigenschaft)
│ ├── Repository/ (Doctrine Repositories mit Tenant-Scoping)
│ ├── Service/ (Business Logic Services)
│ ├── Middleware/ (MandantId-Middleware + OAuth-Processor)
│ └── Kernel.php
├── templates/ (Twig Templates)
├── var/ (cache, logs via Volume gemountet)
├── var/cache → symfony_var Volume
├── var/log → symfony_var Volume
└── bin/console (Symfony Command-Wrapper)
```
### PHP-Erweiterungen (Dockerfile.dev)
- `pdo_pgsql` Doctrine DBAL-Konnektivität
- `gd` Bildverarbeitung
- `intl` Internationalisierung
- `mbregex` Multibyte-Regex
---
## 4. frontend/ Verzeichnis Vorbereitungen
### Dateistruktur
```
frontend/
├── .env.example (Commit-Sicherheit)
├── .env.local (gitignored - lokale Secrets)
├── .dockerignore (Build-Kontext filtering)
├── package.json (React + Vite + TypeScript Dependencies)
├── vite.config.ts (Dev Server + Proxy zu nginx/symfony-fpm)
├── tsconfig.json (TypeScript Konfiguration)
├── Dockerfile.dev (node:22-alpine Base)
├── public/ (Statische Assets favicon, manifest etc.)
└── src/
├── components/ (UI-Komponenten z.B. Anwesenheitsliste, Statistiken)
├── pages/ (Seiten Dashboard, Veranstaltungsdetail etc.)
├── stores/ (Zustandsmanagement KEINE OAuth-Tokens!)
├── utils/ (HTTP-Client, Auth-Helfer etc.)
├── App.tsx (Root-Komponente)
└── main.tsx (Entry Point)
```
### Vite Proxy-Konfiguration (vite.config.ts)
- `/api/*``http://nginx:80/api/*` (Reverse-Proxy auf Symfony)
- `/.well-known/mercure``http://mercure-hub:3000/.well-known/mercure`
Damit kommuniziert der Vite Dev Server (`localhost:5173`) nahtlos mit Backend-Services.
---
## 5. Multi-Tenant Scoping Infrastruktur-Ansatz
**Kein Schema-per-Tenant.** Stattdessen Row-Level-Scoping über `mandant_id` in ALLEN Tabellen.
| Schicht | Mechanismus |
|---------|-------------|
| **Client** | MandantID per HTTP Header `X-Mandant-ID` an API senden. Token NUR in HttpOnly, Secure, SameSite=lax Cookies. |
| **Nginx** | Forwarded `X-Mandant-ID` Header via `proxy_set_header` an symfony-fpm weiter. |
| **Symfony Middleware** | Extrahiert MandantID aus Cookie/Header → setzt als RequestAttribute. |
| **Doctrine Layer** | Alle Entities erben Base-Entity mit `mandant_id`. Repositories scopen automatisch `WHERE mandant_id = :mandantId`. |
### Security-Garantie
- Tenant-überschreitende Operationen technisch unmöglich ohne explizites `WithoutScope`-Attribut (standardmäßig nicht gewährt).
- Keine email/benutzername-Felder in Personen-Entity Identität stammt ausschließlich aus OAuth.
---
## 6. OAuth (Vonova) Konfiguration
### Backend Environment Variables (.env.local api/)
```
OAUTH_PROVIDER_NAME=vonova
OAUTH_AUTHORIZATION_ENDPOINT=https://oauth.vonova.de/oauth/authorize
OAUTH_TOKEN_ENDPOINT=https://oauth.vonova.de/oauth/token
OAUTH_CLIENT_ID=<from Vonova admin>
OAUTH_CLIENT_SECRET=<from Vonova admin>
OAUTH_REDIRECT_URI=http://localhost:8080/api/oauth/callback
OAUTH_SCOPES=read profile mandant_id
```
### Frontend Environment Variables (frontend/.env.example)
```
VITE_OAUTH_AUTHORIZATION_ENDPOINT=https://oauth.vonova.de/oauth/authorize
VITE_OAUTH_TOKEN_ENDPOINT=https://oauth.vonova.de/oauth/token
```
### OAuth-Flow (Kurzbeschreibung)
1. Frontend leitet User zu Vonova Authorization Endpoint weiter.
2. Vonova authentifiziert User, gibt Authorisierungscode an `OAUTH_REDIRECT_URI` zurück.
3. Backend tauscht Code gegen Access-Token am Token Endpoint ein.
4. Backend extrahiert MandantID und Profil-Informationen aus dem Token-Payload.
5. Backend generiert Session-Cookie (HttpOnly, Secure, SameSite=lax).
6. Folge-Requests enthalten Cookie automatisch → authentifizierte Api-Zugriffe.
**Wichtig:** Tokens werden NIEMALS auf dem Client gespeichert (kein localStorage/sessionStorage).
---
## 7. Lokaler Start
### Setup
```bash
# Projekt rootVerzeichnis:
# Env-Templates kopieren (keine Secrets generieren Werte aus .env.example übernehmen):
cp api/.env.example api/.env.local
cp frontend/.env.example frontend/.env.local
# APP_SECRET und JWT-Keys in .env.local ergänzen:
docker compose exec symfony-fpm php bin/console security:encode-password
# → Output als APP_SECRET eintragen
# Mercure-JWT-Keys manuell generieren (Mindestlänge 32 Zeichen, kryptografisch sicher):
# z.B.: openssl rand -base64 64
```
### Services starten
```bash
# Alle Services im Hintergrund starten:
docker compose up -d
# Database-Migrations ausführen (erster Start):
docker compose exec symfony-fpm php bin/console doctrine:migrations:migrate --no-interaction
```
### Verfügbare Endpunkte
| URL | Service |
|-----|---------|
| `http://localhost:8080/` | Frontend + API (via nginx) |
| `http://localhost:3000/` | Mercure Hub Debug Interface |
| `http://localhost:5173/` | Vite Dev Server (Hot Reload) |
| `http://localhost:8025/` | Mailpit E-Mail Testing UI |
---
## 8. Health Checks
Jeder kritische Service verfügt über einen `healthcheck` in docker-compose.yaml:
| Service | Check-Methode | Command |
|---------|---------------|---------|
| **postgres-db** | `pg_isready` | `pg_isready -U vv_admin -d vollversammlung` |
| **mercure-hub** | HTTP-Well-Known-Path | `curl -sf http://localhost/.well-known/mercure` |
| **symfony-fpm** | API Health Endpoint *(vom Backend-Coder zu implementieren)* | `curl -sf http://localhost:8000/api/health` |
| **nginx** | Proxy-Pass zu symfony-fpm health | `curl -sf http://localhost:8080/api/health` |
---
## 9. .env.example Konfigurationen
### api/.env.example
```ini
# =========================================
# Vollversammlung Backend Environment
# Kopieren nach .env.local und anpassen!
# =========================================
APP_ENV=dev
APP_DEBUG=1
APP_SECRET=<symfony secret:generate oder security:encode-password>
DATABASE_URL="postgresql://vv_admin:<password>@postgres-db:5432/vollversammlung?serverVersion=16&charset=utf8mb4"
MERCURE_PUBLISHER_JWT_KEY=<openssl rand -base64 64>
MERCURE_SUBSCRIBER_JWT_KEY=<openssl rand -base64 64>
MERCURE_URL=http://mercure-hub/.well-known/mercure
MERCURE_PUBLIC_URL=http://localhost:3000/.well-known/mercure
MAILER_DSN=smtp://mailpit:1025
# =========================================
# Vonova OAuth
# =========================================
OAUTH_PROVIDER_NAME=vonova
OAUTH_AUTHORIZATION_ENDPOINT=https://oauth.vonova.de/oauth/authorize
OAUTH_TOKEN_ENDPOINT=https://oauth.vonova.de/oauth/token
OAUTH_CLIENT_ID=<from vonova admin>
OAUTH_CLIENT_SECRET=<from vonova admin>
OAUTH_REDIRECT_URI=http://localhost:8080/api/oauth/callback
OAUTH_SCOPES=read profile mandant_id
```
### frontend/.env.example
```ini
# =========================================
# Vollversammlung Frontend Environment
# Kopieren nach .env.local und anpassen!
# =========================================
VITE_API_URL=http://localhost:8080/api
VITE_MERCURE_URL=http://localhost:3000/.well-known/mercure
VITE_OAUTH_AUTHORIZATION_ENDPOINT=https://oauth.vonova.de/oauth/authorize
VITE_OAUTH_TOKEN_ENDPOINT=https://oauth.vonova.de/oauth/token
```
---
## 10. Multi-Environment Konfiguration
| Variable | dev (.env.local) | uat/staging (.env.staging) | prod (.env.prod) |
|----------|------------------|----------------------------|-------------------|
| `APP_ENV` | `dev` | `uat` | `prod` |
| `APP_DEBUG` | `1` | `0` | `0` |
| `CORS_ALLOW_ORIGIN` | `http://localhost:5173` | `https://uat.company.com` | `https://www.company.com` |
| `MAILER_DSN` | `smtp://mailpit:1025` | prod SMTP credentials | prod SMTP credentials |
| `DATABASE_URL` | localhost via docker | externe PG-Instanz | externe PG-Instanz |
| `MERCURE_PUBLISHER_JWT_KEY` | dev-key (openssl rand) | Vault-sekret | Vault/AWS Secrets Manager |
| `TRUSTED_HOSTS` | `^localhost$` | `^uat\.example\.com$` | `^www\.example\.com$` |
| `TRUSTED_PROXIES` | | `127.0.0.1,10.0.0.0/8` | Load Balancer IPs |
---
## 11. Checkliste — Nächste Schritte zur Implementierung
### Infrastruktur (DevOps-Coder)
- [ ] `docker-compose.yaml` mit allen 6 Services schreiben
- [ ] Networks & Volumes definieren
- [ ] Health-Checks pro Service implementieren
- [ ] nginx Konfiguration für Proxy-Routing erstellen
- [ ] `.env.example` in api/ und frontend/ anlegen
- [ ] Dockerfiles (dev + prod) pro Service erstellen
### Backend (Backend-Coder)
- [ ] Symfony-Projektstruktur in `api/` aufsetzen
- [ ] `composer.json` mit Symfony Flex konfigurieren
- [ ] Entity-Base für Multi-Tenant Scoping erstellen (`mandant_id` column)
- [ ] Doctrine Repositories mit automatischem Tenant-Scoping implementieren
- [ ] MandantId-Middleware schreiben (Cookie/Header → RequestAttribute)
- [ ] Vonova OAuth-Flow implementieren (Authorization Code Grant + PKCE)
- [ ] Session-Cookie Generation nach OAuth-Flow (HttpOnly, Secure, SameSite=lax)
- [ ] `/api/health` Endpoint erstellen
- [ ] Migrationen für alle Templates schreiben
### Frontend (Frontend-Coder)
- [ ] React/Vite/TypeScript Setup in `frontend/`
- [ ] OAuth Login-Flow im Browser implementieren (Authorization Code + PKCE mit redirect)
- [ ] Auth-Stores ohne token storage (nur Cookie-basiert)
- [ ] HTTP-Client mit MandantID-Header-Injection schreiben
- [ ] Mercure-Client für Event-Subscription einrichten
- [ ] Key-Pages: Dashboard, Veranstaltungsdetail, Anwesenheitsübersicht
---
*Stand: Juni 2026 — Infrastruktur-Planung Vollversammlung*
+57
View File
@@ -0,0 +1,57 @@
# Veranstaltungs-Tool Planungsdokumentation
## Ziel
Mandantenfähiges Tool zur Verwaltung von Veranstaltungen mit Anwesenheitsverwaltung und Statistik.
## Tech Stack
- Backend: Symfony (manuelle Controller, keine API Platform)
- Frontend: React
- Datenbank: PostgreSQL
- Logging: Mercure
- Deployment: Docker / docker-compose
- Auth: OAuth (extern, kein lokaler Login)
## Mandantenfähigkeit
- Daten werden pro MandantID gescoped
- Personen sind mandant-isoliert (keine Überschneidung)
## Veranstaltungen (CRUD via API)
- Titel
- Datum
- Beschreibung
- max_teilnehmer
Anlegen über: UI oder Import-API
## Personenverwaltung (Admin)
- Anlegen / Entfernen über UI oder Import-API
- Felder: vorname, nachname, geschlecht
- Kein lokaler Login (OAuth extern)
- Keine email / benutzername-Felder
## Anwesenheit
- Manuelle Markierung im UI
- Status: anwesend / abwesend
- Alle Änderungen werden geloggt (wer, wann, was)
## Statistikseite
- Anwesenheitszahlen pro Veranstaltung
- Anwesenheitsquote
- Geschlechterquote
- Dashboard-Ansicht als separate Statistik-Seite
## Projektstruktur
```
vollversammlung/
├── docker-compose.yaml
├── api/ (Symfony Backend Repo)
└── frontend/ (React Frontend Repo)
## Nächste Schritte
1. Boilerplate symfony erstellen
2. Boilerplate react erstellen
3. Docker compose konfigurieren
4. Datenmodelle implementieren
5. CRUD-Endpoints entwickeln
6. Frontend-Seiten aufbauen
7. Statistikseite implementieren
+47
View File
@@ -0,0 +1,47 @@
# =============================================
# Symphony Backend Environment Konfiguration
# Kopiere diese Datei nach .env.local und passe die Werte an.
# =============================================
# -------------------
# Symfony Basics
# -------------------
APP_ENV=dev
APP_DEBUG=1
APP_SECRET=__CHANGE_ME__
# -------------------
# PostgreSQL DB
# -------------------
DATABASE_URL="postgresql://symfony:secret@db:5432/symfony?serverVersion=16&charset=utf8mb4"
# -------------------
# Mercure Logging
# -------------------
MERCURE_JWT_SECRET=__CHANGE_ME_MERCURE_JWT_SECRET__
MERCURE_PUBLISHER_JWT_KEY=__CHANGE_ME_PUBLISHER_KEY__
MERCURE_SUBSCRIBER_JWT_KEY=__CHANGE_ME_SUBSCRIBER_KEY__
MERCURE_URL="http://mercure-hub/.well-known/mercure"
MERCURE_PUBLIC_URL="${MERCURE_PUBLIC_URL:-http://localhost:3000/.well-known/mercure}"
# -------------------
# OAuth (Vonova Provider)
# -------------------
OAUTH_PROVIDER="vonova"
OAUTH_PROVIDER_NAME="${OAUTH_PROVIDER_NAME:-vonova}"
OAUTH_AUTHORIZATION_ENDPOINT="https://oauth.vonova.de/oauth/authorize"
OAUTH_TOKEN_ENDPOINT="https://oauth.vonova.de/oauth/token"
OAUTH_CLIENT_ID=__CHANGE_ME_OAUTH_CLIENT_ID__
OAUTH_CLIENT_SECRET=__CHANGE_ME_OAUTH_CLIENT_SECRET__
OAUTH_REDIRECT_URI="http://localhost:8080/api/oauth/callback"
OAUTH_SCOPES="read profile mandant_id"
# -------------------
# Mandant-Scope (Standard/Tenant für lokale Entwicklung)
# -------------------
TENANT_ID=""
# -------------------
# Mail (Mailpit im Dev-Stack)
# -------------------
MAILER_DSN="smtp://mailpit:1025"
+54
View File
@@ -0,0 +1,54 @@
FROM php:8.3-fpm-alpine
# Install system dependencies
RUN apk add --no-cache \
imagemagick \
imagemagick-dev \
git \
curl \
oniguruma-dev \
libpng-dev \
libjpeg-turbo-dev \
libxml2-dev \
zip \
unzip
# Install PHP extensions
RUN docker-php-ext-install \
pdo_pgsql \
gd \
intl \
mbregex \
xml
# Install Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# Create app directory
WORKDIR /app
# Copy entrypoint script
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
# Set up Symfony directory structure
RUN mkdir -p \
src/UI/{API,Console}/{Controller,Request,Response,Command} \
src/Logic/{Manager,Calculator,Model,Provider,Processor,Shared} \
src/Data/{Entity,Repository} \
public/files \
var/cache \
var/log \
var/sessions
# Install Symfony runtime and framework dependencies
RUN composer config allow-plugins true
RUN composer require symfony/framework-bundle symfony/asset symfony/mime symfony/console \
symfony/dotenv symfony/flex symfony/runtime symfony/twig-bundle symfony/validator \
symfony/doctrine-messenger symfony/messenger twig symfony/mercure symfony/routing \
symfony/serializer php-http/discovery
EXPOSE 9000
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["php-fpm", "-F"]
+71
View File
@@ -0,0 +1,71 @@
FROM php:8.3-fpm-alpine AS base
# Install system dependencies (minimal for production)
RUN apk add --no-cache \
curl \
oniguruma-dev \
libpng-dev \
libjpeg-turbo-dev \
libxml2-dev \
zip \
unzip \
imagemagick \
ttf-freefont
# Install PHP extensions (production-safe with --enable-option-checking)
RUN docker-php-ext-configure gd --with-jpeg \
&& docker-php-ext-install \
pdo_pgsql \
gd \
intl \
mbstring \
xmlrpc \
opcache
# Enable OPcache for production
RUN echo "opcache.memory_consumption=256" > /usr/local/etc/php/conf.d/opcache.ini \
&& echo "opcache.max_accelerated_files=32531" >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo "opcache.validate_timestamps=0" >> /usr/local/etc/php/conf.d/opcache.ini \
&& echo "opcache.revalidate_freq=0" >> /usr/local/etc/php/conf.d/opcache.ini
# Install Composer (production stage)
FROM composer:latest AS composer
# ---------------
# Build stage install dependencies
# ---------------
FROM base AS build
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --optimize-autoloader --classmap-authoritative
COPY . .
RUN chmod +x bin/console \
&& APP_ENV=prod APP_DEBUG=0 COMPOSER_DEV_MODE=0 composer dump-autoload --classmap-authoritative --optimize \
&& php bin/console cache:clear --env=prod \
&& php bin/console assets:install public --env=prod
# ---------------
# Final production image
# ---------------
FROM base AS production
LABEL maintainer="devops@vollversammlung" \
description="Vollversammlung Symfony Production Image"
WORKDIR /app
COPY --from=composer /usr/bin/composer /usr/bin/composer
COPY --from=build /app .
# Create necessary directories with correct permissions
RUN chown -R www-data:www-data var/cache var/log var/sessions public/files \
&& find var/ -type d -exec chmod 750 {} \; \
&& find var/ -type f -exec chmod 640 {} \;
EXPOSE 9000
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["php-fpm", "-F"]
+86
View File
@@ -0,0 +1,86 @@
#!/bin/sh
set -e
echo "========================================"
echo " Vollversammlung - Symfony Startup"
echo "========================================"
# ----------------------------
# Wait for database availability
# ----------------------------
wait_for_db() {
echo "Waiting for PostgreSQL to be ready..."
max_tries=30
try=0
while ! nc -z db 5432 2>/dev/null; do
sleep 1
try=$((try + 1))
if [ "$try" -ge "$max_tries" ]; then
echo "ERROR: PostgreSQL did not become ready in time."
exit 1
fi
done
echo "PostgreSQL is ready."
}
wait_for_db
# ----------------------------
# Run Doctrine Migrations
# ----------------------------
run_migrations() {
if [ -f "bin/console" ]; then
echo "Running Doctrine Migrations..."
php bin/console doctrine:migrations:migrate --no-interaction --all-or-nothing || {
echo "WARNING: Migration failed, continuing startup."
}
fi
}
run_migrations
# ----------------------------
# Configure Mercure for logging
# ----------------------------
configure_mercure() {
if [ -n "${MERCURE_JWT_SECRET}" ]; then
export MERCURE_PUBLISHER_JWT_KEY="${MERCURE_JWT_SECRET}"
echo "Mercure Publisher JWT: configured"
export MERCURE_SUBSCRIBER_JWT_KEY="${MERCURE_JWT_SECRET}"
echo "Mercure Subscriber JWT: configured"
else
echo "WARNING: MERCURE_JWT_SECRET not set Mercure logging may not work."
fi
if [ -n "${MERCURE_URL}" ]; then
echo "Mercure URL: ${MERCURE_URL}"
fi
}
configure_mercure
# ----------------------------
# Clear cache for current environment
# ----------------------------
clear_cache() {
if [ -f "bin/console" ]; then
echo "Clearing Symfony cache (env=${APP_ENV:-dev})..."
php bin/console cache:clear --env="${APP_ENV:-dev}" --no-warmup || true
fi
}
clear_cache
# ----------------------------
# Start PHP-FPM or dev server
# ----------------------------
echo ""
echo "========================================"
if [ "${Symfony_USE_DEV_SERVER:-1}" = "1" ] && [ "${APP_ENV:-dev}" = "dev" ]; then
echo " Starting Symfony Dev Server on :8000"
exec php bin/console server:run 0.0.0.0:8000 --no-reload
else
echo " Starting PHP-FPM on port 9000"
exec php-fpm -F
fi
+147
View File
@@ -0,0 +1,147 @@
services:
postgres-db:
image: postgres:16-alpine
container_name: vv-postgres-db
environment:
POSTGRES_DB: vollversammlung
POSTGRES_USER: vv_admin
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- pg_data:/var/lib/postgresql/data
networks:
- vollversammlung_net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U vv_admin -d vollversammlung"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
secrets:
- db_password
symfony-fpm:
build:
context: ./api
dockerfile: Dockerfile.dev
container_name: vv-symfony-fpm
volumes:
- ./api/src:/app/src
- ./api/public:/app/public
- symfony_var:/app/var
environment:
DATABASE_URL: "postgresql://vv_admin:${DB_PASSWORD}@postgres-db:5432/vollversammlung?serverVersion=16&charset=utf8mb4"
MERCURE_PUBLISHER_JWT_KEY: ${MERCURE_PUBLISHER_JWT_KEY}
MERCURE_SUBSCRIBER_JWT_KEY: ${MERCURE_SUBSCRIBER_JWT_KEY}
MERCURE_URL: "http://mercure-hub/.well-known/mercure"
MERCURE_PUBLIC_URL: "${MERCURE_PUBLIC_URL:-http://localhost:3000/.well-known/mercure}"
APP_ENV: "${APP_ENV:-dev}"
APP_DEBUG: "${APP_DEBUG:-1}"
APP_SECRET: ${APP_SECRET}
OAUTH_PROVIDER_NAME: "${OAUTH_PROVIDER_NAME:-vonova}"
OAUTH_AUTHORIZATION_ENDPOINT: "${OAUTH_AUTHORIZATION_ENDPOINT:-https://oauth.vonova.de/oauth/authorize}"
OAUTH_TOKEN_ENDPOINT: "${OAUTH_TOKEN_ENDPOINT:-https://oauth.vonova.de/oauth/token}"
OAUTH_CLIENT_ID: "${OAUTH_CLIENT_ID}"
OAUTH_CLIENT_SECRET: "${OAUTH_CLIENT_SECRET}"
OAUTH_REDIRECT_URI: "${OAUTH_REDIRECT_URI:-http://localhost:8080/api/oauth/callback}"
OAUTH_SCOPES: "${OAUTH_SCOPES:-read profile mandant_id}"
MAILER_DSN: "${MAILER_DSN:-smtp://mailpit:1025}"
networks:
- vollversammlung_net
depends_on:
postgres-db:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:8000/api/health || exit 1"]
interval: 15s
timeout: 5s
retries: 3
start_period: 40s
nginx:
image: nginx:alpine
container_name: vv-nginx
ports:
- "8080:80"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
networks:
- vollversammlung_net
depends_on:
symfony-fpm:
condition: service_healthy
mercure-hub:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost:8080/api/health || exit 1"]
interval: 15s
timeout: 5s
retries: 3
mercure-hub:
image: dunglas/mercure
container_name: vv-mercure-hub
ports:
- "3000:80"
environment:
SERVER_NAME: ":80"
MERCURE_PUBLISHER_JWT_KEY: ${MERCURE_PUBLISHER_JWT_KEY}
MERCURE_SUBSCRIBER_JWT_KEY: ${MERCURE_SUBSCRIBER_JWT_KEY}
MERCURE_EXTRA_DIRECTIVES: |
$merge_headers {
"Access-Control-Allow-Origin" "*";
}
networks:
- vollversammlung_net
healthcheck:
test: ["CMD-SHELL", "curl -sf http://localhost/.well-known/mercure || exit 1"]
interval: 15s
timeout: 5s
retries: 3
mailpit:
image: axllent/mailpit
container_name: vv-mailpit
ports:
- "8025:8025"
networks:
- vollversammlung_net
healthcheck:
test: ["CMD-SHELL", "nc -z localhost 1025 || exit 1"]
interval: 15s
timeout: 5s
retries: 3
vite-dev:
build:
context: ./frontend
dockerfile: Dockerfile.dev
container_name: vv-vite-dev
volumes:
- ./frontend/src:/app/src
- ./frontend/public:/app/public
ports:
- "5173:5173"
environment:
VITE_API_URL: "http://nginx:80/api"
VITE_MERCURE_URL: "http://mercure-hub/.well-known/mercure"
VITE_API_URL: ${VITE_API_URL:-http://nginx:80/api}
VITE_MERCURE_URL: ${VITE_MERCURE_URL:-http://mercure-hub/.well-known/mercure}
VITE_OAUTH_AUTHORIZATION_ENDPOINT: "${VITE_OAUTH_AUTHORIZATION_ENDPOINT:-https://oauth.vonova.de/oauth/authorize}"
VITE_OAUTH_TOKEN_ENDPOINT: "${VITE_OAUTH_TOKEN_ENDPOINT:-https://oauth.vonova.de/oauth/token}"
networks:
- vollversammlung_net
depends_on:
nginx:
condition: service_healthy
networks:
vollversammlung_net:
driver: bridge
volumes:
pg_data:
symfony_var:
secrets:
db_password:
file: ./secrets/db_password.txt
+28
View File
@@ -0,0 +1,28 @@
# =============================================
# React Frontend Environment Konfiguration
# Kopiere diese Datei nach .env.local und passe die Werte an.
# =============================================
# -------------------
# API Backend URL
# -------------------
VITE_API_URL="http://localhost:8080/api"
# -------------------
# Mercure (SSE/Logging)
# -------------------
VITE_MERCURE_URL="http://localhost:3000/.well-known/mercure"
# -------------------
# OAuth Endpunkte (Vonova Provider)
# -------------------
VITE_OAUTH_AUTHORIZATION_ENDPOINT="https://oauth.vonova.de/oauth/authorize"
VITE_OAUTH_TOKEN_ENDPOINT="https://oauth.vonova.de/oauth/token"
VITE_OAUTH_CLIENT_ID=__CHANGE_ME_OAUTH_CLIENT_ID__
VITE_OAUTH_REDIRECT_URI="http://localhost:5173/oauth/callback"
# -------------------
# Manifest / App Metadata
# -------------------
VITE_APP_NAME="Vollversammlung"
VITE_APP_VERSION="0.1.0"
+67
View File
@@ -0,0 +1,67 @@
# ---------------
# Base: install deps for dev server
# ---------------
FROM node:20-alpine AS base-dev
ARG user=viteuser
ARG group=node
ARG uid=1000
ARG gid=1000
RUN apk add --no-cache \
tini \
bash
RUN addgroup -g ${gid} ${group} && \
adduser -u ${uid} -G ${group} -s /bin/sh -D ${user}
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci || npm install
# ---------------
# Production build stage
# ---------------
FROM node:20-alpine AS build
WORKDIR /app
ARG VITE_API_URL="http://nginx:80/api"
ARG VITE_MERCURE_URL="http://mercure-hub/.well-known/mercure"
ARG VITE_OAUTH_AUTHORIZATION_ENDPOINT="https://oauth.vonova.de/oauth/authorize"
ARG VITE_OAUTH_TOKEN_ENDPOINT="https://oauth.vonova.de/oauth/token"
ENV VITE_API_URL=${VITE_API_URL}
ENV VITE_MERCURE_URL=${VITE_MERCURE_URL}
ENV VITE_OAUTH_AUTHORIZATION_ENDPOINT=${VITE_OAUTH_AUTHORIZATION_ENDPOINT}
ENV VITE_OAUTH_TOKEN_ENDPOINT=${VITE_OAUTH_TOKEN_ENDPOINT}
COPY --from=base-dev /app/node_modules ./node_modules
COPY . .
RUN npm run build --if-present
# ---------------
# Development stage
# ---------------
FROM base-dev AS dev
LABEL maintainer="devops@vollversammlung" \
description="Vollversammlung React Vite Dev Image"
EXPOSE 5173
CMD ["tini", "--", "npx", "vite", "--host", "0.0.0.0"]
# ---------------
# Production static serve (nginx will be added later)
# ---------------
FROM nginx:alpine AS production-static
COPY --from=build /app/dist /usr/share/nginx/html
ARG VITE_API_URL="http://nginx:80/api"
RUN printf "server {\n listen 80;\n root /usr/share/nginx/html;\n try_files \$uri \$uri/ /index.html;\n}" > /etc/nginx/conf.d/default.conf
EXPOSE 80
+69
View File
@@ -0,0 +1,69 @@
server {
listen 80;
server_name localhost;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# CORS for API endpoints
location /api/ {
# Forward tenant ID from client
proxy_set_header X-Mandant-ID $http_x_mandant_id;
# Proxy headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
# Disable buffering for streaming responses (Mercure)
proxy_buffering off;
# Pass to Symfony FPM
location ~ ^/api/(.+)\.php(/|$) {
fastcgi_pass symfony-fpm:9000;
fastcgi_split_path_info ^(.+.php)(/.*)$;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /app/$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT /app;
}
# API catch-all - rewrite to index.php for Symfony routing
try_files $uri /api/index.php$is_args$args;
}
# Mercure Hub
location /.well-known/mercure {
proxy_pass https://mercure-hub/.well-known/mercure;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Mercure needs WebSocket support
upgrade_http 2;
}
# Static assets (frontend build output)
location /assets/ {
alias /app/public/assets/;
expires 7d;
add_header Cache-Control "public, immutable";
}
# Health check endpoint
location /api/health {
access_log off;
proxy_pass http://symfony-fpm:8000/api/health;
proxy_set_header Host localhost:8000;
}
# Frontend SPA fallback (static files served from public/)
location / {
root /app/public;
try_files $uri $uri/ /index.html;
}
}