- JWT_SECRET 환경변수 필수화 (기본값 제거) - 앨범 삭제 시 S3 파일(사진, 티저, 비디오) 함께 삭제 - 앨범 삭제 시 관련 DB 테이블 정리 (album_photo_members, album_photos, album_teasers) - Meilisearch latest 태그로 변경 (v1.6 → latest) - 코드 리뷰 문서 추가 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
5.3 KiB
5.3 KiB
코드 리뷰 결과
작성일: 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
// 현재 - 기본값 노출 위험
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 리소스 제한 없음
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에 에러 헬퍼가 있지만 라우트에서 직접 처리.
// 현재 - 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
// 현재
'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
// 현재
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
// 삭제 대상 (마이그레이션 완료 후)
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
{
"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 | docker-compose.yml |
✅ 완료 | |
| 7 | 테스트 코드 작성 | 전체 | 높음 |
참고
- 이 문서는 app 폴더를 제외한 코드 리뷰 결과입니다.
- 보안 관련 항목은 가능한 빨리 적용을 권장합니다.