import express from "express"; import path from "path"; import { createServer as createViteServer } from "vite"; import pg from "pg"; import dotenv from "dotenv"; import bcrypt from "bcryptjs"; import jwt from "jsonwebtoken"; import nodemailer from "nodemailer"; dotenv.config(); const app = express(); const PORT = parseInt(process.env.PORT || "3000", 10); const JWT_SECRET = process.env.JWT_SECRET || "pinnacle_jwt_secret_key_2026_prod"; app.use(express.json({ limit: "50mb" })); // Helper Functions for Password & Token Security function hashPassword(password: string): string { return bcrypt.hashSync(password, 10); } function verifyPassword(password: string, hashOrPlain?: string): boolean { if (!hashOrPlain) return false; if (hashOrPlain.startsWith("$2a$") || hashOrPlain.startsWith("$2b$")) { return bcrypt.compareSync(password, hashOrPlain); } // Fallback for legacy case-insensitive or exact plain match, then upgrade return password.toLowerCase() === hashOrPlain.toLowerCase(); } function generateToken(user: any): string { return jwt.sign( { id: user.id, username: user.username, role: user.role, fullName: user.fullName, email: user.email, dept: user.dept }, JWT_SECRET, { expiresIn: "24h" } ); } // PostgreSQL Connection Pool Setup let dbPool: pg.Pool | null = null; let isPostgresConnected = false; const dbUrl = process.env.DATABASE_URL || (process.env.POSTGRES_HOST ? `postgres://${process.env.POSTGRES_USER || 'postgres'}:${process.env.POSTGRES_PASSWORD || 'postgres_password'}@${process.env.POSTGRES_HOST}:${process.env.POSTGRES_PORT || 5432}/${process.env.POSTGRES_DB || 'pinnacle_db'}` : null); if (dbUrl) { try { dbPool = new pg.Pool({ connectionString: dbUrl, ssl: process.env.PGSSLMODE === "require" ? { rejectUnauthorized: false } : false, connectionTimeoutMillis: 3000, }); // Test DB connection and auto-create tables dbPool.query(` CREATE TABLE IF NOT EXISTS pinnacle_store ( id VARCHAR(50) PRIMARY KEY DEFAULT 'default', data JSONB NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS pinnacle_users ( id SERIAL PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, role VARCHAR(20) NOT NULL DEFAULT 'viewer', full_name VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL, dept VARCHAR(100) NOT NULL, active BOOLEAN DEFAULT TRUE, last_login TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); `).then(() => { isPostgresConnected = true; console.log("Connected to PostgreSQL database successfully."); }).catch(err => { console.warn("PostgreSQL connection notice:", err.message, "(Falling back to memory storage for dev environment)"); }); } catch (err: any) { console.warn("PostgreSQL Pool init notice:", err.message); } } // In-Memory Fallback State (when PostgreSQL container is not connected) let memoryStore: any = null; // Auth Middleware to verify JWT tokens const authenticateToken = (req: any, res: any, next: any) => { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; if (!token) { return res.status(401).json({ error: "Access denied. Authentication token missing." }); } try { const verified = jwt.verify(token, JWT_SECRET); req.user = verified; next(); } catch (err) { return res.status(403).json({ error: "Invalid or expired authentication token." }); } }; // API ROUTES // Health Check app.get("/api/health", async (req, res) => { let pgStatus = "disconnected"; if (dbPool) { try { await dbPool.query("SELECT 1"); pgStatus = "connected"; isPostgresConnected = true; } catch { pgStatus = "error"; isPostgresConnected = false; } } res.json({ status: "ok", app: "Pinnacle Decision Portal", postgres: pgStatus, timestamp: new Date().toISOString(), environment: process.env.NODE_ENV || "development", }); }); // User Authentication: Login app.post("/api/auth/login", async (req, res) => { const { username, password } = req.body; if (!username || !password) { return res.status(400).json({ error: "Username/email and password are required." }); } let users = memoryStore?.users || []; const term = String(username).trim().toLowerCase(); // Search user in memory store or default by username OR email let user = users.find((u: any) => (u.username && u.username.toLowerCase() === term) || (u.email && u.email.toLowerCase() === term) ); if (!user && (!memoryStore || !memoryStore.users || memoryStore.users.length === 0)) { // Check initial defaults const defaults = [ { id: 1, username: 'admin', password: 'Admin', role: 'admin', fullName: 'System Administrator', email: 'admin@pinnacle.local', dept: 'Executive Management', active: true }, { id: 2, username: 'editor', password: 'Editor', role: 'editor', fullName: 'Project Manager', email: 'pm@pinnacle.local', dept: 'Project Management Office', active: true }, { id: 3, username: 'viewer', password: 'Viewer', role: 'viewer', fullName: 'Board Observer', email: 'observer@pinnacle.local', dept: 'Board of Directors', active: true } ]; user = defaults.find(u => (u.username && u.username.toLowerCase() === term) || (u.email && u.email.toLowerCase() === term) ); } if (!user) { return res.status(401).json({ error: "Invalid username/email or password." }); } if (user.active === false) { return res.status(403).json({ error: "Your account has been deactivated by an administrator." }); } const isValid = verifyPassword(password, user.password); if (!isValid) { return res.status(401).json({ error: "Invalid username/email or password." }); } // Upgrade password to hash if stored plain if (!user.password.startsWith("$2a$") && !user.password.startsWith("$2b$")) { user.password = hashPassword(password); } const now = new Date().toISOString(); user.lastLogin = now; const token = generateToken(user); // Return user without sending sensitive hash back directly const { password: _, ...userWithoutPassword } = user; res.json({ success: true, token, mustChangePassword: !!user.mustChangePassword, user: userWithoutPassword }); }); // User Authentication: Session Verification app.get("/api/auth/me", authenticateToken, (req: any, res: any) => { res.json({ authenticated: true, user: req.user }); }); // User Authentication: Change Password app.post("/api/auth/change-password", authenticateToken, (req: any, res: any) => { const { currentPassword, newPassword } = req.body; const username = req.user.username; if (!currentPassword || !newPassword) { return res.status(400).json({ error: "Current password and new password are required." }); } if (newPassword.length < 6) { return res.status(400).json({ error: "New password must be at least 6 characters long." }); } let users = memoryStore?.users || []; let user = users.find((u: any) => u.username.toLowerCase() === username.toLowerCase()); if (!user) { return res.status(404).json({ error: "User account not found." }); } const isValid = verifyPassword(currentPassword, user.password); if (!isValid) { return res.status(401).json({ error: "Current password is incorrect." }); } user.password = hashPassword(newPassword); user.mustChangePassword = false; res.json({ success: true, message: "Password updated successfully." }); }); // User Authentication: First Login Password Change app.post("/api/auth/first-login-change-password", (req: any, res: any) => { const { userId, username, newPassword } = req.body; if (!newPassword || newPassword.length < 6) { return res.status(400).json({ error: "New password must be at least 6 characters long." }); } let users = memoryStore?.users || []; let user = users.find((u: any) => u.id === userId || u.username.toLowerCase() === String(username).toLowerCase()); if (user) { user.password = hashPassword(newPassword); user.mustChangePassword = false; } res.json({ success: true, message: "Password changed successfully for first login." }); }); // User Authentication: Request Forgot Password app.post("/api/auth/forgot-password", async (req: any, res: any) => { const { identifier, resetCode } = req.body; if (!identifier) { return res.status(400).json({ error: "Identifier (username or email) is required." }); } let users = memoryStore?.users || []; let user = users.find((u: any) => u.username.toLowerCase() === identifier.toLowerCase() || u.email.toLowerCase() === identifier.toLowerCase()); if (!user) { return res.status(404).json({ error: "User account not found." }); } // Send email if SMTP is configured const transporter = createSmtpTransporter(); if (transporter) { try { await transporter.sendMail({ from: `"${backendSmtpConfig.fromName}" <${backendSmtpConfig.fromEmail}>`, to: user.email, subject: "Password Reset Request - Pinnacle Portal", html: `

Password Reset Code

Dear ${user.fullName},

You requested a password reset for your Pinnacle Executive Portal account.

Your 6-digit verification code is: ${resetCode}

If you did not request this, please contact your administrator immediately.

` }); } catch (err) { console.warn("SMTP send failed during forgot password request:", err); } } res.json({ success: true, message: `Reset PIN generated and sent to ${user.email}` }); }); // User Authentication: Reset Password with Code app.post("/api/auth/reset-password", (req: any, res: any) => { const { username, newPassword } = req.body; if (!newPassword || newPassword.length < 6) { return res.status(400).json({ error: "New password must be at least 6 characters long." }); } let users = memoryStore?.users || []; const term = String(username || '').trim().toLowerCase(); let user = users.find((u: any) => (u.username && u.username.toLowerCase() === term) || (u.email && u.email.toLowerCase() === term) ); if (user) { user.password = hashPassword(newPassword); user.mustChangePassword = false; } res.json({ success: true, message: "Password reset successfully." }); }); // Backend Secure SMTP Config Store let backendSmtpConfig = { host: process.env.SMTP_HOST || 'smtp.mailtrap.io', port: parseInt(process.env.SMTP_PORT || '587', 10), user: process.env.SMTP_USER || '', pass: process.env.SMTP_PASS || '', secure: process.env.SMTP_SECURE === "true", fromName: process.env.SMTP_FROM_NAME || 'Pinnacle Decision Portal', fromEmail: process.env.SMTP_FROM_EMAIL || 'notifications@pinnacle.local', enabled: true, notifyOnNewDecision: true, notifyOnHighRisk: true, notifyOnUserCreated: true }; function createSmtpTransporter(cfg?: any) { const host = cfg?.host || backendSmtpConfig.host || process.env.SMTP_HOST; const port = cfg?.port || backendSmtpConfig.port || parseInt(process.env.SMTP_PORT || "587", 10); const user = cfg?.user || backendSmtpConfig.user || process.env.SMTP_USER; const pass = (cfg?.pass && cfg.pass !== "••••••••••••") ? cfg.pass : (backendSmtpConfig.pass || process.env.SMTP_PASS); const secure = cfg?.secure ?? backendSmtpConfig.secure; if (!host || !user || !pass) { return null; } return nodemailer.createTransport({ host, port, secure, auth: { user, pass }, tls: { rejectUnauthorized: false } }); } // Backend SMTP Management API app.get("/api/smtp/config", (req: any, res: any) => { res.json({ host: backendSmtpConfig.host, port: backendSmtpConfig.port, user: backendSmtpConfig.user, secure: backendSmtpConfig.secure, fromName: backendSmtpConfig.fromName, fromEmail: backendSmtpConfig.fromEmail, enabled: backendSmtpConfig.enabled, notifyOnNewDecision: backendSmtpConfig.notifyOnNewDecision, notifyOnHighRisk: backendSmtpConfig.notifyOnHighRisk, notifyOnUserCreated: backendSmtpConfig.notifyOnUserCreated, hasPassword: Boolean(backendSmtpConfig.pass && backendSmtpConfig.pass.length > 0), pass: backendSmtpConfig.pass ? "••••••••••••" : "" }); }); app.post("/api/smtp/config", (req: any, res: any) => { const { host, port, user, pass, secure, fromName, fromEmail, enabled, notifyOnNewDecision, notifyOnHighRisk, notifyOnUserCreated } = req.body; let updatedPass = backendSmtpConfig.pass; if (pass && pass !== "••••••••••••") { updatedPass = pass; } backendSmtpConfig = { host: host ?? backendSmtpConfig.host, port: parseInt(port, 10) || backendSmtpConfig.port, user: user ?? backendSmtpConfig.user, pass: updatedPass, secure: Boolean(secure), fromName: fromName ?? backendSmtpConfig.fromName, fromEmail: fromEmail ?? backendSmtpConfig.fromEmail, enabled: enabled !== undefined ? Boolean(enabled) : backendSmtpConfig.enabled, notifyOnNewDecision: notifyOnNewDecision !== undefined ? Boolean(notifyOnNewDecision) : backendSmtpConfig.notifyOnNewDecision, notifyOnHighRisk: notifyOnHighRisk !== undefined ? Boolean(notifyOnHighRisk) : backendSmtpConfig.notifyOnHighRisk, notifyOnUserCreated: notifyOnUserCreated !== undefined ? Boolean(notifyOnUserCreated) : backendSmtpConfig.notifyOnUserCreated }; return res.json({ success: true, message: "Backend SMTP configuration updated securely.", config: { ...backendSmtpConfig, pass: backendSmtpConfig.pass ? "••••••••••••" : "" } }); }); // SMTP Test Endpoint app.post("/api/notifications/smtp-test", async (req: any, res: any) => { const { recipientEmail, host, port, user, pass, secure, fromName, fromEmail } = req.body; const targetRecipient = recipientEmail || "admin@pinnacle.local"; const activeCfg = { host: host || backendSmtpConfig.host, port: port || backendSmtpConfig.port, user: user || backendSmtpConfig.user, pass: (pass && pass !== "••••••••••••") ? pass : backendSmtpConfig.pass, secure: secure !== undefined ? secure : backendSmtpConfig.secure, fromName: fromName || backendSmtpConfig.fromName, fromEmail: fromEmail || backendSmtpConfig.fromEmail }; const senderName = activeCfg.fromName; const senderEmail = activeCfg.fromEmail; const transporter = createSmtpTransporter(activeCfg); const mailOptions = { from: `"${senderName}" <${senderEmail}>`, to: targetRecipient, subject: "Pinnacle Portal - SMTP Test Notification", html: `

Pinnacle Executive Decision Portal

This is a test notification confirming that your Backend SMTP Server Configuration is active and connected.


Automated System Dispatch - Pinnacle Governance Engine

` }; if (!transporter) { return res.json({ success: true, mode: "simulated", message: `Simulated Email Dispatch: Notification preview created for ${targetRecipient}. (Fill Host, User & Password on backend SMTP settings to route via live server).`, details: mailOptions }); } try { const info = await transporter.sendMail(mailOptions); return res.json({ success: true, mode: "live_smtp", message: `Email notification sent successfully to ${targetRecipient}! (Message ID: ${info.messageId})`, messageId: info.messageId }); } catch (err: any) { console.error("SMTP Delivery Error:", err.message); return res.status(500).json({ success: false, mode: "failed", error: `SMTP Delivery Failed: ${err.message}. Please check host, port, user, or password credentials.`, simulatedFallback: mailOptions }); } }); // General Email Dispatch Endpoint app.post("/api/notifications/send-email", async (req: any, res: any) => { const { recipient, subject, body, triggerEvent } = req.body; if (!recipient || !subject) { return res.status(400).json({ error: "Recipient and subject are required." }); } if (!backendSmtpConfig.enabled) { return res.json({ success: true, status: "disabled", message: "Email notifications engine is currently disabled in backend settings." }); } const senderName = backendSmtpConfig.fromName || "Pinnacle Executive Portal"; const senderEmail = backendSmtpConfig.fromEmail || "notifications@pinnacle.local"; const transporter = createSmtpTransporter(); const mailOptions = { from: `"${senderName}" <${senderEmail}>`, to: recipient, subject: subject, html: `

Pinnacle Executive Portal

Governance Alert • ${triggerEvent || 'NOTIFICATION'}
${body || 'No message content provided.'}
This message was dispatched by Pinnacle Portal User Access & Governance Engine at ${new Date().toLocaleString()}.
` }; if (!transporter) { return res.json({ success: true, status: "simulated", message: `Simulated notification created for ${recipient}.`, log: { id: Date.now(), recipient, subject, status: "simulated", timestamp: new Date().toISOString(), triggerEvent: triggerEvent || "GENERAL_ALERT" } }); } try { const info = await transporter.sendMail(mailOptions); return res.json({ success: true, status: "sent", messageId: info.messageId, message: `Email sent to ${recipient}`, log: { id: Date.now(), recipient, subject, status: "sent", timestamp: new Date().toISOString(), triggerEvent: triggerEvent || "GENERAL_ALERT" } }); } catch (err: any) { return res.status(500).json({ success: false, status: "failed", error: err.message, log: { id: Date.now(), recipient, subject, status: "failed", error: err.message, timestamp: new Date().toISOString(), triggerEvent: triggerEvent || "GENERAL_ALERT" } }); } }); // Real-time SSE Connection Pool for Instant Multi-User Synchronization const sseClients = new Set(); function broadcastToClients(messageObj: any) { const payload = `data: ${JSON.stringify(messageObj)}\n\n`; sseClients.forEach((client) => { try { client.write(payload); } catch { sseClients.delete(client); } }); } // Real-time Event Stream Endpoint (SSE) app.get("/api/realtime/stream", (req, res) => { res.setHeader("Content-Type", "text/event-stream"); res.setHeader("Cache-Control", "no-cache"); res.setHeader("Connection", "keep-alive"); res.setHeader("Access-Control-Allow-Origin", "*"); res.flushHeaders(); // Send initial connection confirmation res.write(`data: ${JSON.stringify({ type: "connected", timestamp: new Date().toISOString() })}\n\n`); sseClients.add(res); req.on("close", () => { sseClients.delete(res); }); }); // Automated Real-Time Background Scheduler (Dispatches reminders & checks schedules) setInterval(() => { if (!memoryStore) return; const now = new Date(); const timestamp = now.toISOString(); // Broadcast heart-beat pulse to maintain SSE connections & verify real-time state consistency broadcastToClients({ type: "heartbeat", activeConnections: sseClients.size, timestamp }); }, 15000); // Load Database Store app.get("/api/db", async (req, res) => { if (dbPool && isPostgresConnected) { try { const result = await dbPool.query("SELECT data FROM pinnacle_store WHERE id = 'default' LIMIT 1"); if (result.rows.length > 0) { return res.json({ source: "postgres", data: result.rows[0].data }); } } catch (err: any) { console.error("Error fetching from Postgres:", err.message); } } return res.json({ source: memoryStore ? "memory" : "default", data: memoryStore }); }); // Save Database Store app.post("/api/db", async (req, res) => { const payload = req.body; if (!payload || typeof payload !== "object") { return res.status(400).json({ error: "Invalid payload" }); } // Pre-hash any plain passwords in the store payload for security if (Array.isArray(payload.users)) { payload.users = payload.users.map((u: any) => { if (u.password && !u.password.startsWith("$2a$") && !u.password.startsWith("$2b$")) { return { ...u, password: hashPassword(u.password) }; } return u; }); } memoryStore = payload; // Real-time broadcast to all connected clients & tabs broadcastToClients({ type: "store_update", data: payload, updatedAt: new Date().toISOString() }); if (dbPool && isPostgresConnected) { try { await dbPool.query( `INSERT INTO pinnacle_store (id, data, updated_at) VALUES ('default', $1, CURRENT_TIMESTAMP) ON CONFLICT (id) DO UPDATE SET data = EXCLUDED.data, updated_at = CURRENT_TIMESTAMP`, [JSON.stringify(payload)] ); return res.json({ success: true, savedTo: "postgres" }); } catch (err: any) { console.error("Failed to persist to Postgres:", err.message); return res.json({ success: true, savedTo: "memory", warning: "Failed to persist to Postgres" }); } } return res.json({ success: true, savedTo: "memory" }); }); // Deployment Configurations APIs for Coolify & Traefik app.get("/api/deployment/dockerfile", (req, res) => { res.type("text/plain").send(`FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm install COPY . . RUN npm run build FROM node:20-alpine AS runner WORKDIR /app ENV NODE_ENV=production ENV PORT=3000 COPY package*.json ./ RUN npm install --omit=dev COPY --from=builder /app/dist ./dist EXPOSE 3000 CMD ["node", "dist/server.cjs"]`); }); app.get("/api/deployment/docker-compose", (req, res) => { const domain = req.query.domain || "pinnacle.yourdomain.com"; res.type("text/plain").send(`version: '3.8' services: app: build: context: . dockerfile: Dockerfile container_name: pinnacle_app restart: always environment: - PORT=3000 - NODE_ENV=production - DATABASE_URL=postgres://\${POSTGRES_USER:-postgres}:\${POSTGRES_PASSWORD:-postgres_password}@postgres:5432/\${POSTGRES_DB:-pinnacle_db} - POSTGRES_HOST=postgres - POSTGRES_PORT=5432 - POSTGRES_USER=\${POSTGRES_USER:-postgres} - POSTGRES_PASSWORD=\${POSTGRES_PASSWORD:-postgres_password} - POSTGRES_DB=\${POSTGRES_DB:-pinnacle_db} ports: - "3000:3000" depends_on: postgres: condition: service_healthy labels: - "traefik.enable=true" - "traefik.http.routers.pinnacle-app.rule=Host(\`${domain}\`)" - "traefik.http.routers.pinnacle-app.entrypoints=websecure" - "traefik.http.routers.pinnacle-app.tls=true" - "traefik.http.routers.pinnacle-app.tls.certresolver=letsencrypt" - "traefik.http.services.pinnacle-app.loadbalancer.server.port=3000" postgres: image: postgres:16-alpine container_name: pinnacle_postgres restart: always environment: POSTGRES_USER: \${POSTGRES_USER:-postgres} POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:-postgres_password} POSTGRES_DB: \${POSTGRES_DB:-pinnacle_db} volumes: - postgres_data:/var/lib/postgresql/data - ./schema.sql:/docker-entrypoint-initdb.d/01-schema.sql ports: - "5432:5432" healthcheck: test: ["CMD-SHELL", "pg_isready -U \${POSTGRES_USER:-postgres} -d \${POSTGRES_DB:-pinnacle_db}"] interval: 5s timeout: 5s retries: 5 volumes: postgres_data: driver: local`); }); app.get("/api/deployment/schema.sql", (req, res) => { res.type("text/plain").send(`-- Pinnacle Decision Portal PostgreSQL Schema CREATE TABLE IF NOT EXISTS pinnacle_store ( id VARCHAR(50) PRIMARY KEY DEFAULT 'default', data JSONB NOT NULL, updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_pinnacle_store_updated ON pinnacle_store(updated_at);`); }); // Serve frontend / Vite middleware async function setupFrontend() { if (process.env.NODE_ENV !== "production") { const vite = await createViteServer({ server: { middlewareMode: true }, appType: "spa", }); app.use(vite.middlewares); } else { const distPath = path.join(process.cwd(), "dist"); app.use(express.static(distPath)); app.get("*", (req, res) => { res.sendFile(path.join(distPath, "index.html")); }); } app.listen(PORT, "0.0.0.0", () => { console.log(`Server listening on http://0.0.0.0:${PORT}`); }); } setupFrontend();