refactor(css): CSS 리팩토링 + 문서 전면 갱신 #61

병합
htlee feature/css-refactoring 에서 main 로 2 commits 를 머지했습니다 2026-03-01 14:07:32 +09:00
10개의 변경된 파일5749개의 추가작업 그리고 2724개의 파일을 삭제

1
.gitignore vendored
파일 보기

@ -29,6 +29,7 @@ backend/data/*.db-wal
# Large reference data (keep locally, do not commit)
_reference/
docs/_backup_*/
/scat/
참고용/
논문/

137
CLAUDE.md
파일 보기

@ -1,123 +1,132 @@
# WING-OPS (해양 방제 운영 지원 시스템)
## 프로젝트 개요
해양 오염 사고 대응을 위한 방제 운영 지원 시스템.
유류/HNS 확산 예측, 역추적 분석, 구조 시나리오, 항공 방제, 자산 관리, SCAT 조사, 기상/해상 정보를 통합 제공한다.
해양 오염 사고 대응 방제 운영 지원 시스템. 유류/HNS 확산 예측, 역추적 분석, 구조 시나리오, 항공 방제, 자산 관리, SCAT 조사, 기상/해상 정보를 통합 제공한다.
- **프로젝트 타입**: react-ts (모노레포)
- **타입**: react-ts 모노레포 (frontend + backend)
- **Frontend**: React 19 + Vite 7 + TypeScript 5.9 + Tailwind CSS 3
- **Backend**: Express 4 + PostgreSQL (pg) + TypeScript
- **DB**: PostgreSQL 16 + PostGIS (wing 운영DB + wing_auth 인증DB)
- **상태관리**: Zustand (클라이언트), TanStack Query (서버)
- **지도**: Leaflet + react-leaflet
- **지도**: MapLibre GL JS 5.x + deck.gl 9.x (Leaflet 제거됨)
- **실시간**: Socket.IO
- **인증**: JWT (HttpOnly Cookie) + Google OAuth
- **CI/CD**: Gitea Actions
- **CSS**: Tailwind @layer 아키텍처 (base.css, components.css, wing.css)
- **HTTP 정책**: GET/POST만 사용 (PUT/DELETE/PATCH 금지, 한국 보안취약점 점검 가이드 준수)
- **RBAC**: AUTH_PERM OPER_CD (RCUD), permResolver 2차원 권한 엔진
## 빌드/실행
### Frontend
```bash
cd frontend
npm install
npm run dev # 개발 서버 (Vite, localhost:5173)
npm run build # 프로덕션 빌드 (tsc -b && vite build)
npm run lint # ESLint 검증
npm run preview # 빌드 미리보기
cd frontend && npm install # Frontend
npm run dev # Vite dev (localhost:5173)
npm run build # tsc -b && vite build
npm run lint # ESLint
cd backend && npm install # Backend
npm run dev # tsx watch (localhost:3001)
npm run build && npm start # 프로덕션
npm run db:seed # DB 초기 데이터
```
### Backend
```bash
cd backend
npm install
npm run dev # 개발 서버 (tsx watch, localhost:3001)
npm run build # TypeScript 컴파일 (tsc)
npm start # 프로덕션 실행
npm run db:seed # DB 초기 데이터
```
테스트 프레임워크 미구성 (향후 Vitest + React Testing Library 도입 예정).
## 테스트
테스트 프레임워크 미구성. 향후 Vitest + React Testing Library 도입 예정.
## Lint/Format
```bash
cd frontend && npx eslint . # ESLint (flat config)
npx prettier --check . # Prettier 검증
npx prettier --write . # Prettier 자동 수정
cd frontend && npx eslint . # ESLint (flat config)
npx prettier --write . # Prettier 자동 수정
```
## 프로젝트 구조
```
wing/
├── frontend/ React 19 + Vite + TypeScript + Tailwind
├── frontend/ React 19 + Vite 7 + TypeScript + Tailwind
│ └── src/
│ ├── App.tsx 메인 (탭 라우팅, 감사 로그 자동 기록)
│ ├── index.css @tailwind + @import 엔트리포인트
│ ├── common/ 공통 모듈 (@common/ alias)
│ │ ├── components/ auth/, layer/, layout/, map/, ui/
│ │ ├── hooks/ useLayers, useSubMenu
│ │ ├── hooks/ useLayers, useSubMenu, useFeatureTracking
│ │ ├── services/ api.ts, authApi.ts, layerService.ts
│ │ ├── store/ authStore, menuStore (Zustand)
│ │ ├── styles/ base.css, components.css, wing.css
│ │ ├── constants/ featureIds.ts
│ │ ├── types/ backtrack, boomLine, hns, navigation
│ │ ├── utils/ coordinates, geo, sanitize
│ │ ├── data/ layerData.ts (UI 레이어 트리)
│ │ └── mock/ vesselMockData, backtrackMockData
│ │ ├── utils/ coordinates, geo, sanitize, cn.ts
│ │ └── data/ layerData.ts (UI 레이어 트리)
│ └── tabs/ 탭 단위 패키지 (@tabs/ alias)
│ ├── prediction/ 확산 예측 (OilSpillView, LeftPanel 등)
│ ├── hns/ HNS 분석 (HNSView, HNSSubstanceView 등)
│ ├── prediction/ 확산 예측 (OilSpillView, 역추적, 오일붐)
│ ├── hns/ HNS 분석 (시나리오, 물질 DB, 재계산)
│ ├── rescue/ 구조 시나리오
│ ├── aerial/ 항공 방제
│ ├── weather/ 해양 기상 (오버레이, hooks, services)
│ ├── aerial/ 항공 방제 (위성영상, 드론)
│ ├── weather/ 해양 기상 (KHOA API, 오버레이)
│ ├── incidents/ 사건/사고 관리
│ ├── board/ 게시판
│ ├── reports/ 보고서
│ ├── assets/ 자산 관리
│ ├── scat/ Pre-SCAT 조사
│ └── admin/ 관리자 (사용자/권한/메뉴/설정)
│ ├── assets/ 자산 관리 (기관, 장비, 선박보험)
│ ├── scat/ Pre-SCAT 해안조사
│ └── admin/ 관리자 (사용자/역할/권한/메뉴/설정)
├── backend/ Express + TypeScript
│ └── src/
│ ├── server.ts 진입점 + 라우터 등록
│ ├── auth/ 인증 (JWT, OAuth, 미들웨어)
│ ├── users/ 사용자 관리
│ ├── roles/ 역할/권한 관리
│ ├── roles/ 역할/권한 관리 (permResolver)
│ ├── settings/ 시스템 설정
│ ├── menus/ 메뉴 설정
│ ├── audit/ 감사 로그
│ ├── board/ 게시판 CRUD
│ ├── reports/ 보고서 CRUD
│ ├── assets/ 자산 관리 CRUD
│ ├── incidents/ 사건/사고 CRUD
│ ├── scat/ SCAT 조사 CRUD
│ ├── prediction/ 확산 예측 CRUD
│ ├── aerial/ 항공 방제 CRUD
│ ├── rescue/ 구조 시나리오 CRUD
│ ├── hns/ HNS 물질 검색 API
│ ├── routes/ 레이어, 시뮬레이션
│ ├── middleware/ 보안 (입력 살균, rate-limit)
│ ├── middleware/ 보안 (입력 살균, rate-limit, Helmet CORP cross-origin)
│ └── db/ DB 연결 (wingDb, authDb), seed
├── database/ SQL 스크립트
│ ├── init.sql wing DB 초기 스키마
│ ├── auth_init.sql wing_auth DB 초기 스키마
│ └── migration/ 마이그레이션 (001_layer, 002_hns_substance)
├── database/ SQL 스크립트 + 마이그레이션 (001~016)
├── docs/ 개발 문서
├── .claude/ 팀 워크플로우 (rules, skills, scripts)
├── .claude/ 팀 워크플로우 (rules, skills, scripts, agents)
└── .githooks/ Git hooks (pre-commit, commit-msg)
```
### Path Alias
- `@common/*``src/common/*` (공통 모듈)
- `@tabs/*``src/tabs/*` (탭 패키지)
- `@common/*` -> `src/common/*` (공통 모듈)
- `@tabs/*` -> `src/tabs/*` (탭 패키지)
## 팀 컨벤션
`.claude/rules/` 디렉토리 참조:
- `team-policy.md` — 보안/품질 정책
- `git-workflow.md` — 브랜치/커밋/MR 규칙
- `code-style.md` — TypeScript/React 코드 스타일
- `naming.md` — 네이밍 규칙
- `testing.md` — 테스트 규칙
## 개발 문서 (`docs/`)
- `docs/README.md` — 프로젝트 개요, 초기 세팅, 워크플로우 요약, 문서 안내
- `docs/DEVELOPMENT-GUIDE.md` — 개발 워크플로우 전체 흐름 (Plan → Branch → MR → Deploy)
- `docs/COMMON-GUIDE.md` — 공통 로직 개발 가이드 (인증, 감사로그, 메뉴, API 통신, 상태 관리)
- `docs/MENU-TAB-GUIDE.md` — 새 메뉴 탭 추가 절차 (5단계)
- `docs/INSTALL_GUIDE.md` — 설치 매뉴얼 (온라인/오프라인, DB)
- `docs/CHANGELOG.md` — 변경 이력
`.claude/rules/` 디렉토리 참조:
- `team-policy.md` -- 보안/품질 정책
- `git-workflow.md` -- 브랜치/커밋/MR 규칙
- `code-style.md` -- TypeScript/React 코드 스타일
- `naming.md` -- 네이밍 규칙
- `testing.md` -- 테스트 규칙
- `subagent-policy.md` -- 서브에이전트 활용 정책
## 개발 문서 (docs/)
- `docs/README.md` -- 프로젝트 아키텍처 상세
- `docs/DEVELOPMENT-GUIDE.md` -- 개발 워크플로우 전체 흐름
- `docs/COMMON-GUIDE.md` -- 공통 로직 가이드 (인증, 감사로그, 메뉴, API 통신, 상태관리, RBAC, 지도, CSS)
- `docs/MENU-TAB-GUIDE.md` -- 새 메뉴 탭 추가 절차 (5단계)
- `docs/CRUD-API-GUIDE.md` -- CRUD API 개발 가이드 (DB -> 백엔드 -> 프론트 End-to-End)
- `docs/MOCK-TO-API-GUIDE.md` -- Mock -> API 전환 프로세스
- `docs/INSTALL_GUIDE.md` -- 설치 매뉴얼 (온라인/오프라인, DB 초기화)
- `docs/CHANGELOG.md` -- 변경 이력
### 문서 최신화 규칙
- 공통 기능(인증, 감사로그, 메뉴 시스템, API 통신 등)을 추가/변경할 때 반드시 `docs/COMMON-GUIDE.md`를 최신화할 것
- 개별 탭 개발자는 이 문서를 참조하여 공통 영역과의 연동을 구현
- 공통 기능을 추가/변경할 때 반드시 `docs/COMMON-GUIDE.md`를 최신화할 것
- API 인터페이스 변경 시 `memory/api-types.md` 갱신
- 개별 탭 개발자는 공통 가이드를 참조하여 연동 구현
## 환경 설정
- Node.js 20 (`.node-version`, fnm 사용)
- npm registry: Nexus proxy (`.npmrc`)
- Git hooks: `.githooks/` (core.hooksPath 설정됨)

181
README.md
파일 보기

@ -1,167 +1,164 @@
# WING-OPS (해양 방제 운영 지원 시스템)
# WING-OPS
해양 오염 사고 대응을 위한 방제 운영 지원 시스템.
유류/HNS 확산 예측, 역추적 분석, 구조 시나리오, 항공 방제, 자산 관리, SCAT 조사, 기상/해상 정보를 통합 제공합니다.
유류/HNS 확산 예측, 역추적 분석, 구조 시나리오, 항공 방제, 자산 관리, SCAT 조사, 기상/해상 정보를 통합 제공다.
---
## 1. 시작하기
### 1-1. 저장소 복제
## 시작하기
```bash
git clone https://gitea.gc-si.dev/gc/wing-ops.git
cd wing-ops
```
# 1. 저장소 복제
git clone https://gitea.gc-si.dev/gc/wing-ops.git && cd wing-ops
### 1-2. Claude Code 초기화
```bash
# Claude Code 세션 열기
# 2. Claude Code 초기화 (.claude/, .githooks/, 메모리 디렉토리 자동 구성)
claude
# 팀 워크플로우 초기화
/init-project
```
`/init-project` 실행 시 자동으로 구성되는 항목:
- `.claude/` 디렉토리 (rules, skills, scripts, settings)
- `.githooks/` (pre-commit, commit-msg 자동 검증)
- Git hooks 경로 설정 (`core.hooksPath`)
- 메모리 디렉토리 초기화
### 1-3. 의존성 설치 및 실행
```bash
# 백엔드 (터미널 1)
# 3. 백엔드 (터미널 1)
cd backend && npm install && npm run dev # localhost:3001
# 프론트엔드 (터미널 2)
# 4. 프론트엔드 (터미널 2)
cd frontend && npm install && npm run dev # localhost:5173
```
> 사전 요구사항: Node.js 20+ (`.node-version`, fnm 사용), PostgreSQL 16+ (운영 DB 직접 연결)
>
> 상세 설치 절차(오프라인 환경, DB 초기화 등)는 [docs/INSTALL_GUIDE.md](docs/INSTALL_GUIDE.md)를 참조하세요.
사전 요구사항: Node.js 20+ (`.node-version`, fnm 사용), PostgreSQL 16+ (원격 DB 직접 연결)
상세 설치 절차: [docs/INSTALL_GUIDE.md](docs/INSTALL_GUIDE.md)
### 빌드 및 검증
```bash
cd frontend && npm run build # tsc -b && vite build
cd frontend && npx eslint . # ESLint 검증
cd backend && npm run build # tsc
cd backend && npm run db:seed # DB 초기 데이터
```
---
## 2. 개발 워크플로우
## 개발 워크플로우
```
계획 → 브랜치 → 개발 → 커밋/푸시 → develop MR → main PR → 자동 배포
계획 -> 브랜치 -> 개발 -> 커밋/푸시 -> develop MR -> main PR -> 자동 배포
```
| 단계 | 작업 | Claude 스킬 |
|------|------|-------------|
| 1. 계획 | 3개+ 파일 수정 시 Claude가 Plan Mode 진입 | (자동) |
| 2. 브랜치 | `feature/기능명` 으로 develop에서 분기 | - |
| 3. 개발 | Claude가 코드 작성 + 타입/린트 검증 | - |
| 4. 커밋/푸시 | pre-commit 자동 검증 후 푸시 | `/push` |
| 5. develop MR | feature → develop MR 생성 | `/mr` |
| 6. 릴리즈 | develop → main PR 생성 | `/release` |
| 7. 배포 | main 머지 시 Gitea Actions 자동 배포 | - |
| 계획 | 3개+ 파일 수정 시 Plan Mode 자동 진입 | (자동) |
| 브랜치 | `feature/기능명`으로 develop에서 분기 | - |
| 개발 | 코드 작성 + 타입/린트 검증 | - |
| 커밋/푸시 | pre-commit 자동 검증 후 푸시 | `/push` |
| develop MR | feature -> develop MR 생성 | `/mr` |
| 릴리즈 | develop -> main PR 생성 | `/release` |
| 배포 | main 머지 시 Gitea Actions 자동 배포 | - |
> 상세 워크플로우(브랜치 규칙, 커밋 형식, MR 절차, 배포 확인, 실전 예시)는 [docs/DEVELOPMENT-GUIDE.md](docs/DEVELOPMENT-GUIDE.md)를 참조하세요.
상세 워크플로우: [docs/DEVELOPMENT-GUIDE.md](docs/DEVELOPMENT-GUIDE.md)
---
## 3. 탭 개발
## 탭 개발
개별 탭(기능 화면)을 개발할 때 아래 공통 기능을 활용합니다.
11개 탭: prediction, hns, rescue, aerial, weather, incidents, board, reports, assets, scat, admin
| 기능 | 프론트엔드 | 백엔드 | 상세 |
|------|-----------|--------|------|
| 인증/인가 | `authStore`, `api.ts` (자동 쿠키) | `requireAuth`, `requireRole` | [COMMON-GUIDE.md #1](docs/COMMON-GUIDE.md#1-인증인가) |
| 감사 로그 | 탭 이동 자동 기록 (sendBeacon) | `audit/` 모듈 | [COMMON-GUIDE.md #2](docs/COMMON-GUIDE.md#2-감사-로그-audit-log) |
| 메뉴 시스템 | `menuStore` | `menus/`, `settings/` | [COMMON-GUIDE.md #3](docs/COMMON-GUIDE.md#3-메뉴-시스템) |
| API 통신 | `api.ts` (Axios + 인터셉터) | Express 라우터 | [COMMON-GUIDE.md #4](docs/COMMON-GUIDE.md#4-api-통신-패턴) |
| API 통신 | `api.ts` (Axios + 인터셉터) | Express 라우터 (GET/POST only) | [COMMON-GUIDE.md #4](docs/COMMON-GUIDE.md#4-api-통신-패턴) |
| 상태 관리 | Zustand, TanStack Query | - | [COMMON-GUIDE.md #5](docs/COMMON-GUIDE.md#5-상태-관리) |
> 공통 로직 전체 가이드: [docs/COMMON-GUIDE.md](docs/COMMON-GUIDE.md)
>
> 새 메뉴 탭 추가 절차 (5단계): [docs/MENU-TAB-GUIDE.md](docs/MENU-TAB-GUIDE.md)
- 새 메뉴 탭 추가 (5단계): [docs/MENU-TAB-GUIDE.md](docs/MENU-TAB-GUIDE.md)
- CRUD API 개발: [docs/CRUD-API-GUIDE.md](docs/CRUD-API-GUIDE.md)
- HTTP 정책: GET/POST만 사용 (PUT/DELETE/PATCH 금지, 보안취약점 가이드 준수)
---
## 4. 프로젝트 구조
## 프로젝트 구조
Path Alias: `@common/*` -> `src/common/*`, `@tabs/*` -> `src/tabs/*`
```
wing/
├── frontend/ React 19 + Vite + TypeScript + Tailwind
├── frontend/ React 19 + Vite 7 + TypeScript + Tailwind
│ └── src/
│ ├── App.tsx 메인 (탭 라우팅, 감사 로그)
│ ├── App.tsx 메인 (탭 라우팅, 감사 로그 자동 기록)
│ ├── index.css @tailwind + @import 엔트리포인트
│ ├── common/ 공통 모듈 (@common/ alias)
│ │ ├── components/ auth/, layer/, layout/, map/, ui/
│ │ ├── hooks/ useLayers, useSubMenu
│ │ ├── hooks/ useLayers, useSubMenu, useFeatureTracking
│ │ ├── services/ api.ts, authApi.ts, layerService.ts
│ │ ├── store/ authStore, menuStore (Zustand)
│ │ ├── styles/ base.css, components.css, wing.css
│ │ ├── constants/ featureIds.ts
│ │ ├── types/ backtrack, boomLine, hns, navigation
│ │ └── utils/ coordinates, geo, sanitize
│ │ ├── utils/ coordinates, geo, sanitize, cn.ts
│ │ └── data/ layerData.ts (UI 레이어 트리)
│ └── tabs/ 탭 단위 패키지 (@tabs/ alias)
│ ├── prediction/ 확산 예측
│ ├── hns/ HNS 분석
│ ├── prediction/ 확산 예측 (OilSpillView, 역추적, 오일붐)
│ ├── hns/ HNS 분석 (시나리오, 물질 DB, 재계산)
│ ├── rescue/ 구조 시나리오
│ ├── aerial/ 항공 방제
│ ├── weather/ 해양 기상
│ ├── incidents/ 사건/사고
│ ├── aerial/ 항공 방제 (위성영상, 드론)
│ ├── weather/ 해양 기상 (KHOA API, 오버레이)
│ ├── incidents/ 사건/사고 관리
│ ├── board/ 게시판
│ ├── reports/ 보고서
│ ├── assets/ 자산 관리
│ ├── scat/ Pre-SCAT
│ └── admin/ 관리자
│ ├── assets/ 자산 관리 (기관, 장비, 선박보험)
│ ├── scat/ Pre-SCAT 해안조사
│ └── admin/ 관리자 (사용자/역할/권한/메뉴/설정)
├── backend/ Express + TypeScript
│ └── src/
│ ├── server.ts 진입점 + 라우터 등록
│ ├── auth/ 인증 (JWT, OAuth, 미들웨어)
│ ├── users/ 사용자 관리
│ ├── roles/ 역할/권한 관리
│ ├── settings/ 시스템 설정
│ ├── menus/ 메뉴 설정
│ ├── users/, roles/ 사용자, 역할/권한 (permResolver)
│ ├── settings/, menus/ 시스템 설정, 메뉴 설정
│ ├── audit/ 감사 로그
│ ├── board/, reports/ 게시판, 보고서 CRUD
│ ├── assets/, incidents/, scat/ 자산, 사건/사고, SCAT CRUD
│ ├── prediction/, aerial/, rescue/ 예측, 항공, 구조 CRUD
│ ├── hns/ HNS 물질 검색 API
│ ├── routes/ 레이어, 시뮬레이션
│ ├── middleware/ 보안 (입력 살균, rate-limit)
│ └── db/ DB 연결 (wingDb, authDb), seed
├── database/ SQL 스크립트 + 마이그레이션
├── database/ SQL 스크립트 + 마이그레이션 (001~016)
├── docs/ 개발 문서
├── .claude/ 팀 워크플로우 (rules, skills, scripts)
├── .claude/ 팀 워크플로우 (rules, skills, scripts, agents)
└── .githooks/ Git hooks (pre-commit, commit-msg)
```
---
## 5. 기술 스택
## 기술 스택
| 영역 | 기술 |
|------|------|
| Frontend | React 19, Vite 7, TypeScript 5.9, Tailwind CSS 3 |
| Backend | Express 4, TypeScript, PostgreSQL (pg) |
| Frontend | React 19, Vite 7.3, TypeScript 5.9, Tailwind CSS 3 |
| CSS 아키텍처 | Tailwind @layer (base.css, components.css, wing.css), cn() 유틸리티 |
| 상태 관리 | Zustand (클라이언트), TanStack Query (서버) |
| 지도 | Leaflet + react-leaflet |
| 실시간 | Socket.IO |
| 인증 | JWT (HttpOnly Cookie), Google OAuth |
| 지도 | MapLibre GL JS 5.x + @vis.gl/react-maplibre 8.1 + deck.gl 9.x |
| 실시간 | Socket.IO Client 4.8 |
| UI | lucide-react, @dnd-kit, emoji-mart |
| Backend | Express 4, TypeScript, pg (PostgreSQL) |
| 보안 | Helmet, CORS, Rate-limit, Input sanitization |
| 인증 | JWT (HttpOnly Cookie `WING_SESSION`), bcrypt, Google OAuth |
| DB | PostgreSQL 16 + PostGIS (wing 운영DB + wing_auth 인증DB) |
| CI/CD | Gitea Actions |
| CI/CD | Gitea Actions (.gitea/workflows/deploy.yml) |
---
## 6. 문서 안내
### 개발 가이드
## 문서 안내
| 문서 | 설명 | 대상 |
|------|------|------|
| [DEVELOPMENT-GUIDE.md](docs/DEVELOPMENT-GUIDE.md) | 개발 워크플로우 전체 흐름 (Plan → Branch → MR → Deploy) | 모든 개발자 |
| [COMMON-GUIDE.md](docs/COMMON-GUIDE.md) | 공통 로직 개발 가이드 (인증, 감사로그, 메뉴, API, 상태 관리) | 탭 개발자 |
| [MENU-TAB-GUIDE.md](docs/MENU-TAB-GUIDE.md) | 새 메뉴 탭 추가 절차 (5단계) | 탭 개발자 |
### 운영 가이드
| 문서 | 설명 | 대상 |
|------|------|------|
| [INSTALL_GUIDE.md](docs/INSTALL_GUIDE.md) | 설치 매뉴얼 (온라인/오프라인, DB 초기화) | 운영/인프라 |
| [CHANGELOG.md](docs/CHANGELOG.md) | 변경 이력 | 모든 개발자 |
| [docs/README.md](docs/README.md) | 프로젝트 아키텍처 상세 | 모든 개발자 |
| [docs/DEVELOPMENT-GUIDE.md](docs/DEVELOPMENT-GUIDE.md) | 개발 워크플로우 전체 흐름 | 모든 개발자 |
| [docs/COMMON-GUIDE.md](docs/COMMON-GUIDE.md) | 공통 로직 가이드 (인증, 감사로그, 메뉴, API, 상태 관리) | 탭 개발자 |
| [docs/MENU-TAB-GUIDE.md](docs/MENU-TAB-GUIDE.md) | 새 메뉴 탭 추가 절차 (5단계) | 탭 개발자 |
| [docs/CRUD-API-GUIDE.md](docs/CRUD-API-GUIDE.md) | CRUD API 개발 가이드 | 탭 개발자 |
| [docs/MOCK-TO-API-GUIDE.md](docs/MOCK-TO-API-GUIDE.md) | Mock -> API 전환 프로세스 | 탭 개발자 |
| [docs/INSTALL_GUIDE.md](docs/INSTALL_GUIDE.md) | 설치 매뉴얼 (온라인/오프라인, DB 초기화) | 운영/인프라 |
| [docs/CHANGELOG.md](docs/CHANGELOG.md) | 변경 이력 | 모든 개발자 |
### 코드 컨벤션 (.claude/rules/)
@ -172,18 +169,21 @@ wing/
| `code-style.md` | TypeScript/React 코드 스타일 |
| `naming.md` | 네이밍 규칙 |
| `testing.md` | 테스트 규칙 |
| `subagent-policy.md` | 서브에이전트 활용 정책 |
---
## 7. 환경 변수
## 환경 변수
### 프론트엔드 (`frontend/.env`)
```
VITE_API_URL=http://localhost:3001/api
VITE_GOOGLE_CLIENT_ID=your-google-client-id
```
### 백엔드 (`backend/.env`)
```
PORT=3001
NODE_ENV=development
@ -198,26 +198,27 @@ GOOGLE_CLIENT_ID=your-google-client-id
---
## 8. 배포
## 배포
| 항목 | 값 |
|------|---|
| 프론트엔드 | https://wing-demo.gc-si.dev |
| 백엔드 API | https://wing-demo.gc-si.dev/api/ |
| CI/CD | Gitea Actions (main 머지 시 자동 배포) |
배포 파이프라인 상세는 [docs/DEVELOPMENT-GUIDE.md #7](docs/DEVELOPMENT-GUIDE.md#7-자동-배포)을 참조하세요.
| Gitea | https://gitea.gc-si.dev/gc/wing-ops |
| 보호 브랜치 | main, develop (MR 필수) |
---
## 9. Claude Code 스킬
## Claude Code 스킬
| 스킬 | 설명 |
|------|------|
| `/push` | 커밋 + 푸시 (한 번에) |
| `/mr` | 커밋 + 푸시 + develop MR (한 번에) |
| `/release` | develop main 릴리즈 MR |
| `/release` | develop -> main 릴리즈 MR |
| `/create-mr` | MR만 생성 (세부 옵션) |
| `/fix-issue` | Gitea 이슈 분석 + 수정 브랜치 생성 |
| `/sync-team-workflow` | 팀 워크플로우 동기화 |
| `/changelog` | CHANGELOG.md 갱신 |
| `/init-project` | 프로젝트 초기 설정 |

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다. Load Diff

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다. Load Diff

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다. Load Diff

파일 보기

@ -1,13 +1,63 @@
# WING 해양환경 위기대응 통합시스템 - 설치 매뉴얼
# WING-OPS 설치 매뉴얼
## 1. 필수 소프트웨어
## 목차
| 소프트웨어 | 최소 버전 | 용도 | 다운로드 |
|-----------|----------|------|---------|
| Node.js | v20 이상 (권장 v25) | 프론트엔드/백엔드 실행 | https://nodejs.org |
| npm | v10 이상 | 패키지 관리 | Node.js에 포함 |
1. [시스템 요구사항](#1-시스템-요구사항)
2. [프로젝트 구조](#2-프로젝트-구조)
3. [온라인 설치](#3-온라인-설치)
4. [오프라인 설치](#4-오프라인-설치)
5. [DB 초기화 및 마이그레이션](#5-db-초기화-및-마이그레이션)
6. [개발 서버 실행](#6-개발-서버-실행)
7. [운영 서버 배포](#7-운영-서버-배포)
8. [CI/CD 자동 배포](#8-cicd-자동-배포)
9. [접속 정보 요약](#9-접속-정보-요약)
10. [트러블슈팅](#10-트러블슈팅)
> **오프라인 환경**: 인터넷이 안 되는 망에서는 `node_modules`가 포함된 압축 파일을 사용하세요 (아래 "오프라인 설치" 참고).
---
## 1. 시스템 요구사항
### 필수 소프트웨어
| 소프트웨어 | 최소 버전 | 권장 버전 | 용도 |
|-----------|----------|----------|------|
| Node.js | v20 | v20 LTS | Frontend/Backend 실행 |
| npm | v10 | v10+ | 패키지 관리 (Node.js 포함) |
| PostgreSQL | v15 | v16 | 운영 DB + 인증 DB |
| PostGIS | v3.3 | v3.4 | 공간 데이터 처리 |
| Git | v2.30 | v2.40+ | 소스 코드 관리 |
### 하드웨어 권장 사양
| 항목 | 개발 환경 | 운영 환경 |
|------|----------|----------|
| CPU | 2 Core | 4 Core |
| RAM | 4 GB | 8 GB |
| Disk | 10 GB | 50 GB |
| OS | macOS / Linux / Windows | Rocky Linux 9 / CentOS 9 |
### Node.js 설치 (fnm 권장)
fnm(Fast Node Manager)으로 Node.js 버전을 관리한다. 프로젝트 루트의 `.node-version` 파일에 `20`이 지정되어 있다.
**macOS / Linux:**
```bash
# fnm 설치
curl -fsSL https://fnm.vercel.app/install | bash
# 셸 재시작 후
fnm install 20
fnm use 20
# 확인
node -v # v20.x.x
npm -v # v10.x.x
```
**수동 설치 (fnm 없이):**
https://nodejs.org 에서 Node.js 20 LTS를 다운로드하여 설치한다.
---
@ -15,151 +65,669 @@
```
wing/
├── frontend/ # React + Vite 프론트엔드 (포트 5173)
├── frontend/ React 19 + Vite 7 + TypeScript 5.9 + Tailwind CSS 3
│ ├── src/
│ │ ├── components/ # UI 컴포넌트
│ │ ├── data/ # 정적 데이터
│ │ ├── hooks/ # 커스텀 훅
│ │ ├── types/ # TypeScript 타입 정의
│ │ ├── utils/ # 유틸리티 함수
│ │ └── store/ # 상태관리
│ └── package.json
├── backend/ # Express 백엔드 API (포트 3001)
│ │ ├── App.tsx 메인 (탭 라우팅, 감사 로그 자동 기록)
│ │ ├── common/ 공통 모듈 (@common/ alias)
│ │ │ ├── components/ auth/, layer/, layout/, map/, ui/
│ │ │ ├── hooks/ useLayers, useSubMenu, useAuth
│ │ │ ├── services/ api.ts (Axios), authApi.ts, layerService.ts
│ │ │ ├── store/ authStore, menuStore (Zustand)
│ │ │ ├── types/ backtrack, boomLine, hns, navigation
│ │ │ └── utils/ coordinates, geo, sanitize
│ │ └── tabs/ 탭 단위 패키지 (11개)
│ │ ├── prediction/ 확산 예측
│ │ ├── hns/ HNS 분석
│ │ ├── rescue/ 구조 시나리오
│ │ ├── aerial/ 항공 방제
│ │ ├── weather/ 해양 기상
│ │ ├── incidents/ 사건/사고 관리
│ │ ├── board/ 게시판
│ │ ├── reports/ 보고서
│ │ ├── assets/ 자산 관리
│ │ ├── scat/ Pre-SCAT 조사
│ │ └── admin/ 관리자
│ ├── package.json
│ └── vite.config.ts
├── backend/ Express 4 + TypeScript + PostgreSQL
│ ├── src/
│ │ ├── routes/ # API 라우트
│ │ ├── middleware/ # 미들웨어 (보안 등)
│ │ ├── db/ # DB 연결
│ │ └── server.ts # 서버 엔트리
│ └── package.json
└── database/ # DB 초기화 SQL
├── database_init.sql
└── auth_init.sql
│ │ ├── server.ts 진입점 (보안 미들웨어 + 라우터 등록)
│ │ ├── auth/ 인증 (JWT, Google OAuth)
│ │ ├── users/ 사용자 관리
│ │ ├── roles/ 역할/권한 (RBAC 2차원 권한)
│ │ ├── db/ DB Pool (wingDb, authDb), seed
│ │ ├── middleware/ 보안 (입력 살균, rate-limit)
│ │ └── {도메인}/ 도메인별 모듈 (Router + Service)
│ ├── package.json
│ └── tsconfig.json
├── database/ SQL 스크립트
│ ├── init.sql wing DB 초기 스키마 (PostGIS)
│ ├── auth_init.sql wing_auth DB 초기 스키마
│ └── migration/ 마이그레이션 (001 ~ 016)
├── docs/ 개발 문서
├── .githooks/ Git Hooks (pre-commit, commit-msg)
├── .gitea/workflows/ CI/CD (Gitea Actions)
├── .node-version Node.js 20
└── .npmrc npm 레지스트리 (Nexus 프록시)
```
---
## 3. 온라인 설치 (인터넷 가능한 환경)
## 3. 온라인 설치
### 3-1. 의존성 설치
인터넷이 가능한 환경에서의 설치 절차.
### 3-1. 소스 코드 클론
```bash
# 프론트엔드
cd wing/frontend
git clone https://gitea.gc-si.dev/gc/wing-ops.git wing
cd wing
```
### 3-2. Git Hooks 설정
```bash
# pre-commit (TypeScript + ESLint 검증), commit-msg (Conventional Commits 검증)
git config core.hooksPath .githooks
chmod +x .githooks/pre-commit .githooks/commit-msg
```
### 3-3. 의존성 설치
npm 레지스트리는 프로젝트 루트의 `.npmrc`에 Nexus 프록시로 설정되어 있다.
```bash
# Frontend
cd frontend
npm install
# 백엔드
# Backend
cd ../backend
npm install
```
### 3-2. 데이터베이스 설정
### 3-4. 환경변수 설정
운영 PostgreSQL에 직접 연결합니다. `backend/.env` 파일에서 DB 연결 정보를 설정하세요.
#### Backend (`backend/.env`)
```bash
# backend/.env
AUTH_DB_HOST=<PostgreSQL 호스트>
# ===========================================
# 서버 설정
# ===========================================
PORT=3001
NODE_ENV=development
# ===========================================
# wing DB (운영 데이터 - PostgreSQL + PostGIS)
# ===========================================
WING_DB_HOST=211.208.115.83
WING_DB_PORT=5432
WING_DB_USER=wing
WING_DB_PASS=<비밀번호>
WING_DB_NAME=wing
# ===========================================
# wing_auth DB (인증/권한 - PostgreSQL)
# ===========================================
AUTH_DB_HOST=211.208.115.83
AUTH_DB_PORT=5432
AUTH_DB_NAME=wing_auth
AUTH_DB_USER=wing_auth
AUTH_DB_PASSWORD=<비밀번호>
AUTH_DB_PASS=<비밀번호>
AUTH_DB_NAME=wing_auth
# ===========================================
# JWT 인증
# ===========================================
JWT_SECRET=<랜덤 시크릿 문자열>
JWT_EXPIRES_IN=24h
# ===========================================
# CORS (프론트엔드 출처)
# ===========================================
FRONTEND_URL=http://localhost:5173
# ===========================================
# Google OAuth (선택)
# ===========================================
GOOGLE_CLIENT_ID=<Google OAuth 클라이언트 ID>
```
> 신규 DB 초기화가 필요한 경우 `database/auth_init.sql`을 실행하세요.
### 3-3. 백엔드 실행
#### Frontend (`frontend/.env`)
```bash
cd wing/backend
npm run dev
# 백엔드 API URL
VITE_API_URL=http://localhost:3001/api
# Google OAuth (선택)
VITE_GOOGLE_CLIENT_ID=<Google OAuth 클라이언트 ID>
# 공공데이터 API 키 (해양기상, 선택)
VITE_DATA_GO_KR_API_KEY=<data.go.kr API >
VITE_WEATHER_API_KEY=<기상 API >
```
`http://localhost:3001` 에서 API 서버 시작
> `.env` 파일은 `.gitignore`에 포함된다. 실제 값은 팀 내부에서 공유한다.
### 3-4. 프론트엔드 실행
### 3-5. 실행
```bash
cd wing/frontend
# 터미널 1: 백엔드
cd backend
npm run dev
# 출력: 서버가 포트 3001에서 실행 중입니다.
# 터미널 2: 프론트엔드
cd frontend
npm run dev
# 출력: VITE vX.X.X ready in XXXms
# -> Local: http://localhost:5173/
```
`http://localhost:5173` 에서 웹 앱 시작
브라우저에서 http://localhost:5173 접속하여 확인한다.
---
## 4. 오프라인 설치 (폐쇄망/다른 망)
## 4. 오프라인 설치
인터넷이 안 되는 환경에서는 `npm install`이 불가능합니다.
이 경우 **node_modules 포함 압축 파일**을 사용하세요.
인터넷이 불가능한 폐쇄망 환경에서의 설치 절차.
### 4-1. 압축 해제
### 4-1. 온라인 환경에서 패키지 준비
인터넷이 되는 PC에서 다음을 실행한다:
```bash
# wing_full.tar.gz 파일을 작업 폴더에 복사한 뒤:
# 1. 프로젝트 클론 및 의존성 설치
git clone https://gitea.gc-si.dev/gc/wing-ops.git wing
cd wing/frontend && npm install
cd ../backend && npm install
# 2. 전체 프로젝트 압축 (node_modules 포함)
cd ../..
tar -czf wing_full.tar.gz wing/
```
### 4-2. Node.js 오프라인 설치 파일 준비
대상 OS에 맞는 설치 파일을 다운로드한다:
| OS | 파일 | 다운로드 |
|----|------|---------|
| Linux (x64) | `node-v20.x.x-linux-x64.tar.xz` | https://nodejs.org/dist/v20.x.x/ |
| macOS (arm64) | `node-v20.x.x-darwin-arm64.tar.gz` | https://nodejs.org/dist/v20.x.x/ |
| macOS (x64) | `node-v20.x.x-darwin-x64.tar.gz` | https://nodejs.org/dist/v20.x.x/ |
| Windows | `node-v20.x.x-x64.msi` | https://nodejs.org/dist/v20.x.x/ |
### 4-3. 대상 서버에 설치
```bash
# 1. Node.js 설치 (Linux 예시)
tar -xJf node-v20.x.x-linux-x64.tar.xz
export PATH=$PWD/node-v20.x.x-linux-x64/bin:$PATH
# .bashrc 또는 .profile에 PATH 영구 등록
# 2. 프로젝트 압축 해제
tar -xzf wing_full.tar.gz
cd wing
# 3. 환경변수 설정 (3-4 참조)
vi backend/.env
vi frontend/.env
# 4. 실행 (node_modules가 이미 포함되어 있으므로 npm install 불필요)
cd backend && npm run dev # 터미널 1
cd frontend && npm run dev # 터미널 2
```
### 4-2. Node.js 설치
### 4-4. 오프라인 환경 주의사항
대상 PC에 Node.js가 없으면 오프라인 설치 파일(.msi 또는 .pkg)을 미리 준비하여 설치합니다.
- `npm install` 불가하므로 패키지 추가 시 온라인 환경에서 설치 후 재배포해야 한다.
- DB는 대상 환경에서 접근 가능한 PostgreSQL을 사용해야 한다.
- 공공데이터 API (기상, 해양) 키가 필요한 기능은 외부 네트워크 접근이 필요하다.
- Windows: `node-v25.x.x-x64.msi`
- macOS: `node-v25.x.x.pkg`
---
### 4-3. DB 연결 설정
## 5. DB 초기화 및 마이그레이션
`backend/.env` 파일에서 연결 가능한 PostgreSQL 정보를 설정합니다.
### 5-1. DB 구성
### 4-4. 실행
프로젝트는 동일 PostgreSQL 서버에 2개의 데이터베이스를 사용한다:
node_modules가 이미 포함되어 있으므로 바로 실행 가능합니다.
| DB | 용도 | 확장 |
|----|------|------|
| `wing` | 운영 데이터 (사고, 예측, 자산 등) | PostGIS |
| `wing_auth` | 인증/권한 (사용자, 역할, 메뉴) | uuid-ossp, pgcrypto |
### 5-2. 신규 DB 초기화
PostgreSQL에 최초 설치 시 아래 순서로 실행한다.
```bash
# 터미널 1 - 백엔드
cd wing/backend
npm run dev
# 1. wing DB 초기화 (PostgreSQL superuser로 실행)
psql -U postgres -f database/init.sql
# 터미널 2 - 프론트엔드
cd wing/frontend
npm run dev
# 2. wing_auth DB 초기화
psql -U postgres -f database/auth_init.sql
```
`init.sql`에 포함된 내용:
- 사용자(wing) 및 데이터베이스(wing) 생성
- PostGIS 확장 설치
- 공통코드, 시뮬레이션, 사고, 예측 등 핵심 테이블 생성
`auth_init.sql`에 포함된 내용:
- 사용자(wing_auth) 및 데이터베이스(wing_auth) 생성
- uuid-ossp, pgcrypto 확장 설치
- 조직, 역할, 사용자, 권한, 메뉴, 설정, 감사로그 테이블 생성
### 5-3. 마이그레이션 적용
초기 스키마 이후 추가된 변경 사항은 `database/migration/` 디렉토리에 순번대로 관리된다.
```
database/migration/
├── 001_layer_table.sql 레이어 테이블
├── 002_hns_substance.sql HNS 물질 데이터
├── 003_perm_tree.sql 권한 트리
├── 004_oper_cd.sql 오퍼레이션 코드
├── 005_db_consolidation.sql DB 통합
├── 006_board.sql 게시판
├── 007_reports.sql 보고서
├── 008_assets.sql 자산
├── 008_assets_seed.sql 자산 시드 데이터
├── 009_incidents.sql 사건/사고
├── 010_postgis_geom.sql PostGIS 지오메트리
├── 011_scat.sql SCAT 조사
├── 012_board_ext.sql 게시판 확장
├── 013_hns_analysis.sql HNS 분석
├── 014_prediction.sql 확산 예측
├── 015_aerial.sql 항공 방제
└── 016_rescue.sql 구조 시나리오
```
**마이그레이션 실행 (수동):**
```bash
# wing DB에 순번대로 적용
psql -h <호스트> -p 5432 -U wing -d wing -f database/migration/001_layer_table.sql
psql -h <호스트> -p 5432 -U wing -d wing -f database/migration/002_hns_substance.sql
# ... 순번대로 계속
```
> 이미 적용된 마이그레이션을 다시 실행해도 대부분 `IF NOT EXISTS` 조건이 포함되어 있어 안전하다.
### 5-4. 시드 데이터
초기 데이터(관리자 계정, 공통코드 등)를 입력한다:
```bash
cd backend
npm run db:seed
```
---
## 5. 접속 정보 요약
## 6. 개발 서버 실행
| 서비스 | URL | 비고 |
|--------|-----|------|
| 프론트엔드 (WING) | http://localhost:5173 | Vite dev server |
| 백엔드 API | http://localhost:3001 | Express |
| PostgreSQL | 운영 DB 직접 연결 | `.env` 설정 참조 |
### 6-1. 명령어 요약
---
**Frontend:**
## 6. 주요 명령어
| 명령어 | 설명 |
|--------|------|
| `npm run dev` | Vite 개발 서버 (localhost:5173, HMR 지원) |
| `npm run build` | 프로덕션 빌드 (`tsc -b && vite build` -> `dist/`) |
| `npm run lint` | ESLint 검증 |
| `npm run preview` | 빌드 결과 미리보기 |
**Backend:**
| 명령어 | 설명 |
|--------|------|
| `npm run dev` | tsx watch 개발 서버 (localhost:3001, 파일 변경 시 자동 재시작) |
| `npm run build` | TypeScript 컴파일 (`tsc` -> `dist/`) |
| `npm start` | 프로덕션 실행 (`node dist/server.js`) |
| `npm run db:seed` | DB 시드 데이터 입력 |
### 6-2. 개발 서버 시작
```bash
# 프론트엔드 빌드 (배포용)
cd frontend && npm run build # dist/ 폴더에 정적 파일 생성
# 터미널 1: 백엔드 (먼저 실행)
cd backend
npm run dev
# 서버가 포트 3001에서 실행 중입니다.
# wing DB 연결 성공 (211.208.115.83:5432/wing)
# 백엔드 빌드
cd backend && npm run build # dist/ 폴더에 JS 생성
# 터미널 2: 프론트엔드
cd frontend
npm run dev
# VITE v7.x.x ready in XXXms
# -> Local: http://localhost:5173/
```
# DB 시드 데이터 입력
cd backend && npm run db:seed
### 6-3. 검증 명령어
```bash
# TypeScript 타입 체크
cd frontend && npx tsc --noEmit
cd backend && npx tsc --noEmit
# ESLint
cd frontend && npx eslint src/
# Prettier
cd frontend && npx prettier --check src/
cd frontend && npx prettier --write src/ # 자동 수정
```
---
## 7. 트러블슈팅
## 7. 운영 서버 배포
| 증상 | 해결 |
### 7-1. 서버 환경 준비
운영 서버(Rocky Linux 9 기준)에 다음을 설치한다:
```bash
# Node.js 20 설치 (fnm 또는 직접 설치)
curl -fsSL https://fnm.vercel.app/install | bash
source ~/.bashrc
fnm install 20
fnm use 20
# PostgreSQL 16 + PostGIS 설치 (dnf 또는 직접 설치)
# 이미 설치된 경우 생략
```
### 7-2. 수동 배포 절차
#### Frontend
```bash
cd frontend
# 의존성 설치
npm ci
# 환경변수 설정 (빌드 시 Vite가 주입)
export VITE_API_URL=/api
export VITE_GOOGLE_CLIENT_ID=<클라이언트ID>
export VITE_DATA_GO_KR_API_KEY=<>
export VITE_WEATHER_API_KEY=<>
# 프로덕션 빌드
npx vite build
# 빌드 결과를 웹 서버 디렉토리로 복사
cp -r dist/* /deploy/wing-demo/
```
#### Backend
```bash
cd backend
# 의존성 설치
npm ci
# TypeScript 컴파일
npx tsc
# 프로덕션 의존성만 유지 (devDependencies 제거)
npm prune --omit=dev
# 배포 디렉토리로 복사
mkdir -p /deploy/wing-demo-backend/dist
cp -r dist/* /deploy/wing-demo-backend/dist/
cp -r node_modules /deploy/wing-demo-backend/
cp package.json /deploy/wing-demo-backend/
```
### 7-3. systemd 서비스 등록
Backend를 systemd 서비스로 등록하여 자동 시작/재시작을 설정한다.
**서비스 파일 생성** (`/etc/systemd/system/wing-demo-api.service`):
```ini
[Unit]
Description=WING-OPS Backend API
After=network.target postgresql.service
[Service]
Type=simple
User=deploy
WorkingDirectory=/deploy/wing-demo-backend
ExecStart=/home/deploy/.local/share/fnm/aliases/default/bin/node dist/server.js
Restart=on-failure
RestartSec=5
# 환경변수
Environment=NODE_ENV=production
Environment=PORT=3001
Environment=WING_DB_HOST=211.208.115.83
Environment=WING_DB_PORT=5432
Environment=WING_DB_USER=wing
Environment=WING_DB_PASS=<비밀번호>
Environment=WING_DB_NAME=wing
Environment=AUTH_DB_HOST=211.208.115.83
Environment=AUTH_DB_PORT=5432
Environment=AUTH_DB_USER=wing_auth
Environment=AUTH_DB_PASS=<비밀번호>
Environment=AUTH_DB_NAME=wing_auth
Environment=JWT_SECRET=<시크릿>
Environment=JWT_EXPIRES_IN=24h
Environment=FRONTEND_URL=https://wing-demo.gc-si.dev
Environment=GOOGLE_CLIENT_ID=<클라이언트ID>
[Install]
WantedBy=multi-user.target
```
**서비스 등록 및 시작:**
```bash
sudo systemctl daemon-reload
sudo systemctl enable wing-demo-api
sudo systemctl start wing-demo-api
# 상태 확인
sudo systemctl status wing-demo-api
# 로그 확인
sudo journalctl -u wing-demo-api -f
```
### 7-4. 리버스 프록시 (Nginx)
Frontend 정적 파일과 Backend API를 하나의 도메인으로 서비스한다.
```nginx
server {
listen 443 ssl;
server_name wing-demo.gc-si.dev;
ssl_certificate /etc/ssl/certs/wing-demo.crt;
ssl_certificate_key /etc/ssl/private/wing-demo.key;
# Frontend 정적 파일
root /deploy/wing-demo;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# Backend API 프록시
location /api/ {
proxy_pass http://127.0.0.1:3001/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 헬스 체크
location /health {
proxy_pass http://127.0.0.1:3001/health;
}
}
# HTTP -> HTTPS 리다이렉트
server {
listen 80;
server_name wing-demo.gc-si.dev;
return 301 https://$host$request_uri;
}
```
---
## 8. CI/CD 자동 배포
### 8-1. Gitea Actions 파이프라인
main 브랜치에 push(머지)되면 `.gitea/workflows/deploy.yml`이 자동 실행된다.
```
main 브랜치 push
|
[Checkout] 소스 코드 체크아웃
|
[Setup Node.js] Node.js 24 설정
|
[Configure npm] Nexus 프록시 레지스트리 설정
|
[Frontend Build] npm ci -> vite build -> /deploy/wing-demo/
|
[Backend Build] npm ci -> tsc -> npm prune --omit=dev
| -> /deploy/wing-demo-backend/
|
[Deploy Trigger] .deploy-trigger 파일 생성
-> cron/watchdog이 감지 -> 서비스 재시작
```
### 8-2. Gitea Secrets 설정
CI/CD에서 사용하는 시크릿을 Gitea에 등록해야 한다:
**등록 경로:** Settings -> Actions -> Secrets -> Add Secret
| Secret 이름 | 용도 |
|-------------|------|
| `NEXUS_NPM_AUTH` | npm Nexus 프록시 인증 토큰 |
| `GOOGLE_CLIENT_ID` | Google OAuth 클라이언트 ID |
| `DATA_GO_KR_API_KEY` | 공공데이터 API 키 |
| `WEATHER_API_KEY` | 기상 API 키 |
### 8-3. 배포 확인
```bash
# 프론트엔드 응답 확인
curl -s -o /dev/null -w '%{http_code}' https://wing-demo.gc-si.dev/
# 기대: 200
# 백엔드 헬스 체크
curl -s https://wing-demo.gc-si.dev/api/health
# 기대: {"status":"ok"}
# 백엔드 API 정보
curl -s https://wing-demo.gc-si.dev/api/
# 기대: {"name":"WING Backend API","version":"1.0.0","status":"running"}
```
---
## 9. 접속 정보 요약
### 개발 환경
| 서비스 | URL | 포트 |
|--------|-----|------|
| Frontend (Vite dev) | http://localhost:5173 | 5173 |
| Backend API | http://localhost:3001 | 3001 |
| Backend 헬스 체크 | http://localhost:3001/health | 3001 |
| PostgreSQL (wing) | 211.208.115.83:5432/wing | 5432 |
| PostgreSQL (wing_auth) | 211.208.115.83:5432/wing_auth | 5432 |
### 운영 환경
| 서비스 | URL |
|--------|-----|
| Frontend | https://wing-demo.gc-si.dev |
| Backend API | https://wing-demo.gc-si.dev/api/ |
| 헬스 체크 | https://wing-demo.gc-si.dev/api/health |
| Gitea | https://gitea.gc-si.dev/gc/wing-ops |
### API 엔드포인트
| 경로 | 용도 |
|------|------|
| `npm run dev` 실행 시 포트 충돌 | `lsof -i :5173` 또는 `lsof -i :3001`로 확인 후 프로세스 종료 |
| `EACCES` 권한 오류 | `sudo chown -R $(whoami) wing/` |
| 프론트엔드에서 API 호출 실패 | 백엔드(`localhost:3001`)가 실행 중인지 확인 |
| DB 연결 실패 | `backend/.env`의 DB 연결 정보 확인, PostgreSQL 접근 가능 여부 확인 |
| `MODULE_NOT_FOUND` 오류 | `npm install` 재실행 (온라인) 또는 node_modules 포함 압축본 사용 |
| `GET /` | API 정보 |
| `GET /health` | 헬스 체크 |
| `POST /api/auth/login` | 로그인 |
| `POST /api/auth/logout` | 로그아웃 |
| `GET /api/auth/me` | 현재 사용자 |
| `GET /api/users` | 사용자 목록 |
| `GET /api/roles` | 역할 목록 |
| `GET /api/menus` | 메뉴 목록 |
| `GET /api/settings` | 시스템 설정 |
| `GET /api/audit` | 감사 로그 |
| `GET /api/board` | 게시판 |
| `GET /api/layers` | 레이어 |
| `GET /api/simulation` | 시뮬레이션 |
| `GET /api/hns` | HNS 물질 |
| `GET /api/reports` | 보고서 |
| `GET /api/assets` | 자산 |
| `GET /api/incidents` | 사건/사고 |
| `GET /api/scat` | SCAT 조사 |
| `GET /api/prediction` | 확산 예측 |
| `GET /api/aerial` | 항공 방제 |
| `GET /api/rescue` | 구조 시나리오 |
---
## 10. 트러블슈팅
### 의존성 설치
| 증상 | 원인 | 해결 |
|------|------|------|
| `npm install` 실패 (ENETUNREACH) | Nexus 프록시 접근 불가 | `.npmrc`의 registry URL 확인, VPN/네트워크 상태 확인 |
| `npm install` 실패 (EAUTH) | Nexus 인증 토큰 만료 | `.npmrc``_auth` 값 갱신 |
| `npm install` 실패 (peer dependency) | 패키지 버전 충돌 | `npm install --legacy-peer-deps` 시도 |
| `MODULE_NOT_FOUND` | node_modules 누락/손상 | `rm -rf node_modules package-lock.json && npm install` |
### 서버 실행
| 증상 | 원인 | 해결 |
|------|------|------|
| 포트 5173/3001 충돌 | 이미 다른 프로세스가 사용 중 | `lsof -i :5173` 또는 `lsof -i :3001`로 PID 확인 후 `kill -9 <PID>` |
| 백엔드 시작 시 DB 연결 실패 | DB 접속 정보 오류 또는 네트워크 | `backend/.env`의 DB 호스트/포트/사용자/비밀번호 확인, `nc -zv <호스트> 5432`로 네트워크 확인 |
| 프론트엔드에서 API 호출 CORS 에러 | 백엔드 CORS 설정 불일치 | `backend/.env``FRONTEND_URL`이 프론트엔드 URL과 일치하는지 확인 |
| `env: tsx: No such file or directory` | tsx 미설치 | `cd backend && npm install` 재실행 |
### DB
| 증상 | 원인 | 해결 |
|------|------|------|
| `relation "XXX" does not exist` | 테이블 미생성 | `database/init.sql` 또는 해당 마이그레이션 SQL 실행 |
| PostGIS 함수 에러 | PostGIS 확장 미설치 | `psql -U postgres -d wing -c "CREATE EXTENSION IF NOT EXISTS postgis"` |
| 인코딩 문제 (한글 깨짐) | DB 인코딩 설정 | DB 생성 시 `ENCODING='UTF8' LC_COLLATE='ko_KR.UTF-8'` 지정 |
| seed 실패 | 이미 데이터 존재 또는 FK 제약 | 에러 메시지 확인 후 해당 테이블 데이터 정리 |
### Git Hooks
| 증상 | 원인 | 해결 |
|------|------|------|
| pre-commit이 실행되지 않음 | hooksPath 미설정 | `git config core.hooksPath .githooks` |
| pre-commit permission denied | 실행 권한 없음 | `chmod +x .githooks/pre-commit .githooks/commit-msg` |
| TypeScript 에러로 커밋 차단 | 타입 에러 존재 | `npx tsc --noEmit`으로 에러 확인 후 수정 |
| commit-msg 형식 오류 | Conventional Commits 미준수 | `type(scope): subject` 형식 준수 (type: feat, fix, docs 등) |
### 빌드
| 증상 | 원인 | 해결 |
|------|------|------|
| `vite build` 메모리 부족 | Node.js 힙 메모리 부족 | `export NODE_OPTIONS=--max-old-space-size=4096` 후 재시도 |
| `tsc` 타입 에러 | TypeScript strict 모드 위반 | `npx tsc --noEmit`으로 에러 목록 확인 후 수정 |
| 빌드 후 라우팅 404 | SPA 서버 설정 누락 | Nginx `try_files $uri $uri/ /index.html` 설정 확인 |

파일 보기

@ -1,16 +1,23 @@
# WING 메뉴 탭 추가 가이드
# 메뉴 탭 추가 가이드
새로운 메뉴 탭을 추가할 때 필요한 절차를 설명합니다.
새로운 메뉴 탭을 추가하는 전체 절차를 5단계로 설명한다.
board 탭을 기준 템플릿으로 사용하며, 각 단계별 실제 코드 예시를 제공한다.
## 메뉴 시스템 구조
> **소요 시간**: 약 20~30분 (기본 CRUD 탭 기준)
---
## 메뉴 시스템 아키텍처
```
DB: AUTH_SETTING (menu.config JSON)
↕ GET/PUT /api/menus
Backend: settingsService.ts (DEFAULT_MENU_CONFIG, VALID_MENU_IDS)
↕ API
Frontend: menuStore.ts → TopBar.tsx (탭 렌더링)
→ App.tsx (renderView 라우팅)
[DB] AUTH_SETTING (menu.config JSON)
|
v GET /api/menus
[Backend] settingsService.ts (DEFAULT_MENU_CONFIG, VALID_MENU_IDS)
|
v API
[Frontend] menuStore.ts --> TopBar.tsx (탭 렌더링, enabled && hasPermission 필터링)
--> App.tsx (renderView 라우팅)
```
- **DB**가 메뉴 정의의 단일 소스 (id, label, icon, enabled, order)
@ -18,177 +25,648 @@ Frontend: menuStore.ts → TopBar.tsx (탭 렌더링)
- **App.tsx**의 `renderView`가 탭 ID에 따라 뷰 컴포넌트를 매핑
- **admin** 탭은 메뉴 관리 대상에서 제외 (TopBar에서 별도 아이콘 버튼으로 접근)
---
## 수정 파일 요약
| 순서 | 파일 | 작업 | 필수 |
|------|------|------|------|
| 1 | `frontend/src/tabs/{탭명}/components/XxxView.tsx` | 뷰 컴포넌트 생성 | O |
| 2 | `frontend/src/tabs/{탭명}/index.ts` | re-export 생성 | O |
| 3 | `frontend/src/App.tsx` | MainTab 타입 + import + renderView | O |
| 4 | `backend/src/settings/settingsService.ts` | DEFAULT_MENU_CONFIG에 항목 추가 | O |
| 5 | `database/auth_init.sql` | menu.config 초기 JSON에 추가 | O |
| 6 | 관리자 UI | 메뉴 관리에서 활성화 | O |
| 단계 | 파일 | 작업 |
|------|------|------|
| **Step 1** | `frontend/src/tabs/{탭명}/components/{TabName}View.tsx` | 뷰 컴포넌트 생성 |
| | `frontend/src/tabs/{탭명}/services/{tabName}Api.ts` | API 서비스 생성 |
| | `frontend/src/tabs/{탭명}/index.ts` | re-export |
| **Step 2** | `frontend/src/common/types/navigation.ts` | MainTab 타입 추가 |
| | `frontend/src/App.tsx` | import + renderView case 추가 |
| | `frontend/src/common/hooks/useSubMenu.ts` | 서브메뉴 설정 (서브탭이 있는 경우) |
| **Step 3** | `frontend/src/common/constants/featureIds.ts` | FEATURE_ID 등록 |
| **Step 4** | `backend/src/{도메인}/{domain}Router.ts` | 라우터 생성 |
| | `backend/src/{도메인}/{domain}Service.ts` | 서비스 생성 |
| **Step 5** | `backend/src/server.ts` | 라우트 등록 |
| | `backend/src/settings/settingsService.ts` | DEFAULT_MENU_CONFIG 추가 |
| | `database/auth_init.sql` | menu.config 초기 JSON 추가 |
| | `database/migration/NNN_{domain}.sql` | DB 마이그레이션 |
## Step 1: 뷰 컴포넌트 생성
---
`frontend/src/tabs/{탭명}/components/` 에 새 뷰 컴포넌트를 생성합니다.
## Step 1: 프론트엔드 탭 패키지 생성
### 1-1. 디렉토리 구조
```
frontend/src/tabs/{탭명}/
components/
{TabName}View.tsx # 메인 뷰 컴포넌트
services/
{tabName}Api.ts # API 서비스
index.ts # re-export
```
### 1-2. 뷰 컴포넌트 (보일러플레이트)
서브탭이 **없는** 간단한 탭:
```tsx
// frontend/src/tabs/monitoring/components/MonitoringView.tsx
export function MonitoringView() {
return (
<div className="flex flex-1 overflow-hidden bg-bg-0">
<div className="flex-1 flex flex-col overflow-hidden p-6">
<h1 className="text-lg font-bold text-text-1 font-korean">실시간 모니터링</h1>
{/* 뷰 콘텐츠 */}
<div className="flex flex-1 overflow-hidden">
<div className="flex-1 relative overflow-hidden">
<div className="flex flex-col h-full bg-bg-0">
{/* 헤더 */}
<div className="flex items-center justify-between px-8 py-4 border-b border-border bg-bg-1">
<div className="text-sm font-bold text-text-1">실시간 모니터링</div>
</div>
{/* 본문 */}
<div className="flex-1 overflow-auto px-8 py-6">
<p className="text-text-3 text-sm">준비 중입니다.</p>
</div>
</div>
</div>
</div>
)
);
}
```
`index.ts`에서 re-export합니다:
서브탭이 **있는** 탭 (board 패턴):
```tsx
// frontend/src/tabs/monitoring/index.ts
export { MonitoringView } from './components/MonitoringView'
// frontend/src/tabs/monitoring/components/MonitoringView.tsx
import { useSubMenu } from '@common/hooks/useSubMenu';
export function MonitoringView() {
const { activeSubTab } = useSubMenu('monitoring');
const renderContent = () => {
switch (activeSubTab) {
case 'dashboard':
return <div>대시보드 컨텐츠</div>;
case 'alerts':
return <div>알림 컨텐츠</div>;
default:
return <div>준비 중입니다.</div>;
}
};
return (
<div className="flex flex-1 overflow-hidden">
<div className="flex-1 relative overflow-hidden">
{renderContent()}
</div>
</div>
);
}
```
기존 탭(`@tabs/prediction`, `@tabs/weather` 등)의 레이아웃 패턴을 참고하세요.
공통 모듈은 `@common/` alias로 import합니다.
### 1-3. API 서비스 (보일러플레이트)
## Step 2: App.tsx 탭 등록
```ts
// frontend/src/tabs/monitoring/services/monitoringApi.ts
3가지를 수정합니다.
import { api } from '@common/services/api';
// ============================================================
// 인터페이스
// ============================================================
export interface MonitoringItem {
sn: number;
title: string;
status: string;
regDtm: string;
}
export interface MonitoringListResponse {
items: MonitoringItem[];
totalCount: number;
page: number;
size: number;
}
export interface MonitoringListParams {
search?: string;
page?: number;
size?: number;
}
export interface CreateMonitoringInput {
title: string;
status?: string;
}
// ============================================================
// API 함수
// ============================================================
export async function fetchMonitoringList(
params?: MonitoringListParams,
): Promise<MonitoringListResponse> {
const response = await api.get<MonitoringListResponse>('/monitoring', { params });
return response.data;
}
export async function fetchMonitoringDetail(sn: number): Promise<MonitoringItem> {
const response = await api.get<MonitoringItem>(`/monitoring/${sn}`);
return response.data;
}
export async function createMonitoring(input: CreateMonitoringInput): Promise<{ sn: number }> {
const response = await api.post<{ sn: number }>('/monitoring', input);
return response.data;
}
```
### 1-4. index.ts (re-export)
```ts
// frontend/src/tabs/monitoring/index.ts
export { MonitoringView } from './components/MonitoringView';
```
> **참고**: 기존 탭의 index.ts 패턴과 동일하다. 모든 탭은 index.ts에서 메인 뷰만 export한다.
---
## Step 2: navigation.ts에 MainTab 추가 + App.tsx 라우팅
### 2-1. MainTab 타입에 ID 추가
```tsx
// frontend/src/App.tsx (line 20)
```ts
// frontend/src/common/types/navigation.ts
// Before
export type MainTab = 'prediction' | 'hns' | ... | 'admin'
export type MainTab = 'prediction' | 'hns' | 'rescue' | ... | 'admin';
// After
export type MainTab = 'prediction' | 'hns' | ... | 'monitoring' | 'admin'
// After (새 탭 ID를 admin 앞에 추가)
export type MainTab = 'prediction' | 'hns' | 'rescue' | ... | 'monitoring' | 'admin';
```
### 2-2. 뷰 컴포넌트 import
### 2-2. App.tsx에 import + renderView case 추가
```tsx
import { MonitoringView } from '@tabs/monitoring'
```
// frontend/src/App.tsx
### 2-3. renderView switch에 case 추가
// 1. import 추가
import { MonitoringView } from '@tabs/monitoring';
```tsx
// 2. renderView switch에 case 추가
const renderView = () => {
switch (activeMainTab) {
// ... 기존 case들 ...
case 'monitoring':
return <MonitoringView />
// ...
return <MonitoringView />;
// admin은 항상 마지막
case 'admin':
return <AdminView />;
default:
return <div className="flex items-center justify-center h-full text-text-3">준비 중입니다...</div>;
}
};
```
### 2-3. 서브메뉴 설정 (서브탭이 있는 경우)
서브탭이 있다면 `useSubMenu.ts`에 3곳을 수정한다:
```ts
// frontend/src/common/hooks/useSubMenu.ts
// 1. subMenuConfigs 에 서브탭 배열 추가
const subMenuConfigs: Record<MainTab, SubMenuItem[] | null> = {
// ... 기존 설정 ...
monitoring: [
{ id: 'dashboard', label: '대시보드', icon: '📊' },
{ id: 'alerts', label: '알림 관리', icon: '🔔' },
],
};
// 2. subMenuState 에 기본 서브탭 추가
const subMenuState: Record<MainTab, string> = {
// ... 기존 상태 ...
monitoring: 'dashboard',
};
```
서브탭이 **없으면** `null`과 빈 문자열을 설정한다:
```ts
monitoring: null, // subMenuConfigs
monitoring: '', // subMenuState
```
---
## Step 3: featureIds.ts에 FEATURE_ID 등록
FEATURE_ID는 RBAC 권한 검사와 감사 로그에 사용된다.
형식: `'{메인탭}:{서브탭}'`
```ts
// frontend/src/common/constants/featureIds.ts
export const FEATURE_IDS = {
// ... 기존 항목 ...
// monitoring
'monitoring:dashboard': '모니터링 대시보드',
'monitoring:alerts': '알림 관리',
} as const;
```
> **동기화 필수**: 여기에 등록한 키는 백엔드의 `AUTH_PERM.RSRC_CD`와 일치해야 한다.
> 서브탭이 없는 탭은 `'{탭명}:main'` 형태로 하나만 등록한다.
---
## Step 4: 백엔드 모듈 생성
### 4-1. 디렉토리 구조
```
backend/src/{도메인}/
{domain}Router.ts # Express 라우터 (요청 파싱, 응답 포맷)
{domain}Service.ts # 비즈니스 로직 + DB 쿼리
```
### 4-2. 라우터 (보일러플레이트)
```ts
// backend/src/monitoring/monitoringRouter.ts
import { Router } from 'express';
import { requireAuth, requirePermission } from '../auth/authMiddleware.js';
import { AuthError } from '../auth/authService.js';
import { listItems, getItem, createItem } from './monitoringService.js';
const router = Router();
// GET /api/monitoring -- 목록 조회
router.get('/', requireAuth, requirePermission('monitoring', 'READ'), async (req, res) => {
try {
const { search, page, size } = req.query;
const result = await listItems({
search: search as string | undefined,
page: page ? parseInt(page as string, 10) : undefined,
size: size ? parseInt(size as string, 10) : undefined,
});
res.json(result);
} catch (err) {
console.error('[monitoring] 목록 조회 오류:', err);
res.status(500).json({ error: '목록 조회 중 오류가 발생했습니다.' });
}
});
// GET /api/monitoring/:sn -- 상세 조회
router.get('/:sn', requireAuth, requirePermission('monitoring', 'READ'), async (req, res) => {
try {
const sn = parseInt(req.params.sn as string, 10);
if (isNaN(sn)) {
res.status(400).json({ error: '유효하지 않은 번호입니다.' });
return;
}
const item = await getItem(sn);
res.json(item);
} catch (err) {
if (err instanceof AuthError) {
res.status(err.status).json({ error: err.message });
return;
}
console.error('[monitoring] 상세 조회 오류:', err);
res.status(500).json({ error: '조회 중 오류가 발생했습니다.' });
}
});
// POST /api/monitoring -- 등록
router.post('/', requireAuth, requirePermission('monitoring', 'CREATE'), async (req, res) => {
try {
const { title, status } = req.body;
if (!title) {
res.status(400).json({ error: '제목은 필수입니다.' });
return;
}
const result = await createItem({
title,
status,
authorId: req.user!.sub,
});
res.status(201).json(result);
} catch (err) {
if (err instanceof AuthError) {
res.status(err.status).json({ error: err.message });
return;
}
console.error('[monitoring] 등록 오류:', err);
res.status(500).json({ error: '등록 중 오류가 발생했습니다.' });
}
});
export default router;
```
### 4-3. 서비스 (보일러플레이트)
```ts
// backend/src/monitoring/monitoringService.ts
import { wingPool } from '../db/wingDb.js';
import { AuthError } from '../auth/authService.js';
// ============================================================
// 인터페이스
// ============================================================
interface MonitoringItem {
sn: number;
title: string;
status: string;
authorId: string;
regDtm: string;
}
interface ListItemsInput {
search?: string;
page?: number;
size?: number;
}
interface ListItemsResult {
items: MonitoringItem[];
totalCount: number;
page: number;
size: number;
}
interface CreateItemInput {
title: string;
status?: string;
authorId: string;
}
// ============================================================
// CRUD 함수
// ============================================================
export async function listItems(input: ListItemsInput): Promise<ListItemsResult> {
const page = input.page && input.page > 0 ? input.page : 1;
const size = input.size && input.size > 0 ? Math.min(input.size, 100) : 20;
const offset = (page - 1) * size;
let whereClause = "WHERE USE_YN = 'Y'";
const params: (string | number)[] = [];
let paramIdx = 1;
if (input.search) {
whereClause += ` AND TITLE ILIKE $${paramIdx}`;
params.push(`%${input.search}%`);
paramIdx++;
}
// 전체 건수
const countResult = await wingPool.query(
`SELECT COUNT(*) as cnt FROM MONITORING ${whereClause}`,
params,
);
const totalCount = parseInt(countResult.rows[0].cnt, 10);
// 목록
const listParams = [...params, size, offset];
const listResult = await wingPool.query(
`SELECT SN, TITLE, STATUS, AUTHOR_ID, REG_DTM
FROM MONITORING
${whereClause}
ORDER BY REG_DTM DESC
LIMIT $${paramIdx++} OFFSET $${paramIdx}`,
listParams,
);
const items: MonitoringItem[] = listResult.rows.map((r: Record<string, unknown>) => ({
sn: r.sn as number,
title: r.title as string,
status: r.status as string,
authorId: r.author_id as string,
regDtm: r.reg_dtm as string,
}));
return { items, totalCount, page, size };
}
export async function getItem(sn: number): Promise<MonitoringItem> {
const result = await wingPool.query(
`SELECT SN, TITLE, STATUS, AUTHOR_ID, REG_DTM
FROM MONITORING
WHERE SN = $1 AND USE_YN = 'Y'`,
[sn],
);
if (result.rows.length === 0) {
throw new AuthError('데이터를 찾을 수 없습니다.', 404);
}
const r = result.rows[0];
return {
sn: r.sn,
title: r.title,
status: r.status,
authorId: r.author_id,
regDtm: r.reg_dtm,
};
}
export async function createItem(input: CreateItemInput): Promise<{ sn: number }> {
if (!input.title || input.title.trim().length === 0) {
throw new AuthError('제목은 필수입니다.', 400);
}
const result = await wingPool.query(
`INSERT INTO MONITORING (TITLE, STATUS, AUTHOR_ID)
VALUES ($1, $2, $3)
RETURNING SN`,
[input.title.trim(), input.status || 'ACTIVE', input.authorId],
);
return { sn: result.rows[0].sn };
}
```
## Step 3: 백엔드 메뉴 설정 등록
### 주요 패턴 요약
`backend/src/settings/settingsService.ts``DEFAULT_MENU_CONFIG` 배열에 항목을 추가합니다.
| 항목 | 패턴 |
|------|------|
| DB Pool | `wingPool` (wing DB) 또는 `authPool` (wing_auth DB) |
| 에러 처리 | `AuthError(message, status)` 활용 |
| 논리 삭제 | `USE_YN = 'Y'/'N'` 컬럼 사용, DELETE 대신 UPDATE |
| 페이징 | `LIMIT $N OFFSET $M`, 기본 size 20, 최대 100 |
| 인증 | `requireAuth` (JWT 검증) + `requirePermission(resource, operation)` |
| 작성자 | `req.user!.sub` (JWT payload에서 USER_ID 추출) |
---
## Step 5: server.ts 라우트 등록 + DB 마이그레이션
### 5-1. server.ts에 라우트 등록
```ts
// backend/src/server.ts
// 1. import 추가
import monitoringRouter from './monitoring/monitoringRouter.js';
// 2. 업무 API 라우트 등록 (기존 라우트 아래에)
app.use('/api/monitoring', monitoringRouter);
```
> **참고**: import 경로에 `.js` 확장자가 필요하다 (TypeScript ESM 빌드).
### 5-2. DEFAULT_MENU_CONFIG에 메뉴 항목 추가
```ts
// backend/src/settings/settingsService.ts
```typescript
const DEFAULT_MENU_CONFIG: MenuConfigItem[] = [
// ... 기존 10개 메뉴 ...
{ id: 'incidents', label: '통합조회', icon: '🔍', enabled: true, order: 10 },
{ id: 'monitoring', label: '실시간 모니터링', icon: '📡', enabled: true, order: 11 },
]
];
```
`VALID_MENU_IDS``DEFAULT_MENU_CONFIG`에서 자동 파생되므로 별도 수정 불필요합니다.
```typescript
const VALID_MENU_IDS = DEFAULT_MENU_CONFIG.map(m => m.id) // 자동 포함됨
```
> **주의**: `updateMenuConfig()``VALID_MENU_IDS.length` 개수 전체가 포함되어야 저장을 허용합니다.
> 기존 운영 DB에 새 메뉴가 없는 상태에서도 `getMenuConfig()`의 fallback이 DEFAULT_MENU_CONFIG을 반환하므로 정상 동작합니다.
## Step 4: DB 초기 데이터 업데이트
`database/auth_init.sql``menu.config` 초기 JSON에 새 항목을 추가합니다.
### 5-3. auth_init.sql에 menu.config 초기 JSON 추가
```sql
INSERT INTO AUTH_SETTING (SETTING_KEY, SETTING_VAL, SETTING_DC, MDFCN_DTM) VALUES
('menu.config', '[
{"id":"prediction","label":"유출유 확산예측","icon":"🛢️","enabled":true,"order":1},
...기존 메뉴들...
{"id":"monitoring","label":"실시간 모니터링","icon":"📡","enabled":true,"order":11}
]', '메뉴 구성 설정', NOW())
ON CONFLICT (SETTING_KEY) DO NOTHING;
-- database/auth_init.sql 의 menu.config INSERT 문에 새 항목 추가
-- (신규 설치 시에만 적용. 기존 운영 DB는 관리자 UI에서 관리)
```
> **참고**: 이 SQL은 신규 설치 시에만 적용됩니다. 기존 운영 DB는 관리자 UI에서 메뉴를 관리합니다.
### 5-4. DB 마이그레이션 작성
## Step 5: 관리자 메뉴 관리에서 활성화
```sql
-- database/migration/017_monitoring.sql
코드 배포 후:
1. 관리자 계정으로 로그인
2. 관리자 패널(⚙️) → 메뉴 관리 탭
3. 새 메뉴가 목록에 표시됨
4. 활성/비활성 토글, 순서, 라벨, 아이콘을 설정
5. "변경사항 저장" 클릭
-- ============================================================
-- 마이그레이션 017: 모니터링 (MONITORING)
-- ============================================================
> 기존 DB에 새 메뉴 ID가 없으면 `getMenuConfig()`가 DEFAULT_MENU_CONFIG fallback을 사용하여 새 메뉴가 자동으로 목록에 나타납니다.
CREATE TABLE IF NOT EXISTS MONITORING (
SN SERIAL PRIMARY KEY,
TITLE VARCHAR(200) NOT NULL,
STATUS VARCHAR(20) NOT NULL DEFAULT 'ACTIVE',
AUTHOR_ID UUID NOT NULL,
USE_YN CHAR(1) NOT NULL DEFAULT 'Y',
REG_DTM TIMESTAMPTZ NOT NULL DEFAULT NOW(),
MDFCN_DTM TIMESTAMPTZ,
## 실전 예시: "모니터링" 탭 추가
CONSTRAINT FK_MONITORING_AUTHOR FOREIGN KEY (AUTHOR_ID)
REFERENCES auth.AUTH_USER(USER_ID),
CONSTRAINT CK_MONITORING_USE CHECK (USE_YN IN ('Y','N'))
);
### 1. 뷰 컴포넌트 생성
COMMENT ON TABLE MONITORING IS '모니터링';
COMMENT ON COLUMN MONITORING.USE_YN IS '사용여부 (N=논리삭제)';
CREATE INDEX IF NOT EXISTS IDX_MONITORING_REG_DTM ON MONITORING(REG_DTM DESC);
```
> 마이그레이션 파일 네이밍: `NNN_{도메인}.sql` (NNN은 다음 순번).
> 자세한 마이그레이션 패턴은 `CRUD-API-GUIDE.md`를 참고한다.
---
## 실전 예시: "monitoring" 탭 추가 전체 흐름
### 1단계: 프론트엔드 파일 생성
```bash
# frontend/src/components/views/MonitoringView.tsx 파일 생성
mkdir -p frontend/src/tabs/monitoring/components
mkdir -p frontend/src/tabs/monitoring/services
```
### 2. App.tsx 수정 (3곳)
- `frontend/src/tabs/monitoring/components/MonitoringView.tsx` 생성
- `frontend/src/tabs/monitoring/services/monitoringApi.ts` 생성
- `frontend/src/tabs/monitoring/index.ts` 생성
### 2단계: 프론트엔드 기존 파일 수정
```diff
+ import { MonitoringView } from './components/views/MonitoringView'
--- frontend/src/common/types/navigation.ts
+ export type MainTab = '...' | 'monitoring' | 'admin';
- export type MainTab = 'prediction' | 'hns' | 'rescue' | 'reports' | 'aerial' | 'assets' | 'scat' | 'incidents' | 'board' | 'weather' | 'admin'
+ export type MainTab = 'prediction' | 'hns' | 'rescue' | 'reports' | 'aerial' | 'assets' | 'scat' | 'incidents' | 'board' | 'weather' | 'monitoring' | 'admin'
const renderView = () => {
switch (activeMainTab) {
// ...
--- frontend/src/App.tsx
+ import { MonitoringView } from '@tabs/monitoring';
// renderView switch 내:
+ case 'monitoring':
+ return <MonitoringView />
case 'admin':
return <AdminView />
}
}
+ return <MonitoringView />;
--- frontend/src/common/hooks/useSubMenu.ts
// subMenuConfigs:
+ monitoring: null,
// subMenuState:
+ monitoring: '',
```
### 3. settingsService.ts 수정
### 3단계: FEATURE_ID 등록
```diff
const DEFAULT_MENU_CONFIG: MenuConfigItem[] = [
// ... 기존 메뉴들 ...
{ id: 'incidents', label: '통합조회', icon: '🔍', enabled: true, order: 10 },
+ { id: 'monitoring', label: '실시간 모니터링', icon: '📡', enabled: true, order: 11 },
]
--- frontend/src/common/constants/featureIds.ts
+ // monitoring
+ 'monitoring:main': '모니터링',
```
### 4. auth_init.sql 수정
### 4단계: 백엔드 파일 생성
menu.config JSON에 새 항목 추가 (신규 설치용)
- `backend/src/monitoring/monitoringRouter.ts` 생성
- `backend/src/monitoring/monitoringService.ts` 생성
### 5. 배포 후 관리자 UI에서 활성화
### 5단계: 백엔드 기존 파일 수정 + DB
```diff
--- backend/src/server.ts
+ import monitoringRouter from './monitoring/monitoringRouter.js';
+ app.use('/api/monitoring', monitoringRouter);
--- backend/src/settings/settingsService.ts
+ { id: 'monitoring', label: '실시간 모니터링', icon: '📡', enabled: true, order: 11 },
```
- `database/migration/017_monitoring.sql` 생성
- `database/auth_init.sql` 의 menu.config JSON에 항목 추가
### 6단계: 검증
```bash
cd frontend && npx tsc --noEmit # TypeScript 컴파일 검증
cd frontend && npx eslint . # ESLint 검증
cd backend && npx tsc --noEmit # 백엔드 컴파일 검증
```
---
## 체크리스트
- [ ] 뷰 컴포넌트 생성 (`frontend/src/components/views/`)
- [ ] `MainTab` 타입 업데이트 (`App.tsx`)
- [ ] import 및 renderView switch case 추가 (`App.tsx`)
- [ ] `DEFAULT_MENU_CONFIG`에 추가 (`settingsService.ts`)
- [ ] `menu.config` 초기 JSON 업데이트 (`auth_init.sql`)
- [ ] TypeScript 컴파일 통과 (`cd frontend && npx tsc --noEmit`)
- [ ] ESLint 통과 (`cd frontend && npx eslint .`)
- [ ] 관리자 메뉴 관리에서 새 메뉴 표시 확인
### 프론트엔드
- [ ] `frontend/src/tabs/{탭명}/components/{TabName}View.tsx` 생성
- [ ] `frontend/src/tabs/{탭명}/services/{tabName}Api.ts` 생성
- [ ] `frontend/src/tabs/{탭명}/index.ts` re-export 생성
- [ ] `navigation.ts` MainTab 타입에 새 ID 추가
- [ ] `App.tsx` import + renderView switch case 추가
- [ ] `useSubMenu.ts` subMenuConfigs + subMenuState 추가 (서브탭 있는 경우)
- [ ] `featureIds.ts` FEATURE_ID 등록
- [ ] `npx tsc --noEmit` 통과
- [ ] `npx eslint .` 통과
### 백엔드
- [ ] `backend/src/{도메인}/{domain}Router.ts` 생성
- [ ] `backend/src/{도메인}/{domain}Service.ts` 생성
- [ ] `server.ts` import + `app.use()` 등록
- [ ] `settingsService.ts` DEFAULT_MENU_CONFIG에 항목 추가
- [ ] `npx tsc --noEmit` 통과
### DB
- [ ] `database/migration/NNN_{domain}.sql` 마이그레이션 작성
- [ ] `database/auth_init.sql` menu.config 초기 JSON 업데이트
- [ ] SQL 실행 검증
### 배포 후
- [ ] 관리자 로그인 -> 메뉴 관리에서 새 메뉴 표시 확인
- [ ] 메뉴 활성화/비활성화 토글 동작 확인
- [ ] 권한 미부여 사용자에게 메뉴가 보이지 않는지 확인

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다. Load Diff

파일 보기

@ -1,31 +1,7 @@
# WING-OPS (해양 방제 운영 지원 시스템)
# WING-OPS
해양 오염 사고 대응을 위한 방제 운영 지원 시스템.
유류/HNS 확산 예측, 역추적 분석, 구조 시나리오, 항공 방제, 자산 관리, SCAT 조사, 기상/해상 정보를 통합 제공합니다.
---
## 빠른 시작
```bash
# 1. 저장소 복제
git clone https://gitea.gc-si.dev/gc/wing-ops.git
cd wing-ops
# 2. Claude Code 세션 열기
claude
# 3. 팀 워크플로우 초기화
/init-project
```
`/init-project` 실행 시 자동으로 구성되는 항목:
- `.claude/` 디렉토리 (rules, skills, scripts, settings)
- `.githooks/` (pre-commit, commit-msg 자동 검증)
- Git hooks 경로 설정 (`core.hooksPath`)
- 메모리 디렉토리 초기화
> 상세 설치 절차(Docker, DB, 오프라인 환경 등)는 [INSTALL_GUIDE.md](INSTALL_GUIDE.md)를 참조하세요.
유류/HNS 확산 예측, 역추적 분석, 구조 시나리오, 항공 방제, 자산 관리, SCAT 해안평가, 기상/해상 정보를 통합 제공한다.
---
@ -34,13 +10,43 @@ claude
| 영역 | 기술 |
|------|------|
| Frontend | React 19, Vite 7, TypeScript 5.9, Tailwind CSS 3 |
| Backend | Express 4, TypeScript, PostgreSQL (pg) |
| 상태 관리 | Zustand (클라이언트), TanStack Query (서버) |
| 지도 | Leaflet + react-leaflet |
| 실시간 | Socket.IO |
| 인증 | JWT (HttpOnly Cookie), Google OAuth |
| DB | PostgreSQL 16 + PostGIS (wing 운영DB + wing_auth 인증DB) |
| CI/CD | Gitea Actions |
| 지도 | MapLibre GL JS 5.x + deck.gl 9.x (GPU 렌더링) |
| UI | lucide-react (아이콘), @dnd-kit (드래그앤드롭), emoji-mart (이모지) |
| 실시간 | Socket.IO Client 4.8 |
| Backend | Express 4, TypeScript, PostgreSQL 16 + PostGIS |
| 인증 | JWT HttpOnly Cookie (`WING_SESSION`) + Google OAuth + RBAC 2차원 권한 |
| 보안 | Helmet, CORS, Rate-limit, 입력 살균 (sanitize) |
| CI/CD | Gitea Actions (main 머지 시 자동 배포) |
---
## 아키텍처
```
[Browser]
|
| React 19 + MapLibre GL JS + deck.gl
| Zustand (로컬 상태) + TanStack Query (서버 상태)
|
|--- HTTP (Axios) ---> [Express 4 API]
|--- WebSocket ------> [Socket.IO]
|
[PostgreSQL 16 + PostGIS]
wing DB (운영) + wing_auth DB (인증)
```
### HTTP 정책
- **GET / POST only** (보안취약점 가이드 준수, PUT/DELETE 미사용)
- JWT는 HttpOnly Cookie로 전송, Axios 인터셉터에서 자동 처리
- Helmet CORP cross-origin 설정 (sendBeacon 허용)
### 권한 체계
- RBAC 2차원 권한: **리소스(FEATURE_ID)** x **오퍼레이션(RCUD)**
- `permResolver` 엔진이 AUTH_PERM 테이블의 OPER_CD 기반으로 권한 해석
- 백엔드: `requireAuth` > `requireRole` > `requirePermission` 미들웨어 체인
---
@ -48,53 +54,140 @@ claude
```
wing/
├── frontend/ React 19 + Vite + TypeScript + Tailwind
│ └── src/
│ ├── App.tsx 메인 (탭 라우팅, 감사 로그)
│ ├── common/ 공통 모듈 (@common/ alias)
│ │ ├── components/ auth/, layer/, layout/, map/, ui/
│ │ ├── hooks/ useLayers, useSubMenu
│ │ ├── services/ api.ts, authApi.ts, layerService.ts
│ │ ├── store/ authStore, menuStore (Zustand)
│ │ ├── types/ backtrack, boomLine, hns, navigation
│ │ └── utils/ coordinates, geo, sanitize
│ └── tabs/ 탭 단위 패키지 (@tabs/ alias)
│ ├── prediction/ 확산 예측 (OilSpillView, LeftPanel 등)
│ ├── hns/ HNS 분석 (HNSView, HNSSubstanceView 등)
│ ├── rescue/ 구조 시나리오
│ ├── aerial/ 항공 방제
│ ├── weather/ 해양 기상
│ └── ... incidents, board, reports, assets, scat, admin
├── backend/ Express + TypeScript
│ └── src/
│ ├── server.ts 진입점 + 라우터 등록
│ ├── auth/ 인증 (JWT, OAuth, 미들웨어)
│ ├── users/ 사용자 관리
│ ├── roles/ 역할/권한 관리
│ ├── settings/ 시스템 설정
│ ├── menus/ 메뉴 설정
│ ├── audit/ 감사 로그
│ ├── hns/ HNS 물질 검색 API
│ ├── routes/ 레이어, 시뮬레이션
│ ├── middleware/ 보안 (입력 살균, rate-limit)
│ └── db/ DB 연결 (wingDb, authDb), seed
├── database/ SQL 스크립트 + 마이그레이션
├── docs/ 개발 문서
├── .claude/ 팀 워크플로우 (rules, skills, scripts)
└── .githooks/ Git hooks (pre-commit, commit-msg)
├── frontend/src/
│ ├── App.tsx MainTab 라우팅, 감사 로그 자동 기록
│ ├── index.css @tailwind + @import 엔트리
│ ├── common/ @common/ alias
│ │ ├── components/ auth/, layer/, layout/, map/, ui/
│ │ ├── hooks/ useFeatureTracking, useLayers, useSubMenu
│ │ ├── services/ api.ts, authApi.ts, layerService.ts
│ │ ├── store/ authStore, menuStore (Zustand)
│ │ ├── types/ backtrack, boomLine, hns, navigation
│ │ ├── utils/ cn, coordinates, geo, sanitize
│ │ ├── styles/ base.css, components.css, wing.css (@layer)
│ │ └── constants/ featureIds.ts (FEATURE_ID 상수 체계)
│ └── tabs/ @tabs/ alias (11개 탭)
│ ├── prediction/ 유류 확산 예측
│ ├── hns/ HNS 분석
│ ├── rescue/ 구조 시나리오
│ ├── aerial/ 항공 방제
│ ├── weather/ 해양 기상
│ ├── incidents/ 사건/사고 관리
│ ├── board/ 게시판
│ ├── reports/ 보고서
│ ├── assets/ 자산 관리
│ ├── scat/ Pre-SCAT 해안평가
│ └── admin/ 관리자
├── backend/src/
│ ├── server.ts Express 진입점 + 보안 미들웨어
│ ├── auth/ JWT, OAuth, 미들웨어
│ ├── users/, roles/ 사용자/역할 관리, permResolver
│ ├── settings/, menus/ 시스템 설정, 메뉴
│ ├── audit/ 감사 로그
│ ├── board/, hns/, reports/ 업무 도메인
│ ├── assets/, incidents/ 업무 도메인
│ ├── scat/, prediction/ 업무 도메인
│ ├── aerial/, rescue/ 업무 도메인
│ ├── routes/ layers, simulation
│ ├── middleware/ security (sanitize, rate-limit)
│ └── db/ wingDb, authDb, seed
├── database/
│ ├── init.sql wing DB 스키마
│ ├── auth_init.sql wing_auth DB 스키마
│ └── migration/ 001~016 마이그레이션
└── .gitea/workflows/ CI/CD 파이프라인
```
### Path Alias
| Alias | 경로 |
|-------|------|
| `@common/*` | `src/common/*` |
| `@tabs/*` | `src/tabs/*` |
---
## 개발 환경 실행
## 탭 구성 (11개)
| 탭 | 패키지 | 설명 |
|---|---|---|
| prediction | 확산 예측 | 유류 확산 모델 시뮬레이션 (KOSPS/POSEIDON/OpenDrift 앙상블) |
| hns | HNS 분석 | 위험유해물질 거동 분석 + 물질 DB |
| rescue | 구조 시나리오 | 해상 구조 시나리오 모의 |
| aerial | 항공 방제 | 항공 탐색, 드론, CCTV, 위성 |
| weather | 해양 기상 | 실시간 해양기상 오버레이 |
| incidents | 사건/사고 | 사건 이력 관리, 미디어 |
| board | 게시판 | 공지/자료 게시판 |
| reports | 보고서 | 오염보고서 생성기 |
| assets | 자산 관리 | 방제 장비/기관/담당자/선박보험 |
| scat | 해안평가 | Pre-SCAT 해안 구간 조사 |
| admin | 관리자 | 사용자/역할/권한/메뉴/설정 |
---
## 백엔드 API 라우트
모든 API는 `/api/` 접두사 하위에 등록된다.
### 인증/관리
| 라우트 | 설명 |
|--------|------|
| `/api/auth` | JWT 로그인/로그아웃, OAuth, 토큰 갱신 |
| `/api/users` | 사용자 CRUD |
| `/api/roles` | 역할/권한 관리, RCUD 매트릭스 |
| `/api/settings` | 시스템 설정 |
| `/api/menus` | 메뉴 설정 |
| `/api/audit` | 감사 로그 |
### 업무 도메인
| 라우트 | 설명 |
|--------|------|
| `/api/board` | 게시판 CRUD + 첨부파일 |
| `/api/hns` | HNS 물질 검색/분석 |
| `/api/reports` | 보고서 템플릿/생성/조회 |
| `/api/assets` | 자산(장비/기관/선박/보험) 관리 |
| `/api/incidents` | 사건/사고 이력 관리 |
| `/api/scat` | SCAT 구역/구간/조사 |
| `/api/prediction` | 확산 분석/역추적/오일펜스 |
| `/api/aerial` | 항공 미디어/CCTV/위성 |
| `/api/rescue` | 구난 작전/시나리오 |
### 지도/시뮬레이션
| 라우트 | 설명 |
|--------|------|
| `/api/layers` | 지도 레이어 데이터 |
| `/api/simulation` | 시뮬레이션 실행/결과 |
---
## 데이터베이스
| DB | 용도 | 비고 |
|----|------|------|
| wing | 운영 데이터 | PostgreSQL 16 + PostGIS |
| wing_auth | 인증/권한 | 동일 서버, 별도 DB |
마이그레이션 파일: `database/migration/001~016`
---
## 퀵스타트
### 사전 요구사항
- Node.js 20+ (`.node-version`, fnm 사용)
- PostgreSQL 16+ (운영 DB에 직접 연결)
- Node.js 20+ (`.node-version` 파일, fnm 사용)
- PostgreSQL 16+ (운영 DB 접근 가능해야 함)
### 실행
```bash
# 저장소 복제
git clone https://gitea.gc-si.dev/gc/wing-ops.git
cd wing-ops
# 백엔드 (터미널 1)
cd backend && npm install && npm run dev # localhost:3001
@ -113,101 +206,19 @@ cd backend && npx tsc --noEmit
cd frontend && npx eslint .
# 프로덕션 빌드
cd frontend && npm run build # dist/ 생성
cd backend && npm run build # dist/ 생성
cd frontend && npm run build # tsc -b && vite build → dist/
cd backend && npm run build # tsc → dist/
```
---
### 환경 변수
## 개발 워크플로우
```
계획 → 브랜치 → 개발 → 커밋/푸시 → develop MR → main PR → 자동 배포
```
### Claude Code 기반 개발 절차
| 단계 | 작업 | Claude 스킬 |
|------|------|-------------|
| 1. 계획 | 3개+ 파일 수정 시 Claude가 Plan Mode 진입 | (자동) |
| 2. 브랜치 | `feature/기능명` 으로 develop에서 분기 | - |
| 3. 개발 | Claude가 코드 작성 + 타입/린트 검증 | - |
| 4. 커밋/푸시 | pre-commit 자동 검증 후 푸시 | `/push` |
| 5. develop MR | feature → develop MR 생성 | `/mr` |
| 6. 릴리즈 | develop → main PR 생성 | `/release` |
| 7. 배포 | main 머지 시 Gitea Actions 자동 배포 | - |
> 상세 워크플로우는 [DEVELOPMENT-GUIDE.md](DEVELOPMENT-GUIDE.md)를 참조하세요.
---
## 문서 안내
### 개발 가이드
| 문서 | 설명 | 대상 |
|------|------|------|
| [DEVELOPMENT-GUIDE.md](DEVELOPMENT-GUIDE.md) | 개발 워크플로우 전체 흐름 (Plan → Branch → MR → Deploy) | 모든 개발자 |
| [COMMON-GUIDE.md](COMMON-GUIDE.md) | 공통 로직 개발 가이드 (인증, 감사로그, 메뉴, API 통신, 상태 관리) | 탭 개발자 |
| [MENU-TAB-GUIDE.md](MENU-TAB-GUIDE.md) | 새 메뉴 탭 추가 절차 (5단계) | 탭 개발자 |
### 운영 가이드
| 문서 | 설명 | 대상 |
|------|------|------|
| [INSTALL_GUIDE.md](INSTALL_GUIDE.md) | 설치 매뉴얼 (온라인/오프라인, DB 초기화) | 운영/인프라 |
| [CHANGELOG.md](CHANGELOG.md) | 변경 이력 | 모든 개발자 |
### 코드 컨벤션 (.claude/rules/)
| 규칙 | 설명 |
|------|------|
| `team-policy.md` | 보안/품질 정책 (필수 준수) |
| `git-workflow.md` | 브랜치/커밋/MR 규칙 |
| `code-style.md` | TypeScript/React 코드 스타일 |
| `naming.md` | 네이밍 규칙 |
| `testing.md` | 테스트 규칙 |
---
## 공통 기능 요약
개별 탭 개발 시 아래 공통 기능을 활용합니다.
상세 사용법은 [COMMON-GUIDE.md](COMMON-GUIDE.md)를 참조하세요.
| 기능 | 프론트엔드 | 백엔드 | 상세 |
|------|-----------|--------|------|
| 인증/인가 | `authStore`, `api.ts` (자동 쿠키) | `requireAuth`, `requireRole` | [COMMON-GUIDE.md #1](COMMON-GUIDE.md#1-인증인가) |
| 감사 로그 | 탭 이동 자동 기록 (sendBeacon) | `audit/` 모듈 | [COMMON-GUIDE.md #2](COMMON-GUIDE.md#2-감사-로그-audit-log) |
| 메뉴 시스템 | `menuStore` | `menus/`, `settings/` | [COMMON-GUIDE.md #3](COMMON-GUIDE.md#3-메뉴-시스템) |
| API 통신 | `api.ts` (Axios + 인터셉터) | Express 라우터 | [COMMON-GUIDE.md #4](COMMON-GUIDE.md#4-api-통신-패턴) |
| 상태 관리 | Zustand, TanStack Query | - | [COMMON-GUIDE.md #5](COMMON-GUIDE.md#5-상태-관리) |
---
## Claude Code 스킬
| 스킬 | 설명 |
|------|------|
| `/push` | 커밋 + 푸시 (한 번에) |
| `/mr` | 커밋 + 푸시 + develop MR (한 번에) |
| `/release` | develop → main 릴리즈 MR |
| `/create-mr` | MR만 생성 (세부 옵션) |
| `/fix-issue` | Gitea 이슈 분석 + 수정 브랜치 생성 |
| `/sync-team-workflow` | 팀 워크플로우 동기화 |
| `/changelog` | CHANGELOG.md 갱신 |
---
## 환경 변수
### 프론트엔드 (.env)
프론트엔드 (`frontend/.env`):
```
VITE_API_URL=http://localhost:3001/api
VITE_GOOGLE_CLIENT_ID=your-google-client-id
```
### 백엔드 (.env)
백엔드 (`backend/.env`):
```
PORT=3001
NODE_ENV=development
@ -216,7 +227,7 @@ AUTH_DB_HOST=localhost
AUTH_DB_PORT=5432
AUTH_DB_NAME=wing_auth
AUTH_DB_USER=wing_auth
AUTH_DB_PASSWORD=WingAuth!2026
AUTH_DB_PASSWORD=your-password
GOOGLE_CLIENT_ID=your-google-client-id
```
@ -230,18 +241,44 @@ GOOGLE_CLIENT_ID=your-google-client-id
| 백엔드 API | https://wing-demo.gc-si.dev/api/ |
| CI/CD | Gitea Actions (main 머지 시 자동 배포) |
배포 파이프라인 상세는 [DEVELOPMENT-GUIDE.md #7](DEVELOPMENT-GUIDE.md#7-자동-배포)을 참조하세요.
---
## 개발 워크플로우
```
브랜치 분기 → 개발 → 커밋/푸시 → develop MR → main PR → 자동 배포
```
- `main`: 배포 가능한 안정 브랜치 (보호됨)
- `develop`: 개발 통합 브랜치 (보호됨)
- `feature/`, `bugfix/`, `hotfix/`: 작업 브랜치
- 직접 push 금지, MR을 통해서만 머지
---
## 문서 최신화 규칙
## 문서 안내
공통 기능(인증, 감사로그, 메뉴 시스템, API 통신 등)을 추가/변경할 때:
1. 해당 기능 코드 구현
2. `docs/COMMON-GUIDE.md` 최신화 (필수)
3. 필요 시 `CLAUDE.md` 프로젝트 구조 갱신
### 개발 가이드
매 기능 개발 완료 시:
```
Claude에게: "memory 파일 최신화해줘"
```
| 문서 | 설명 |
|------|------|
| [DEVELOPMENT-GUIDE.md](DEVELOPMENT-GUIDE.md) | 개발 워크플로우 전체 흐름 |
| [COMMON-GUIDE.md](COMMON-GUIDE.md) | 공통 로직 (인증, 감사로그, 메뉴, API, 상태관리) |
| [MENU-TAB-GUIDE.md](MENU-TAB-GUIDE.md) | 새 메뉴 탭 추가 절차 |
### 운영 가이드
| 문서 | 설명 |
|------|------|
| [INSTALL_GUIDE.md](INSTALL_GUIDE.md) | 설치 매뉴얼 (온라인/오프라인, DB 초기화) |
| [CHANGELOG.md](CHANGELOG.md) | 전체 변경 이력 |
### 코드 컨벤션 (`.claude/rules/`)
| 규칙 | 설명 |
|------|------|
| `team-policy.md` | 보안/품질 정책 |
| `git-workflow.md` | 브랜치/커밋/MR 규칙 |
| `code-style.md` | TypeScript/React 코드 스타일 |
| `naming.md` | 네이밍 규칙 |
| `testing.md` | 테스트 규칙 |