From ffde4d669403db1e5287c1e9031ff9a6b74a90ef Mon Sep 17 00:00:00 2001 From: htlee Date: Sat, 28 Feb 2026 21:36:35 +0900 Subject: [PATCH] =?UTF-8?q?chore:=20=ED=8C=80=20=EC=9B=8C=ED=81=AC?= =?UTF-8?q?=ED=94=8C=EB=A1=9C=EC=9A=B0=20v1.4.0=20=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=ED=99=94=20+=20=EB=AC=B8=EC=84=9C=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 에이전트 파일 YAML frontmatter 형식 갱신 (explorer, implementer, reviewer) - subagent-policy.md 규칙 추가 - commit-msg hook 패턴 간소화 - COMMON-GUIDE.md API 연동 가이드 보강 - MOCK-TO-API-GUIDE.md mock→API 전환 가이드 추가 Co-Authored-By: Claude Opus 4.6 --- .claude/agents/explorer.md | 47 ++++ .claude/agents/implementer.md | 55 ++++ .claude/agents/reviewer.md | 53 ++++ .claude/rules/subagent-policy.md | 61 +++++ .claude/workflow-version.json | 4 +- .githooks/commit-msg | 17 +- docs/COMMON-GUIDE.md | 55 +++- docs/MOCK-TO-API-GUIDE.md | 435 +++++++++++++++++++++++++++++++ 8 files changed, 710 insertions(+), 17 deletions(-) create mode 100644 .claude/agents/explorer.md create mode 100644 .claude/agents/implementer.md create mode 100644 .claude/agents/reviewer.md create mode 100644 .claude/rules/subagent-policy.md create mode 100644 docs/MOCK-TO-API-GUIDE.md diff --git a/.claude/agents/explorer.md b/.claude/agents/explorer.md new file mode 100644 index 0000000..be33b64 --- /dev/null +++ b/.claude/agents/explorer.md @@ -0,0 +1,47 @@ +--- +name: explorer +description: 코드베이스 탐색 및 분석 에이전트. 3개 이상의 파일을 탐색하거나 프로젝트 구조를 파악할 때 사용한다. +model: sonnet +tools: Read, Glob, Grep +maxTurns: 12 +--- + +지정된 영역 내에서 코드베이스를 분석하고 구조화된 결과를 반환한다. +읽기 전용 — 파일을 수정하지 않는다. + +## 자율 범위 + +- 메인 세션이 지정한 **탐색 영역**(디렉토리 또는 파일 목록) 내에서 자유롭게 탐색 +- 영역 내 파일 간 의존성 추적, 임포트 체인 분석은 자율 수행 +- 탐색 영역 밖 파일은 임포트/참조 관계 확인 목적으로만 열람 가능 + +## 입력 (메인 세션이 제공) + +- **탐색 영역**: 디렉토리 경로 또는 파일 목록 +- **목적**: 분석 목적이나 답변할 질문 (구체적일수록 좋음) + +## 출력 형식 + +``` +## 분석 결과 + +### 구조 +- 핵심 파일/디렉토리 구성 (파일:라인 근거) + +### 발견사항 +- 목적에 대한 답변 (파일:라인 근거 포함) + +### 패턴 +- 코드 컨벤션, 반복 패턴, 아키텍처 특성 + +### 확신도 +- 각 발견사항별: 확정 / 추정(근거) / 판단불가(필요 정보) + +### 범위 외 참고 +- 탐색 영역 밖에서 발견된 관련 사항 (해당 시) +``` + +## 제약 + +- 파일 수정/생성 금지 +- 정보 부족 시 추측하지 않고 "판단불가 — [필요한 정보]"로 표시 diff --git a/.claude/agents/implementer.md b/.claude/agents/implementer.md new file mode 100644 index 0000000..6ac24b6 --- /dev/null +++ b/.claude/agents/implementer.md @@ -0,0 +1,55 @@ +--- +name: implementer +description: 모듈 단위 코드 구현 에이전트. 독립 모듈 구현이나 병렬 작업이 필요할 때 사용한다. +model: sonnet +tools: Read, Write, Edit, Glob, Grep, Bash +maxTurns: 20 +--- + +메인 세션이 정의한 계약(인터페이스, 타입, 제약)에 따라 코드를 구현한다. +내부 구현 방식은 자율 판단하되, 계약과 제약을 벗어나지 않는다. + +## 자율 범위 + +- 계약(함수 시그니처, API 스펙, 타입)은 메인 세션이 확정 — 변경 불가 +- 내부 구현 로직, 헬퍼 함수, 에러 처리 방식은 자율 판단 +- **[참조]** 파일이 제공되면 해당 파일의 코드 패턴(네이밍, 구조, 에러 처리)을 따름 + +## 입력 (메인 세션이 제공) + +- **[파일]**: 수정/생성할 파일 경로 +- **[계약]**: 인터페이스, 타입, 함수 시그니처, API 스펙 등 외부 계약 +- **[참조]**: 패턴을 따를 기존 파일 (선택, 제공 시 해당 패턴 준수) +- **[제약]**: 특별한 요구사항 (선택) + +## 출력 형식 + +``` +## 구현 결과 + +### 수정 파일 +- 파일 경로 목록 + +### 파일별 변경 +- 각 파일에서 추가/수정한 내용 요약 + +### 자체 검증 +- tsc --noEmit: 통과 / 실패(에러 내용) +- [추가 검증 항목]: 결과 + +### 계약 외 판단 +- 자율 판단한 구현 결정 사항 (메인 세션 참고용) + +### 보고 사항 (해당 시) +- 계약 불충분: 추가 정보가 필요한 항목 +- 아키텍처 영향: 범위 밖 변경이 필요한 사항 +``` + +## 제약 + +- [파일]에 명시되지 않은 파일 수정 금지 +- [계약]의 시그니처/타입 임의 변경 금지 +- 아키텍처 변경이 필요하면 구현하지 않고 "보고 사항"에 기록 +- 커밋/푸시 금지 +- any 타입 금지, strict 모드 준수 +- 구현 완료 후 tsc --noEmit 자체 검증 수행 diff --git a/.claude/agents/reviewer.md b/.claude/agents/reviewer.md new file mode 100644 index 0000000..e4763ac --- /dev/null +++ b/.claude/agents/reviewer.md @@ -0,0 +1,53 @@ +--- +name: reviewer +description: 코드 리뷰 및 품질 검증 에이전트. 커밋 전 검증이나 MR 리뷰 시 사용한다. +model: sonnet +tools: Read, Glob, Grep, Bash +maxTurns: 12 +--- + +변경된 코드를 체크리스트 기반으로 검증한다. +읽기 전용 — 파일을 수정하지 않는다. + +## 자율 범위 + +- 지정된 변경 파일을 자유롭게 분석 +- 관련 파일(임포트 대상, 호출자)도 열람하여 영향 범위 확인 가능 +- 기본 체크리스트 + 자체 판단으로 추가 이슈 탐지 + +## 입력 (메인 세션이 제공) + +- **[대상]**: 리뷰할 파일 경로 목록 또는 git diff 범위 +- **[체크리스트]**: 검증 항목 (선택, 미제공 시 기본 체크리스트 사용) + +## 기본 체크리스트 + +1. 타입 안전성 — any, 타입 단언(as), non-null 단언(!) 사용 +2. 에러 처리 — try-catch 누락, empty catch, 에러 무시 +3. 보안 — 하드코딩 인증정보, injection 가능성, XSS +4. 미사용 코드 — 미사용 import, 변수, 함수 +5. 팀 정책 — team-policy.md 위반 사항 +6. 일관성 — 기존 코드 패턴과의 불일치 + +## 출력 형식 + +``` +## 리뷰 결과 + +| # | 항목 | 판정 | 근거 | +|---|------|------|------| +| 1 | [항목명] | PASS / FAIL | [파일:라인] 설명 | + +### 추가 발견 (자체 판단) +- [파일:라인] 설명 (심각도: Critical / Warning / Info) + +### 요약 +- 전체: N개 PASS / M개 FAIL +- 커밋 가능 여부: 가능 / 차단 권고(사유) +``` + +## 제약 + +- 파일 수정/생성 금지 +- 각 항목에 반드시 PASS 또는 FAIL 판정 (애매하면 FAIL + 사유) +- 스타일 개선 제안은 "추가 발견"에 Info로만 기록 diff --git a/.claude/rules/subagent-policy.md b/.claude/rules/subagent-policy.md new file mode 100644 index 0000000..05164c5 --- /dev/null +++ b/.claude/rules/subagent-policy.md @@ -0,0 +1,61 @@ +# 서브에이전트 활용 정책 + +커스텀 에이전트(`.claude/agents/`)를 활용하여 컨텍스트를 보호하고 병렬 작업을 수행한다. +메인 세션은 리더 역할(설계, 조율, 최종 판단)에 집중하고, 실제 작업은 서브에이전트에 위임한다. + +## 에이전트 구성 + +| 에이전트 | 역할 | 자율성 | 모델 | +|----------|------|--------|------| +| explorer | 코드베이스 탐색/분석 (읽기 전용) | 높음 | sonnet | +| implementer | 모듈 단위 코드 구현 | 중간 | sonnet | +| reviewer | 코드 리뷰/품질 검증 (읽기 전용) | 높음 | sonnet | + +## 사용 시점 + +### explorer +- 3개 이상의 파일/디렉토리를 탐색해야 할 때 +- 프로젝트 구조나 패턴을 파악할 때 +- 의존성 체인, 임포트 관계를 추적할 때 + +### implementer +- 독립 모듈/컴포넌트를 구현할 때 +- 여러 모듈을 병렬로 구현할 때 (각각 별도 implementer) +- 반복 패턴을 여러 파일에 적용할 때 + +### reviewer +- 구현 완료 후 커밋 전 검증 +- MR 생성 전 자체 리뷰 +- 변경 범위가 클 때 (5개 이상 파일) + +## 사용하지 않는 경우 + +- 단일 파일의 간단한 수정 +- 위치를 이미 아는 코드 수정 +- 설정 파일 변경 + +## 메인 세션 작업 흐름 + +### 단일 모듈 +1. 메인: 계약(인터페이스, 타입) 설계 +2. implementer: 계약 기반 구현 + 자체 검증 +3. reviewer: 변경 파일 리뷰 +4. 메인: 결과 확인 → 커밋 + +### 다중 모듈 (병렬) +1. 메인: 모듈 간 공유 인터페이스 확정 +2. implementer A + B: 각 모듈 동시 구현 +3. 메인: 통합 확인 (인터페이스 일치) +4. reviewer: 전체 변경 리뷰 +5. 메인: 최종 확인 → 커밋 + +### 분석 +1. explorer: 탐색 영역 + 목적 전달 → 분석 결과 반환 +2. 메인: "추정" 항목만 직접 확인 → 판단 + +## 핵심 원칙 + +- **읽기 전용 에이전트(explorer/reviewer)**: 결과가 부정확해도 손해 없음 → 높은 자율성 부여 +- **쓰기 에이전트(implementer)**: 계약은 고정, 내부 구현은 자율 → 중간 자율성 +- **같은 파일을 두 에이전트가 동시에 수정하지 않는다** +- **커밋/푸시는 반드시 메인 세션에서 수행** diff --git a/.claude/workflow-version.json b/.claude/workflow-version.json index 77a685f..3655a84 100644 --- a/.claude/workflow-version.json +++ b/.claude/workflow-version.json @@ -1,6 +1,6 @@ { - "applied_global_version": "1.3.0", - "applied_date": "2026-02-27", + "applied_global_version": "1.4.0", + "applied_date": "2026-02-28", "project_type": "react-ts", "gitea_url": "https://gitea.gc-si.dev" } diff --git a/.githooks/commit-msg b/.githooks/commit-msg index 67be0a9..93bb350 100755 --- a/.githooks/commit-msg +++ b/.githooks/commit-msg @@ -20,10 +20,9 @@ fi # Conventional Commits 정규식 # type(scope): subject # - type: feat|fix|docs|style|refactor|test|chore|ci|perf (필수) -# - scope: 괄호 제외 모든 문자 허용 — 한/영/숫자/특수문자 (선택) -# - subject: 1자 이상 (길이는 바이트 기반 별도 검증) -PATTERN='^(feat|fix|docs|style|refactor|test|chore|ci|perf)(\([^)]+\))?: .+$' -MAX_SUBJECT_BYTES=200 # UTF-8 한글(3byte) 허용: 72문자 ≈ 최대 216byte +# - scope: 영문, 숫자, 한글, 점, 밑줄, 하이픈 허용 (선택) +# - subject: 1~72자, 한/영 혼용 허용 (필수) +PATTERN='^(feat|fix|docs|style|refactor|test|chore|ci|perf)(\([a-zA-Z0-9가-힣._-]+\))?: .{1,72}$' FIRST_LINE=$(head -1 "$COMMIT_MSG_FILE") @@ -59,13 +58,3 @@ if ! echo "$FIRST_LINE" | grep -qE "$PATTERN"; then echo "" exit 1 fi - -# 길이 검증 (바이트 기반 — UTF-8 한글 허용) -MSG_LEN=$(echo -n "$FIRST_LINE" | wc -c | tr -d ' ') -if [ "$MSG_LEN" -gt "$MAX_SUBJECT_BYTES" ]; then - echo "" - echo " ✗ 커밋 메시지가 너무 깁니다 (${MSG_LEN}바이트, 최대 ${MAX_SUBJECT_BYTES})" - echo " 현재 메시지: $FIRST_LINE" - echo "" - exit 1 -fi diff --git a/docs/COMMON-GUIDE.md b/docs/COMMON-GUIDE.md index 561f4ca..b43ec70 100644 --- a/docs/COMMON-GUIDE.md +++ b/docs/COMMON-GUIDE.md @@ -416,6 +416,55 @@ const result = await authPool.query('SELECT * FROM AUTH_USER WHERE USER_ID = $1' --- +## 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) // 상세 + 섹션 복원 +``` + +--- + ## 파일 구조 요약 ``` @@ -433,6 +482,8 @@ backend/src/ ├── auth/ 인증 (JWT, OAuth, 미들웨어, requirePermission) ├── users/ 사용자 관리 ├── roles/ 역할/권한 관리 (permResolver, roleService) +├── board/ 게시판 CRUD (boardService, boardRouter) +├── reports/ 보고서 CRUD (reportsService, reportsRouter) ├── settings/ 시스템 설정 ├── menus/ 메뉴 설정 ├── audit/ 감사 로그 @@ -445,5 +496,7 @@ database/ ├── init.sql 운영 DB DDL └── migration/ 마이그레이션 스크립트 ├── 003_perm_tree.sql 리소스 트리 (AUTH_PERM_TREE) - └── 004_oper_cd.sql 오퍼레이션 코드 (OPER_CD) 추가 + ├── 004_oper_cd.sql 오퍼레이션 코드 (OPER_CD) 추가 + ├── 006_board.sql 게시판 (BOARD_POST) + └── 007_reports.sql 보고서 (REPORT_TMPL, REPORT, REPORT_SECT_DATA 등 7개) ``` diff --git a/docs/MOCK-TO-API-GUIDE.md b/docs/MOCK-TO-API-GUIDE.md new file mode 100644 index 0000000..684f201 --- /dev/null +++ b/docs/MOCK-TO-API-GUIDE.md @@ -0,0 +1,435 @@ +# 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 { + await api.post('/{탭명}/update', { sn, ...data }) +} + +// 삭제 — POST /delete 사용 +export async function delete{탭명}(sn: number): Promise { + 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 { + 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) — 새 메뉴 탭 추가 절차