wing-ops/docs/COMMON-GUIDE.md
htlee 199d5310db refactor(backend): SQLite → PostgreSQL 마이그레이션 + wing DB 연결
- better-sqlite3 제거, wingDb.ts (PostgreSQL wing DB Pool) 추가
- layers 라우터: 동기(better-sqlite3) → 비동기(pg) 전환
- LAYER 테이블 마이그레이션 SQL 생성 (database/migration/001_layer_table.sql)
- seed 스크립트 PostgreSQL 전환
- 문서 업데이트: CLAUDE.md, README.md, docs/README.md, COMMON-GUIDE.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 14:18:00 +09:00

326 lines
9.5 KiB
Markdown

# WING-OPS 공통 로직 개발 가이드
개별 탭 개발자가 공통 영역 구현을 참조하여 연동할 수 있도록 정리한 문서입니다.
공통 기능을 추가/변경할 때 반드시 이 문서를 최신화하세요.
---
## 1. 인증/인가
### 개요
JWT 기반 세션 인증. HttpOnly 쿠키(`WING_SESSION`)로 토큰을 관리하며, 프론트엔드에서는 Zustand `authStore`로 상태를 관리합니다.
### 백엔드
#### 미들웨어 적용
```typescript
// backend/src/auth/authMiddleware.ts
import { requireAuth, requireRole } from '../auth/authMiddleware.js'
// 인증만 필요한 라우트
router.use(requireAuth)
// 특정 역할 필요
router.use(requireRole('ADMIN'))
router.use(requireRole('ADMIN', 'MANAGER'))
```
#### 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)
}
```
#### 라우터 패턴
```typescript
// backend/src/[모듈]/[모듈]Router.ts
import { Router } from 'express'
import { requireAuth, requireRole } from '../auth/authMiddleware.js'
const router = Router()
router.use(requireAuth)
router.get('/', async (req, res) => {
try {
const userId = req.user!.sub
// 비즈니스 로직...
res.json(result)
} catch (err) {
console.error('[모듈] 오류:', err)
res.status(500).json({ error: '처리 중 오류가 발생했습니다.' })
}
})
export default router
```
### 프론트엔드
#### authStore (Zustand)
```typescript
// frontend/src/store/authStore.ts
import { useAuthStore } from '../store/authStore'
// 컴포넌트 내에서 사용
const { user, isAuthenticated, hasPermission, logout } = useAuthStore()
// 사용자 정보
user?.id // UUID
user?.name // 이름
user?.roles // ['ADMIN', 'USER']
// 권한 확인 (탭 ID 기준)
hasPermission('prediction') // true/false
hasPermission('admin') // true/false
```
#### API 클라이언트
```typescript
// frontend/src/services/api.ts
import { api } from './api'
// withCredentials: true 설정으로 JWT 쿠키 자동 포함
const response = await api.get('/your-endpoint')
const response = await api.post('/your-endpoint', data)
// 401 응답 시 자동 로그아웃 처리 (인터셉터)
```
---
## 2. 감사 로그 (Audit Log)
### 개요
사용자 행동을 추적하는 감사 로그 시스템. 현재 탭 이동 로그를 자동 기록하며, 향후 API 호출 로깅으로 확장 가능합니다.
### 자동 기록 (탭 이동)
`App.tsx``useEffect`에서 `activeMainTab` 변경을 감지하여 `navigator.sendBeacon`으로 자동 전송합니다. 개별 탭 개발자는 별도 작업이 필요 없습니다.
```typescript
// frontend/src/App.tsx (자동 적용, 수정 불필요)
useEffect(() => {
if (!isAuthenticated) return
const blob = new Blob(
[JSON.stringify({ action: 'TAB_VIEW', detail: activeMainTab })],
{ type: 'text/plain' }
)
navigator.sendBeacon('/api/audit/log', blob)
}, [activeMainTab, isAuthenticated])
```
### 수동 기록 (향후 확장)
특정 작업에 대해 명시적으로 감사 로그를 기록하려면:
```typescript
// 프론트엔드에서 sendBeacon 사용
const blob = new Blob(
[JSON.stringify({ action: 'ADMIN_ACTION', detail: '사용자 승인' })],
{ type: 'text/plain' }
)
navigator.sendBeacon('/api/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. 백엔드 모듈 추가 절차
새 백엔드 모듈을 추가할 때:
1. `backend/src/[모듈명]/` 디렉토리 생성
2. `[모듈명]Service.ts` — 비즈니스 로직 (DB 쿼리)
3. `[모듈명]Router.ts` — Express 라우터 (입력 검증, 에러 처리)
4. `backend/src/server.ts`에 라우터 등록:
```typescript
import newRouter from './[모듈명]/[모듈명]Router.js'
app.use('/api/[경로]', newRouter)
```
5. DB 테이블 필요 시 `database/auth_init.sql`에 DDL 추가
### 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])
```
---
## 파일 구조 요약
```
frontend/src/
├── services/api.ts Axios 인스턴스 + 인터셉터
├── services/authApi.ts 인증/사용자/역할/설정/메뉴/감사로그 API
├── store/authStore.ts 인증 상태 (Zustand)
├── store/menuStore.ts 메뉴 상태 (Zustand)
└── App.tsx 탭 라우팅 + 감사 로그 자동 기록
backend/src/
├── auth/ 인증 (JWT, OAuth, 미들웨어)
├── users/ 사용자 관리
├── roles/ 역할/권한 관리
├── settings/ 시스템 설정
├── menus/ 메뉴 설정
├── audit/ 감사 로그
├── db/ DB 연결 (authDb, database)
├── middleware/ 보안 미들웨어
└── server.ts Express 진입점 + 라우터 등록
```