# INSIGHT MVP - Aenderungsprotokoll ## Stand: 2026-03-13 ### Aktueller Sprint: CRM Phase 3 — Kanban-Board + Microsoft 365 OAuth-Integration (Feature-Branch: feature/crm-service) --- ### Aenderungen 2026-03-13 (6): Dashboard Home-Tab — Analoguhr, 3-Tage-Wetter, Spruch, kompakte Widgets #### Frontend - `components/AnalogClock.tsx` + `AnalogClock.module.css` — Neue SVG-Analoguhr mit Stunden-/Minuten-/Sekundenzeiger (Gegengewicht), digitaler Zeit- und Datumsanzeige; aktualisiert jede Sekunde via setInterval - `hooks/useWeather.ts` — Open-Meteo API um 3-Tage-Prognose erweitert (`&daily=weather_code,temperature_2m_max,temperature_2m_min&forecast_days=3`); `WeatherData` um `forecast: ForecastDay[]` ergaenzt; neues Interface `ForecastDay` - `shell/DashboardPage.tsx` — Home-Tab vollstaendig ueberarbeitet: - Spruch des Tages (35 dt. Zitate, deterministisch nach Tagesdatum) rechtsbuendig im Header neben dem Willkommensnamen - 3-Spalten-Layout: Linke Spalte (240px): Analoguhr + WeatherWidget (aus Header verschoben) + 3-Tage-Prognose; Mittlere Spalte (flex:1): Kompakte Aufgaben + Kompakte E-Mails; Rechte Sidebar (300px, unveraendert): Messe-Ticker + Tagesagenda - `HomeTasksWidget`: Top-8 offene Aufgaben (CRM + O365, gleiche Zusammenfuehrungs-Logik wie DashboardTasksTab), direkt erledigbar, „Alle →" schaltet auf Aufgaben-Tab - `HomeEmailsWidget`: Posteingang-E-Mails der letzten 3 Tage (client-seitiger Filter), direkter Outlook-Link, „Alle →" schaltet auf E-Mail-Tab - `role`-Parameter aus HomeTab entfernt (nicht mehr angezeigt) - `shell/DashboardPage.module.css` — Neue CSS-Klassen: `.quoteOfDay`, `.homeLeft`, `.forecastStrip`, `.forecastDay*`, `.homeWidgetCard`, `.homeTaskRow*`, `.homeEmailRow*` u.a. (ca. 280 neue Zeilen) --- ### Aenderungen 2026-03-13 (5): Erweiterte Profilfelder (analog O365) + Profilbild-Sync aus Microsoft 365 #### Backend: Core-Service - `prisma/core.schema.prisma` — 4 neue optionale Felder im User-Modell: `jobTitle`, `department`, `companyName`, `officeLocation` (mit `@map` auf snake_case, `@db.VarChar`) - `prisma/migrations/20260313_user_profile_extra_fields/migration.sql` — ALTER TABLE fuer die 4 neuen Spalten - `core/users/dto/update-user.dto.ts` — Neue Felder mit `@IsOptional`, `@ValidateIf`, `@IsString`, `@MaxLength` - `core/users/users.service.ts` — `findById()`, `update()`, `updateProfile()` um neue Felder erweitert #### Backend: CRM-Service - `graph/graph.service.ts` — `M365UserProfile` um `department`, `companyName`, `officeLocation` erweitert; `getM365Profile()` `$select` erweitert; neue Methode `getM365Photo()`: Graph `/me/photos/96x96/$value` als ArrayBuffer → Base64 JPEG Data-URL; 404/400 → null - `graph/office365.controller.ts` — Neuer Endpoint `GET /crm/office365/photo` #### Frontend - `auth/AuthContext.tsx` — `User`-Interface: `jobTitle?`, `department?`, `companyName?`, `officeLocation?` - `crm/types.ts` — `M365UserProfile` um `department`, `companyName`, `officeLocation` erweitert - `crm/api.ts` — `office365Api.getM365Photo()` hinzugefuegt - `profile/ProfilePage.tsx` — 4 neue State-Variablen, Organisations-Fieldset (2×2 Grid), `handleEnrichFromO365` ueberschreibt Profilfoto immer; Auto-Sync-Hook befuellt alle neuen Felder - `hooks/useO365ProfileSync.ts` — Fetcht Profil + Foto parallel; Profilfoto nur gesetzt wenn `!user.avatar` --- ### Aenderungen 2026-03-13 (4): O365-Profil-Auto-Sync beim Login (useO365ProfileSync) #### Frontend - `hooks/useO365ProfileSync.ts` — Neuer Hook: synchronisiert INSIGHT-Profil einmalig pro Browser-Session aus Microsoft 365; sessionStorage-Flag (`o365_profile_synced`) verhindert Mehrfach-Sync; stille Ausfuehrmg (kein UI-Feedback); bei Fehler wird Flag entfernt fuer naechsten Retry; `PATCH /users/me` mit Kontaktfeldern (firstName, lastName, phone, mobile, city, street, postalCode) - `shell/AppLayout.tsx` — `useO365ProfileSync()` direkt in der AppLayout-Komponente aufgerufen --- ### Aenderungen 2026-03-13 (3): Dashboard Kalender-Tab — Monats-/Wochenansicht + Tages-Agenda #### Backend: CRM-Service - `graph/graph.service.ts` — Neue Methode `getCalendarEventsForRange(userJwt, userId, startDate, endDate)`: laedt Kalender-Termine fuer beliebigen Zeitraum via `/me/calendarView`, Redis-Cache 5 Min; `wellKnownName` aus `getMailFolders()` $select entfernt (400-Fehler auf Exchange-Tenants die das OData-Property nicht unterstuetzen) - `graph/office365.controller.ts` — Neuer Endpoint `GET /crm/office365/calendar/range?startDate=&endDate=` (vor `@Get('calendar')` definiert, um Routing-Konflikt zu vermeiden) #### Frontend - `crm/types.ts` — `M365MailFolder.wellKnownName` als `optional` markiert (nicht alle Exchange-Tenants liefern das Feld) - `crm/api.ts` — `office365Api.getCalendarRange(startDate, endDate)` - `crm/hooks.ts` — Neuer Hook `useOffice365CalendarRange(startDate, endDate)` - `shell/DashboardCalendarTab.tsx` — Neue Hauptkomponente mit: Toolbar (Vor/Zurueck/Heute + Monat/Woche Toggle), MonthView (6×7 CSS-Grid, Heute-Kreis, Event-Chips max. 2 + "+N"), WeekView (7-Spalten-Grid, Events mit farbigem linken Rand, Klick oeffnet Outlook), DayAgenda (rechts 1/3 — Uhrzeit/Betreff/Ort/Teilnehmer, Online-Badge, Outlook-Link); deterministisches Event-Coloring per ID-Hash - `shell/DashboardCalendarTab.module.css` — Vollstaendiges Styling - `shell/DashboardPage.tsx` — Kalender-Tab ersetzt ComingSoonTab durch DashboardCalendarTab #### Fix: Ordner-Sidebar 400-Fehler - `DashboardEmailTab.tsx` — Ordner-Sortierung auf Display-Name-Basis umgestellt (Posteingang/Inbox, Gesendete Elemente/Sent Items, etc.) da `wellKnownName` nicht verfuegbar; `isInboxFolder()` erkennt Posteingang per Anzeigename --- ### Aenderungen 2026-03-13 (2): Dashboard E-Mail Tab — Outlook-Postfach mit Ordner-Navigation + Aktivitaeten-Speicherung #### Backend: CRM-Service - `contacts/contacts.service.ts` — Neue Methode `findByEmail(tenantId, email)`: sucht Kontakt anhand E-Mail-Adresse (legacy + multi-value emails) - `contacts/contacts.controller.ts` — Neuer Endpoint `GET /crm/contacts/lookup?email=xxx` (vor `:id`-Route, damit kein UUID-Konflikt) - `graph/graph.service.ts` — Neue Methoden: `getMailFolders()` (alle Outlook-Ordner inkl. unreadItemCount), `getMailsByFolder(folderId, days)` (E-Mails mit optionalem Tages-Filter via `$filter receivedDateTime ge`) - `graph/office365.controller.ts` — Neue Endpoints: `GET /crm/office365/folders`, `GET /crm/office365/folders/:folderId/messages?days=7` #### Frontend - `crm/types.ts` — Neue Interfaces: `M365MailFolder` (id, displayName, totalItemCount, unreadItemCount, childFolderCount, wellKnownName), `CrmContactLookup` (id, firstName, lastName, email, companyName) - `crm/api.ts` — `office365Api.getMailFolders()`, `office365Api.getMailsInFolder(folderId, days)`, `contactsApi.lookupByEmail(email)` - `crm/hooks.ts` — Neue Hooks: `useOffice365MailFolders()`, `useOffice365MailsInFolder(folderId, days)`, `useContactByEmail(email)` (mit retry: false fuer 404-Faelle) - `shell/DashboardEmailTab.tsx` — Neue Hauptkomponente mit: Ordner-Sidebar (sortiert nach Prioritaet: Posteingang, Gesendet, Entwuerfe, ...), Zeitfilter-Leiste (1/7/14 Tage/alle), E-Mail-Liste mit Outlook-Link, CRM-Badge fuer bekannte Absender, Aktivitaeten-Modal mit Kommentarfeld - `shell/DashboardEmailTab.module.css` — Vollstaendiges Styling - `shell/DashboardPage.tsx` — E-Mail-Tab ersetzt ComingSoonTab durch DashboardEmailTab --- ### Aenderungen 2026-03-13: Office365-Seite + Graph API Bugfixes #### Backend: CRM-Service — GraphModule erweitert - `graph/graph.service.ts` — Fix: `$search` + `$orderby` koennen nicht kombiniert werden (Graph API Limitation) → `$orderby` aus Kontakt-E-Mail-Suche entfernt; neue globale Methoden: `getAllEmails`, `getAllCalendarEvents`, `getAllOutlookContacts`, `getTasks` (war schon vorhanden); `attendees` zu Kalender-Abfragen ergaenzt; Fehler werden jetzt geloggt - `graph/office365.controller.ts` — Neuer Controller mit globalen Office365-Endpoints: `GET /crm/office365/emails`, `GET /crm/office365/calendar`, `GET /crm/office365/contacts`, `GET /crm/office365/tasks` - `graph/graph.module.ts` — `Office365Controller` registriert #### Frontend: Office365-Uebersichtsseite - `crm/office365/Office365Page.tsx` — Neue Seite mit 4 Tabs: E-Mails, Kalender, Outlook-Kontakte, Aufgaben; zeigt alle M365-Daten des eingeloggten Users; Suchfilter fuer Kontakte; "CRM"-Button zum Navigieren in CRM-Kontakte - `crm/office365/Office365Page.module.css` — Vollstaendiges Styling (Cards, Tabs, Grid, Badges) - `crm/types.ts` — `M365Email.hasAttachments` und `M365CalendarEvent.attendees` ergaenzt; neues Interface `M365Contact` - `crm/api.ts` — `office365Api` (getEmails, getCalendar, getContacts, getTasks) - `crm/hooks.ts` — `useOffice365Emails`, `useOffice365Calendar`, `useOffice365Contacts`, `useOffice365Tasks` - `shell/App.tsx` — Route `/crm/office365` hinzugefuegt - `shell/AppLayout.tsx` — NavLink "Office 365" (Grid-Icon) nach Kanban ergaenzt #### Datenbankfix - `user_integrations`-Tabelle: Tenant-Membership fuer `t.reitz@xinion.de` in "Xinion GmbH" manuell angelegt (fehlende Zuordnung verursachte 403 auf allen CRM-Endpoints) --- ### Aenderungen 2026-03-12: Microsoft 365 OAuth-Integration — Frontend #### Frontend: MS365 Integration-Tab + Kontakt-Tabs - `crm/types.ts` — Neue Interfaces: `UserIntegration`, `M365Email`, `M365EmailAddress`, `M365CalendarEvent`, `M365Task`, `M365TaskList` - `crm/api.ts` — `integrationsApi` (list, disconnectM365, getM365ConnectUrl) + `graphApi` (getContactEmails, getContactCalendar, getContactTasks) - `crm/hooks.ts` — `useIntegrations`, `useDisconnectM365`, `useContactEmails`, `useContactCalendar`, `useContactTasks` - `profile/ProfilePage.tsx`: - Neuer Tab "Integrationen" (Typ: `ProfileTab = 'personal' | 'expert' | 'password' | 'integrations'`) - Oeffnet automatisch wenn `?integration=microsoft-365` URL-Param gesetzt ist - Zeigt Erfolgs-/Fehlermeldung aus `?status=success|error` Param - "Microsoft 365 verbinden" Button (Link zu `/api/v1/auth/integrations/microsoft-365`) - Verbunden-Ansicht: Tenant-ID, Token-Ablauf, "Verbindung trennen" Button - `crm/contacts/EmailsTab.tsx` — E-Mail-Liste aus MS Graph, ungelesen fett + blauer Rand, Link zu Outlook Web - `crm/contacts/CalendarTab.tsx` — Kalendertermine (naechste 90 Tage), Online-Meeting-Badge, Link zu Outlook Web - `crm/contacts/TasksTab.tsx` — Microsoft To Do Aufgaben, gruppiert nach Listen, Status-/Prioritaets-Badges - `crm/contacts/ContactDetailPage.tsx`: - Neuer "Microsoft 365" Abschnitt (Card) am Seitenende (nur wenn Kontakt E-Mail hat) - Drei Tabs: E-Mails / Kalender / Aufgaben - Ohne MS365-Verbindung: "Verbinden"-Button direkt im Tab - TypeScript `npx tsc --noEmit`: 0 Fehler --- ### Aenderungen 2026-03-12: Microsoft 365 OAuth-Integration — CRM-Service (GraphModule) - `crm-service/src/graph/graph.service.ts` — Token von Core-Service holen (JWT-Forwarding), Graph-Calls (Emails/Kalender/Tasks), Redis-Cache 5 Min - `crm-service/src/graph/graph.controller.ts` — GET /crm/contacts/:id/emails|calendar|tasks - `crm-service/src/graph/graph.module.ts` — Modul-Definition - `crm-service/src/app.module.ts` — GraphModule registriert - `crm-service/src/config/env.validation.ts` — `CORE_SERVICE_URL` ergaenzt - `docker-compose.crm.yml` — `CORE_SERVICE_URL=http://core:3000` hinzugefuegt --- ### Aenderungen 2026-03-12: Microsoft 365 OAuth-Integration — Core-Service - `core-service/prisma/core.schema.prisma` — `UserIntegration` Modell + Relation auf `User` - `core-service/prisma/migrations/20260312_user_integrations/migration.sql` — Migration fuer `user_integrations` Tabelle - `core-service/src/config/env.validation.ts` — `AZURE_INTEGRATION_REDIRECT_URI`, `INTEGRATION_ENCRYPTION_KEY` - `core-service/src/core/auth/sso/entra-id.service.ts` — `getIntegrationAuthUrl`, `handleIntegrationCallback`, `refreshIntegrationToken` - `core-service/src/core/integrations/integrations.service.ts` — AES-256-GCM Token-Verschluesselung, Token-CRUD, Auto-Refresh - `core-service/src/core/integrations/integrations.controller.ts` — OAuth-Flow + Token-Endpoints - `core-service/src/core/integrations/integrations.module.ts` - `core-service/src/app.module.ts` — IntegrationsModule registriert --- ### Aenderungen 2026-03-12: Kanban-Board (Frontend) - `frontend/src/crm/deals/KanbanPage.tsx` — Drag-&-Drop Kanban-Board (@dnd-kit) - Pipeline-Selektor + Toggle "Abgeschlossene anzeigen" - DealCard (useDraggable), KanbanColumn (useDroppable), DragOverlay - Optimistisches Update via `localStageMap`; Rollback bei Fehler - `frontend/src/crm/deals/KanbanPage.module.css` — Styles fuer Board, Spalten, Cards - `frontend/src/shell/App.tsx` — Route `/crm/kanban` - `frontend/src/shell/AppLayout.tsx` — NavLink "Kanban" im CRM-Bereich - `frontend/package.json` — `@dnd-kit/core`, `@dnd-kit/sortable`, `@dnd-kit/utilities` --- ### Aktueller Sprint: CRM Phase 2 / Vertraege-Modul (Feature-Branch: feature/crm-service) --- ### Aenderungen 2026-03-12: Vertraege-Modul Frontend (ContractsCard) #### Neue/geaenderte Dateien - `crm/companies/ContractsCard.tsx` — Vollstaendige Vertraege-Card fuer CompanyDetailPage - Tabelle mit Titel, Status-Badge, Laufzeit, Vertragswert - "+ Neu" Button oeffnet Create-Modal - Stift-Icon (Bearbeiten) + X-Icon (Loeschen) pro Zeile - Status-Farben: DRAFT=grau, ACTIVE=gruen, EXPIRED=orange, CANCELLED=rot - Wert-Formatierung (Intl.NumberFormat de-DE), Datumsformatierung - `crm/types.ts` — `CreateContractPayload`, `UpdateContractPayload`, `ContractsQueryParams` ergaenzt - `crm/api.ts` — `contractsApi` (list, create, update, delete) mit `/crm/companies/:id/contracts` - `crm/hooks.ts` — `crmKeys.contracts` + `useContracts`, `useCreateContract`, `useUpdateContract`, `useDeleteContract` - `crm/companies/CompanyDetailPage.tsx` — `contractCount` Prop entfernt (Card laedt selbst) --- ### Aenderungen 2026-03-12: CRM Phase 2 — Forecast, Import, Enrichment (Frontend komplett) #### Phase 2.3: Forecasting - `crm/forecast/ForecastPage.tsx` — Prognose-Dashboard unter `/crm/forecast` - Pipeline-Filter (Dropdown) + Zeitraum-Toggle (Monat/Quartal/Jahr) - Summary-Cards: Offene Vorgaenge, Gesamtwert, Gewichteter Wert, Zeitraum - Tabelle: Stage | Wahrscheinlichkeit | Vorgaenge | Gesamtwert | Gewichteter Wert - `crm/pipelines/PipelinesPage.tsx` — Probability-Feld (0–100%) pro Pipeline-Stage #### Phase 2.2: CSV/Excel Import - `crm/import/ImportPage.tsx` — 3-Schritt-Wizard in CRM-Einstellungen → Tab "Import" - Schritt 1: Typ-Auswahl (Kontakte/Unternehmen/Vorgaenge) + Datei-Upload (CSV, XLSX, max 10MB) - Schritt 2: Spalten-Mapping (Auto-Match + manuell), Duplikat-Strategie, Kopfzeilen-Option - Schritt 3: Ergebnis (created/updated/skipped/errors + Fehlerdetails) - API-Konvertierungen: `PERSON→contact`, mapping `Record→Array [{sourceColumn,targetField}]` #### Phase 2.4: Datenanreicherung - `crm/companies/CompanyDetailPage.tsx` — "Anreichern"-Button im Header - POST /companies/:id/enrich → Suggestions-Modal - Tabelle: Feld | Aktuell | Vorschlag | Quelle + "Uebernehmen"-Button (PATCH) - suggestions-Konvertierung: Backend-Record → UI-Array - `crm/settings/CrmSettingsPage.tsx` — North Data API-Key in Tab "Integrationen" #### Kontakt-Entity UI/UX Redesign (aus dieser Sprint-Session) - `components/Drawer.tsx` + `Drawer.module.css` — Wiederverwendbarer Right-Side-Drawer - `crm/contacts/ContactFormModal.tsx` — Modal → Drawer, Zwei-Tab-Struktur (Allgemein/Details) - `crm/contacts/ContactDetailPage.tsx`: - Header: Name (Unternehmen) in Klammern, Aktiv/Inaktiv-Badge (Pill mit farbigem Dot) - Kontaktdaten-Card: immer alle Allgemein-Felder (leere Felder = "—"), 2 Sub-Spalten - Lexware-Section entfernt (Kontakte werden nicht separat in Lexware gepflegt) #### TypeScript - `npx tsc --noEmit` in packages/frontend: 0 Fehler (alle Commits) --- ### Aktueller Sprint: Sprint 1 (Alpha) --- ### Aenderungen 2026-03-12: CRM UI/UX Redesign – Kontakt-Entitaet #### Drawer-Komponente (neu) - `packages/frontend/src/components/Drawer.tsx` – Wiederverwendbares Right-Side-Drawer-Pattern mit Portal, ESC-Key, Backdrop-Click, optionalem Footer - `packages/frontend/src/components/Drawer.module.css` – Animiertes Slide-In von rechts, sticky Header + Footer, scrollbarer Body #### ContactFormModal – Redesign (Modal → Drawer + Tabs) - Gewechselt von `` (zentriert) zu `` (von rechts, 540px) - Zwei-Tab-Struktur: - Tab "Allgemein": Vorname, Nachname, Unternehmen (Autocomplete), Position, Abteilung, E-Mail, Telefon, Mobil, LinkedIn - Tab "Details & Adresse": Geburtsdatum, Quelle, Status, Website, Adresse, Notizen, Tags, Custom Fields - "Adresse vom Unternehmen uebernehmen" Checkbox: wird angezeigt wenn Unternehmen verlinkt ist; bei aktivierter Checkbox werden Adressfelder ausgeblendet und die Adresse wird aus dem verlinkten Unternehmen uebernommen - "Typ"-Feld entfernt (Kontakte sind immer Personen; ORGANIZATION-Typ bleibt intern erhalten) - Sticky Footer (Abbrechen / Anlegen|Speichern) via HTML5 `form` Attribut - Adress-Duplikate bereinigt #### ContactDetailPage – Redesign - **Header**: Subtitle "Position @ Unternehmen" unter dem Namen (z.B. "Geschaeftsfuehrerin @ team neusta SE"), Status-Dot inline - **Kontaktdaten-Card**: Zwei Sub-Spalten (Kommunikation links, Kontext rechts), Adresse als volle Breite darunter - Entfernt: Vorname/Nachname (im Header), Firma-Duplikat (nur noch "Unternehmen" als Link) - Hinzugefuegt: Mobil als `tel:`-Link, LinkedIn mit Icon, Geburtsdatum, Quelle, Abteilung - **Neue Card "Notizen & Tags"**: Tags als Badges, Notizen-Text, Custom Fields - **Layout**: Rechte Spalte (Aktivitaeten) auf `minmax(380px, 40%)` verbreitert (min. 1/3 fuer kuenftigen O365-Feed) - **CSS**: `.infoColumns` (2-spaltig), `.addressRow`, `.subtitle`, `.nameRow` neu #### TypeScript - `npx tsc --noEmit` in packages/frontend: 0 Fehler --- ### Aenderungen in dieser Session #### 1. Projektinitialisierung & Infrastruktur-Definition **Was wurde gemacht:** 1. **SSH Keys erstellt** - Deploy-Key (`.keys/deploy_ed25519`) fuer Server-Zugriff - CI/CD-Key (`.keys/cicd_ed25519`) fuer Forgejo Actions Pipeline 2. **Infrastruktur-Definition erstellt** (`docs/INFRASTRUCTURE.md`) - ProxmoxVE VM-Spezifikation: Ubuntu 24.04 LTS, 4 vCPU, 8 GB RAM, 60 GB SSD - Docker-Netzwerk-Architektur mit 3 isolierten Netzwerken - Komplette Service-Landschaft definiert - Schritt-fuer-Schritt VM-Setup Anleitung 3. **Zugangsdaten-Dokument erstellt** (`docs/ACCESS.md`) - Server-IP: 172.20.10.59 (insight-dev-01) - Git-Server: 172.20.10.11 (GAIA-GIT) - Alle SSH-Keys, Ports, Befehle dokumentiert 4. **Projektstruktur aufgesetzt** (packages/core-service, packages/frontend, config, .forgejo) 5. **Basis-Konfigurationsdateien** (.gitignore, .env.example, README.md) #### 2. Forgejo Git-Server Konfiguration **Was wurde auf dem Git-Server (172.20.10.11) gemacht:** 1. **Docker Engine 29.3 installiert** (fuer Forgejo Actions Runner) 2. **Forgejo Actions aktiviert** (`[actions] ENABLED = true` in app.ini) 3. **Container Registry aktiviert** (`[packages] ENABLED = true` in app.ini) 4. **Forgejo Runner v6.3.1 installiert und registriert** - Runner-Name: `insight-runner` - Labels: `ubuntu-latest` (docker://node:20) - Laeuft als Systemd-Service (`forgejo-runner.service`) 5. **Repository Secrets angelegt:** - `SSH_DEPLOY_KEY` - CI/CD Private Key - `DEPLOY_HOST` - 172.20.10.59 - `DEPLOY_USER` - deploy - `REGISTRY_USER` - gitadmin - `REGISTRY_PASSWORD` - Forgejo Access Token 6. **Branch Protection eingerichtet:** - `main`: Kein direkter Push, 1 Approval erforderlich - `develop`: Kein direkter Push, 1 Approval erforderlich 7. **Forgejo Setup-Anleitung erstellt** (`docs/FORGEJO_SETUP.md`) #### 3. Server-Setup (insight-dev-01) **Was wurde auf dem Entwicklungsserver (172.20.10.59) gemacht:** 1. **SSH Public Keys hinterlegt** in `/home/deploy/.ssh/authorized_keys` - Deploy-Key (`insight-deploy@xinion.lan`) - fuer manuellen Zugriff - CI/CD-Key (`insight-cicd@xinion.lan`) - fuer Forgejo Actions Pipeline 2. **SSH-Zugang getestet** - Key-basierter Login als `deploy` funktioniert #### 4. Docker Compose & Service-Konfiguration **Erstellte Dateien:** 1. **`docker-compose.yml`** - Alle Basis-Services: - Traefik 3 (API Gateway, Reverse Proxy, Rate Limiting) - PostgreSQL 16-alpine (Performance-Tuning: 1GB shared_buffers, 4GB cache) - PgBouncer (Connection Pooling, Transaction Mode) - Redis 7-alpine (Cache, Sessions, Token-Revocation) - step-ca (Interne Certificate Authority fuer mTLS - geplant) - Core-Service (NestJS) mit Traefik-Labels - Frontend (React) mit Traefik-Labels - 3 isolierte Docker-Netzwerke (insight-web, insight-db, insight-cache) - Health-Checks fuer alle Services 2. **`docker-compose.observability.yml`** - Monitoring-Stack: - Prometheus (Metrics-Sammlung, 30 Tage Retention) - Grafana (Dashboards, automatisch provisionierte Datenquellen) - Loki (Log-Aggregation) - Promtail (Docker Log-Collector) - Tempo (Distributed Tracing, OTLP gRPC) - cAdvisor (Container-Metriken) - PostgreSQL Exporter (DB-Metriken) 3. **Konfigurationsdateien:** - `config/traefik/dynamic/tls.yml` - TLS deaktiviert (Alpha/Dev) - `config/traefik/dynamic/middlewares.yml` - Security-Headers, CORS, Compression - `config/prometheus/prometheus.yml` - Scrape-Konfiguration - `config/loki/loki.yml` - Log-Storage-Konfiguration - `config/promtail/promtail.yml` - Docker-Log-Collector - `config/tempo/tempo.yml` - Tracing-Backend - `config/grafana/provisioning/datasources/datasources.yml` - Auto-Provisioning - `config/postgres/init/01-init-extensions.sql` - DB-Extensions (uuid-ossp, pgcrypto, pg_trgm) #### 5. NestJS Core-Service Implementierung **Projekt-Setup:** - `package.json` mit allen Dependencies (NestJS 10, Prisma 6, Passport, JWT, bcrypt, TOTP) - `tsconfig.json` mit strict: true, noImplicitAny, strictNullChecks - `Dockerfile` (Multi-Stage: base, deps, development, build, production) - `nest-cli.json` Konfiguration **Implementierte Module:** 1. **Auth-Modul** (`src/core/auth/`) - `AuthService`: Login (E-Mail/Passwort), Token-Refresh, Logout, Token-Revocation - `AuthController`: POST /login, /refresh, /logout - `JwtStrategy`: RS256 Passport-Strategy - `TotpService`: TOTP 2FA (Google Authenticator kompatibel) - `LoginDto`: Validierung mit class-validator - Account-Lockout nach 5 Fehlversuchen (15 Min Sperre) - Refresh-Token als HttpOnly Cookie (secure/sameSite umgebungsabhaengig) - Token-Rotation mit Redis-basierter Familien-Erkennung 2. **Users-Modul** (`src/core/users/`) - `UsersService`: CRUD, Bcrypt Cost 12, Passwort-Hashing - `UsersController`: GET /me, GET /users, POST /users, PATCH /users/:id - DTOs: CreateUserDto, UpdateUserDto - Paginierung mit Meta-Informationen 3. **Tenants-Modul** (`src/core/tenants/`) - `TenantsService`: CRUD, Member-Management - `TenantsController`: CRUD + POST /:id/members, DELETE /:id/members/:userId - DTOs: CreateTenantDto, UpdateTenantDto, AddMemberDto - Slug-Validierung (URL-freundlich) 4. **Infrastruktur-Module:** - `PrismaService`: PostgreSQL-Verbindung (platform_core) - `TenantPrismaService`: Dynamische Tenant-DB-Verbindungen mit Caching - `RedisService`: Token-Blocklist, Refresh-Token-Familien, generischer Cache - `HealthController`: GET /health (DB + Redis Status) 5. **Common (Guards, Decorators, Filter):** - `@Public()` Decorator fuer oeffentliche Routen - `@Roles()` Decorator fuer rollenbasierte Zugriffskontrolle - `@CurrentUser()` Decorator fuer User-Extraktion aus JWT - `JwtAuthGuard` (global) mit Token-Revocation-Check - `RolesGuard` fuer Rollen-Pruefung - `GlobalExceptionFilter` fuer strukturierte Fehlerantworten 6. **Config:** - `validateConfig()` mit class-validator fuer Umgebungsvariablen #### 6. Prisma-Schemas & Migration 1. **`core.schema.prisma`** (platform_core Datenbank): - `User` - Plattform-Benutzer (mit Login-Tracking, 2FA) - `AuthProvider` - Multi-Provider Auth (LOCAL, MS_SSO, M2M) - `Tenant` - Mandanten mit JSON-Settings - `TenantMembership` - User-Tenant-Zuordnung (M:N) - `Module` - Verfuegbare Plattform-Module - `TenantModule` - Module pro Tenant - `AuditLog` - Plattform-weites Audit-Log 2. **`tenant.schema.prisma`** (tenant_{slug} Datenbanken): - `Contact` - CRM-Kontakte (Person/Organisation) - `Activity` - CRM-Aktivitaeten (Notiz, Anruf, E-Mail, Meeting, Task) - Referenz-Schema fuer Sprint 2 (CRM-Modul) 3. **Erste Migration erstellt** (`prisma/migrations/20260308000000_init/`) - 7 Tabellen, alle Indizes und Foreign Keys - `migration_lock.toml` fuer Prisma 4. **Seed-Script erstellt** (`prisma/seed.ts`) - Erstellt Platform-Admin: `admin@xinion.de` / `ChangeMe123!` - Bcrypt Cost 12, Rolle: PLATFORM_ADMIN #### 7. React Frontend-Shell **Projekt-Setup:** - `package.json` mit React 18, Vite 6, React Router 6, TanStack Query 5, Axios - `tsconfig.json` mit strict TypeScript - `vite.config.ts` mit API-Proxy und Path-Aliases - `Dockerfile` (Multi-Stage: development mit Vite, production mit Nginx) - `nginx.conf` (SPA-Routing, Security-Headers, Caching) **Implementierte Komponenten:** 1. **Auth-System** (`src/auth/`) - `AuthContext` + `useAuth()` Hook: Login, Logout, Silent Refresh - `LoginPage`: E-Mail/Passwort + optionaler TOTP 2FA-Code - Access-Token NUR im Memory (kein localStorage!) - Automatischer Silent Refresh via HttpOnly Cookie 2. **API-Client** (`src/api/client.ts`) - Axios-Instanz mit automatischem Token-Handling - Request-Interceptor fuer Authorization-Header - Response-Interceptor fuer automatisches Token-Refresh bei 401 3. **App-Shell** (`src/shell/`) - `App`: React Router mit PrivateRoute-Guard - `AppLayout`: Sidebar-Navigation + Outlet - `DashboardPage`: Willkommens-Seite 4. **Admin-Bereich** (`src/admin/`) - `AdminUsersPage`: Benutzer-Tabelle mit Paginierung - `AdminTenantsPage`: Mandanten-Tabelle mit Member-Count 5. **Styling:** - CSS Custom Properties (Farben, Layout, Schatten, Radien) - CSS Modules fuer komponentenspezifische Styles - Responsive Sidebar-Layout #### 8. CI/CD Pipelines 1. **`.forgejo/workflows/ci.yml`** - Continuous Integration: - Trigger: Push auf alle Branches + Pull Requests - Core-Service: npm ci, Prisma Generate, Lint, Type-Check, Test, Build - Frontend: npm ci, Lint, Type-Check, Build 2. **`.forgejo/workflows/deploy.yml`** - Deployment: - Trigger: Push auf main/develop - Build Docker-Images (Core + Frontend) - Push in Forgejo Container Registry - SSH-Deploy auf insight-dev-01 - Health-Check Verifizierung #### 9. IP-basierte Deployment-Anpassung (HTTP statt HTTPS) **Grund:** Kein DNS-Eintrag vorhanden, Zugriff nur ueber IP 172.20.10.59. **Geaenderte Dateien:** 1. **`auth.controller.ts`** - Cookie secure/sameSite umgebungsabhaengig - `secure: true` -> `secure: process.env.NODE_ENV === 'production'` - `sameSite: 'strict'` -> `isProduction ? 'strict' : 'lax'` - Betrifft `setRefreshTokenCookie()` und `logout()` 2. **`docker-compose.yml`** - HTTP + IP Umstellung - HTTPS-Redirect entfernt - TLS-Entrypoint deaktiviert, Port 443 entfernt - Alle Host-Rules: `insight-dev.xinion.lan` -> `172.20.10.59` - Alle Entrypoints: `websecure` -> `web` - URL-Defaults auf `http://172.20.10.59` - PostgreSQL Memory reduziert (1GB/4GB/256MB fuer 8GB RAM VM) - JWT-Keys Volume-Mount hinzugefuegt: `./keys:/app/keys:ro` 3. **`config/traefik/dynamic/tls.yml`** - TLS-Konfiguration deaktiviert 4. **`config/traefik/dynamic/middlewares.yml`** - HSTS-Headers entfernt - CSP: `wss://insight-dev.xinion.lan` -> `ws://172.20.10.59` - CORS: `https://insight-dev.xinion.lan` -> `http://172.20.10.59` 5. **`main.ts`** - CORS-Fallback auf `http://172.20.10.59` 6. **`env.validation.ts`** - APP_URL Default auf `http://172.20.10.59` 7. **`.env.example`** - Alle URLs auf `http://172.20.10.59` 8. **`package-lock.json`** - Generiert fuer core-service und frontend (npm ci braucht diese) 9. **Dokumentation aktualisiert:** - `docs/INFRASTRUCTURE.md` - HTTP statt HTTPS, IP statt DNS - `docs/ACCESS.md` - Ports, URLs, Default-Zugangsdaten - `README.md` - Setup-Anleitung, URLs, Seed-Befehle --- ### Naechste Schritte - [x] SSH Deploy Keys auf insight-dev-01 Server hinterlegen - [x] `docker-compose.yml` erstellen (alle Basis-Services) - [x] `docker-compose.observability.yml` erstellen - [x] NestJS Core-Service implementieren (Auth, Users, Tenants) - [x] Prisma-Schemas erstellen (core + tenant) - [x] React Frontend-Shell implementieren - [x] CI/CD Pipelines (.forgejo/workflows/) definieren - [x] Codebase auf HTTP + IP (172.20.10.59) umstellen - [x] Seed-Script erstellen (admin@xinion.de) - [x] Prisma-Migration erstellen (init) - [x] package-lock.json generieren - [x] Dokumentation aktualisieren - [ ] Commit & Push auf develop - [ ] LVM-Festplatte auf Server erweitern (60GB -> voll nutzbar) - [ ] Docker + Docker Compose auf insight-dev-01 installieren - [ ] Repo klonen, .env + JWT-Keys auf Server erstellen - [ ] Services starten, Migration + Seed ausfuehren - [ ] Erster End-to-End Test (Login -> Dashboard) --- ### Offene Fragen / Abhaengigkeiten - DNS-Eintrag `insight-dev.xinion.lan` wird spaeter eingerichtet (dann HTTPS aktivieren) - LVM auf Server muss erweitert werden (60GB Disk, nur ~56GB sichtbar)