Files
Hyungi Ahn 1e7155b864 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>
2025-12-11 10:16:57 +09:00

786 lines
17 KiB
Markdown

# 리팩토링 실행 계획
> **작성일**: 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)