diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a9348f0 --- /dev/null +++ b/.env.example @@ -0,0 +1,19 @@ +# Server +PORT=8000 +NODE_ENV=development + +# Database (PostgreSQL) +DATABASE_URL=postgres://user:password@localhost:5432/pulse_crm + +# JWT +JWT_SECRET=your-super-secret-jwt-key-min-32-chars +JWT_ACCESS_EXPIRES=15m +JWT_REFRESH_EXPIRES=7d + +# Email (Resend) +RESEND_API_KEY=re_xxxxxxxxxxxxx +EMAIL_FROM=noreply@pulse-crm.de + +# App +APP_URL=https://crm.kronos-soulution.de +API_URL=https://api.crm.kronos-soulution.de diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d97575 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Environment +.env +.env.local +.env.*.local + +# Deno +.deno/ + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +logs/ + +# Build +dist/ diff --git a/README.md b/README.md index 05c4ef5..9aa47c4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,80 @@ -# pulse-crm-backend +# Pulse CRM Backend -Pulse CRM Backend - Deno + PostgreSQL \ No newline at end of file +πŸ«€ **Der Herzschlag deines Business** - Cloud-basiertes CRM fΓΌr den deutschen Markt. + +## Tech Stack + +- **Runtime:** Deno 2.x +- **Framework:** Oak +- **Database:** PostgreSQL 16 +- **Auth:** JWT + Argon2 +- **Hosting:** Hetzner Cloud (DSGVO-konform) + +## Quick Start + +```bash +# Dependencies cachen +deno cache src/main.ts + +# Development Server +deno task dev + +# Production +deno task start +``` + +## API Endpoints + +| Endpoint | Beschreibung | +|----------|--------------| +| `GET /health` | Health Check | +| `GET /api/v1` | API Info | +| `POST /api/v1/auth/login` | Login | +| `POST /api/v1/auth/register` | Registrierung | +| `GET /api/v1/contacts` | Kontakte auflisten | +| `GET /api/v1/deals` | Deals auflisten | +| ... | ... | + +## Projektstruktur + +``` +src/ +β”œβ”€β”€ main.ts # Entry Point +β”œβ”€β”€ routes/ # API Routes +β”‚ β”œβ”€β”€ auth.ts +β”‚ β”œβ”€β”€ contacts.ts +β”‚ β”œβ”€β”€ deals.ts +β”‚ └── ... +β”œβ”€β”€ middleware/ # Middleware +β”‚ β”œβ”€β”€ auth.ts +β”‚ β”œβ”€β”€ cors.ts +β”‚ └── rateLimit.ts +β”œβ”€β”€ services/ # Business Logic +β”œβ”€β”€ models/ # Type Definitions +β”œβ”€β”€ db/ # Database +β”‚ β”œβ”€β”€ client.ts +β”‚ β”œβ”€β”€ migrate.ts +β”‚ └── schema.ts +└── utils/ # Helpers +``` + +## Environment Variables + +Siehe `.env.example` + +## DSGVO + +- βœ… Hosting in Deutschland +- βœ… VerschlΓΌsselung (TLS + AES-256) +- βœ… Audit Logging +- βœ… Datenexport +- βœ… LΓΆschkonzept + +## Docs + +- [Architektur](docs/ARCHITECTURE.md) +- [Tech Stack](docs/TECH-STACK.md) + +--- + +Β© 2026 Pulse CRM - Made with πŸ’š in Germany diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..029f738 --- /dev/null +++ b/deno.json @@ -0,0 +1,22 @@ +{ + "name": "pulse-crm-backend", + "version": "0.1.0", + "tasks": { + "dev": "deno run --allow-net --allow-env --allow-read --watch src/main.ts", + "start": "deno run --allow-net --allow-env --allow-read src/main.ts", + "test": "deno test --allow-net --allow-env --allow-read", + "db:migrate": "deno run --allow-net --allow-env --allow-read src/db/migrate.ts", + "db:seed": "deno run --allow-net --allow-env --allow-read src/db/seed.ts" + }, + "imports": { + "@oak/oak": "jsr:@oak/oak@^17.0.0", + "@std/dotenv": "jsr:@std/dotenv@^0.225.0", + "postgres": "https://deno.land/x/postgres@v0.19.3/mod.ts", + "zod": "https://deno.land/x/zod@v3.22.4/mod.ts", + "argon2": "https://deno.land/x/argon2@v0.9.2/mod.ts", + "djwt": "https://deno.land/x/djwt@v3.0.2/mod.ts" + }, + "compilerOptions": { + "strict": true + } +} diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..a260295 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,103 @@ +# Pulse CRM - Architektur + +## Übersicht + +Pulse CRM ist eine cloud-basierte (SaaS) Customer Relationship Management LΓΆsung, entwickelt fΓΌr den deutschen Markt mit voller DSGVO-KonformitΓ€t. + +## Architektur-Diagramm + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ CLIENTS β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Web App β”‚ β”‚ Mobile β”‚ β”‚ API β”‚ β”‚ Webhooks β”‚ β”‚ +β”‚ β”‚ (Vue 3) β”‚ β”‚ PWA β”‚ β”‚ Client β”‚ β”‚ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ HTTPS + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ LOAD BALANCER β”‚ +β”‚ (nginx / Traefik) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ API GATEWAY β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Deno + Oak β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ Auth β”‚ β”‚ CORS β”‚ β”‚ Rate β”‚ β”‚ Logging β”‚ β”‚ β”‚ +β”‚ β”‚ β”‚Middlewareβ”‚ β”‚Middlewareβ”‚ β”‚ Limiter β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ API ROUTES β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ /auth β”‚ β”‚/contactsβ”‚ β”‚ /deals β”‚ β”‚/pipelineβ”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚/company β”‚ β”‚/activityβ”‚ β”‚ /users β”‚ β”‚/settingsβ”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ SERVICE LAYER β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ContactServiceβ”‚ β”‚ DealService β”‚ β”‚ UserService β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ AuthService β”‚ β”‚ EmailService β”‚ β”‚ AuditService β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ DATA LAYER β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ PostgreSQL β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚ users β”‚ β”‚contacts β”‚ β”‚ deals β”‚ β”‚activitiesβ”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ +β”‚ β”‚ β”‚companiesβ”‚ β”‚pipelinesβ”‚ β”‚ orgs β”‚ β”‚audit_logβ”‚ β”‚ β”‚ +β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +## Entscheidungen + +### Cloud vs. Client + +**Entscheidung: Cloud-basiert (SaaS)** + +| Kriterium | Cloud | Client | +|-----------|-------|--------| +| Installation | Keine | Erforderlich | +| Updates | Automatisch | Manuell | +| Wartung | Zentral | Pro Installation | +| Zugriff | Überall | Nur lokal | +| DSGVO | EU-Hosting | Kunde verantwortlich | +| Skalierung | Einfach | Komplex | +| Time-to-Market | Schnell | Langsam | + +### Multi-Tenancy + +**Shared Database, Separate Schemas** - Jeder Kunde (Organization) hat: +- Eigene `org_id` in allen Tabellen +- Row-Level Security in PostgreSQL +- Isolierte Daten, geteilte Infrastruktur + +## Hosting (DSGVO-konform) + +- **Provider:** Hetzner Cloud +- **Standort:** Falkenstein/NΓΌrnberg, Deutschland πŸ‡©πŸ‡ͺ +- **Backup:** TΓ€glich, 30 Tage Retention +- **VerschlΓΌsselung:** TLS 1.3 (Transit), AES-256 (Rest) diff --git a/docs/TECH-STACK.md b/docs/TECH-STACK.md new file mode 100644 index 0000000..0d7cee3 --- /dev/null +++ b/docs/TECH-STACK.md @@ -0,0 +1,121 @@ +# Pulse CRM - Tech Stack + +## Backend + +| Komponente | Technologie | Version | BegrΓΌndung | +|------------|-------------|---------|------------| +| Runtime | **Deno** | 2.x | Sicher by default, TypeScript nativ, moderne APIs | +| Framework | **Oak** | 17.x | Express-Γ€hnlich, bewΓ€hrt fΓΌr Deno | +| Datenbank | **PostgreSQL** | 16.x | ACID, JSON-Support, Row-Level Security | +| ORM | **Drizzle ORM** | Latest | Type-safe, leichtgewichtig, gute DX | +| Auth | **JWT** | - | Stateless, skalierbar | +| Hashing | **Argon2** | - | Sicherster Passwort-Hash-Algorithmus | +| Validation | **Zod** | 3.x | Runtime type validation | +| Email | **Resend** | - | Moderne E-Mail API | + +## Frontend + +| Komponente | Technologie | Version | BegrΓΌndung | +|------------|-------------|---------|------------| +| Framework | **Vue 3** | 3.5.x | Composition API, TypeScript, reaktiv | +| UI Library | **PrimeVue** | 4.x | Enterprise-ready, umfangreich | +| State | **Pinia** | 2.x | Offizieller Vue Store | +| Router | **Vue Router** | 4.x | SPA Navigation | +| HTTP | **Axios** | 1.x | HTTP Client | +| Build | **Vite** | 5.x | Schnell, HMR, optimiert | +| CSS | **TailwindCSS** | 3.x | Utility-first | +| i18n | **vue-i18n** | 9.x | Mehrsprachigkeit (DE/EN) | + +## Infrastruktur + +| Komponente | Technologie | BegrΓΌndung | +|------------|-------------|------------| +| Hosting | **Hetzner Cloud** | DSGVO, Deutschland, gΓΌnstig | +| Container | **Docker** | PortabilitΓ€t, Reproduzierbarkeit | +| Reverse Proxy | **nginx** | Performance, SSL Termination | +| SSL | **Let's Encrypt** | Kostenlose Zertifikate | +| CI/CD | **Gitea Actions** | Self-hosted, integriert | +| Monitoring | **Prometheus + Grafana** | Open Source, bewΓ€hrt | + +## Datenbank Schema (Übersicht) + +```sql +-- Multi-Tenant Core +organizations (id, name, settings, created_at) +users (id, org_id, email, password_hash, role, ...) + +-- CRM Core +contacts (id, org_id, first_name, last_name, email, phone, company_id, ...) +companies (id, org_id, name, industry, website, ...) +deals (id, org_id, title, value, stage_id, contact_id, owner_id, ...) +pipelines (id, org_id, name, stages JSONB, ...) + +-- Activities +activities (id, org_id, type, contact_id, deal_id, note, due_at, ...) + +-- System +audit_logs (id, org_id, user_id, action, entity, entity_id, changes, ...) +``` + +## API Design + +### RESTful Conventions + +``` +GET /api/v1/contacts # Liste +GET /api/v1/contacts/:id # Detail +POST /api/v1/contacts # Erstellen +PUT /api/v1/contacts/:id # Update +DELETE /api/v1/contacts/:id # LΓΆschen + +# Nested Resources +GET /api/v1/contacts/:id/activities +POST /api/v1/deals/:id/move # Custom Action +``` + +### Response Format + +```json +{ + "success": true, + "data": { ... }, + "meta": { + "page": 1, + "limit": 20, + "total": 150 + } +} +``` + +### Error Format + +```json +{ + "success": false, + "error": { + "code": "VALIDATION_ERROR", + "message": "Invalid email format", + "details": [...] + } +} +``` + +## Security + +- **Authentication:** JWT (Access Token 15min, Refresh Token 7d) +- **Password:** Argon2id, min 8 chars +- **Rate Limiting:** 100 req/min per IP, 1000 req/min per User +- **CORS:** Whitelist allowed origins +- **Input Validation:** Zod schemas for all inputs +- **SQL Injection:** Parameterized queries via ORM +- **XSS:** Content-Security-Policy headers +- **HTTPS:** Enforced, HSTS enabled + +## DSGVO Compliance + +- βœ… Hosting in Deutschland (Hetzner) +- βœ… VerschlΓΌsselung (Transit + Rest) +- βœ… Audit Logging +- βœ… Datenexport (Art. 20) +- βœ… LΓΆschkonzept (Art. 17) +- βœ… AVV-Vorlage fΓΌr Kunden diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..d69c62e --- /dev/null +++ b/src/main.ts @@ -0,0 +1,81 @@ +import { Application } from "@oak/oak"; +import "@std/dotenv/load"; + +const app = new Application(); +const PORT = parseInt(Deno.env.get("PORT") || "8000"); + +// CORS Middleware +app.use(async (ctx, next) => { + ctx.response.headers.set("Access-Control-Allow-Origin", "*"); + ctx.response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); + ctx.response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization"); + + if (ctx.request.method === "OPTIONS") { + ctx.response.status = 204; + return; + } + + await next(); +}); + +// Logger Middleware +app.use(async (ctx, next) => { + const start = Date.now(); + await next(); + const ms = Date.now() - start; + console.log(`${ctx.request.method} ${ctx.request.url.pathname} - ${ctx.response.status} (${ms}ms)`); +}); + +// Health Check +app.use(async (ctx, next) => { + if (ctx.request.url.pathname === "/health") { + ctx.response.body = { + status: "ok", + service: "pulse-crm-backend", + version: "0.1.0", + timestamp: new Date().toISOString() + }; + return; + } + await next(); +}); + +// API Info +app.use(async (ctx, next) => { + if (ctx.request.url.pathname === "/api" || ctx.request.url.pathname === "/api/v1") { + ctx.response.body = { + name: "Pulse CRM API", + version: "1.0.0", + docs: "/api/v1/docs", + endpoints: { + auth: "/api/v1/auth/*", + contacts: "/api/v1/contacts/*", + companies: "/api/v1/companies/*", + deals: "/api/v1/deals/*", + pipelines: "/api/v1/pipelines/*", + activities: "/api/v1/activities/*", + users: "/api/v1/users/*" + } + }; + return; + } + await next(); +}); + +// 404 Handler +app.use((ctx) => { + ctx.response.status = 404; + ctx.response.body = { + success: false, + error: { + code: "NOT_FOUND", + message: "Endpoint not found" + } + }; +}); + +console.log(`πŸš€ Pulse CRM Backend starting on port ${PORT}...`); +console.log(`πŸ“š API Docs: http://localhost:${PORT}/api/v1`); +console.log(`❀️ Health: http://localhost:${PORT}/health`); + +await app.listen({ port: PORT });