import "dotenv/config"; import express from "express"; import cors from "cors"; import helmet from "helmet"; import compression from "compression"; import rateLimit from "express-rate-limit"; import path from "path"; import config from "./config/environment"; import { logger } from "./utils/logger"; import { errorHandler } from "./middleware/errorHandler"; import { refreshTokenIfNeeded } from "./middleware/authMiddleware"; // 라우터 임포트 import authRoutes from "./routes/authRoutes"; import adminRoutes from "./routes/adminRoutes"; import multilangRoutes from "./routes/multilangRoutes"; import tableManagementRoutes from "./routes/tableManagementRoutes"; import entityJoinRoutes from "./routes/entityJoinRoutes"; import screenManagementRoutes from "./routes/screenManagementRoutes"; import commonCodeRoutes from "./routes/commonCodeRoutes"; import dynamicFormRoutes from "./routes/dynamicFormRoutes"; import fileRoutes from "./routes/fileRoutes"; import companyManagementRoutes from "./routes/companyManagementRoutes"; import dataflowRoutes from "./routes/dataflowRoutes"; import dataflowDiagramRoutes from "./routes/dataflowDiagramRoutes"; import webTypeStandardRoutes from "./routes/webTypeStandardRoutes"; import buttonActionStandardRoutes from "./routes/buttonActionStandardRoutes"; import screenStandardRoutes from "./routes/screenStandardRoutes"; import templateStandardRoutes from "./routes/templateStandardRoutes"; import componentStandardRoutes from "./routes/componentStandardRoutes"; import layoutRoutes from "./routes/layoutRoutes"; import mailTemplateFileRoutes from "./routes/mailTemplateFileRoutes"; import mailAccountFileRoutes from "./routes/mailAccountFileRoutes"; import mailSendSimpleRoutes from "./routes/mailSendSimpleRoutes"; import mailSentHistoryRoutes from "./routes/mailSentHistoryRoutes"; import mailReceiveBasicRoutes from "./routes/mailReceiveBasicRoutes"; import dataRoutes from "./routes/dataRoutes"; import testButtonDataflowRoutes from "./routes/testButtonDataflowRoutes"; import externalDbConnectionRoutes from "./routes/externalDbConnectionRoutes"; import externalRestApiConnectionRoutes from "./routes/externalRestApiConnectionRoutes"; import multiConnectionRoutes from "./routes/multiConnectionRoutes"; import screenFileRoutes from "./routes/screenFileRoutes"; //import dbTypeCategoryRoutes from "./routes/dbTypeCategoryRoutes"; import batchRoutes from "./routes/batchRoutes"; import batchManagementRoutes from "./routes/batchManagementRoutes"; import batchExecutionLogRoutes from "./routes/batchExecutionLogRoutes"; // import dbTypeCategoryRoutes from "./routes/dbTypeCategoryRoutes"; // 파일이 존재하지 않음 import ddlRoutes from "./routes/ddlRoutes"; import entityReferenceRoutes from "./routes/entityReferenceRoutes"; import externalCallRoutes from "./routes/externalCallRoutes"; import externalCallConfigRoutes from "./routes/externalCallConfigRoutes"; import dataflowExecutionRoutes from "./routes/dataflowExecutionRoutes"; import dashboardRoutes from "./routes/dashboardRoutes"; import reportRoutes from "./routes/reportRoutes"; import openApiProxyRoutes from "./routes/openApiProxyRoutes"; // 날씨/환율 API import deliveryRoutes from "./routes/deliveryRoutes"; // 배송/화물 관리 import riskAlertRoutes from "./routes/riskAlertRoutes"; // 리스크/알림 관리 import todoRoutes from "./routes/todoRoutes"; // To-Do 관리 import bookingRoutes from "./routes/bookingRoutes"; // 예약 요청 관리 import mapDataRoutes from "./routes/mapDataRoutes"; // 지도 데이터 관리 import yardLayoutRoutes from "./routes/yardLayoutRoutes"; // 3D 필드 //import materialRoutes from "./routes/materialRoutes"; // 자재 관리 import digitalTwinRoutes from "./routes/digitalTwinRoutes"; // 디지털 트윈 (야드 관제) import flowRoutes from "./routes/flowRoutes"; // 플로우 관리 import flowExternalDbConnectionRoutes from "./routes/flowExternalDbConnectionRoutes"; // 플로우 전용 외부 DB 연결 import workHistoryRoutes from "./routes/workHistoryRoutes"; // 작업 이력 관리 import tableHistoryRoutes from "./routes/tableHistoryRoutes"; // 테이블 변경 이력 조회 import roleRoutes from "./routes/roleRoutes"; // 권한 그룹 관리 import departmentRoutes from "./routes/departmentRoutes"; // 부서 관리 import tableCategoryValueRoutes from "./routes/tableCategoryValueRoutes"; // 카테고리 값 관리 import codeMergeRoutes from "./routes/codeMergeRoutes"; // 코드 병합 import numberingRuleRoutes from "./routes/numberingRuleRoutes"; // 채번 규칙 관리 import entitySearchRoutes from "./routes/entitySearchRoutes"; // 엔티티 검색 import screenEmbeddingRoutes from "./routes/screenEmbeddingRoutes"; // 화면 임베딩 및 데이터 전달 import vehicleTripRoutes from "./routes/vehicleTripRoutes"; // 차량 운행 이력 관리 import driverRoutes from "./routes/driverRoutes"; // 공차중계 운전자 관리 import taxInvoiceRoutes from "./routes/taxInvoiceRoutes"; // 세금계산서 관리 import cascadingRelationRoutes from "./routes/cascadingRelationRoutes"; // 연쇄 드롭다운 관계 관리 import cascadingAutoFillRoutes from "./routes/cascadingAutoFillRoutes"; // 자동 입력 관리 import cascadingConditionRoutes from "./routes/cascadingConditionRoutes"; // 조건부 연쇄 관리 import cascadingMutualExclusionRoutes from "./routes/cascadingMutualExclusionRoutes"; // 상호 배제 관리 import cascadingHierarchyRoutes from "./routes/cascadingHierarchyRoutes"; // 다단계 계층 관리 import { BatchSchedulerService } from "./services/batchSchedulerService"; // import collectionRoutes from "./routes/collectionRoutes"; // 임시 주석 // import batchRoutes from "./routes/batchRoutes"; // 임시 주석 // import userRoutes from './routes/userRoutes'; // import menuRoutes from './routes/menuRoutes'; const app = express(); // 기본 미들웨어 app.use( helmet({ contentSecurityPolicy: { directives: { ...helmet.contentSecurityPolicy.getDefaultDirectives(), "frame-ancestors": [ "'self'", "http://localhost:9771", "http://localhost:3000", ], // 프론트엔드 도메인 허용 }, }, }) ); app.use(compression()); app.use(express.json({ limit: "10mb" })); app.use(express.urlencoded({ extended: true, limit: "10mb" })); // 정적 파일 서빙 전에 CORS 미들웨어 추가 (OPTIONS 요청 처리) app.options("/uploads/*", (req, res) => { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); res.sendStatus(200); }); // 정적 파일 서빙 (업로드된 파일들) app.use( "/uploads", (req, res, next) => { // 모든 정적 파일 요청에 CORS 헤더 추가 res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS"); res.setHeader( "Access-Control-Allow-Headers", "Content-Type, Authorization" ); res.setHeader("Cross-Origin-Resource-Policy", "cross-origin"); res.setHeader("Cache-Control", "public, max-age=3600"); next(); }, express.static(path.join(process.cwd(), "uploads")) ); // CORS 설정 - environment.ts에서 이미 올바른 형태로 처리됨 app.use( cors({ origin: config.cors.origin, // 이미 배열 또는 boolean으로 처리됨 credentials: config.cors.credentials, methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"], allowedHeaders: [ "Content-Type", "Authorization", "X-Requested-With", "Accept", "Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", ], preflightContinue: false, optionsSuccessStatus: 200, }) ); // Rate Limiting (개발 환경에서는 완화) const limiter = rateLimit({ windowMs: 1 * 60 * 1000, // 1분 max: config.nodeEnv === "development" ? 10000 : 10000, // 개발환경에서는 10000으로 증가, 운영환경에서는 100 message: { error: "너무 많은 요청이 발생했습니다. 잠시 후 다시 시도해주세요.", }, skip: (req) => { // 헬스 체크와 자주 호출되는 API들은 Rate Limiting 완화 return ( req.path === "/health" || req.path.includes("/table-management/") || req.path.includes("/external-db-connections/") || req.path.includes("/screen-management/") || req.path.includes("/multi-connection/") || req.path.includes("/dataflow-diagrams/") ); }, }); app.use("/api/", limiter); // 토큰 자동 갱신 미들웨어 (모든 API 요청에 적용) // 토큰이 1시간 이내에 만료되는 경우 자동으로 갱신하여 응답 헤더에 포함 app.use("/api/", refreshTokenIfNeeded); // 헬스 체크 엔드포인트 app.get("/health", (req, res) => { res.status(200).json({ status: "OK", timestamp: new Date().toISOString(), uptime: process.uptime(), environment: config.nodeEnv, }); }); // API 라우터 app.use("/api/auth", authRoutes); app.use("/api/admin", adminRoutes); app.use("/api/multilang", multilangRoutes); app.use("/api/table-management", tableManagementRoutes); app.use("/api/table-management", entityJoinRoutes); // 🎯 Entity 조인 기능 app.use("/api/screen-management", screenManagementRoutes); app.use("/api/common-codes", commonCodeRoutes); app.use("/api/dynamic-form", dynamicFormRoutes); app.use("/api/files", fileRoutes); app.use("/api/company-management", companyManagementRoutes); app.use("/api/dataflow", dataflowRoutes); app.use("/api/dataflow-diagrams", dataflowDiagramRoutes); app.use("/api/admin/web-types", webTypeStandardRoutes); app.use("/api/admin/button-actions", buttonActionStandardRoutes); app.use("/api/admin/template-standards", templateStandardRoutes); app.use("/api/admin/component-standards", componentStandardRoutes); app.use("/api/layouts", layoutRoutes); app.use("/api/mail/accounts", mailAccountFileRoutes); // 파일 기반 계정 app.use("/api/mail/templates-file", mailTemplateFileRoutes); // 파일 기반 템플릿 app.use("/api/mail/send", mailSendSimpleRoutes); // 메일 발송 app.use("/api/mail/sent", mailSentHistoryRoutes); // 메일 발송 이력 app.use("/api/mail/receive", mailReceiveBasicRoutes); // 메일 수신 app.use("/api/screen", screenStandardRoutes); app.use("/api/data", dataRoutes); app.use("/api/test-button-dataflow", testButtonDataflowRoutes); app.use("/api/external-db-connections", externalDbConnectionRoutes); app.use("/api/external-rest-api-connections", externalRestApiConnectionRoutes); app.use("/api/multi-connection", multiConnectionRoutes); app.use("/api/screen-files", screenFileRoutes); app.use("/api/batch-configs", batchRoutes); app.use("/api/batch-management", batchManagementRoutes); app.use("/api/batch-execution-logs", batchExecutionLogRoutes); // app.use("/api/db-type-categories", dbTypeCategoryRoutes); // 파일이 존재하지 않음 app.use("/api/ddl", ddlRoutes); app.use("/api/entity-reference", entityReferenceRoutes); app.use("/api/external-calls", externalCallRoutes); app.use("/api/external-call-configs", externalCallConfigRoutes); app.use("/api/dataflow", dataflowExecutionRoutes); app.use("/api/dashboards", dashboardRoutes); app.use("/api/admin/reports", reportRoutes); app.use("/api/open-api", openApiProxyRoutes); // 날씨/환율 외부 API app.use("/api/delivery", deliveryRoutes); // 배송/화물 관리 app.use("/api/risk-alerts", riskAlertRoutes); // 리스크/알림 관리 app.use("/api/todos", todoRoutes); // To-Do 관리 app.use("/api/bookings", bookingRoutes); // 예약 요청 관리 app.use("/api/map-data", mapDataRoutes); // 지도 데이터 조회 app.use("/api/yard-layouts", yardLayoutRoutes); // 3D 필드 // app.use("/api/materials", materialRoutes); // 자재 관리 (임시 주석) app.use("/api/digital-twin", digitalTwinRoutes); // 디지털 트윈 (야드 관제) app.use("/api/flow-external-db", flowExternalDbConnectionRoutes); // 플로우 전용 외부 DB 연결 app.use("/api/flow", flowRoutes); // 플로우 관리 (마지막에 등록하여 다른 라우트와 충돌 방지) app.use("/api/work-history", workHistoryRoutes); // 작업 이력 관리 app.use("/api/table-history", tableHistoryRoutes); // 테이블 변경 이력 조회 app.use("/api/roles", roleRoutes); // 권한 그룹 관리 app.use("/api/departments", departmentRoutes); // 부서 관리 app.use("/api/table-categories", tableCategoryValueRoutes); // 카테고리 값 관리 app.use("/api/code-merge", codeMergeRoutes); // 코드 병합 app.use("/api/numbering-rules", numberingRuleRoutes); // 채번 규칙 관리 app.use("/api/entity-search", entitySearchRoutes); // 엔티티 검색 app.use("/api/driver", driverRoutes); // 공차중계 운전자 관리 app.use("/api/tax-invoice", taxInvoiceRoutes); // 세금계산서 관리 app.use("/api/cascading-relations", cascadingRelationRoutes); // 연쇄 드롭다운 관계 관리 app.use("/api/cascading-auto-fill", cascadingAutoFillRoutes); // 자동 입력 관리 app.use("/api/cascading-conditions", cascadingConditionRoutes); // 조건부 연쇄 관리 app.use("/api/cascading-exclusions", cascadingMutualExclusionRoutes); // 상호 배제 관리 app.use("/api/cascading-hierarchy", cascadingHierarchyRoutes); // 다단계 계층 관리 app.use("/api", screenEmbeddingRoutes); // 화면 임베딩 및 데이터 전달 app.use("/api/vehicle", vehicleTripRoutes); // 차량 운행 이력 관리 // app.use("/api/collections", collectionRoutes); // 임시 주석 // app.use("/api/batch", batchRoutes); // 임시 주석 // app.use('/api/users', userRoutes); // app.use('/api/menus', menuRoutes); // 404 핸들러 app.use("*", (req, res) => { res.status(404).json({ success: false, message: "요청한 리소스를 찾을 수 없습니다.", path: req.originalUrl, }); }); // 에러 핸들러 app.use(errorHandler); // 서버 시작 const PORT = config.port; const HOST = config.host; app.listen(PORT, HOST, async () => { logger.info(`🚀 Server is running on ${HOST}:${PORT}`); logger.info(`📊 Environment: ${config.nodeEnv}`); logger.info(`🔗 Health check: http://${HOST}:${PORT}/health`); logger.info(`🌐 External access: http://39.117.244.52:${PORT}/health`); // 데이터베이스 마이그레이션 실행 try { const { runDashboardMigration, runTableHistoryActionMigration, runDtgManagementLogMigration, } = await import("./database/runMigration"); await runDashboardMigration(); await runTableHistoryActionMigration(); await runDtgManagementLogMigration(); } catch (error) { logger.error(`❌ 마이그레이션 실패:`, error); } // 배치 스케줄러 초기화 try { await BatchSchedulerService.initializeScheduler(); logger.info(`⏰ 배치 스케줄러가 시작되었습니다.`); } catch (error) { logger.error(`❌ 배치 스케줄러 초기화 실패:`, error); } // 리스크/알림 자동 갱신 시작 try { const { RiskAlertCacheService } = await import( "./services/riskAlertCacheService" ); const cacheService = RiskAlertCacheService.getInstance(); cacheService.startAutoRefresh(); logger.info(`⏰ 리스크/알림 자동 갱신이 시작되었습니다. (10분 간격)`); } catch (error) { logger.error(`❌ 리스크/알림 자동 갱신 시작 실패:`, error); } // 메일 자동 삭제 (30일 지난 삭제된 메일) - 매일 새벽 2시 실행 try { const cron = await import("node-cron"); const { mailSentHistoryService } = await import( "./services/mailSentHistoryService" ); cron.schedule("0 2 * * *", async () => { try { logger.info("🗑️ 30일 지난 삭제된 메일 자동 삭제 시작..."); const deletedCount = await mailSentHistoryService.cleanupOldDeletedMails(); logger.info(`✅ 30일 지난 메일 ${deletedCount}개 자동 삭제 완료`); } catch (error) { logger.error("❌ 메일 자동 삭제 실패:", error); } }); logger.info(`⏰ 메일 자동 삭제 스케줄러가 시작되었습니다. (매일 새벽 2시)`); } catch (error) { logger.error(`❌ 메일 자동 삭제 스케줄러 시작 실패:`, error); } }); export default app;