20 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.lanfür Deployments - Nginx auf
insight-web01.xinion.lanfür das Frontend
Was ist bereits fertig
- Infra: PostgreSQL, PgBouncer, Redis, Docker, Nginx — alles läuft ✅
- Datenbank
insight_coreexistiert mit Userinsight_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:
moduleRolesals JSON-Feld statttenantRole(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 |
|---|---|
ExpertProfile (Skills, Erfahrung, Sprachen) |
IT-Berater-Profil für MVP — kein Teil von INSIGHT |
ExpertProject (Projekthistorie) |
IT-Berater-Profil für MVP — kein Teil von INSIGHT |
ExpertCertification (Zertifikate) |
IT-Berater-Profil für MVP — kein Teil von INSIGHT |
ExpertAttachment (PDF/Word Export) |
IT-Berater-Profil für MVP — kein Teil von INSIGHT |
ProfileAccessGroup (Profilzugriff) |
IT-Berater-spezifisch — kein Teil von INSIGHT |
MasterData (Departments, Locations, etc.) |
MVP-spezifisch — kein Teil von INSIGHT |
| Tab "Experten Profil" im Profil | IT-Berater-Feature — kein Teil von INSIGHT |
AdminProfileAccessPage, AdminMasterDataPage |
MVP-spezifisch — kein Teil von INSIGHT |
Folgende MVP-Features werden übernommen (inkl. Profil-Seite):
| Feature | Details |
|---|---|
| Profil-Seite | Name, Avatar, Kontakt, Adresse, Organisation (4 Felder) |
| "O365 übernehmen" Button | Einmaliger manueller Import aller Felder aus M365 |
| Profil-Sync beim Login | Automatisch: Name, Jobitel, Department, Company aus M365 aktualisieren |
| 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 |
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). Das setzt voraus, dass MS SSO konfiguriert ist — ohne SSO nur lokales Profil.
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[]
@@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")
}
RBAC-Konzept
User.platformRole:
PLATFORM_ADMIN → Zugang zum kompletten Admin-Bereich
USER → Normaler Nutzer
User.moduleRoles (JSON):
{ "crm": "ADMIN" } → Admin im CRM-Modul
{ "crm": "MANAGER" } → Manager im CRM-Modul
{ "crm": "USER" } → Normaler CRM-Nutzer
{ "crm": "VIEWER" } → Nur lesen
{} → Kein Zugang zu CRM
Im JWT-Token werden beide Felder mitgegeben:
{
"sub": "uuid",
"email": "user@firma.de",
"platformRole": "USER",
"moduleRoles": { "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:
redis/— RedisService (aus MVP kopieren)prisma/— PrismaService (aus MVP kopieren)common/— Guards, Decorators, Pipes (aus MVP kopieren,tenantRole→platformRole)core/auth/— AuthService, AuthController, JwtStrategy, TotpService (aus MVP, tenant-Felder entfernen)core/auth/sso/— EntraIdService, SsoController (aus MVP 1:1 übernehmen)core/users/— UsersService, UsersController (aus MVP,moduleRolesstatttenantRole)core/settings/— SettingsController (aus MVP, External Links + Branding + Company + SSL + AI-Config)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:
auth/AuthContext.tsx— AuthContext + useAuth Hook + Silent Refresh (aus MVP)auth/LoginPage.tsx— Login-Formular (lokal + SSO-Button) (aus MVP)auth/SsoCallbackPage.tsx— SSO-Callback (aus MVP)shell/App.tsx— Routing-Struktur (ohne CRM-Routen — die kommen Phase 3)shell/AppLayout.tsx— Haupt-Layout (Sidebar + Topbar + Outlet)shell/DashboardPage.tsx— Leeres Dashboard mit Widget-Platzhalternprofile/ProfilePage.tsx— Eigenes Profil (aus MVP)admin/AdminLayout.tsx— Admin-Navigation (aus MVP, CRM-Settings entfernen)admin/AdminUsersPage.tsx— User-Verwaltung + Rollen-Zuweisung (aus MVP erweitern)admin/AdminSsoPage.tsx— SSO-Konfiguration (aus MVP 1:1)admin/AdminCustomizePage.tsx— Branding (aus MVP 1:1)admin/AdminExternalLinksPage.tsx— Externe Links (aus MVP 1:1)admin/AdminCompanyPage.tsx— Firmendaten (aus MVP 1:1)admin/AdminSslPage.tsx— SSL/Domain (aus MVP 1:1)admin/AdminAiSettingsPage.tsx— KI-Einstellungen (aus MVP 1:1)admin/AdminModulesPage.tsx— NEU: Modul-Registry (welche Module aktiv)
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 (Dropdown: ADMIN/MANAGER/USER/VIEWER/Kein Zugang)
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
anyin TypeScript — strict mode immer - Kein
localStoragefü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 (ausINSIGHT-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