refactor: Phase 1 - 긴급 보안 및 중복 제거
## 🚨 보안 강화 - 하드코딩된 비밀번호를 환경변수로 전환 - .env.example 생성 및 보안 가이드 추가 - docker-compose.yml 환경변수 적용 - README.md에서 실제 비밀번호 제거 ## 🗑️ 중복 제거 - synology_deployment/ 디렉토리 제거 (268MB) - synology_deployment*.tar.gz 아카이브 제거 (234MB) - 총 502MB의 중복 파일 삭제 ## 🧹 백업 파일 정리 - *.backup 파일 제거 (10개) - *복사본* 파일 제거 - *이전* 파일 제거 - json(백업)/ 디렉토리 제거 ## 📋 .gitignore 업데이트 - 백업 파일 패턴 추가 - 보안 파일 제외 (.env, *.pem, *.key) - 임시 파일 제외 (*.tmp, *.new) - 빌드 아티팩트 제외 (*.tar.gz) ## 📚 문서화 - docs/ 디렉토리 구조 생성 - 리팩토링 분석 및 계획 문서 작성 - 코딩 스타일 가이드 작성 - 개발 환경 설정 가이드 작성 - 시스템 아키텍처 문서 작성 ## 변경된 파일 - .env.example (신규) - .gitignore (업데이트) - docker-compose.yml (환경변수 적용) - README.md (보안 정보 제거) - docs/* (신규 문서 7개) ## 보안 개선 효과 ✅ 비밀번호 노출 위험 제거 ✅ Git 히스토리에서 민감 정보 분리 ✅ 환경별 설정 분리 가능 ✅ 배포 보안 강화 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
89
docs/README.md
Normal file
89
docs/README.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# TK-FB-Project 개발 문서
|
||||
|
||||
## 📚 문서 구조
|
||||
|
||||
이 디렉토리는 TK-FB-Project의 개발 및 유지보수를 위한 종합 문서 저장소입니다.
|
||||
|
||||
### 디렉토리 구조
|
||||
|
||||
```
|
||||
docs/
|
||||
├── README.md # 이 파일 - 문서 인덱스
|
||||
├── architecture/ # 아키텍처 관련 문서
|
||||
│ ├── OVERVIEW.md # 시스템 아키텍처 개요
|
||||
│ ├── DATABASE.md # 데이터베이스 스키마 및 설계
|
||||
│ └── API_DESIGN.md # API 설계 원칙
|
||||
├── refactoring/ # 리팩토링 관련 문서
|
||||
│ ├── PLAN.md # 리팩토링 계획 및 로드맵
|
||||
│ ├── LOG.md # 리팩토링 작업 로그
|
||||
│ └── ANALYSIS.md # 코드 분석 리포트
|
||||
├── guides/ # 개발 가이드
|
||||
│ ├── SETUP.md # 개발 환경 설정
|
||||
│ ├── CODING_STYLE.md # 코딩 스타일 가이드
|
||||
│ ├── GIT_WORKFLOW.md # Git 워크플로우
|
||||
│ ├── SECURITY.md # 보안 가이드라인
|
||||
│ └── TROUBLESHOOTING.md # 문제 해결 가이드
|
||||
└── api/ # API 문서
|
||||
├── ENDPOINTS.md # API 엔드포인트 목록
|
||||
├── AUTHENTICATION.md # 인증 및 권한
|
||||
└── EXAMPLES.md # API 사용 예제
|
||||
```
|
||||
|
||||
## 📖 주요 문서
|
||||
|
||||
### 시작하기
|
||||
- [개발 환경 설정](guides/SETUP.md) - 프로젝트 설정 및 실행 방법
|
||||
- [시스템 아키텍처](architecture/OVERVIEW.md) - 전체 시스템 구조 이해
|
||||
- [코딩 스타일 가이드](guides/CODING_STYLE.md) - 코드 작성 규칙
|
||||
|
||||
### 개발
|
||||
- [API 문서](api/ENDPOINTS.md) - REST API 엔드포인트 레퍼런스
|
||||
- [데이터베이스 스키마](architecture/DATABASE.md) - DB 구조 및 관계
|
||||
- [문제 해결](guides/TROUBLESHOOTING.md) - 자주 발생하는 문제 해결법
|
||||
|
||||
### 리팩토링
|
||||
- [리팩토링 계획](refactoring/PLAN.md) - 개선 로드맵
|
||||
- [리팩토링 로그](refactoring/LOG.md) - 변경 이력
|
||||
- [코드 분석](refactoring/ANALYSIS.md) - 현재 코드베이스 분석
|
||||
|
||||
## 🔄 문서 업데이트 규칙
|
||||
|
||||
1. **리팩토링 시**: 변경 사항을 `refactoring/LOG.md`에 기록
|
||||
2. **API 변경 시**: `api/ENDPOINTS.md` 업데이트
|
||||
3. **아키텍처 변경 시**: 관련 다이어그램 및 문서 갱신
|
||||
4. **새로운 기능 추가 시**: 해당 가이드 문서 작성
|
||||
|
||||
## 📝 문서 작성 가이드
|
||||
|
||||
### 문서 작성 원칙
|
||||
- **명확성**: 기술 용어는 쉽게 설명
|
||||
- **최신성**: 코드 변경 시 즉시 업데이트
|
||||
- **완결성**: 독립적으로 이해 가능하도록 작성
|
||||
- **예제 포함**: 코드 예제와 스크린샷 활용
|
||||
|
||||
### 마크다운 스타일
|
||||
```markdown
|
||||
# H1: 문서 제목 (한 문서에 한 개만)
|
||||
## H2: 주요 섹션
|
||||
### H3: 하위 섹션
|
||||
|
||||
- 리스트 사용
|
||||
- 코드 블록 활용
|
||||
- 테이블로 정보 정리
|
||||
```
|
||||
|
||||
## 🔗 외부 리소스
|
||||
|
||||
- [프로젝트 README](../README.md)
|
||||
- [데이터베이스 스키마](../DATABASE_SCHEMA.md)
|
||||
- [MySQL 호환성 노트](../MYSQL_COMPATIBILITY_NOTES.md)
|
||||
|
||||
## 📅 문서 이력
|
||||
|
||||
| 날짜 | 버전 | 변경 내용 | 작성자 |
|
||||
|------|------|----------|--------|
|
||||
| 2025-12-11 | 1.0 | 문서 구조 초기 생성 | Claude Code |
|
||||
|
||||
## 📧 문의
|
||||
|
||||
문서 관련 질문이나 개선 제안은 프로젝트 관리자에게 문의하세요.
|
||||
624
docs/architecture/OVERVIEW.md
Normal file
624
docs/architecture/OVERVIEW.md
Normal file
@@ -0,0 +1,624 @@
|
||||
# 시스템 아키텍처 개요
|
||||
|
||||
> TK-FB-Project 시스템 아키텍처 및 설계 문서
|
||||
|
||||
## 📊 시스템 개요
|
||||
|
||||
TK-FB-Project는 Technical Korea의 **일일 작업 관리 시스템**으로, 작업자들의 일일 업무를 기록하고 관리하는 웹 기반 애플리케이션입니다.
|
||||
|
||||
### 주요 기능
|
||||
1. **작업 보고서 관리**: 일일 작업 내용 기록 및 조회
|
||||
2. **작업자 관리**: 작업자 정보 및 권한 관리
|
||||
3. **프로젝트 관리**: 프로젝트별 작업 추적
|
||||
4. **근태 관리**: 출퇴근 및 휴가 관리
|
||||
5. **대시보드**: 작업 통계 및 분석
|
||||
6. **이슈 관리**: 작업 중 발생한 이슈 추적
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ 아키텍처 다이어그램
|
||||
|
||||
### 전체 시스템 구조
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 클라이언트 │
|
||||
│ (Web Browser) │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Dashboard │ │ Calendar │ │ Report │ │
|
||||
│ │ (HTML/ │ │ (HTML/ │ │ (HTML/ │ │
|
||||
│ │ CSS/JS) │ │ CSS/JS) │ │ CSS/JS) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ HTTP/HTTPS
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Nginx (Port 8080) │
|
||||
│ (Static File Server) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ Reverse Proxy
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ API Server (Port 20005) │
|
||||
│ Node.js + Express │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Controllers │ │ Services │ │ Models │ │
|
||||
│ │ (Routes) │ │ (Business │ │ (Data │ │
|
||||
│ │ │ │ Logic) │ │ Access) │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Middlewares │ │ Utilities │ │
|
||||
│ │ (Auth, CORS) │ │ (Logger) │ │
|
||||
│ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
│ MySQL Protocol
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ MySQL 8.0 (Port 3306) │
|
||||
│ (Database) │
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ users │ │ workers │ │ projects │ │
|
||||
│ ├──────────────┤ ├──────────────┤ ├──────────────┤ │
|
||||
│ │daily_work_ │ │ attendance │ │ issues │ │
|
||||
│ │ reports │ │ _records │ │ │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 배포 구조 (Docker Compose)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Docker Host (Synology NAS) │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ Container: web-ui (Nginx) │ │
|
||||
│ │ Port: 8080:80 │ │
|
||||
│ │ Volume: ./web-ui:/usr/share/nginx/html │ │
|
||||
│ └────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ Container: api (Node.js) │ │
|
||||
│ │ Port: 20005:20005 │ │
|
||||
│ │ Volume: ./api.hyungi.net:/app │ │
|
||||
│ └────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ Container: db (MySQL 8.0) │ │
|
||||
│ │ Port: 3306:3306 │ │
|
||||
│ │ Volume: mysql_data:/var/lib/mysql │ │
|
||||
│ └────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌────────────────────────────────────────────────────┐ │
|
||||
│ │ Container: fastapi-bridge (Python) │ │
|
||||
│ │ Port: 8000:8000 │ │
|
||||
│ │ (Optional - AI 기능용) │ │
|
||||
│ └────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 기술 스택
|
||||
|
||||
### 프론트엔드
|
||||
| 기술 | 버전 | 용도 |
|
||||
|------|------|------|
|
||||
| HTML5 | - | 마크업 |
|
||||
| CSS3 | - | 스타일링 |
|
||||
| JavaScript (ES6+) | - | 클라이언트 로직 |
|
||||
| FullCalendar | 6.x | 캘린더 UI |
|
||||
| Chart.js | 3.x | 차트 시각화 |
|
||||
| jQuery | 3.x | DOM 조작 (레거시) |
|
||||
|
||||
### 백엔드
|
||||
| 기술 | 버전 | 용도 |
|
||||
|------|------|------|
|
||||
| Node.js | 20.x | 런타임 |
|
||||
| Express | 4.18+ | 웹 프레임워크 |
|
||||
| MySQL2 | 3.6+ | 데이터베이스 드라이버 |
|
||||
| JWT | 9.0+ | 인증 |
|
||||
| bcryptjs | 2.4+ | 비밀번호 해싱 |
|
||||
| dotenv | 16.3+ | 환경변수 관리 |
|
||||
|
||||
### 데이터베이스
|
||||
| 기술 | 버전 | 용도 |
|
||||
|------|------|------|
|
||||
| MySQL | 8.0+ | 관계형 데이터베이스 |
|
||||
|
||||
### 인프라
|
||||
| 기술 | 버전 | 용도 |
|
||||
|------|------|------|
|
||||
| Docker | 24+ | 컨테이너화 |
|
||||
| Docker Compose | 2.x | 오케스트레이션 |
|
||||
| Nginx | 1.25+ | 웹 서버 / 리버스 프록시 |
|
||||
|
||||
### 추가 도구
|
||||
| 기술 | 용도 |
|
||||
|------|------|
|
||||
| Python FastAPI | AI 기능 브릿지 (선택) |
|
||||
| OpenAI API | 작업 내용 분석 (선택) |
|
||||
|
||||
---
|
||||
|
||||
## 📂 디렉토리 구조
|
||||
|
||||
### 현재 구조
|
||||
|
||||
```
|
||||
TK-FB-Project/
|
||||
├── api.hyungi.net/ # 백엔드 API 서버
|
||||
│ ├── controllers/ # 컨트롤러 (라우트 핸들러)
|
||||
│ │ ├── dailyWorkReportController.js
|
||||
│ │ ├── workerController.js
|
||||
│ │ ├── projectController.js
|
||||
│ │ └── ...
|
||||
│ ├── models/ # 데이터 모델
|
||||
│ │ ├── dailyWorkReportModel.js
|
||||
│ │ ├── workerModel.js
|
||||
│ │ └── ...
|
||||
│ ├── routes/ # 라우트 정의
|
||||
│ │ ├── dailyWorkReportRoutes.js
|
||||
│ │ ├── workerRoutes.js
|
||||
│ │ └── ...
|
||||
│ ├── services/ # 비즈니스 로직 (일부)
|
||||
│ │ ├── authService.js
|
||||
│ │ ├── emailService.js
|
||||
│ │ └── ...
|
||||
│ ├── middleware/ # 미들웨어
|
||||
│ │ ├── errorMiddleware.js
|
||||
│ │ └── uploadMiddleware.js
|
||||
│ ├── utils/ # 유틸리티
|
||||
│ │ └── logger.js
|
||||
│ ├── uploads/ # 업로드 파일 저장소
|
||||
│ ├── index.js # 애플리케이션 진입점
|
||||
│ ├── package.json
|
||||
│ └── .env # 환경 변수
|
||||
│
|
||||
├── web-ui/ # 프론트엔드
|
||||
│ ├── pages/ # HTML 페이지 (51개)
|
||||
│ │ ├── index.html
|
||||
│ │ ├── daily-work-report.html
|
||||
│ │ ├── work-report-calendar.html
|
||||
│ │ └── ...
|
||||
│ ├── js/ # JavaScript (49개)
|
||||
│ │ ├── api-config.js
|
||||
│ │ ├── daily-work-report.js
|
||||
│ │ ├── work-report-calendar.js
|
||||
│ │ └── ...
|
||||
│ ├── css/ # CSS (22개)
|
||||
│ │ ├── common.css
|
||||
│ │ ├── daily-work-report.css
|
||||
│ │ └── ...
|
||||
│ └── assets/ # 정적 리소스
|
||||
│ └── images/
|
||||
│
|
||||
├── fastapi-bridge/ # Python FastAPI (선택)
|
||||
│ ├── main.py
|
||||
│ ├── requirements.txt
|
||||
│ └── ...
|
||||
│
|
||||
├── docs/ # 📚 프로젝트 문서
|
||||
│ ├── README.md
|
||||
│ ├── architecture/
|
||||
│ │ └── OVERVIEW.md # 이 파일
|
||||
│ ├── refactoring/
|
||||
│ │ ├── ANALYSIS.md
|
||||
│ │ ├── PLAN.md
|
||||
│ │ └── LOG.md
|
||||
│ ├── guides/
|
||||
│ │ ├── SETUP.md
|
||||
│ │ └── CODING_STYLE.md
|
||||
│ └── api/
|
||||
│
|
||||
├── docker-compose.yml # Docker Compose 설정
|
||||
├── .gitignore
|
||||
├── README.md
|
||||
└── package.json
|
||||
```
|
||||
|
||||
### 목표 구조 (리팩토링 후)
|
||||
|
||||
```
|
||||
TK-FB-Project/
|
||||
├── api/ # 백엔드 (이름 변경)
|
||||
│ ├── src/
|
||||
│ │ ├── config/ # 설정 파일
|
||||
│ │ │ ├── database.js
|
||||
│ │ │ ├── cors.js
|
||||
│ │ │ └── routes.js
|
||||
│ │ ├── controllers/ # 라우트 핸들러만
|
||||
│ │ │ └── ...
|
||||
│ │ ├── services/ # 비즈니스 로직 (완성)
|
||||
│ │ │ └── ...
|
||||
│ │ ├── repositories/ # 데이터 접근 계층 (신규)
|
||||
│ │ │ └── ...
|
||||
│ │ ├── models/ # 데이터 모델 / 스키마
|
||||
│ │ │ └── ...
|
||||
│ │ ├── middlewares/ # 미들웨어
|
||||
│ │ │ ├── auth.js
|
||||
│ │ │ ├── permission.js
|
||||
│ │ │ └── errorHandler.js
|
||||
│ │ ├── utils/ # 유틸리티
|
||||
│ │ │ ├── errors.js
|
||||
│ │ │ ├── logger.js
|
||||
│ │ │ └── validator.js
|
||||
│ │ └── index.js # 진입점 (간결화)
|
||||
│ ├── tests/ # 테스트
|
||||
│ │ ├── unit/
|
||||
│ │ └── integration/
|
||||
│ └── package.json
|
||||
│
|
||||
├── web/ # 프론트엔드 (이름 변경)
|
||||
│ ├── public/ # 정적 파일
|
||||
│ │ └── assets/
|
||||
│ ├── src/
|
||||
│ │ ├── modules/ # 모듈화된 코드
|
||||
│ │ │ ├── common/
|
||||
│ │ │ │ ├── api-client.js
|
||||
│ │ │ │ ├── utils.js
|
||||
│ │ │ │ └── validator.js
|
||||
│ │ │ ├── calendar/
|
||||
│ │ │ │ ├── CalendarView.js
|
||||
│ │ │ │ ├── CalendarAPI.js
|
||||
│ │ │ │ └── CalendarUtils.js
|
||||
│ │ │ └── dashboard/
|
||||
│ │ ├── pages/ # 페이지별 스크립트
|
||||
│ │ └── styles/ # CSS
|
||||
│ │ ├── base/
|
||||
│ │ ├── components/
|
||||
│ │ ├── layouts/
|
||||
│ │ └── pages/
|
||||
│ └── index.html
|
||||
│
|
||||
├── database/ # 데이터베이스 관련
|
||||
│ ├── migrations/ # 마이그레이션
|
||||
│ ├── seeds/ # 시드 데이터
|
||||
│ └── schema/ # 스키마 정의
|
||||
│
|
||||
├── deployment/ # 배포 스크립트
|
||||
│ ├── docker/
|
||||
│ │ ├── api.Dockerfile
|
||||
│ │ ├── web.Dockerfile
|
||||
│ │ └── nginx.conf
|
||||
│ ├── docker-compose.yml
|
||||
│ ├── docker-compose.dev.yml
|
||||
│ └── deploy.sh
|
||||
│
|
||||
├── docs/ # 문서
|
||||
└── scripts/ # 유틸리티 스크립트
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 데이터 흐름
|
||||
|
||||
### 1. 사용자 인증 플로우
|
||||
|
||||
```
|
||||
┌──────┐ ┌──────┐ ┌──────────┐ ┌──────┐
|
||||
│Client│ │ API │ │ Service │ │ DB │
|
||||
└──┬───┘ └──┬───┘ └────┬─────┘ └──┬───┘
|
||||
│ │ │ │
|
||||
│ POST /login │ │ │
|
||||
├────────────>│ │ │
|
||||
│ │ validate │ │
|
||||
│ ├───────────────>│ │
|
||||
│ │ │ findByUsername│
|
||||
│ │ ├──────────────>│
|
||||
│ │ │<──────────────┤
|
||||
│ │ │ user data │
|
||||
│ │ verify pwd │ │
|
||||
│ │<───────────────┤ │
|
||||
│ │ generate JWT │ │
|
||||
│ ├───────────────>│ │
|
||||
│ JWT Token│ │ │
|
||||
│<────────────┤ │ │
|
||||
│ │ │ │
|
||||
└─────────────┴────────────────┴───────────────┘
|
||||
```
|
||||
|
||||
### 2. 작업 보고서 생성 플로우
|
||||
|
||||
```
|
||||
┌──────┐ ┌─────┐ ┌──────────┐ ┌─────────┐ ┌────┐
|
||||
│Client│ │ API │ │ Service │ │ Repo │ │ DB │
|
||||
└──┬───┘ └──┬──┘ └────┬─────┘ └────┬────┘ └─┬──┘
|
||||
│ │ │ │ │
|
||||
│POST /reports │ │ │
|
||||
├──────────>│ │ │ │
|
||||
│ │ auth check │ │ │
|
||||
│ │ │ │ │
|
||||
│ │ validate │ │ │
|
||||
│ ├───────────>│ │ │
|
||||
│ │ │ checkDup │ │
|
||||
│ │ ├─────────────>│ │
|
||||
│ │ │ │ SELECT │
|
||||
│ │ │ ├─────────>│
|
||||
│ │ │ │<─────────┤
|
||||
│ │ │<─────────────┤ │
|
||||
│ │ │ create │ │
|
||||
│ │ ├─────────────>│ │
|
||||
│ │ │ │ INSERT │
|
||||
│ │ │ ├─────────>│
|
||||
│ │ │ │<─────────┤
|
||||
│ │ │<─────────────┤ │
|
||||
│ │ │ notify │ │
|
||||
│ │ │ (email/push) │ │
|
||||
│ │<───────────┤ │ │
|
||||
│<──────────┤ │ │ │
|
||||
│ 201 │ │ │ │
|
||||
└───────────┴────────────┴──────────────┴──────────┘
|
||||
```
|
||||
|
||||
### 3. 대시보드 데이터 조회 플로우
|
||||
|
||||
```
|
||||
Client → API → Service → Repository → DB
|
||||
↓
|
||||
Cache (Redis - 향후 도입 예정)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 보안 아키텍처
|
||||
|
||||
### 인증 (Authentication)
|
||||
|
||||
**JWT 기반 인증**:
|
||||
```javascript
|
||||
// 로그인 성공 시
|
||||
const token = jwt.sign(
|
||||
{
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
access_level: user.access_level
|
||||
},
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '24h' }
|
||||
);
|
||||
|
||||
// 요청 시 헤더에 포함
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
**토큰 검증 플로우**:
|
||||
```
|
||||
Request → Auth Middleware → Verify JWT → Attach user to req → Next
|
||||
↓ (invalid)
|
||||
401 Unauthorized
|
||||
```
|
||||
|
||||
### 권한 (Authorization)
|
||||
|
||||
**역할 기반 접근 제어 (RBAC)**:
|
||||
|
||||
| 역할 | 권한 |
|
||||
|------|------|
|
||||
| `system` | 모든 권한 (시스템 관리자) |
|
||||
| `admin` | 전체 데이터 조회 및 수정 |
|
||||
| `group_leader` | 그룹 내 데이터 관리 |
|
||||
| `worker` | 본인 데이터만 조회/수정 |
|
||||
|
||||
**권한 체크 미들웨어** (목표):
|
||||
```javascript
|
||||
// 특정 역할 필요
|
||||
router.get('/admin', requireRole('admin', 'system'), getAdminData);
|
||||
|
||||
// 본인 또는 관리자
|
||||
router.put('/reports/:id', requireOwnerOrAdmin, updateReport);
|
||||
```
|
||||
|
||||
### 데이터 보안
|
||||
|
||||
1. **비밀번호**: bcrypt로 해싱 (salt rounds: 10)
|
||||
2. **환경변수**: .env 파일 (Git 제외)
|
||||
3. **SQL Injection**: Prepared Statements 사용
|
||||
4. **XSS**: 입력값 sanitization
|
||||
5. **CORS**: 허용된 origin만 접근
|
||||
|
||||
---
|
||||
|
||||
## 📊 데이터베이스 설계
|
||||
|
||||
### ER 다이어그램
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐
|
||||
│ users │ │ workers │
|
||||
├─────────────┤ ├─────────────┤
|
||||
│ id (PK) │ ┌───>│ id (PK) │
|
||||
│ username │ │ │ name │
|
||||
│ password │ │ │ email │
|
||||
│ access_level│ │ │ position │
|
||||
│ worker_id(FK├────┘ │ is_active │
|
||||
└─────────────┘ └──────┬──────┘
|
||||
│
|
||||
│ 1:N
|
||||
│
|
||||
┌──────▼──────────┐
|
||||
│daily_work_ │
|
||||
│ reports │
|
||||
├─────────────────┤
|
||||
│ id (PK) │
|
||||
│ worker_id (FK) │
|
||||
│ project_id (FK) │
|
||||
│ report_date │
|
||||
│ work_content │
|
||||
│ work_hours │
|
||||
│ created_at │
|
||||
└──────┬──────────┘
|
||||
│ N:1
|
||||
│
|
||||
┌──────▼──────┐
|
||||
│ projects │
|
||||
├─────────────┤
|
||||
│ id (PK) │
|
||||
│ name │
|
||||
│ description │
|
||||
│ start_date │
|
||||
│ end_date │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
### 주요 테이블
|
||||
|
||||
| 테이블명 | 설명 | 주요 컬럼 |
|
||||
|----------|------|-----------|
|
||||
| `users` | 사용자 계정 | id, username, password, access_level |
|
||||
| `workers` | 작업자 정보 | id, name, email, position, group_id |
|
||||
| `daily_work_reports` | 일일 작업 보고서 | id, worker_id, project_id, report_date, work_content |
|
||||
| `projects` | 프로젝트 | id, name, description, start_date, end_date |
|
||||
| `attendance_records` | 근태 기록 | id, worker_id, date, clock_in, clock_out |
|
||||
| `issues` | 이슈 | id, project_id, reporter_id, title, status |
|
||||
| `work_types` | 작업 유형 | id, name, category |
|
||||
| `work_status_types` | 작업 상태 | id, name, color |
|
||||
|
||||
자세한 스키마는 [DATABASE_SCHEMA.md](../../DATABASE_SCHEMA.md) 참조
|
||||
|
||||
---
|
||||
|
||||
## 🚀 배포 프로세스
|
||||
|
||||
### 현재 배포 방식
|
||||
|
||||
```bash
|
||||
# 1. Docker Compose로 배포
|
||||
docker-compose up -d
|
||||
|
||||
# 2. 수동 업데이트
|
||||
docker-compose down
|
||||
git pull origin master
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
### 목표 CI/CD 파이프라인
|
||||
|
||||
```
|
||||
┌────────────┐ ┌────────────┐ ┌────────────┐
|
||||
│ Push │ │ Build │ │ Test │
|
||||
│ to GitHub ├─────>│ & Lint ├─────>│ (Jest) │
|
||||
└────────────┘ └────────────┘ └──────┬─────┘
|
||||
│
|
||||
▼
|
||||
┌────────────┐ ┌────────────┐
|
||||
│ Deploy │<─────│ Security │
|
||||
│ Production │ │ Scan │
|
||||
└────────────┘ └────────────┘
|
||||
```
|
||||
|
||||
**GitHub Actions 예시**:
|
||||
```yaml
|
||||
name: CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '20'
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
- run: npm run lint
|
||||
|
||||
deploy:
|
||||
needs: test
|
||||
if: github.ref == 'refs/heads/master'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Deploy to Synology
|
||||
run: ./deployment/deploy.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 성능 고려사항
|
||||
|
||||
### 현재 성능 이슈
|
||||
|
||||
1. **N+1 쿼리 문제**: JOIN 사용 필요
|
||||
2. **SELECT * 사용**: 명시적 컬럼 지정 필요
|
||||
3. **인덱스 부족**: 주요 컬럼에 인덱스 추가 필요
|
||||
4. **캐싱 없음**: Redis 도입 검토
|
||||
|
||||
### 최적화 계획
|
||||
|
||||
```javascript
|
||||
// 1. 쿼리 최적화
|
||||
// Before: N+1 쿼리
|
||||
const reports = await getReports();
|
||||
for (const report of reports) {
|
||||
report.worker = await getWorker(report.worker_id);
|
||||
}
|
||||
|
||||
// After: JOIN 사용
|
||||
const reports = await db.query(`
|
||||
SELECT r.*, w.name as worker_name
|
||||
FROM daily_work_reports r
|
||||
JOIN workers w ON r.worker_id = w.id
|
||||
`);
|
||||
|
||||
// 2. 인덱스 추가
|
||||
CREATE INDEX idx_report_date ON daily_work_reports(report_date);
|
||||
CREATE INDEX idx_worker_id ON daily_work_reports(worker_id);
|
||||
|
||||
// 3. 캐싱 (Redis - 향후)
|
||||
const cachedData = await redis.get('dashboard:stats');
|
||||
if (cachedData) return JSON.parse(cachedData);
|
||||
|
||||
const data = await fetchDashboardStats();
|
||||
await redis.setex('dashboard:stats', 300, JSON.stringify(data));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔮 향후 계획
|
||||
|
||||
### Phase 1: 안정화 (현재)
|
||||
- 보안 강화
|
||||
- 코드 리팩토링
|
||||
- 테스트 작성
|
||||
|
||||
### Phase 2: 모듈화 (3개월)
|
||||
- 서비스 레이어 완성
|
||||
- 프론트엔드 모듈화
|
||||
- API 문서화
|
||||
|
||||
### Phase 3: 현대화 (6개월)
|
||||
- TypeScript 전환
|
||||
- React/Vue 도입
|
||||
- ORM 적용 (TypeORM/Prisma)
|
||||
|
||||
### Phase 4: 확장 (12개월)
|
||||
- 마이크로서비스 아키텍처
|
||||
- GraphQL API
|
||||
- 실시간 기능 (WebSocket)
|
||||
- 모바일 앱 (React Native)
|
||||
|
||||
---
|
||||
|
||||
## 📚 관련 문서
|
||||
|
||||
- [데이터베이스 스키마](../../DATABASE_SCHEMA.md)
|
||||
- [API 문서](../api/ENDPOINTS.md)
|
||||
- [개발 환경 설정](../guides/SETUP.md)
|
||||
- [리팩토링 계획](../refactoring/PLAN.md)
|
||||
|
||||
---
|
||||
|
||||
*마지막 업데이트: 2025-12-11*
|
||||
686
docs/guides/CODING_STYLE.md
Normal file
686
docs/guides/CODING_STYLE.md
Normal file
@@ -0,0 +1,686 @@
|
||||
# 코딩 스타일 가이드
|
||||
|
||||
> TK-FB-Project 코드 작성 규칙 및 베스트 프랙티스
|
||||
|
||||
## 📚 목차
|
||||
|
||||
1. [일반 원칙](#일반-원칙)
|
||||
2. [JavaScript/Node.js](#javascriptnodejs)
|
||||
3. [HTML/CSS](#htmlcss)
|
||||
4. [SQL](#sql)
|
||||
5. [Git 커밋 메시지](#git-커밋-메시지)
|
||||
6. [파일 및 디렉토리 구조](#파일-및-디렉토리-구조)
|
||||
|
||||
---
|
||||
|
||||
## 일반 원칙
|
||||
|
||||
### 1. 가독성 우선
|
||||
```javascript
|
||||
// ❌ 나쁜 예
|
||||
const x = a.filter(i => i.active).map(i => ({...i, name: i.n}));
|
||||
|
||||
// ✅ 좋은 예
|
||||
const activeWorkers = workers
|
||||
.filter(worker => worker.is_active)
|
||||
.map(worker => ({
|
||||
...worker,
|
||||
name: worker.name
|
||||
}));
|
||||
```
|
||||
|
||||
### 2. 명확한 네이밍
|
||||
```javascript
|
||||
// ❌ 나쁜 예
|
||||
const d = new Date();
|
||||
const arr = [];
|
||||
const temp = getUserData();
|
||||
|
||||
// ✅ 좋은 예
|
||||
const currentDate = new Date();
|
||||
const activeWorkers = [];
|
||||
const userData = getUserData();
|
||||
```
|
||||
|
||||
### 3. 단일 책임 원칙
|
||||
```javascript
|
||||
// ❌ 나쁜 예 - 하나의 함수가 너무 많은 일을 함
|
||||
async function processReport(data) {
|
||||
// 검증
|
||||
if (!data.worker_id) throw new Error('Invalid');
|
||||
// DB 저장
|
||||
await db.query('INSERT INTO ...');
|
||||
// 이메일 발송
|
||||
await sendEmail();
|
||||
// 알림 전송
|
||||
await sendNotification();
|
||||
}
|
||||
|
||||
// ✅ 좋은 예 - 책임 분리
|
||||
async function processReport(data) {
|
||||
validateReport(data);
|
||||
const report = await saveReport(data);
|
||||
await notifyReportCreation(report);
|
||||
return report;
|
||||
}
|
||||
|
||||
function validateReport(data) {
|
||||
if (!data.worker_id) {
|
||||
throw new ValidationError('작업자를 선택해주세요');
|
||||
}
|
||||
}
|
||||
|
||||
async function saveReport(data) {
|
||||
return await reportRepository.create(data);
|
||||
}
|
||||
|
||||
async function notifyReportCreation(report) {
|
||||
await emailService.send(report);
|
||||
await notificationService.send(report);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. DRY (Don't Repeat Yourself)
|
||||
```javascript
|
||||
// ❌ 나쁜 예 - 중복 코드
|
||||
if (!req.user || !['admin', 'system'].includes(req.user.access_level)) {
|
||||
return res.status(403).json({ error: 'Forbidden' });
|
||||
}
|
||||
|
||||
// ✅ 좋은 예 - 재사용 가능한 미들웨어
|
||||
const requireRole = (...roles) => {
|
||||
return (req, res, next) => {
|
||||
if (!req.user || !roles.includes(req.user.access_level)) {
|
||||
throw new ForbiddenError();
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
// 사용
|
||||
router.get('/admin', requireRole('admin', 'system'), getAdminData);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## JavaScript/Node.js
|
||||
|
||||
### 변수 선언
|
||||
|
||||
```javascript
|
||||
// ✅ const 우선, 재할당 필요시 let, var 사용 금지
|
||||
const API_URL = 'http://api.example.com';
|
||||
let currentPage = 1;
|
||||
|
||||
// ❌ var 사용 금지
|
||||
var x = 10; // NO!
|
||||
```
|
||||
|
||||
### 함수 작성
|
||||
|
||||
```javascript
|
||||
// ✅ 화살표 함수 사용 (콜백, 간단한 함수)
|
||||
const double = (n) => n * 2;
|
||||
const sum = (a, b) => a + b;
|
||||
|
||||
// ✅ 일반 함수 (메서드, 복잡한 로직)
|
||||
function calculateTotalHours(reports) {
|
||||
let total = 0;
|
||||
for (const report of reports) {
|
||||
total += report.work_hours;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// ✅ async/await 사용 (Promise보다 선호)
|
||||
async function fetchUserData(userId) {
|
||||
try {
|
||||
const user = await userModel.findById(userId);
|
||||
const reports = await reportModel.findByUser(userId);
|
||||
return { user, reports };
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch user data', { userId, error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 에러 처리
|
||||
|
||||
```javascript
|
||||
// ❌ 나쁜 예
|
||||
try {
|
||||
await saveData();
|
||||
} catch (error) {
|
||||
console.log(error); // 단순 로그만
|
||||
}
|
||||
|
||||
// ✅ 좋은 예
|
||||
try {
|
||||
await saveData(data);
|
||||
} catch (error) {
|
||||
logger.error('데이터 저장 실패', {
|
||||
data,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
throw new AppError('데이터 저장에 실패했습니다', 500);
|
||||
}
|
||||
```
|
||||
|
||||
### 객체 및 배열
|
||||
|
||||
```javascript
|
||||
// ✅ 구조 분해 할당
|
||||
const { name, email, phone } = user;
|
||||
const [first, second, ...rest] = items;
|
||||
|
||||
// ✅ 스프레드 연산자
|
||||
const newUser = { ...user, is_active: true };
|
||||
const allItems = [...items1, ...items2];
|
||||
|
||||
// ✅ 단축 속성
|
||||
const name = 'John';
|
||||
const age = 30;
|
||||
const user = { name, age }; // { name: name, age: age } 대신
|
||||
```
|
||||
|
||||
### 비동기 처리
|
||||
|
||||
```javascript
|
||||
// ❌ 콜백 지옥
|
||||
getData(function(a) {
|
||||
getMoreData(a, function(b) {
|
||||
getMoreData(b, function(c) {
|
||||
console.log(c);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ✅ async/await
|
||||
async function processData() {
|
||||
const a = await getData();
|
||||
const b = await getMoreData(a);
|
||||
const c = await getMoreData(b);
|
||||
return c;
|
||||
}
|
||||
|
||||
// ✅ 병렬 처리가 가능한 경우
|
||||
const [users, projects, reports] = await Promise.all([
|
||||
fetchUsers(),
|
||||
fetchProjects(),
|
||||
fetchReports()
|
||||
]);
|
||||
```
|
||||
|
||||
### 조건문
|
||||
|
||||
```javascript
|
||||
// ✅ Early Return 패턴
|
||||
function processUser(user) {
|
||||
if (!user) {
|
||||
throw new ValidationError('User is required');
|
||||
}
|
||||
|
||||
if (!user.is_active) {
|
||||
throw new ValidationError('User is not active');
|
||||
}
|
||||
|
||||
// 메인 로직
|
||||
return processActiveUser(user);
|
||||
}
|
||||
|
||||
// ❌ 중첩된 조건문
|
||||
function processUser(user) {
|
||||
if (user) {
|
||||
if (user.is_active) {
|
||||
// 메인 로직
|
||||
return processActiveUser(user);
|
||||
} else {
|
||||
throw new Error('Not active');
|
||||
}
|
||||
} else {
|
||||
throw new Error('No user');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 주석
|
||||
|
||||
```javascript
|
||||
// ✅ JSDoc 사용
|
||||
/**
|
||||
* 작업 보고서를 생성합니다
|
||||
* @param {Object} reportData - 보고서 데이터
|
||||
* @param {number} reportData.worker_id - 작업자 ID
|
||||
* @param {string} reportData.work_content - 작업 내용
|
||||
* @param {number} userId - 생성하는 사용자 ID
|
||||
* @returns {Promise<Object>} 생성된 보고서
|
||||
* @throws {ValidationError} 검증 실패 시
|
||||
*/
|
||||
async function createReport(reportData, userId) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// ✅ 복잡한 로직 설명
|
||||
// NOTE: MySQL 8.0에서는 GROUP BY 동작이 다르므로 명시적으로 컬럼 지정
|
||||
const query = `
|
||||
SELECT worker_id, COUNT(*) as count
|
||||
FROM reports
|
||||
GROUP BY worker_id
|
||||
ORDER BY count DESC
|
||||
`;
|
||||
|
||||
// ⚠️ TODO, FIXME 등 명확히 표시
|
||||
// TODO: 캐싱 로직 추가 필요
|
||||
// FIXME: 날짜 범위 검증 개선 필요
|
||||
// HACK: 임시 해결책, 나중에 리팩토링 필요
|
||||
```
|
||||
|
||||
### 모듈 구조
|
||||
|
||||
```javascript
|
||||
// ✅ 명확한 import/export
|
||||
// 파일 상단에 모든 import
|
||||
const express = require('express');
|
||||
const { ValidationError } = require('../utils/errors');
|
||||
const reportService = require('../services/reportService');
|
||||
|
||||
// 함수 정의
|
||||
function createReport(req, res, next) {
|
||||
// ...
|
||||
}
|
||||
|
||||
function getReports(req, res, next) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// 파일 하단에 export
|
||||
module.exports = {
|
||||
createReport,
|
||||
getReports
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTML/CSS
|
||||
|
||||
### HTML
|
||||
|
||||
```html
|
||||
<!-- ✅ 시맨틱 태그 사용 -->
|
||||
<header>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">홈</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section>
|
||||
<article>
|
||||
<h1>제목</h1>
|
||||
<p>내용</p>
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 TK-FB</p>
|
||||
</footer>
|
||||
|
||||
<!-- ✅ 들여쓰기 2칸 -->
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
내용
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ 속성 순서 -->
|
||||
<!-- 1. class 2. id 3. data-* 4. 기타 -->
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
id="submit-btn"
|
||||
data-action="submit"
|
||||
type="submit"
|
||||
disabled
|
||||
>
|
||||
제출
|
||||
</button>
|
||||
```
|
||||
|
||||
### CSS
|
||||
|
||||
```css
|
||||
/* ✅ 클래스 네이밍: BEM 방식 */
|
||||
.block {}
|
||||
.block__element {}
|
||||
.block--modifier {}
|
||||
|
||||
/* 예시 */
|
||||
.card {}
|
||||
.card__header {}
|
||||
.card__body {}
|
||||
.card__footer {}
|
||||
.card--large {}
|
||||
.card--primary {}
|
||||
|
||||
/* ✅ 속성 순서 */
|
||||
.element {
|
||||
/* 1. Positioning */
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
|
||||
/* 2. Box Model */
|
||||
display: block;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
border: 1px solid #000;
|
||||
|
||||
/* 3. Typography */
|
||||
font-family: Arial;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
|
||||
/* 4. Visual */
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
|
||||
/* 5. Other */
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* ✅ CSS 변수 사용 */
|
||||
:root {
|
||||
--color-primary: #007bff;
|
||||
--color-success: #28a745;
|
||||
--spacing-md: 1rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: var(--color-primary);
|
||||
padding: var(--spacing-md);
|
||||
}
|
||||
|
||||
/* ✅ 중첩 최소화 (3단계 이하) */
|
||||
.nav {}
|
||||
.nav__item {}
|
||||
.nav__link {}
|
||||
|
||||
/* ❌ 과도한 중첩 */
|
||||
.nav ul li a span {} /* NO! */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SQL
|
||||
|
||||
### 쿼리 작성
|
||||
|
||||
```sql
|
||||
-- ✅ 대문자 키워드, 명시적 컬럼 지정
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
email,
|
||||
created_at
|
||||
FROM users
|
||||
WHERE is_active = 1
|
||||
AND role = 'admin'
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 10;
|
||||
|
||||
-- ❌ SELECT * 사용 금지
|
||||
SELECT * FROM users; -- NO!
|
||||
|
||||
-- ✅ 조인 명시적 작성
|
||||
SELECT
|
||||
r.id,
|
||||
r.work_content,
|
||||
w.name AS worker_name,
|
||||
p.name AS project_name
|
||||
FROM daily_work_reports r
|
||||
INNER JOIN workers w ON r.worker_id = w.id
|
||||
LEFT JOIN projects p ON r.project_id = p.id
|
||||
WHERE r.report_date BETWEEN ? AND ?;
|
||||
|
||||
-- ✅ 파라미터 바인딩 사용
|
||||
const query = 'SELECT * FROM users WHERE id = ? AND email = ?';
|
||||
const [rows] = await db.query(query, [userId, email]);
|
||||
|
||||
-- ❌ 문자열 연결 금지 (SQL Injection 위험)
|
||||
const query = `SELECT * FROM users WHERE email = '${email}'`; -- NO!
|
||||
```
|
||||
|
||||
### 테이블/컬럼 네이밍
|
||||
|
||||
```sql
|
||||
-- ✅ 스네이크 케이스
|
||||
CREATE TABLE daily_work_reports (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
worker_id INT NOT NULL,
|
||||
report_date DATE NOT NULL,
|
||||
work_content TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- ✅ 복수형 테이블명
|
||||
users, workers, projects, reports
|
||||
|
||||
-- ✅ 외래키 명확히
|
||||
worker_id, project_id (테이블명_id 형식)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Git 커밋 메시지
|
||||
|
||||
### 형식
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
<body>
|
||||
|
||||
<footer>
|
||||
```
|
||||
|
||||
### Type
|
||||
- `feat`: 새로운 기능
|
||||
- `fix`: 버그 수정
|
||||
- `refactor`: 리팩토링
|
||||
- `style`: 코드 포맷팅
|
||||
- `docs`: 문서 수정
|
||||
- `test`: 테스트 추가/수정
|
||||
- `chore`: 빌드, 설정 변경
|
||||
|
||||
### 예시
|
||||
|
||||
```bash
|
||||
# ✅ 좋은 커밋 메시지
|
||||
feat(report): 작업 보고서 엑셀 내보내기 기능 추가
|
||||
|
||||
사용자가 작업 보고서를 엑셀 파일로 내보낼 수 있도록 기능 추가
|
||||
- xlsx 라이브러리 사용
|
||||
- 날짜 범위 선택 가능
|
||||
- 필터링 옵션 제공
|
||||
|
||||
Closes #123
|
||||
|
||||
# ✅ 리팩토링
|
||||
refactor(api): index.js를 여러 파일로 분리
|
||||
|
||||
- config/database.js: DB 연결 설정
|
||||
- config/cors.js: CORS 설정
|
||||
- middlewares/auth.js: 인증 미들웨어
|
||||
- controllers/userController.js: 사용자 관리 API
|
||||
|
||||
index.js 파일 크기: 889줄 → 95줄
|
||||
|
||||
# ✅ 버그 수정
|
||||
fix(calendar): 주말 날짜 선택 시 오류 수정
|
||||
|
||||
주말 날짜 클릭 시 TypeError 발생하는 문제 해결
|
||||
Date 객체 null 체크 추가
|
||||
|
||||
Fixes #456
|
||||
|
||||
# ❌ 나쁜 커밋 메시지
|
||||
update
|
||||
fix bug
|
||||
asdf
|
||||
ㅁㄴㅇㄹ
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 파일 및 디렉토리 구조
|
||||
|
||||
### 백엔드
|
||||
|
||||
```
|
||||
api.hyungi.net/
|
||||
├── index.js # 진입점 (간결하게)
|
||||
├── config/ # 설정 파일
|
||||
│ ├── database.js
|
||||
│ ├── cors.js
|
||||
│ └── routes.js
|
||||
├── controllers/ # 라우트 핸들러
|
||||
│ ├── userController.js
|
||||
│ └── reportController.js
|
||||
├── services/ # 비즈니스 로직
|
||||
│ ├── userService.js
|
||||
│ └── reportService.js
|
||||
├── models/ # 데이터 모델
|
||||
│ ├── User.js
|
||||
│ └── Report.js
|
||||
├── middlewares/ # 미들웨어
|
||||
│ ├── auth.js
|
||||
│ └── errorHandler.js
|
||||
├── utils/ # 유틸리티
|
||||
│ ├── errors.js
|
||||
│ └── logger.js
|
||||
└── tests/ # 테스트
|
||||
├── unit/
|
||||
└── integration/
|
||||
```
|
||||
|
||||
### 프론트엔드
|
||||
|
||||
```
|
||||
web-ui/
|
||||
├── js/
|
||||
│ ├── modules/ # 모듈화된 코드
|
||||
│ │ ├── common/
|
||||
│ │ │ ├── api-client.js
|
||||
│ │ │ └── utils.js
|
||||
│ │ ├── calendar/
|
||||
│ │ │ ├── CalendarView.js
|
||||
│ │ │ └── CalendarAPI.js
|
||||
│ │ └── dashboard/
|
||||
│ └── pages/ # 페이지별 스크립트
|
||||
├── css/
|
||||
│ ├── base/
|
||||
│ ├── components/
|
||||
│ ├── layouts/
|
||||
│ └── pages/
|
||||
└── pages/ # HTML 파일
|
||||
```
|
||||
|
||||
### 파일 네이밍
|
||||
|
||||
```javascript
|
||||
// ✅ 케이스 규칙
|
||||
// 파일명: kebab-case
|
||||
user-controller.js
|
||||
daily-work-report.js
|
||||
|
||||
// 클래스/컴포넌트: PascalCase
|
||||
UserController.js
|
||||
CalendarView.js
|
||||
|
||||
// 일반 함수/변수: camelCase
|
||||
const getUserData = () => {};
|
||||
const reportService = {};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 코드 리뷰 체크리스트
|
||||
|
||||
### 제출 전 확인
|
||||
- [ ] 코드가 스타일 가이드를 따르는가?
|
||||
- [ ] 주석이 적절한가?
|
||||
- [ ] 테스트가 작성되었는가?
|
||||
- [ ] 에러 처리가 되어있는가?
|
||||
- [ ] 보안 취약점이 없는가?
|
||||
- [ ] 성능 이슈가 없는가?
|
||||
- [ ] 문서가 업데이트되었는가?
|
||||
|
||||
### 리뷰어 확인사항
|
||||
- [ ] 코드 로직이 명확한가?
|
||||
- [ ] 중복 코드가 없는가?
|
||||
- [ ] 변수명이 의미있는가?
|
||||
- [ ] 함수가 단일 책임을 가지는가?
|
||||
- [ ] 테스트가 충분한가?
|
||||
|
||||
---
|
||||
|
||||
## 도구 및 린터 설정
|
||||
|
||||
### ESLint
|
||||
|
||||
```json
|
||||
{
|
||||
"extends": ["eslint:recommended"],
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"rules": {
|
||||
"indent": ["error", 2],
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "always"],
|
||||
"no-var": "error",
|
||||
"prefer-const": "error",
|
||||
"no-console": "warn"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Prettier
|
||||
|
||||
```json
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 80
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 참고 자료
|
||||
|
||||
- [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript)
|
||||
- [Google JavaScript Style Guide](https://google.github.io/styleguide/jsguide.html)
|
||||
- [Node.js Best Practices](https://github.com/goldbergyoni/nodebestpractices)
|
||||
- [Clean Code JavaScript](https://github.com/ryanmcdermott/clean-code-javascript)
|
||||
|
||||
---
|
||||
|
||||
*마지막 업데이트: 2025-12-11*
|
||||
662
docs/guides/SETUP.md
Normal file
662
docs/guides/SETUP.md
Normal file
@@ -0,0 +1,662 @@
|
||||
# 개발 환경 설정 가이드
|
||||
|
||||
> TK-FB-Project 로컬 개발 환경 구축 가이드
|
||||
|
||||
## 📋 목차
|
||||
|
||||
1. [시스템 요구사항](#시스템-요구사항)
|
||||
2. [필수 도구 설치](#필수-도구-설치)
|
||||
3. [프로젝트 설정](#프로젝트-설정)
|
||||
4. [데이터베이스 설정](#데이터베이스-설정)
|
||||
5. [애플리케이션 실행](#애플리케이션-실행)
|
||||
6. [개발 도구](#개발-도구)
|
||||
7. [문제 해결](#문제-해결)
|
||||
|
||||
---
|
||||
|
||||
## 시스템 요구사항
|
||||
|
||||
### 최소 사양
|
||||
- **OS**: macOS, Linux, Windows 10+
|
||||
- **CPU**: 2 Core 이상
|
||||
- **RAM**: 4GB 이상
|
||||
- **디스크**: 10GB 이상 여유 공간
|
||||
|
||||
### 권장 사양
|
||||
- **OS**: macOS (Apple Silicon) 또는 Ubuntu 22.04 LTS
|
||||
- **CPU**: 4 Core 이상
|
||||
- **RAM**: 8GB 이상
|
||||
- **디스크**: SSD 20GB 이상
|
||||
|
||||
---
|
||||
|
||||
## 필수 도구 설치
|
||||
|
||||
### 1. Node.js & npm
|
||||
|
||||
**macOS (Homebrew)**:
|
||||
```bash
|
||||
# Homebrew 설치 (없는 경우)
|
||||
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||
|
||||
# Node.js 설치
|
||||
brew install node@20
|
||||
|
||||
# 버전 확인
|
||||
node --version # v20.x.x
|
||||
npm --version # 10.x.x
|
||||
```
|
||||
|
||||
**Ubuntu/Debian**:
|
||||
```bash
|
||||
# NodeSource repository 추가
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
|
||||
|
||||
# Node.js 설치
|
||||
sudo apt-get install -y nodejs
|
||||
|
||||
# 버전 확인
|
||||
node --version
|
||||
npm --version
|
||||
```
|
||||
|
||||
**Windows**:
|
||||
- [Node.js 공식 사이트](https://nodejs.org/)에서 LTS 버전 다운로드 및 설치
|
||||
|
||||
### 2. Git
|
||||
|
||||
**macOS**:
|
||||
```bash
|
||||
brew install git
|
||||
```
|
||||
|
||||
**Ubuntu/Debian**:
|
||||
```bash
|
||||
sudo apt-get install git
|
||||
```
|
||||
|
||||
**Windows**:
|
||||
- [Git for Windows](https://git-scm.com/download/win) 다운로드
|
||||
|
||||
**설정**:
|
||||
```bash
|
||||
git config --global user.name "Your Name"
|
||||
git config --global user.email "your.email@example.com"
|
||||
```
|
||||
|
||||
### 3. Docker & Docker Compose
|
||||
|
||||
**macOS**:
|
||||
```bash
|
||||
# Docker Desktop 설치
|
||||
brew install --cask docker
|
||||
|
||||
# Docker Desktop 실행 후 확인
|
||||
docker --version
|
||||
docker-compose --version
|
||||
```
|
||||
|
||||
**Ubuntu/Debian**:
|
||||
```bash
|
||||
# Docker 설치
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sudo sh get-docker.sh
|
||||
|
||||
# Docker Compose 설치
|
||||
sudo apt-get install docker-compose-plugin
|
||||
|
||||
# 사용자를 docker 그룹에 추가
|
||||
sudo usermod -aG docker $USER
|
||||
newgrp docker
|
||||
|
||||
# 버전 확인
|
||||
docker --version
|
||||
docker compose version
|
||||
```
|
||||
|
||||
**Windows**:
|
||||
- [Docker Desktop for Windows](https://docs.docker.com/desktop/install/windows-install/) 설치
|
||||
|
||||
### 4. MySQL Client (선택사항)
|
||||
|
||||
**macOS**:
|
||||
```bash
|
||||
brew install mysql-client
|
||||
```
|
||||
|
||||
**Ubuntu/Debian**:
|
||||
```bash
|
||||
sudo apt-get install mysql-client
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 프로젝트 설정
|
||||
|
||||
### 1. 저장소 클론
|
||||
|
||||
```bash
|
||||
# HTTPS
|
||||
git clone https://github.com/your-org/TK-FB-Project.git
|
||||
|
||||
# 또는 SSH
|
||||
git clone git@github.com:your-org/TK-FB-Project.git
|
||||
|
||||
# 프로젝트 디렉토리로 이동
|
||||
cd TK-FB-Project
|
||||
```
|
||||
|
||||
### 2. 환경 변수 설정
|
||||
|
||||
```bash
|
||||
# .env.example 복사
|
||||
cp .env.example .env
|
||||
|
||||
# .env 파일 편집
|
||||
nano .env # 또는 vi, code 등
|
||||
```
|
||||
|
||||
**.env 파일 설정**:
|
||||
```bash
|
||||
# Database
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306
|
||||
DB_NAME=hyungi
|
||||
DB_USER=hyungi
|
||||
DB_PASSWORD=your_secure_password_here
|
||||
DB_ROOT_PASSWORD=your_root_password_here
|
||||
|
||||
# API
|
||||
API_PORT=20005
|
||||
NODE_ENV=development
|
||||
|
||||
# JWT
|
||||
JWT_SECRET=your_jwt_secret_key_at_least_32_characters
|
||||
JWT_REFRESH_SECRET=your_refresh_secret_key_at_least_32_characters
|
||||
|
||||
# Email (선택사항)
|
||||
EMAIL_HOST=smtp.gmail.com
|
||||
EMAIL_PORT=587
|
||||
EMAIL_USER=your-email@gmail.com
|
||||
EMAIL_PASSWORD=your-app-password
|
||||
|
||||
# OpenAI (선택사항)
|
||||
OPENAI_API_KEY=your_openai_api_key
|
||||
```
|
||||
|
||||
**보안 참고**:
|
||||
```bash
|
||||
# 강력한 랜덤 비밀번호 생성
|
||||
openssl rand -base64 32
|
||||
|
||||
# 또는
|
||||
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
||||
```
|
||||
|
||||
### 3. 백엔드 의존성 설치
|
||||
|
||||
```bash
|
||||
cd api.hyungi.net
|
||||
npm install
|
||||
```
|
||||
|
||||
**주요 의존성**:
|
||||
- express: 웹 프레임워크
|
||||
- mysql2: MySQL 드라이버
|
||||
- jsonwebtoken: JWT 인증
|
||||
- bcryptjs: 비밀번호 해싱
|
||||
- cors: CORS 미들웨어
|
||||
- dotenv: 환경변수 관리
|
||||
|
||||
### 4. 프론트엔드 설정
|
||||
|
||||
```bash
|
||||
cd ../web-ui
|
||||
# 프론트엔드는 Vanilla JS이므로 별도 빌드 불필요
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 데이터베이스 설정
|
||||
|
||||
### 방법 1: Docker Compose (권장)
|
||||
|
||||
```bash
|
||||
# 프로젝트 루트에서
|
||||
cd /path/to/TK-FB-Project
|
||||
|
||||
# Docker 컨테이너 시작
|
||||
docker-compose up -d db
|
||||
|
||||
# 로그 확인
|
||||
docker-compose logs -f db
|
||||
|
||||
# MySQL 준비 확인 (약 30초 소요)
|
||||
docker-compose exec db mysqladmin ping -h localhost
|
||||
```
|
||||
|
||||
**초기 데이터 로드**:
|
||||
```bash
|
||||
# SQL 파일 임포트
|
||||
docker-compose exec -T db mysql -u hyungi -p hyungi < hyungi.sql
|
||||
|
||||
# 비밀번호 입력: .env 파일의 DB_PASSWORD
|
||||
```
|
||||
|
||||
### 방법 2: 로컬 MySQL 설치
|
||||
|
||||
**macOS**:
|
||||
```bash
|
||||
# MySQL 설치
|
||||
brew install mysql@8.0
|
||||
|
||||
# MySQL 시작
|
||||
brew services start mysql@8.0
|
||||
|
||||
# 보안 설정
|
||||
mysql_secure_installation
|
||||
```
|
||||
|
||||
**Ubuntu/Debian**:
|
||||
```bash
|
||||
# MySQL 설치
|
||||
sudo apt-get install mysql-server
|
||||
|
||||
# MySQL 시작
|
||||
sudo systemctl start mysql
|
||||
sudo systemctl enable mysql
|
||||
|
||||
# 보안 설정
|
||||
sudo mysql_secure_installation
|
||||
```
|
||||
|
||||
**데이터베이스 생성**:
|
||||
```bash
|
||||
# MySQL 접속
|
||||
mysql -u root -p
|
||||
|
||||
# SQL 실행
|
||||
CREATE DATABASE hyungi CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE USER 'hyungi'@'localhost' IDENTIFIED BY 'your_password';
|
||||
GRANT ALL PRIVILEGES ON hyungi.* TO 'hyungi'@'localhost';
|
||||
FLUSH PRIVILEGES;
|
||||
EXIT;
|
||||
|
||||
# 데이터 임포트
|
||||
mysql -u hyungi -p hyungi < hyungi.sql
|
||||
```
|
||||
|
||||
### 데이터베이스 확인
|
||||
|
||||
```bash
|
||||
# Docker 사용 시
|
||||
docker-compose exec db mysql -u hyungi -p hyungi
|
||||
|
||||
# 로컬 MySQL 사용 시
|
||||
mysql -u hyungi -p hyungi
|
||||
```
|
||||
|
||||
```sql
|
||||
-- 테이블 확인
|
||||
SHOW TABLES;
|
||||
|
||||
-- 사용자 확인
|
||||
SELECT * FROM users LIMIT 5;
|
||||
|
||||
-- 데이터 확인
|
||||
SELECT COUNT(*) FROM daily_work_reports;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 애플리케이션 실행
|
||||
|
||||
### 개발 모드 (Docker Compose)
|
||||
|
||||
```bash
|
||||
# 전체 서비스 시작
|
||||
docker-compose up
|
||||
|
||||
# 백그라운드 실행
|
||||
docker-compose up -d
|
||||
|
||||
# 로그 확인
|
||||
docker-compose logs -f
|
||||
|
||||
# 특정 서비스 로그
|
||||
docker-compose logs -f api
|
||||
docker-compose logs -f web
|
||||
|
||||
# 서비스 중지
|
||||
docker-compose down
|
||||
|
||||
# 볼륨 포함 완전 삭제
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
### 개발 모드 (로컬 실행)
|
||||
|
||||
**터미널 1 - 백엔드**:
|
||||
```bash
|
||||
cd api.hyungi.net
|
||||
|
||||
# 개발 모드 (nodemon 사용)
|
||||
npm run dev
|
||||
|
||||
# 또는 일반 실행
|
||||
npm start
|
||||
```
|
||||
|
||||
**터미널 2 - 프론트엔드**:
|
||||
```bash
|
||||
cd web-ui
|
||||
|
||||
# 간단한 HTTP 서버 실행
|
||||
python3 -m http.server 8080
|
||||
|
||||
# 또는 Node.js http-server
|
||||
npx http-server -p 8080
|
||||
|
||||
# 또는 PHP
|
||||
php -S localhost:8080
|
||||
```
|
||||
|
||||
### 접속 확인
|
||||
|
||||
- **프론트엔드**: http://localhost:8080
|
||||
- **백엔드 API**: http://localhost:20005/api
|
||||
- **데이터베이스**: localhost:3306
|
||||
|
||||
**API 테스트**:
|
||||
```bash
|
||||
# Health check
|
||||
curl http://localhost:20005/api/health
|
||||
|
||||
# 로그인 테스트
|
||||
curl -X POST http://localhost:20005/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"admin","password":"your_password"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 개발 도구
|
||||
|
||||
### 1. VSCode 확장 프로그램
|
||||
|
||||
**필수**:
|
||||
- ESLint
|
||||
- Prettier - Code formatter
|
||||
- MySQL (cweijan.vscode-mysql-client2)
|
||||
- Docker
|
||||
|
||||
**권장**:
|
||||
- GitLens
|
||||
- Thunder Client (API 테스트)
|
||||
- Live Server
|
||||
- JavaScript (ES6) code snippets
|
||||
|
||||
**설치**:
|
||||
```bash
|
||||
# VSCode에서
|
||||
Cmd/Ctrl + Shift + X → 확장 프로그램 검색 및 설치
|
||||
```
|
||||
|
||||
### 2. VSCode 설정
|
||||
|
||||
`.vscode/settings.json`:
|
||||
```json
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"javascript.suggest.autoImports": true,
|
||||
"javascript.updateImportsOnFileMove.enabled": "always",
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 디버깅 설정
|
||||
|
||||
`.vscode/launch.json`:
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch API Server",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"program": "${workspaceFolder}/api.hyungi.net/index.js",
|
||||
"env": {
|
||||
"NODE_ENV": "development"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 유용한 명령어
|
||||
|
||||
```bash
|
||||
# 패키지 업데이트 확인
|
||||
npm outdated
|
||||
|
||||
# 보안 취약점 확인
|
||||
npm audit
|
||||
|
||||
# 보안 취약점 자동 수정
|
||||
npm audit fix
|
||||
|
||||
# 캐시 정리
|
||||
npm cache clean --force
|
||||
|
||||
# node_modules 재설치
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 문제 해결
|
||||
|
||||
### 일반적인 문제
|
||||
|
||||
#### 1. Port already in use
|
||||
|
||||
**증상**:
|
||||
```
|
||||
Error: listen EADDRINUSE: address already in use :::20005
|
||||
```
|
||||
|
||||
**해결**:
|
||||
```bash
|
||||
# 포트 사용 중인 프로세스 확인
|
||||
lsof -i :20005
|
||||
|
||||
# 프로세스 종료
|
||||
kill -9 <PID>
|
||||
|
||||
# 또는 다른 포트 사용
|
||||
# .env 파일에서 API_PORT 변경
|
||||
```
|
||||
|
||||
#### 2. MySQL 연결 실패
|
||||
|
||||
**증상**:
|
||||
```
|
||||
Error: connect ECONNREFUSED 127.0.0.1:3306
|
||||
```
|
||||
|
||||
**해결**:
|
||||
```bash
|
||||
# Docker 사용 시
|
||||
docker-compose ps # db 컨테이너 상태 확인
|
||||
docker-compose up -d db # db 재시작
|
||||
|
||||
# 로컬 MySQL 사용 시
|
||||
sudo systemctl status mysql # 상태 확인
|
||||
sudo systemctl start mysql # MySQL 시작
|
||||
|
||||
# 연결 테스트
|
||||
mysql -h localhost -u hyungi -p
|
||||
```
|
||||
|
||||
#### 3. Permission denied
|
||||
|
||||
**증상**:
|
||||
```
|
||||
Error: EACCES: permission denied
|
||||
```
|
||||
|
||||
**해결**:
|
||||
```bash
|
||||
# Docker 권한 문제
|
||||
sudo usermod -aG docker $USER
|
||||
newgrp docker
|
||||
|
||||
# 파일 권한 문제
|
||||
sudo chown -R $USER:$USER /path/to/project
|
||||
```
|
||||
|
||||
#### 4. Module not found
|
||||
|
||||
**증상**:
|
||||
```
|
||||
Error: Cannot find module 'express'
|
||||
```
|
||||
|
||||
**해결**:
|
||||
```bash
|
||||
# node_modules 재설치
|
||||
cd api.hyungi.net
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
```
|
||||
|
||||
#### 5. JWT 토큰 오류
|
||||
|
||||
**증상**:
|
||||
```
|
||||
Error: jwt malformed
|
||||
Error: invalid signature
|
||||
```
|
||||
|
||||
**해결**:
|
||||
```bash
|
||||
# .env 파일 확인
|
||||
# JWT_SECRET이 올바르게 설정되어 있는지 확인
|
||||
|
||||
# 토큰 재발급
|
||||
# 로그아웃 후 다시 로그인
|
||||
```
|
||||
|
||||
### 데이터베이스 문제
|
||||
|
||||
#### MySQL 8.0 호환성
|
||||
|
||||
**증상**:
|
||||
```
|
||||
Error: ER_NOT_SUPPORTED_AUTH_MODE
|
||||
```
|
||||
|
||||
**해결**:
|
||||
```sql
|
||||
-- MySQL 접속
|
||||
mysql -u root -p
|
||||
|
||||
-- 인증 방식 변경
|
||||
ALTER USER 'hyungi'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password';
|
||||
FLUSH PRIVILEGES;
|
||||
```
|
||||
|
||||
#### 한글 깨짐
|
||||
|
||||
**해결**:
|
||||
```sql
|
||||
-- 데이터베이스 문자셋 확인
|
||||
SHOW VARIABLES LIKE 'character_set%';
|
||||
|
||||
-- 테이블 문자셋 변경
|
||||
ALTER TABLE daily_work_reports CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
```
|
||||
|
||||
### Docker 문제
|
||||
|
||||
#### 컨테이너 재시작
|
||||
|
||||
```bash
|
||||
# 컨테이너 재시작
|
||||
docker-compose restart
|
||||
|
||||
# 특정 서비스만
|
||||
docker-compose restart api
|
||||
|
||||
# 완전 재빌드
|
||||
docker-compose down
|
||||
docker-compose build --no-cache
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
#### 볼륨 문제
|
||||
|
||||
```bash
|
||||
# 볼륨 확인
|
||||
docker volume ls
|
||||
|
||||
# 볼륨 삭제 (주의: 데이터 손실)
|
||||
docker-compose down -v
|
||||
|
||||
# 특정 볼륨만 삭제
|
||||
docker volume rm tk-fb-project_mysql_data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 추가 리소스
|
||||
|
||||
### 공식 문서
|
||||
- [Node.js](https://nodejs.org/docs)
|
||||
- [Express](https://expressjs.com/)
|
||||
- [MySQL](https://dev.mysql.com/doc/)
|
||||
- [Docker](https://docs.docker.com/)
|
||||
|
||||
### 내부 문서
|
||||
- [아키텍처 개요](../architecture/OVERVIEW.md)
|
||||
- [API 문서](../api/ENDPOINTS.md)
|
||||
- [코딩 스타일 가이드](CODING_STYLE.md)
|
||||
- [문제 해결 가이드](TROUBLESHOOTING.md)
|
||||
|
||||
### 커뮤니티
|
||||
- [Node.js GitHub](https://github.com/nodejs/node)
|
||||
- [Stack Overflow](https://stackoverflow.com/questions/tagged/node.js)
|
||||
|
||||
---
|
||||
|
||||
## 체크리스트
|
||||
|
||||
설정 완료 확인:
|
||||
|
||||
- [ ] Node.js 설치 (v20+)
|
||||
- [ ] Git 설치 및 설정
|
||||
- [ ] Docker 설치 및 실행
|
||||
- [ ] 프로젝트 클론
|
||||
- [ ] .env 파일 생성 및 설정
|
||||
- [ ] npm install 완료
|
||||
- [ ] 데이터베이스 생성 및 데이터 로드
|
||||
- [ ] 백엔드 서버 실행 확인
|
||||
- [ ] 프론트엔드 서버 실행 확인
|
||||
- [ ] API 테스트 성공
|
||||
- [ ] 로그인 성공
|
||||
- [ ] VSCode 확장 프로그램 설치
|
||||
|
||||
---
|
||||
|
||||
**도움이 필요하면**: 프로젝트 관리자에게 문의하세요.
|
||||
|
||||
*마지막 업데이트: 2025-12-11*
|
||||
1150
docs/refactoring/ANALYSIS.md
Normal file
1150
docs/refactoring/ANALYSIS.md
Normal file
File diff suppressed because it is too large
Load Diff
139
docs/refactoring/LOG.md
Normal file
139
docs/refactoring/LOG.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# 리팩토링 로그
|
||||
|
||||
> 이 문서는 리팩토링 과정에서 수행한 모든 변경 사항을 기록합니다.
|
||||
> 각 변경은 날짜, 작업 내용, 영향 범위, 테스트 결과를 포함합니다.
|
||||
|
||||
## 📝 작성 규칙
|
||||
|
||||
각 리팩토링 작업 후 다음 형식으로 기록:
|
||||
|
||||
```markdown
|
||||
## YYYY-MM-DD: 작업 제목
|
||||
|
||||
### 변경 사항
|
||||
- 변경된 내용 상세 기술
|
||||
|
||||
### 영향 범위
|
||||
- 수정된 파일 목록
|
||||
- 영향받는 기능
|
||||
|
||||
### 테스트
|
||||
- [ ] 단위 테스트 통과
|
||||
- [ ] 통합 테스트 통과
|
||||
- [ ] 수동 테스트 완료
|
||||
|
||||
### 관련 이슈
|
||||
- 관련 이슈 번호 또는 참고 문서
|
||||
|
||||
### 비고
|
||||
- 추가 메모나 주의사항
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2025-12-11: 문서 구조 초기 생성
|
||||
|
||||
### 변경 사항
|
||||
- docs/ 디렉토리 생성
|
||||
- 리팩토링 관련 문서 작성
|
||||
- README.md: 문서 인덱스
|
||||
- refactoring/ANALYSIS.md: 코드베이스 분석 리포트
|
||||
- refactoring/PLAN.md: 리팩토링 실행 계획
|
||||
- refactoring/LOG.md: 이 파일
|
||||
|
||||
### 영향 범위
|
||||
**새로 생성된 파일**:
|
||||
- docs/README.md
|
||||
- docs/refactoring/ANALYSIS.md
|
||||
- docs/refactoring/PLAN.md
|
||||
- docs/refactoring/LOG.md
|
||||
|
||||
### 테스트
|
||||
- [x] 문서 구조 검토
|
||||
- [x] 마크다운 형식 확인
|
||||
|
||||
### 관련 이슈
|
||||
- 리팩토링 프로젝트 시작
|
||||
|
||||
### 비고
|
||||
- 이후 모든 리팩토링 작업은 이 로그에 기록할 것
|
||||
- 코드 변경 전후를 명확히 문서화
|
||||
|
||||
---
|
||||
|
||||
## 템플릿 (아래 양식 복사해서 사용)
|
||||
|
||||
```markdown
|
||||
## YYYY-MM-DD: [작업 제목]
|
||||
|
||||
### 변경 사항
|
||||
-
|
||||
|
||||
### 영향 범위
|
||||
**수정된 파일**:
|
||||
-
|
||||
-
|
||||
|
||||
**영향받는 기능**:
|
||||
-
|
||||
|
||||
### 코드 변경
|
||||
|
||||
#### Before
|
||||
\`\`\`javascript
|
||||
// 변경 전 코드
|
||||
\`\`\`
|
||||
|
||||
#### After
|
||||
\`\`\`javascript
|
||||
// 변경 후 코드
|
||||
\`\`\`
|
||||
|
||||
### 테스트
|
||||
- [ ] 단위 테스트 통과
|
||||
- [ ] 통합 테스트 통과
|
||||
- [ ] 수동 테스트 완료
|
||||
- [ ] 성능 테스트 (필요시)
|
||||
|
||||
### 성능 영향
|
||||
- 이전:
|
||||
- 이후:
|
||||
- 개선율:
|
||||
|
||||
### 관련 이슈
|
||||
-
|
||||
|
||||
### 비고
|
||||
-
|
||||
|
||||
### Git Commit
|
||||
\`\`\`bash
|
||||
git commit -m "refactor: [커밋 메시지]"
|
||||
\`\`\`
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
## 통계
|
||||
|
||||
| Phase | 완료 항목 | 전체 항목 | 진행률 |
|
||||
|-------|----------|----------|--------|
|
||||
| Phase 1 | 0 | 4 | 0% |
|
||||
| Phase 2 | 0 | 5 | 0% |
|
||||
| Phase 3 | 0 | 4 | 0% |
|
||||
| Phase 4 | 0 | 4 | 0% |
|
||||
| **총계** | **0** | **17** | **0%** |
|
||||
|
||||
---
|
||||
|
||||
## 다음 작업
|
||||
|
||||
Phase 1 긴급 작업:
|
||||
1. synology_deployment 디렉토리 제거
|
||||
2. 보안 정보 환경변수화
|
||||
3. 백업 파일 정리
|
||||
4. .gitignore 업데이트
|
||||
|
||||
---
|
||||
|
||||
*마지막 업데이트: 2025-12-11*
|
||||
785
docs/refactoring/PLAN.md
Normal file
785
docs/refactoring/PLAN.md
Normal file
@@ -0,0 +1,785 @@
|
||||
# 리팩토링 실행 계획
|
||||
|
||||
> **작성일**: 2025-12-11
|
||||
> **목표**: 코드 품질 개선, 유지보수성 향상, 보안 강화
|
||||
|
||||
## 📋 전체 로드맵
|
||||
|
||||
```
|
||||
Phase 1 (1-2주) → Phase 2 (1개월) → Phase 3 (2-3개월) → Phase 4 (6개월)
|
||||
긴급 조치 코드 구조화 아키텍처 개선 현대화
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Phase 1: 긴급 조치 (1-2주)
|
||||
|
||||
### 목표
|
||||
보안 취약점 제거 및 명백한 문제 해결
|
||||
|
||||
### 작업 항목
|
||||
|
||||
#### 1.1 중복 디렉토리 제거 (2시간)
|
||||
**현재 문제**:
|
||||
- `synology_deployment/` 전체 프로젝트 중복 (268MB)
|
||||
- `synology_deployment_v3.tar.gz`, `synology_deployment.tar.gz` 불필요한 아카이브
|
||||
|
||||
**작업**:
|
||||
```bash
|
||||
# 1. 백업 확인
|
||||
git status
|
||||
|
||||
# 2. 중복 디렉토리 제거
|
||||
rm -rf synology_deployment/
|
||||
rm synology_deployment_v3.tar.gz
|
||||
rm synology_deployment.tar.gz
|
||||
rm TK-FB-Project_Synology_Volume2_20251105_120332.tar.gz
|
||||
|
||||
# 3. 배포 스크립트 생성
|
||||
mkdir deployment
|
||||
# deployment/deploy.sh 생성
|
||||
```
|
||||
|
||||
**검증**:
|
||||
- [ ] 디렉토리 삭제 확인
|
||||
- [ ] .gitignore 업데이트
|
||||
- [ ] 배포 스크립트 테스트
|
||||
|
||||
#### 1.2 보안 정보 환경변수화 (4시간)
|
||||
|
||||
**현재 문제**:
|
||||
- docker-compose.yml에 평문 비밀번호
|
||||
- README.md에 실제 비밀번호 노출
|
||||
|
||||
**작업**:
|
||||
|
||||
**Step 1**: .env.example 생성
|
||||
```bash
|
||||
# .env.example
|
||||
DB_HOST=db
|
||||
DB_PORT=3306
|
||||
DB_NAME=hyungi
|
||||
DB_USER=hyungi
|
||||
DB_PASSWORD=<your_password_here>
|
||||
DB_ROOT_PASSWORD=<your_root_password_here>
|
||||
|
||||
JWT_SECRET=<your_jwt_secret_here>
|
||||
JWT_REFRESH_SECRET=<your_jwt_refresh_secret_here>
|
||||
|
||||
API_PORT=20005
|
||||
NODE_ENV=production
|
||||
```
|
||||
|
||||
**Step 2**: docker-compose.yml 수정
|
||||
```yaml
|
||||
services:
|
||||
db:
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||
MYSQL_DATABASE: ${DB_NAME}
|
||||
MYSQL_USER: ${DB_USER}
|
||||
MYSQL_PASSWORD: ${DB_PASSWORD}
|
||||
|
||||
api:
|
||||
environment:
|
||||
DB_HOST: ${DB_HOST}
|
||||
DB_USER: ${DB_USER}
|
||||
DB_PASSWORD: ${DB_PASSWORD}
|
||||
DB_NAME: ${DB_NAME}
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
JWT_REFRESH_SECRET: ${JWT_REFRESH_SECRET}
|
||||
PORT: ${API_PORT}
|
||||
NODE_ENV: ${NODE_ENV}
|
||||
```
|
||||
|
||||
**Step 3**: .gitignore 업데이트
|
||||
```gitignore
|
||||
# 환경 변수
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# 보안 정보
|
||||
secrets/
|
||||
*.pem
|
||||
*.key
|
||||
```
|
||||
|
||||
**Step 4**: README.md 업데이트
|
||||
```markdown
|
||||
## 환경 설정
|
||||
|
||||
1. `.env.example`을 복사하여 `.env` 파일 생성
|
||||
2. 실제 비밀번호로 값 변경
|
||||
3. `.env` 파일은 절대 Git에 커밋하지 않음
|
||||
```
|
||||
|
||||
**검증**:
|
||||
- [ ] .env 파일로 정상 동작 확인
|
||||
- [ ] README에서 실제 비밀번호 제거
|
||||
- [ ] .env가 .gitignore에 포함됨
|
||||
|
||||
#### 1.3 백업 파일 정리 (1시간)
|
||||
|
||||
**작업**:
|
||||
```bash
|
||||
# 1. 백업 파일 확인
|
||||
find . -name "*.backup" -o -name "*복사본*" -o -name "*이전*"
|
||||
|
||||
# 2. 제거
|
||||
rm api.hyungi.net/index.js.backup
|
||||
rm "api.hyungi.net/controllers/dailyWorkReportController 이전.js"
|
||||
rm hyungi.sql.backup
|
||||
rm docker-compose.yml.backup
|
||||
rm docker-compose.yml.new
|
||||
rm "web-ui/js/daily-report-viewer 복사본.js"
|
||||
rm -rf "json(백업)"
|
||||
|
||||
# 3. .gitignore 업데이트
|
||||
echo "*.backup" >> .gitignore
|
||||
echo "*복사본*" >> .gitignore
|
||||
echo "*이전*" >> .gitignore
|
||||
echo "*.old" >> .gitignore
|
||||
```
|
||||
|
||||
**검증**:
|
||||
- [ ] 백업 파일 모두 제거
|
||||
- [ ] .gitignore 적용 확인
|
||||
|
||||
#### 1.4 Git Commit & Push (0.5시간)
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "refactor: Phase 1 - 긴급 보안 및 중복 제거
|
||||
|
||||
- synology_deployment 중복 디렉토리 제거
|
||||
- 비밀번호 환경변수화 (.env)
|
||||
- 백업/임시 파일 정리
|
||||
- .gitignore 업데이트
|
||||
|
||||
🤖 Generated with Claude Code"
|
||||
|
||||
git push origin master
|
||||
```
|
||||
|
||||
**Phase 1 완료 체크리스트**:
|
||||
- [ ] 중복 디렉토리 제거
|
||||
- [ ] 보안 정보 환경변수화
|
||||
- [ ] 백업 파일 정리
|
||||
- [ ] .gitignore 업데이트
|
||||
- [ ] Git 커밋 & 푸시
|
||||
- [ ] refactoring/LOG.md에 기록
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Phase 2: 코드 구조화 (1개월)
|
||||
|
||||
### 목표
|
||||
index.js 분리, 파일 크기 축소, 코드 정리
|
||||
|
||||
### 2.1 백엔드 구조 개선 (2주)
|
||||
|
||||
#### 2.1.1 index.js 분리 (3일)
|
||||
|
||||
**현재**: 889줄의 거대한 파일
|
||||
|
||||
**목표 구조**:
|
||||
```
|
||||
api.hyungi.net/
|
||||
├── index.js # 100줄 이하
|
||||
├── config/
|
||||
│ ├── database.js # DB 연결 설정
|
||||
│ ├── cors.js # CORS 설정
|
||||
│ ├── middleware.js # 미들웨어 설정
|
||||
│ └── routes.js # 라우트 등록
|
||||
├── middlewares/
|
||||
│ ├── auth.js # 인증
|
||||
│ ├── permission.js # 권한
|
||||
│ ├── errorHandler.js # 에러 처리
|
||||
│ └── requestLogger.js # 요청 로깅
|
||||
└── utils/
|
||||
├── errors.js # 커스텀 에러 클래스
|
||||
└── logger.js # 로거
|
||||
```
|
||||
|
||||
**작업 순서**:
|
||||
|
||||
1. **config/database.js 생성** (347-377줄 이동)
|
||||
```javascript
|
||||
const mysql = require('mysql2/promise');
|
||||
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
user: process.env.DB_USER || 'hyungi',
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME || 'hyungi',
|
||||
port: process.env.DB_PORT || 3306,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
queueLimit: 0
|
||||
});
|
||||
|
||||
module.exports = pool;
|
||||
```
|
||||
|
||||
2. **config/cors.js 생성** (183-228줄 이동)
|
||||
```javascript
|
||||
const allowedOrigins = [
|
||||
'http://localhost:3000',
|
||||
'http://localhost:8080',
|
||||
'http://192.168.0.6:8080',
|
||||
'http://hyungi.net',
|
||||
'https://hyungi.net'
|
||||
];
|
||||
|
||||
const corsOptions = {
|
||||
origin: (origin, callback) => {
|
||||
if (process.env.NODE_ENV === 'development' && !origin) {
|
||||
return callback(null, true);
|
||||
}
|
||||
if (allowedOrigins.includes(origin)) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(new Error('CORS policy violation'));
|
||||
}
|
||||
},
|
||||
credentials: true
|
||||
};
|
||||
|
||||
module.exports = corsOptions;
|
||||
```
|
||||
|
||||
3. **middlewares/auth.js 생성** (240-274줄 이동)
|
||||
```javascript
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { AuthenticationError } = require('../utils/errors');
|
||||
|
||||
const authenticateToken = (req, res, next) => {
|
||||
const authHeader = req.headers['authorization'];
|
||||
const token = authHeader && authHeader.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
throw new AuthenticationError('토큰이 필요합니다');
|
||||
}
|
||||
|
||||
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
|
||||
if (err) {
|
||||
throw new AuthenticationError('유효하지 않은 토큰입니다');
|
||||
}
|
||||
req.user = user;
|
||||
next();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = { authenticateToken };
|
||||
```
|
||||
|
||||
4. **controllers/userController.js 생성** (347-631줄 이동)
|
||||
- 인라인 사용자 관리 API를 별도 컨트롤러로 분리
|
||||
|
||||
5. **index.js 재구성**
|
||||
```javascript
|
||||
require('dotenv').config();
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const db = require('./config/database');
|
||||
const corsOptions = require('./config/cors');
|
||||
const { errorHandler } = require('./middlewares/errorHandler');
|
||||
const routes = require('./config/routes');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 20005;
|
||||
|
||||
// 미들웨어
|
||||
app.use(cors(corsOptions));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// 라우트
|
||||
app.use('/api', routes);
|
||||
|
||||
// 에러 핸들러 (마지막에 위치)
|
||||
app.use(errorHandler);
|
||||
|
||||
// 서버 시작
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on port ${PORT}`);
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
```
|
||||
|
||||
**검증**:
|
||||
- [ ] 서버 정상 시작
|
||||
- [ ] 모든 API 엔드포인트 동작 확인
|
||||
- [ ] 에러 처리 정상 동작
|
||||
- [ ] index.js가 100줄 이하
|
||||
|
||||
#### 2.1.2 에러 처리 통일 (2일)
|
||||
|
||||
**utils/errors.js 생성**:
|
||||
```javascript
|
||||
class AppError extends Error {
|
||||
constructor(message, statusCode = 500, code = 'INTERNAL_ERROR') {
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
this.code = code;
|
||||
this.isOperational = true;
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
}
|
||||
|
||||
class ValidationError extends AppError {
|
||||
constructor(message, details = {}) {
|
||||
super(message, 400, 'VALIDATION_ERROR');
|
||||
this.details = details;
|
||||
}
|
||||
}
|
||||
|
||||
class AuthenticationError extends AppError {
|
||||
constructor(message = '인증이 필요합니다') {
|
||||
super(message, 401, 'AUTHENTICATION_ERROR');
|
||||
}
|
||||
}
|
||||
|
||||
class ForbiddenError extends AppError {
|
||||
constructor(message = '권한이 없습니다') {
|
||||
super(message, 403, 'FORBIDDEN');
|
||||
}
|
||||
}
|
||||
|
||||
class NotFoundError extends AppError {
|
||||
constructor(message = '리소스를 찾을 수 없습니다') {
|
||||
super(message, 404, 'NOT_FOUND');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
AppError,
|
||||
ValidationError,
|
||||
AuthenticationError,
|
||||
ForbiddenError,
|
||||
NotFoundError
|
||||
};
|
||||
```
|
||||
|
||||
**middlewares/errorHandler.js 생성**:
|
||||
```javascript
|
||||
const { AppError } = require('../utils/errors');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const errorHandler = (err, req, res, next) => {
|
||||
// 로깅
|
||||
logger.error({
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
code: err.code,
|
||||
path: req.path,
|
||||
method: req.method,
|
||||
userId: req.user?.id
|
||||
});
|
||||
|
||||
// 운영 에러 (예상된 에러)
|
||||
if (err.isOperational) {
|
||||
return res.status(err.statusCode).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: err.message,
|
||||
code: err.code,
|
||||
details: err.details
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 프로그래밍 에러 (예상치 못한 에러)
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: {
|
||||
message: process.env.NODE_ENV === 'development'
|
||||
? err.message
|
||||
: '서버 오류가 발생했습니다',
|
||||
code: 'INTERNAL_ERROR'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = { errorHandler };
|
||||
```
|
||||
|
||||
**기존 코드 변경**:
|
||||
```javascript
|
||||
// Before
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: 'Unauthorized' });
|
||||
}
|
||||
|
||||
// After
|
||||
const { AuthenticationError } = require('../utils/errors');
|
||||
if (!user) {
|
||||
throw new AuthenticationError();
|
||||
}
|
||||
```
|
||||
|
||||
**검증**:
|
||||
- [ ] 모든 컨트롤러에 에러 클래스 적용
|
||||
- [ ] 에러 응답 형식 통일
|
||||
- [ ] 로깅 정상 동작
|
||||
|
||||
#### 2.1.3 SELECT * 쿼리 개선 (3일)
|
||||
|
||||
**작업 파일**:
|
||||
- models/*.js (14개 파일)
|
||||
- controllers/*.js (17개 파일)
|
||||
|
||||
**변경 예시**:
|
||||
```javascript
|
||||
// Before
|
||||
const [workers] = await db.query('SELECT * FROM workers');
|
||||
|
||||
// After
|
||||
const [workers] = await db.query(`
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
email,
|
||||
phone,
|
||||
position,
|
||||
department,
|
||||
is_active,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM workers
|
||||
WHERE is_active = 1
|
||||
ORDER BY name ASC
|
||||
`);
|
||||
```
|
||||
|
||||
**검증**:
|
||||
- [ ] 모든 SELECT * 제거 확인
|
||||
- [ ] 쿼리 성능 테스트
|
||||
- [ ] API 응답 확인
|
||||
|
||||
### 2.2 프론트엔드 모듈화 (2주)
|
||||
|
||||
#### 2.2.1 공통 모듈 추출 (5일)
|
||||
|
||||
**목표 구조**:
|
||||
```
|
||||
web-ui/js/
|
||||
├── modules/
|
||||
│ ├── common/
|
||||
│ │ ├── api-client.js # API 호출 통합
|
||||
│ │ ├── utils.js # 유틸리티 함수
|
||||
│ │ ├── validator.js # 폼 검증
|
||||
│ │ └── logger.js # 로깅
|
||||
│ ├── calendar/
|
||||
│ │ ├── CalendarView.js # UI 렌더링
|
||||
│ │ ├── CalendarAPI.js # API 호출
|
||||
│ │ └── CalendarUtils.js # 유틸리티
|
||||
│ └── dashboard/
|
||||
│ ├── DashboardView.js
|
||||
│ └── DashboardData.js
|
||||
├── pages/ # 기존 파일 (모듈 사용)
|
||||
└── legacy/ # 임시 백업
|
||||
```
|
||||
|
||||
**1단계: api-client.js 생성**
|
||||
```javascript
|
||||
// modules/common/api-client.js
|
||||
export class ApiClient {
|
||||
constructor(baseUrl) {
|
||||
this.baseUrl = baseUrl || this.getBaseUrl();
|
||||
}
|
||||
|
||||
getBaseUrl() {
|
||||
const protocol = window.location.protocol;
|
||||
const hostname = window.location.hostname;
|
||||
return `${protocol}//${hostname}:20005/api`;
|
||||
}
|
||||
|
||||
async request(endpoint, options = {}) {
|
||||
const token = localStorage.getItem('token');
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
};
|
||||
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}${endpoint}`, {
|
||||
...options,
|
||||
headers
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error?.message || '요청 실패');
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('[API Error]', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
get(endpoint) {
|
||||
return this.request(endpoint, { method: 'GET' });
|
||||
}
|
||||
|
||||
post(endpoint, data) {
|
||||
return this.request(endpoint, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
}
|
||||
|
||||
put(endpoint, data) {
|
||||
return this.request(endpoint, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
}
|
||||
|
||||
delete(endpoint) {
|
||||
return this.request(endpoint, { method: 'DELETE' });
|
||||
}
|
||||
}
|
||||
|
||||
export default new ApiClient();
|
||||
```
|
||||
|
||||
**2단계: 기존 파일에서 API 호출 변경**
|
||||
```javascript
|
||||
// Before
|
||||
const response = await fetch(`${baseUrl}/workers`, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
// After
|
||||
import api from './modules/common/api-client.js';
|
||||
const data = await api.get('/workers');
|
||||
```
|
||||
|
||||
**검증**:
|
||||
- [ ] 모든 API 호출이 api-client 사용
|
||||
- [ ] 토큰 자동 포함 확인
|
||||
- [ ] 에러 처리 통일
|
||||
|
||||
#### 2.2.2 큰 파일 분리 (5일)
|
||||
|
||||
**우선순위**:
|
||||
1. work-report-calendar.js (1,720줄) → 3-4개 파일
|
||||
2. modern-dashboard.js (1,299줄) → 3-4개 파일
|
||||
3. daily-work-report.js (1,137줄) → 3개 파일
|
||||
|
||||
**검증**:
|
||||
- [ ] 각 파일 500줄 이하
|
||||
- [ ] 기능 정상 동작
|
||||
- [ ] 모듈 간 의존성 명확
|
||||
|
||||
**Phase 2 완료 체크리스트**:
|
||||
- [ ] index.js 100줄 이하로 축소
|
||||
- [ ] 에러 처리 통일
|
||||
- [ ] SELECT * 모두 제거
|
||||
- [ ] 공통 모듈 추출
|
||||
- [ ] 큰 파일 분리 (각 500줄 이하)
|
||||
- [ ] Git 커밋 & 푸시
|
||||
- [ ] refactoring/LOG.md 업데이트
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Phase 3: 아키텍처 개선 (2-3개월)
|
||||
|
||||
### 3.1 서비스 레이어 완성 (4주)
|
||||
|
||||
**목표**: 비즈니스 로직을 컨트롤러에서 서비스로 분리
|
||||
|
||||
**작업 대상**:
|
||||
- DailyWorkReport (851줄 컨트롤러)
|
||||
- Worker 관리
|
||||
- Project 관리
|
||||
- Attendance 관리
|
||||
- Issue 관리
|
||||
|
||||
**패턴**:
|
||||
```
|
||||
Controller → Service → Repository → Model
|
||||
↓ ↓ ↓
|
||||
라우팅 비즈니스 로직 데이터 접근
|
||||
```
|
||||
|
||||
### 3.2 권한 체크 미들웨어 (1주)
|
||||
|
||||
**middlewares/permission.js**:
|
||||
```javascript
|
||||
const requireAuth = (req, res, next) => { ... };
|
||||
const requireRole = (...roles) => { ... };
|
||||
const requireOwnerOrAdmin = (getOwnerId) => { ... };
|
||||
```
|
||||
|
||||
### 3.3 테스트 코드 작성 (4주)
|
||||
|
||||
**목표**: 50% 이상 커버리지
|
||||
|
||||
**도구**:
|
||||
- Jest (백엔드)
|
||||
- Testing Library (프론트엔드)
|
||||
|
||||
**우선순위**:
|
||||
1. 서비스 레이어 (비즈니스 로직)
|
||||
2. API 엔드포인트 (통합 테스트)
|
||||
3. 유틸리티 함수
|
||||
|
||||
### 3.4 API 문서화 (2주)
|
||||
|
||||
**도구**: Swagger/OpenAPI
|
||||
|
||||
**목표**:
|
||||
- 모든 엔드포인트 문서화
|
||||
- 요청/응답 예시
|
||||
- 에러 코드 정의
|
||||
|
||||
**Phase 3 완료 체크리스트**:
|
||||
- [ ] 서비스 레이어 완성
|
||||
- [ ] 권한 미들웨어 통일
|
||||
- [ ] 테스트 커버리지 50% 이상
|
||||
- [ ] API 문서 완성
|
||||
- [ ] 성능 최적화 (인덱스, 캐싱)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Phase 4: 현대화 (6개월)
|
||||
|
||||
### 4.1 TypeScript 마이그레이션 (8주)
|
||||
|
||||
**순서**:
|
||||
1. tsconfig.json 설정
|
||||
2. 유틸리티 함수부터 변환
|
||||
3. 모델 → 서비스 → 컨트롤러 순서
|
||||
4. 프론트엔드 변환
|
||||
|
||||
### 4.2 프론트엔드 프레임워크 (12주)
|
||||
|
||||
**옵션**:
|
||||
- React + TypeScript
|
||||
- Vue 3 + TypeScript
|
||||
- Svelte + TypeScript
|
||||
|
||||
**마이그레이션 전략**:
|
||||
- 점진적 마이그레이션
|
||||
- 기존 페이지와 공존
|
||||
- 중요 페이지부터 전환
|
||||
|
||||
### 4.3 ORM 도입 (4주)
|
||||
|
||||
**옵션**:
|
||||
- TypeORM
|
||||
- Prisma
|
||||
- Sequelize
|
||||
|
||||
**장점**:
|
||||
- 타입 안전성
|
||||
- 마이그레이션 관리
|
||||
- 쿼리 빌더
|
||||
|
||||
### 4.4 CI/CD 파이프라인 (2주)
|
||||
|
||||
**GitHub Actions**:
|
||||
```yaml
|
||||
name: CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, develop]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
deploy:
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref == 'refs/heads/master'
|
||||
steps:
|
||||
- name: Deploy to production
|
||||
run: ./deploy.sh
|
||||
```
|
||||
|
||||
**Phase 4 완료 체크리스트**:
|
||||
- [ ] TypeScript 100% 전환
|
||||
- [ ] 프론트엔드 프레임워크 도입
|
||||
- [ ] ORM 적용
|
||||
- [ ] CI/CD 구축
|
||||
- [ ] 모니터링 시스템
|
||||
|
||||
---
|
||||
|
||||
## 📊 진행 상황 추적
|
||||
|
||||
### 주간 체크포인트
|
||||
|
||||
매주 금요일:
|
||||
- [ ] 완료된 작업 리뷰
|
||||
- [ ] 다음 주 계획 수립
|
||||
- [ ] 블로커 식별 및 해결
|
||||
- [ ] refactoring/LOG.md 업데이트
|
||||
|
||||
### 월간 리뷰
|
||||
|
||||
매월 말:
|
||||
- [ ] 코드 메트릭스 측정
|
||||
- [ ] 성능 벤치마크
|
||||
- [ ] 기술 부채 평가
|
||||
- [ ] 로드맵 조정
|
||||
|
||||
---
|
||||
|
||||
## 🎯 성공 기준
|
||||
|
||||
### Phase 1
|
||||
- ✅ 보안 취약점 0개
|
||||
- ✅ 코드 중복 <10%
|
||||
- ✅ 백업 파일 0개
|
||||
|
||||
### Phase 2
|
||||
- ✅ 평균 파일 크기 <500줄
|
||||
- ✅ 에러 처리 통일률 100%
|
||||
- ✅ SELECT * 사용 0개
|
||||
|
||||
### Phase 3
|
||||
- ✅ 서비스 레이어 커버리지 100%
|
||||
- ✅ 테스트 커버리지 >50%
|
||||
- ✅ API 문서화 100%
|
||||
|
||||
### Phase 4
|
||||
- ✅ TypeScript 전환율 100%
|
||||
- ✅ 프레임워크 마이그레이션 완료
|
||||
- ✅ CI/CD 파이프라인 운영
|
||||
|
||||
---
|
||||
|
||||
## 📝 참고 문서
|
||||
|
||||
- [코드 분석 리포트](ANALYSIS.md)
|
||||
- [리팩토링 로그](LOG.md)
|
||||
- [코딩 스타일 가이드](../guides/CODING_STYLE.md)
|
||||
- [아키텍처 개요](../architecture/OVERVIEW.md)
|
||||
|
||||
---
|
||||
|
||||
**다음**: [리팩토링 시작하기](LOG.md)
|
||||
Reference in New Issue
Block a user