import cors from 'cors'; import express, { type ErrorRequestHandler } from 'express'; import { ZodError } from 'zod'; import type { DomesticDataset } from './types/domestic-dataset.js'; import { LocalSnapshotService } from './modules/domestic-voc/local/local-snapshot.service.js'; import { LocalSyncJobStore } from './modules/domestic-voc/local/local-sync-job.store.js'; import { createDomesticVocRouter } from './modules/domestic-voc/routes.js'; import { SyncService } from './modules/domestic-voc/services/sync.service.js'; export function createLocalDemoApp(input: { dataset: DomesticDataset; corsOrigins: string[]; }) { const app = express(); app.disable('x-powered-by'); app.use(cors({ origin(origin, callback) { if (!origin || input.corsOrigins.includes(origin)) return callback(null, true); return callback(new Error('Origin is not allowed')); }, })); app.use(express.json({ limit: '1mb' })); const jobs = new LocalSyncJobStore(input.dataset); const sync = new SyncService(jobs); const snapshot = new LocalSnapshotService(input.dataset); app.get('/health', (_request, response) => { response.json({ service: 'saas-voc-server', status: 'ok', mode: 'local-demo', database: 'deferred', dataset: { caseName: input.dataset.caseName, platform: input.dataset.platform, products: input.dataset.products.length, reviews: input.dataset.reviews.length, }, timestamp: new Date().toISOString(), }); }); app.use('/api/domestic-voc', createDomesticVocRouter({ jobs, sync, snapshot })); app.use((_request, response) => { response.status(404).json({ error: 'not_found' }); }); const errorHandler: ErrorRequestHandler = (error, _request, response, _next) => { if (error instanceof ZodError) { response.status(400).json({ error: 'invalid_request', issues: error.issues.map((issue) => ({ path: issue.path.join('.'), message: issue.message })), }); return; } console.error('[local-http] unhandled request error', error instanceof Error ? error.message : error); response.status(500).json({ error: 'internal_error' }); }; app.use(errorHandler); return app; }