## 🚨 보안 강화 - 하드코딩된 비밀번호를 환경변수로 전환 - .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>
25 KiB
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: 심각 (즉시 해결 필요)
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 하드코딩된 보안 정보
발견된 위치:
- docker-compose.yml (15-17줄):
MYSQL_ROOT_PASSWORD=tkfb2024!
DB_PASSWORD=hyungi2024!
JWT_SECRET=tkfb_jwt_secret_2024_hyungi_secure_key
- README.md (공개 문서):
- 비밀번호: hyungi_password_2025
- Root 비밀번호: hyungi_root_password_2025
- 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 (직접 호출)
문제점:
- 대부분의 비즈니스 로직이 컨트롤러에 직접 구현
- 코드 재사용 어려움
- 테스트 작성 어려움
서비스 레이어가 있는 모듈:
- authService.js
- emailService.js
- openaiService.js
- reportSubmissionService.js
- tokenService.js
- 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
보안
다음 단계: 리팩토링 계획 참고