INSIGHT-MVP/repos/INSIGHT-Platform/BRIEFING.md

27 KiB

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

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:

{
  "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:

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):

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:

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, tenantRoleplatformRole)
  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:

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):

// 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:

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.tsxNEU: Menüpunkt "Experten Profile" (Liste aller User-Profile, permission-gesteuert)
  9. expert-profiles/ExpertProfileDetailPage.tsxNEU: 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.tsxNEU: 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)

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):

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)

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


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:

## [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