refactor(backend): 17단계 중복 코드 제거 - 멤버/앨범 조회 서비스 분리
- services/member.js 생성: getAllMembers, getMemberByName, getMemberBasicByName - services/album.js에 getAlbumByName, getAlbumById 추가 - routes/members/index.js 서비스 호출로 변경 (약 50줄 감소) - routes/albums/index.js 서비스 호출로 변경 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5cc258b009
commit
b0ac0e51e4
5 changed files with 150 additions and 102 deletions
|
|
@ -1,6 +1,8 @@
|
|||
import {
|
||||
getAlbumDetails,
|
||||
getAlbumsWithTracks,
|
||||
getAlbumByName,
|
||||
getAlbumById,
|
||||
createAlbum,
|
||||
updateAlbum,
|
||||
deleteAlbum,
|
||||
|
|
@ -62,17 +64,11 @@ export default async function albumsRoutes(fastify) {
|
|||
const albumName = decodeURIComponent(request.params.albumName);
|
||||
const trackTitle = decodeURIComponent(request.params.trackTitle);
|
||||
|
||||
const [albums] = await db.query(
|
||||
'SELECT * FROM albums WHERE folder_name = ? OR title = ?',
|
||||
[albumName, albumName]
|
||||
);
|
||||
|
||||
if (albums.length === 0) {
|
||||
const album = await getAlbumByName(db, albumName);
|
||||
if (!album) {
|
||||
return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
const album = albums[0];
|
||||
|
||||
const [tracks] = await db.query(
|
||||
'SELECT * FROM album_tracks WHERE album_id = ? AND title = ?',
|
||||
[album.id, trackTitle]
|
||||
|
|
@ -124,18 +120,11 @@ export default async function albumsRoutes(fastify) {
|
|||
},
|
||||
},
|
||||
}, async (request, reply) => {
|
||||
const name = decodeURIComponent(request.params.name);
|
||||
|
||||
const [albums] = await db.query(
|
||||
'SELECT * FROM albums WHERE folder_name = ? OR title = ?',
|
||||
[name, name]
|
||||
);
|
||||
|
||||
if (albums.length === 0) {
|
||||
const album = await getAlbumByName(db, decodeURIComponent(request.params.name));
|
||||
if (!album) {
|
||||
return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
return getAlbumDetails(db, albums[0]);
|
||||
return getAlbumDetails(db, album);
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
@ -152,15 +141,11 @@ export default async function albumsRoutes(fastify) {
|
|||
},
|
||||
},
|
||||
}, async (request, reply) => {
|
||||
const [albums] = await db.query('SELECT * FROM albums WHERE id = ?', [
|
||||
request.params.id,
|
||||
]);
|
||||
|
||||
if (albums.length === 0) {
|
||||
const album = await getAlbumById(db, request.params.id);
|
||||
if (!album) {
|
||||
return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' });
|
||||
}
|
||||
|
||||
return getAlbumDetails(db, albums[0]);
|
||||
return getAlbumDetails(db, album);
|
||||
});
|
||||
|
||||
// ==================== POST/PUT/DELETE (인증 필요) ====================
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { uploadMemberImage } from '../../services/image.js';
|
||||
import { getAllMembers, getMemberByName, getMemberBasicByName } from '../../services/member.js';
|
||||
|
||||
/**
|
||||
* 멤버 라우트
|
||||
|
|
@ -18,39 +19,7 @@ export default async function membersRoutes(fastify, opts) {
|
|||
},
|
||||
}, async (request, reply) => {
|
||||
try {
|
||||
const [members] = await db.query(`
|
||||
SELECT
|
||||
m.id, m.name, m.name_en, m.birth_date, m.instagram, m.image_id, m.is_former,
|
||||
i.original_url as image_original,
|
||||
i.medium_url as image_medium,
|
||||
i.thumb_url as image_thumb
|
||||
FROM members m
|
||||
LEFT JOIN images i ON m.image_id = i.id
|
||||
ORDER BY m.is_former ASC, m.id ASC
|
||||
`);
|
||||
|
||||
// 별명 조회
|
||||
const [nicknames] = await db.query(
|
||||
'SELECT member_id, nickname FROM member_nicknames'
|
||||
);
|
||||
|
||||
// 멤버별 별명 매핑
|
||||
const nicknameMap = {};
|
||||
for (const n of nicknames) {
|
||||
if (!nicknameMap[n.member_id]) {
|
||||
nicknameMap[n.member_id] = [];
|
||||
}
|
||||
nicknameMap[n.member_id].push(n.nickname);
|
||||
}
|
||||
|
||||
// 멤버 데이터에 별명 추가
|
||||
const result = members.map(m => ({
|
||||
...m,
|
||||
nicknames: nicknameMap[m.id] || [],
|
||||
image_url: m.image_thumb || m.image_medium || m.image_original,
|
||||
}));
|
||||
|
||||
return result;
|
||||
return await getAllMembers(db);
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: '멤버 목록 조회 실패' });
|
||||
|
|
@ -73,37 +42,12 @@ export default async function membersRoutes(fastify, opts) {
|
|||
},
|
||||
},
|
||||
}, async (request, reply) => {
|
||||
const { name } = request.params;
|
||||
|
||||
try {
|
||||
const [members] = await db.query(`
|
||||
SELECT
|
||||
m.id, m.name, m.name_en, m.birth_date, m.instagram, m.image_id, m.is_former,
|
||||
i.original_url as image_original,
|
||||
i.medium_url as image_medium,
|
||||
i.thumb_url as image_thumb
|
||||
FROM members m
|
||||
LEFT JOIN images i ON m.image_id = i.id
|
||||
WHERE m.name = ?
|
||||
`, [decodeURIComponent(name)]);
|
||||
|
||||
if (members.length === 0) {
|
||||
const member = await getMemberByName(db, decodeURIComponent(request.params.name));
|
||||
if (!member) {
|
||||
return reply.code(404).send({ error: '멤버를 찾을 수 없습니다' });
|
||||
}
|
||||
|
||||
const member = members[0];
|
||||
|
||||
// 별명 조회
|
||||
const [nicknames] = await db.query(
|
||||
'SELECT nickname FROM member_nicknames WHERE member_id = ?',
|
||||
[member.id]
|
||||
);
|
||||
|
||||
return {
|
||||
...member,
|
||||
nicknames: nicknames.map(n => n.nickname),
|
||||
image_url: member.image_original || member.image_medium || member.image_thumb,
|
||||
};
|
||||
return member;
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: '멤버 조회 실패' });
|
||||
|
|
@ -134,17 +78,13 @@ export default async function membersRoutes(fastify, opts) {
|
|||
|
||||
try {
|
||||
// 기존 멤버 조회
|
||||
const [existing] = await db.query(
|
||||
'SELECT id, image_id FROM members WHERE name = ?',
|
||||
[decodedName]
|
||||
);
|
||||
|
||||
if (existing.length === 0) {
|
||||
const existing = await getMemberBasicByName(db, decodedName);
|
||||
if (!existing) {
|
||||
return reply.code(404).send({ error: '멤버를 찾을 수 없습니다' });
|
||||
}
|
||||
|
||||
const memberId = existing[0].id;
|
||||
let imageId = existing[0].image_id;
|
||||
const memberId = existing.id;
|
||||
let imageId = existing.image_id;
|
||||
|
||||
// multipart 데이터 파싱
|
||||
const parts = request.parts();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,31 @@
|
|||
import { uploadAlbumCover, deleteAlbumCover } from './image.js';
|
||||
import { withTransaction } from '../utils/transaction.js';
|
||||
|
||||
/**
|
||||
* 앨범명 또는 폴더명으로 앨범 조회
|
||||
* @param {object} db - 데이터베이스 연결
|
||||
* @param {string} name - 앨범명 또는 폴더명
|
||||
* @returns {object|null} 앨범 정보 또는 null
|
||||
*/
|
||||
export async function getAlbumByName(db, name) {
|
||||
const [albums] = await db.query(
|
||||
'SELECT * FROM albums WHERE folder_name = ? OR title = ?',
|
||||
[name, name]
|
||||
);
|
||||
return albums.length > 0 ? albums[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ID로 앨범 조회
|
||||
* @param {object} db - 데이터베이스 연결
|
||||
* @param {number} id - 앨범 ID
|
||||
* @returns {object|null} 앨범 정보 또는 null
|
||||
*/
|
||||
export async function getAlbumById(db, id) {
|
||||
const [albums] = await db.query('SELECT * FROM albums WHERE id = ?', [id]);
|
||||
return albums.length > 0 ? albums[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 앨범 상세 정보 조회 (트랙, 티저, 컨셉포토 포함)
|
||||
* @param {object} db - 데이터베이스 연결
|
||||
|
|
|
|||
94
backend/src/services/member.js
Normal file
94
backend/src/services/member.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* 멤버 서비스
|
||||
* 멤버 관련 비즈니스 로직
|
||||
*/
|
||||
|
||||
/**
|
||||
* 전체 멤버 목록 조회 (별명 포함)
|
||||
* @param {object} db - 데이터베이스 연결
|
||||
* @returns {array} 멤버 목록
|
||||
*/
|
||||
export async function getAllMembers(db) {
|
||||
const [members] = await db.query(`
|
||||
SELECT
|
||||
m.id, m.name, m.name_en, m.birth_date, m.instagram, m.image_id, m.is_former,
|
||||
i.original_url as image_original,
|
||||
i.medium_url as image_medium,
|
||||
i.thumb_url as image_thumb
|
||||
FROM members m
|
||||
LEFT JOIN images i ON m.image_id = i.id
|
||||
ORDER BY m.is_former ASC, m.id ASC
|
||||
`);
|
||||
|
||||
// 별명 조회
|
||||
const [nicknames] = await db.query(
|
||||
'SELECT member_id, nickname FROM member_nicknames'
|
||||
);
|
||||
|
||||
// 멤버별 별명 매핑
|
||||
const nicknameMap = {};
|
||||
for (const n of nicknames) {
|
||||
if (!nicknameMap[n.member_id]) {
|
||||
nicknameMap[n.member_id] = [];
|
||||
}
|
||||
nicknameMap[n.member_id].push(n.nickname);
|
||||
}
|
||||
|
||||
// 멤버 데이터에 별명 추가
|
||||
return members.map(m => ({
|
||||
...m,
|
||||
nicknames: nicknameMap[m.id] || [],
|
||||
image_url: m.image_thumb || m.image_medium || m.image_original,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 이름으로 멤버 조회 (별명 포함)
|
||||
* @param {object} db - 데이터베이스 연결
|
||||
* @param {string} name - 멤버 이름
|
||||
* @returns {object|null} 멤버 정보 또는 null
|
||||
*/
|
||||
export async function getMemberByName(db, name) {
|
||||
const [members] = await db.query(`
|
||||
SELECT
|
||||
m.id, m.name, m.name_en, m.birth_date, m.instagram, m.image_id, m.is_former,
|
||||
i.original_url as image_original,
|
||||
i.medium_url as image_medium,
|
||||
i.thumb_url as image_thumb
|
||||
FROM members m
|
||||
LEFT JOIN images i ON m.image_id = i.id
|
||||
WHERE m.name = ?
|
||||
`, [name]);
|
||||
|
||||
if (members.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const member = members[0];
|
||||
|
||||
// 별명 조회
|
||||
const [nicknames] = await db.query(
|
||||
'SELECT nickname FROM member_nicknames WHERE member_id = ?',
|
||||
[member.id]
|
||||
);
|
||||
|
||||
return {
|
||||
...member,
|
||||
nicknames: nicknames.map(n => n.nickname),
|
||||
image_url: member.image_original || member.image_medium || member.image_thumb,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* ID로 멤버 기본 정보 조회 (수정용)
|
||||
* @param {object} db - 데이터베이스 연결
|
||||
* @param {string} name - 멤버 이름
|
||||
* @returns {object|null} 멤버 기본 정보 또는 null
|
||||
*/
|
||||
export async function getMemberBasicByName(db, name) {
|
||||
const [members] = await db.query(
|
||||
'SELECT id, image_id FROM members WHERE name = ?',
|
||||
[name]
|
||||
);
|
||||
return members.length > 0 ? members[0] : null;
|
||||
}
|
||||
|
|
@ -172,13 +172,17 @@
|
|||
|
||||
---
|
||||
|
||||
### 17단계: 중복 코드 제거 (멤버 조회) 🔄 진행 예정
|
||||
- [ ] 멤버 조회 로직을 서비스로 분리
|
||||
- [ ] 앨범 존재 확인 로직 통합
|
||||
### 17단계: 중복 코드 제거 (멤버 조회) ✅ 완료
|
||||
- [x] 멤버 조회 로직을 서비스로 분리
|
||||
- [x] 앨범 존재 확인 로직 통합
|
||||
|
||||
**대상 파일:**
|
||||
- `src/services/member.js` - 신규 생성
|
||||
- `src/routes/members/index.js` - 서비스 호출로 변경
|
||||
**생성된 파일:**
|
||||
- `src/services/member.js` - getAllMembers, getMemberByName, getMemberBasicByName
|
||||
|
||||
**수정된 파일:**
|
||||
- `src/services/album.js` - getAlbumByName, getAlbumById 추가
|
||||
- `src/routes/members/index.js` - 서비스 호출로 변경 (약 50줄 감소)
|
||||
- `src/routes/albums/index.js` - 서비스 호출로 변경
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -239,8 +243,8 @@
|
|||
| 13단계 | Swagger/OpenAPI 문서화 | ✅ 완료 |
|
||||
| 14단계 | 입력 검증 강화 (JSON Schema) | ✅ 완료 |
|
||||
| 15단계 | 스키마 파일 분리 | ✅ 완료 |
|
||||
| 16단계 | 에러 처리 일관성 | 🔄 진행 예정 |
|
||||
| 17단계 | 중복 코드 제거 (멤버 조회) | 🔄 진행 예정 |
|
||||
| 16단계 | 에러 처리 일관성 | ✅ 완료 |
|
||||
| 17단계 | 중복 코드 제거 (멤버 조회) | ✅ 완료 |
|
||||
| 18단계 | 이미지 처리 최적화 | 🔄 진행 예정 |
|
||||
| 19단계 | Redis 캐시 확대 | 🔄 진행 예정 |
|
||||
| 20단계 | 서비스 레이어 확대 | 🔄 진행 예정 |
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue