Initial commit: Docker infrastructure for fullstack Vollversammlung app
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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`
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 |
|
||||
@@ -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`).
|
||||
@@ -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*
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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"]
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user