ERP-node/ai-assistant/src/app.js

187 lines
5.0 KiB
JavaScript
Raw Normal View History

// src/app.js
// AI Assistant API 서버 메인 엔트리포인트
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const swaggerUi = require('swagger-ui-express');
const swaggerSpec = require('./config/swagger.config');
const logger = require('./config/logger.config');
const { sequelize } = require('./models');
const routes = require('./routes');
const errorHandler = require('./middlewares/error-handler.middleware');
const app = express();
// VEXPLOR 내장 시 backend-node가 이 포트로 프록시하므로 기본 3100 사용
const PORT = process.env.PORT || 3100;
// ===========================================
// 미들웨어 설정
// ===========================================
// Trust proxy (Docker/Nginx 환경)
app.set('trust proxy', 1);
// CORS 설정 (helmet보다 먼저 설정)
app.use(cors({
origin: true, // 모든 origin 허용
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
}));
// Preflight 요청 처리
app.options('*', cors());
// 보안 헤더 (CORS 이후에 설정)
app.use(helmet({
crossOriginResourcePolicy: { policy: 'cross-origin' },
crossOriginOpenerPolicy: { policy: 'unsafe-none' },
}));
// 요청 본문 파싱
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// 압축
app.use(compression());
// Rate Limiting (전역)
const limiter = rateLimit({
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) || 60000,
max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS, 10) || 100,
message: {
success: false,
error: {
code: 'RATE_LIMIT_EXCEEDED',
message: '요청 한도를 초과했습니다. 잠시 후 다시 시도해주세요.',
},
},
standardHeaders: true,
legacyHeaders: false,
});
app.use(limiter);
// 요청 로깅
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info(`${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms`);
});
next();
});
// ===========================================
// 헬스 체크
// ===========================================
app.get('/health', (req, res) => {
res.json({
success: true,
data: {
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
},
});
});
// ===========================================
// Swagger API 문서
// ===========================================
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec, {
explorer: true,
customCss: '.swagger-ui .topbar { display: none }',
customSiteTitle: 'AI Assistant API 문서',
swaggerOptions: {
persistAuthorization: true,
displayRequestDuration: true,
},
}));
// Swagger JSON
app.get('/api-docs.json', (req, res) => {
res.setHeader('Content-Type', 'application/json');
res.send(swaggerSpec);
});
// ===========================================
// API 라우트
// ===========================================
app.use('/api/v1', routes);
// ===========================================
// 404 처리
// ===========================================
app.use((req, res) => {
res.status(404).json({
success: false,
error: {
code: 'NOT_FOUND',
message: `요청한 리소스를 찾을 수 없습니다: ${req.method} ${req.originalUrl}`,
},
});
});
// ===========================================
// 에러 핸들러
// ===========================================
app.use(errorHandler);
// ===========================================
// 서버 시작
// ===========================================
async function startServer() {
try {
// 데이터베이스 연결
await sequelize.authenticate();
logger.info('✅ 데이터베이스 연결 성공');
// 테이블 동기화 (테이블이 없으면 생성)
await sequelize.sync();
logger.info('✅ 데이터베이스 스키마 동기화 완료');
// 초기 데이터 설정 (관리자 계정, LLM 프로바이더)
const initService = require('./services/init.service');
await initService.initialize();
// 서버 시작
app.listen(PORT, () => {
logger.info(`🚀 AI Assistant API 서버가 포트 ${PORT}에서 실행 중입니다`);
logger.info(`📚 API 문서 (Swagger): http://localhost:${PORT}/api-docs`);
logger.info(`📚 API 엔드포인트: http://localhost:${PORT}/api/v1`);
});
} catch (error) {
logger.error('❌ 서버 시작 실패:', error);
process.exit(1);
}
}
// 프로세스 종료 처리
process.on('SIGTERM', async () => {
logger.info('SIGTERM 신호 수신, 서버 종료 중...');
await sequelize.close();
process.exit(0);
});
process.on('SIGINT', async () => {
logger.info('SIGINT 신호 수신, 서버 종료 중...');
await sequelize.close();
process.exit(0);
});
startServer();
module.exports = app;