From 5e4044d46199696b9173349d5b24c3bbaf2856ed Mon Sep 17 00:00:00 2001 From: htlee Date: Sun, 1 Mar 2026 01:36:07 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=ED=95=98=EB=93=9C=EC=BD=94=EB=94=A9=20U?= =?UTF-8?q?RL=20=EC=A0=9C=EA=B1=B0=20+=20=ED=99=98=EA=B2=BD=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - App.tsx: 중복 API_BASE_URL 정의 → @common/services/api import - MapView.tsx: GeoServer localhost:8080 → VITE_GEOSERVER_URL 환경변수 - ShipInsurance.tsx: 해운조합 API URL → VITE_HAEWOON_API_URL 환경변수 - server.ts CORS: 운영 도메인 → FRONTEND_URL 환경변수 통합 - server.ts CSP: localhost 허용을 개발 환경(NODE_ENV≠production)에만 적용 Co-Authored-By: Claude Opus 4.6 --- backend/src/server.ts | 19 +++++++++++++------ frontend/src/App.tsx | 4 ++-- .../src/common/components/map/MapView.tsx | 4 +++- .../tabs/assets/components/ShipInsurance.tsx | 4 +++- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/backend/src/server.ts b/backend/src/server.ts index 25e2865..6efd807 100755 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -49,7 +49,13 @@ app.use(helmet({ scriptSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", "data:", "blob:"], - connectSrc: ["'self'", "http://localhost:*", "https://*.gc-si.dev", "https://*.data.go.kr", "https://*.khoa.go.kr"], + connectSrc: [ + "'self'", + ...(process.env.NODE_ENV !== 'production' ? ['http://localhost:*'] : []), + 'https://*.gc-si.dev', + 'https://*.data.go.kr', + 'https://*.khoa.go.kr', + ], fontSrc: ["'self'"], objectSrc: ["'none'"], frameSrc: ["'none'"], @@ -65,11 +71,12 @@ app.disable('x-powered-by') // 3. CORS: 허용된 출처만 접근 가능 const allowedOrigins = [ - 'http://localhost:5173', // Vite dev server - 'http://localhost:5174', - 'http://localhost:3000', - 'https://wing-demo.gc-si.dev', - process.env.FRONTEND_URL, // 운영 환경 프론트엔드 URL (추가 도메인) + process.env.FRONTEND_URL || 'https://wing-demo.gc-si.dev', + ...(process.env.NODE_ENV !== 'production' ? [ + 'http://localhost:5173', + 'http://localhost:5174', + 'http://localhost:3000', + ] : []), ].filter(Boolean) as string[] app.use(cors({ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7059d68..66dce36 100755 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,6 +6,7 @@ import { LoginPage } from '@common/components/auth/LoginPage' import { registerMainTabSwitcher } from '@common/hooks/useSubMenu' import { useAuthStore } from '@common/store/authStore' import { useMenuStore } from '@common/store/menuStore' +import { API_BASE_URL } from '@common/services/api' import { OilSpillView } from '@tabs/prediction' import { ReportsView } from '@tabs/reports' import { HNSView } from '@tabs/hns' @@ -46,8 +47,7 @@ function App() { [JSON.stringify({ action: 'TAB_VIEW', detail: activeMainTab })], { type: 'text/plain' } ) - const apiBase = import.meta.env.VITE_API_URL || 'http://localhost:3001/api' - navigator.sendBeacon(`${apiBase}/audit/log`, blob) + navigator.sendBeacon(`${API_BASE_URL}/audit/log`, blob) }, [activeMainTab, isAuthenticated]) // 세션 확인 중 스플래시 diff --git a/frontend/src/common/components/map/MapView.tsx b/frontend/src/common/components/map/MapView.tsx index fce41a6..50afc57 100755 --- a/frontend/src/common/components/map/MapView.tsx +++ b/frontend/src/common/components/map/MapView.tsx @@ -9,6 +9,8 @@ import type { BoomLine, BoomLineCoord } from '@common/types/boomLine' import type { ReplayShip, CollisionEvent } from '@common/types/backtrack' import { BacktrackReplayOverlay } from './BacktrackReplayOverlay' +const GEOSERVER_URL = import.meta.env.VITE_GEOSERVER_URL || 'http://localhost:8080' + // Fix Leaflet default icon issue import icon from 'leaflet/dist/images/marker-icon.png' import iconShadow from 'leaflet/dist/images/marker-shadow.png' @@ -190,7 +192,7 @@ export function MapView({ {wmsLayers.map(layer => ( diff --git a/frontend/src/tabs/assets/components/ShipInsurance.tsx b/frontend/src/tabs/assets/components/ShipInsurance.tsx index 47dbeb6..862aaee 100644 --- a/frontend/src/tabs/assets/components/ShipInsurance.tsx +++ b/frontend/src/tabs/assets/components/ShipInsurance.tsx @@ -1,6 +1,8 @@ import { useState } from 'react' import type { InsuranceRow } from './assetTypes' +const DEFAULT_HAEWOON_API = import.meta.env.VITE_HAEWOON_API_URL || 'https://api.haewoon.or.kr/v1/insurance' + // 샘플 데이터 (외부 한국해운조합 API 연동 전 데모용) const INSURANCE_DEMO_DATA: InsuranceRow[] = [ { shipName: '유조선 한라호', mmsi: '440123456', imo: '9876001', insType: 'P&I 보험', insurer: '한국P&I클럽', policyNo: 'PI-2025-1234', start: '2025-07-01', expiry: '2026-06-30', limit: '50억' }, @@ -13,7 +15,7 @@ const INSURANCE_DEMO_DATA: InsuranceRow[] = [ function ShipInsurance() { const [apiConnected, setApiConnected] = useState(false) const [showConfig, setShowConfig] = useState(false) - const [configEndpoint, setConfigEndpoint] = useState('https://api.haewoon.or.kr/v1/insurance') + const [configEndpoint, setConfigEndpoint] = useState(DEFAULT_HAEWOON_API) const [configApiKey, setConfigApiKey] = useState('') const [configKeyType, setConfigKeyType] = useState('mmsi') const [configRespType, setConfigRespType] = useState('json')