- GET /settings/ai-config: gibt { configured: boolean } zurück
- POST /settings/ai-config: speichert Key Base64-kodiert in Redis (PLATFORM_ADMIN)
- HelpService: dynamische Key-Auflösung aus Redis mit 60s In-Memory-Cache
- AdminAiSettingsPage: neue Admin-Seite /admin/ai-settings
- HelpPanel: zeigt Hinweis + Link wenn KI nicht konfiguriert
- Env-Variable ANTHROPIC_API_KEY hat weiterhin Vorrang (backward compat.)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
50 KiB
INSIGHT MVP - Aenderungsprotokoll
Stand: 2026-03-15
Aktueller Sprint: CRM Phase 3 + Stammdaten + Reporting + Hilfesystem + KI-Einstellungen (Feature-Branch: feature/crm-service)
Aenderungen 2026-03-15 (20): KI-Einstellungen über Admin-UI konfigurierbar
Feature: Anthropic API-Key über Admin-UI setzen
Problem: ANTHROPIC_API_KEY war nur per .env-Datei setzbar — Admins ohne Server-Zugang konnten den KI-Chat nicht aktivieren.
Backend (Core Service)
src/core/settings/settings.controller.ts— 2 neue Endpoints:GET /settings/ai-config— gibt{ configured: boolean }zurück (Key wird niemals zurückgesendet)POST /settings/ai-config— speichert Key Base64-kodiert in Redis unterplatform_ai_config; leerer String löscht den Key (@Roles PLATFORM_ADMIN)
src/core/help/help.service.ts— RedisService injiziert; Key wird pergetApiKey()dynamisch geladen (Env-Variable hat Vorrang, 60s In-Memory-Cache); Cache-Invalidierung bei 401-Fehlersrc/core/help/help.module.ts— RedisModule importiert
Frontend
src/admin/AdminAiSettingsPage.tsx(neu) — Admin-Seite mit Status-Anzeige, Password-Input, Speichern/Entfernensrc/admin/AdminLayout.tsx— Tab "KI-Einstellungen" ergänztsrc/shell/App.tsx— Route/admin/ai-settings+ Import ergänztsrc/components/HelpPanel/HelpPanel.tsx—useQuery(['settings', 'ai-config']): wenn nicht konfiguriert → Hinweis mit Link zu Admin-Seite statt Chat-Input
Sicherheit: GET gibt niemals den Key zurück. Base64-Kodierung (konsistent mit Projektstil). Backward-Kompatibilität: ANTHROPIC_API_KEY in .env hat weiterhin Vorrang.
Aenderungen 2026-03-15 (19): Stammdaten, CRM Reporting, Hilfesystem
Feature 1: Stammdaten (Master Data)
Backend (Core Service)
prisma/core.schema.prisma— 5 neue Modelle:Department,Location,CostCenter,JobTitle,SkillCategory(Tabellen: departments, locations, cost_centers, job_titles, skill_categories)src/core/master-data/master-data.service.ts— CRUD-Service für alle 5 Stammdaten-Entitätensrc/core/master-data/master-data.controller.ts— REST-Controller mit Public-Endpunkten (Dropdowns) + Admin-Endpunkten (@Roles('PLATFORM_ADMIN'))src/core/master-data/master-data.module.ts— NestJS-Modulsrc/app.module.ts— MasterDataModule registriert
Frontend
src/admin/AdminMasterDataPage.tsx— Admin-Seite mit 5 Tabs, inline Edit/Delete/Createsrc/admin/AdminLayout.tsx— Tab "Stammdaten" ergänztsrc/shell/App.tsx— Route/admin/master-dataergänzt
Deployment-Hinweis: prisma migrate deploy auf Server erforderlich + Rebuild core-service + frontend
Feature 2: CRM Reporting
Backend (CRM Service)
src/deals/deals.service.ts— MethodegetStats(): Win/Loss-Rate, Umsatz, Avg-Deal-Value, LostByReason, MonthlyTrend (12 Monate)src/deals/dto/stats-query.dto.ts—StatsQueryDtomit Period-Enum (MONTH/QUARTER/YEAR)src/deals/deals.controller.ts— GET/deals/statsEndpunktsrc/activities/activities.service.ts— MethodegetStats(): Aktivitäten nach Typ, Erledigungsrate, Upcoming Taskssrc/activities/activities.controller.ts— GET/activities/statsEndpunkt
Frontend
package.json—recharts ^2.12.0als Dependencysrc/crm/types.ts— InterfacesDealStats,ActivityStats,MonthlyTrendEntry,LostByReasonEntry,ActivityStatsByTypesrc/crm/api.ts—dealStatsApi.get(),activitiesApi.getStats()ergänztsrc/crm/hooks.ts—useDealStats(),useActivityStats()Hookssrc/crm/reports/ReportsPage.tsx— Reporting-Seite mit Period-Selector, Deal/Aktivitäts-Tabs, LineChart/BarChart/PieChart (recharts)src/shell/App.tsx— Route/crm/reportssrc/shell/AppLayout.tsx— Sidebar-Link "Reports" nach Kanban
Feature 3: Hilfesystem (KI-Chat + Tooltips + Panel)
Backend (Core Service)
package.json—@anthropic-ai/sdk ^0.37.0src/config/env.validation.ts—ANTHROPIC_API_KEY(optional)src/core/help/help.service.ts— Claude Haiku Integration, Chat-Methode mit System-Promptsrc/core/help/help.controller.ts— POST/help/chatsrc/core/help/help.module.ts— NestJS-Modulsrc/app.module.ts— HelpModule registriert
Frontend
src/components/HelpTooltip/HelpTooltip.tsx+index.ts— Hover-Tooltip mit ❓-Iconsrc/components/HelpPanel/HelpPanel.tsx+index.ts— Side-Panel mit Kontexthilfe + KI-Chatsrc/shell/AppLayout.tsx— ❓-Button in Topbar + HelpPanel-Integration
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
ExportAttachmentInterface +attachments[]inExportData - 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)
- Neues
expert-profile/profile-export.service.ts— DOCX-Export:- Tasks: Gleiche Bullet-Fix-Logik (nur Bullet wenn Original-Zeile Bullet/Nummer hat)
TypeScript
npx tsc --noEmitin 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()undnormalizeTaskLine() - 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>)
- Neue Helper-Methoden
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
- PDF:
TypeScript
npx tsc --noEmitin 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/brandingerweitert:- 4 neue Felder:
loginBgType('gradient'|'solid'|'image'),loginBgColor1,loginBgColor2,loginBgImage loginBgImageGroeßen-Validierung: max. 2MBloginBgTypeEnum-Validierung: nur erlaubte Werte werden gespeichert- Rueckwaertskompatibel: bestehende Redis-Daten ohne neue Felder liefern
null-Defaults
- 4 neue Felder:
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 noetiguseMemo→containerStyle: leitet CSSbackgroundausloginBgTypeab- Logo-Anzeige: zeigt
<img>mitbranding.logostatt hardcoded "INSIGHT"-Text
auth/LoginPage.module.css— Neue Klasse.logoImage(max-width 180px, object-fit contain)
TypeScript
npx tsc --noEmitin packages/frontend: 0 Fehlernpx tsc --noEmitin 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)
- Neue Hilfsfunktion
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)
- Neue Funktion
profile/ExpertProfileTab.module.css— Neue Klassen:.bulletBtnBold,.bulletBtnItalic,.bulletBtnUnderline;.entryItemExpanded,.entryItemRow,.entryTasks;.richText,.richTextLine,.richTextBullet,.richTextNum,.richTextBlank
TypeScript
npx tsc --noEmitin 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 Backdropprofile/sections/ProjectModal.tsx— Neue lokaleBulletEditor-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 mitborder + :focus-within-Ring analog zu Input-Feldern; Textarea innerhalb des Editors hat keinen eigenen Rand (verhindert doppelten Rahmen)
TypeScript
npx tsc --noEmitin 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—@IsInValidator um fehlende Werte erweitert:Verhandlungssicher,Fliesend,Gutwaren 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 MethodepushContactToOutlook(userJwt, contact): prueft viaGET /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, businessAddresscontacts/contacts.module.ts—GraphModuleimportiert (ermoeglicht GraphService-Injektion)contacts/contacts.controller.ts— Neuer EndpointPOST /crm/contacts/:id/push-to-outlook: laedt CRM-Kontakt, leitet alle relevanten Felder angraphService.pushContactToOutlookweiter; gibt{ created: boolean, outlookContactId: string }zurueck
Frontend
crm/api.ts—contactsApi.pushToOutlook(id):POST /crm/contacts/:id/push-to-outlookcrm/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.companyNameals 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_LABELSKonstante,handlePushToOutlookFunktion,outlookExpanded+pushStatusStates
- Breite: Aeusserer Wrapper mit
TypeScript
npx tsc --noEmitin packages/frontend: 0 Fehlernpx tsc --noEmitin packages/crm-service: 0 neue Fehler (vorhandene pre-existing Fehler aus Prisma-Client-Mismatch auf lokalem Mac — werden durchprisma generateauf 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 Typcrm/deals/DealsPage.tsx— Zweiter Button "Neue Projektanfrage" (outlined, primary) neben "Neuer Vorgang";isProjectRequestOpenState;ProjectRequestFormModaleingebunden
Aenderungen 2026-03-13 (9): Projektanfrage-Vorgangstyp (isProjectType + ProjectRequestDetails)
Backend (crm-service)
prisma/crm.schema.prisma—DealTypeumisProjectType Boolean @default(false)erweitert; neues 1:1-ModelProjectRequestDetails(notes, workload, startDate, duration, onsitePercent, rateRemote, rateOnsite) mitON DELETE CASCADEzuDeal;Deal.projectRequest ProjectRequestDetails?Relation hinzugefuegtprisma/migrations/20260313_project_request/migration.sql—ALTER TABLE deal_types ADD COLUMN is_project_type;CREATE TABLE project_request_detailsmit Unique-Index auf deal_id und FK mit CASCADEsrc/deal-types/dto/create-deal-type.dto.ts—isProjectType?: boolean(@IsBoolean) hinzugefuegtsrc/deal-types/dto/update-deal-type.dto.ts—isProjectType?: booleanhinzugefuegtsrc/deals/dto/project-request.dto.ts— Neues DTO: notes, workload (0-100%), startDate, duration, onsitePercent (0-100%), rateRemote, rateOnsitesrc/deals/dto/create-deal.dto.ts—projectRequest?: ProjectRequestDtomit@ValidateNested()+@Type(()=>ProjectRequestDto)hinzugefuegtsrc/deals/deals.service.ts—create(): NestedprojectRequest.create+include: {projectRequest:true};findOne():include: {projectRequest:true};update():projectRequestaus Destructuring extrahiert,upsertvor Deal-Update,include: {projectRequest:true}
Frontend
crm/types.ts—DealType.isProjectType: boolean;CreateDealTypePayload.isProjectType?; neues InterfaceProjectRequestDetails;CreateProjectRequestPayload;Deal.projectRequest?;Deal.dealType.isProjectTypeergaenzt;CreateDealPayload.projectRequest?crm/deals/DealFormModal.tsx— Vorgangsart-Dropdown an Anfang des Formulars; Projektanfrage-Sektion (grau hinterlegt, blauer Titel) erscheint conditional beiisProjectType=true: Beschreibung, Auslastung/Start-Zeile, Laufzeit/Vorort-Anteil-Zeile, Stundensatz Remote/Vorort-Zeile; Initialisierung ausdeal.projectRequestbeim Bearbeiten; Payload-Zusammenbau mitprojectRequest-Objektcrm/settings/CrmSettingsPage.tsx—DealTypesConfig:isProjectTypeState + 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 ModelDealType(id, tenantId, name, color, sortOrder) mit@@unique([tenantId, name]),@@map("deal_types"); Relationdeals Deal[]; Deal-Model um optionales FelddealTypeId String? @map("deal_type_id") @db.Uuid+ RelationdealType DealType? @relation(...)erweitertprisma/migrations/20260313_deal_type/migration.sql— Migration: CREATE TABLEapp_crm.deal_types, ALTER TABLEapp_crm.dealsADD COLUMNdeal_type_id, Unique/Index/FK-Constraintssrc/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 optionalsrc/deal-types/deal-types.service.ts— CRUD-Service (findAll/create/update/remove) analog IndustriesService, Duplikat-Check, Nutzungs-Guard beim Loeschensrc/deal-types/deal-types.controller.ts— REST-Controller: GET/POST/PATCH/DELETE unter/crm/deal-types, TenantGuardsrc/deal-types/deal-types.module.ts— NestJS-Modulsrc/app.module.ts— DealTypesModule registriertsrc/deals/dto/create-deal.dto.ts—dealTypeId?: string(UUID, optional) hinzugefuegtsrc/deals/deals.service.ts—dealTypeIdbeim Create an Prisma weitergegeben
Frontend
crm/types.ts— Neues InterfaceDealType(id, tenantId, name, color, sortOrder, _count?);CreateDealTypePayload,UpdateDealTypePayload;Deal.dealTypeId: string|null+dealType?: {...}|null;CreateDealPayload.dealTypeId?: stringcrm/api.ts—dealTypesApi(list/create/update/delete auf/crm/deal-types) importiert; Typ-Imports erweitertcrm/hooks.ts—crmKeys.dealTypes,useDealTypes,useCreateDealType,useUpdateDealType,useDeleteDealTypecrm/settings/CrmSettingsPage.tsx—DealTypesConfig-Komponente (analog IndustriesConfig mit Farbpicker, Sortierung); als erster Block im Tab "Weitere Einstellungen" platziertcrm/deals/DealFormModal.tsx— Vorgangsart-Dropdown (select mit DealType-Optionen, "— Keine —" als Default); Pipeline-Leerstate-Hinweis mit Link zu CRM-Einstellungen;dealTypeIdState + 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);.mainaufdisplay:flex; flex-direction:columnumgestellt; 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 setIntervalhooks/useWeather.ts— Open-Meteo API um 3-Tage-Prognose erweitert (&daily=weather_code,temperature_2m_max,temperature_2m_min&forecast_days=3);WeatherDataumforecast: ForecastDay[]ergaenzt; neues InterfaceForecastDayshell/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-TabHomeEmailsWidget: Posteingang-E-Mails der letzten 3 Tage (client-seitiger Filter), direkter Outlook-Link, „Alle →" schaltet auf E-Mail-Tabrole-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@mapauf snake_case,@db.VarChar)prisma/migrations/20260313_user_profile_extra_fields/migration.sql— ALTER TABLE fuer die 4 neuen Spaltencore/users/dto/update-user.dto.ts— Neue Felder mit@IsOptional,@ValidateIf,@IsString,@MaxLengthcore/users/users.service.ts—findById(),update(),updateProfile()um neue Felder erweitert
Backend: CRM-Service
graph/graph.service.ts—M365UserProfileumdepartment,companyName,officeLocationerweitert;getM365Profile()$selecterweitert; neue MethodegetM365Photo(): Graph/me/photos/96x96/$valueals ArrayBuffer → Base64 JPEG Data-URL; 404/400 → nullgraph/office365.controller.ts— Neuer EndpointGET /crm/office365/photo
Frontend
auth/AuthContext.tsx—User-Interface:jobTitle?,department?,companyName?,officeLocation?crm/types.ts—M365UserProfileumdepartment,companyName,officeLocationerweitertcrm/api.ts—office365Api.getM365Photo()hinzugefuegtprofile/ProfilePage.tsx— 4 neue State-Variablen, Organisations-Fieldset (2×2 Grid),handleEnrichFromO365ueberschreibt Profilfoto immer; Auto-Sync-Hook befuellt alle neuen Felderhooks/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/memit 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 MethodegetCalendarEventsForRange(userJwt, userId, startDate, endDate): laedt Kalender-Termine fuer beliebigen Zeitraum via/me/calendarView, Redis-Cache 5 Min;wellKnownNameausgetMailFolders()$select entfernt (400-Fehler auf Exchange-Tenants die das OData-Property nicht unterstuetzen)graph/office365.controller.ts— Neuer EndpointGET /crm/office365/calendar/range?startDate=&endDate=(vor@Get('calendar')definiert, um Routing-Konflikt zu vermeiden)
Frontend
crm/types.ts—M365MailFolder.wellKnownNamealsoptionalmarkiert (nicht alle Exchange-Tenants liefern das Feld)crm/api.ts—office365Api.getCalendarRange(startDate, endDate)crm/hooks.ts— Neuer HookuseOffice365CalendarRange(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-Hashshell/DashboardCalendarTab.module.css— Vollstaendiges Stylingshell/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.) dawellKnownNamenicht 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 MethodefindByEmail(tenantId, email): sucht Kontakt anhand E-Mail-Adresse (legacy + multi-value emails)contacts/contacts.controller.ts— Neuer EndpointGET /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 Kommentarfeldshell/DashboardEmailTab.module.css— Vollstaendiges Stylingshell/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+$orderbykoennen nicht kombiniert werden (Graph API Limitation) →$orderbyaus Kontakt-E-Mail-Suche entfernt; neue globale Methoden:getAllEmails,getAllCalendarEvents,getAllOutlookContacts,getTasks(war schon vorhanden);attendeeszu Kalender-Abfragen ergaenzt; Fehler werden jetzt geloggtgraph/office365.controller.ts— Neuer Controller mit globalen Office365-Endpoints:GET /crm/office365/emails,GET /crm/office365/calendar,GET /crm/office365/contacts,GET /crm/office365/tasksgraph/graph.module.ts—Office365Controllerregistriert
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-Kontaktecrm/office365/Office365Page.module.css— Vollstaendiges Styling (Cards, Tabs, Grid, Badges)crm/types.ts—M365Email.hasAttachmentsundM365CalendarEvent.attendeesergaenzt; neues InterfaceM365Contactcrm/api.ts—office365Api(getEmails, getCalendar, getContacts, getTasks)crm/hooks.ts—useOffice365Emails,useOffice365Calendar,useOffice365Contacts,useOffice365Tasksshell/App.tsx— Route/crm/office365hinzugefuegtshell/AppLayout.tsx— NavLink "Office 365" (Grid-Icon) nach Kanban ergaenzt
Datenbankfix
user_integrations-Tabelle: Tenant-Membership fuert.reitz@xinion.dein "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,M365TaskListcrm/api.ts—integrationsApi(list, disconnectM365, getM365ConnectUrl) +graphApi(getContactEmails, getContactCalendar, getContactTasks)crm/hooks.ts—useIntegrations,useDisconnectM365,useContactEmails,useContactCalendar,useContactTasksprofile/ProfilePage.tsx:- Neuer Tab "Integrationen" (Typ:
ProfileTab = 'personal' | 'expert' | 'password' | 'integrations') - Oeffnet automatisch wenn
?integration=microsoft-365URL-Param gesetzt ist - Zeigt Erfolgs-/Fehlermeldung aus
?status=success|errorParam - "Microsoft 365 verbinden" Button (Link zu
/api/v1/auth/integrations/microsoft-365) - Verbunden-Ansicht: Tenant-ID, Token-Ablauf, "Verbindung trennen" Button
- Neuer Tab "Integrationen" (Typ:
crm/contacts/EmailsTab.tsx— E-Mail-Liste aus MS Graph, ungelesen fett + blauer Rand, Link zu Outlook Webcrm/contacts/CalendarTab.tsx— Kalendertermine (naechste 90 Tage), Online-Meeting-Badge, Link zu Outlook Webcrm/contacts/TasksTab.tsx— Microsoft To Do Aufgaben, gruppiert nach Listen, Status-/Prioritaets-Badgescrm/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 Mincrm-service/src/graph/graph.controller.ts— GET /crm/contacts/:id/emails|calendar|taskscrm-service/src/graph/graph.module.ts— Modul-Definitioncrm-service/src/app.module.ts— GraphModule registriertcrm-service/src/config/env.validation.ts—CORE_SERVICE_URLergaenztdocker-compose.crm.yml—CORE_SERVICE_URL=http://core:3000hinzugefuegt
Aenderungen 2026-03-12: Microsoft 365 OAuth-Integration — Core-Service
core-service/prisma/core.schema.prisma—UserIntegrationModell + Relation aufUsercore-service/prisma/migrations/20260312_user_integrations/migration.sql— Migration fueruser_integrationsTabellecore-service/src/config/env.validation.ts—AZURE_INTEGRATION_REDIRECT_URI,INTEGRATION_ENCRYPTION_KEYcore-service/src/core/auth/sso/entra-id.service.ts—getIntegrationAuthUrl,handleIntegrationCallback,refreshIntegrationTokencore-service/src/core/integrations/integrations.service.ts— AES-256-GCM Token-Verschluesselung, Token-CRUD, Auto-Refreshcore-service/src/core/integrations/integrations.controller.ts— OAuth-Flow + Token-Endpointscore-service/src/core/integrations/integrations.module.tscore-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, Cardsfrontend/src/shell/App.tsx— Route/crm/kanbanfrontend/src/shell/AppLayout.tsx— NavLink "Kanban" im CRM-Bereichfrontend/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,ContractsQueryParamsergaenztcrm/api.ts—contractsApi(list, create, update, delete) mit/crm/companies/:id/contractscrm/hooks.ts—crmKeys.contracts+useContracts,useCreateContract,useUpdateContract,useDeleteContractcrm/companies/CompanyDetailPage.tsx—contractCountProp 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, mappingRecord→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-Drawercrm/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 --noEmitin 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 Footerpackages/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
formAttribut - 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,.nameRowneu
TypeScript
npx tsc --noEmitin packages/frontend: 0 Fehler
Aenderungen in dieser Session
1. Projektinitialisierung & Infrastruktur-Definition
Was wurde gemacht:
-
SSH Keys erstellt
- Deploy-Key (
.keys/deploy_ed25519) fuer Server-Zugriff - CI/CD-Key (
.keys/cicd_ed25519) fuer Forgejo Actions Pipeline
- Deploy-Key (
-
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
-
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
-
Projektstruktur aufgesetzt (packages/core-service, packages/frontend, config, .forgejo)
-
Basis-Konfigurationsdateien (.gitignore, .env.example, README.md)
2. Forgejo Git-Server Konfiguration
Was wurde auf dem Git-Server (172.20.10.11) gemacht:
- Docker Engine 29.3 installiert (fuer Forgejo Actions Runner)
- Forgejo Actions aktiviert (
[actions] ENABLED = truein app.ini) - Container Registry aktiviert (
[packages] ENABLED = truein app.ini) - 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)
- Runner-Name:
- Repository Secrets angelegt:
SSH_DEPLOY_KEY- CI/CD Private KeyDEPLOY_HOST- 172.20.10.59DEPLOY_USER- deployREGISTRY_USER- gitadminREGISTRY_PASSWORD- Forgejo Access Token
- Branch Protection eingerichtet:
main: Kein direkter Push, 1 Approval erforderlichdevelop: Kein direkter Push, 1 Approval erforderlich
- Forgejo Setup-Anleitung erstellt (
docs/FORGEJO_SETUP.md)
3. Server-Setup (insight-dev-01)
Was wurde auf dem Entwicklungsserver (172.20.10.59) gemacht:
- 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
- Deploy-Key (
- SSH-Zugang getestet - Key-basierter Login als
deployfunktioniert
4. Docker Compose & Service-Konfiguration
Erstellte Dateien:
-
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
-
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)
-
Konfigurationsdateien:
config/traefik/dynamic/tls.yml- TLS deaktiviert (Alpha/Dev)config/traefik/dynamic/middlewares.yml- Security-Headers, CORS, Compressionconfig/prometheus/prometheus.yml- Scrape-Konfigurationconfig/loki/loki.yml- Log-Storage-Konfigurationconfig/promtail/promtail.yml- Docker-Log-Collectorconfig/tempo/tempo.yml- Tracing-Backendconfig/grafana/provisioning/datasources/datasources.yml- Auto-Provisioningconfig/postgres/init/01-init-extensions.sql- DB-Extensions (uuid-ossp, pgcrypto, pg_trgm)
5. NestJS Core-Service Implementierung
Projekt-Setup:
package.jsonmit allen Dependencies (NestJS 10, Prisma 6, Passport, JWT, bcrypt, TOTP)tsconfig.jsonmit strict: true, noImplicitAny, strictNullChecksDockerfile(Multi-Stage: base, deps, development, build, production)nest-cli.jsonKonfiguration
Implementierte Module:
-
Auth-Modul (
src/core/auth/)AuthService: Login (E-Mail/Passwort), Token-Refresh, Logout, Token-RevocationAuthController: POST /login, /refresh, /logoutJwtStrategy: RS256 Passport-StrategyTotpService: 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
-
Users-Modul (
src/core/users/)UsersService: CRUD, Bcrypt Cost 12, Passwort-HashingUsersController: GET /me, GET /users, POST /users, PATCH /users/:id- DTOs: CreateUserDto, UpdateUserDto
- Paginierung mit Meta-Informationen
-
Tenants-Modul (
src/core/tenants/)TenantsService: CRUD, Member-ManagementTenantsController: CRUD + POST /:id/members, DELETE /:id/members/:userId- DTOs: CreateTenantDto, UpdateTenantDto, AddMemberDto
- Slug-Validierung (URL-freundlich)
-
Infrastruktur-Module:
PrismaService: PostgreSQL-Verbindung (platform_core)TenantPrismaService: Dynamische Tenant-DB-Verbindungen mit CachingRedisService: Token-Blocklist, Refresh-Token-Familien, generischer CacheHealthController: GET /health (DB + Redis Status)
-
Common (Guards, Decorators, Filter):
@Public()Decorator fuer oeffentliche Routen@Roles()Decorator fuer rollenbasierte Zugriffskontrolle@CurrentUser()Decorator fuer User-Extraktion aus JWTJwtAuthGuard(global) mit Token-Revocation-CheckRolesGuardfuer Rollen-PruefungGlobalExceptionFilterfuer strukturierte Fehlerantworten
-
Config:
validateConfig()mit class-validator fuer Umgebungsvariablen
6. Prisma-Schemas & Migration
-
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-SettingsTenantMembership- User-Tenant-Zuordnung (M:N)Module- Verfuegbare Plattform-ModuleTenantModule- Module pro TenantAuditLog- Plattform-weites Audit-Log
-
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)
-
Erste Migration erstellt (
prisma/migrations/20260308000000_init/)- 7 Tabellen, alle Indizes und Foreign Keys
migration_lock.tomlfuer Prisma
-
Seed-Script erstellt (
prisma/seed.ts)- Erstellt Platform-Admin:
admin@xinion.de/ChangeMe123! - Bcrypt Cost 12, Rolle: PLATFORM_ADMIN
- Erstellt Platform-Admin:
7. React Frontend-Shell
Projekt-Setup:
package.jsonmit React 18, Vite 6, React Router 6, TanStack Query 5, Axiostsconfig.jsonmit strict TypeScriptvite.config.tsmit API-Proxy und Path-AliasesDockerfile(Multi-Stage: development mit Vite, production mit Nginx)nginx.conf(SPA-Routing, Security-Headers, Caching)
Implementierte Komponenten:
-
Auth-System (
src/auth/)AuthContext+useAuth()Hook: Login, Logout, Silent RefreshLoginPage: E-Mail/Passwort + optionaler TOTP 2FA-Code- Access-Token NUR im Memory (kein localStorage!)
- Automatischer Silent Refresh via HttpOnly Cookie
-
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
-
App-Shell (
src/shell/)App: React Router mit PrivateRoute-GuardAppLayout: Sidebar-Navigation + OutletDashboardPage: Willkommens-Seite
-
Admin-Bereich (
src/admin/)AdminUsersPage: Benutzer-Tabelle mit PaginierungAdminTenantsPage: Mandanten-Tabelle mit Member-Count
-
Styling:
- CSS Custom Properties (Farben, Layout, Schatten, Radien)
- CSS Modules fuer komponentenspezifische Styles
- Responsive Sidebar-Layout
8. CI/CD Pipelines
-
.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
-
.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:
-
auth.controller.ts- Cookie secure/sameSite umgebungsabhaengigsecure: true->secure: process.env.NODE_ENV === 'production'sameSite: 'strict'->isProduction ? 'strict' : 'lax'- Betrifft
setRefreshTokenCookie()undlogout()
-
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
-
config/traefik/dynamic/tls.yml- TLS-Konfiguration deaktiviert -
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
-
main.ts- CORS-Fallback aufhttp://172.20.10.59 -
env.validation.ts- APP_URL Default aufhttp://172.20.10.59 -
.env.example- Alle URLs aufhttp://172.20.10.59 -
package-lock.json- Generiert fuer core-service und frontend (npm ci braucht diese) -
Dokumentation aktualisiert:
docs/INFRASTRUCTURE.md- HTTP statt HTTPS, IP statt DNSdocs/ACCESS.md- Ports, URLs, Default-ZugangsdatenREADME.md- Setup-Anleitung, URLs, Seed-Befehle
Naechste Schritte
- SSH Deploy Keys auf insight-dev-01 Server hinterlegen
docker-compose.ymlerstellen (alle Basis-Services)docker-compose.observability.ymlerstellen- NestJS Core-Service implementieren (Auth, Users, Tenants)
- Prisma-Schemas erstellen (core + tenant)
- React Frontend-Shell implementieren
- CI/CD Pipelines (.forgejo/workflows/) definieren
- Codebase auf HTTP + IP (172.20.10.59) umstellen
- Seed-Script erstellen (admin@xinion.de)
- Prisma-Migration erstellen (init)
- package-lock.json generieren
- 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.lanwird spaeter eingerichtet (dann HTTPS aktivieren) - LVM auf Server muss erweitert werden (60GB Disk, nur ~56GB sichtbar)