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

25 KiB

코드베이스 분석 리포트

분석 일자: 2025-12-11 분석 도구: Claude Code 프로젝트: TK-FB-Project v2.2.0

📊 프로젝트 개요

시스템 구조

Technical Korea 작업 관리 시스템 - 3-tier 아키텍처

TK-FB-Project/
├── web-ui/                    # 프론트엔드 (Nginx + Vanilla JS)
│   ├── js/                    # 49개 파일, 554개 함수
│   ├── css/                   # 22개 파일
│   └── pages/                 # 51개 HTML 페이지
├── api.hyungi.net/           # 백엔드 (Node.js + Express)
│   ├── controllers/          # 17개 컨트롤러
│   ├── models/              # 14개 모델
│   ├── routes/              # 22개 라우트
│   ├── services/            # 6개 서비스 (불완전)
│   └── index.js             # 889줄 메인 파일
├── fastapi-bridge/          # Python FastAPI
└── synology_deployment/     # ⚠️ 중복 디렉토리 (268MB)

주요 기능

  1. 일일 작업 보고서 관리
  2. 작업자 및 프로젝트 관리
  3. 근태 관리 시스템
  4. 작업 분석 및 대시보드
  5. 이슈 관리
  6. 사용자 인증 및 권한

🔴 우선순위 1: 심각 (즉시 해결 필요)

1.1 코드 중복 - synology_deployment

문제점:

  • 전체 프로젝트가 /synology_deployment에 중복 복사 (268MB)
  • 두 버전이 서로 다른 내용 포함 (CORS 설정 차이)
  • 이중 관리 문제

영향도: 높음 (유지보수성, 저장소 크기)

해결 방안:

# 제거 대상
synology_deployment/
synology_deployment_v3.tar.gz
synology_deployment.tar.gz

# 대안: Docker 빌드 스크립트
deployment/
├── Dockerfile
├── docker-compose.yml
└── deploy.sh

1.2 하드코딩된 보안 정보

발견된 위치:

  1. docker-compose.yml (15-17줄):
MYSQL_ROOT_PASSWORD=tkfb2024!
DB_PASSWORD=hyungi2024!
JWT_SECRET=tkfb_jwt_secret_2024_hyungi_secure_key
  1. README.md (공개 문서):
- 비밀번호: hyungi_password_2025
- Root 비밀번호: hyungi_root_password_2025
  1. web-ui/js/api-config.js (17줄):
const baseUrl = `${protocol}//${hostname}:20005/api`;  // 포트 하드코딩

보안 위험: 높음 (비밀번호 노출)

해결 방안:

# .env 파일 사용
DB_PASSWORD=${DB_PASSWORD}
JWT_SECRET=${JWT_SECRET}

# Docker secrets
secrets:
  db_root_password:
    file: ./secrets/db_root_password.txt

1.3 백업/임시 파일 정리

발견된 파일들:

web-ui/js/daily-report-viewer 복사본.js
api.hyungi.net/index.js.backup
api.hyungi.net/controllers/dailyWorkReportController 이전.js
hyungi.sql.backup
docker-compose.yml.backup
docker-compose.yml.new
json(백업)/
개발 log/

문제점: 13개 백업 파일이 Git 저장소에 포함됨

해결 방안:

# .gitignore 추가
*.backup
*복사본*
*이전*
*.old
*.sql.backup
*.new
*백업*

🟠 우선순위 2: 높음 (단기 개선)

2.1 거대한 파일들

파일 줄 수 문제점
work-report-calendar.js 1,720 달력 + API + UI 로직 혼재
modern-dashboard.js 1,299 대시보드 모든 기능 포함
daily-work-report.js 1,137 폼 + 검증 + API 통합
work-report-review.js 1,060 리뷰 전체 로직
index.js (백엔드) 889 라우트 + 컨트롤러 + 설정
dailyWorkReportController.js 851 모든 CRUD 로직

개선 방안:

백엔드 (index.js → 여러 파일로 분리)

api.hyungi.net/
├── index.js              # 100줄 이하로 축소
├── config/
│   ├── middleware.js     # 미들웨어 설정
│   ├── cors.js          # CORS 설정
│   ├── database.js      # DB 연결
│   └── routes.js        # 라우트 등록
├── middlewares/
│   ├── auth.js          # 인증 미들웨어
│   ├── permission.js    # 권한 체크
│   └── errorHandler.js  # 에러 핸들링
└── controllers/
    └── userController.js # 인라인 코드 이동

프론트엔드 (모듈화)

web-ui/js/
├── modules/
│   ├── calendar/
│   │   ├── CalendarView.js      # UI 렌더링
│   │   ├── CalendarAPI.js       # API 호출
│   │   ├── CalendarUtils.js     # 유틸리티
│   │   └── CalendarState.js     # 상태 관리
│   ├── dashboard/
│   │   ├── DashboardView.js
│   │   ├── DashboardCharts.js
│   │   └── DashboardData.js
│   └── common/
│       ├── api-client.js
│       ├── utils.js
│       └── validator.js

2.2 일관성 없는 에러 처리

통계:

  • console.log/error/warn: 684개 (프론트엔드)
  • try-catch 블록: 534개 (백엔드)
  • 에러 처리 패턴: 5가지 이상

현재 패턴:

// 패턴 1: 단순 로그
catch (error) {
  console.error('오류:', error);
}

// 패턴 2: 커스텀 응답
catch (err) {
  return res.status(500).json({
    error: '오류가 발생했습니다.',
    details: err.message
  });
}

// 패턴 3: errorMiddleware
throw new ApiError('작업보고서 생성 실패', 400);

// 패턴 4: 에러 무시
catch (error) { }

// 패턴 5: 부분적 처리
catch (error) {
  console.error(error);
  alert('오류 발생');
}

표준화 방안:

// 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');
  }
}

// 사용 예시
if (!user) {
  throw new AuthenticationError();
}

if (!['admin', 'system'].includes(user.access_level)) {
  throw new ForbiddenError('관리자 권한이 필요합니다');
}
// middlewares/errorHandler.js
const errorHandler = (err, req, res, next) => {
  // 로깅
  logger.error({
    message: err.message,
    stack: err.stack,
    code: err.code,
    path: req.path,
    method: req.method,
    user: 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: '서버 오류가 발생했습니다',
      code: 'INTERNAL_ERROR'
    }
  });
};
// 프론트엔드 로거
class Logger {
  static error(message, context = {}) {
    const logData = {
      timestamp: new Date().toISOString(),
      message,
      ...context,
      userAgent: navigator.userAgent,
      url: window.location.href
    };

    if (window.ENV === 'development') {
      console.error('[ERROR]', logData);
    } else {
      // 프로덕션: 외부 로깅 서비스로 전송
      this.sendToLoggingService(logData);
    }
  }

  static warn(message, context = {}) {
    if (window.ENV === 'development') {
      console.warn('[WARN]', message, context);
    }
  }

  static info(message, context = {}) {
    if (window.ENV === 'development') {
      console.log('[INFO]', message, context);
    }
  }

  static sendToLoggingService(data) {
    // Sentry, LogRocket 등 통합
    // fetch('/api/logs', { method: 'POST', body: JSON.stringify(data) });
  }
}

// 사용
try {
  await saveReport(data);
} catch (error) {
  Logger.error('작업 보고서 저장 실패', {
    reportId: data.id,
    error: error.message
  });
  showErrorToast('저장에 실패했습니다. 다시 시도해주세요.');
}

2.3 SELECT * 쿼리 사용

발견된 위치: 20개 이상 파일

예시:

-- models/workTypeModel.js
SELECT * FROM work_types ORDER BY name ASC

-- models/toolModel.js
SELECT * FROM Tools

-- controllers/dailyWorkReportController.js
SELECT * FROM daily_work_reports WHERE id = ?

문제점:

  • 불필요한 데이터 전송
  • 성능 저하
  • 스키마 변경 시 예측 불가능

개선 방안:

-- 명시적 컬럼 지정
SELECT
  id,
  name,
  description,
  category,
  is_active,
  created_at,
  updated_at
FROM work_types
WHERE is_active = 1
ORDER BY name ASC;

-- 조인 시에도 명시적 지정
SELECT
  r.id,
  r.report_date,
  r.work_content,
  w.name AS worker_name,
  p.name AS project_name
FROM daily_work_reports r
JOIN workers w ON r.worker_id = w.id
JOIN projects p ON r.project_id = p.id
WHERE r.report_date = ?;

🟡 우선순위 3: 중간 (중기 개선)

3.1 불완전한 서비스 레이어

현재 구조:

Controllers (17개) → Services (6개만) → Models (14개)
                   ↘ Models (직접 호출)

문제점:

  • 대부분의 비즈니스 로직이 컨트롤러에 직접 구현
  • 코드 재사용 어려움
  • 테스트 작성 어려움

서비스 레이어가 있는 모듈:

  1. authService.js
  2. emailService.js
  3. openaiService.js
  4. reportSubmissionService.js
  5. tokenService.js
  6. workAnalysisService.js

서비스 레이어가 없는 모듈:

  • dailyWorkReport (851줄 컨트롤러)
  • worker 관리
  • project 관리
  • attendance 관리
  • issue 관리
  • 등 11개 도메인

개선 방안:

// services/dailyWorkReportService.js
class DailyWorkReportService {
  constructor(reportModel, workerModel, projectModel) {
    this.reportModel = reportModel;
    this.workerModel = workerModel;
    this.projectModel = projectModel;
  }

  async createReport(reportData, userId) {
    // 1. 검증
    await this.validateReport(reportData);

    // 2. 권한 확인
    await this.checkPermission(userId, reportData.workerId);

    // 3. 중복 체크
    const exists = await this.reportModel.findByDateAndWorker(
      reportData.date,
      reportData.workerId
    );
    if (exists) {
      throw new ValidationError('해당 날짜에 이미 보고서가 존재합니다');
    }

    // 4. 생성
    const report = await this.reportModel.create(reportData);

    // 5. 후처리 (알림, 로그 등)
    await this.notifyReportCreation(report);

    return report;
  }

  async validateReport(data) {
    if (!data.workContent || data.workContent.length < 10) {
      throw new ValidationError('작업 내용은 10자 이상이어야 합니다');
    }

    if (!data.workerId) {
      throw new ValidationError('작업자를 선택해주세요');
    }

    // 작업자 존재 확인
    const worker = await this.workerModel.findById(data.workerId);
    if (!worker) {
      throw new ValidationError('존재하지 않는 작업자입니다');
    }

    if (!worker.is_active) {
      throw new ValidationError('비활성화된 작업자입니다');
    }
  }

  async checkPermission(userId, workerId) {
    const user = await this.userModel.findById(userId);

    // 관리자는 모든 보고서 작성 가능
    if (['admin', 'system'].includes(user.access_level)) {
      return true;
    }

    // 일반 사용자는 자신의 보고서만
    if (user.worker_id !== workerId) {
      throw new ForbiddenError('다른 작업자의 보고서를 작성할 수 없습니다');
    }
  }

  async notifyReportCreation(report) {
    // 그룹 리더에게 알림
    // 이메일 발송
    // 로그 기록
  }
}

// controllers/dailyWorkReportController.js
const createReport = async (req, res, next) => {
  try {
    const report = await dailyWorkReportService.createReport(
      req.body,
      req.user.id
    );

    res.status(201).json({
      success: true,
      data: report
    });
  } catch (error) {
    next(error);
  }
};

3.2 인증/권한 로직 중복

발견된 패턴:

// 패턴 1: 컨트롤러 내부에서 체크 (가장 많음)
if (!req.user || !['admin', 'system'].includes(req.user.access_level)) {
  return res.status(403).json({
    success: false,
    error: '관리자 권한이 필요합니다.'
  });
}

// 패턴 2: 미들웨어 (index.js 276-314줄)
const checkAdmin = (req, res, next) => {
  if (req.user && ['admin', 'system'].includes(req.user.access_level)) {
    next();
  } else {
    res.status(403).json({ error: 'Forbidden' });
  }
};

// 패턴 3: 직접 user 확인
if (!user) {
  return res.status(401).json({ error: 'Unauthorized' });
}

개선 방안:

// middlewares/permission.js
const requireAuth = (req, res, next) => {
  if (!req.user) {
    throw new AuthenticationError();
  }
  next();
};

const requireRole = (...allowedRoles) => {
  return (req, res, next) => {
    if (!req.user) {
      throw new AuthenticationError();
    }

    if (!allowedRoles.includes(req.user.access_level)) {
      throw new ForbiddenError(
        `이 작업은 ${allowedRoles.join(', ')} 권한이 필요합니다`
      );
    }

    next();
  };
};

const requireOwnerOrAdmin = (getOwnerId) => {
  return async (req, res, next) => {
    if (!req.user) {
      throw new AuthenticationError();
    }

    // 관리자는 모두 접근 가능
    if (['admin', 'system'].includes(req.user.access_level)) {
      return next();
    }

    // 소유자 확인
    const ownerId = await getOwnerId(req);
    if (req.user.id !== ownerId) {
      throw new ForbiddenError('본인의 데이터만 수정할 수 있습니다');
    }

    next();
  };
};

// 사용 예시
router.get('/users',
  requireAuth,
  requireRole('admin', 'system'),
  getUsers
);

router.put('/reports/:id',
  requireAuth,
  requireOwnerOrAdmin(async (req) => {
    const report = await ReportModel.findById(req.params.id);
    return report.user_id;
  }),
  updateReport
);

router.post('/projects',
  requireAuth,
  requireRole('admin', 'project_manager'),
  createProject
);

3.3 CORS 설정 중복

위치: index.js 136-181줄 (주석 처리됨)

문제점:

// 136-181줄: 주석 처리된 CORS 설정
/*
app.use(cors({
  origin: function (origin, callback) {
    // ... 복잡한 로직
  }
}));
*/

// 183-228줄: 실제 사용 중인 CORS 설정
app.use(cors({
  origin: function (origin, callback) {
    // ... 동일한 로직
  }
}));

개선 방안:

// config/cors.js
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) => {
    // 개발 환경에서는 모든 origin 허용
    if (process.env.NODE_ENV === 'development' && !origin) {
      return callback(null, true);
    }

    // 허용된 origin 확인
    if (allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('CORS policy violation'));
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization']
};

module.exports = corsOptions;

// index.js
const corsOptions = require('./config/cors');
app.use(cors(corsOptions));

🟢 우선순위 4: 낮음 (장기 개선)

4.1 프론트엔드 모듈화 부족

현재 상태:

  • Vanilla JavaScript
  • 전역 변수 남용
  • 파일 간 의존성 불명확
  • 코드 재사용 어려움

개선 방안:

// 현재 (전역 변수)
var currentDate = new Date();
var selectedReport = null;

function loadReport() {
  // ...
}

// 개선 (ES6 모듈)
// modules/calendar/CalendarState.js
export class CalendarState {
  constructor() {
    this.currentDate = new Date();
    this.selectedReport = null;
    this.events = [];
  }

  setCurrentDate(date) {
    this.currentDate = date;
    this.notifyListeners();
  }

  selectReport(report) {
    this.selectedReport = report;
    this.notifyListeners();
  }
}

// modules/calendar/CalendarView.js
import { CalendarState } from './CalendarState.js';
import { CalendarAPI } from './CalendarAPI.js';

export class CalendarView {
  constructor(containerId) {
    this.container = document.getElementById(containerId);
    this.state = new CalendarState();
    this.api = new CalendarAPI();
  }

  async render() {
    const events = await this.api.fetchEvents(this.state.currentDate);
    this.renderCalendar(events);
  }

  renderCalendar(events) {
    // ...
  }
}

// main.js
import { CalendarView } from './modules/calendar/CalendarView.js';

document.addEventListener('DOMContentLoaded', () => {
  const calendar = new CalendarView('calendar-container');
  calendar.render();
});

4.2 CSS 구조 개선

현재: 22개의 개별 CSS 파일

개선안:

css/
├── base/
│   ├── reset.css              # CSS 리셋
│   ├── variables.css          # CSS 변수
│   ├── typography.css         # 폰트 스타일
│   └── utilities.css          # 유틸리티 클래스
├── components/
│   ├── button.css            # 버튼 스타일
│   ├── form.css              # 폼 요소
│   ├── modal.css             # 모달
│   ├── table.css             # 테이블
│   ├── card.css              # 카드
│   └── badge.css             # 뱃지
├── layouts/
│   ├── header.css            # 헤더
│   ├── sidebar.css           # 사이드바
│   ├── footer.css            # 푸터
│   └── grid.css              # 그리드 시스템
└── pages/
    ├── dashboard.css         # 대시보드
    ├── calendar.css          # 캘린더
    └── report.css            # 보고서

# main.css (통합)
@import 'base/reset.css';
@import 'base/variables.css';
@import 'base/typography.css';
/* ... */
/* base/variables.css - CSS 변수 사용 */
:root {
  /* Colors */
  --color-primary: #007bff;
  --color-success: #28a745;
  --color-danger: #dc3545;
  --color-warning: #ffc107;

  /* Spacing */
  --spacing-xs: 0.25rem;
  --spacing-sm: 0.5rem;
  --spacing-md: 1rem;
  --spacing-lg: 1.5rem;
  --spacing-xl: 2rem;

  /* Typography */
  --font-family-base: 'Noto Sans KR', sans-serif;
  --font-size-base: 14px;
  --line-height-base: 1.5;
}

/* components/button.css */
.btn {
  padding: var(--spacing-sm) var(--spacing-md);
  font-family: var(--font-family-base);
  border-radius: 4px;
  transition: all 0.3s ease;
}

.btn-primary {
  background-color: var(--color-primary);
  color: white;
}

🔒 보안 취약점

1. 비밀번호 평문 노출 (심각)

위치:

  • docker-compose.yml
  • README.md
  • .env 파일 (Git에 포함 가능성)

해결:

# docker-compose.yml
services:
  db:
    secrets:
      - db_root_password
      - db_password
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
      MYSQL_PASSWORD_FILE: /run/secrets/db_password

secrets:
  db_root_password:
    file: ./secrets/db_root_password.txt
  db_password:
    file: ./secrets/db_password.txt
# .gitignore
.env
.env.local
.env.*.local
secrets/
*.pem
*.key

2. SQL Injection 위험 (중간)

취약한 코드:

// ❌ 위험
const query = `SELECT * FROM users WHERE username = '${username}'`;
db.query(query);

// ✅ 안전
const query = 'SELECT * FROM users WHERE username = ?';
db.query(query, [username]);

검토 필요:

  • 모든 동적 쿼리 검토
  • Prepared Statement 사용 확인
  • ORM 도입 검토

3. XSS (Cross-Site Scripting) (중간)

취약한 코드:

// ❌ 위험
element.innerHTML = `<div>${userInput}</div>`;

// ✅ 안전
element.textContent = userInput;
// 또는
const div = document.createElement('div');
div.textContent = userInput;
element.appendChild(div);

검토 필요:

  • innerHTML 사용처 전수 조사
  • 사용자 입력 sanitization
  • Content Security Policy 적용

4. 인증 토큰 보안 (중간)

개선 사항:

// JWT 설정 강화
const token = jwt.sign(
  { userId: user.id },
  process.env.JWT_SECRET,
  {
    expiresIn: '1h',  // 만료 시간 설정
    issuer: 'tkfb-api',
    audience: 'tkfb-client'
  }
);

// Refresh Token 도입
const refreshToken = jwt.sign(
  { userId: user.id, type: 'refresh' },
  process.env.JWT_REFRESH_SECRET,
  { expiresIn: '7d' }
);

// HttpOnly 쿠키 사용
res.cookie('refreshToken', refreshToken, {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  sameSite: 'strict',
  maxAge: 7 * 24 * 60 * 60 * 1000
});

성능 최적화 제안

1. 데이터베이스 쿼리

N+1 쿼리 문제:

// ❌ 비효율 (N+1 쿼리)
const reports = await ReportModel.findAll();
for (const report of reports) {
  report.worker = await WorkerModel.findById(report.worker_id);
  report.project = await ProjectModel.findById(report.project_id);
}

// ✅ 효율적 (JOIN 사용)
const reports = await db.query(`
  SELECT
    r.*,
    w.name AS worker_name,
    p.name AS project_name
  FROM daily_work_reports r
  LEFT 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 ?
`, [startDate, endDate]);

인덱스 추가:

-- 자주 조회되는 컬럼에 인덱스
CREATE INDEX idx_report_date ON daily_work_reports(report_date);
CREATE INDEX idx_worker_id ON daily_work_reports(worker_id);
CREATE INDEX idx_project_id ON daily_work_reports(project_id);

-- 복합 인덱스
CREATE INDEX idx_report_worker_date
ON daily_work_reports(worker_id, report_date);

-- 쿼리 성능 분석
EXPLAIN SELECT * FROM daily_work_reports
WHERE worker_id = 1 AND report_date BETWEEN '2025-01-01' AND '2025-12-31';

2. 프론트엔드 최적화

번들링:

// webpack.config.js
module.exports = {
  entry: './web-ui/js/main.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'web-ui/dist')
  },
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10
        },
        common: {
          minChunks: 2,
          priority: 5,
          reuseExistingChunk: true
        }
      }
    }
  }
};

이미지 최적화:

<!-- Lazy loading -->
<img src="image.jpg" loading="lazy" alt="...">

<!-- Responsive images -->
<img
  srcset="image-320w.jpg 320w,
          image-640w.jpg 640w,
          image-1280w.jpg 1280w"
  sizes="(max-width: 320px) 280px,
         (max-width: 640px) 600px,
         1200px"
  src="image-640w.jpg"
  alt="..."
>

3. 캐싱 전략

서버 사이드:

// Redis 캐싱
const getWorkers = async () => {
  const cacheKey = 'workers:active';

  // 캐시 확인
  const cached = await redis.get(cacheKey);
  if (cached) {
    return JSON.parse(cached);
  }

  // DB 조회
  const workers = await WorkerModel.findActive();

  // 캐시 저장 (1시간)
  await redis.setex(cacheKey, 3600, JSON.stringify(workers));

  return workers;
};

클라이언트 사이드:

// Service Worker로 정적 리소스 캐싱
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('tkfb-v1').then((cache) => {
      return cache.addAll([
        '/',
        '/css/main.css',
        '/js/bundle.js',
        '/images/logo.png'
      ]);
    })
  );
});

📈 코드 메트릭스

복잡도 분석

지표 현재 목표 상태
평균 파일 크기 487줄 <300줄 🔴
평균 함수 크기 42줄 <20줄 🟡
최대 중첩 깊이 7단계 <4단계 🔴
코드 중복률 ~35% <10% 🔴
테스트 커버리지 0% >80% 🔴
JSDoc 커버리지 ~5% >60% 🔴

의존성 분석

백엔드:

{
  "dependencies": {
    "express": "^4.18.2",
    "mysql2": "^3.6.0",
    "jsonwebtoken": "^9.0.2",
    "bcryptjs": "^2.4.3",
    "cors": "^2.8.5",
    "dotenv": "^16.3.1"
  }
}

프론트엔드:

  • jQuery (버전 확인 필요)
  • 외부 CDN 의존성 (FullCalendar, Chart.js 등)

🎯 리팩토링 목표

단기 (1-2개월)

  • synology_deployment 제거
  • 보안 정보 환경변수화
  • index.js 분리 (<200줄)
  • 에러 처리 표준화
  • SELECT * 제거

중기 (3-6개월)

  • 서비스 레이어 완성
  • 큰 파일 모듈화 (<500줄)
  • 테스트 코드 작성 (>50% 커버리지)
  • API 문서화
  • 성능 최적화 (DB 인덱스, 캐싱)

장기 (6-12개월)

  • TypeScript 마이그레이션
  • 프론트엔드 프레임워크 도입
  • ORM 도입 (TypeORM/Prisma)
  • CI/CD 파이프라인 구축
  • 모니터링 시스템 구축

📚 참고 자료

코딩 표준

아키텍처 패턴

  • Clean Architecture
  • Domain-Driven Design (DDD)
  • SOLID Principles

보안


다음 단계: 리팩토링 계획 참고