import express from 'express'; import cors from 'cors'; import helmet from 'helmet'; import morgan from 'morgan'; import path from 'path'; import { errorHandler } from './middleware/errorHandler'; import routes from './routes'; const app = express(); app.use(helmet()); const allowedOrigins = [ 'http://localhost:3000', 'http://localhost:5173', 'http://127.0.0.1:3000', 'http://172.16.8.248:3000', 'https://eene-dashboard.vercel.app', process.env.FRONTEND_URL, ].filter(Boolean) as string[]; function isAllowedOrigin(origin: string): boolean { if (allowedOrigins.includes(origin)) return true; if (/^https:\/\/[\w-]+\.vercel\.app$/.test(origin)) return true; // 로컬·사설망 프론트 (용량 절약용 로컬 서버) if (/^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(origin)) return true; if (/^http:\/\/172\.(1[6-9]|2\d|3[01])\.\d+\.\d+(:\d+)?$/.test(origin)) return true; if (/^http:\/\/192\.168\.\d+\.\d+(:\d+)?$/.test(origin)) return true; if (/^http:\/\/10\.\d+\.\d+\.\d+(:\d+)?$/.test(origin)) return true; return false; } app.use( cors({ origin: (origin, callback) => { if (!origin || isAllowedOrigin(origin)) { callback(null, true); } else { callback(new Error(`CORS 차단: ${origin}`)); } }, credentials: true, }), ); app.use(morgan('dev')); app.use(express.json()); app.use(express.urlencoded({ extended: true })); // 업로드 파일 정적 서빙 const uploadDir = path.resolve(process.env.UPLOAD_DIR || '../uploads'); app.use('/uploads', express.static(uploadDir)); // API Routes app.use('/api', routes); app.get('/health', (_req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); app.use(errorHandler); export default app;