# BergVOLT Materialschein Materialschein & Kabelrechner für BergVOLT Elektrotechnik. React + Vite + TypeScript (PWA) im Client, Express + TypeScript + Prisma im Server, Postgres als Datenbank. Anmeldung über Microsoft Entra ID (gleiche App-Registrierung wie das Zeiterfassungs-Tool). ## Lokale Entwicklung ```bash # 1. Postgres lokal starten docker compose -f docker-compose.dev.yml up -d # 2. Server cd server npm install echo 'DATABASE_URL="postgresql://materialschein:dev@localhost:5433/materialschein"' > .env echo 'MSAL_CLIENT_ID="b1cd974c-77ce-4d93-b6e8-00e41a31adc8"' >> .env echo 'MSAL_TENANT_ID="d9adeaaf-853b-4c4e-a822-8f1bedbb84f6"' >> .env echo 'PORT=8787' >> .env npx prisma migrate deploy npm run dev # läuft auf :8787 # 3. Client (neues Terminal) cd client npm install npm run dev # läuft auf :5173, proxied /api auf :8787 ``` ## Architektur - `client/` – React 18 + Vite + TypeScript, `vite-plugin-pwa` für Manifest/Service Worker. MSAL.js (`@azure/msal-browser` + `@azure/msal-react`) für den Microsoft-Login. - `server/` – Express + TypeScript, validiert das MSAL-ID-Token serverseitig (JWKS von `login.microsoftonline.com`) und liefert zusätzlich die gebaute Client-App aus (`public/` = `client/dist`, SPA-Fallback für React-Router). - `server/prisma/schema.prisma` – zwei Tabellen: `projekte` (Baustellen) und `material` (Positionen je Projekt, per `projektId` verknüpft). - Ein Docker-Image (`Dockerfile`, Multi-Stage) enthält Client-Build + Server, Port 80. ## Deploy (git.prox.ws → Portainer) Folgt [deploy-standard](https://git.prox.ws/PROX/deploy-standard) 1:1 – `gitops/stack.yml` und `.gitea/workflows/build.yml` sind bereits fertig im Repo. Zum Scharfschalten: ### 1. Azure AD – zweite Redirect-URI eintragen Bestehende App-Registrierung "BergVolt Zeiterfassung" im Azure Portal öffnen → Authentifizierung → bei "Single-Page-Anwendung" ergänzen: `https://materialschein.bergvolt.at` (oder die tatsächlich vorgesehene Domain/Pfad). **Redirect-URI in `client/src/lib/authConfig.ts` steht auf `window.location.origin` – d.h. die exakte Domain, unter der die App später erreichbar ist, muss hier eingetragen werden.** ### 2. Gitea – Repo-Secrets setzen Repo → Settings → Actions → Secrets: | Secret | Wert | |---|---| | `GITEAUSER` | Gitea-Username des Package-Namespace-Owners (siehe deploy-standard: Package-Push geht nur als Namespace-Owner) | | `GITEAPASSWORD` | PAT dieses Users, Scope **nur `write:package`** (Settings → Applications → Generate New Token) | | `PORTAINER_WEBHOOK_URL` | kommt aus Schritt 4 | Repo → Settings → Actions: Default-Permission auf **Read and Write**, damit der Commit-back (`gitops/stack.yml` auf `:` pinnen) funktioniert. ### 3. Erster Push ```bash git push origin main ``` `build` wird grün, `deploy` schlägt fehl (Webhook-Secret fehlt noch) – erwartet. ### 4. Portainer – Stack anlegen Auf `https://10.1.10.111:9443` (Zugang laut deploy-standard bei "dominic"): - Stacks → Add stack → **Repository** - Repository URL: `https://git.prox.ws/BERGVOLT/Materialschein` - Reference: `refs/heads/main` - Compose path: `gitops/stack.yml` - Authentication: `portainer-deploy` + dessen PAT (Org-Repos sind über das `deploy-read`-Team bereits abgedeckt, nichts zusätzlich einzutragen) - Environment-Variablen setzen: - `POSTGRES_PASSWORD` = ein zufälliges, sicheres Passwort - `JWT_SECRET` = ein zufälliger, langer String (signiert die lokalen Login-Tokens – niemals ins Repo, nur hier als Stack-Env). Beispiel zum Generieren: `openssl rand -base64 48` - `SEED_ADMIN_USERNAME` / `SEED_ADMIN_PASSWORD` = legt beim ersten Start automatisch ein lokales Fallback-Konto an (siehe "Lokales Konto" unten) - **Webhook aktivieren**, **ForceUpdate an**, `ForcePullImage` bleibt aus - Die angezeigte Webhook-URL (`.../api/stacks/webhooks/`) als Gitea-Secret `PORTAINER_WEBHOOK_URL` eintragen (Schritt 2). - nginx-proxy-manager: Proxy Host auf Container `materialschein`, Port `80`. ### 5. Verifizieren - Webhook von Hand feuern (`curl -sk -X POST `) → `204`, Container laufen (`db` + `web`). - Push oder „Run workflow" auslösen → beide Jobs grün, `gitops/stack.yml` zeigt danach `image: …:1.0.` (kein `:latest` mehr), Commit `deploy: v1.0. [skip ci]` im Log. - Cleanup-Rule für den Package-Owner einmalig setzen (Org/User → Settings → Packages → Cleanup Rules): keep most recent 10 + keep matching `^latest$`. ## Lokales Konto (Fallback ohne Microsoft-Login) Auf dem Login-Screen gibt es unter dem Microsoft-Button den Link "Alternativ mit Benutzername anmelden". Damit das funktioniert, muss im Portainer-Stack `JWT_SECRET` gesetzt sein (Schritt 4). Ein erstes Konto legt der Server **automatisch beim Start** an, wenn `SEED_ADMIN_USERNAME` und `SEED_ADMIN_PASSWORD` als Stack-Env gesetzt sind (Upsert – läuft bei jedem Neustart mit und synchronisiert das Passwort neu, solange die Variable gesetzt ist – ein falsch übernommenes Passwort lässt sich also einfach per Neustart korrigieren). Das Klartext-Passwort steht dabei nur in der Portainer-Stack-Konfiguration, nie im Git-Repo – der Server speichert ausschließlich einen bcrypt-Hash in der Datenbank. **Vorsicht bei `$` im Passwort:** Docker-Compose/Portainer interpretieren `$` in Env-Werten als Beginn einer Variablen-Referenz (`$xyz` wird als `${xyz}` gelesen und bei fehlender Definition oft still zu leer aufgelöst) – ein Passwort mit `$` kann dadurch unbemerkt anders ankommen, als eingetippt. Entweder jedes `$` im Passwortwert als `$$` escapen, oder – einfacher – ein Passwort ohne `$` verwenden. Optional danach `SEED_ADMIN_PASSWORD` aus dem Stack wieder entfernen (Härtung), sobald das Konto wie gewünscht funktioniert – `SEED_ADMIN_USERNAME` kann stehen bleiben, ohne Passwort-Variable wird nichts mehr verändert. Weitere lokale Konten aktuell nur direkt in der Datenbank anlegbar (`INSERT INTO users …` mit einem bcrypt-Hash) – ein Verwaltungs-UI dafür gibt es noch nicht. ### Danach: zum Homescreen hinzufügen Seite im Handy-Browser öffnen, anmelden, "Zum Home-Bildschirm hinzufügen" (iOS Safari) bzw. "App installieren" (Android Chrome) – die App ist als PWA installierbar (`vite-plugin-pwa` generiert Manifest + Service Worker automatisch beim Build).