import { Application } from "@oak/oak"; import "@std/dotenv/load"; // Routes import { authRouter } from "./routes/auth.ts"; import { usersRouter } from "./routes/users.ts"; import inboxRouter from "./routes/inbox.ts"; import { contactsRouter } from "./routes/contacts.ts"; import { companiesRouter } from "./routes/companies.ts"; import { dealsRouter } from "./routes/deals.ts"; import { activitiesRouter } from "./routes/activities.ts"; import { pipelinesRouter } from "./routes/pipelines.ts"; // Database import { checkHealth as checkDbHealth } from "./db/connection.ts"; const app = new Application(); const PORT = parseInt(Deno.env.get("PORT") || "8000"); const NODE_ENV = Deno.env.get("NODE_ENV") || "development"; // ============================================ // MIDDLEWARE // ============================================ // CORS Middleware app.use(async (ctx, next) => { const allowedOrigins = Deno.env.get("CORS_ORIGINS")?.split(",") || ["*"]; const origin = ctx.request.headers.get("origin"); if (origin && (allowedOrigins.includes("*") || allowedOrigins.includes(origin))) { ctx.response.headers.set("Access-Control-Allow-Origin", origin); } else if (allowedOrigins.includes("*")) { ctx.response.headers.set("Access-Control-Allow-Origin", "*"); } ctx.response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH"); ctx.response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization"); ctx.response.headers.set("Access-Control-Allow-Credentials", "true"); ctx.response.headers.set("Access-Control-Max-Age", "86400"); if (ctx.request.method === "OPTIONS") { ctx.response.status = 204; return; } await next(); }); // Security Headers app.use(async (ctx, next) => { ctx.response.headers.set("X-Content-Type-Options", "nosniff"); ctx.response.headers.set("X-Frame-Options", "DENY"); ctx.response.headers.set("X-XSS-Protection", "1; mode=block"); ctx.response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin"); await next(); }); // Request Logger app.use(async (ctx, next) => { const start = Date.now(); await next(); const ms = Date.now() - start; const status = ctx.response.status; const statusColor = status >= 500 ? "\x1b[31m" : status >= 400 ? "\x1b[33m" : "\x1b[32m"; console.log(`${statusColor}${status}\x1b[0m ${ctx.request.method} ${ctx.request.url.pathname} - ${ms}ms`); }); // Error Handler app.use(async (ctx, next) => { try { await next(); } catch (err) { console.error("Error:", err); const status = err.status || 500; ctx.response.status = status; ctx.response.body = { success: false, error: { code: status === 500 ? "INTERNAL_ERROR" : "ERROR", message: NODE_ENV === "development" ? err.message : status === 500 ? "An internal error occurred" : err.message, }, }; } }); // ============================================ // SYSTEM ROUTES // ============================================ // Health Check (includes DB status) app.use(async (ctx, next) => { if (ctx.request.url.pathname === "/health") { const dbHealthy = await checkDbHealth(); ctx.response.status = dbHealthy ? 200 : 503; ctx.response.body = { status: dbHealthy ? "ok" : "degraded", service: "pulse-crm-backend", version: "0.1.0", timestamp: new Date().toISOString(), checks: { database: dbHealthy ? "ok" : "error", }, }; return; } await next(); }); // Liveness probe (simple check) app.use(async (ctx, next) => { if (ctx.request.url.pathname === "/live") { ctx.response.body = { status: "ok" }; 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", description: "Der Herzschlag deines Business", documentation: "/api/v1/docs", endpoints: { auth: { "POST /api/v1/auth/register": "Register new user & organization", "POST /api/v1/auth/login": "Login", "POST /api/v1/auth/refresh": "Refresh access token", "POST /api/v1/auth/logout": "Logout (revoke token)", "POST /api/v1/auth/logout-all": "Logout from all devices", "POST /api/v1/auth/forgot-password": "Request password reset", "POST /api/v1/auth/reset-password": "Reset password with token", "POST /api/v1/auth/verify-email": "Verify email address", "GET /api/v1/auth/me": "Get current user", }, users: { "GET /api/v1/users": "List organization users (admin/owner)", "GET /api/v1/users/:id": "Get user details", "POST /api/v1/users": "Create/invite new user (admin/owner)", "PUT /api/v1/users/:id": "Update user (admin/owner)", "DELETE /api/v1/users/:id": "Delete user (admin/owner)", "POST /api/v1/users/:id/reset-password": "Reset user password (admin/owner)", }, inbox: { "GET /api/v1/inbox": "List inbox items (tasks, appointments, emails)", "GET /api/v1/inbox/stats": "Inbox statistics", "GET /api/v1/inbox/team": "Team inbox overview (manager+)", "POST /api/v1/inbox": "Create inbox item / assign task", "PUT /api/v1/inbox/:id": "Update inbox item", "PUT /api/v1/inbox/:id/status": "Quick status update", "DELETE /api/v1/inbox/:id": "Delete inbox item", }, contacts: { "GET /api/v1/contacts": "List contacts", "GET /api/v1/contacts/stats": "Contact statistics", "GET /api/v1/contacts/export": "Export contacts (DSGVO)", "GET /api/v1/contacts/:id": "Get contact", "POST /api/v1/contacts": "Create contact", "POST /api/v1/contacts/import": "Bulk import contacts", "PUT /api/v1/contacts/:id": "Update contact", "DELETE /api/v1/contacts/:id": "Soft delete contact", "DELETE /api/v1/contacts/:id/permanent": "Permanent delete (DSGVO)", }, companies: { "GET /api/v1/companies": "List companies", "GET /api/v1/companies/industries": "Get industries", "GET /api/v1/companies/:id": "Get company", "GET /api/v1/companies/:id/contacts": "Get company contacts", "POST /api/v1/companies": "Create company", "PUT /api/v1/companies/:id": "Update company", "DELETE /api/v1/companies/:id": "Delete company", }, deals: { "GET /api/v1/deals": "List deals with filters", "GET /api/v1/deals/stats": "Deal statistics", "GET /api/v1/deals/forecast": "Sales forecast", "GET /api/v1/deals/pipeline/:pipelineId": "Kanban board view", "GET /api/v1/deals/:id": "Get deal", "POST /api/v1/deals": "Create deal", "PUT /api/v1/deals/:id": "Update deal", "POST /api/v1/deals/:id/move": "Move to stage", "POST /api/v1/deals/:id/won": "Mark as won", "POST /api/v1/deals/:id/lost": "Mark as lost", "POST /api/v1/deals/:id/reopen": "Reopen closed deal", "DELETE /api/v1/deals/:id": "Delete deal", }, pipelines: { "GET /api/v1/pipelines": "List pipelines", "GET /api/v1/pipelines/default": "Get/create default pipeline", "GET /api/v1/pipelines/:id": "Get pipeline", "POST /api/v1/pipelines": "Create pipeline", "PUT /api/v1/pipelines/:id": "Update pipeline", "PUT /api/v1/pipelines/:id/stages": "Update stages", "DELETE /api/v1/pipelines/:id": "Delete pipeline", }, activities: { "GET /api/v1/activities": "List activities", "GET /api/v1/activities/stats": "Activity statistics", "GET /api/v1/activities/upcoming": "Upcoming tasks", "GET /api/v1/activities/overdue": "Overdue tasks", "GET /api/v1/activities/timeline/:type/:id": "Entity timeline", "GET /api/v1/activities/:id": "Get activity", "POST /api/v1/activities": "Create activity", "PUT /api/v1/activities/:id": "Update activity", "POST /api/v1/activities/:id/complete": "Mark completed", "POST /api/v1/activities/:id/reopen": "Reopen activity", "DELETE /api/v1/activities/:id": "Delete activity", }, pipelines: { "GET /api/v1/pipelines": "List pipelines", "POST /api/v1/pipelines": "Create pipeline", "PUT /api/v1/pipelines/:id/stages": "Update stages", }, }, }; return; } await next(); }); // ============================================ // API ROUTES // ============================================ app.use(authRouter.routes()); app.use(authRouter.allowedMethods()); app.use(usersRouter.routes()); app.use(usersRouter.allowedMethods()); app.use(inboxRouter.routes()); app.use(inboxRouter.allowedMethods()); app.use(contactsRouter.routes()); app.use(contactsRouter.allowedMethods()); app.use(companiesRouter.routes()); app.use(companiesRouter.allowedMethods()); app.use(dealsRouter.routes()); app.use(dealsRouter.allowedMethods()); app.use(activitiesRouter.routes()); app.use(activitiesRouter.allowedMethods()); app.use(pipelinesRouter.routes()); app.use(pipelinesRouter.allowedMethods()); // ============================================ // 404 HANDLER // ============================================ app.use((ctx) => { ctx.response.status = 404; ctx.response.body = { success: false, error: { code: "NOT_FOUND", message: `Endpoint ${ctx.request.method} ${ctx.request.url.pathname} not found`, }, }; }); // ============================================ // START SERVER // ============================================ console.log(""); console.log(" 🫀 Pulse CRM Backend"); console.log(" ===================="); console.log(` 📡 Server: http://localhost:${PORT}`); console.log(` 📚 API: http://localhost:${PORT}/api/v1`); console.log(` ❤️ Health: http://localhost:${PORT}/health`); console.log(` 🔧 Mode: ${NODE_ENV}`); console.log(""); await app.listen({ port: PORT });