# 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= OAUTH_CLIENT_SECRET= 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= DATABASE_URL="postgresql://vv_admin:@postgres-db:5432/vollversammlung?serverVersion=16&charset=utf8mb4" MERCURE_PUBLISHER_JWT_KEY= MERCURE_SUBSCRIBER_JWT_KEY= 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= OAUTH_CLIENT_SECRET= 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*