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
+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*