## 🚨 보안 강화 - 하드코딩된 비밀번호를 환경변수로 전환 - .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>
786 lines
17 KiB
Markdown
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)
|