From 1d899a3f187029d1ba67c64cf7477ed5e423997e Mon Sep 17 00:00:00 2001 From: oonyeje Date: Tue, 7 Jul 2026 04:16:55 +0000 Subject: [PATCH] Replace server.js with thin entry pointing at compiled TS output --- server.js | 158 +++--------------------------------------------------- 1 file changed, 7 insertions(+), 151 deletions(-) diff --git a/server.js b/server.js index 505ca4b..47c3e0f 100644 --- a/server.js +++ b/server.js @@ -1,155 +1,11 @@ /** - * bside-webhook-proxy + * Entry point. The actual source lives in src/server.ts and is compiled + * to dist/server.js by the TypeScript build step (see package.json "build"). * - * Multi-project webhook proxy for incoming webhooks (Square, Stripe, GitHub, etc.). - * Receives webhooks that can't send custom auth headers and forwards them to the - * appropriate Appwrite function with X-Appwrite-Project + X-Appwrite-Key headers. + * Run the compiled output via: + * npm run build && npm start * - * Routing is resolved via querystring params OR a short-path route map loaded - * from the ROUTE_MAP environment variable. - * - * Required env vars: - * PROJECT_CREDENTIALS JSON object mapping slot name -> { project, key } - * e.g. {"square":{"project":"abc","key":"def"}} - * - * Optional env vars: - * ROUTE_MAP String mapping short paths to { apiKeySlot, functionId } - * e.g. /square=>{"apiKeySlot":"square","functionId":"verify_square_webhook"} - * APPWRITE_BASE_URL Base URL for the Appwrite API - * (default: https://appwrite.bsidesolutions.net/v1) - * PORT Port to listen on (default: 3012) + * Or directly with tsx during development: + * npx tsx src/server.ts */ - -const express = require('express'); -const app = express(); - -// Capture raw request body for all content types so we can forward it as-is. -// This is important for services like Square that use signature validation -// based on the raw payload bytes. -app.use(express.text({ type: '*/*', limit: '1mb' })); - -// ----- Config --------------------------------------------------------------- - -const PORT = parseInt(process.env.PORT || '3012', 10); -const APPWRITE_BASE_URL = (process.env.APPWRITE_BASE_URL || 'https://appwrite.bsidesolutions.net/v1').replace(/\/$/, ''); - -let PROJECT_CREDENTIALS = {}; -try { - PROJECT_CREDENTIALS = JSON.parse(process.env.PROJECT_CREDENTIALS || '{}'); -} catch (err) { - console.error('[startup] PROJECT_CREDENTIALS is not valid JSON:', err.message); - process.exit(1); -} - -// Parse ROUTE_MAP. Format: "/path=>{json};/path2=>{json2}" -const ROUTE_MAP = {}; -if (process.env.ROUTE_MAP) { - for (const entry of process.env.ROUTE_MAP.split(';')) { - const trimmed = entry.trim(); - if (!trimmed) continue; - const arrowIdx = trimmed.indexOf('=>'); - if (arrowIdx === -1) continue; - const path = trimmed.slice(0, arrowIdx).trim(); - const cfgStr = trimmed.slice(arrowIdx + 2).trim(); - try { - ROUTE_MAP[path] = JSON.parse(cfgStr); - } catch (err) { - console.error(`[startup] Invalid ROUTE_MAP entry for "${path}":`, err.message); - } - } -} - -console.log(`[startup] Listening on port ${PORT}`); -console.log(`[startup] Appwrite base URL: ${APPWRITE_BASE_URL}`); -console.log(`[startup] Loaded ${Object.keys(PROJECT_CREDENTIALS).length} project credential slot(s)`); -console.log(`[startup] Loaded ${Object.keys(ROUTE_MAP).length} short-path route(s)`); - -// ----- Health check --------------------------------------------------------- - -app.get('/health', (_req, res) => { - res.json({ - status: 'ok', - projects: Object.keys(PROJECT_CREDENTIALS), - routes: Object.keys(ROUTE_MAP), - timestamp: new Date().toISOString(), - }); -}); - -// ----- Webhook receiver ----------------------------------------------------- - -app.post('*', async (req, res) => { - try { - const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`); - const projectParam = url.searchParams.get('project'); - const functionParam = url.searchParams.get('function'); - const apiKeyParam = url.searchParams.get('apiKey'); - const pathRoute = url.pathname; - - // Resolve project/function/apiKey. Explicit querystring params take priority, - // then short-path route map (e.g. POST /square). - let resolvedProject = projectParam; - let resolvedFunction = functionParam; - let resolvedSlot = apiKeyParam; - - if (!resolvedProject && ROUTE_MAP[pathRoute]) { - const route = ROUTE_MAP[pathRoute]; - resolvedSlot = route.apiKeySlot; - resolvedFunction = route.functionId; - resolvedProject = PROJECT_CREDENTIALS[resolvedSlot]?.project; - } - - if (!resolvedProject || !resolvedFunction || !resolvedSlot) { - return res.status(400).json({ - error: 'Missing required params', - required: ['project', 'function', 'apiKey'], - or: 'POST to a configured short-path route (e.g. /square)', - available_routes: Object.keys(ROUTE_MAP), - }); - } - - const credentials = PROJECT_CREDENTIALS[resolvedSlot]; - if (!credentials || credentials.project !== resolvedProject) { - return res.status(403).json({ error: 'Unknown project or apiKey slot' }); - } - - const rawBody = typeof req.body === 'string' ? req.body : JSON.stringify(req.body || {}); - const contentType = req.headers['content-type'] || 'application/json'; - - const appwriteUrl = `${APPWRITE_BASE_URL}/functions/${resolvedFunction}/executions`; - console.log(`[webhook] slot=${resolvedSlot} project=${resolvedProject} function=${resolvedFunction} -> ${appwriteUrl}`); - - const appwriteResp = await fetch(appwriteUrl, { - method: 'POST', - headers: { - 'X-Appwrite-Project': credentials.project, - 'X-Appwrite-Key': credentials.key, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - body: rawBody, - async: false, - method: 'POST', - path: '/webhook', - headers: { 'content-type': contentType }, - }), - }); - - const responseText = await appwriteResp.text(); - res.status(appwriteResp.status).type('application/json').send(responseText); - } catch (err) { - console.error('[webhook] Error forwarding webhook:', err); - res.status(500).json({ error: 'Internal proxy error', detail: err.message }); - } -}); - -// ----- 404 ------------------------------------------------------------------ - -app.use((_req, res) => { - res.status(404).json({ error: 'Not found. POST webhooks to this proxy with the right params.' }); -}); - -// ----- Start ---------------------------------------------------------------- - -app.listen(PORT, () => { - // The startup banner already printed above. -}); \ No newline at end of file +require('./dist/server.js'); \ No newline at end of file