193 lines
5.0 KiB
TypeScript

/**
* 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();