Merge pull request 'refactor(css): CSS 리팩토링 + 문서 전면 갱신' (#61) from feature/css-refactoring into main
All checks were successful
Build and Deploy Wing-Demo / build-and-deploy (push) Successful in 31s
All checks were successful
Build and Deploy Wing-Demo / build-and-deploy (push) Successful in 31s
Reviewed-on: #61
This commit is contained in:
커밋
a9294e848c
1
.gitignore
vendored
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
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
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` | 프로젝트 초기 설정 |
|
||||
|
||||
1760
docs/COMMON-GUIDE.md
1760
docs/COMMON-GUIDE.md
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
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
371
docs/README.md
371
docs/README.md
@ -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` | 테스트 규칙 |
|
||||
|
||||
불러오는 중...
Reference in New Issue
Block a user