13 KiB
13 KiB
Infrastruktur-Planung — Vollversammlung
Inhaltsverzeichnis
- docker-compose.yaml – Services
- Netzwerk & Volumes
- api/ Verzeichnis – Vorbereitungen
- frontend/ Verzeichnis – Vorbereitungen
- Multi-Tenant Scoping – Infrastruktur-Ansatz
- OAuth (Vonova) – Infrastruktur
- Lokaler Start
- Health Checks
- .env.example Konfigurationen
- Checkliste – Nächste Schritte
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
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ätgd– Bildverarbeitungintl– Internationalisierungmbregex– 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)
- Frontend leitet User zu Vonova Authorization Endpoint weiter.
- Vonova authentifiziert User, gibt Authorisierungscode an
OAUTH_REDIRECT_URIzurück. - Backend tauscht Code gegen Access-Token am Token Endpoint ein.
- Backend extrahiert MandantID und Profil-Informationen aus dem Token-Payload.
- Backend generiert Session-Cookie (HttpOnly, Secure, SameSite=lax).
- Folge-Requests enthalten Cookie automatisch → authentifizierte Api-Zugriffe.
Wichtig: Tokens werden NIEMALS auf dem Client gespeichert (kein localStorage/sessionStorage).
7. Lokaler Start
Setup
# 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
# 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
# =========================================
# 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
# =========================================
# 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.yamlmit allen 6 Services schreiben- Networks & Volumes definieren
- Health-Checks pro Service implementieren
- nginx Konfiguration für Proxy-Routing erstellen
.env.examplein api/ und frontend/ anlegen- Dockerfiles (dev + prod) pro Service erstellen
Backend (Backend-Coder)
- Symfony-Projektstruktur in
api/aufsetzen composer.jsonmit Symfony Flex konfigurieren- Entity-Base für Multi-Tenant Scoping erstellen (
mandant_idcolumn) - 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/healthEndpoint 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