chore: 백업 디렉토리 제거 및 gitignore 추가
docs/_backup_*/ 패턴을 .gitignore에 추가하여 문서 백업 디렉토리가 커밋에 포함되지 않도록 설정. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
부모
6fbb3fc249
커밋
a2cfc02b70
1
.gitignore
vendored
1
.gitignore
vendored
@ -29,6 +29,7 @@ backend/data/*.db-wal
|
|||||||
|
|
||||||
# Large reference data (keep locally, do not commit)
|
# Large reference data (keep locally, do not commit)
|
||||||
_reference/
|
_reference/
|
||||||
|
docs/_backup_*/
|
||||||
/scat/
|
/scat/
|
||||||
참고용/
|
참고용/
|
||||||
논문/
|
논문/
|
||||||
|
|||||||
@ -1,42 +0,0 @@
|
|||||||
# 변경 이력
|
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
### 2026-03-01
|
|
||||||
|
|
||||||
## [2026-03-01] Phase 4 완료 — 나머지 6개 탭 Mock → API 전환
|
|
||||||
|
|
||||||
### Added
|
|
||||||
- SCAT: 구역/구간 조회 3 API + PostGIS (011_scat.sql)
|
|
||||||
- Board: 매뉴얼 CRUD + 첨부파일 API (012_board_ext.sql)
|
|
||||||
- HNS: 분석 CRUD 5 API (013_hns_analysis.sql)
|
|
||||||
- Prediction: 분석/역추적/오일펜스 7 API (014_prediction.sql)
|
|
||||||
- Aerial: 미디어/CCTV/위성 6 API + PostGIS (015_aerial.sql)
|
|
||||||
- Rescue: 구난 작전/시나리오 3 API + JSONB (016_rescue.sql)
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
- Prediction 분석 상세 500 에러 (ACDNT_WEATHER 컬럼명 불일치)
|
|
||||||
- 시뮬레이션 API CORS 에러 (localhost 하드코딩 → api 인스턴스)
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
- 하드코딩 URL 환경변수 전환 (GeoServer, CORS, CSP 등)
|
|
||||||
- backtrackMockData.ts 삭제
|
|
||||||
|
|
||||||
### 2026-02-28
|
|
||||||
- feat(reports): 보고서 탭 localStorage → DB/API 전환 (MR#31)
|
|
||||||
- DB 7개 테이블 (REPORT_TMPL, REPORT_TMPL_SECT, REPORT_ANALYSIS_CTGR, REPORT_CTGR_SECT, REPORT, REPORT_SECT_DATA 등)
|
|
||||||
- 백엔드 CRUD API (GET/POST only 패턴)
|
|
||||||
- 프론트 4개 컴포넌트 API 연동 (localStorage 제거)
|
|
||||||
- refactor(backend): SQLite → PostgreSQL 마이그레이션 + wing DB 연결 (MR#22)
|
|
||||||
- feat: Phase 5 View 분할 + RBAC 2차원 권한 + 게시판 CRUD API 연동 (MR#29)
|
|
||||||
- 대형 View 서브탭 분할 + FEATURE_ID 체계 도입
|
|
||||||
- RBAC 오퍼레이션 기반 2차원 권한 시스템 (permResolver, AUTH_PERM OPER_CD)
|
|
||||||
- 게시판 CRUD API (boardService/Router) + 프론트 연동
|
|
||||||
- refactor(frontend): 공통 모듈 common/ 분리 + 탭 단위 패키지 구조 전환 (MR#21)
|
|
||||||
- docs: MOCK-TO-API-GUIDE.md 작성 (Mock→API 전환 개발 지침)
|
|
||||||
- docs: CRUD-API-GUIDE.md 작성 (RBAC 기반 CRUD API 표준 가이드)
|
|
||||||
- chore: 팀 워크플로우 v1.4.0 동기화 (서브에이전트 3종 + 정책)
|
|
||||||
- policy: HTTP 메소드 제한 결정 (GET/POST only — 보안취약점 가이드 준수)
|
|
||||||
|
|
||||||
### 2026-02-27
|
|
||||||
- chore: 팀 워크플로우 v1.3.0 초기화
|
|
||||||
@ -1,502 +0,0 @@
|
|||||||
# WING-OPS 공통 로직 개발 가이드
|
|
||||||
|
|
||||||
개별 탭 개발자가 공통 영역 구현을 참조하여 연동할 수 있도록 정리한 문서입니다.
|
|
||||||
공통 기능을 추가/변경할 때 반드시 이 문서를 최신화하세요.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 인증/인가
|
|
||||||
|
|
||||||
### 개요
|
|
||||||
JWT 기반 세션 인증. HttpOnly 쿠키(`WING_SESSION`)로 토큰을 관리하며, 프론트엔드에서는 Zustand `authStore`로 상태를 관리합니다.
|
|
||||||
|
|
||||||
### 권한 모델: 리소스 × 오퍼레이션 (RBAC)
|
|
||||||
|
|
||||||
**2차원 권한 모델**: 리소스 트리(상속) × 오퍼레이션(RCUD, 플랫)
|
|
||||||
|
|
||||||
```
|
|
||||||
AUTH_PERM 테이블: (ROLE_SN, RSRC_CD, OPER_CD, GRANT_YN)
|
|
||||||
|
|
||||||
리소스 트리 (AUTH_PERM_TREE) 오퍼레이션 (플랫)
|
|
||||||
├── prediction READ = 조회/열람
|
|
||||||
│ ├── prediction:analysis CREATE = 생성
|
|
||||||
│ ├── prediction:list UPDATE = 수정
|
|
||||||
│ └── prediction:theory DELETE = 삭제
|
|
||||||
├── board
|
|
||||||
│ ├── board:notice
|
|
||||||
│ └── board:data
|
|
||||||
└── admin
|
|
||||||
├── admin:users
|
|
||||||
└── admin:permissions
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 오퍼레이션 코드
|
|
||||||
|
|
||||||
| OPER_CD | 설명 | 비고 |
|
|
||||||
|---------|------|------|
|
|
||||||
| `READ` | 조회/열람 | 목록, 상세 조회 |
|
|
||||||
| `CREATE` | 생성 | 새 데이터 등록 |
|
|
||||||
| `UPDATE` | 수정 | 기존 데이터 변경 |
|
|
||||||
| `DELETE` | 삭제 | 데이터 삭제 |
|
|
||||||
| `MANAGE` | 관리 | 관리자 설정 (확장용) |
|
|
||||||
| `EXPORT` | 내보내기 | 다운로드/출력 (확장용) |
|
|
||||||
|
|
||||||
#### 상속 규칙
|
|
||||||
|
|
||||||
1. 부모 리소스의 **READ**가 N → 자식의 **모든 오퍼레이션** 강제 N (접근 자체 차단)
|
|
||||||
2. 해당 `(RSRC_CD, OPER_CD)` 명시적 레코드 있으면 → 그 값 사용
|
|
||||||
3. 명시적 레코드 없으면 → 부모의 **같은 OPER_CD** 상속
|
|
||||||
4. 최상위까지 없으면 → 기본 N (거부)
|
|
||||||
|
|
||||||
```
|
|
||||||
예시: board (READ:Y, CREATE:Y, UPDATE:Y, DELETE:N)
|
|
||||||
└── board:notice
|
|
||||||
├── READ: 상속 Y (부모 READ Y)
|
|
||||||
├── CREATE: 상속 Y (부모 CREATE Y)
|
|
||||||
├── UPDATE: 명시적 N (override 가능)
|
|
||||||
└── DELETE: 상속 N (부모 DELETE N)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 키 구분자
|
|
||||||
- 리소스 내부 경로: `:` (board:notice)
|
|
||||||
- 리소스-오퍼레이션 결합 (내부용): `::` (board:notice::READ)
|
|
||||||
|
|
||||||
### 백엔드
|
|
||||||
|
|
||||||
#### 미들웨어
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { requireAuth, requireRole, requirePermission } from '../auth/authMiddleware.js'
|
|
||||||
|
|
||||||
// 인증만 필요한 라우트
|
|
||||||
router.use(requireAuth)
|
|
||||||
|
|
||||||
// 역할 기반 (관리 API용)
|
|
||||||
router.use(requireRole('ADMIN'))
|
|
||||||
|
|
||||||
// 리소스×오퍼레이션 기반 (일반 비즈니스 API용)
|
|
||||||
router.post('/notice/list', requirePermission('board:notice', 'READ'), handler)
|
|
||||||
router.post('/notice/create', requirePermission('board:notice', 'CREATE'), handler)
|
|
||||||
router.post('/notice/update', requirePermission('board:notice', 'UPDATE'), handler)
|
|
||||||
router.post('/notice/delete', requirePermission('board:notice', 'DELETE'), handler)
|
|
||||||
```
|
|
||||||
|
|
||||||
`requirePermission`은 요청당 1회만 DB 조회하고 `req.resolvedPermissions`에 캐싱합니다.
|
|
||||||
|
|
||||||
#### JWT 페이로드 (req.user)
|
|
||||||
`requireAuth` 통과 후 `req.user`에 담기는 정보:
|
|
||||||
```typescript
|
|
||||||
interface JwtPayload {
|
|
||||||
sub: string // 사용자 UUID (USER_ID)
|
|
||||||
acnt: string // 계정명 (USER_ACNT)
|
|
||||||
name: string // 사용자명 (USER_NM)
|
|
||||||
roles: string[] // 역할 코드 목록 (ADMIN, MANAGER, USER, VIEWER)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 라우터 패턴 (CRUD 구조)
|
|
||||||
```typescript
|
|
||||||
// backend/src/[모듈]/[모듈]Router.ts
|
|
||||||
import { Router } from 'express'
|
|
||||||
import { requireAuth, requirePermission } from '../auth/authMiddleware.js'
|
|
||||||
|
|
||||||
const router = Router()
|
|
||||||
router.use(requireAuth)
|
|
||||||
|
|
||||||
// 리소스별 CRUD 엔드포인트
|
|
||||||
router.post('/list', requirePermission('module:sub', 'READ'), listHandler)
|
|
||||||
router.post('/detail', requirePermission('module:sub', 'READ'), detailHandler)
|
|
||||||
router.post('/create', requirePermission('module:sub', 'CREATE'), createHandler)
|
|
||||||
router.post('/update', requirePermission('module:sub', 'UPDATE'), updateHandler)
|
|
||||||
router.post('/delete', requirePermission('module:sub', 'DELETE'), deleteHandler)
|
|
||||||
|
|
||||||
export default router
|
|
||||||
```
|
|
||||||
|
|
||||||
### 프론트엔드
|
|
||||||
|
|
||||||
#### authStore (Zustand)
|
|
||||||
```typescript
|
|
||||||
import { useAuthStore } from '@common/store/authStore'
|
|
||||||
|
|
||||||
const { user, isAuthenticated, hasPermission, logout } = useAuthStore()
|
|
||||||
|
|
||||||
// 사용자 정보
|
|
||||||
user?.id // UUID
|
|
||||||
user?.name // 이름
|
|
||||||
user?.roles // ['ADMIN', 'USER']
|
|
||||||
user?.permissions // { 'prediction': ['READ','CREATE','UPDATE','DELETE'], ... }
|
|
||||||
|
|
||||||
// 권한 확인 (리소스 × 오퍼레이션)
|
|
||||||
hasPermission('prediction') // READ 확인 (기본값)
|
|
||||||
hasPermission('prediction', 'READ') // 명시적 READ 확인
|
|
||||||
hasPermission('board:notice', 'CREATE') // 공지사항 생성 권한
|
|
||||||
hasPermission('board:notice', 'DELETE') // 공지사항 삭제 권한
|
|
||||||
|
|
||||||
// 하위 호환: operation 생략 시 'READ' 기본값
|
|
||||||
hasPermission('admin') // === hasPermission('admin', 'READ')
|
|
||||||
```
|
|
||||||
|
|
||||||
#### API 클라이언트
|
|
||||||
```typescript
|
|
||||||
import { api } from '@common/services/api'
|
|
||||||
|
|
||||||
// withCredentials: true 설정으로 JWT 쿠키 자동 포함
|
|
||||||
const response = await api.post('/your-endpoint/list', params)
|
|
||||||
const response = await api.post('/your-endpoint/create', data)
|
|
||||||
|
|
||||||
// 401 응답 시 자동 로그아웃 처리 (인터셉터)
|
|
||||||
// 403 응답 시 권한 부족 (requirePermission 미들웨어)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 감사 로그 (Audit Log)
|
|
||||||
|
|
||||||
### 개요
|
|
||||||
사용자 행동을 추적하는 감사 로그 시스템. 현재 탭 이동 로그를 자동 기록하며, 향후 API 호출 로깅으로 확장 가능합니다.
|
|
||||||
|
|
||||||
### 자동 기록 (탭 이동)
|
|
||||||
`App.tsx`의 `useEffect`에서 `activeMainTab` 변경을 감지하여 `navigator.sendBeacon`으로 자동 전송합니다. 개별 탭 개발자는 별도 작업이 필요 없습니다.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// frontend/src/App.tsx (자동 적용, 수정 불필요)
|
|
||||||
import { API_BASE_URL } from '@common/services/api'
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isAuthenticated) return
|
|
||||||
const blob = new Blob(
|
|
||||||
[JSON.stringify({ action: 'TAB_VIEW', detail: activeMainTab })],
|
|
||||||
{ type: 'text/plain' }
|
|
||||||
)
|
|
||||||
navigator.sendBeacon(`${API_BASE_URL}/audit/log`, blob)
|
|
||||||
}, [activeMainTab, isAuthenticated])
|
|
||||||
```
|
|
||||||
|
|
||||||
### 수동 기록 (향후 확장)
|
|
||||||
특정 작업에 대해 명시적으로 감사 로그를 기록하려면:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { API_BASE_URL } from '@common/services/api'
|
|
||||||
|
|
||||||
const blob = new Blob(
|
|
||||||
[JSON.stringify({ action: 'ADMIN_ACTION', detail: '사용자 승인' })],
|
|
||||||
{ type: 'text/plain' }
|
|
||||||
)
|
|
||||||
navigator.sendBeacon(`${API_BASE_URL}/audit/log`, blob)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 감사 로그 테이블 구조 (AUTH_AUDIT_LOG)
|
|
||||||
|
|
||||||
| 컬럼 | 타입 | 용도 | 현재 사용 |
|
|
||||||
|------|------|------|-----------|
|
|
||||||
| LOG_SN | SERIAL PK | 로그 순번 | O |
|
|
||||||
| USER_ID | UUID | 사용자 ID | O |
|
|
||||||
| ACTION_CD | VARCHAR(30) | 액션 코드 | O (TAB_VIEW) |
|
|
||||||
| ACTION_DTL | VARCHAR(100) | 액션 상세 (탭ID 등) | O |
|
|
||||||
| HTTP_METHOD | VARCHAR(10) | GET/POST/PUT/DELETE | - (향후) |
|
|
||||||
| CRUD_TYPE | VARCHAR(10) | SELECT/INSERT/UPDATE/DELETE | - (향후) |
|
|
||||||
| REQ_URL | VARCHAR(500) | 요청 URL | - (향후) |
|
|
||||||
| REQ_DTM | TIMESTAMPTZ | 요청 시각 | O |
|
|
||||||
| RES_DTM | TIMESTAMPTZ | 응답 완료 시각 | - (향후) |
|
|
||||||
| RES_STATUS | SMALLINT | HTTP 상태 코드 | - (향후) |
|
|
||||||
| RES_SIZE | INTEGER | 응답 데이터 크기(bytes) | - (향후) |
|
|
||||||
| IP_ADDR | VARCHAR(45) | 클라이언트 IP | O |
|
|
||||||
| USER_AGENT | VARCHAR(500) | 브라우저 정보 | O |
|
|
||||||
| EXTRA | JSONB | 추가 메타데이터 | - (향후) |
|
|
||||||
|
|
||||||
### ACTION_CD 코드 체계
|
|
||||||
| 코드 | 설명 |
|
|
||||||
|------|------|
|
|
||||||
| TAB_VIEW | 상단 탭 이동 |
|
|
||||||
| API_CALL | API 호출 (향후) |
|
|
||||||
| LOGIN | 로그인 (향후) |
|
|
||||||
| LOGOUT | 로그아웃 (향후) |
|
|
||||||
| ADMIN_ACTION | 관리자 작업 (향후) |
|
|
||||||
|
|
||||||
### 관리자 조회 API
|
|
||||||
```typescript
|
|
||||||
// frontend/src/services/authApi.ts
|
|
||||||
import { fetchAuditLogs } from '../services/authApi'
|
|
||||||
|
|
||||||
const result = await fetchAuditLogs({
|
|
||||||
page: 1,
|
|
||||||
size: 50,
|
|
||||||
actionCd: 'TAB_VIEW',
|
|
||||||
from: '2026-02-28',
|
|
||||||
to: '2026-02-28',
|
|
||||||
})
|
|
||||||
// result: { items: AuditLogItem[], total: number, page: number, size: number }
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 메뉴 시스템
|
|
||||||
|
|
||||||
### 개요
|
|
||||||
DB 기반 동적 메뉴 구성. 관리자가 메뉴 표시 여부/순서를 설정하면 모든 사용자에게 반영됩니다.
|
|
||||||
새 메뉴 탭 추가 시 `docs/MENU-TAB-GUIDE.md`를 참조하세요.
|
|
||||||
|
|
||||||
### 메뉴 상태 (menuStore)
|
|
||||||
```typescript
|
|
||||||
// frontend/src/store/menuStore.ts
|
|
||||||
import { useMenuStore } from '../store/menuStore'
|
|
||||||
|
|
||||||
const { menus, loadMenuConfig } = useMenuStore()
|
|
||||||
|
|
||||||
// menus: MenuConfigItem[] — 활성화되고 정렬된 메뉴 목록
|
|
||||||
// menus[0].id → 'prediction'
|
|
||||||
// menus[0].label → '유출유 확산예측'
|
|
||||||
// menus[0].enabled → true
|
|
||||||
```
|
|
||||||
|
|
||||||
### 메뉴 설정 저장소
|
|
||||||
- DB: `AUTH_SETTING` 테이블의 `menu.config` 키 (JSON 배열)
|
|
||||||
- 백엔드: `backend/src/settings/settingsService.ts`의 `DEFAULT_MENU_CONFIG`
|
|
||||||
- API: `GET/PUT /api/menus`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. API 통신 패턴
|
|
||||||
|
|
||||||
### Axios 인스턴스 설정
|
|
||||||
```typescript
|
|
||||||
// frontend/src/services/api.ts
|
|
||||||
export const api = axios.create({
|
|
||||||
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3001/api',
|
|
||||||
withCredentials: true, // JWT 쿠키 자동 포함
|
|
||||||
timeout: 30000,
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### 새 API 서비스 작성 패턴
|
|
||||||
```typescript
|
|
||||||
// frontend/src/services/newService.ts
|
|
||||||
import { api } from './api'
|
|
||||||
|
|
||||||
export interface MyData {
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchMyData(): Promise<MyData[]> {
|
|
||||||
const response = await api.get<MyData[]>('/my-endpoint')
|
|
||||||
return response.data
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createMyData(data: Omit<MyData, 'id'>): Promise<MyData> {
|
|
||||||
const response = await api.post<MyData>('/my-endpoint', data)
|
|
||||||
return response.data
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 에러 처리
|
|
||||||
- 401 응답: `api.ts` 인터셉터가 자동으로 로그아웃 처리
|
|
||||||
- 비즈니스 에러: `response.data.error` 메시지로 사용자에게 안내
|
|
||||||
- 백엔드에서 `AuthError` 사용 시 적절한 HTTP 상태 코드와 메시지 반환
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 상태 관리
|
|
||||||
|
|
||||||
### Zustand (클라이언트 상태)
|
|
||||||
```typescript
|
|
||||||
// frontend/src/store/newStore.ts
|
|
||||||
import { create } from 'zustand'
|
|
||||||
|
|
||||||
interface MyState {
|
|
||||||
items: string[]
|
|
||||||
addItem: (item: string) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useMyStore = create<MyState>((set) => ({
|
|
||||||
items: [],
|
|
||||||
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
|
|
||||||
}))
|
|
||||||
```
|
|
||||||
|
|
||||||
### TanStack Query (서버 상태) — 권장
|
|
||||||
```typescript
|
|
||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
||||||
import { fetchMyData, createMyData } from '../services/newService'
|
|
||||||
|
|
||||||
// 조회
|
|
||||||
const { data, isLoading } = useQuery({
|
|
||||||
queryKey: ['myData'],
|
|
||||||
queryFn: fetchMyData,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 생성/수정
|
|
||||||
const queryClient = useQueryClient()
|
|
||||||
const mutation = useMutation({
|
|
||||||
mutationFn: createMyData,
|
|
||||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['myData'] }),
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 백엔드 API CRUD 규칙
|
|
||||||
|
|
||||||
> 상세 가이드 + 게시판 실전 튜토리얼: **[CRUD-API-GUIDE.md](./CRUD-API-GUIDE.md)** 참조
|
|
||||||
|
|
||||||
### HTTP Method 정책 (보안 가이드 준수)
|
|
||||||
- 보안 취약점 점검 가이드에 따라 **POST 메서드를 기본**으로 사용한다.
|
|
||||||
- GET은 단순 조회 중 민감하지 않은 경우에만 허용 (필요 시 POST로 전환).
|
|
||||||
- PUT, DELETE, PATCH 등 기타 메서드는 사용하지 않는다.
|
|
||||||
|
|
||||||
### 오퍼레이션 기반 권한 미들웨어
|
|
||||||
OPER_CD는 HTTP Method가 아닌 **비즈니스 의미**로 결정한다.
|
|
||||||
`requirePermission` 미들웨어에 명시적으로 오퍼레이션을 지정한다.
|
|
||||||
|
|
||||||
| URL 패턴 | OPER_CD | 미들웨어 |
|
|
||||||
|----------|---------|----------|
|
|
||||||
| `/resource/list` | READ | `requirePermission(resource, 'READ')` |
|
|
||||||
| `/resource/detail` | READ | `requirePermission(resource, 'READ')` |
|
|
||||||
| `/resource/create` | CREATE | `requirePermission(resource, 'CREATE')` |
|
|
||||||
| `/resource/update` | UPDATE | `requirePermission(resource, 'UPDATE')` |
|
|
||||||
| `/resource/delete` | DELETE | `requirePermission(resource, 'DELETE')` |
|
|
||||||
|
|
||||||
### 라우터 작성 예시
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// backend/src/board/noticeRouter.ts
|
|
||||||
import { Router } from 'express'
|
|
||||||
import { requireAuth, requirePermission } from '../auth/authMiddleware.js'
|
|
||||||
|
|
||||||
const router = Router()
|
|
||||||
router.use(requireAuth)
|
|
||||||
|
|
||||||
// 조회
|
|
||||||
router.post('/list', requirePermission('board:notice', 'READ'), listHandler)
|
|
||||||
router.post('/detail', requirePermission('board:notice', 'READ'), detailHandler)
|
|
||||||
|
|
||||||
// 생성/수정/삭제
|
|
||||||
router.post('/create', requirePermission('board:notice', 'CREATE'), createHandler)
|
|
||||||
router.post('/update', requirePermission('board:notice', 'UPDATE'), updateHandler)
|
|
||||||
router.post('/delete', requirePermission('board:notice', 'DELETE'), deleteHandler)
|
|
||||||
|
|
||||||
export default router
|
|
||||||
```
|
|
||||||
|
|
||||||
### 관리 API (예외)
|
|
||||||
사용자/역할/설정 등 관리 API는 `requireRole('ADMIN')` 유지:
|
|
||||||
```typescript
|
|
||||||
router.use(requireAuth)
|
|
||||||
router.use(requireRole('ADMIN'))
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 백엔드 모듈 추가 절차
|
|
||||||
|
|
||||||
새 백엔드 모듈을 추가할 때:
|
|
||||||
|
|
||||||
1. `backend/src/[모듈명]/` 디렉토리 생성
|
|
||||||
2. `[모듈명]Service.ts` — 비즈니스 로직 (DB 쿼리)
|
|
||||||
3. `[모듈명]Router.ts` — Express 라우터 (CRUD 엔드포인트 + requirePermission)
|
|
||||||
4. `backend/src/server.ts`에 라우터 등록:
|
|
||||||
```typescript
|
|
||||||
import newRouter from './[모듈명]/[모듈명]Router.js'
|
|
||||||
app.use('/api/[경로]', newRouter)
|
|
||||||
```
|
|
||||||
5. DB 테이블 필요 시 `database/auth_init.sql`에 DDL 추가
|
|
||||||
6. 리소스 코드를 `AUTH_PERM_TREE`에 등록 (마이그레이션 SQL)
|
|
||||||
|
|
||||||
### DB 접근
|
|
||||||
```typescript
|
|
||||||
// PostgreSQL — wing DB (운영 데이터: 레이어, 사고, 예측 등)
|
|
||||||
import { wingPool } from '../db/wingDb.js'
|
|
||||||
const result = await wingPool.query('SELECT * FROM LAYER WHERE LAYER_CD = $1', [id])
|
|
||||||
|
|
||||||
// PostgreSQL — wing_auth DB (인증 데이터: 사용자, 역할, 권한 등)
|
|
||||||
import { authPool } from '../db/authDb.js'
|
|
||||||
const result = await authPool.query('SELECT * FROM AUTH_USER WHERE USER_ID = $1', [id])
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Mock → API 전환 가이드
|
|
||||||
|
|
||||||
각 탭의 mock 데이터를 DB/API로 전환하는 프로세스는 **[MOCK-TO-API-GUIDE.md](./MOCK-TO-API-GUIDE.md)** 참조.
|
|
||||||
|
|
||||||
### 전환 완료 탭
|
|
||||||
|
|
||||||
| 탭 | MR | API 경로 | 비고 |
|
|
||||||
|----|-----|----------|------|
|
|
||||||
| Board (게시판) | MR#29 | `/api/board` | PUT/DELETE 사용 (레거시, POST 전환 예정) |
|
|
||||||
| Reports (보고서) | MR#31 | `/api/reports` | GET/POST only 적용 |
|
|
||||||
|
|
||||||
### Reports API 엔드포인트
|
|
||||||
|
|
||||||
| Method | Path | 설명 | 권한 |
|
|
||||||
|--------|------|------|------|
|
|
||||||
| GET | `/api/reports/templates` | 템플릿 목록 + 섹션 정의 | requireAuth |
|
|
||||||
| GET | `/api/reports/categories` | 분석 카테고리 목록 + 섹션 | requireAuth |
|
|
||||||
| GET | `/api/reports` | 보고서 목록 (필터: jrsdCd, tmplCd, sttsCd, search) | reports READ |
|
|
||||||
| GET | `/api/reports/:sn` | 보고서 상세 (섹션 데이터 포함) | reports READ |
|
|
||||||
| POST | `/api/reports` | 보고서 생성 | reports CREATE |
|
|
||||||
| POST | `/api/reports/:sn/update` | 보고서 수정 | reports UPDATE |
|
|
||||||
| POST | `/api/reports/:sn/delete` | 보고서 삭제 (논리) | reports DELETE |
|
|
||||||
| POST | `/api/reports/:sn/sections/:sectCd` | 개별 섹션 수정 | reports UPDATE |
|
|
||||||
|
|
||||||
### 프론트엔드 API 서비스
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// frontend/src/tabs/reports/services/reportsApi.ts
|
|
||||||
import { api } from '@common/services/api'
|
|
||||||
|
|
||||||
// 조회 (GET)
|
|
||||||
const templates = await fetchTemplates() // GET /reports/templates (캐싱)
|
|
||||||
const categories = await fetchCategories() // GET /reports/categories (캐싱)
|
|
||||||
const list = await fetchReports({ tmplCd, sttsCd }) // GET /reports
|
|
||||||
const detail = await fetchReport(sn) // GET /reports/:sn
|
|
||||||
|
|
||||||
// 생성/수정/삭제 (POST)
|
|
||||||
await createReportApi({ tmplSn, title, sections }) // POST /reports
|
|
||||||
await updateReportApi(sn, { title, sections }) // POST /reports/:sn/update
|
|
||||||
await deleteReportApi(sn) // POST /reports/:sn/delete
|
|
||||||
|
|
||||||
// 고수준 함수 (OilSpillReportData ↔ API 변환 포함)
|
|
||||||
await saveReport(reportData) // create 또는 update 자동 분기
|
|
||||||
const reports = await loadReportsFromApi() // 전체 목록 + 변환
|
|
||||||
const detail = await loadReportDetail(sn) // 상세 + 섹션 복원
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 파일 구조 요약
|
|
||||||
|
|
||||||
```
|
|
||||||
frontend/src/
|
|
||||||
├── common/
|
|
||||||
│ ├── services/api.ts Axios 인스턴스 + API_BASE_URL + 인터셉터
|
|
||||||
│ ├── services/authApi.ts 인증/사용자/역할/설정/메뉴/감사로그 API
|
|
||||||
│ ├── store/authStore.ts 인증 상태 + hasPermission (Zustand)
|
|
||||||
│ ├── store/menuStore.ts 메뉴 상태 (Zustand)
|
|
||||||
│ └── hooks/ useSubMenu, useFeatureTracking 등
|
|
||||||
├── tabs/ 탭별 패키지 (11개)
|
|
||||||
└── App.tsx 탭 라우팅 + 감사 로그 자동 기록
|
|
||||||
|
|
||||||
backend/src/
|
|
||||||
├── auth/ 인증 (JWT, OAuth, 미들웨어, requirePermission)
|
|
||||||
├── users/ 사용자 관리
|
|
||||||
├── roles/ 역할/권한 관리 (permResolver, roleService)
|
|
||||||
├── board/ 게시판 CRUD (boardService, boardRouter)
|
|
||||||
├── reports/ 보고서 CRUD (reportsService, reportsRouter)
|
|
||||||
├── settings/ 시스템 설정
|
|
||||||
├── menus/ 메뉴 설정
|
|
||||||
├── audit/ 감사 로그
|
|
||||||
├── db/ DB 연결 (authDb, wingDb)
|
|
||||||
├── middleware/ 보안 미들웨어
|
|
||||||
└── server.ts Express 진입점 + 라우터 등록
|
|
||||||
|
|
||||||
database/
|
|
||||||
├── auth_init.sql 인증 DB DDL + 초기 데이터
|
|
||||||
├── init.sql 운영 DB DDL
|
|
||||||
└── migration/ 마이그레이션 스크립트
|
|
||||||
├── 003_perm_tree.sql 리소스 트리 (AUTH_PERM_TREE)
|
|
||||||
├── 004_oper_cd.sql 오퍼레이션 코드 (OPER_CD) 추가
|
|
||||||
├── 006_board.sql 게시판 (BOARD_POST)
|
|
||||||
└── 007_reports.sql 보고서 (REPORT_TMPL, REPORT, REPORT_SECT_DATA 등 7개)
|
|
||||||
```
|
|
||||||
파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
Load Diff
@ -1,433 +0,0 @@
|
|||||||
# WING 개발 워크플로우 가이드
|
|
||||||
|
|
||||||
## 목차
|
|
||||||
1. [전체 흐름 요약](#1-전체-흐름-요약)
|
|
||||||
2. [계획 수립 (Plan)](#2-계획-수립-plan)
|
|
||||||
3. [브랜치 생성 및 개발](#3-브랜치-생성-및-개발)
|
|
||||||
4. [커밋 & 푸시](#4-커밋--푸시)
|
|
||||||
5. [MR 생성 (feature → develop)](#5-mr-생성-feature--develop)
|
|
||||||
6. [릴리즈 PR (develop → main)](#6-릴리즈-pr-develop--main)
|
|
||||||
7. [자동 배포](#7-자동-배포)
|
|
||||||
8. [프로젝트 문서 최신화](#8-프로젝트-문서-최신화)
|
|
||||||
9. [실전 예시: 기능 추가 A to Z](#9-실전-예시-기능-추가-a-to-z)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 전체 흐름 요약
|
|
||||||
|
|
||||||
```
|
|
||||||
계획 수립 → 브랜치 생성 → 개발 → 커밋/푸시 → develop MR → main PR → 자동 배포
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
[Plan Mode] Claude가 코드베이스 분석 후 구현 계획 작성
|
|
||||||
↓
|
|
||||||
[Branch] feature/기능명 브랜치 생성 (develop 기반)
|
|
||||||
↓
|
|
||||||
[Develop] 코드 작성 + TypeScript/ESLint 검증
|
|
||||||
↓
|
|
||||||
[Commit & Push] Conventional Commits 형식 + pre-commit 자동 검증
|
|
||||||
↓
|
|
||||||
[MR → develop] 코드 리뷰 + 머지
|
|
||||||
↓
|
|
||||||
[PR → main] 릴리즈 MR + 머지
|
|
||||||
↓
|
|
||||||
[Auto Deploy] Gitea Actions → 빌드 → 서버 배포
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 계획 수립 (Plan)
|
|
||||||
|
|
||||||
3개 이상 파일 수정이 예상되거나 아키텍처에 영향을 주는 작업은 **Plan Mode**로 시작합니다.
|
|
||||||
|
|
||||||
### Claude에게 요청하는 방법
|
|
||||||
|
|
||||||
```
|
|
||||||
"사용자 프로필 페이지를 추가해줘"
|
|
||||||
→ Claude가 자동으로 Plan Mode 진입 → 코드베이스 분석 → 구현 계획 제시
|
|
||||||
→ 사용자 승인 후 구현 시작
|
|
||||||
```
|
|
||||||
|
|
||||||
### 계획에 포함되는 내용
|
|
||||||
- 수정/생성할 파일 목록
|
|
||||||
- 변경 범위 및 영향도
|
|
||||||
- 기술적 선택지와 권장안
|
|
||||||
- 구현 순서
|
|
||||||
|
|
||||||
### Plan Mode가 불필요한 경우
|
|
||||||
- 단순 버그 수정 (1~2개 파일)
|
|
||||||
- 텍스트/스타일 수정
|
|
||||||
- 설정 변경
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 브랜치 생성 및 개발
|
|
||||||
|
|
||||||
### 브랜치 네이밍 규칙
|
|
||||||
|
|
||||||
| 유형 | 형식 | 예시 |
|
|
||||||
|------|------|------|
|
|
||||||
| 기능 | `feature/설명` | `feature/user-profile` |
|
|
||||||
| 이슈 | `feature/ISSUE-번호-설명` | `feature/ISSUE-42-login-fix` |
|
|
||||||
| 버그 | `bugfix/ISSUE-번호-설명` | `bugfix/ISSUE-15-token-expired` |
|
|
||||||
| 긴급 | `hotfix/설명` | `hotfix/security-patch` |
|
|
||||||
|
|
||||||
### 브랜치 생성
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# develop에서 분기
|
|
||||||
git checkout develop
|
|
||||||
git pull origin develop
|
|
||||||
git checkout -b feature/user-profile
|
|
||||||
```
|
|
||||||
|
|
||||||
### 개발 중 검증
|
|
||||||
|
|
||||||
로컬에서 타입 체크와 린트를 수시로 확인합니다:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Frontend
|
|
||||||
cd frontend && npx tsc --noEmit && npx eslint src/
|
|
||||||
|
|
||||||
# Backend
|
|
||||||
cd backend && npx tsc --noEmit
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 커밋 & 푸시
|
|
||||||
|
|
||||||
### Conventional Commits 형식
|
|
||||||
|
|
||||||
```
|
|
||||||
type(scope): 한국어 설명
|
|
||||||
```
|
|
||||||
|
|
||||||
| type | 용도 | 예시 |
|
|
||||||
|------|------|------|
|
|
||||||
| `feat` | 새 기능 | `feat(auth): Google OAuth 로그인 추가` |
|
|
||||||
| `fix` | 버그 수정 | `fix(map): 레이어 겹침 오류 수정` |
|
|
||||||
| `refactor` | 리팩토링 | `refactor(api): 중복 호출 제거` |
|
|
||||||
| `docs` | 문서 | `docs: API 엔드포인트 문서 추가` |
|
|
||||||
| `chore` | 설정/빌드 | `chore: 의존성 버전 업데이트` |
|
|
||||||
| `ci` | CI/CD | `ci: 백엔드 빌드 스텝 추가` |
|
|
||||||
| `style` | 포맷팅 | `style: ESLint 경고 수정` |
|
|
||||||
|
|
||||||
### pre-commit 자동 검증
|
|
||||||
|
|
||||||
커밋 시 `.githooks/pre-commit`이 자동 실행됩니다:
|
|
||||||
1. Frontend TypeScript 타입 체크
|
|
||||||
2. Frontend ESLint 검증
|
|
||||||
3. Backend TypeScript 타입 체크
|
|
||||||
|
|
||||||
**하나라도 실패하면 커밋이 차단됩니다.**
|
|
||||||
|
|
||||||
### 푸시
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git push origin feature/user-profile
|
|
||||||
```
|
|
||||||
|
|
||||||
### Claude 스킬 활용
|
|
||||||
|
|
||||||
```
|
|
||||||
/push # 변경사항 확인 → 커밋 → 푸시 (한 번에)
|
|
||||||
/mr # 커밋 → 푸시 → MR 생성 (한 번에)
|
|
||||||
/mr main # 커밋 → 푸시 → main으로 MR 생성
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. MR 생성 (feature → develop)
|
|
||||||
|
|
||||||
### Gitea에서 MR 생성
|
|
||||||
|
|
||||||
1. https://gitea.gc-si.dev/gc/wing-ops/compare/develop...feature/user-profile
|
|
||||||
2. 제목: Conventional Commits 형식
|
|
||||||
3. 본문: 변경 내용 요약 + 테스트 계획
|
|
||||||
|
|
||||||
### Claude로 MR 생성
|
|
||||||
|
|
||||||
```
|
|
||||||
/create-mr develop # feature → develop MR 자동 생성
|
|
||||||
```
|
|
||||||
|
|
||||||
### MR 본문 템플릿
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Summary
|
|
||||||
- 사용자 프로필 페이지 추가
|
|
||||||
- 프로필 수정 API 연동
|
|
||||||
|
|
||||||
## 변경 파일
|
|
||||||
- frontend/src/components/views/ProfileView.tsx (신규)
|
|
||||||
- backend/src/users/userRouter.ts (수정)
|
|
||||||
|
|
||||||
## Test plan
|
|
||||||
- [ ] 프로필 페이지 접근 확인
|
|
||||||
- [ ] 프로필 수정 후 저장 확인
|
|
||||||
```
|
|
||||||
|
|
||||||
### 머지 후
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 로컬 develop 동기화
|
|
||||||
git checkout develop
|
|
||||||
git pull origin develop
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 릴리즈 PR (develop → main)
|
|
||||||
|
|
||||||
develop에 기능이 머지된 후, 배포를 위해 main으로 릴리즈 MR을 생성합니다.
|
|
||||||
|
|
||||||
### Claude로 릴리즈 MR 생성
|
|
||||||
|
|
||||||
```
|
|
||||||
/release # develop → main 릴리즈 MR 자동 생성
|
|
||||||
```
|
|
||||||
|
|
||||||
### 릴리즈 MR 체크리스트
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Release v2.x.x
|
|
||||||
|
|
||||||
### 포함 기능
|
|
||||||
1. feat(auth): Google OAuth 로그인
|
|
||||||
2. fix(map): 레이어 오류 수정
|
|
||||||
|
|
||||||
### 배포 전 확인
|
|
||||||
- [ ] 로컬 빌드 성공 (frontend + backend)
|
|
||||||
- [ ] 서버 환경변수 설정 완료
|
|
||||||
- [ ] DB 마이그레이션 적용 (필요 시)
|
|
||||||
```
|
|
||||||
|
|
||||||
### main 머지 → 자동 배포 트리거
|
|
||||||
|
|
||||||
main에 머지되면 `.gitea/workflows/deploy.yml`이 자동 실행됩니다.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 자동 배포
|
|
||||||
|
|
||||||
### CI/CD 파이프라인 (Gitea Actions)
|
|
||||||
|
|
||||||
```
|
|
||||||
main 브랜치 push
|
|
||||||
↓
|
|
||||||
[Frontend] npm ci → vite build → /deploy/wing-demo/
|
|
||||||
↓
|
|
||||||
[Backend] npm ci → tsc → /deploy/wing-demo-backend/
|
|
||||||
↓
|
|
||||||
[Server] .deploy-trigger 감지 → wing-demo-api 재시작
|
|
||||||
```
|
|
||||||
|
|
||||||
### 배포 환경
|
|
||||||
|
|
||||||
| 항목 | 값 |
|
|
||||||
|------|---|
|
|
||||||
| 프론트엔드 | https://wing-demo.gc-si.dev |
|
|
||||||
| 백엔드 API | https://wing-demo.gc-si.dev/api/ |
|
|
||||||
| 서버 | rocky-211 (Rocky Linux 9.6) |
|
|
||||||
| 프로세스 | systemd `wing-demo-api.service` |
|
|
||||||
|
|
||||||
### 배포 확인
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 프론트엔드 응답 확인
|
|
||||||
curl -s -o /dev/null -w '%{http_code}' https://wing-demo.gc-si.dev/
|
|
||||||
|
|
||||||
# 백엔드 API 확인
|
|
||||||
curl -s https://wing-demo.gc-si.dev/api/auth/me
|
|
||||||
```
|
|
||||||
|
|
||||||
### 환경변수 관리
|
|
||||||
|
|
||||||
| 위치 | 용도 |
|
|
||||||
|------|------|
|
|
||||||
| systemd 서비스 파일 | 서버 런타임 환경변수 (DB, JWT 등) |
|
|
||||||
| Gitea Secrets | CI/CD 빌드 시 환경변수 (API 키 등) |
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Gitea Secret 등록 (API)
|
|
||||||
curl -X PUT "https://gitea.gc-si.dev/api/v1/repos/gc/wing-ops/actions/secrets/KEY_NAME" \
|
|
||||||
-H "Authorization: token <token>" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{"data":"secret-value"}'
|
|
||||||
|
|
||||||
# Gitea Secret 등록 (Web UI)
|
|
||||||
# Settings → Actions → Secrets → Add Secret
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. 프로젝트 문서 최신화
|
|
||||||
|
|
||||||
### 자동 관리되는 문서
|
|
||||||
|
|
||||||
Claude 세션 중 커밋/컴팩트 시 hook이 자동으로 갱신을 안내합니다:
|
|
||||||
|
|
||||||
| 문서 | 위치 | 갱신 시점 |
|
|
||||||
|------|------|----------|
|
|
||||||
| MEMORY.md | `~/.claude/projects/.../memory/` | 매 세션 |
|
|
||||||
| project-snapshot.md | 위와 동일 | 구조 변경 시 |
|
|
||||||
| project-history.md | 위와 동일 | 매 커밋 |
|
|
||||||
| api-types.md | 위와 동일 | API 변경 시 |
|
|
||||||
| CHANGELOG.md | `docs/CHANGELOG.md` | 매 커밋 |
|
|
||||||
|
|
||||||
### 수동으로 최신화해야 하는 문서
|
|
||||||
|
|
||||||
| 문서 | 위치 | 갱신 주기 |
|
|
||||||
|------|------|----------|
|
|
||||||
| CLAUDE.md | 프로젝트 루트 | 기술 스택 변경 시 |
|
|
||||||
| INSTALL_GUIDE.md | `docs/` | 배포 환경 변경 시 |
|
|
||||||
| auth_init.sql | `database/` | DB 스키마 변경 시 |
|
|
||||||
|
|
||||||
### 주기적 최신화 체크리스트
|
|
||||||
|
|
||||||
**매 기능 개발 완료 시:**
|
|
||||||
```
|
|
||||||
Claude에게: "memory 파일 최신화해줘"
|
|
||||||
→ project-snapshot.md 갱신
|
|
||||||
→ api-types.md 갱신 (API 변경 시)
|
|
||||||
→ project-history.md에 변경 이력 추가
|
|
||||||
```
|
|
||||||
|
|
||||||
**매주 또는 스프린트 종료 시:**
|
|
||||||
```
|
|
||||||
Claude에게: "프로젝트 문서 전체 점검해줘"
|
|
||||||
→ CLAUDE.md 기술 스택 확인
|
|
||||||
→ CHANGELOG.md 누락 항목 보충
|
|
||||||
→ 의존성 버전 확인 (npm outdated)
|
|
||||||
```
|
|
||||||
|
|
||||||
**팀 워크플로우 업데이트 시:**
|
|
||||||
```
|
|
||||||
/sync-team-workflow # Gitea에서 최신 팀 규칙 동기화
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. 실전 예시: 기능 추가 A to Z
|
|
||||||
|
|
||||||
### 시나리오: "사고 상세 조회 페이지 추가"
|
|
||||||
|
|
||||||
#### Step 1. 계획
|
|
||||||
|
|
||||||
```
|
|
||||||
사용자: "사고 상세 조회 페이지를 추가해줘. 사고 목록에서 클릭하면 상세 정보를 보여주는 페이지야"
|
|
||||||
|
|
||||||
Claude: (Plan Mode 진입)
|
|
||||||
→ 코드베이스 분석
|
|
||||||
→ 구현 계획 제시:
|
|
||||||
1. backend/src/incidents/incidentService.ts - 상세 조회 함수
|
|
||||||
2. backend/src/incidents/incidentRouter.ts - GET /api/incidents/:id
|
|
||||||
3. frontend/src/services/incidentApi.ts - API 함수
|
|
||||||
4. frontend/src/components/views/IncidentDetailView.tsx - 상세 화면
|
|
||||||
5. frontend/src/App.tsx - 라우팅 연결
|
|
||||||
|
|
||||||
사용자: (계획 승인)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 2. 브랜치 생성
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout develop
|
|
||||||
git pull origin develop
|
|
||||||
git checkout -b feature/incident-detail
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 3. 개발
|
|
||||||
|
|
||||||
Claude가 계획에 따라 코드 작성 (5개 파일 수정/생성)
|
|
||||||
|
|
||||||
#### Step 4. 커밋 & 푸시
|
|
||||||
|
|
||||||
```
|
|
||||||
사용자: "커밋하고 푸시해줘"
|
|
||||||
|
|
||||||
Claude:
|
|
||||||
git add backend/src/incidents/... frontend/src/...
|
|
||||||
git commit -m "feat(incidents): 사고 상세 조회 페이지 추가"
|
|
||||||
# pre-commit: TypeScript ✓, ESLint ✓
|
|
||||||
git push origin feature/incident-detail
|
|
||||||
```
|
|
||||||
|
|
||||||
또는 스킬 사용:
|
|
||||||
|
|
||||||
```
|
|
||||||
/push feat(incidents): 사고 상세 조회 페이지 추가
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 5. develop MR
|
|
||||||
|
|
||||||
```
|
|
||||||
사용자: "develop MR 만들어줘"
|
|
||||||
|
|
||||||
Claude:
|
|
||||||
→ Gitea API로 MR 생성
|
|
||||||
→ feature/incident-detail → develop
|
|
||||||
→ MR #5: https://gitea.gc-si.dev/gc/wing-ops/pulls/5
|
|
||||||
```
|
|
||||||
|
|
||||||
또는:
|
|
||||||
```
|
|
||||||
/create-mr develop
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 6. 코드 리뷰 & 머지
|
|
||||||
|
|
||||||
- Gitea에서 MR 리뷰
|
|
||||||
- 승인 후 Squash Merge
|
|
||||||
|
|
||||||
#### Step 7. 릴리즈 PR
|
|
||||||
|
|
||||||
```
|
|
||||||
사용자: "main으로 릴리즈 MR 만들어줘"
|
|
||||||
|
|
||||||
Claude:
|
|
||||||
→ develop → main MR 생성
|
|
||||||
→ MR #6 (release)
|
|
||||||
```
|
|
||||||
|
|
||||||
또는:
|
|
||||||
```
|
|
||||||
/release
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 8. main 머지 → 자동 배포
|
|
||||||
|
|
||||||
- main에 머지 → Gitea Actions 실행
|
|
||||||
- Frontend 빌드 (Vite) → /deploy/wing-demo/
|
|
||||||
- Backend 빌드 (tsc) → /deploy/wing-demo-backend/
|
|
||||||
- .deploy-trigger → cron이 감지 → wing-demo-api 재시작
|
|
||||||
- https://wing-demo.gc-si.dev 에서 확인
|
|
||||||
|
|
||||||
#### Step 9. 문서 최신화
|
|
||||||
|
|
||||||
```
|
|
||||||
사용자: "memory 파일 최신화해줘"
|
|
||||||
|
|
||||||
Claude:
|
|
||||||
→ project-snapshot.md: incidents 모듈 추가 반영
|
|
||||||
→ api-types.md: GET /api/incidents/:id 추가
|
|
||||||
→ project-history.md: "사고 상세 조회 페이지 추가" 기록
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 부록: 자주 쓰는 Claude 명령
|
|
||||||
|
|
||||||
| 명령 | 설명 |
|
|
||||||
|------|------|
|
|
||||||
| `"커밋하고 푸시해줘"` | 변경사항 커밋 + 푸시 |
|
|
||||||
| `"develop MR 만들어줘"` | feature → develop MR |
|
|
||||||
| `"memory 최신화해줘"` | 프로젝트 문서 갱신 |
|
|
||||||
| `/push` | 커밋 + 푸시 (스킬) |
|
|
||||||
| `/mr` | 커밋 + 푸시 + MR (스킬) |
|
|
||||||
| `/release` | develop → main 릴리즈 MR (스킬) |
|
|
||||||
| `/create-mr develop` | MR만 생성 (스킬) |
|
|
||||||
| `/sync-team-workflow` | 팀 워크플로우 동기화 (스킬) |
|
|
||||||
| `/changelog` | CHANGELOG.md 갱신 (스킬) |
|
|
||||||
@ -1,165 +0,0 @@
|
|||||||
# WING 해양환경 위기대응 통합시스템 - 설치 매뉴얼
|
|
||||||
|
|
||||||
## 1. 필수 소프트웨어
|
|
||||||
|
|
||||||
| 소프트웨어 | 최소 버전 | 용도 | 다운로드 |
|
|
||||||
|-----------|----------|------|---------|
|
|
||||||
| Node.js | v20 이상 (권장 v25) | 프론트엔드/백엔드 실행 | https://nodejs.org |
|
|
||||||
| npm | v10 이상 | 패키지 관리 | Node.js에 포함 |
|
|
||||||
|
|
||||||
> **오프라인 환경**: 인터넷이 안 되는 망에서는 `node_modules`가 포함된 압축 파일을 사용하세요 (아래 "오프라인 설치" 참고).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 프로젝트 구조
|
|
||||||
|
|
||||||
```
|
|
||||||
wing/
|
|
||||||
├── frontend/ # React + Vite 프론트엔드 (포트 5173)
|
|
||||||
│ ├── src/
|
|
||||||
│ │ ├── components/ # UI 컴포넌트
|
|
||||||
│ │ ├── data/ # 정적 데이터
|
|
||||||
│ │ ├── hooks/ # 커스텀 훅
|
|
||||||
│ │ ├── types/ # TypeScript 타입 정의
|
|
||||||
│ │ ├── utils/ # 유틸리티 함수
|
|
||||||
│ │ └── store/ # 상태관리
|
|
||||||
│ └── package.json
|
|
||||||
├── backend/ # Express 백엔드 API (포트 3001)
|
|
||||||
│ ├── src/
|
|
||||||
│ │ ├── routes/ # API 라우트
|
|
||||||
│ │ ├── middleware/ # 미들웨어 (보안 등)
|
|
||||||
│ │ ├── db/ # DB 연결
|
|
||||||
│ │ └── server.ts # 서버 엔트리
|
|
||||||
│ └── package.json
|
|
||||||
└── database/ # DB 초기화 SQL
|
|
||||||
├── database_init.sql
|
|
||||||
└── auth_init.sql
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 온라인 설치 (인터넷 가능한 환경)
|
|
||||||
|
|
||||||
### 3-1. 의존성 설치
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 프론트엔드
|
|
||||||
cd wing/frontend
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# 백엔드
|
|
||||||
cd ../backend
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3-2. 데이터베이스 설정
|
|
||||||
|
|
||||||
운영 PostgreSQL에 직접 연결합니다. `backend/.env` 파일에서 DB 연결 정보를 설정하세요.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# backend/.env
|
|
||||||
AUTH_DB_HOST=<PostgreSQL 호스트>
|
|
||||||
AUTH_DB_PORT=5432
|
|
||||||
AUTH_DB_NAME=wing_auth
|
|
||||||
AUTH_DB_USER=wing_auth
|
|
||||||
AUTH_DB_PASSWORD=<비밀번호>
|
|
||||||
```
|
|
||||||
|
|
||||||
> 신규 DB 초기화가 필요한 경우 `database/auth_init.sql`을 실행하세요.
|
|
||||||
|
|
||||||
### 3-3. 백엔드 실행
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd wing/backend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
→ `http://localhost:3001` 에서 API 서버 시작
|
|
||||||
|
|
||||||
### 3-4. 프론트엔드 실행
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd wing/frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
→ `http://localhost:5173` 에서 웹 앱 시작
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 오프라인 설치 (폐쇄망/다른 망)
|
|
||||||
|
|
||||||
인터넷이 안 되는 환경에서는 `npm install`이 불가능합니다.
|
|
||||||
이 경우 **node_modules 포함 압축 파일**을 사용하세요.
|
|
||||||
|
|
||||||
### 4-1. 압축 해제
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# wing_full.tar.gz 파일을 작업 폴더에 복사한 뒤:
|
|
||||||
tar -xzf wing_full.tar.gz
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4-2. Node.js 설치
|
|
||||||
|
|
||||||
대상 PC에 Node.js가 없으면 오프라인 설치 파일(.msi 또는 .pkg)을 미리 준비하여 설치합니다.
|
|
||||||
|
|
||||||
- Windows: `node-v25.x.x-x64.msi`
|
|
||||||
- macOS: `node-v25.x.x.pkg`
|
|
||||||
|
|
||||||
### 4-3. DB 연결 설정
|
|
||||||
|
|
||||||
`backend/.env` 파일에서 연결 가능한 PostgreSQL 정보를 설정합니다.
|
|
||||||
|
|
||||||
### 4-4. 실행
|
|
||||||
|
|
||||||
node_modules가 이미 포함되어 있으므로 바로 실행 가능합니다.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 터미널 1 - 백엔드
|
|
||||||
cd wing/backend
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
# 터미널 2 - 프론트엔드
|
|
||||||
cd wing/frontend
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 접속 정보 요약
|
|
||||||
|
|
||||||
| 서비스 | URL | 비고 |
|
|
||||||
|--------|-----|------|
|
|
||||||
| 프론트엔드 (WING) | http://localhost:5173 | Vite dev server |
|
|
||||||
| 백엔드 API | http://localhost:3001 | Express |
|
|
||||||
| PostgreSQL | 운영 DB 직접 연결 | `.env` 설정 참조 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. 주요 명령어
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 프론트엔드 빌드 (배포용)
|
|
||||||
cd frontend && npm run build # dist/ 폴더에 정적 파일 생성
|
|
||||||
|
|
||||||
# 백엔드 빌드
|
|
||||||
cd backend && npm run build # dist/ 폴더에 JS 생성
|
|
||||||
|
|
||||||
# DB 시드 데이터 입력
|
|
||||||
cd backend && npm run db:seed
|
|
||||||
|
|
||||||
# TypeScript 타입 체크
|
|
||||||
cd frontend && npx tsc --noEmit
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. 트러블슈팅
|
|
||||||
|
|
||||||
| 증상 | 해결 |
|
|
||||||
|------|------|
|
|
||||||
| `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 포함 압축본 사용 |
|
|
||||||
@ -1,194 +0,0 @@
|
|||||||
# WING 메뉴 탭 추가 가이드
|
|
||||||
|
|
||||||
새로운 메뉴 탭을 추가할 때 필요한 절차를 설명합니다.
|
|
||||||
|
|
||||||
## 메뉴 시스템 구조
|
|
||||||
|
|
||||||
```
|
|
||||||
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**가 메뉴 정의의 단일 소스 (id, label, icon, enabled, order)
|
|
||||||
- **TopBar**는 `enabled && hasPermission` 조건으로 탭을 필터링하고 `order` 순 정렬
|
|
||||||
- **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/` 에 새 뷰 컴포넌트를 생성합니다.
|
|
||||||
|
|
||||||
```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>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`index.ts`에서 re-export합니다:
|
|
||||||
```tsx
|
|
||||||
// frontend/src/tabs/monitoring/index.ts
|
|
||||||
export { MonitoringView } from './components/MonitoringView'
|
|
||||||
```
|
|
||||||
|
|
||||||
기존 탭(`@tabs/prediction`, `@tabs/weather` 등)의 레이아웃 패턴을 참고하세요.
|
|
||||||
공통 모듈은 `@common/` alias로 import합니다.
|
|
||||||
|
|
||||||
## Step 2: App.tsx 탭 등록
|
|
||||||
|
|
||||||
3가지를 수정합니다.
|
|
||||||
|
|
||||||
### 2-1. MainTab 타입에 ID 추가
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// frontend/src/App.tsx (line 20)
|
|
||||||
|
|
||||||
// Before
|
|
||||||
export type MainTab = 'prediction' | 'hns' | ... | 'admin'
|
|
||||||
|
|
||||||
// After
|
|
||||||
export type MainTab = 'prediction' | 'hns' | ... | 'monitoring' | 'admin'
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2-2. 뷰 컴포넌트 import
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { MonitoringView } from '@tabs/monitoring'
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2-3. renderView switch에 case 추가
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const renderView = () => {
|
|
||||||
switch (activeMainTab) {
|
|
||||||
// ... 기존 case들 ...
|
|
||||||
case 'monitoring':
|
|
||||||
return <MonitoringView />
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Step 3: 백엔드 메뉴 설정 등록
|
|
||||||
|
|
||||||
`backend/src/settings/settingsService.ts`의 `DEFAULT_MENU_CONFIG` 배열에 항목을 추가합니다.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const DEFAULT_MENU_CONFIG: MenuConfigItem[] = [
|
|
||||||
// ... 기존 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에 새 항목을 추가합니다.
|
|
||||||
|
|
||||||
```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;
|
|
||||||
```
|
|
||||||
|
|
||||||
> **참고**: 이 SQL은 신규 설치 시에만 적용됩니다. 기존 운영 DB는 관리자 UI에서 메뉴를 관리합니다.
|
|
||||||
|
|
||||||
## Step 5: 관리자 메뉴 관리에서 활성화
|
|
||||||
|
|
||||||
코드 배포 후:
|
|
||||||
1. 관리자 계정으로 로그인
|
|
||||||
2. 관리자 패널(⚙️) → 메뉴 관리 탭
|
|
||||||
3. 새 메뉴가 목록에 표시됨
|
|
||||||
4. 활성/비활성 토글, 순서, 라벨, 아이콘을 설정
|
|
||||||
5. "변경사항 저장" 클릭
|
|
||||||
|
|
||||||
> 기존 DB에 새 메뉴 ID가 없으면 `getMenuConfig()`가 DEFAULT_MENU_CONFIG fallback을 사용하여 새 메뉴가 자동으로 목록에 나타납니다.
|
|
||||||
|
|
||||||
## 실전 예시: "모니터링" 탭 추가
|
|
||||||
|
|
||||||
### 1. 뷰 컴포넌트 생성
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# frontend/src/components/views/MonitoringView.tsx 파일 생성
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. App.tsx 수정 (3곳)
|
|
||||||
|
|
||||||
```diff
|
|
||||||
+ import { MonitoringView } from './components/views/MonitoringView'
|
|
||||||
|
|
||||||
- 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) {
|
|
||||||
// ...
|
|
||||||
+ case 'monitoring':
|
|
||||||
+ return <MonitoringView />
|
|
||||||
case 'admin':
|
|
||||||
return <AdminView />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. settingsService.ts 수정
|
|
||||||
|
|
||||||
```diff
|
|
||||||
const DEFAULT_MENU_CONFIG: MenuConfigItem[] = [
|
|
||||||
// ... 기존 메뉴들 ...
|
|
||||||
{ id: 'incidents', label: '통합조회', icon: '🔍', enabled: true, order: 10 },
|
|
||||||
+ { id: 'monitoring', label: '실시간 모니터링', icon: '📡', enabled: true, order: 11 },
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. auth_init.sql 수정
|
|
||||||
|
|
||||||
menu.config JSON에 새 항목 추가 (신규 설치용)
|
|
||||||
|
|
||||||
### 5. 배포 후 관리자 UI에서 활성화
|
|
||||||
|
|
||||||
## 체크리스트
|
|
||||||
|
|
||||||
- [ ] 뷰 컴포넌트 생성 (`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 .`)
|
|
||||||
- [ ] 관리자 메뉴 관리에서 새 메뉴 표시 확인
|
|
||||||
@ -1,435 +0,0 @@
|
|||||||
# Mock → API 전환 개발 지침
|
|
||||||
|
|
||||||
이 문서는 각 탭의 mock 데이터를 PostgreSQL DB + REST API 기반으로 전환할 때 따라야 할 표준 프로세스를 정의한다.
|
|
||||||
CRUD API 작성 규칙은 [CRUD-API-GUIDE.md](./CRUD-API-GUIDE.md) 참조.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 전환 프로세스 (탭당 반복)
|
|
||||||
|
|
||||||
### Step A. 브랜치 생성
|
|
||||||
|
|
||||||
`feature/{탭명}-crud` 형식으로 develop에서 분기한다.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout develop
|
|
||||||
git pull
|
|
||||||
git checkout -b feature/{탭명}-crud
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step B. Mock 전수 조사 (필수!)
|
|
||||||
|
|
||||||
탭 디렉토리 전체에서 mock/하드코딩 데이터를 빠짐없이 검색한다.
|
|
||||||
|
|
||||||
**검색 키워드**: `mock`, `Mock`, `MOCK`, `sample`, `initial`, `hardcod`, `localStorage`, 인라인 배열 상수
|
|
||||||
|
|
||||||
```bash
|
|
||||||
grep -rn "mock\|Mock\|MOCK\|sample\|initial\|hardcod\|localStorage" frontend/src/tabs/{탭}/
|
|
||||||
```
|
|
||||||
|
|
||||||
**체크리스트 형식**으로 정리한다:
|
|
||||||
|
|
||||||
```
|
|
||||||
□ 파일명:라인 — 변수명 (N건) — 전환방법
|
|
||||||
□ components/AssetList.tsx:12 — MOCK_ASSETS (30건) — DB 이전
|
|
||||||
□ services/assetService.ts:5 — INITIAL_FILTER — 프론트 상수 유지
|
|
||||||
□ hooks/useAsset.ts:88 — localStorage.getItem('draft') — DB 이전
|
|
||||||
```
|
|
||||||
|
|
||||||
board 전환 시 mock 참조 누락으로 런타임 에러가 발생한 경험이 있다. 전수 조사를 건너뛰지 말 것.
|
|
||||||
|
|
||||||
### Step C. 프론트 상수 vs DB 데이터 판단
|
|
||||||
|
|
||||||
조사한 mock/하드코딩 데이터를 아래 기준으로 분류한다.
|
|
||||||
|
|
||||||
| 분류 | 유지/이전 | 예시 |
|
|
||||||
|------|-----------|------|
|
|
||||||
| UI 전용 색상 매핑 | 프론트 상수 유지 | 상태별 뱃지 색, 심각도 색상 |
|
|
||||||
| 레이아웃/뷰 설정 | 프론트 상수 유지 | 기본 페이지 크기, 컬럼 너비 |
|
|
||||||
| 비즈니스 목록 데이터 | DB 이전 | 자산 목록, 사고 목록, 보고서 |
|
|
||||||
| 검색/필터 대상 데이터 | DB 이전 | 카테고리, 기관명, 상태값 |
|
|
||||||
| 유형/카테고리 코드 | DB 이전 또는 CHECK 제약 | 자산유형, 오염물질유형 |
|
|
||||||
|
|
||||||
### Step D. DB 스키마 설계 + 마이그레이션
|
|
||||||
|
|
||||||
DDL 규칙은 [CRUD-API-GUIDE.md](./CRUD-API-GUIDE.md) (4. DB 설계 규칙) 참조.
|
|
||||||
|
|
||||||
1. 기존 테이블 활용 가능 여부 확인 (예: ACDNT, LAYER 등)
|
|
||||||
2. `database/migration/NNN_{탭명}.sql` 파일 작성 (번호는 기존 파일 다음 순번)
|
|
||||||
3. 초기 데이터 INSERT (mock 데이터를 SQL로 변환)
|
|
||||||
4. psql로 원격 DB에 직접 실행
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 원격 wing DB에 마이그레이션 실행
|
|
||||||
PGPASSWORD=Wing2026 psql -h 211.208.115.83 -U wing -d wing \
|
|
||||||
-f database/migration/NNN_{탭명}.sql
|
|
||||||
|
|
||||||
# 실행 결과 검증 (마이그레이션 파일 끝의 SELECT 확인)
|
|
||||||
```
|
|
||||||
|
|
||||||
마이그레이션 파일 규칙:
|
|
||||||
- 모든 DDL에 `IF NOT EXISTS` / `IF EXISTS` 사용 (재실행 안전)
|
|
||||||
- 파일 끝에 검증 SELECT 포함
|
|
||||||
|
|
||||||
### Step E. 백엔드 Service + Router 구현
|
|
||||||
|
|
||||||
Service/Router 패턴은 [CRUD-API-GUIDE.md](./CRUD-API-GUIDE.md) (5. Service 레이어 패턴, 6. Router 레이어 패턴) 참조.
|
|
||||||
|
|
||||||
**HTTP 메소드 규칙** (보안취약점 가이드 준수):
|
|
||||||
|
|
||||||
| 메소드 | 용도 |
|
|
||||||
|--------|------|
|
|
||||||
| GET | 단순 조회 (민감하지 않은 경우) |
|
|
||||||
| POST | 생성/수정/삭제 및 복잡한 조회 파라미터 |
|
|
||||||
|
|
||||||
PUT, DELETE, PATCH는 사용하지 않는다. 자세한 내용은 [2. HTTP 메소드 정책](#2-http-메소드-정책-필독) 참조.
|
|
||||||
|
|
||||||
**URL 패턴**:
|
|
||||||
|
|
||||||
| URL | 설명 |
|
|
||||||
|-----|------|
|
|
||||||
| `GET /api/{domain}` | 목록 (간단한 파라미터) |
|
|
||||||
| `GET /api/{domain}/:sn` | 상세 |
|
|
||||||
| `POST /api/{domain}/list` | 목록 (복잡한 필터 파라미터) |
|
|
||||||
| `POST /api/{domain}/detail` | 상세 |
|
|
||||||
| `POST /api/{domain}/create` | 생성 |
|
|
||||||
| `POST /api/{domain}/update` | 수정 |
|
|
||||||
| `POST /api/{domain}/delete` | 삭제 |
|
|
||||||
| `GET /api/{domain}/templates` | 메타데이터/코드 조회 |
|
|
||||||
|
|
||||||
**인증 패턴**:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 현재 로그인 사용자 UUID 추출
|
|
||||||
const userId = req.user!.sub // JWT payload의 사용자 UUID
|
|
||||||
|
|
||||||
// ❌ 절대 사용 금지 (reports 전환 시 실제 발생한 버그)
|
|
||||||
const user = (req as unknown as { user: { id: string } }).user
|
|
||||||
const userId = user.id // undefined → DB NOT NULL 제약 위반
|
|
||||||
```
|
|
||||||
|
|
||||||
구현 후 `backend/src/server.ts`에 라우터를 등록한다.
|
|
||||||
|
|
||||||
### Step F. 프론트엔드 API 서비스 + 컴포넌트 전환
|
|
||||||
|
|
||||||
1. `frontend/src/tabs/{탭}/services/{탭}Api.ts` 생성
|
|
||||||
2. API 응답 타입 (`interface Api{탭명}Item` 등) 정의
|
|
||||||
3. API ↔ 프론트 모델 변환 함수 작성 (필요 시)
|
|
||||||
4. 정적 마스터 데이터 캐싱: 모듈 변수 또는 TanStack Query `staleTime: Infinity`
|
|
||||||
5. 컴포넌트에서 mock import → API 호출로 교체
|
|
||||||
6. `api.post()` 사용 (`api.put()`, `api.delete()` 사용 금지)
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// frontend/src/tabs/{탭}/services/{탭}Api.ts
|
|
||||||
import { api } from '@common/services/api'
|
|
||||||
|
|
||||||
export interface Api{탭명}Item {
|
|
||||||
sn: number
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetch{탭명}List(params: {
|
|
||||||
search?: string
|
|
||||||
page?: number
|
|
||||||
size?: number
|
|
||||||
}): Promise<{ items: Api{탭명}Item[]; totalCount: number }> {
|
|
||||||
const response = await api.post('/{ 탭명}/list', params)
|
|
||||||
return response.data
|
|
||||||
}
|
|
||||||
|
|
||||||
// 수정 — POST /update 사용
|
|
||||||
export async function update{탭명}(sn: number, data: Update{탭명}Input): Promise<void> {
|
|
||||||
await api.post('/{탭명}/update', { sn, ...data })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 삭제 — POST /delete 사용
|
|
||||||
export async function delete{탭명}(sn: number): Promise<void> {
|
|
||||||
await api.post('/{탭명}/delete', { sn })
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step G. 빌드 검증
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 백엔드 TypeScript 컴파일
|
|
||||||
cd backend && npm run build
|
|
||||||
|
|
||||||
# 프론트엔드 타입 체크 + ESLint
|
|
||||||
cd frontend && npx tsc --noEmit && npx eslint .
|
|
||||||
```
|
|
||||||
|
|
||||||
빌드/린트 에러가 0건이어야 다음 단계로 진행한다.
|
|
||||||
|
|
||||||
### Step H. 로컬 API 동작 테스트
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 백엔드 개발 서버 시작
|
|
||||||
cd backend && npm run dev
|
|
||||||
|
|
||||||
# 로그인 — 쿠키 파일 획득
|
|
||||||
curl -s -c /tmp/wing.cookie -X POST http://localhost:3001/api/auth/login \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d '{"account":"admin","password":"admin1234"}' | jq .
|
|
||||||
|
|
||||||
# 목록 조회
|
|
||||||
curl -s -b /tmp/wing.cookie -X POST http://localhost:3001/api/{탭명}/list \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d '{"page":1,"size":10}' | jq .
|
|
||||||
|
|
||||||
# 생성
|
|
||||||
curl -s -b /tmp/wing.cookie -X POST http://localhost:3001/api/{탭명}/create \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d '{...}' | jq .
|
|
||||||
|
|
||||||
# 수정
|
|
||||||
curl -s -b /tmp/wing.cookie -X POST http://localhost:3001/api/{탭명}/update \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d '{"sn": 1, ...}' | jq .
|
|
||||||
|
|
||||||
# 삭제
|
|
||||||
curl -s -b /tmp/wing.cookie -X POST http://localhost:3001/api/{탭명}/delete \
|
|
||||||
-H 'Content-Type: application/json' \
|
|
||||||
-d '{"sn": 1}' | jq .
|
|
||||||
```
|
|
||||||
|
|
||||||
CRUD 전체 흐름(생성 → 조회 → 수정 → 삭제 → 필터)을 확인하고 테스트 데이터를 정리한다.
|
|
||||||
|
|
||||||
### Step I. Mock 잔여 확인
|
|
||||||
|
|
||||||
```bash
|
|
||||||
grep -rn "mock\|Mock\|MOCK\|localStorage" frontend/src/tabs/{탭}/
|
|
||||||
# → UI 상수(색상, 레이아웃) 외 결과 0건이어야 함
|
|
||||||
```
|
|
||||||
|
|
||||||
잔여가 있으면 Step F로 돌아가 처리한다.
|
|
||||||
|
|
||||||
### Step J. 커밋 + 푸시 + MR
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 커밋 (Conventional Commits 형식, 한국어)
|
|
||||||
git add -p
|
|
||||||
git commit -m "feat({탭명}): mock 데이터 DB + REST API 전환"
|
|
||||||
|
|
||||||
# 푸시
|
|
||||||
git push -u origin feature/{탭명}-crud
|
|
||||||
```
|
|
||||||
|
|
||||||
`feature/{탭명}-crud` → `develop` MR을 Gitea에서 생성한다.
|
|
||||||
`/push` 또는 `/mr` 스킬 활용 가능.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. HTTP 메소드 정책 (필독)
|
|
||||||
|
|
||||||
한국 보안취약점 점검 가이드에 따라 GET/POST만 사용한다.
|
|
||||||
|
|
||||||
### 허용
|
|
||||||
|
|
||||||
| 메소드 | 용도 | 예시 |
|
|
||||||
|--------|------|------|
|
|
||||||
| GET | 단순 조회 (파라미터가 단순하고 민감하지 않은 경우) | `GET /api/reports`, `GET /api/reports/:sn` |
|
|
||||||
| POST | 생성/수정/삭제, 복잡한 필터 파라미터 조회 | `POST /api/reports/create`, `POST /api/reports/list` |
|
|
||||||
|
|
||||||
### 금지
|
|
||||||
|
|
||||||
| 메소드 | 이유 |
|
|
||||||
|--------|------|
|
|
||||||
| PUT | 보안취약점 가이드 위반 |
|
|
||||||
| DELETE | 보안취약점 가이드 위반 |
|
|
||||||
| PATCH | 보안취약점 가이드 위반 |
|
|
||||||
|
|
||||||
### 기존 API 현황
|
|
||||||
|
|
||||||
`boardRouter`, `userRouter`, `roleRouter` 등은 아직 PUT/DELETE를 사용 중이다.
|
|
||||||
별도 세션에서 POST 패턴으로 마이그레이션 예정.
|
|
||||||
**신규 탭 전환 시 반드시 POST 패턴을 적용한다.**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 전환 시 주의사항 (실전 교훈)
|
|
||||||
|
|
||||||
### 3.1 req.user 접근 패턴
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 올바른 패턴
|
|
||||||
const userId = req.user!.sub // JWT payload의 사용자 UUID
|
|
||||||
|
|
||||||
// 잘못된 패턴 (런타임 에러 발생)
|
|
||||||
const user = (req as unknown as { user: { id: string } }).user
|
|
||||||
const userId = user.id // undefined → DB NOT NULL 제약 위반
|
|
||||||
```
|
|
||||||
|
|
||||||
Reports 전환 시 실제 발생한 버그. `boardRouter.ts`의 패턴을 확인하고 `req.user!.sub`을 사용한다.
|
|
||||||
|
|
||||||
JWT 페이로드 전체 구조:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface JwtPayload {
|
|
||||||
sub: string // 사용자 UUID (USER_ID)
|
|
||||||
acnt: string // 계정명 (USER_ACNT)
|
|
||||||
name: string // 사용자명 (USER_NM)
|
|
||||||
roles: string[] // 역할 코드 목록
|
|
||||||
}
|
|
||||||
|
|
||||||
// 사용 예시
|
|
||||||
const userId = req.user!.sub // UUID
|
|
||||||
const userName = req.user!.name // 이름
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 AUTH_USER 테이블 컬럼명
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- 올바른 컬럼명
|
|
||||||
SELECT u.USER_NM as author_name FROM AUTH_USER u
|
|
||||||
|
|
||||||
-- 잘못된 컬럼명 (500 에러 발생)
|
|
||||||
SELECT u.NM as author_name FROM AUTH_USER u
|
|
||||||
```
|
|
||||||
|
|
||||||
Reports 전환 시 실제 발생한 버그. 반드시 `USER_NM`을 사용한다.
|
|
||||||
|
|
||||||
`AUTH_USER` 주요 컬럼 참조:
|
|
||||||
|
|
||||||
| 컬럼 | 타입 | 설명 |
|
|
||||||
|------|------|------|
|
|
||||||
| `USER_ID` | UUID PK | 사용자 UUID (`req.user!.sub`과 일치) |
|
|
||||||
| `USER_ACNT` | VARCHAR | 계정명 (`req.user!.acnt`와 일치) |
|
|
||||||
| `USER_NM` | VARCHAR | 사용자명 (`req.user!.name`와 일치) |
|
|
||||||
| `EMAIL` | VARCHAR | 이메일 |
|
|
||||||
|
|
||||||
### 3.3 Mock 전수 조사 누락
|
|
||||||
|
|
||||||
Board 전환 시 일부 mock 참조를 놓쳐 런타임 에러가 발생했다.
|
|
||||||
[Step B](#step-b-mock-전수-조사-필수)의 전수 조사를 건너뛰지 말 것.
|
|
||||||
|
|
||||||
특히 다음 위치를 반드시 확인한다:
|
|
||||||
|
|
||||||
- 컴포넌트 파일 내 인라인 배열 (`const ITEMS = [{ id: 1, ... }]`)
|
|
||||||
- 커스텀 훅 초기값 (`useState([{ ... }])`)
|
|
||||||
- `localStorage.getItem` / `localStorage.setItem` 호출
|
|
||||||
- 서비스 파일 내 하드코딩 반환값
|
|
||||||
|
|
||||||
### 3.4 프론트 api.put() / api.delete() 금지
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 올바른 POST 패턴
|
|
||||||
await api.post(`/reports/update`, { sn, ...input })
|
|
||||||
await api.post(`/reports/delete`, { sn })
|
|
||||||
|
|
||||||
// 금지 — PUT/DELETE 사용 불가
|
|
||||||
await api.put(`/reports/${sn}`, input)
|
|
||||||
await api.delete(`/reports/${sn}`)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.5 트랜잭션 사용 시점
|
|
||||||
|
|
||||||
- 단일 테이블 INSERT/UPDATE: 트랜잭션 불필요
|
|
||||||
- 다중 테이블 동시 변경 (예: 헤더 + 섹션, 보고서 + 첨부파일): 반드시 트랜잭션 사용
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const client = await wingPool.connect()
|
|
||||||
try {
|
|
||||||
await client.query('BEGIN')
|
|
||||||
|
|
||||||
// 헤더 INSERT
|
|
||||||
const headerResult = await client.query(
|
|
||||||
'INSERT INTO REPORT_HDR (...) VALUES ($1, $2) RETURNING HDR_SN',
|
|
||||||
[...]
|
|
||||||
)
|
|
||||||
const hdrSn = headerResult.rows[0].hdr_sn
|
|
||||||
|
|
||||||
// 섹션 INSERT (헤더 FK 참조)
|
|
||||||
for (const section of sections) {
|
|
||||||
await client.query(
|
|
||||||
'INSERT INTO REPORT_SECT (HDR_SN, ...) VALUES ($1, ...)',
|
|
||||||
[hdrSn, ...]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
await client.query('COMMIT')
|
|
||||||
return { hdrSn }
|
|
||||||
} catch (err) {
|
|
||||||
await client.query('ROLLBACK')
|
|
||||||
throw err
|
|
||||||
} finally {
|
|
||||||
client.release()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.6 에러 처리 일관성
|
|
||||||
|
|
||||||
모든 라우트 핸들러에서 동일한 에러 처리 구조를 사용한다.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
try {
|
|
||||||
// 비즈니스 로직
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof AuthError) {
|
|
||||||
res.status(err.status).json({ error: err.message })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.error('[{탭명}] 작업 오류:', err)
|
|
||||||
res.status(500).json({ error: '처리 중 오류가 발생했습니다.' })
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Board의 GET 목록 라우트에서 `AuthError` 분기 누락 이슈가 있었다.
|
|
||||||
목록 조회처럼 평범해 보이는 라우트도 예외 없이 동일한 구조를 사용한다.
|
|
||||||
|
|
||||||
### 3.7 정적 마스터 데이터 캐싱
|
|
||||||
|
|
||||||
코드 목록, 기관 목록 등 변경이 드문 마스터 데이터는 매 호출마다 DB 조회하지 않는다.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 방법 1: 모듈 변수 캐싱 (서버 재시작 시까지 유지)
|
|
||||||
let cachedOrgList: OrgItem[] | null = null
|
|
||||||
|
|
||||||
export async function getOrgList(): Promise<OrgItem[]> {
|
|
||||||
if (cachedOrgList) return cachedOrgList
|
|
||||||
const result = await wingPool.query('SELECT * FROM ORG WHERE USE_YN = $1', ['Y'])
|
|
||||||
cachedOrgList = result.rows.map(mapOrg)
|
|
||||||
return cachedOrgList
|
|
||||||
}
|
|
||||||
|
|
||||||
// 방법 2: TanStack Query staleTime 설정 (프론트엔드)
|
|
||||||
const { data: orgList } = useQuery({
|
|
||||||
queryKey: ['orgList'],
|
|
||||||
queryFn: fetchOrgList,
|
|
||||||
staleTime: 1000 * 60 * 10, // 10분간 리패치 없음
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 탭별 전환 우선순위
|
|
||||||
|
|
||||||
| # | 탭 | 난이도 | 상태 | 비고 |
|
|
||||||
|---|---|--------|------|------|
|
|
||||||
| 1 | Reports (보고서) | ★★★ | 완료 | 7개 DB 테이블, 섹션 단위 JSONB |
|
|
||||||
| 2 | Assets (방제자산) | ★★☆ | 대기 | mock 1파일 집중, ORG 테이블 활용 |
|
|
||||||
| 3 | Incidents (사고관리) | ★★★ | 대기 | mock 5파일 분산, ACDNT 테이블 존재 |
|
|
||||||
| 4 | SCAT (해안조사) | ★★★★ | 대기 | 1,084개 세그먼트, 스키마 격차 |
|
|
||||||
| 5 | Rescue (구조시나리오) | ★★★★ | 대기 | DB 미정의, 시뮬레이션 복잡 |
|
|
||||||
| 6 | Prediction (확산예측) | ★★★★★ | 대기 | 시뮬레이션 엔진 의존, 부분 API 연동 |
|
|
||||||
|
|
||||||
제외: Weather (KHOA API 연동 완료), HNS (API 연동 완료), Board (API 연동 완료), Aerial (스켈레톤 수준)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 완료 검증 체크리스트 (탭당)
|
|
||||||
|
|
||||||
- [ ] 백엔드 빌드 통과 (`cd backend && npm run build`)
|
|
||||||
- [ ] 프론트 타입 체크 통과 (`cd frontend && npx tsc --noEmit`)
|
|
||||||
- [ ] 프론트 ESLint 통과 (`cd frontend && npx eslint .`)
|
|
||||||
- [ ] API CRUD 전체 테스트 (curl: 생성, 조회, 수정, 삭제, 필터)
|
|
||||||
- [ ] Mock/localStorage 잔여 0건 (UI 상수 제외)
|
|
||||||
- [ ] PUT/DELETE 사용 0건 (프론트/백엔드 모두)
|
|
||||||
- [ ] 커밋 + 푸시 + MR 생성
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 관련 문서
|
|
||||||
|
|
||||||
- [CRUD-API-GUIDE.md](./CRUD-API-GUIDE.md) — CRUD API 표준 (DB 설계, Service/Router 패턴, 권한 모델)
|
|
||||||
- [COMMON-GUIDE.md](./COMMON-GUIDE.md) — 공통 로직 (인증, 감사 로그, 메뉴, API 통신)
|
|
||||||
- [MENU-TAB-GUIDE.md](./MENU-TAB-GUIDE.md) — 새 메뉴 탭 추가 절차
|
|
||||||
@ -1,247 +0,0 @@
|
|||||||
# 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)를 참조하세요.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 기술 스택
|
|
||||||
|
|
||||||
| 영역 | 기술 |
|
|
||||||
|------|------|
|
|
||||||
| 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 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 프로젝트 구조
|
|
||||||
|
|
||||||
```
|
|
||||||
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)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 개발 환경 실행
|
|
||||||
|
|
||||||
### 사전 요구사항
|
|
||||||
- Node.js 20+ (`.node-version`, fnm 사용)
|
|
||||||
- PostgreSQL 16+ (운영 DB에 직접 연결)
|
|
||||||
|
|
||||||
### 실행
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 백엔드 (터미널 1)
|
|
||||||
cd backend && npm install && npm run dev # localhost:3001
|
|
||||||
|
|
||||||
# 프론트엔드 (터미널 2)
|
|
||||||
cd frontend && npm install && npm run dev # localhost:5173
|
|
||||||
```
|
|
||||||
|
|
||||||
### 빌드/검증
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# TypeScript 타입 체크
|
|
||||||
cd frontend && npx tsc --noEmit
|
|
||||||
cd backend && npx tsc --noEmit
|
|
||||||
|
|
||||||
# ESLint
|
|
||||||
cd frontend && npx eslint .
|
|
||||||
|
|
||||||
# 프로덕션 빌드
|
|
||||||
cd frontend && npm run build # dist/ 생성
|
|
||||||
cd backend && npm run build # 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)
|
|
||||||
```
|
|
||||||
VITE_API_URL=http://localhost:3001/api
|
|
||||||
VITE_GOOGLE_CLIENT_ID=your-google-client-id
|
|
||||||
```
|
|
||||||
|
|
||||||
### 백엔드 (.env)
|
|
||||||
```
|
|
||||||
PORT=3001
|
|
||||||
NODE_ENV=development
|
|
||||||
JWT_SECRET=your-jwt-secret
|
|
||||||
AUTH_DB_HOST=localhost
|
|
||||||
AUTH_DB_PORT=5432
|
|
||||||
AUTH_DB_NAME=wing_auth
|
|
||||||
AUTH_DB_USER=wing_auth
|
|
||||||
AUTH_DB_PASSWORD=WingAuth!2026
|
|
||||||
GOOGLE_CLIENT_ID=your-google-client-id
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 배포
|
|
||||||
|
|
||||||
| 항목 | 값 |
|
|
||||||
|------|---|
|
|
||||||
| 프론트엔드 | https://wing-demo.gc-si.dev |
|
|
||||||
| 백엔드 API | https://wing-demo.gc-si.dev/api/ |
|
|
||||||
| CI/CD | Gitea Actions (main 머지 시 자동 배포) |
|
|
||||||
|
|
||||||
배포 파이프라인 상세는 [DEVELOPMENT-GUIDE.md #7](DEVELOPMENT-GUIDE.md#7-자동-배포)을 참조하세요.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 문서 최신화 규칙
|
|
||||||
|
|
||||||
공통 기능(인증, 감사로그, 메뉴 시스템, API 통신 등)을 추가/변경할 때:
|
|
||||||
1. 해당 기능 코드 구현
|
|
||||||
2. `docs/COMMON-GUIDE.md` 최신화 (필수)
|
|
||||||
3. 필요 시 `CLAUDE.md` 프로젝트 구조 갱신
|
|
||||||
|
|
||||||
매 기능 개발 완료 시:
|
|
||||||
```
|
|
||||||
Claude에게: "memory 파일 최신화해줘"
|
|
||||||
```
|
|
||||||
@ -1,123 +0,0 @@
|
|||||||
# WING-OPS (해양 방제 운영 지원 시스템)
|
|
||||||
|
|
||||||
## 프로젝트 개요
|
|
||||||
해양 오염 사고 대응을 위한 방제 운영 지원 시스템.
|
|
||||||
유류/HNS 확산 예측, 역추적 분석, 구조 시나리오, 항공 방제, 자산 관리, SCAT 조사, 기상/해상 정보를 통합 제공한다.
|
|
||||||
|
|
||||||
- **프로젝트 타입**: react-ts (모노레포)
|
|
||||||
- **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
|
|
||||||
- **실시간**: Socket.IO
|
|
||||||
|
|
||||||
## 빌드/실행
|
|
||||||
|
|
||||||
### 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 # 빌드 미리보기
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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 도입 예정.
|
|
||||||
|
|
||||||
## Lint/Format
|
|
||||||
```bash
|
|
||||||
cd frontend && npx eslint . # ESLint (flat config)
|
|
||||||
npx prettier --check . # Prettier 검증
|
|
||||||
npx prettier --write . # Prettier 자동 수정
|
|
||||||
```
|
|
||||||
|
|
||||||
## 프로젝트 구조
|
|
||||||
```
|
|
||||||
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
|
|
||||||
│ │ ├── data/ layerData.ts (UI 레이어 트리)
|
|
||||||
│ │ └── mock/ vesselMockData, backtrackMockData
|
|
||||||
│ └── tabs/ 탭 단위 패키지 (@tabs/ alias)
|
|
||||||
│ ├── prediction/ 확산 예측 (OilSpillView, LeftPanel 등)
|
|
||||||
│ ├── hns/ HNS 분석 (HNSView, HNSSubstanceView 등)
|
|
||||||
│ ├── rescue/ 구조 시나리오
|
|
||||||
│ ├── aerial/ 항공 방제
|
|
||||||
│ ├── weather/ 해양 기상 (오버레이, hooks, services)
|
|
||||||
│ ├── incidents/ 사건/사고 관리
|
|
||||||
│ ├── board/ 게시판
|
|
||||||
│ ├── reports/ 보고서
|
|
||||||
│ ├── assets/ 자산 관리
|
|
||||||
│ ├── scat/ Pre-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 스크립트
|
|
||||||
│ ├── init.sql wing DB 초기 스키마
|
|
||||||
│ ├── auth_init.sql wing_auth DB 초기 스키마
|
|
||||||
│ └── migration/ 마이그레이션 (001_layer, 002_hns_substance)
|
|
||||||
├── docs/ 개발 문서
|
|
||||||
├── .claude/ 팀 워크플로우 (rules, skills, scripts)
|
|
||||||
└── .githooks/ Git hooks (pre-commit, commit-msg)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Path Alias
|
|
||||||
- `@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` — 변경 이력
|
|
||||||
|
|
||||||
### 문서 최신화 규칙
|
|
||||||
- 공통 기능(인증, 감사로그, 메뉴 시스템, API 통신 등)을 추가/변경할 때 반드시 `docs/COMMON-GUIDE.md`를 최신화할 것
|
|
||||||
- 개별 탭 개발자는 이 문서를 참조하여 공통 영역과의 연동을 구현
|
|
||||||
|
|
||||||
## 환경 설정
|
|
||||||
- Node.js 20 (`.node-version`, fnm 사용)
|
|
||||||
- npm registry: Nexus proxy (`.npmrc`)
|
|
||||||
- Git hooks: `.githooks/` (core.hooksPath 설정됨)
|
|
||||||
@ -1,223 +0,0 @@
|
|||||||
# WING-OPS (해양 방제 운영 지원 시스템)
|
|
||||||
|
|
||||||
해양 오염 사고 대응을 위한 방제 운영 지원 시스템.
|
|
||||||
유류/HNS 확산 예측, 역추적 분석, 구조 시나리오, 항공 방제, 자산 관리, SCAT 조사, 기상/해상 정보를 통합 제공합니다.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. 시작하기
|
|
||||||
|
|
||||||
### 1-1. 저장소 복제
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://gitea.gc-si.dev/gc/wing-ops.git
|
|
||||||
cd wing-ops
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1-2. Claude Code 초기화
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Claude Code 세션 열기
|
|
||||||
claude
|
|
||||||
|
|
||||||
# 팀 워크플로우 초기화
|
|
||||||
/init-project
|
|
||||||
```
|
|
||||||
|
|
||||||
`/init-project` 실행 시 자동으로 구성되는 항목:
|
|
||||||
- `.claude/` 디렉토리 (rules, skills, scripts, settings)
|
|
||||||
- `.githooks/` (pre-commit, commit-msg 자동 검증)
|
|
||||||
- Git hooks 경로 설정 (`core.hooksPath`)
|
|
||||||
- 메모리 디렉토리 초기화
|
|
||||||
|
|
||||||
### 1-3. 의존성 설치 및 실행
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 백엔드 (터미널 1)
|
|
||||||
cd backend && npm install && npm run dev # localhost:3001
|
|
||||||
|
|
||||||
# 프론트엔드 (터미널 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)를 참조하세요.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. 개발 워크플로우
|
|
||||||
|
|
||||||
```
|
|
||||||
계획 → 브랜치 → 개발 → 커밋/푸시 → 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 자동 배포 | - |
|
|
||||||
|
|
||||||
> 상세 워크플로우(브랜치 규칙, 커밋 형식, MR 절차, 배포 확인, 실전 예시)는 [docs/DEVELOPMENT-GUIDE.md](docs/DEVELOPMENT-GUIDE.md)를 참조하세요.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 탭 개발
|
|
||||||
|
|
||||||
개별 탭(기능 화면)을 개발할 때 아래 공통 기능을 활용합니다.
|
|
||||||
|
|
||||||
| 기능 | 프론트엔드 | 백엔드 | 상세 |
|
|
||||||
|------|-----------|--------|------|
|
|
||||||
| 인증/인가 | `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-통신-패턴) |
|
|
||||||
| 상태 관리 | 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)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 프로젝트 구조
|
|
||||||
|
|
||||||
```
|
|
||||||
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/ 확산 예측
|
|
||||||
│ ├── hns/ HNS 분석
|
|
||||||
│ ├── rescue/ 구조 시나리오
|
|
||||||
│ ├── aerial/ 항공 방제
|
|
||||||
│ ├── weather/ 해양 기상
|
|
||||||
│ ├── incidents/ 사건/사고
|
|
||||||
│ ├── board/ 게시판
|
|
||||||
│ ├── reports/ 보고서
|
|
||||||
│ ├── assets/ 자산 관리
|
|
||||||
│ ├── scat/ Pre-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)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 기술 스택
|
|
||||||
|
|
||||||
| 영역 | 기술 |
|
|
||||||
|------|------|
|
|
||||||
| 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 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 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) | 변경 이력 | 모든 개발자 |
|
|
||||||
|
|
||||||
### 코드 컨벤션 (.claude/rules/)
|
|
||||||
|
|
||||||
| 규칙 | 설명 |
|
|
||||||
|------|------|
|
|
||||||
| `team-policy.md` | 보안/품질 정책 (필수 준수) |
|
|
||||||
| `git-workflow.md` | 브랜치/커밋/MR 규칙 |
|
|
||||||
| `code-style.md` | TypeScript/React 코드 스타일 |
|
|
||||||
| `naming.md` | 네이밍 규칙 |
|
|
||||||
| `testing.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
|
|
||||||
JWT_SECRET=your-jwt-secret
|
|
||||||
AUTH_DB_HOST=localhost
|
|
||||||
AUTH_DB_PORT=5432
|
|
||||||
AUTH_DB_NAME=wing_auth
|
|
||||||
AUTH_DB_USER=wing_auth
|
|
||||||
AUTH_DB_PASSWORD=<비밀번호>
|
|
||||||
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-자동-배포)을 참조하세요.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Claude Code 스킬
|
|
||||||
|
|
||||||
| 스킬 | 설명 |
|
|
||||||
|------|------|
|
|
||||||
| `/push` | 커밋 + 푸시 (한 번에) |
|
|
||||||
| `/mr` | 커밋 + 푸시 + develop MR (한 번에) |
|
|
||||||
| `/release` | develop → main 릴리즈 MR |
|
|
||||||
| `/create-mr` | MR만 생성 (세부 옵션) |
|
|
||||||
| `/fix-issue` | Gitea 이슈 분석 + 수정 브랜치 생성 |
|
|
||||||
| `/sync-team-workflow` | 팀 워크플로우 동기화 |
|
|
||||||
| `/changelog` | CHANGELOG.md 갱신 |
|
|
||||||
불러오는 중...
Reference in New Issue
Block a user