Files
vollversammlung/Doku/infrastruktur_planung.md
T

340 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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*