From 094db465cb480376ef3f3d7bdb07d320bca5854e Mon Sep 17 00:00:00 2001 From: Thomas Reitz Date: Tue, 10 Mar 2026 16:03:59 +0100 Subject: [PATCH] feat(crm): add standalone dev environment and test token generator - docker-compose.crm-dev.yml: isolierte Testumgebung (PostgreSQL + Redis + CRM) - scripts/generate-token.ts: generiert Test-JWTs fuer curl-basiertes API-Testing - scripts/init-schema.sql: erstellt app_crm Schema beim DB-Start - keys/: Test-RSA-Schluessel (nur fuer lokale Entwicklung!) - Fix: multiSchema previewFeature entfernt (deprecated) Co-Authored-By: Claude Opus 4.6 --- .../crm-service/docker-compose.crm-dev.yml | 70 ++++++++++++++ packages/crm-service/keys/jwt-private.pem | 27 ++++++ packages/crm-service/keys/jwt-public.pem | 9 ++ packages/crm-service/prisma/crm.schema.prisma | 5 +- .../crm-service/scripts/generate-token.ts | 93 +++++++++++++++++++ packages/crm-service/scripts/init-schema.sql | 2 + 6 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 packages/crm-service/docker-compose.crm-dev.yml create mode 100644 packages/crm-service/keys/jwt-private.pem create mode 100644 packages/crm-service/keys/jwt-public.pem create mode 100644 packages/crm-service/scripts/generate-token.ts create mode 100644 packages/crm-service/scripts/init-schema.sql diff --git a/packages/crm-service/docker-compose.crm-dev.yml b/packages/crm-service/docker-compose.crm-dev.yml new file mode 100644 index 0000000..7e4730d --- /dev/null +++ b/packages/crm-service/docker-compose.crm-dev.yml @@ -0,0 +1,70 @@ +# ============================================================ +# CRM-Service - Standalone Entwicklungsumgebung +# ============================================================ +# Startet PostgreSQL + Redis + CRM isoliert (ohne Core/Frontend) +# +# Starten: docker compose -f docker-compose.crm-dev.yml up -d +# Stoppen: docker compose -f docker-compose.crm-dev.yml down +# Logs: docker compose -f docker-compose.crm-dev.yml logs -f crm +# Reset DB: docker compose -f docker-compose.crm-dev.yml down -v + +services: + crm-postgres: + image: postgres:16-alpine + container_name: crm-dev-postgres + environment: + POSTGRES_USER: insight + POSTGRES_PASSWORD: devpassword + POSTGRES_DB: platform_core + ports: + - "15432:5432" + volumes: + - crm-pg-data:/var/lib/postgresql/data + - ./scripts/init-schema.sql:/docker-entrypoint-initdb.d/01-init-schema.sql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U insight -d platform_core"] + interval: 5s + timeout: 3s + retries: 5 + + crm-redis: + image: redis:7-alpine + container_name: crm-dev-redis + ports: + - "16379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 5 + + crm: + build: + context: . + dockerfile: Dockerfile + target: development + container_name: crm-dev-service + environment: + - NODE_ENV=development + - APP_PORT=3100 + - DATABASE_URL=postgresql://insight:devpassword@crm-postgres:5432/platform_core?schema=app_crm + - DATABASE_URL_DIRECT=postgresql://insight:devpassword@crm-postgres:5432/platform_core?schema=app_crm + - REDIS_HOST=crm-redis + - REDIS_PORT=6379 + - JWT_PUBLIC_KEY_PATH=/app/keys/jwt-public.pem + - JWT_ISSUER=insight-platform + - CORS_ORIGINS=http://localhost:3100,http://127.0.0.1:3100 + volumes: + - .:/app + - /app/node_modules + - ./keys/jwt-public.pem:/app/keys/jwt-public.pem:ro + ports: + - "3100:3100" + depends_on: + crm-postgres: + condition: service_healthy + crm-redis: + condition: service_healthy + +volumes: + crm-pg-data: diff --git a/packages/crm-service/keys/jwt-private.pem b/packages/crm-service/keys/jwt-private.pem new file mode 100644 index 0000000..ea6aac5 --- /dev/null +++ b/packages/crm-service/keys/jwt-private.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAwyZKQNL8oGoKlRyZzNtHXiLgDE8Zw+7Ll8XUsgI93CZBPEbL +UCh6C2AZOaar5YdfgD6acX5PQxHnn28x8cAhLDhXcutX+riyJ0Mq+yNnuJrAt645 +Yp1pbDRseePm+Z6OnCbKnULvfqautWBRfBDFmAPfF91/tnQPb6CtC7iUVjNpBuK0 +eX5UnbSxI5IgDk8tLyG/j8IufVQnNRN7t9oSpvS0wKQpKjMX4hGcESd/zyO0gnam +47mT8v2x7noS6NGZncB2RSmajMvvYx/mZxLk7kf1MRiLUlM+BOUw/hh7Sod0gOLa +S9iLhe+sgu54Tc9cgU2+QY/9EHMqUiT7jxeuewIDAQABAoIBAA+iB5y9yiMHm6Vq +Tx/MCSGPZadWxtedT43u+STxbQcvwVYUzcC0HWK/5gVqRqbye0IIwdKJrcvPqWBq +RfP50i56rPa1x6x8Ezl19gF8SpjNPNb6C/rMZV+Xq4DwMtaYTxBPQzfp3smkjKZW ++vAXX9zXoo2E3vA8x+fHVVV1Cd0FyUvcVdeLT16eEqsOfS347KfUDCQ5Nv63xpj9 +dGXVOOmUwWY8pCmW+mz15q7Y1stegCV/XMv7vuxyhFpD1W29NyLvJ8pzKhQxN+xS +/ivE0L+pq+PeQ9/4MERt6q5xp5TQy0lj4kxjnvv+5FkZj2s4KVUr5F0b6vkOhvip +4P9rV6ECgYEA9zxVtI7SBNsRcIo7gRwdy+L0kz6qYWRUpZ+hc8OnZjvtw8BcfzwO +XnlGFUTSfMaPwzJc7Ny/uKHpCovnszUzzgJ0FMv+mLKLU8Noaay2lFRc9zrGef2S ++2bdjTZYjRLt1S7nczowuGesvMJqic30ic0t7xgUZ4AhWCVSgue9pcMCgYEAyhFG +BE+mzkCRcLh9U4mkbXOKsy3SHBhgFpAUPjY9dlnIFpjOITDLS07Wd3NQ7u72qQw/ +dDtq17LsoUzpkzcMDVSUUWawfdYwza0IaGfnprECtk38pj0MtRSDJfT8AQQvzdSC +ZAhMFdfa9ZqVQ/1jbU+2aJ2esFXUZ20Mg7VH8OkCgYEAr/pKJtLhuoMTzr8Vy7hv +nQhWfdhE/j2j4p/VE8lYBfTyMDtjm0zsDWLU956dFCNhgNcAPbiC3rCgZ9ldermL +vj8Q0RzCg33SnjSgxVciPkIuSeuUCpDrZfa6DCF6ti+bCfrw05u/wgJJebIIkz39 +qXhaEa1aGLGjClLGgFbjLnECgYBAdvpLbc3dhyPfPjxdZlAayv245Clf5Tbie9y5 +bDx3gXUgIGfClvqEcAZj3Vo4n+v5SnsD7eDMJ7zuSMdLvAgQSKd4wLYVrzuqokVI +ab2xpE8lMgQkVN2093JPrbSn7loB5IYku7DqVw73w/VS14fc281p2r9BqmA1Dskr +S65D4QKBgHOpjCDmRqzrl+EDOjRZinXHWZqm/lX5pBFBxtTGYstbg7gY4D6nLhsR +mCbYQr7WOf+ouQYkg5ck82K/0Gu46rjkpBcIdbnW1wC9iE1iJxd5u96iEcJNu2oM +H5gx73pkmO4rVnInv67VYeDrGHGd/aix6b5nSDaYg5Ct1yENlzCD +-----END RSA PRIVATE KEY----- diff --git a/packages/crm-service/keys/jwt-public.pem b/packages/crm-service/keys/jwt-public.pem new file mode 100644 index 0000000..bc50a57 --- /dev/null +++ b/packages/crm-service/keys/jwt-public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwyZKQNL8oGoKlRyZzNtH +XiLgDE8Zw+7Ll8XUsgI93CZBPEbLUCh6C2AZOaar5YdfgD6acX5PQxHnn28x8cAh +LDhXcutX+riyJ0Mq+yNnuJrAt645Yp1pbDRseePm+Z6OnCbKnULvfqautWBRfBDF +mAPfF91/tnQPb6CtC7iUVjNpBuK0eX5UnbSxI5IgDk8tLyG/j8IufVQnNRN7t9oS +pvS0wKQpKjMX4hGcESd/zyO0gnam47mT8v2x7noS6NGZncB2RSmajMvvYx/mZxLk +7kf1MRiLUlM+BOUw/hh7Sod0gOLaS9iLhe+sgu54Tc9cgU2+QY/9EHMqUiT7jxeu +ewIDAQAB +-----END PUBLIC KEY----- diff --git a/packages/crm-service/prisma/crm.schema.prisma b/packages/crm-service/prisma/crm.schema.prisma index 175fcba..d734fb6 100644 --- a/packages/crm-service/prisma/crm.schema.prisma +++ b/packages/crm-service/prisma/crm.schema.prisma @@ -6,9 +6,8 @@ // ============================================================ generator client { - provider = "prisma-client-js" - output = "../node_modules/.prisma/crm-client" - previewFeatures = ["multiSchema"] + provider = "prisma-client-js" + output = "../node_modules/.prisma/crm-client" } datasource db { diff --git a/packages/crm-service/scripts/generate-token.ts b/packages/crm-service/scripts/generate-token.ts new file mode 100644 index 0000000..3ef3ed8 --- /dev/null +++ b/packages/crm-service/scripts/generate-token.ts @@ -0,0 +1,93 @@ +/** + * Test-Token-Generator fuer CRM-Service Entwicklung. + * + * Generiert ein gueltiges JWT mit konfigurierbaren Claims. + * + * Verwendung: + * npx ts-node scripts/generate-token.ts + * npx ts-node scripts/generate-token.ts --role TENANT_ADMIN + * npx ts-node scripts/generate-token.ts --expiry 60 (60 Minuten) + */ + +import * as crypto from 'crypto'; +import * as fs from 'fs'; +import * as path from 'path'; + +function base64url(data: Buffer | string): string { + const buf = typeof data === 'string' ? Buffer.from(data) : data; + return buf.toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); +} + +function generateToken(options: { + role?: string; + tenantId?: string; + tenantSlug?: string; + userId?: string; + email?: string; + expiryMinutes?: number; +}): string { + const privateKeyPath = path.join(__dirname, '..', 'keys', 'jwt-private.pem'); + const privateKey = fs.readFileSync(privateKeyPath, 'utf8'); + + const now = Math.floor(Date.now() / 1000); + const expiryMinutes = options.expiryMinutes ?? 60; + + const header = { + alg: 'RS256', + typ: 'JWT', + }; + + const payload = { + sub: options.userId ?? 'test-user-00000000-0000-0000-0000-000000000001', + email: options.email ?? 'testuser@insight.local', + role: options.role ?? 'TENANT_ADMIN', + tenantId: options.tenantId ?? 'test-tenant-00000000-0000-0000-0000-000000000001', + tenantSlug: options.tenantSlug ?? 'test_tenant', + jti: crypto.randomUUID(), + iss: 'insight-platform', + iat: now, + exp: now + expiryMinutes * 60, + }; + + const headerB64 = base64url(JSON.stringify(header)); + const payloadB64 = base64url(JSON.stringify(payload)); + const signingInput = `${headerB64}.${payloadB64}`; + + const sign = crypto.createSign('RSA-SHA256'); + sign.update(signingInput); + const signature = sign.sign(privateKey); + const signatureB64 = base64url(signature); + + return `${signingInput}.${signatureB64}`; +} + +// CLI args parsen +const args = process.argv.slice(2); +const getArg = (name: string): string | undefined => { + const idx = args.indexOf(`--${name}`); + return idx >= 0 && idx + 1 < args.length ? args[idx + 1] : undefined; +}; + +const token = generateToken({ + role: getArg('role'), + tenantId: getArg('tenantId'), + tenantSlug: getArg('tenantSlug'), + userId: getArg('userId'), + email: getArg('email'), + expiryMinutes: getArg('expiry') ? parseInt(getArg('expiry')!, 10) : undefined, +}); + +console.log('\n=== INSIGHT CRM Test-Token ===\n'); +console.log('Token:'); +console.log(token); +console.log('\n--- Verwendung mit curl ---\n'); +console.log(`export CRM_TOKEN="${token}"\n`); +console.log('# Kontakte auflisten:'); +console.log('curl -s http://localhost:3100/api/v1/crm/contacts -H "Authorization: Bearer $CRM_TOKEN" | jq\n'); +console.log('# Kontakt erstellen:'); +console.log(`curl -s -X POST http://localhost:3100/api/v1/crm/contacts \\ + -H "Authorization: Bearer $CRM_TOKEN" \\ + -H "Content-Type: application/json" \\ + -d '{"type":"PERSON","firstName":"Max","lastName":"Mustermann","email":"max@firma.de","companyName":"Firma GmbH"}' | jq\n`); +console.log('# Health-Check (kein Token noetig):'); +console.log('curl -s http://localhost:3100/health | jq\n'); diff --git a/packages/crm-service/scripts/init-schema.sql b/packages/crm-service/scripts/init-schema.sql new file mode 100644 index 0000000..f6f469f --- /dev/null +++ b/packages/crm-service/scripts/init-schema.sql @@ -0,0 +1,2 @@ +-- Erstellt das app_crm Schema fuer die CRM-Tabellen +CREATE SCHEMA IF NOT EXISTS app_crm;