import 'dotenv/config'; import express from 'express'; import { pool } from './lib/db/pool.js'; import { log } from './lib/log.js'; import { mountApi } from './lib/api/index.js'; import * as queue from './lib/jobs/queue.js'; import { registerWorkers } from './lib/jobs/index.js'; import { router as ingestRouter } from './lib/api/routes/ingest.js'; const VERSION = '2.0.0-alpha.3'; export function createApp() { const app = express(); app.use(express.json({ limit: '10mb', verify: (req, _res, buf) => { req.rawBody = buf; } })); app.use(express.static('public')); // /api/ingest/* bypasses agentOrOwner — webhooks authenticate via HMAC // and need access to req.rawBody captured above. app.use('/api/ingest', ingestRouter); app.get('/health', async (_req, res) => { let db_ok = false; try { await pool.query('SELECT 1'); db_ok = true; } catch (e) { log.error({ err: e }, 'healthcheck db ping failed'); } res.json({ ok: true, db_ok, version: VERSION }); }); mountApi(app); app.use((_req, res) => res.status(404).json({ error: { code: 'not_found' } })); app.use((err, _req, res, _next) => { log.error({ err }, 'unhandled'); res.status(500).json({ error: { code: 'internal', message: 'internal server error' } }); }); return app; } if (import.meta.url === `file://${process.argv[1]}`) { const port = process.env.PORT || 3000; const app = createApp(); queue.start() .then(registerWorkers) .then(() => log.info('job queue ready')) .catch(err => log.error({ err }, 'queue boot failed')); app.listen(port, () => log.info({ port }, 'void-server listening')); for (const sig of ['SIGTERM', 'SIGINT']) { process.on(sig, async () => { log.info({ sig }, 'shutting down'); try { await queue.stop(); } catch { /* */ } process.exit(0); }); } }