diff --git a/backend-node/src/app.ts b/backend-node/src/app.ts index 3b567a0a..465570ce 100644 --- a/backend-node/src/app.ts +++ b/backend-node/src/app.ts @@ -27,11 +27,11 @@ app.use(compression()); app.use(express.json({ limit: "10mb" })); app.use(express.urlencoded({ extended: true, limit: "10mb" })); -// CORS 설정 +// CORS 설정 - environment.ts에서 이미 올바른 형태로 처리됨 app.use( cors({ - origin: config.cors.origin.split(",").map((url) => url.trim()), - credentials: true, + origin: config.cors.origin, // 이미 배열 또는 boolean으로 처리됨 + credentials: config.cors.credentials, methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"], allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"], }) diff --git a/backend-node/src/config/environment.ts b/backend-node/src/config/environment.ts index 56478ae3..62fe0635 100644 --- a/backend-node/src/config/environment.ts +++ b/backend-node/src/config/environment.ts @@ -26,7 +26,7 @@ interface Config { // CORS 설정 cors: { - origin: string; + origin: string | string[] | boolean; // 타입을 확장하여 배열과 boolean도 허용 credentials: boolean; }; @@ -58,6 +58,26 @@ interface Config { showErrorDetails: boolean; } +// CORS origin 처리 함수 +const getCorsOrigin = (): string[] | boolean => { + // 개발 환경에서는 모든 origin 허용 + if (process.env.NODE_ENV === "development") { + return true; + } + + // 환경변수가 있으면 쉼표로 구분하여 배열로 변환 + if (process.env.CORS_ORIGIN) { + return process.env.CORS_ORIGIN.split(",").map((origin) => origin.trim()); + } + + // 기본값: 허용할 도메인들 + return [ + "http://localhost:9771", // 로컬 개발 환경 + "http://192.168.0.70:5555", // 내부 네트워크 접근 + "http://39.117.244.52:5555", // 외부 네트워크 접근 + ]; +}; + const config: Config = { // 서버 설정 port: parseInt(process.env.PORT || "3000", 10), @@ -82,8 +102,8 @@ const config: Config = { // CORS 설정 cors: { - origin: process.env.CORS_ORIGIN || "http://localhost:9771,http://192.168.0.70:5555,http://39.117.244.52:5555", - credentials: process.env.CORS_CREDENTIALS === "true" || true, + origin: getCorsOrigin(), + credentials: true, // 쿠키 및 인증 정보 포함 허용 }, // 로깅 설정 diff --git a/docker/prod/docker-compose.backend.prod.yml b/docker/prod/docker-compose.backend.prod.yml index 3cded048..85a0d189 100644 --- a/docker/prod/docker-compose.backend.prod.yml +++ b/docker/prod/docker-compose.backend.prod.yml @@ -13,7 +13,7 @@ services: - DATABASE_URL=postgresql://postgres:ph0909!!@39.117.244.52:11132/plm - JWT_SECRET=ilshin-plm-super-secret-jwt-key-2024 - JWT_EXPIRES_IN=24h - - CORS_ORIGIN=http://192.168.0.70:5555,http://39.117.244.52:5555 + - CORS_ORIGIN=http://192.168.0.70:5555,http://39.117.244.52:5555,http://localhost:9771 - CORS_CREDENTIALS=true - LOG_LEVEL=info restart: unless-stopped diff --git a/docker/prod/docker-compose.frontend.prod.yml b/docker/prod/docker-compose.frontend.prod.yml index 7878cde0..de07bec4 100644 --- a/docker/prod/docker-compose.frontend.prod.yml +++ b/docker/prod/docker-compose.frontend.prod.yml @@ -5,13 +5,13 @@ services: context: ../../frontend dockerfile: ../docker/prod/frontend.Dockerfile args: - - NEXT_PUBLIC_API_URL=http://192.168.0.70:8080/api + - NEXT_PUBLIC_API_URL=http://39.117.244.52:8080/api container_name: pms-frontend-linux ports: - "5555:5555" environment: - NODE_ENV=production - - NEXT_PUBLIC_API_URL=http://192.168.0.70:8080/api + - NEXT_PUBLIC_API_URL=http://39.117.244.52:8080/api networks: - pms-network restart: unless-stopped diff --git a/docker/prod/frontend.Dockerfile b/docker/prod/frontend.Dockerfile index c3703011..17df01e2 100644 --- a/docker/prod/frontend.Dockerfile +++ b/docker/prod/frontend.Dockerfile @@ -22,6 +22,10 @@ COPY . . # Disable telemetry during the build ENV NEXT_TELEMETRY_DISABLED 1 +# 빌드 시 환경변수 설정 (ARG로 받아서 ENV로 설정) +ARG NEXT_PUBLIC_API_URL=http://192.168.0.70:8080/api +ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL + # Build the application ENV DISABLE_ESLINT_PLUGIN=true RUN npm run build diff --git a/frontend/constants/auth.ts b/frontend/constants/auth.ts index 3d9b67eb..c1880a69 100644 --- a/frontend/constants/auth.ts +++ b/frontend/constants/auth.ts @@ -3,7 +3,7 @@ */ export const AUTH_CONFIG = { - API_BASE_URL: process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080", + API_BASE_URL: process.env.NEXT_PUBLIC_API_URL || "http://39.117.244.52:8080/api", ENDPOINTS: { LOGIN: "/auth/login", STATUS: "/auth/status", @@ -15,18 +15,21 @@ export const AUTH_CONFIG = { }, } as const; +export const UI_CONFIG = { + COMPANY_NAME: "WACE 솔루션", + COPYRIGHT: "© 2024 WACE 솔루션. All rights reserved.", + POWERED_BY: "Powered by WACE PLM System", +} as const; + export const FORM_VALIDATION = { MESSAGES: { + REQUIRED: "필수 입력 항목입니다.", + INVALID_FORMAT: "형식이 올바르지 않습니다.", + PASSWORD_MISMATCH: "비밀번호가 일치하지 않습니다.", + INVALID_CREDENTIALS: "아이디 또는 비밀번호가 올바르지 않습니다.", USER_ID_REQUIRED: "사용자 ID를 입력해주세요.", PASSWORD_REQUIRED: "비밀번호를 입력해주세요.", LOGIN_FAILED: "로그인에 실패했습니다.", - CONNECTION_FAILED: "서버 연결에 실패했습니다. 잠시 후 다시 시도해주세요.", - BACKEND_CONNECTION_FAILED: "백엔드 서버에 연결할 수 없습니다.", + CONNECTION_FAILED: "서버 연결에 실패했습니다.", }, } as const; - -export const UI_CONFIG = { - COMPANY_NAME: "WACE 솔루션", - COPYRIGHT: "© 2025 WACE PLM Solution. All rights reserved.", - POWERED_BY: "Powered by Spring Boot + Next.js", -} as const; diff --git a/frontend/constants/layout.ts b/frontend/constants/layout.ts index 8268dbba..e245b1de 100644 --- a/frontend/constants/layout.ts +++ b/frontend/constants/layout.ts @@ -4,7 +4,7 @@ export const LAYOUT_CONFIG = { COMPANY_NAME: "WACE 솔루션", - API_BASE_URL: process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080", + API_BASE_URL: process.env.NEXT_PUBLIC_API_URL || "http://39.117.244.52:8080/api", ENDPOINTS: { USER_MENUS: "/admin/user-menus", @@ -24,18 +24,9 @@ export const LAYOUT_CONFIG = { export const MESSAGES = { LOADING: "로딩 중...", - NO_MENUS: "메뉴가 없습니다.", - PROFILE_SAVE_SUCCESS: "프로필이 성공적으로 저장되었습니다.", - PROFILE_SAVE_ERROR: "프로필 저장 중 오류가 발생했습니다.", - FILE_SIZE_ERROR: "파일 크기는 5MB를 초과할 수 없습니다.", - FILE_TYPE_ERROR: "이미지 파일만 업로드 가능합니다.", -} as const; - -export const MENU_ICONS = { - DEFAULT: "FileText", - HOME: ["홈", "메인"], - DOCUMENT: ["문서", "게시"], - USERS: ["사용자", "회원"], - STATISTICS: ["통계", "현황"], - SETTINGS: ["설정", "관리"], + ERROR: "오류가 발생했습니다.", + SUCCESS: "성공적으로 처리되었습니다.", + CONFIRM: "정말로 진행하시겠습니까?", + NO_DATA: "데이터가 없습니다.", + NO_MENUS: "사용 가능한 메뉴가 없습니다.", } as const; diff --git a/frontend/hooks/useAuth.ts b/frontend/hooks/useAuth.ts index deb05ab3..99376992 100644 --- a/frontend/hooks/useAuth.ts +++ b/frontend/hooks/useAuth.ts @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; -import { apiCall } from "@/lib/api/client"; +import { apiCall, API_BASE_URL } from "@/lib/api/client"; // 사용자 정보 타입 정의 interface UserInfo { @@ -98,8 +98,7 @@ export const useAuth = () => { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - // API 기본 URL 설정 - const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080"; + // API 기본 URL 설정 (동적으로 결정) /** * 현재 사용자 정보 조회 diff --git a/frontend/hooks/useLogin.ts b/frontend/hooks/useLogin.ts index 733716d6..5abffac9 100644 --- a/frontend/hooks/useLogin.ts +++ b/frontend/hooks/useLogin.ts @@ -4,6 +4,7 @@ import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import { LoginFormData, LoginResponse } from "@/types/auth"; import { AUTH_CONFIG, FORM_VALIDATION } from "@/constants/auth"; +import { API_BASE_URL } from "@/lib/api/client"; /** * 로그인 관련 비즈니스 로직을 관리하는 커스텀 훅 @@ -60,7 +61,7 @@ export const useLogin = () => { * API 호출 공통 함수 */ const apiCall = useCallback(async (endpoint: string, options: RequestInit = {}): Promise => { - const response = await fetch(`${AUTH_CONFIG.API_BASE_URL}${endpoint}`, { + const response = await fetch(`${API_BASE_URL}${endpoint}`, { credentials: "include", headers: { "Content-Type": "application/json", diff --git a/frontend/lib/api/client.ts b/frontend/lib/api/client.ts index 659a512a..ecd20973 100644 --- a/frontend/lib/api/client.ts +++ b/frontend/lib/api/client.ts @@ -1,6 +1,43 @@ import axios, { AxiosResponse, AxiosError } from "axios"; -export const API_BASE_URL = "http://39.117.244.52:8080/api"; +// API URL 동적 설정 - 환경별 명확한 분리 +const getApiBaseUrl = (): string => { + console.log("🔍 API URL 결정 시작!"); + + if (typeof window !== "undefined") { + const currentHost = window.location.hostname; + const currentPort = window.location.port; + const fullUrl = window.location.href; + + console.log("🌐 현재 접속 정보:", { + hostname: currentHost, + fullUrl: fullUrl, + port: currentPort, + }); + + // 로컬 개발환경: localhost:9771 → localhost:8080 + if ((currentHost === "localhost" || currentHost === "127.0.0.1") && currentPort === "9771") { + console.log("🏠 로컬 개발 환경 감지 → localhost:8080/api"); + return "http://localhost:8080/api"; + } + + // 서버 환경에서 localhost:5555 → 39.117.244.52:8080 + if ((currentHost === "localhost" || currentHost === "127.0.0.1") && currentPort === "5555") { + console.log("🌍 서버 환경 (localhost:5555) 감지 → 39.117.244.52:8080/api"); + return "http://39.117.244.52:8080/api"; + } + + // 기타 서버 환경 (내부/외부 IP): → 39.117.244.52:8080 + console.log("🌍 서버 환경 감지 → 39.117.244.52:8080/api"); + return "http://39.117.244.52:8080/api"; + } + + // 서버 사이드 렌더링 기본값 + console.log("🖥️ SSR 기본값 → 39.117.244.52:8080/api"); + return "http://39.117.244.52:8080/api"; +}; + +export const API_BASE_URL = getApiBaseUrl(); // JWT 토큰 관리 유틸리티 const TokenManager = { diff --git a/frontend/lib/api/company.ts b/frontend/lib/api/company.ts index 0d9e85ca..ea5aa700 100644 --- a/frontend/lib/api/company.ts +++ b/frontend/lib/api/company.ts @@ -5,7 +5,7 @@ import { Company, CompanyFormData } from "@/types/company"; import { apiClient } from "./client"; -const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "/api"; +const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://39.117.244.52:8080/api"; // API 응답 타입 정의 interface ApiResponse { diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs index 50f2755d..5323571a 100644 --- a/frontend/next.config.mjs +++ b/frontend/next.config.mjs @@ -41,7 +41,7 @@ const nextConfig = { ]; }, - // 환경 변수 (런타임에 읽기) - 내부 IP로 통일 + // 환경 변수 (런타임에 읽기) env: { NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || "http://39.117.244.52:8080/api", },