# 코드 리뷰 결과 > 작성일: 2025-01-23 > 범위: app 폴더 제외한 backend, frontend, docker 설정 ## 잘 된 점 ### 백엔드 구조 - Fastify 플러그인 패턴을 적절히 활용 - 서비스/라우트 계층 분리가 명확함 - `withTransaction` 유틸리티로 트랜잭션 처리 일관성 확보 - N+1 쿼리 최적화 적용 (`getAlbumsWithTracks`) ### 캐싱 - Redis 캐시 유틸리티(`utils/cache.js`)가 깔끔하게 구현됨 - TTL 상수화로 관리 용이 - 캐시 키 생성 헬퍼 함수 제공 ### 프론트엔드 - API 클라이언트의 에러 처리와 인증 토큰 관리가 잘 구현됨 - PC/Mobile 분리 구조 적용 - Zustand를 활용한 상태 관리 --- ## 개선 필요 사항 ### 1. 보안 (우선순위: 높음) #### 1.1 JWT Secret 하드코딩 **파일**: `backend/src/config/index.js:37` ```javascript // 현재 - 기본값 노출 위험 secret: process.env.JWT_SECRET || 'fromis9-admin-secret-key-2026', // 권장 - 환경변수 필수화 secret: process.env.JWT_SECRET, ``` 서버 시작 시 JWT_SECRET 환경변수 존재 여부를 검증하는 로직 추가 필요. ### 2. Docker 설정 (우선순위: 중간) #### 2.1 Meilisearch 버전 ~~현재 `v1.6` 사용 중. 최신 버전으로 업데이트 권장.~~ ✅ `latest`로 변경 완료 (v1.33.1) #### 2.2 리소스 제한 없음 ```yaml redis: # ... 기존 설정 deploy: resources: limits: memory: 256M meilisearch: # ... 기존 설정 deploy: resources: limits: memory: 512M ``` #### 2.3 프로덕션 Dockerfile 현재 개발용 Dockerfile만 활성화됨. 배포 시 주석 해제 또는 multi-stage build 적용 필요. --- ### 3. 백엔드 코드 (우선순위: 중간) #### 3.1 에러 유틸리티 미활용 `src/utils/error.js`에 에러 헬퍼가 있지만 라우트에서 직접 처리. ```javascript // 현재 - routes/albums/index.js return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' }); // 권장 import { notFound } from '../../utils/error.js'; return notFound(reply, '앨범을 찾을 수 없습니다.'); ``` #### 3.2 SELECT * 사용 **파일**: `services/album.js:16-19` ```javascript // 현재 'SELECT * FROM albums WHERE folder_name = ? OR title = ?' // 권장 'SELECT id, title, folder_name, album_type, album_type_short, release_date, cover_original_url, cover_medium_url, cover_thumb_url, description FROM albums WHERE folder_name = ? OR title = ?' ``` #### 3.3 앨범 삭제 시 관련 데이터 누락 **파일**: `services/album.js:301-312` ```javascript // 현재 await connection.query('DELETE FROM album_tracks WHERE album_id = ?', [id]); await connection.query('DELETE FROM albums WHERE id = ?', [id]); // 수정 필요 - 관련 테이블 모두 삭제 await connection.query( 'DELETE FROM album_photo_members WHERE photo_id IN (SELECT id FROM album_photos WHERE album_id = ?)', [id] ); await connection.query('DELETE FROM album_photos WHERE album_id = ?', [id]); await connection.query('DELETE FROM album_teasers WHERE album_id = ?', [id]); await connection.query('DELETE FROM album_tracks WHERE album_id = ?', [id]); await connection.query('DELETE FROM albums WHERE id = ?', [id]); ``` ### 4. 프론트엔드 코드 (우선순위: 낮음) #### 4.1 App.jsx 라우트 분리 현재 194줄로 너무 큼. 라우트 분리 권장. ``` src/ ├── routes/ │ ├── index.jsx # 라우트 통합 │ ├── pc.jsx # PC 라우트 │ ├── mobile.jsx # Mobile 라우트 │ └── admin.jsx # Admin 라우트 └── App.jsx # 간소화 ``` #### 4.2 레거시 export 정리 **파일**: `api/client.js:147-155` ```javascript // 삭제 대상 (마이그레이션 완료 후) export const get = api.get; export const post = api.post; export const put = api.put; export const del = api.del; export const authGet = authApi.get; export const authPost = authApi.post; export const authPut = authApi.put; export const authDel = authApi.del; ``` #### 4.3 개발 도구 미설정 **파일**: `frontend/package.json` ```json { "scripts": { "lint": "eslint src --ext .js,.jsx", "lint:fix": "eslint src --ext .js,.jsx --fix", "test": "vitest", "test:coverage": "vitest --coverage" }, "devDependencies": { "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.0", "eslint-plugin-react-hooks": "^4.6.0", "vitest": "^1.6.0" } } ``` --- ### 5. 문서 (우선순위: 낮음) #### 5.1 migration.md 파일 누락 `CLAUDE.md`와 `README.md`에서 `docs/migration.md`를 참조하지만 실제 파일이 없음. #### 5.2 환경별 설정 문서화 개발/스테이징/프로덕션 환경별 설정 가이드 필요. --- ## 개선 우선순위 요약 | 순위 | 항목 | 파일 | 난이도 | |:----:|------|------|:------:| | 1 | JWT Secret 기본값 제거 | `config/index.js` | 낮음 | | 2 | 앨범 삭제 시 관련 테이블 정리 | `services/album.js` | 중간 | | 3 | 에러 유틸리티 통일 | 라우트 파일들 | 중간 | | 4 | App.jsx 라우트 분리 | `App.jsx` | 중간 | | 5 | 레거시 export 정리 | `api/client.js` | 낮음 | | 6 | ~~Meilisearch 버전 업데이트~~ | `docker-compose.yml` | ✅ 완료 | | 7 | 테스트 코드 작성 | 전체 | 높음 | --- ## 참고 - 이 문서는 app 폴더를 제외한 코드 리뷰 결과입니다. - 보안 관련 항목은 가능한 빨리 적용을 권장합니다.