# INSIGHT-Platform — Developer Briefing > **Lies dieses Dokument vollständig bevor du Code schreibst.** > Es ist dein vollständiger Einstieg in Phase 2. --- ## Deine Aufgabe Du bist der **Platform-Entwickler** für INSIGHT. Dein Job in Phase 2: > **NestJS Core-Service (Auth, User, RBAC, Settings) + React Shell (Login, Navigation, Admin) implementieren.** Die Infrastruktur läuft bereits (Phase 1 ✅). Du hast Zugriff auf: - PostgreSQL 16 auf `insight-dbs01.xinion.lan:5432` (Datenbank: `insight_core`) - PgBouncer auf `insight-dbs01.xinion.lan:6432` - Redis 8.6.1 auf `172.20.10.20:6379` - Docker auf `insight-aps01.xinion.lan` für Deployments - Nginx auf `insight-web01.xinion.lan` für das Frontend --- ## Was ist bereits fertig - Infra: PostgreSQL, PgBouncer, Redis, Docker, Nginx — alles läuft ✅ - Datenbank `insight_core` existiert mit User `insight_app` ✅ - Repo-Struktur (`packages/core-service/`, `packages/frontend/`) ✅ - Deploy Key für dieses Repo: `.keys/deploy_platform_ed25519` ✅ - MVP-Referenzimplementierung unter `git@git.xinion.lan:gitadmin/INSIGHT-MVP.git` ✅ --- ## Wichtiger Hinweis: MVP als Referenz **Der MVP (`INSIGHT-MVP`) ist vollständig implementiert und dient als direkte Vorlage.** Folgende Module kannst du 1:1 oder minimal angepasst übernehmen: | MVP-Datei | Für INSIGHT nutzen | |---|---| | `packages/core-service/src/core/auth/auth.service.ts` | Auth-Logik (Login, TOTP, SSO, Refresh) | | `packages/core-service/src/core/auth/auth.controller.ts` | Auth-Endpoints | | `packages/core-service/src/core/auth/strategies/jwt.strategy.ts` | JWT RS256 Passport-Strategy | | `packages/core-service/src/core/auth/totp.service.ts` | TOTP 2FA | | `packages/core-service/src/core/auth/sso/entra-id.service.ts` | MS Entra ID SSO + M365 OAuth2 | | `packages/core-service/src/core/auth/sso/sso.controller.ts` | SSO-Endpoints | | `packages/core-service/src/core/users/users.service.ts` | User-CRUD | | `packages/core-service/src/core/users/users.controller.ts` | User-Endpoints | | `packages/core-service/src/core/settings/settings.controller.ts` | External Links, Branding, Company, SSL, AI-Config | | `packages/core-service/src/redis/redis.service.ts` | Redis-Wrapper | | `packages/core-service/src/common/guards/` | JwtAuthGuard, RolesGuard, Public-Decorator | | `packages/frontend/src/auth/` | LoginPage, AuthContext, SsoCallbackPage | | `packages/frontend/src/admin/AdminLayout.tsx` | Admin-Navigation-Shell | | `packages/frontend/src/admin/Admin*.tsx` | Admin-Seiten (Users, SSO, Branding, etc.) | | `packages/frontend/src/hooks/` | useAuth, TanStack Query Hooks | | `packages/core-service/src/core/integrations/integrations.service.ts` | M365 Token speichern/laden/refreshen | | `packages/core-service/src/core/integrations/integrations.controller.ts` | Integrationen-Endpoints | | `packages/frontend/src/profile/ProfilePage.tsx` | Profil-Seite (nur Profil/Passwort/2FA/Integrationen Tabs) | **Anpassungen notwendig:** - **Kein Multi-Tenant**: INSIGHT ist single-tenant. Alle `tenant*`-Felder aus Prisma-Schema entfernen. - **RBAC erweitern**: `moduleRoles` als JSON-Feld statt `tenantRole` (siehe Schema unten). - **Neue Admin-Seiten**: User-Rollenmanagement (platformRole + moduleRoles), Modul-Registry. - **Dashboard**: Modulare Widget-Area statt MVP-Dashboard (M365 Calendar/Email). **Folgende MVP-Module werden NICHT übernommen (MVP-spezifisch):** | MVP-Feature | Grund | |---|---| | `ProfileAccessGroup` (Gruppen-basierter Profilzugriff) | Wird durch RBAC `moduleRoles.expertprofile` ersetzt | | `MasterData` (Departments, Locations, CostCenters, etc.) | MVP-spezifisch — kein Teil von INSIGHT | | `AdminProfileAccessPage`, `AdminMasterDataPage` | MVP-spezifisch — kein Teil von INSIGHT | **Folgende MVP-Features werden übernommen:** | Feature | Details | |---|---| | **Profil-Seite** | Tabs: Profil \| Experten Profil \| Passwort ändern \| 2FA \| Integrationen | | **"O365 übernehmen"** Button | Einmaliger manueller Import aller Felder aus M365 | | **Profil-Sync beim Login** | Automatisch: Name, Jobtitel, Department, Company aus M365 | | **Passwort ändern** | Eigenes Passwort mit Verifikation des aktuellen Passworts | | **2FA Tab** | TOTP aktivieren/deaktivieren (nur für lokale Auth-User) | | **Integrationen Tab** | Microsoft 365: verbinden, Status anzeigen, trennen | | **Experten Profil Tab** (eigenes) | User bearbeitet sein eigenes Experten Profil | | **ExpertProfile Datenmodell** | Skills, Sprachen, Erfahrung, Projekthistorie, Zertifikate, Anlagen | | **PDF / Word Export** | Eigenes Profil exportieren | > **Hintergrund Profil-Sync:** Wenn ein User sich via MS SSO einloggt, werden Name, JobTitle, > Department, CompanyName, OfficeLocation automatisch aus dem M365-Token aktualisiert. > Der "O365 übernehmen"-Button ermöglicht zusätzlich den manuellen Sync (Graph API `/me`). --- ## Experten Profile — Eigenständiger Menüpunkt **"Experten Profile" ist ein eigener Menüpunkt in der Hauptnavigation** (nicht nur ein persönlicher Tab). ### Was er leistet: | Aktion | Wer darf das | |---|---| | Eigenes Experten Profil bearbeiten | Jeder eingeloggte User (im Tab "Experten Profil" seiner Profil-Seite) | | Alle Profile auflisten und **ansehen** | `moduleRoles.expertprofile: "VIEWER"` oder höher | | Profile anderer User **bearbeiten** | `moduleRoles.expertprofile: "EDITOR"` oder höher | | Profile **exportieren** (PDF/Word) | `moduleRoles.expertprofile: "EDITOR"` oder höher | | Alles + Profil-Zuweisung verwalten | `moduleRoles.expertprofile: "ADMIN"` oder `platformRole: PLATFORM_ADMIN` | ### RBAC für Experten Profile: ``` moduleRoles.expertprofile: "ADMIN" → vollständiger Zugang, kann alle Profile bearbeiten + exportieren "EDITOR" → kann alle Profile bearbeiten + exportieren "VIEWER" → kann alle Profile nur ansehen (kein Export, kein Bearbeiten) (nicht gesetzt) → nur eigenes Profil über persönliche Profil-Seite ``` ### UI-Konzept: ``` Navigation Sidebar: └── 📋 Experten Profile (sichtbar für User mit expertprofile-Rolle) ├── Liste aller User mit Profilvorschau ├── Klick auf User → vollständiges Profil ansehen ├── [Bearbeiten]-Button (nur für EDITOR/ADMIN) └── [PDF Export] / [Word Export] (nur für EDITOR/ADMIN) ``` ### MVP-Referenzdateien für Experten Profile: | MVP-Datei | Für INSIGHT nutzen | |---|---| | `packages/core-service/src/core/expert-profile/expert-profile.service.ts` | Experten Profil CRUD | | `packages/core-service/src/core/expert-profile/expert-profile.controller.ts` | Endpoints | | `packages/core-service/src/core/expert-profile/profile-export.service.ts` | PDF/Word Export | | `packages/core-service/prisma/core.schema.prisma` (ExpertProfile-Modelle) | Datenmodell | | `packages/frontend/src/profile/ProfilePage.tsx` | Eigenes Profil + Experten-Profil-Tab | **Anpassung gegenüber MVP:** Im MVP war der Profilzugriff über `ProfileAccessGroup` geregelt (Gruppen mit `canView`, `canEdit`, `canExport`). In INSIGHT wird das durch `moduleRoles.expertprofile` ersetzt — einfacher, konsistent mit dem restlichen RBAC-System. --- ## Prisma Schema (insight_core) **Datei:** `packages/core-service/prisma/schema.prisma` ```prisma generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") directUrl = env("DATABASE_URL_DIRECT") } // -------------------------------------------------------- // User - Plattform-Benutzer // -------------------------------------------------------- model User { id String @id @default(uuid()) @db.Uuid email String @unique @db.VarChar(255) firstName String @map("first_name") @db.VarChar(100) lastName String @map("last_name") @db.VarChar(100) avatar String? @db.Text // Kontaktdaten phone String? @db.VarChar(30) mobile String? @db.VarChar(30) street String? @db.VarChar(200) postalCode String? @map("postal_code") @db.VarChar(10) city String? @db.VarChar(100) // Organisation jobTitle String? @map("job_title") @db.VarChar(100) department String? @db.VarChar(100) companyName String? @map("company_name") @db.VarChar(200) officeLocation String? @map("office_location") @db.VarChar(200) // Rollen platformRole String @default("USER") @map("platform_role") @db.VarChar(50) // "PLATFORM_ADMIN" oder "USER" // Modul-Rollen als JSON: { "crm": "MANAGER", "project": "VIEWER" } moduleRoles Json @default("{}") @map("module_roles") isActive Boolean @default(true) @map("is_active") // 2FA (nur für lokale Auth) twoFactorEnabled Boolean @default(false) @map("two_factor_enabled") // Login-Tracking lastLogin DateTime? @map("last_login") failedLoginAttempts Int @default(0) @map("failed_login_attempts") lastFailedLogin DateTime? @map("last_failed_login") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") authProvider AuthProvider[] integrations UserIntegration[] auditLogs AuditLog[] expertProfile ExpertProfile? @@map("users") } // -------------------------------------------------------- // AuthProvider - Lokale Auth, MS SSO // -------------------------------------------------------- model AuthProvider { id String @id @default(uuid()) @db.Uuid userId String @map("user_id") @db.Uuid provider String @db.VarChar(50) // LOCAL, MS_SSO providerId String? @map("provider_id") @db.VarChar(255) passwordHash String? @map("password_hash") @db.VarChar(255) totpSecret String? @map("totp_secret") @db.VarChar(255) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([userId, provider]) @@map("auth_providers") } // -------------------------------------------------------- // UserIntegration - Microsoft 365 OAuth2 Tokens pro User // -------------------------------------------------------- model UserIntegration { id String @id @default(uuid()) @db.Uuid userId String @map("user_id") @db.Uuid provider String @db.VarChar(50) // MICROSOFT_365 accessToken String @map("access_token") @db.Text // AES-256-GCM verschlüsselt refreshToken String @map("refresh_token") @db.Text expiresAt DateTime @map("expires_at") scopes String[] @default([]) msTenantId String? @map("ms_tenant_id") @db.VarChar(100) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([userId, provider]) @@map("user_integrations") } // -------------------------------------------------------- // Module - Registrierte INSIGHT-Module // -------------------------------------------------------- model Module { id String @id @default(uuid()) @db.Uuid key String @unique @db.VarChar(50) // "crm", "project" name String @db.VarChar(100) description String? @db.Text version String @default("1.0.0") @db.VarChar(20) isActive Boolean @default(true) @map("is_active") // Verfügbare Rollen für dieses Modul (JSON Array): ["ADMIN","MANAGER","USER","VIEWER"] availableRoles Json @default("[]") @map("available_roles") config Json @default("{}") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") @@map("modules") } // -------------------------------------------------------- // AuditLog // -------------------------------------------------------- model AuditLog { id String @id @default(uuid()) @db.Uuid userId String? @map("user_id") @db.Uuid action String @db.VarChar(100) entity String @db.VarChar(100) entityId String? @map("entity_id") @db.VarChar(255) details Json? ipAddress String? @map("ip_address") @db.VarChar(45) createdAt DateTime @default(now()) @map("created_at") user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([userId]) @@index([action]) @@index([createdAt]) @@map("audit_logs") } // -------------------------------------------------------- // ExpertProfile - Experten-Profil (1:1 mit User) // -------------------------------------------------------- model ExpertProfile { id String @id @default(uuid()) @db.Uuid userId String @unique @map("user_id") @db.Uuid skills String[] @default([]) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") user User @relation(fields: [userId], references: [id], onDelete: Cascade) experiences ExpertExperience[] languages ExpertLanguage[] projects ExpertProject[] certifications ExpertCertification[] attachments ExpertAttachment[] @@map("expert_profiles") } model ExpertExperience { id String @id @default(uuid()) @db.Uuid expertProfileId String @map("expert_profile_id") @db.Uuid area String @db.VarChar(200) years Int level String? @db.VarChar(50) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") expertProfile ExpertProfile @relation(fields: [expertProfileId], references: [id], onDelete: Cascade) @@map("expert_experiences") } model ExpertLanguage { id String @id @default(uuid()) @db.Uuid expertProfileId String @map("expert_profile_id") @db.Uuid language String @db.VarChar(100) level String @db.VarChar(20) // Muttersprache, C2, C1, B2, B1, A2, A1 createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") expertProfile ExpertProfile @relation(fields: [expertProfileId], references: [id], onDelete: Cascade) @@map("expert_languages") } model ExpertProject { id String @id @default(uuid()) @db.Uuid expertProfileId String @map("expert_profile_id") @db.Uuid fromMonth Int @map("from_month") fromYear Int @map("from_year") toMonth Int? @map("to_month") toYear Int? @map("to_year") isCurrent Boolean @default(false) @map("is_current") role String @db.VarChar(200) tasks String? @db.Text company String? @db.VarChar(200) companySize String? @map("company_size") @db.VarChar(20) industry String? @db.VarChar(200) createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") expertProfile ExpertProfile @relation(fields: [expertProfileId], references: [id], onDelete: Cascade) @@index([expertProfileId, fromYear, fromMonth]) @@map("expert_projects") } model ExpertCertification { id String @id @default(uuid()) @db.Uuid expertProfileId String @map("expert_profile_id") @db.Uuid title String @db.VarChar(300) issuingBody String @map("issuing_body") @db.VarChar(300) website String? @db.VarChar(500) issueYear Int @map("issue_year") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") expertProfile ExpertProfile @relation(fields: [expertProfileId], references: [id], onDelete: Cascade) @@map("expert_certifications") } model ExpertAttachment { id String @id @default(uuid()) @db.Uuid expertProfileId String @map("expert_profile_id") @db.Uuid filename String @db.VarChar(255) mimetype String @db.VarChar(100) size Int data String @db.Text // Base64 createdAt DateTime @default(now()) @map("created_at") expertProfile ExpertProfile @relation(fields: [expertProfileId], references: [id], onDelete: Cascade) @@map("expert_attachments") } ``` --- ## RBAC-Konzept ``` User.platformRole: PLATFORM_ADMIN → Zugang zum kompletten Admin-Bereich + alle Experten Profile USER → Normaler Nutzer User.moduleRoles (JSON-Objekt mit Rollen pro Modul): Experten Profile: { "expertprofile": "ADMIN" } → Alle Profile ansehen + bearbeiten + exportieren { "expertprofile": "EDITOR" } → Alle Profile ansehen + bearbeiten + exportieren { "expertprofile": "VIEWER" } → Alle Profile nur ansehen (kein Export, kein Bearbeiten) (nicht gesetzt) → Nur eigenes Profil über persönliche Profil-Seite CRM (Phase 3): { "crm": "ADMIN" } → Admin im CRM-Modul { "crm": "MANAGER" } → Manager im CRM-Modul { "crm": "USER" } → Normaler CRM-Nutzer { "crm": "VIEWER" } → Nur lesen (nicht gesetzt) → Kein Zugang zu CRM Kombiniert möglich: { "expertprofile": "EDITOR", "crm": "MANAGER" } ``` **Im JWT-Token** werden beide Felder mitgegeben: ```json { "sub": "uuid", "email": "user@firma.de", "platformRole": "USER", "moduleRoles": { "expertprofile": "EDITOR", "crm": "MANAGER" }, "jti": "uuid", "iat": 1234567890, "exp": 1234568790 } ``` **Guards:** - `JwtAuthGuard` — globaler Default (alle Routen geschützt) - `@Public()` — explizit für Login, SSO-Callback, Branding - `@Roles('PLATFORM_ADMIN')` + `RolesGuard` — für Admin-Endpoints - Module-Rollen: im jeweiligen Modul-Service prüfen (z.B. `user.moduleRoles.crm`) --- ## Implementierungsreihenfolge ### Backend (core-service) **1. Projekt aufsetzen:** ```bash cd packages/core-service npm init -y # NestJS CLI installieren und Projekt erstellen npm install -g @nestjs/cli nest new . --skip-git --package-manager npm ``` Abhängigkeiten (analog MVP): ```bash npm install @nestjs/passport @nestjs/jwt passport passport-jwt npm install @nestjs/config @nestjs/swagger npm install @prisma/client prisma npm install bcrypt uuid speakeasy qrcode npm install @azure/msal-node npm install ioredis cookie-parser npm install class-validator class-transformer npm install @types/bcrypt @types/uuid @types/passport-jwt @types/cookie-parser ``` **2. Prisma initialisieren:** ```bash npx prisma init --datasource-provider postgresql # schema.prisma aus diesem Briefing verwenden npx prisma migrate dev --name init ``` **3. Implementierungsreihenfolge Module:** 1. `redis/` — RedisService (aus MVP kopieren) 2. `prisma/` — PrismaService (aus MVP kopieren) 3. `common/` — Guards, Decorators, Pipes (aus MVP kopieren, `tenantRole` → `platformRole`) 4. `core/auth/` — AuthService, AuthController, JwtStrategy, TotpService (aus MVP, tenant-Felder entfernen) 5. `core/auth/sso/` — EntraIdService, SsoController (aus MVP 1:1 übernehmen) 6. `core/users/` — UsersService, UsersController (aus MVP, `moduleRoles` statt `tenantRole`) 7. `core/settings/` — SettingsController (aus MVP, External Links + Branding + Company + SSL + AI-Config) 8. `core/integrations/` — IntegrationsService, IntegrationsController (aus MVP, M365 OAuth2 Tokens) 9. `core/expert-profile/` — ExpertProfileService, ExpertProfileController, ProfileExportService (aus MVP) - Eigenes Profil bearbeiten: alle authentifizierten User - Fremde Profile lesen: `moduleRoles.expertprofile` = VIEWER/EDITOR/ADMIN - Fremde Profile bearbeiten: `moduleRoles.expertprofile` = EDITOR/ADMIN - Export: `moduleRoles.expertprofile` = EDITOR/ADMIN 10. `core/modules/` — NEU: ModuleRegistryService (Module aus DB laden, User-Rollen-Zuweisung) **4. JWT RS256 Keys generieren:** ```bash mkdir -p keys openssl genrsa -out keys/jwt-private.pem 4096 openssl rsa -in keys/jwt-private.pem -pubout -out keys/jwt-public.pem ``` Keys werden als Docker-Volume gemountet (nicht in Git!). **5. Seed (erster Admin-User):** ```typescript // prisma/seed.ts const admin = await prisma.user.create({ data: { email: 'admin@insight.local', firstName: 'Platform', lastName: 'Admin', platformRole: 'PLATFORM_ADMIN', moduleRoles: {}, isActive: true, authProvider: { create: { provider: 'LOCAL', passwordHash: await bcrypt.hash('ChangeMe123!', 12), }, }, }, }); ``` --- ### Frontend (frontend) **1. Projekt aufsetzen:** ```bash cd packages/frontend npm create vite@latest . -- --template react-ts npm install react-router-dom @tanstack/react-query axios npm install react-hot-toast @heroicons/react ``` **2. Implementierungsreihenfolge:** 1. `auth/AuthContext.tsx` — AuthContext + useAuth Hook + Silent Refresh (aus MVP) 2. `auth/LoginPage.tsx` — Login-Formular (lokal + SSO-Button) (aus MVP) 3. `auth/SsoCallbackPage.tsx` — SSO-Callback (aus MVP) 4. `shell/App.tsx` — Routing-Struktur (ohne CRM-Routen — die kommen Phase 3) 5. `shell/AppLayout.tsx` — Haupt-Layout (Sidebar + Topbar + Outlet) 6. `shell/DashboardPage.tsx` — Leeres Dashboard mit Widget-Platzhaltern 7. `profile/ProfilePage.tsx` — Eigenes Profil mit Tabs: Profil | Experten Profil | Passwort | 2FA | Integrationen (aus MVP) 8. `expert-profiles/ExpertProfilesPage.tsx` — **NEU**: Menüpunkt "Experten Profile" (Liste aller User-Profile, permission-gesteuert) 9. `expert-profiles/ExpertProfileDetailPage.tsx` — **NEU**: Einzelnes Profil ansehen + bearbeiten (EDITOR/ADMIN) + exportieren 10. `admin/AdminLayout.tsx` — Admin-Navigation (aus MVP) 11. `admin/AdminUsersPage.tsx` — User-Verwaltung + Rollen-Zuweisung (aus MVP erweitern) 12. `admin/AdminSsoPage.tsx` — SSO-Konfiguration (aus MVP 1:1) 13. `admin/AdminCustomizePage.tsx` — Branding (aus MVP 1:1) 14. `admin/AdminExternalLinksPage.tsx` — Externe Links (aus MVP 1:1) 15. `admin/AdminCompanyPage.tsx` — Firmendaten (aus MVP 1:1) 16. `admin/AdminSslPage.tsx` — SSL/Domain (aus MVP 1:1) 17. `admin/AdminAiSettingsPage.tsx` — KI-Einstellungen (aus MVP 1:1) 18. `admin/AdminModulesPage.tsx` — **NEU**: Modul-Registry (welche Module aktiv, Rollen-Konfiguration) **Navigation Sidebar — Hauptmenü:** ``` 🏠 Dashboard 📋 Experten Profile ← nur sichtbar wenn moduleRoles.expertprofile gesetzt ── Admin-Bereich ── ← nur sichtbar für PLATFORM_ADMIN ⚙️ Benutzer ⚙️ SSO / Sicherheit ⚙️ Erscheinungsbild ⚙️ Externe Links ⚙️ Firmendaten ⚙️ SSL / Domain ⚙️ KI-Einstellungen ⚙️ Module ``` **Wichtig für Admin-User-Management (NEU gegenüber MVP):** - User-Liste zeigt: Name, E-Mail, platformRole, moduleRoles - PLATFORM_ADMIN kann platformRole ändern - PLATFORM_ADMIN kann moduleRoles pro Modul zuweisen: - expertprofile: ADMIN / EDITOR / VIEWER / (kein Zugang) - crm: ADMIN / MANAGER / USER / VIEWER / (kein Zugang) ← Phase 3, schon vorbereiten --- ## Deployment auf APS01 ### Docker Compose (`docker-compose.yml` im Repo-Root) ```yaml services: core-service: build: ./packages/core-service restart: unless-stopped environment: DATABASE_URL: postgresql://insight_app:${DB_PASSWORD}@insight-dbs01.xinion.lan:6432/insight_core DATABASE_URL_DIRECT: postgresql://insight_app:${DB_PASSWORD}@insight-dbs01.xinion.lan:5432/insight_core REDIS_HOST: 172.20.10.20 REDIS_PORT: 6379 REDIS_PASSWORD: ${REDIS_PASSWORD} JWT_ISSUER: insight-platform JWT_ACCESS_TOKEN_EXPIRY: 15m JWT_REFRESH_TOKEN_EXPIRY: 7d JWT_PRIVATE_KEY_PATH: /app/keys/jwt-private.pem JWT_PUBLIC_KEY_PATH: /app/keys/jwt-public.pem FRONTEND_URL: http://insight-web01.xinion.lan NODE_ENV: production volumes: - ./keys:/app/keys:ro - traefik_certs:/app/traefik-certs - traefik_dynamic:/app/traefik-dynamic labels: - "traefik.enable=true" - "traefik.http.routers.core-api.rule=PathPrefix(`/api`)" - "traefik.http.services.core-api.loadbalancer.server.port=3000" networks: - insight frontend: build: ./packages/frontend restart: unless-stopped labels: - "traefik.enable=true" - "traefik.http.routers.frontend.rule=PathPrefix(`/`)" - "traefik.http.services.frontend.loadbalancer.server.port=80" networks: - insight traefik: image: traefik:v3 restart: unless-stopped command: - "--api.insecure=true" - "--providers.docker=true" - "--entrypoints.web.address=:80" ports: - "80:80" - "8080:8080" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro networks: - insight networks: insight: driver: bridge volumes: traefik_certs: traefik_dynamic: ``` ### Deployment-Kommando (von Mac aus): ```bash ssh -i .keys/deploy_platform_ed25519 deploy@172.20.10.21 \ "cd ~/insight-platform && git pull origin develop && \ docker compose build && docker compose up -d" ``` --- ## Sicherheitsregeln (nicht verhandelbar) - Kein `any` in TypeScript — strict mode immer - Kein `localStorage` für Tokens (Access: Memory, Refresh: HttpOnly Cookie) - Globaler `ValidationPipe` (whitelist + forbidNonWhitelisted) - `@Public()` explizit für öffentliche Routen - Rollen immer via `@Roles()` + `RolesGuard` - Kein raw SQL — ausschließlich Prisma - BCRYPT_COST 12 - JWT RS256 (nicht HS256!) - Tokens in Redis blocklistbar (JTI-basiert) --- ## Umgebungsvariablen (.env auf APS01) ```env DATABASE_URL=postgresql://insight_app:PASSWORT@insight-dbs01.xinion.lan:6432/insight_core DATABASE_URL_DIRECT=postgresql://insight_app:PASSWORT@insight-dbs01.xinion.lan:5432/insight_core REDIS_HOST=172.20.10.20 REDIS_PORT=6379 REDIS_PASSWORD=PASSWORT BCRYPT_COST=12 JWT_ISSUER=insight-platform JWT_ACCESS_TOKEN_EXPIRY=15m JWT_REFRESH_TOKEN_EXPIRY=7d NODE_ENV=production FRONTEND_URL=http://insight-web01.xinion.lan ``` Passwörter: siehe Confluence → [SSH Keys & Zugangsverwaltung](https://xinion.atlassian.net/wiki/x/UACVEw) --- ## INSIGHT-Shared — Kommunikations-Repo **Wir nutzen `INSIGHT-Shared` als zentrales Kommunikations-Repo aller Entwickler.** Nach jedem relevanten Fortschritt trägst du deinen Stand in `dev-status/platform.md` ein. Dort siehst du auch was Infra- und Apps-Entwickler gerade machen. ``` git@git.xinion.lan:Projekt-INSIGHT/INSIGHT-Shared.git ``` **Format:** ```markdown ## [Datum] — [Kurzbeschreibung] **Abgeschlossen:** - Was ist fertig? **Nächste Schritte:** - Was machst du als nächstes? **Blockiert / Brauche Input:** - Was fehlt dir von anderen Streams? **Bereit für andere Streams:** - Was kann ein anderer Entwickler jetzt nutzen? ``` **Wichtig für Apps-Entwickler (Phase 3):** Sobald folgendes fertig ist, sofort in `dev-status/platform.md` dokumentieren: - `InsightModule`-Interface stabil (aus `INSIGHT-Shared/contracts/module-interface.md`) - Auth-Guards und RBAC-Mechanismus beschrieben - `useAuth()` Hook API definiert - React Shell kann Module registrieren und rendern --- ## Confluence-Dokumentation | Seite | Link | |---|---| | Phase 2 — Plattform | https://xinion.atlassian.net/wiki/x/oYKSEw | | Berechtigungskonzept | https://xinion.atlassian.net/wiki/x/FQCVEw | | Phase 1 Status (Infra fertig) | https://xinion.atlassian.net/wiki/x/PACVEw | | Netzwerk & IPs | https://xinion.atlassian.net/wiki/x/FYCVEw | | Projekt-Übersicht | https://xinion.atlassian.net/wiki/spaces/ProjektINS/overview | --- ## Branching ``` main — stabil, nur via PR develop — Integration, hier arbeitest du feature/* — für größere Änderungen fix/* — Bugfixes ``` Commit-Format: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:` --- ## Server-Referenz | Server | IP | DNS | Funktion | |---|---|---|---| | DBS01 | 172.20.10.20 | insight-dbs01.xinion.lan | PostgreSQL:5432, PgBouncer:6432, Redis:6379 | | APS01 | 172.20.10.21 | insight-aps01.xinion.lan | Docker (core-service läuft hier) | | WEB01 | 172.20.10.22 | insight-web01.xinion.lan | Nginx (Frontend) | --- _Stand: 15.03.2026 | Phase 2 | Repo: git@git.xinion.lan:Projekt-INSIGHT/INSIGHT-Platform.git_