feat: Architektur & Tech-Stack Dokumentation
📐 Architektur: - Cloud-basiert (SaaS) Entscheidung - Multi-Tenancy Konzept - Architektur-Diagramm 🛠️ Tech-Stack: - Deno + Oak Backend - PostgreSQL Datenbank - Vue 3 + PrimeVue Frontend - Hetzner Hosting (DSGVO) 📁 Projektstruktur: - src/ mit routes, middleware, services - docs/ mit Architektur-Doku - Basis main.ts mit Health Check
This commit is contained in:
19
.env.example
Normal file
19
.env.example
Normal file
@@ -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
|
||||||
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@@ -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/
|
||||||
81
README.md
81
README.md
@@ -1,3 +1,80 @@
|
|||||||
# pulse-crm-backend
|
# Pulse CRM Backend
|
||||||
|
|
||||||
Pulse CRM Backend - Deno + PostgreSQL
|
🫀 **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
|
||||||
|
|||||||
22
deno.json
Normal file
22
deno.json
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
103
docs/ARCHITECTURE.md
Normal file
103
docs/ARCHITECTURE.md
Normal file
@@ -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)
|
||||||
121
docs/TECH-STACK.md
Normal file
121
docs/TECH-STACK.md
Normal file
@@ -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
|
||||||
81
src/main.ts
Normal file
81
src/main.ts
Normal file
@@ -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 });
|
||||||
Reference in New Issue
Block a user