22 KiB
WING-OPS 개발 워크플로우 가이드
목차
- 전체 흐름 요약
- 개발 환경 설정
- 계획 수립 (Plan)
- 브랜치 생성
- 개발
- 검증
- 커밋 & 푸시
- MR(Merge Request)
- 릴리즈 & 배포
- 디버깅 팁
- 실전 예시: 기능 추가 A to Z
1. 전체 흐름 요약
Plan ─── Branch ─── Implement ─── Test ─── MR ─── Deploy
[1. Plan] 작업 범위 정의, 수정 파일 식별
|
[2. Branch] develop 기반 feature 브랜치 생성
|
[3. Implement] 코드 작성 (Frontend / Backend / DB)
|
[4. Test] tsc --noEmit + ESLint + 수동 테스트
|
[5. Commit & Push] Conventional Commits 형식, pre-commit 자동 검증
|
[6. MR -> develop] 코드 리뷰 + 승인 + Squash Merge
|
[7. MR -> main] 릴리즈 MR + 머지
|
[8. Auto Deploy] Gitea Actions -> 빌드 -> 서버 배포
브랜치 흐름
main (보호, 배포용)
|
+-- develop (보호, 개발 통합)
|
+-- feature/ISSUE-42-login (기능 개발)
+-- bugfix/ISSUE-15-token-fix (버그 수정)
+-- hotfix/critical-patch (긴급 수정, main에서 분기)
2. 개발 환경 설정
2-1. Node.js 설치 (fnm)
프로젝트는 Node.js 20을 사용한다. .node-version 파일로 버전이 고정되어 있다.
# fnm 설치 (이미 설치된 경우 생략)
curl -fsSL https://fnm.vercel.app/install | bash
# Node.js 20 설치 및 활성화
fnm install 20
fnm use 20
# 버전 확인
node -v # v20.x.x
npm -v # v10.x.x
2-2. 프로젝트 클론 및 의존성 설치
# 클론
git clone https://gitea.gc-si.dev/gc/wing-ops.git wing
cd wing
# Git Hooks 경로 설정 (최초 1회)
git config core.hooksPath .githooks
# npm 레지스트리는 .npmrc에 설정됨 (Nexus 프록시)
# 별도 설정 불필요
# 의존성 설치
cd frontend && npm install
cd ../backend && npm install
2-3. 환경변수 설정
Backend (backend/.env):
# 서버
PORT=3001
NODE_ENV=development
# wing DB (운영 데이터)
WING_DB_HOST=211.208.115.83
WING_DB_PORT=5432
WING_DB_USER=wing
WING_DB_PASS=<비밀번호>
WING_DB_NAME=wing
# wing_auth DB (인증/권한)
AUTH_DB_HOST=211.208.115.83
AUTH_DB_PORT=5432
AUTH_DB_USER=wing_auth
AUTH_DB_PASS=<비밀번호>
AUTH_DB_NAME=wing_auth
# JWT
JWT_SECRET=<시크릿>
JWT_EXPIRES_IN=24h
# CORS
FRONTEND_URL=http://localhost:5173
# Google OAuth
GOOGLE_CLIENT_ID=<클라이언트ID>
Frontend (frontend/.env):
# 백엔드 API URL
VITE_API_URL=http://localhost:3001/api
# Google OAuth
VITE_GOOGLE_CLIENT_ID=<클라이언트ID>
# 공공 API 키 (해양기상 등)
VITE_DATA_GO_KR_API_KEY=<키>
VITE_WEATHER_API_KEY=<키>
.env파일은.gitignore에 포함되어 있으므로 커밋되지 않는다. 실제 값은 팀 내부 공유 채널에서 확인한다.
2-4. 개발 서버 실행
터미널 2개를 열어 각각 실행한다:
# 터미널 1: 백엔드 (localhost:3001)
cd backend
npm run dev
# 터미널 2: 프론트엔드 (localhost:5173)
cd frontend
npm run dev
2-5. Path Alias
Frontend에서 두 가지 경로 별칭을 사용한다:
| Alias | 실제 경로 | 용도 |
|---|---|---|
@common/* |
src/common/* |
공통 모듈 (컴포넌트, 훅, 서비스, 스토어) |
@tabs/* |
src/tabs/* |
탭별 패키지 (11개 탭) |
import { useAuth } from '@common/hooks/useAuth';
import OilSpillView from '@tabs/prediction/components/OilSpillView';
3. 계획 수립 (Plan)
계획이 필요한 경우
- 3개 이상 파일 수정이 예상되는 작업
- 아키텍처에 영향을 주는 변경
- 새로운 탭/모듈 추가
- DB 스키마 변경
계획에 포함할 내용
- 수정/생성할 파일 목록
- 변경 범위 및 영향도 (다른 모듈에 미치는 영향)
- DB 마이그레이션 필요 여부 (필요 시 migration SQL 작성)
- 구현 순서 (의존성 고려)
계획이 불필요한 경우
- 단일 파일 수정 (버그 수정, 텍스트 변경)
- 스타일/포맷팅 변경
- 설정 파일 변경
4. 브랜치 생성
네이밍 규칙
| 유형 | 형식 | 예시 | 분기 기준 |
|---|---|---|---|
| 기능 | feature/ISSUE-번호-설명 |
feature/ISSUE-42-user-login |
develop |
| 기능 (이슈 없음) | feature/설명 |
feature/add-swagger-docs |
develop |
| 버그 | bugfix/ISSUE-번호-설명 |
bugfix/ISSUE-15-token-expired |
develop |
| 긴급 | hotfix/설명 |
hotfix/security-patch |
main |
브랜치 생성
# 1. develop 최신화
git checkout develop
git pull origin develop
# 2. feature 브랜치 생성
git checkout -b feature/ISSUE-42-user-login
# 확인
git branch # * feature/ISSUE-42-user-login
브랜치 규칙
- main, develop 브랜치에 직접 커밋/푸시 금지 (보호 브랜치)
- 머지는 반드시 **MR(Merge Request)**을 통해 수행
- 머지 후 소스 브랜치 삭제
5. 개발
5-1. Frontend 개발 구조
frontend/src/
├── common/ 공통 모듈 (모든 탭에서 공유)
│ ├── components/ auth/, layer/, layout/, map/, ui/
│ ├── hooks/ useAuth, useLayers, useSubMenu
│ ├── services/ api.ts (Axios 인스턴스), authApi.ts
│ ├── store/ authStore (Zustand), menuStore
│ ├── types/ backtrack, boomLine, hns, navigation
│ └── utils/ coordinates, geo, sanitize
└── tabs/ 탭 단위 패키지 (11개)
├── prediction/ 확산 예측
├── hns/ HNS 분석
├── rescue/ 구조 시나리오
├── aerial/ 항공 방제
├── weather/ 해양 기상
├── incidents/ 사건/사고 관리
├── board/ 게시판
├── reports/ 보고서
├── assets/ 자산 관리
├── scat/ Pre-SCAT 조사
└── admin/ 관리자
각 탭 내부 구조 패턴:
tabs/{탭명}/
├── components/ UI 컴포넌트
│ ├── {Tab}View.tsx 메인 뷰 (App.tsx에서 라우팅)
│ ├── {Tab}LeftPanel.tsx
│ └── {Tab}RightPanel.tsx
├── services/ API 호출
│ └── {탭명}Api.ts
├── hooks/ 탭 전용 훅 (선택)
└── types/ 탭 전용 타입 (선택)
5-2. Backend 개발 구조
backend/src/
├── server.ts 진입점 (미들웨어, 라우터 등록)
├── auth/ 인증 (JWT, OAuth, 미들웨어)
│ ├── authRouter.ts POST /api/auth/login, /api/auth/logout 등
│ ├── authService.ts
│ └── authMiddleware.ts requireAuth, requireRole, requirePermission
├── users/ 사용자 관리
├── roles/ 역할/권한
│ └── permResolver.ts 2차원 권한 해석 엔진
├── {도메인}/ 도메인별 모듈
│ ├── {도메인}Router.ts
│ └── {도메인}Service.ts
├── middleware/ 보안 (입력 살균, rate-limit)
└── db/
├── wingDb.ts wing DB Pool
├── authDb.ts wing_auth DB Pool
└── seed.ts 시드 데이터
라우터 작성 패턴:
import { Router } from 'express';
import { requireAuth } from '../auth/authMiddleware.js';
import * as service from './exampleService.js';
const router = Router();
// GET: 목록 조회
router.get('/', requireAuth, async (req, res) => {
try {
const result = await service.getList();
res.json(result);
} catch (err) {
res.status(500).json({ error: '조회 실패' });
}
});
// POST: 생성 (GET/POST only 정책)
router.post('/', requireAuth, async (req, res) => {
try {
const result = await service.create(req.body);
res.json(result);
} catch (err) {
res.status(500).json({ error: '생성 실패' });
}
});
export default router;
HTTP 정책: GET/POST만 사용한다 (보안취약점 가이드 준수, PUT/DELETE 미사용).
5-3. DB 마이그레이션
스키마 변경이 필요한 경우 database/migration/ 디렉토리에 SQL 파일을 추가한다.
database/migration/
├── 001_layer_table.sql
├── 002_hns_substance.sql
├── ...
└── 016_rescue.sql
네이밍: {3자리 번호}_{설명}.sql (예: 017_weather_alert.sql)
-- 017_weather_alert.sql
-- 기상 경보 테이블 추가
CREATE TABLE WTHR_ALRT (
ALRT_SN SERIAL NOT NULL,
ALRT_TP_CD VARCHAR(20) NOT NULL,
ALRT_CN TEXT,
REG_DTM TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT PK_WTHR_ALRT PRIMARY KEY (ALRT_SN)
);
COMMENT ON TABLE WTHR_ALRT IS '기상경보';
COMMENT ON COLUMN WTHR_ALRT.ALRT_SN IS '경보순번';
COMMENT ON COLUMN WTHR_ALRT.ALRT_TP_CD IS '경보유형코드';
COMMENT ON COLUMN WTHR_ALRT.ALRT_CN IS '경보내용';
COMMENT ON COLUMN WTHR_ALRT.REG_DTM IS '등록일시';
DB 표준화: 공공데이터베이스 표준화 관리 매뉴얼(2021.06) 기준을 따른다. 컬럼명은 영문약어명 조합 (30자 이내, 언더스코어 구분).
6. 검증
6-1. TypeScript 타입 체크
# Frontend
cd frontend && npx tsc --noEmit
# Backend
cd backend && npx tsc --noEmit
6-2. ESLint
# Frontend (flat config)
cd frontend && npx eslint src/
# 자동 수정
cd frontend && npx eslint src/ --fix
6-3. Prettier (선택)
cd frontend && npx prettier --check src/
cd frontend && npx prettier --write src/
6-4. 빌드 검증
배포 전 빌드가 성공하는지 확인한다:
# Frontend
cd frontend && npm run build # tsc -b && vite build -> dist/
# Backend
cd backend && npm run build # tsc -> dist/
6-5. 수동 테스트
테스트 프레임워크가 미구성이므로 수동으로 검증한다:
- 개발 서버 실행 (
npm run dev) - 브라우저에서 기능 동작 확인
- API 호출은 브라우저 DevTools Network 탭 또는 curl로 확인
# API 헬스 체크
curl http://localhost:3001/health
# 인증이 필요한 API 테스트 (쿠키 기반)
curl -b "WING_SESSION=<JWT토큰>" http://localhost:3001/api/users
7. 커밋 & 푸시
7-1. Conventional Commits 형식
type(scope): subject
| type | 용도 | 예시 |
|---|---|---|
feat |
새 기능 | feat(auth): Google OAuth 로그인 추가 |
fix |
버그 수정 | fix(map): 레이어 겹침 오류 수정 |
refactor |
리팩토링 | refactor(api): 중복 호출 제거 |
docs |
문서 | docs: API 엔드포인트 문서 추가 |
chore |
설정/빌드 | chore: 의존성 버전 업데이트 |
style |
포맷팅 | style: ESLint 경고 수정 |
test |
테스트 | test(auth): 로그인 단위 테스트 추가 |
ci |
CI/CD | ci: 백엔드 빌드 스텝 추가 |
perf |
성능 개선 | perf(map): 레이어 렌더링 최적화 |
규칙:
type필수,scope선택 (한/영 모두 가능)subject72자 이내, 마침표 없이 끝냄- 한국어, 영어 모두 허용
7-2. pre-commit Hook
커밋 시 .githooks/pre-commit이 자동 실행되어 다음 항목을 검증한다:
[1] Frontend TypeScript 타입 체크 (tsc --noEmit)
[2] Frontend ESLint 검증 (eslint src/ --quiet)
[3] Backend TypeScript 타입 체크 (tsc --noEmit)
하나라도 실패하면 커밋이 차단된다.
pre-commit: [frontend] TypeScript 타입 체크 중...
pre-commit: [frontend] 타입 체크 성공
pre-commit: [frontend] ESLint 검증 중...
pre-commit: [frontend] ESLint 통과
pre-commit: [backend] TypeScript 타입 체크 중...
pre-commit: [backend] 타입 체크 성공
실패 예시:
╔══════════════════════════════════════════════════════════╗
║ [frontend] TypeScript 타입 에러! 커밋이 차단됩니다. ║
╚══════════════════════════════════════════════════════════╝
타입/린트 에러를 수정한 후 다시 커밋해주세요.
7-3. commit-msg Hook
커밋 메시지가 Conventional Commits 형식인지 검증한다:
- 통과:
feat(auth): JWT 기반 로그인 구현 - 차단:
로그인 기능 추가(type 누락) - 예외:
Merge ...,Revert ...커밋은 검증 건너뜀
7-4. 커밋 & 푸시 예시
# 변경 파일 확인
git status
# 스테이징 (파일 지정)
git add frontend/src/tabs/incidents/components/IncidentDetailView.tsx
git add backend/src/incidents/incidentService.ts
# 커밋 (pre-commit + commit-msg 검증 자동 실행)
git commit -m "feat(incidents): 사고 상세 조회 페이지 추가"
# 푸시
git push -u origin feature/ISSUE-42-incident-detail
8. MR(Merge Request)
8-1. MR 생성 (feature -> develop)
Gitea Web UI:
- https://gitea.gc-si.dev/gc/wing-ops 접속
- "New Pull Request" 클릭
- Base:
develop, Compare:feature/ISSUE-42-incident-detail - 제목/본문 작성 후 생성
CLI (gh 또는 tea):
# Gitea API로 MR 생성
curl -X POST "https://gitea.gc-si.dev/api/v1/repos/gc/wing-ops/pulls" \
-H "Authorization: token <API_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"title": "feat(incidents): 사고 상세 조회 페이지 추가",
"body": "## Summary\n- 사고 상세 조회 페이지 추가\n- 사고 목록에서 클릭 시 상세 정보 표시",
"base": "develop",
"head": "feature/ISSUE-42-incident-detail"
}'
8-2. MR 본문 템플릿
## Summary
- 변경 내용을 1~3줄로 요약
## 변경 파일
- `frontend/src/tabs/incidents/components/IncidentDetailView.tsx` (신규)
- `backend/src/incidents/incidentService.ts` (수정)
## Test plan
- [ ] 사고 목록에서 상세 조회 클릭 확인
- [ ] 데이터 없을 때 빈 화면 처리 확인
- [ ] API 에러 시 에러 메시지 표시 확인
8-3. MR 리뷰 & 머지
- 리뷰어 지정: 최소 1명 승인 필수
- CI 통과: 자동 검증이 설정된 경우 통과 필수
- 리뷰 코멘트: 모두 해결 후 머지
- 머지 방식: Squash Merge 권장 (깔끔한 히스토리)
- 소스 브랜치: 머지 후 삭제
8-4. 머지 후 로컬 동기화
git checkout develop
git pull origin develop
# 작업했던 feature 브랜치 삭제
git branch -d feature/ISSUE-42-incident-detail
9. 릴리즈 & 배포
9-1. 릴리즈 MR (develop -> main)
develop에 기능이 머지된 후, 배포를 위해 main으로 릴리즈 MR을 생성한다.
## Release
### 포함 기능
1. feat(incidents): 사고 상세 조회 페이지 추가
2. fix(map): 레이어 오류 수정
### 배포 전 확인
- [ ] 로컬 빌드 성공 (frontend + backend)
- [ ] DB 마이그레이션 적용 완료 (해당 시)
- [ ] 서버 환경변수 설정 완료 (해당 시)
9-2. 자동 배포 (Gitea Actions)
main에 머지되면 .gitea/workflows/deploy.yml이 자동 실행된다:
main 브랜치 push
|
[1. Checkout] 소스 코드 체크아웃
|
[2. Setup Node.js] Node.js 24 설정
|
[3. npm registry] Nexus 프록시 설정
|
[4. Frontend] npm ci -> vite build -> /deploy/wing-demo/
|
[5. Backend] npm ci -> tsc -> npm prune --omit=dev
| -> /deploy/wing-demo-backend/
|
[6. Trigger] .deploy-trigger 생성 -> 서비스 재시작
9-3. 배포 환경
| 항목 | 값 |
|---|---|
| 프론트엔드 | https://wing-demo.gc-si.dev |
| 백엔드 API | https://wing-demo.gc-si.dev/api/ |
| DB | PostgreSQL 16 + PostGIS (211.208.115.83:5432) |
| 서버 OS | Rocky Linux 9.6 |
| 프로세스 관리 | systemd (wing-demo-api.service) |
9-4. 배포 확인
# 프론트엔드 응답 확인
curl -s -o /dev/null -w '%{http_code}' https://wing-demo.gc-si.dev/
# 백엔드 헬스 체크
curl -s https://wing-demo.gc-si.dev/api/health
# 기대: {"status":"ok"}
# 백엔드 API 정보
curl -s https://wing-demo.gc-si.dev/api/
# 기대: {"name":"WING Backend API","version":"1.0.0","status":"running"}
9-5. 환경변수 관리
| 위치 | 용도 | 예시 |
|---|---|---|
| systemd 서비스 파일 | 서버 런타임 환경변수 | DB 접속 정보, JWT_SECRET |
| Gitea Secrets | CI/CD 빌드 시 환경변수 | NEXUS_NPM_AUTH, GOOGLE_CLIENT_ID |
Gitea Secret 등록:
Settings -> Actions -> Secrets -> Add Secret
10. 디버깅 팁
10-1. Frontend
Vite 개발 서버 프록시 문제:
- API 호출이 CORS 에러를 발생시키면 백엔드
FRONTEND_URL환경변수를 확인한다. - 개발 환경에서는
localhost:5173,localhost:5174,localhost:3000이 자동 허용된다. - KHOA 해양 이미지(
/daily_ocean)는 Vite 프록시 경유:vite.config.ts→proxy설정 확인
타입 에러:
# 전체 타입 체크
cd frontend && npx tsc --noEmit
# 특정 파일만 확인하고 싶으면 IDE의 TypeScript 서버 출력을 확인한다
상태 관리 디버깅:
- Zustand DevTools: 브라우저 확장에서 스토어 상태 확인 가능
- React Query DevTools: TanStack Query의 캐시 상태 확인
10-2. Backend
DB 연결 실패:
# PostgreSQL 접속 테스트
psql -h 211.208.115.83 -p 5432 -U wing -d wing -c "SELECT 1"
# 방화벽 확인
nc -zv 211.208.115.83 5432
API 디버깅:
# 헬스 체크
curl http://localhost:3001/health
# 인증 테스트 (로그인 후 쿠키 확인)
curl -c cookies.txt -X POST http://localhost:3001/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"test@test.com","password":"test1234"}'
# 인증이 필요한 API
curl -b cookies.txt http://localhost:3001/api/users
Rate Limit:
- 일반 API: 15분당 IP당 200회
- 시뮬레이션 API: 1분당 IP당 10회
- 초과 시
429 Too Many Requests응답
10-3. 공통
포트 충돌:
# 사용 중인 포트 확인
lsof -i :5173 # Frontend
lsof -i :3001 # Backend
# 프로세스 종료
kill -9 <PID>
node_modules 문제:
# 의존성 초기화
rm -rf node_modules package-lock.json
npm install
Git Hooks가 동작하지 않을 때:
# hooks 경로 확인
git config core.hooksPath
# 출력이 .githooks가 아니면 재설정
git config core.hooksPath .githooks
# 실행 권한 확인
chmod +x .githooks/pre-commit .githooks/commit-msg
11. 실전 예시: 기능 추가 A to Z
시나리오: "사고 상세 조회 페이지 추가"
Step 1. 계획
수정/생성 파일 식별:
| 파일 | 변경 |
|---|---|
database/migration/017_incident_detail.sql |
DB 마이그레이션 (필요 시) |
backend/src/incidents/incidentService.ts |
상세 조회 함수 추가 |
backend/src/incidents/incidentRouter.ts |
GET /api/incidents/:id 라우트 |
frontend/src/tabs/incidents/services/incidentsApi.ts |
API 호출 함수 |
frontend/src/tabs/incidents/components/IncidentDetailView.tsx |
상세 뷰 컴포넌트 |
Step 2. 브랜치 생성
git checkout develop
git pull origin develop
git checkout -b feature/ISSUE-42-incident-detail
Step 3. 개발
Backend - Service:
// backend/src/incidents/incidentService.ts
export async function getIncidentById(id: number) {
const { rows } = await wingPool.query(
'SELECT * FROM ACDNT WHERE ACDNT_SN = $1',
[id]
);
return rows[0] || null;
}
Backend - Router:
// backend/src/incidents/incidentRouter.ts
router.get('/:id', requireAuth, async (req, res) => {
const id = Number(req.params.id);
const incident = await getIncidentById(id);
if (!incident) {
return res.status(404).json({ error: '사고를 찾을 수 없습니다.' });
}
res.json(incident);
});
Frontend - API:
// frontend/src/tabs/incidents/services/incidentsApi.ts
export async function fetchIncidentById(id: number) {
const { data } = await api.get(`/incidents/${id}`);
return data;
}
Frontend - Component:
// frontend/src/tabs/incidents/components/IncidentDetailView.tsx
const IncidentDetailView = ({ incidentId }: IncidentDetailViewProps) => {
const { data, isLoading } = useQuery({
queryKey: ['incident', incidentId],
queryFn: () => fetchIncidentById(incidentId),
});
if (isLoading) return <div>Loading...</div>;
// ...렌더링
};
Step 4. 검증
cd frontend && npx tsc --noEmit && npx eslint src/
cd ../backend && npx tsc --noEmit
Step 5. 커밋 & 푸시
git add backend/src/incidents/ frontend/src/tabs/incidents/
git commit -m "feat(incidents): 사고 상세 조회 페이지 추가"
# pre-commit: TypeScript OK, ESLint OK
# commit-msg: Conventional Commits OK
git push -u origin feature/ISSUE-42-incident-detail
Step 6. MR 생성 & 리뷰
- Gitea에서
feature/ISSUE-42-incident-detail -> developMR 생성 - 리뷰어 승인 후 Squash Merge
- 소스 브랜치 삭제
Step 7. 릴리즈 & 배포
develop -> main릴리즈 MR 생성- 머지 -> Gitea Actions 자동 배포
- https://wing-demo.gc-si.dev 에서 확인
Step 8. 로컬 동기화
git checkout develop
git pull origin develop
git branch -d feature/ISSUE-42-incident-detail