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(); // Vercel 프론트에서 Render /uploads 이미지를 img로 불러올 수 있도록 cross-origin 허용 app.use( helmet({ crossOriginResourcePolicy: { policy: 'cross-origin' }, }), ); const allowedOrigins = [ 'http://localhost:3000', 'http://localhost:5173', 'https://localhost:3000', 'http://127.0.0.1:3000', 'https://127.0.0.1:3000', 'http://172.16.8.248:3000', 'https://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; // 로컬·사설망 프론트 (http/https, LAN IP) if (/^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/i.test(origin)) return true; if (/^https?:\/\/172\.(1[6-9]|2\d|3[01])\.\d+\.\d+(:\d+)?$/.test(origin)) return true; if (/^https?:\/\/192\.168\.\d+\.\d+(:\d+)?$/.test(origin)) return true; if (/^https?:\/\/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;