// Force Eastern Time for all date operations (Gatineau, QC) process.env.TZ = 'America/Toronto'; import express from 'express'; import { createServer } from 'http'; import { Server as SocketIOServer } from 'socket.io'; import cors from 'cors'; import helmet from 'helmet'; import cookieParser from 'cookie-parser'; import pino from 'pino'; import { resolve, dirname } from 'path'; import { fileURLToPath } from 'url'; import { existsSync } from 'fs'; import { config } from './config/env.js'; import { db } from './db/index.js'; import { setupScanEvents } from './websocket/scanEvents.js'; import authRoutes from './routes/auth.js'; import emailRoutes from './routes/emails.js'; import transactionRoutes from './routes/transactions.js'; import settingsRoutes from './routes/settings.js'; import receiptRoutes from './routes/receipts.js'; import senderRoutes from './routes/senders.js'; import screenshotRoutes from './routes/screenshots.js'; import { errorHandler } from './middleware/errorHandler.js'; const logger = pino({ level: 'info', transport: { target: 'pino-pretty', options: { colorize: true }, }, }); const app = express(); const httpServer = createServer(app); const corsOrigin = config.APP_URL === '*' ? true : config.APP_URL; const io = new SocketIOServer(httpServer, { cors: { origin: corsOrigin, methods: ['GET', 'POST'], credentials: true, }, path: '/ws', }); // Trust reverse proxy (HuggingFace, nginx, etc.) app.set('trust proxy', true); // Middleware app.use( helmet({ contentSecurityPolicy: false, crossOriginEmbedderPolicy: false, }) ); app.use(cors({ origin: corsOrigin, credentials: true })); app.use(express.json({ limit: '10mb' })); app.use(cookieParser()); // Health check app.get('/api/health', (_req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); // Routes app.use('/api/auth', authRoutes); app.use('/api/scan', emailRoutes); app.use('/api/transactions', transactionRoutes); app.use('/api/settings', settingsRoutes); app.use('/api/receipts', receiptRoutes); app.use('/api/senders', senderRoutes); app.use('/api/screenshots', screenshotRoutes); // Serve frontend static files in production const __dirname_server = dirname(fileURLToPath(import.meta.url)); const frontendDist = resolve(__dirname_server, '../../web/dist'); if (existsSync(frontendDist)) { app.use(express.static(frontendDist)); // SPA fallback — serve index.html for non-API routes (Express 5 syntax) app.get('{*path}', (_req, res) => { res.sendFile(resolve(frontendDist, 'index.html')); }); logger.info(`Serving frontend from ${frontendDist}`); } // Error handler app.use(errorHandler); // WebSocket setup setupScanEvents(io); // Export io for use in services export { io }; const PORT = config.PORT; httpServer.listen(PORT, () => { logger.info(`ICC Interac Manager API running on http://localhost:${PORT}`); });