← back to Bertha
react-dash/server.js
696 lines
import http from 'http';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import pg from 'pg';
import crypto from 'crypto';
import { OAuth2Client } from 'google-auth-library';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const PORT = 7809;
const DIST = path.join(__dirname, 'dist');
// ── Unified DW Auth Config ──
// Audit 2026-05-04: fail-closed on missing env. Old literal `DWSecure2024!`
// is now in source-control history and must be rotated.
const AUTH_USER = process.env.AUTH_USERNAME || 'admin';
const AUTH_PASS = process.env.AUTH_PASSWORD;
if (!AUTH_PASS) {
throw new Error('AUTH_PASSWORD env var required (no fallback for production safety)');
}
const SESSION_COOKIE = 'bertha_session';
const SESSION_MAX_AGE = 30 * 24 * 60 * 60; // 30 days in seconds
const WHITELISTED_IPS = ['127.0.0.1', '::1', '45.61.58.125', 'localhost'];
// ── Google OAuth Config ──
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID || '';
const ALLOWED_EMAILS = (process.env.ALLOWED_EMAILS || '').split(',').filter(Boolean);
const googleClient = GOOGLE_CLIENT_ID ? new OAuth2Client(GOOGLE_CLIENT_ID) : null;
const pool = new pg.Pool({
connectionString: (process.env.DATABASE_URL || 'postgresql://dw_admin@127.0.0.1:5432/bertha_betting'),
max: 10,
idleTimeoutMillis: 30000,
});
const MIME = {
'.html': 'text/html', '.js': 'application/javascript', '.css': 'text/css',
'.json': 'application/json', '.png': 'image/png', '.svg': 'image/svg+xml',
'.ico': 'image/x-icon', '.woff2': 'font/woff2', '.woff': 'font/woff',
};
async function q(text, params) {
return pool.query(text, params);
}
function json(res, data, status = 200, extraHeaders = {}) {
res.writeHead(status, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*', ...extraHeaders });
res.end(JSON.stringify(data));
}
async function readBody(req) {
return new Promise(resolve => {
let d = '';
req.on('data', c => d += c);
req.on('end', () => { try { resolve(JSON.parse(d)); } catch { resolve({}); } });
});
}
// ── Unified Session Helpers ──
function parseCookies(req) {
const obj = {};
(req.headers.cookie || '').split(';').forEach(c => {
const [k, ...v] = c.trim().split('=');
if (k) obj[k] = v.join('=');
});
return obj;
}
function getSession(req) {
const cookies = parseCookies(req);
const token = cookies[SESSION_COOKIE];
if (!token) return null;
try {
const data = JSON.parse(Buffer.from(decodeURIComponent(token), 'base64').toString());
if (!data.authenticated) return null;
const age = Date.now() - data.loginTime;
if (age > SESSION_MAX_AGE * 1000) return null;
return data;
} catch { return null; }
}
function getClientIP(req) {
return req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.socket.remoteAddress || '';
}
function isWhitelistedIP(req) {
const ip = getClientIP(req);
return WHITELISTED_IPS.some(w => ip.includes(w));
}
function hasBasicAuth(req) {
const auth = req.headers.authorization;
if (!auth || !auth.startsWith('Basic ')) return false;
const [user, pass] = Buffer.from(auth.split(' ')[1], 'base64').toString().split(':');
return user === AUTH_USER && pass === AUTH_PASS;
}
function isAuthenticated(req) {
if (isWhitelistedIP(req)) return true;
if (hasBasicAuth(req)) return true;
if (getSession(req)) return true;
return false;
}
function setSessionCookie(res, sessionToken) {
const cookie = `${SESSION_COOKIE}=${encodeURIComponent(sessionToken)}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${SESSION_MAX_AGE}`;
return cookie;
}
function clearSessionCookie() {
return `${SESSION_COOKIE}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0`;
}
// API route handlers
const routes = {
'GET /api/health': async (req, res) => {
try {
const dbCheck = await q('SELECT 1');
const risk = await q('SELECT * FROM risk_state ORDER BY updated_at DESC LIMIT 1');
const r = risk.rows[0] || {};
const slack = process.env.SLACK_BOT_TOKEN ? true : false;
json(res, {
status: 'ok',
db_connected: !!dbCheck,
paper_mode: r.config?.paper_mode ?? true,
kill_switch: r.kill_switch_active ?? false,
slack_configured: slack,
setup_complete: r.config?.setup_complete || false,
});
} catch (err) {
json(res, { status: 'error', error: err.message }, 500);
}
},
// ── Unified Auth Endpoints ──
'POST /api/auth/login': async (req, res) => {
const body = await readBody(req);
if (body.username === AUTH_USER && body.password === AUTH_PASS) {
const sessionData = { authenticated: true, username: AUTH_USER, loginTime: Date.now() };
const token = Buffer.from(JSON.stringify(sessionData)).toString('base64');
json(res, { success: true, message: 'Login successful', username: AUTH_USER }, 200, {
'Set-Cookie': setSessionCookie(res, token),
});
} else {
json(res, { error: 'Invalid credentials' }, 401);
}
},
'POST /api/auth/logout': async (req, res) => {
json(res, { success: true, message: 'Logged out successfully' }, 200, {
'Set-Cookie': clearSessionCookie(),
});
},
'POST /api/auth/google': async (req, res) => {
if (!googleClient) return json(res, { error: 'Google OAuth not configured' }, 400);
const body = await readBody(req);
if (!body.credential) return json(res, { error: 'Missing credential' }, 400);
try {
const ticket = await googleClient.verifyIdToken({ idToken: body.credential, audience: GOOGLE_CLIENT_ID });
const payload = ticket.getPayload();
const email = payload.email;
if (!payload.email_verified) return json(res, { error: 'Email not verified' }, 401);
// Check allowed emails (if list configured, enforce it; otherwise allow any Google user)
if (ALLOWED_EMAILS.length > 0 && !ALLOWED_EMAILS.includes(email)) {
return json(res, { error: 'Email not authorized' }, 403);
}
const sessionData = { authenticated: true, username: payload.name || email, email, picture: payload.picture, provider: 'google', loginTime: Date.now() };
const token = Buffer.from(JSON.stringify(sessionData)).toString('base64');
json(res, { success: true, username: payload.name || email, email, picture: payload.picture }, 200, {
'Set-Cookie': setSessionCookie(res, token),
});
} catch (err) {
json(res, { error: 'Invalid Google token', details: err.message }, 401);
}
},
'GET /api/auth/google-config': async (req, res) => {
json(res, { client_id: GOOGLE_CLIENT_ID || null, configured: !!GOOGLE_CLIENT_ID });
},
'GET /api/auth/session': async (req, res) => {
if (isWhitelistedIP(req)) {
return json(res, { authenticated: true, username: AUTH_USER, bypassReason: 'whitelisted_ip' });
}
if (hasBasicAuth(req)) {
return json(res, { authenticated: true, username: AUTH_USER, bypassReason: 'basic_auth' });
}
const session = getSession(req);
if (session) {
return json(res, { authenticated: true, username: session.username, email: session.email, picture: session.picture, provider: session.provider, loginTime: session.loginTime });
}
json(res, { authenticated: false }, 401);
},
// ── Setup Wizard ──
'GET /api/setup': async (req, res) => {
try {
const r = await q('SELECT config FROM risk_state ORDER BY updated_at DESC LIMIT 1');
const cfg = r.rows[0]?.config || {};
json(res, { setup_complete: cfg.setup_complete || false, setup_progress: cfg.setup_progress || null });
} catch (err) { json(res, { error: err.message }, 500); }
},
'POST /api/setup': async (req, res) => {
const body = await readBody(req);
try {
const current = await q('SELECT * FROM risk_state ORDER BY updated_at DESC LIMIT 1');
const row = current.rows[0] || {};
if (body.action === 'save_progress') {
const newConfig = { ...row.config, setup_progress: body.progress };
await q('UPDATE risk_state SET config = $1, updated_at = NOW() WHERE id = $2', [JSON.stringify(newConfig), row.id]);
json(res, { success: true });
} else if (body.action === 'complete') {
const newConfig = { ...row.config, setup_complete: true, setup_completed_at: new Date().toISOString(), setup_progress: body.progress || row.config?.setup_progress };
await q('UPDATE risk_state SET config = $1, updated_at = NOW() WHERE id = $2', [JSON.stringify(newConfig), row.id]);
json(res, { success: true });
} else {
json(res, { error: 'Unknown action' }, 400);
}
} catch (err) { json(res, { error: err.message }, 500); }
},
'GET /api/dashboard': async (req, res) => {
try {
const [markets, weatherMarkets, forecasts, predictions, trades, alerts, risk] = await Promise.all([
q('SELECT COUNT(*) as cnt FROM markets'),
q("SELECT COUNT(*) as cnt FROM markets WHERE category = 'weather' OR parsed_event IS NOT NULL"),
q('SELECT COUNT(*) as cnt FROM forecast_runs'),
q('SELECT * FROM predictions ORDER BY asof_ts DESC LIMIT 10'),
q('SELECT t.*, o.side, o.outcome FROM trades t LEFT JOIN orders o ON t.order_id = o.order_id ORDER BY t.match_time DESC NULLS LAST LIMIT 10'),
q('SELECT * FROM alerts_log ORDER BY ts DESC LIMIT 5'),
q('SELECT * FROM risk_state ORDER BY updated_at DESC LIMIT 1'),
]);
const r = risk.rows[0] || {};
json(res, {
market_count: parseInt(markets.rows[0]?.cnt || 0),
weather_market_count: parseInt(weatherMarkets.rows[0]?.cnt || 0),
forecast_count: parseInt(forecasts.rows[0]?.cnt || 0),
recent_predictions: predictions.rows,
recent_trades: trades.rows,
recent_alerts: alerts.rows,
risk: {
bankroll: parseFloat(r.bankroll || 50),
daily_pnl: parseFloat(r.daily_pnl || 0),
open_exposure: parseFloat(r.open_exposure || 0),
max_drawdown_pct: parseFloat(r.max_drawdown_pct || 0),
paper_mode: r.config?.paper_mode ?? true,
kill_switch_active: r.kill_switch_active || false,
config: r.config || {},
},
});
} catch (err) {
json(res, { error: err.message }, 500);
}
},
'GET /api/markets': async (req, res) => {
try {
const r = await q('SELECT * FROM markets ORDER BY created_at DESC LIMIT 100');
const tokens = await q('SELECT * FROM tokens ORDER BY condition_id');
json(res, { markets: r.rows, tokens: tokens.rows });
} catch (err) {
json(res, { error: err.message }, 500);
}
},
'POST /api/markets': async (req, res) => {
const body = await readBody(req);
if (body.action === 'test_api') {
try {
const r = await fetch('https://gamma-api.polymarket.com/markets?limit=5&active=true');
const data = await r.json();
json(res, { success: true, market_count: data.length });
} catch (err) {
json(res, { error: err.message }, 500);
}
} else if (body.action === 'ingest') {
// Forward to Next.js for complex ingest logic
try {
const r = await fetch('http://127.0.0.1:7800/api/markets', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
json(res, await r.json());
} catch (err) {
json(res, { error: 'Backend unavailable: ' + err.message }, 502);
}
} else {
json(res, { error: 'Unknown action' }, 400);
}
},
'GET /api/weather': async (req, res) => {
try {
const r = await q('SELECT * FROM forecast_runs ORDER BY created_at DESC LIMIT 200');
json(res, { forecasts: r.rows });
} catch (err) {
json(res, { error: err.message }, 500);
}
},
'POST /api/weather': async (req, res) => {
const body = await readBody(req);
// Forward to Next.js for NWS API calls
try {
const r = await fetch('http://127.0.0.1:7800/api/weather', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
json(res, await r.json());
} catch (err) {
json(res, { error: 'Backend unavailable: ' + err.message }, 502);
}
},
'GET /api/models': async (req, res) => {
try {
const [models, predictions] = await Promise.all([
q('SELECT * FROM model_versions ORDER BY created_at DESC'),
q('SELECT p.*, m.question FROM predictions p LEFT JOIN markets m ON p.condition_id = m.condition_id ORDER BY p.asof_ts DESC LIMIT 50'),
]);
json(res, { models: models.rows, predictions: predictions.rows });
} catch (err) {
json(res, { error: err.message }, 500);
}
},
'POST /api/models': async (req, res) => {
const body = await readBody(req);
try {
const r = await fetch('http://127.0.0.1:7800/api/models', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
json(res, await r.json());
} catch (err) {
json(res, { error: 'Backend unavailable: ' + err.message }, 502);
}
},
'GET /api/risk': async (req, res) => {
try {
const r = await q('SELECT * FROM risk_state ORDER BY updated_at DESC LIMIT 1');
const row = r.rows[0] || {};
json(res, {
state: {
bankroll: parseFloat(row.bankroll || 50),
daily_pnl: parseFloat(row.daily_pnl || 0),
open_exposure: parseFloat(row.open_exposure || 0),
max_drawdown_pct: parseFloat(row.max_drawdown_pct || 0),
kill_switch_active: row.kill_switch_active || false,
kill_reason: row.kill_reason,
config: row.config || {},
},
});
} catch (err) {
json(res, { error: err.message }, 500);
}
},
'POST /api/risk': async (req, res) => {
const body = await readBody(req);
try {
const current = await q('SELECT * FROM risk_state ORDER BY updated_at DESC LIMIT 1');
const row = current.rows[0] || {};
if (body.action === 'kill_switch_activate') {
await q('UPDATE risk_state SET kill_switch_active = true, kill_reason = $1, updated_at = NOW() WHERE id = $2', [body.reason || 'Manual', row.id]);
} else if (body.action === 'kill_switch_deactivate') {
await q('UPDATE risk_state SET kill_switch_active = false, kill_reason = NULL, updated_at = NOW() WHERE id = $1', [row.id]);
} else if (body.action === 'update_config') {
const newConfig = { ...row.config, ...body.config };
await q('UPDATE risk_state SET config = $1, updated_at = NOW() WHERE id = $2', [JSON.stringify(newConfig), row.id]);
}
const updated = await q('SELECT * FROM risk_state ORDER BY updated_at DESC LIMIT 1');
const u = updated.rows[0] || {};
json(res, {
success: true,
state: {
bankroll: parseFloat(u.bankroll || 50),
daily_pnl: parseFloat(u.daily_pnl || 0),
open_exposure: parseFloat(u.open_exposure || 0),
max_drawdown_pct: parseFloat(u.max_drawdown_pct || 0),
kill_switch_active: u.kill_switch_active || false,
kill_reason: u.kill_reason,
config: u.config || {},
},
});
} catch (err) {
json(res, { error: err.message }, 500);
}
},
'GET /api/trades': async (req, res) => {
try {
const [orders, trades] = await Promise.all([
q('SELECT * FROM orders ORDER BY submitted_ts DESC LIMIT 50'),
q('SELECT * FROM trades ORDER BY match_time DESC NULLS LAST LIMIT 50'),
]);
json(res, { orders: orders.rows, trades: trades.rows });
} catch (err) {
json(res, { error: err.message }, 500);
}
},
'POST /api/trades': async (req, res) => {
const body = await readBody(req);
try {
const r = await fetch('http://127.0.0.1:7800/api/trades', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
json(res, await r.json());
} catch (err) {
json(res, { error: 'Backend unavailable: ' + err.message }, 502);
}
},
'GET /api/bankroll': async (req, res) => {
try {
const [ledger, summary] = await Promise.all([
q('SELECT * FROM bankroll_ledger ORDER BY ts DESC LIMIT 200'),
q(`SELECT
COALESCE(SUM(CASE WHEN event_type='deposit' THEN amount ELSE 0 END), 0) as total_deposits,
COALESCE(SUM(CASE WHEN event_type='withdrawal' THEN amount ELSE 0 END), 0) as total_withdrawals,
COALESCE(SUM(CASE WHEN event_type='trade_pnl' THEN amount ELSE 0 END), 0) as total_pnl,
COALESCE(SUM(CASE WHEN event_type='fee' THEN amount ELSE 0 END), 0) as total_fees,
(SELECT balance_after FROM bankroll_ledger ORDER BY ts DESC LIMIT 1) as current_balance
FROM bankroll_ledger`),
]);
json(res, { ledger: ledger.rows, summary: summary.rows[0] || {} });
} catch (err) {
json(res, { error: err.message }, 500);
}
},
'GET /api/alerts': async (req, res) => {
try {
const r = await q('SELECT * FROM alerts_log ORDER BY ts DESC LIMIT 50');
json(res, { alerts: r.rows });
} catch (err) {
json(res, { error: err.message }, 500);
}
},
'POST /api/alerts': async (req, res) => {
const body = await readBody(req);
try {
const r = await fetch('http://127.0.0.1:7800/api/alerts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
json(res, await r.json());
} catch (err) {
json(res, { error: 'Backend unavailable: ' + err.message }, 502);
}
},
'POST /api/workers': async (req, res) => {
const body = await readBody(req);
try {
const r = await fetch('http://127.0.0.1:7800/api/workers', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
json(res, await r.json());
} catch (err) {
json(res, { error: 'Backend unavailable: ' + err.message }, 502);
}
},
// ── Polymarket CLOB API (direct HTTP) ──
'POST /api/polymarket': async (req, res) => {
const body = await readBody(req);
const CLOB = 'https://clob.polymarket.com';
const GAMMA = 'https://gamma-api.polymarket.com';
// Helper: HMAC-SHA256 sign for L2 auth
function hmacSign(secret, timestamp, method, path, bodyStr = '') {
const message = timestamp + method + path + bodyStr;
return crypto.createHmac('sha256', Buffer.from(secret, 'base64')).update(message).digest('base64');
}
// Helper: build L2 auth headers
function l2Headers(apiKey, secret, passphrase, method, urlPath, bodyStr = '') {
const ts = Math.floor(Date.now() / 1000).toString();
const sig = hmacSign(secret, ts, method, urlPath, bodyStr);
return { 'POLY_API_KEY': apiKey, 'POLY_PASSPHRASE': passphrase, 'POLY_TIMESTAMP': ts, 'POLY_SIGNATURE': sig };
}
if (body.action === 'test_public') {
// Test public CLOB connection — no auth needed
try {
const [okRes, timeRes] = await Promise.all([
fetch(`${CLOB}/ok`),
fetch(`${CLOB}/time`),
]);
const ok = await okRes.text();
const time = await timeRes.text();
json(res, { success: true, ok: ok.trim(), server_time: time.trim(), endpoint: CLOB });
} catch (err) {
json(res, { success: false, error: err.message }, 500);
}
} else if (body.action === 'save_keys') {
// Save manually-entered API credentials (from polymarket.com/settings?tab=builder)
if (!body.api_key || !body.secret || !body.passphrase) {
return json(res, { error: 'api_key, secret, and passphrase required' }, 400);
}
try {
const current = await q('SELECT * FROM risk_state ORDER BY updated_at DESC LIMIT 1');
const row = current.rows[0] || {};
const newConfig = {
...row.config,
poly_api_key: body.api_key,
poly_api_secret: body.secret,
poly_passphrase: body.passphrase,
poly_wallet: body.wallet || null,
poly_connected: true,
poly_connected_at: new Date().toISOString(),
};
await q('UPDATE risk_state SET config = $1, updated_at = NOW() WHERE id = $2', [JSON.stringify(newConfig), row.id]);
json(res, { success: true, api_key: body.api_key.substring(0, 8) + '...', message: 'Credentials saved' });
} catch (err) {
json(res, { success: false, error: err.message }, 500);
}
} else if (body.action === 'auth_test') {
// Test authenticated connection with stored L2 credentials
try {
const current = await q('SELECT config FROM risk_state ORDER BY updated_at DESC LIMIT 1');
const cfg = current.rows[0]?.config || {};
if (!cfg.poly_api_key || !cfg.poly_api_secret || !cfg.poly_passphrase) {
return json(res, { error: 'No Polymarket credentials configured. Save API keys first.' }, 400);
}
const authHdrs = l2Headers(cfg.poly_api_key, cfg.poly_api_secret, cfg.poly_passphrase, 'GET', '/data/trades');
const r = await fetch(`${CLOB}/data/trades`, { headers: authHdrs });
if (r.ok) {
const data = await r.json();
json(res, { success: true, trades: Array.isArray(data) ? data.length : 0, wallet: cfg.poly_wallet, message: 'Authenticated OK' });
} else {
const errText = await r.text();
json(res, { success: false, error: `CLOB returned ${r.status}: ${errText}` });
}
} catch (err) {
json(res, { success: false, error: err.message }, 500);
}
} else if (body.action === 'get_markets') {
// Get live weather markets from Gamma API
try {
const r = await fetch(`${GAMMA}/markets?limit=50&active=true&tag=weather`);
const markets = await r.json();
// Also try getting prices for our tracked tokens
const tokens = await q('SELECT t.token_id, t.outcome, m.question FROM tokens t JOIN markets m ON t.condition_id = m.condition_id LIMIT 20');
const prices = [];
for (const tok of tokens.rows) {
try {
const pr = await fetch(`${CLOB}/price?token_id=${tok.token_id}&side=buy`);
if (pr.ok) { const d = await pr.json(); prices.push({ ...tok, price: d.price }); }
} catch { /* skip */ }
}
json(res, { success: true, gamma_markets: markets.length || 0, tracked_prices: prices });
} catch (err) {
json(res, { success: false, error: err.message }, 500);
}
} else if (body.action === 'get_orderbook') {
if (!body.token_id) return json(res, { error: 'token_id required' }, 400);
try {
const r = await fetch(`${CLOB}/book?token_id=${body.token_id}`);
const book = await r.json();
json(res, { success: true, orderbook: book });
} catch (err) {
json(res, { success: false, error: err.message }, 500);
}
} else if (body.action === 'status') {
// Get connection status from DB
try {
const current = await q('SELECT config FROM risk_state ORDER BY updated_at DESC LIMIT 1');
const cfg = current.rows[0]?.config || {};
json(res, {
connected: !!cfg.poly_connected,
api_key: cfg.poly_api_key ? cfg.poly_api_key.substring(0, 8) + '...' : null,
wallet: cfg.poly_wallet || null,
connected_at: cfg.poly_connected_at || null,
has_secret: !!cfg.poly_api_secret,
has_passphrase: !!cfg.poly_passphrase,
});
} catch (err) {
json(res, { error: err.message }, 500);
}
} else if (body.action === 'disconnect') {
try {
const current = await q('SELECT * FROM risk_state ORDER BY updated_at DESC LIMIT 1');
const row = current.rows[0] || {};
const newConfig = { ...row.config, poly_api_key: null, poly_api_secret: null, poly_passphrase: null, poly_wallet: null, poly_connected: false, poly_connected_at: null };
await q('UPDATE risk_state SET config = $1, updated_at = NOW() WHERE id = $2', [JSON.stringify(newConfig), row.id]);
json(res, { success: true, message: 'Disconnected' });
} catch (err) {
json(res, { error: err.message }, 500);
}
} else {
json(res, { error: 'Unknown action. Use: test_public, save_keys, auth_test, get_markets, get_orderbook, status, disconnect' }, 400);
}
},
};
const server = http.createServer(async (req, res) => {
// CORS preflight
if (req.method === 'OPTIONS') {
res.writeHead(200, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
});
res.end();
return;
}
// API routes - direct DB access
const routeKey = `${req.method} ${req.url.split('?')[0]}`;
const urlPath = req.url.split('?')[0];
// Public endpoints (no auth required)
const publicPaths = ['/api/auth/login', '/api/auth/logout', '/api/auth/session', '/api/health'];
const isPublic = publicPaths.includes(urlPath);
// Enforce auth on all other API routes
if (!isPublic && urlPath.startsWith('/api/') && !isAuthenticated(req)) {
return json(res, { error: 'Authentication required' }, 401);
}
if (routes[routeKey]) {
try {
await routes[routeKey](req, res);
} catch (err) {
json(res, { error: err.message }, 500);
}
return;
}
// Fallback: proxy unknown API routes to Next.js backend
if (req.url.startsWith('/api/')) {
try {
let body = '';
if (req.method !== 'GET' && req.method !== 'HEAD') {
body = await new Promise(resolve => {
let d = '';
req.on('data', c => d += c);
req.on('end', () => resolve(d));
});
}
const fetchOpts = {
method: req.method,
headers: { 'Content-Type': 'application/json', 'Cookie': req.headers.cookie || '' },
};
if (body && req.method !== 'GET') fetchOpts.body = body;
const apiRes = await fetch(`http://127.0.0.1:7800${req.url}`, fetchOpts);
const apiBody = await apiRes.text();
res.writeHead(apiRes.status, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
res.end(apiBody);
} catch (err) {
json(res, { error: err.message }, 502);
}
return;
}
// Serve static files
let filePath = path.join(DIST, req.url === '/' ? 'index.html' : req.url);
if (!fs.existsSync(filePath)) {
filePath = path.join(DIST, 'index.html');
}
try {
const content = fs.readFileSync(filePath);
const ext = path.extname(filePath);
res.writeHead(200, { 'Content-Type': MIME[ext] || 'application/octet-stream' });
res.end(content);
} catch {
res.writeHead(404);
res.end('Not found');
}
});
server.listen(PORT, '0.0.0.0', () => {
console.log(`[Bertha React] Dashboard running on http://0.0.0.0:${PORT}`);
console.log(`[Bertha React] Direct DB + API proxy mode`);
});