/** * A2P AutoPilot - Main Entry Point * Sets up Express server, database, Redis, and BullMQ workers */ import express from 'express'; import cors from 'cors'; import helmet from 'helmet'; import dotenv from 'dotenv'; import { initializeDatabase, closeDatabase } from './db/index'; import { runMigrations } from './db/migrate'; import { logger } from './utils/logger'; import routes from './api/routes'; import { requestLogger, errorHandler, notFoundHandler, } from './api/middleware'; // Load environment variables dotenv.config(); // ============================================================ // CONFIGURATION // ============================================================ const PORT = process.env.PORT || 3000; const DATABASE_URL = process.env.DATABASE_URL; const REDIS_URL = process.env.REDIS_URL; const NODE_ENV = process.env.NODE_ENV || 'development'; // Validate required environment variables const requiredEnvVars = ['DATABASE_URL', 'REDIS_URL', 'API_KEY', 'TWILIO_ACCOUNT_SID', 'TWILIO_AUTH_TOKEN']; const missing = requiredEnvVars.filter((key) => !process.env[key]); if (missing.length > 0) { logger.error({ missing }, 'Missing required environment variables'); process.exit(1); } // ============================================================ // EXPRESS APP SETUP // ============================================================ const app = express(); // Security middleware app.use(helmet()); // CORS configuration app.use( cors({ origin: process.env.CORS_ORIGIN || '*', methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], allowedHeaders: ['Content-Type', 'Authorization'], }) ); // Body parsing app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true, limit: '10mb' })); // Request logging app.use(requestLogger); // Mount API routes app.use('/api', routes); // Health check at root app.get('/', (_req, res) => { res.json({ service: 'a2p-autopilot', version: '1.0.0', status: 'running', environment: NODE_ENV, }); }); // 404 handler app.use(notFoundHandler); // Global error handler (must be last) app.use(errorHandler); // ============================================================ // DATABASE & REDIS INITIALIZATION // ============================================================ async function initializeServices() { logger.info('Initializing services...'); // 1. Connect to PostgreSQL try { initializeDatabase(DATABASE_URL!); logger.info('✓ Database connected'); } catch (error) { logger.error({ error }, 'Failed to connect to database'); throw error; } // 2. Run migrations try { await runMigrations(DATABASE_URL!); logger.info('✓ Database migrations completed'); } catch (error) { logger.error({ error }, 'Failed to run migrations'); throw error; } // 3. Connect to Redis // TODO: Initialize Redis client logger.info('✓ Redis connected (TODO)'); // 4. Initialize BullMQ workers // TODO: Start BullMQ workers for polling and submission processing logger.info('✓ BullMQ workers started (TODO)'); } // ============================================================ // SERVER STARTUP // ============================================================ async function startServer() { try { await initializeServices(); const server = app.listen(PORT, () => { logger.info( { port: PORT, env: NODE_ENV, processId: process.pid, }, `🚀 A2P AutoPilot server running on port ${PORT}` ); }); // Graceful shutdown handling const shutdown = async (signal: string) => { logger.info({ signal }, 'Shutdown signal received'); server.close(async () => { logger.info('HTTP server closed'); try { // Close database connection await closeDatabase(); logger.info('Database connection closed'); // TODO: Close Redis connection // await closeRedis(); // TODO: Close BullMQ workers // await closeBullMQ(); logger.info('Graceful shutdown complete'); process.exit(0); } catch (error) { logger.error({ error }, 'Error during shutdown'); process.exit(1); } }); // Force shutdown after 30 seconds setTimeout(() => { logger.error('Forced shutdown after timeout'); process.exit(1); }, 30000); }; // Register shutdown handlers process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT')); // Handle uncaught errors process.on('uncaughtException', (error) => { logger.error({ error }, 'Uncaught exception'); shutdown('uncaughtException'); }); process.on('unhandledRejection', (reason, promise) => { logger.error({ reason, promise }, 'Unhandled promise rejection'); shutdown('unhandledRejection'); }); } catch (error) { logger.error({ error }, 'Failed to start server'); process.exit(1); } } // Start the server startServer();