Files
TK-FB-Project/docs/refactoring/PLAN.md
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

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              # 로거

작업 순서:

  1. 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;
  1. 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;
  1. 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 };
  1. controllers/userController.js 생성 (347-631줄 이동)

    • 인라인 사용자 관리 API를 별도 컨트롤러로 분리
  2. 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일)

우선순위:

  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:

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:

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 파이프라인 운영

📝 참고 문서


다음: 리팩토링 시작하기