INSIGHT-MVP/Summarize.md
Thomas Reitz 3d486e0541 fix(core): PDF-Export — Bullets, Zertifizierungen, Bild-Anhaenge
- Tasks: Bullet-Praefix nur fuer Zeilen mit echtem Aufzaehlungszeichen (kein spurious Bullet bei Plaintext)
- Zertifizierungen: Schriftgroesse reduziert (Titel 10->9pt, Aussteller 9->8pt) und Timeline-Linie gekuerzt
- Anhaenge: Bild-Anhaenge werden als zusaetzliche Seiten ans PDF angehaengt
- ExportData-Interface + getExportData() um attachments[] erweitert
- Gleiche Bullet-Fix-Logik im DOCX-Export

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 21:33:01 +01:00

783 lines
46 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.

# 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 (18): Experten-Profil PDF-Export — 3 weitere Korrekturen
#### Backend (Core Service)
- `expert-profile/expert-profile.service.ts``getExportData()`: Anhänge jetzt inkludiert (`attachments: { orderBy: { createdAt: 'asc' } }`)
- `expert-profile/profile-export.service.ts` — PDF-Export:
- Neues `ExportAttachment` Interface + `attachments[]` in `ExportData`
- Tasks: Bullet `•` wird nur noch hinzugefügt, wenn Original-Zeile bereits ein Aufzählungszeichen (`•`, `\u2022`) oder Nummerierung (`1.`) enthält — kein spurious Bullet für Plaintext-Zeilen
- Zertifizierungen: Schriftgröße reduziert (Titel 10pt→9pt, Aussteller 9pt→8pt, Abstände angepasst) — passt jetzt zur linken Spalte
- Zertifizierungen: Timeline-Linienlänge 40→32px
- Anhänge: Bild-Anhänge (image/jpeg, image/png, etc.) werden als zusätzliche Seiten ans PDF angehängt (Überschrift "ANLAGE: dateiname" + Bild; PDFs + andere Formate werden übersprungen)
- `expert-profile/profile-export.service.ts` — DOCX-Export:
- Tasks: Gleiche Bullet-Fix-Logik (nur Bullet wenn Original-Zeile Bullet/Nummer hat)
#### TypeScript
- `npx tsc --noEmit` in packages/core-service: 0 Fehler
#### Deployment-Hinweis (Schritt 18)
- Rebuild + Restart: nur core-service
---
### Aenderungen 2026-03-13 (17): Experten-Profil PDF-Export — 5 Korrekturen
#### Backend (Core Service)
- `expert-profile/profile-export.service.ts` — PDF-Export verbessert:
- Neue Helper-Methoden `stripMarkdown()` und `normalizeTaskLine()`
- Zeitraum fett (Helvetica-Bold, #555555)
- Firmenname + Branche fett (Helvetica-Bold, #333333)
- Tasks: Markdown-Marker (**bold**, *italic*, __u__) werden vor Ausgabe entfernt
- Sprachen/Erfahrung: Abstand 14px → 11px, Sektion-Gap 8px → 4px
- Zertifizierungen in rechte Spalte verschoben (nach Berufserfahrung)
- Return-Typ: `Promise<{ buffer, firstName, lastName }>` (war: `Promise<Buffer>`)
- `expert-profile/profile-export.service.ts` — DOCX analog angepasst:
- Zeitraum + Firma fett; normalizeTaskLine() fuer Tasks; Zertifizierungen in rightParagraphs
- `expert-profile/expert-profile.controller.ts`:
- PDF: `Vorname_Nachname_CV.pdf`, DOCX: `Vorname_Nachname_CV.docx`
- RFC-5987 `filename*=UTF-8''...` fuer Umlaut-Dateinamen
#### TypeScript
- `npx tsc --noEmit` in packages/core-service: 0 Fehler
#### Deployment-Hinweis (Schritt 17)
- Rebuild + Restart: nur core-service
---
### Aenderungen 2026-03-13 (16): Global Admin — Login-Screen-Branding (Hintergrund + Logo)
#### Backend (Core Service)
- `settings/settings.controller.ts``GET /settings/branding` + `POST /settings/branding` erweitert:
- 4 neue Felder: `loginBgType` ('gradient'|'solid'|'image'), `loginBgColor1`, `loginBgColor2`, `loginBgImage`
- `loginBgImage` Groeßen-Validierung: max. 2MB
- `loginBgType` Enum-Validierung: nur erlaubte Werte werden gespeichert
- Rueckwaertskompatibel: bestehende Redis-Daten ohne neue Felder liefern `null`-Defaults
#### Frontend
- `admin/AdminCustomizePage.tsx` — Neuer Card-Block "Login-Hintergrund":
- Typ-Toggle: Farbverlauf / Einfarbig / Hintergrundbild
- Farbverlauf: Zwei Farbpicker (Von/Bis) mit Hex-Eingabe
- Einfarbig: Ein Farbpicker mit Hex-Eingabe
- Hintergrundbild: Datei-Upload (max. 2MB, nur Bilder), Entfernen-Button
- Live-Vorschau: Mini-Login-Mockup (200x120px) mit aktuellem Hintergrund und Logo
- `auth/LoginPage.tsx` — Dynamisches Branding:
- `useQuery(['settings', 'branding'])` — oeffentlicher Endpoint, kein Auth noetig
- `useMemo``containerStyle`: leitet CSS `background` aus `loginBgType` ab
- Logo-Anzeige: zeigt `<img>` mit `branding.logo` statt hardcoded "INSIGHT"-Text
- `auth/LoginPage.module.css` — Neue Klasse `.logoImage` (max-width 180px, object-fit contain)
#### TypeScript
- `npx tsc --noEmit` in packages/frontend: 0 Fehler
- `npx tsc --noEmit` in packages/core-service: 0 Fehler
#### Deployment-Hinweis (Schritt 16)
- Rebuild + Restart: core-service (Backend) und frontend
- Kein Datenbankschema-Aenderung, keine Migration noetig (nur Redis)
---
### Aenderungen 2026-03-13 (15): Experten-Profil BulletEditor Fett/Kursiv/Unterstrichen + Aufgaben-Anzeige
#### Frontend
- `profile/sections/ProjectModal.tsx` — BulletEditor: Inline-Formatierungen
- Neue Hilfsfunktion `blWrapFormat()` auf Modul-Ebene: Wraps/Unwraps markierten Text mit Marker-Paar; kein Text markiert → Marker-Paar einsetzen (Cursor dazwischen); Text bereits gewrapped → aushakenlen
- Toolbar: Neuer Button "B" (Fett, `**text**`), "I" (Kursiv, `*text*`), "U" (Unterstrichen, `__text__`)
- Keyboard-Shortcuts: Strg+B, Strg+I, Strg+U (Mac: Cmd+B/I/U)
- `profile/sections/ProjectsSection.tsx` — Aufgaben-Anzeige mit Markdown-Renderer
- Neue Funktion `renderInline()`: wandelt **bold**, *italic*, __underline__ Marker in React-Elemente um (regex-basiert, kein XSS, kein dangerouslySetInnerHTML)
- Neue Komponente `<RichText text={...}>`: Rendert Aufgaben-Text Zeile fuer Zeile mit Bullet-Punkt-Layout, nummerierter Liste, Einrueckung und Inline-Formatierung
- Projekt-Listenelement: zeigt Aufgaben-Text unterhalb von Taetikeit/Firma an (separator border-top) — nur wenn Aufgaben vorhanden
- Neues Layout `entryItemExpanded + entryItemRow` (column statt row wenn Aufgaben vorhanden)
- `profile/ExpertProfileTab.module.css` — Neue Klassen: `.bulletBtnBold`, `.bulletBtnItalic`, `.bulletBtnUnderline`; `.entryItemExpanded`, `.entryItemRow`, `.entryTasks`; `.richText`, `.richTextLine`, `.richTextBullet`, `.richTextNum`, `.richTextBlank`
#### TypeScript
- `npx tsc --noEmit` in packages/frontend: 0 Fehler
#### Deployment-Hinweis (Schritt 15)
- Rebuild + Restart: nur frontend
---
### Aenderungen 2026-03-13 (14): Experten-Profil BulletEditor mit nummerierter Liste + Tab-Einrueckung
#### Frontend
- `profile/sections/ProjectModal.tsx` — BulletEditor erweitert:
- Neuer Button "1. Liste": toggled nummerierte Liste (1. 2. 3.) ein/aus; konvertiert auch Bullet→Nummeriert und umgekehrt
- Tab-Taste: rueckt aktuelle Zeile um 2 Leerzeichen ein; Shift+Tab nimmt sie wieder raus
- Enter in nummerierter Liste: naechste Zeile erhaelt automatisch naechste Zahl (z.B. "3. " → "4. ")
- Enter in leerer Listenelement: beendet die Liste (Bullet oder Nummeriert)
- Enter beruecksichtigt Einrueckung (eingerueckte " • " → naechste Zeile ebenfalls " • ")
- Modul-Level-Hilfsfunktion `blLineAt()`: parst Zeile an Cursor-Position
- Toolbar-Hinweistext: "Tab = einruecken · Shift+Tab = ausruecken"
- `profile/ExpertProfileTab.module.css``.bulletToolbarSep` (vertikale Linie), `.bulletToolbarHint` (Hinweistext)
#### Deployment-Hinweis (Schritt 14)
- Rebuild + Restart: nur frontend
---
### Aenderungen 2026-03-13 (13): Experten-Profil Bullet-Editor fuer Aufgaben + Popup-Backdrop deaktiviert
#### Frontend
- `components/Modal.tsx``onClick={onClose}` vom Overlay-div entfernt: Popups schliessen sich nicht mehr durch Klick neben das Popup (betrifft alle Modal-Instanzen im Projekt)
- `components/Drawer.tsx` — Gleiches: Drawer schliesst sich nicht mehr durch Klick auf den Backdrop
- `profile/sections/ProjectModal.tsx` — Neue lokale `BulletEditor`-Komponente (keine externe Library): Toolbar mit "☰ Liste"-Button (toggled `• ` am Zeilenanfang ein/aus), automatische Aufzaehlungsfortsetzung beim Enter-Druck, leere Bullet-Zeile `• ` + Enter beendet die Liste; ersetzte die einfache `<textarea>` im Feld "Aufgaben"
- `profile/ExpertProfileTab.module.css` — Neue CSS-Klassen: `.bulletEditor`, `.bulletToolbar`, `.bulletBtn`, `.bulletEditorTextarea`, `.bulletBtn:hover`; Editor-Container mit `border + :focus-within`-Ring analog zu Input-Feldern; Textarea innerhalb des Editors hat keinen eigenen Rand (verhindert doppelten Rahmen)
#### TypeScript
- `npx tsc --noEmit` in packages/frontend: 0 Fehler
#### Deployment-Hinweis (Schritt 13)
- Rebuild + Restart: nur frontend (kein Backend-Aenderung)
---
### Aenderungen 2026-03-13 (12): Bug-Fix Sprachen im Experten-Profil
#### Backend (core-service)
- `core/expert-profile/dto/create-language.dto.ts``@IsIn` Validator um fehlende Werte erweitert: `Verhandlungssicher`, `Fliesend`, `Gut` waren nicht enthalten (Frontend sendete diese Werte → 400 Fehler); alle 10 Werte jetzt korrekt: `['Muttersprache', 'Verhandlungssicher', 'Fliesend', 'Gut', 'C2', 'C1', 'B2', 'B1', 'A2', 'A1']`
#### Deployment-Hinweis (Schritt 12)
- Rebuild + Restart: nur core-service
---
### Aenderungen 2026-03-13 (11): Kontakt-Detailseite Breite, Outlook-Daten-Sektion, Felder, Outlook-Push
#### Backend (crm-service)
- `graph/graph.module.ts``exports: [GraphService]` ergaenzt (GraphService ist jetzt in anderen Modulen nutzbar)
- `graph/graph.service.ts` — Neue Methode `pushContactToOutlook(userJwt, contact)`: prueft via `GET /me/contacts?$filter=emailAddresses/any(...)` ob Kontakt in Outlook existiert; existiert er → PATCH (Update); existiert er nicht → POST (Neuanlage); befuellt Outlook-Kontakt mit givenName, surname, jobTitle, department, companyName, emailAddresses, businessPhones, mobilePhone, businessAddress
- `contacts/contacts.module.ts``GraphModule` importiert (ermoeglicht GraphService-Injektion)
- `contacts/contacts.controller.ts` — Neuer Endpoint `POST /crm/contacts/:id/push-to-outlook`: laedt CRM-Kontakt, leitet alle relevanten Felder an `graphService.pushContactToOutlook` weiter; gibt `{ created: boolean, outlookContactId: string }` zurueck
#### Frontend
- `crm/api.ts``contactsApi.pushToOutlook(id)`: `POST /crm/contacts/:id/push-to-outlook`
- `crm/contacts/ContactDetailPage.tsx`:
- **Breite**: Aeusserer Wrapper mit `maxWidth: 960px, margin: 0 auto`
- **Kontaktdaten**: Neues Feld "Typ" (Person/Organisation) ganz oben in der rechten Spalte; "Status" immer angezeigt (bisher nur wenn != ACTIVE); `contact.companyName` als Fallback wenn kein verknuepftes Unternehmen; `state` (Bundesland) in der Adresszeile ergaenzt; `state`-Bedingung fuer Adressblock
- **Outlook Daten** (neue Bezeichnung fuer "Microsoft 365"): Sektion einklappbar (default: eingeklappt), Chevron-Toggle im Karten-Header; "Aufgaben"-Tab entfernt (M365Tab jetzt nur `emails | calendar`); `TasksTab`-Import entfernt; "In Outlook speichern"-Button im Header mit Status-Feedback (Speichern…/✓ In Outlook gespeichert/Fehler)
- `CONTACT_TYPE_LABELS` Konstante, `handlePushToOutlook` Funktion, `outlookExpanded` + `pushStatus` States
#### TypeScript
- `npx tsc --noEmit` in packages/frontend: 0 Fehler
- `npx tsc --noEmit` in packages/crm-service: 0 neue Fehler (vorhandene pre-existing Fehler aus Prisma-Client-Mismatch auf lokalem Mac — werden durch `prisma generate` auf Server behoben)
#### Deployment-Hinweis (Schritt 11)
- Rebuild + Restart: crm-service + frontend (kein neues DB-Schema, kein migrate benoetigt)
---
### Aenderungen 2026-03-13 (10): Dediziertes Projektanfrage-Formular + Button in Vorgänge-Liste
#### Frontend
- `crm/deals/ProjectRequestFormModal.tsx` — Neues dediziertes Modal "Neue Projektanfrage": Projektdetails-Sektion oben (Beschreibung, Auslastung/Start, Laufzeit/Vorort-Anteil, Stundensätze), darunter Vorgangsdaten (Titel, Pipeline/Stage, Kontakt, Unternehmen, Volumen/Abschluss, Notizen); Auto-Select bei genau einem isProjectType-Typ; Warnung wenn kein Typ konfiguriert; Submit-Button disabled bei fehlendem Typ
- `crm/deals/DealsPage.tsx` — Zweiter Button "Neue Projektanfrage" (outlined, primary) neben "Neuer Vorgang"; `isProjectRequestOpen` State; `ProjectRequestFormModal` eingebunden
---
### Aenderungen 2026-03-13 (9): Projektanfrage-Vorgangstyp (isProjectType + ProjectRequestDetails)
#### Backend (crm-service)
- `prisma/crm.schema.prisma``DealType` um `isProjectType Boolean @default(false)` erweitert; neues 1:1-Model `ProjectRequestDetails` (notes, workload, startDate, duration, onsitePercent, rateRemote, rateOnsite) mit `ON DELETE CASCADE` zu `Deal`; `Deal.projectRequest ProjectRequestDetails?` Relation hinzugefuegt
- `prisma/migrations/20260313_project_request/migration.sql``ALTER TABLE deal_types ADD COLUMN is_project_type`; `CREATE TABLE project_request_details` mit Unique-Index auf deal_id und FK mit CASCADE
- `src/deal-types/dto/create-deal-type.dto.ts``isProjectType?: boolean` (`@IsBoolean`) hinzugefuegt
- `src/deal-types/dto/update-deal-type.dto.ts``isProjectType?: boolean` hinzugefuegt
- `src/deals/dto/project-request.dto.ts` — Neues DTO: notes, workload (0-100%), startDate, duration, onsitePercent (0-100%), rateRemote, rateOnsite
- `src/deals/dto/create-deal.dto.ts``projectRequest?: ProjectRequestDto` mit `@ValidateNested()` + `@Type(()=>ProjectRequestDto)` hinzugefuegt
- `src/deals/deals.service.ts``create()`: Nested `projectRequest.create` + `include: {projectRequest:true}`; `findOne()`: `include: {projectRequest:true}`; `update()`: `projectRequest` aus Destructuring extrahiert, `upsert` vor Deal-Update, `include: {projectRequest:true}`
#### Frontend
- `crm/types.ts``DealType.isProjectType: boolean`; `CreateDealTypePayload.isProjectType?`; neues Interface `ProjectRequestDetails`; `CreateProjectRequestPayload`; `Deal.projectRequest?`; `Deal.dealType.isProjectType` ergaenzt; `CreateDealPayload.projectRequest?`
- `crm/deals/DealFormModal.tsx` — Vorgangsart-Dropdown an Anfang des Formulars; Projektanfrage-Sektion (grau hinterlegt, blauer Titel) erscheint conditional bei `isProjectType=true`: Beschreibung, Auslastung/Start-Zeile, Laufzeit/Vorort-Anteil-Zeile, Stundensatz Remote/Vorort-Zeile; Initialisierung aus `deal.projectRequest` beim Bearbeiten; Payload-Zusammenbau mit `projectRequest`-Objekt
- `crm/settings/CrmSettingsPage.tsx``DealTypesConfig`: `isProjectType` State + Spalte "Projektanfrage" in Tabelle (Checkbox im Edit-/Add-Modus, ✓/— im View-Modus)
#### Deployment-Hinweis (Schritt 9)
- Server: `prisma migrate deploy --schema prisma/crm.schema.prisma`
- Server: `prisma generate --schema prisma/crm.schema.prisma`
- Rebuild + Restart: crm-service + frontend
---
### Aenderungen 2026-03-13 (8): Vorgangsart (DealType) + Pipeline-Leerstate
#### Backend (crm-service)
- `prisma/crm.schema.prisma` — Neues Model `DealType` (id, tenantId, name, color, sortOrder) mit `@@unique([tenantId, name])`, `@@map("deal_types")`; Relation `deals Deal[]`; Deal-Model um optionales Feld `dealTypeId String? @map("deal_type_id") @db.Uuid` + Relation `dealType DealType? @relation(...)` erweitert
- `prisma/migrations/20260313_deal_type/migration.sql` — Migration: CREATE TABLE `app_crm.deal_types`, ALTER TABLE `app_crm.deals` ADD COLUMN `deal_type_id`, Unique/Index/FK-Constraints
- `src/deal-types/dto/create-deal-type.dto.ts` — DTO: name (required), color (optional, Hex-Validierung), sortOrder (optional)
- `src/deal-types/dto/update-deal-type.dto.ts` — Alle Felder optional
- `src/deal-types/deal-types.service.ts` — CRUD-Service (findAll/create/update/remove) analog IndustriesService, Duplikat-Check, Nutzungs-Guard beim Loeschen
- `src/deal-types/deal-types.controller.ts` — REST-Controller: GET/POST/PATCH/DELETE unter `/crm/deal-types`, TenantGuard
- `src/deal-types/deal-types.module.ts` — NestJS-Modul
- `src/app.module.ts` — DealTypesModule registriert
- `src/deals/dto/create-deal.dto.ts``dealTypeId?: string` (UUID, optional) hinzugefuegt
- `src/deals/deals.service.ts``dealTypeId` beim Create an Prisma weitergegeben
#### Frontend
- `crm/types.ts` — Neues Interface `DealType` (id, tenantId, name, color, sortOrder, _count?); `CreateDealTypePayload`, `UpdateDealTypePayload`; `Deal.dealTypeId: string|null` + `dealType?: {...}|null`; `CreateDealPayload.dealTypeId?: string`
- `crm/api.ts``dealTypesApi` (list/create/update/delete auf `/crm/deal-types`) importiert; Typ-Imports erweitert
- `crm/hooks.ts``crmKeys.dealTypes`, `useDealTypes`, `useCreateDealType`, `useUpdateDealType`, `useDeleteDealType`
- `crm/settings/CrmSettingsPage.tsx``DealTypesConfig`-Komponente (analog IndustriesConfig mit Farbpicker, Sortierung); als erster Block im Tab "Weitere Einstellungen" platziert
- `crm/deals/DealFormModal.tsx` — Vorgangsart-Dropdown (select mit DealType-Optionen, "— Keine —" als Default); Pipeline-Leerstate-Hinweis mit Link zu CRM-Einstellungen; `dealTypeId` State + Payload-Uebergabe
#### Deployment-Hinweis
Nach Deploy: `npx prisma migrate deploy && npx prisma generate` im crm-service ausfuehren.
---
### Aenderungen 2026-03-13 (7): Profil-Bereich nach oben rechts verschoben (Topbar)
#### Frontend
- `shell/AppLayout.tsx` — Profil-/Logout-/Theme-Bereich aus der Sidebar entfernt und in einen neuen `<header className={styles.topbar}>` am Anfang des `<main>`-Bereichs verschoben:
- Theme-Schalter: 3 kompakte Icon-Buttons (☀ ☾ ⚙) als Gruppe, aktiver Modus hervorgehoben
- Benutzer-Profil: Avatar + Name, klickbar → `/profile`
- Logout: Icon-Button (Tuer-Pfeil-SVG) mit Tooltip "Abmelden"
- `<Outlet />` nun in `<div className={styles.mainContent}>` eingebettet (eigene Padding-Klasse)
- Sidebar hat nur noch: Logo/Brand + Navigation + Admin-Link (deutlich aufgeraeumt)
- `shell/AppLayout.module.css` — Theme-Toggle- und UserInfo-Abschnitte entfernt (ca. 150 Zeilen weniger); `.main` auf `display:flex; flex-direction:column` umgestellt; neues `.mainContent { padding:2rem }`; neue Klassen: `.topbar`, `.topbarThemeGroup`, `.topbarThemeBtn(Active)`, `.topbarUser`, `.topbarUserName`, `.topbarIconBtn`
---
### 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 (0100%) 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 `<Modal>` (zentriert) zu `<Drawer>` (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)