2026-01-17 13:01:35 +09:00
|
|
|
import {
|
2026-01-21 14:22:45 +09:00
|
|
|
getAlbumDetails,
|
|
|
|
|
getAlbumsWithTracks,
|
|
|
|
|
createAlbum,
|
|
|
|
|
updateAlbum,
|
|
|
|
|
deleteAlbum,
|
|
|
|
|
} from '../../services/album.js';
|
2026-01-17 13:01:35 +09:00
|
|
|
import photosRoutes from './photos.js';
|
|
|
|
|
import teasersRoutes from './teasers.js';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 앨범 라우트
|
|
|
|
|
* GET: 공개, POST/PUT/DELETE: 인증 필요
|
|
|
|
|
*/
|
|
|
|
|
export default async function albumsRoutes(fastify) {
|
|
|
|
|
const { db } = fastify;
|
|
|
|
|
|
|
|
|
|
// 하위 라우트 등록
|
|
|
|
|
fastify.register(photosRoutes);
|
|
|
|
|
fastify.register(teasersRoutes);
|
|
|
|
|
|
|
|
|
|
// ==================== GET (공개) ====================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /api/albums
|
|
|
|
|
*/
|
|
|
|
|
fastify.get('/', {
|
|
|
|
|
schema: {
|
|
|
|
|
tags: ['albums'],
|
|
|
|
|
summary: '전체 앨범 목록 조회',
|
|
|
|
|
},
|
|
|
|
|
}, async () => {
|
2026-01-21 13:42:01 +09:00
|
|
|
return await getAlbumsWithTracks(db);
|
2026-01-17 13:01:35 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /api/albums/by-name/:albumName/track/:trackTitle
|
|
|
|
|
*/
|
|
|
|
|
fastify.get('/by-name/:albumName/track/:trackTitle', {
|
|
|
|
|
schema: {
|
|
|
|
|
tags: ['albums'],
|
|
|
|
|
summary: '앨범명과 트랙명으로 트랙 조회',
|
|
|
|
|
},
|
|
|
|
|
}, async (request, reply) => {
|
|
|
|
|
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) {
|
|
|
|
|
return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const album = albums[0];
|
|
|
|
|
|
|
|
|
|
const [tracks] = await db.query(
|
2026-01-18 21:50:04 +09:00
|
|
|
'SELECT * FROM album_tracks WHERE album_id = ? AND title = ?',
|
2026-01-17 13:01:35 +09:00
|
|
|
[album.id, trackTitle]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (tracks.length === 0) {
|
|
|
|
|
return reply.code(404).send({ error: '트랙을 찾을 수 없습니다.' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const track = tracks[0];
|
|
|
|
|
|
|
|
|
|
const [otherTracks] = await db.query(
|
2026-01-18 21:50:04 +09:00
|
|
|
'SELECT id, track_number, title, is_title_track, duration FROM album_tracks WHERE album_id = ? ORDER BY track_number',
|
2026-01-17 13:01:35 +09:00
|
|
|
[album.id]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...track,
|
|
|
|
|
album: {
|
|
|
|
|
id: album.id,
|
|
|
|
|
title: album.title,
|
|
|
|
|
folder_name: album.folder_name,
|
|
|
|
|
cover_thumb_url: album.cover_thumb_url,
|
|
|
|
|
cover_medium_url: album.cover_medium_url,
|
|
|
|
|
release_date: album.release_date,
|
|
|
|
|
album_type: album.album_type,
|
|
|
|
|
},
|
|
|
|
|
otherTracks,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /api/albums/by-name/:name
|
|
|
|
|
*/
|
|
|
|
|
fastify.get('/by-name/:name', {
|
|
|
|
|
schema: {
|
|
|
|
|
tags: ['albums'],
|
|
|
|
|
summary: '앨범명으로 앨범 조회',
|
|
|
|
|
},
|
|
|
|
|
}, 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) {
|
|
|
|
|
return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' });
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 13:42:01 +09:00
|
|
|
return getAlbumDetails(db, albums[0]);
|
2026-01-17 13:01:35 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /api/albums/:id
|
|
|
|
|
*/
|
|
|
|
|
fastify.get('/:id', {
|
|
|
|
|
schema: {
|
|
|
|
|
tags: ['albums'],
|
|
|
|
|
summary: 'ID로 앨범 조회',
|
|
|
|
|
},
|
|
|
|
|
}, async (request, reply) => {
|
|
|
|
|
const [albums] = await db.query('SELECT * FROM albums WHERE id = ?', [
|
|
|
|
|
request.params.id,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
if (albums.length === 0) {
|
|
|
|
|
return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' });
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 13:42:01 +09:00
|
|
|
return getAlbumDetails(db, albums[0]);
|
2026-01-17 13:01:35 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ==================== POST/PUT/DELETE (인증 필요) ====================
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST /api/albums
|
|
|
|
|
*/
|
|
|
|
|
fastify.post('/', {
|
|
|
|
|
schema: {
|
|
|
|
|
tags: ['albums'],
|
|
|
|
|
summary: '앨범 생성',
|
|
|
|
|
security: [{ bearerAuth: [] }],
|
|
|
|
|
},
|
|
|
|
|
preHandler: [fastify.authenticate],
|
|
|
|
|
}, async (request, reply) => {
|
|
|
|
|
const parts = request.parts();
|
|
|
|
|
let data = null;
|
|
|
|
|
let coverBuffer = null;
|
|
|
|
|
|
|
|
|
|
for await (const part of parts) {
|
|
|
|
|
if (part.type === 'file' && part.fieldname === 'cover') {
|
|
|
|
|
coverBuffer = await part.toBuffer();
|
|
|
|
|
} else if (part.fieldname === 'data') {
|
|
|
|
|
data = JSON.parse(part.value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!data) {
|
|
|
|
|
return reply.code(400).send({ error: '데이터가 필요합니다.' });
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 14:22:45 +09:00
|
|
|
const { title, album_type, release_date, folder_name } = data;
|
2026-01-17 13:01:35 +09:00
|
|
|
|
|
|
|
|
if (!title || !album_type || !release_date || !folder_name) {
|
|
|
|
|
return reply.code(400).send({ error: '필수 필드를 모두 입력해주세요.' });
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 14:22:45 +09:00
|
|
|
return await createAlbum(db, data, coverBuffer);
|
2026-01-17 13:01:35 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* PUT /api/albums/:id
|
|
|
|
|
*/
|
|
|
|
|
fastify.put('/:id', {
|
|
|
|
|
schema: {
|
|
|
|
|
tags: ['albums'],
|
|
|
|
|
summary: '앨범 수정',
|
|
|
|
|
security: [{ bearerAuth: [] }],
|
|
|
|
|
},
|
|
|
|
|
preHandler: [fastify.authenticate],
|
|
|
|
|
}, async (request, reply) => {
|
|
|
|
|
const { id } = request.params;
|
|
|
|
|
const parts = request.parts();
|
|
|
|
|
let data = null;
|
|
|
|
|
let coverBuffer = null;
|
|
|
|
|
|
|
|
|
|
for await (const part of parts) {
|
|
|
|
|
if (part.type === 'file' && part.fieldname === 'cover') {
|
|
|
|
|
coverBuffer = await part.toBuffer();
|
|
|
|
|
} else if (part.fieldname === 'data') {
|
|
|
|
|
data = JSON.parse(part.value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!data) {
|
|
|
|
|
return reply.code(400).send({ error: '데이터가 필요합니다.' });
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-21 14:22:45 +09:00
|
|
|
const result = await updateAlbum(db, id, data, coverBuffer);
|
|
|
|
|
if (!result) {
|
|
|
|
|
return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' });
|
2026-01-17 13:01:35 +09:00
|
|
|
}
|
2026-01-21 14:22:45 +09:00
|
|
|
return result;
|
2026-01-17 13:01:35 +09:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* DELETE /api/albums/:id
|
|
|
|
|
*/
|
|
|
|
|
fastify.delete('/:id', {
|
|
|
|
|
schema: {
|
|
|
|
|
tags: ['albums'],
|
|
|
|
|
summary: '앨범 삭제',
|
|
|
|
|
security: [{ bearerAuth: [] }],
|
|
|
|
|
},
|
|
|
|
|
preHandler: [fastify.authenticate],
|
|
|
|
|
}, async (request, reply) => {
|
|
|
|
|
const { id } = request.params;
|
2026-01-21 14:22:45 +09:00
|
|
|
const result = await deleteAlbum(db, id);
|
|
|
|
|
if (!result) {
|
|
|
|
|
return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' });
|
2026-01-17 13:01:35 +09:00
|
|
|
}
|
2026-01-21 14:22:45 +09:00
|
|
|
return result;
|
2026-01-17 13:01:35 +09:00
|
|
|
});
|
|
|
|
|
}
|