## 🚨 보안 강화 - 하드코딩된 비밀번호를 환경변수로 전환 - .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>
17 KiB
17 KiB
리팩토링 실행 계획
작성일: 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불필요한 아카이브
작업:
# 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 생성
# .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 수정
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 업데이트
# 환경 변수
.env
.env.local
.env.*.local
# 보안 정보
secrets/
*.pem
*.key
Step 4: README.md 업데이트
## 환경 설정
1. `.env.example`을 복사하여 `.env` 파일 생성
2. 실제 비밀번호로 값 변경
3. `.env` 파일은 절대 Git에 커밋하지 않음
검증:
- .env 파일로 정상 동작 확인
- README에서 실제 비밀번호 제거
- .env가 .gitignore에 포함됨
1.3 백업 파일 정리 (1시간)
작업:
# 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시간)
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 # 로거
작업 순서:
- config/database.js 생성 (347-377줄 이동)
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;
- config/cors.js 생성 (183-228줄 이동)
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;
- middlewares/auth.js 생성 (240-274줄 이동)
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 };
-
controllers/userController.js 생성 (347-631줄 이동)
- 인라인 사용자 관리 API를 별도 컨트롤러로 분리
-
index.js 재구성
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 생성:
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 생성:
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 };
기존 코드 변경:
// 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개 파일)
변경 예시:
// 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 생성
// 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 호출 변경
// 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일)
우선순위:
- work-report-calendar.js (1,720줄) → 3-4개 파일
- modern-dashboard.js (1,299줄) → 3-4개 파일
- 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:
const requireAuth = (req, res, next) => { ... };
const requireRole = (...roles) => { ... };
const requireOwnerOrAdmin = (getOwnerId) => { ... };
3.3 테스트 코드 작성 (4주)
목표: 50% 이상 커버리지
도구:
- Jest (백엔드)
- Testing Library (프론트엔드)
우선순위:
- 서비스 레이어 (비즈니스 로직)
- API 엔드포인트 (통합 테스트)
- 유틸리티 함수
3.4 API 문서화 (2주)
도구: Swagger/OpenAPI
목표:
- 모든 엔드포인트 문서화
- 요청/응답 예시
- 에러 코드 정의
Phase 3 완료 체크리스트:
- 서비스 레이어 완성
- 권한 미들웨어 통일
- 테스트 커버리지 50% 이상
- API 문서 완성
- 성능 최적화 (인덱스, 캐싱)
🚀 Phase 4: 현대화 (6개월)
4.1 TypeScript 마이그레이션 (8주)
순서:
- tsconfig.json 설정
- 유틸리티 함수부터 변환
- 모델 → 서비스 → 컨트롤러 순서
- 프론트엔드 변환
4.2 프론트엔드 프레임워크 (12주)
옵션:
- React + TypeScript
- Vue 3 + TypeScript
- Svelte + TypeScript
마이그레이션 전략:
- 점진적 마이그레이션
- 기존 페이지와 공존
- 중요 페이지부터 전환
4.3 ORM 도입 (4주)
옵션:
- TypeORM
- Prisma
- Sequelize
장점:
- 타입 안전성
- 마이그레이션 관리
- 쿼리 빌더
4.4 CI/CD 파이프라인 (2주)
GitHub Actions:
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 파이프라인 운영
📝 참고 문서
다음: 리팩토링 시작하기