2026-01-01 09:32:38 +09:00
import express from "express" ;
import pool from "../lib/db.js" ;
const router = express . Router ( ) ;
2026-01-01 17:23:29 +09:00
// 앨범 상세 정보 조회 헬퍼 함수 (트랙, 티저, 컨셉포토 포함)
async function getAlbumDetails ( album ) {
// 트랙 정보 조회 (가사 포함)
const [ tracks ] = await pool . query (
"SELECT * FROM tracks WHERE album_id = ? ORDER BY track_number" ,
[ album . id ]
) ;
album . tracks = tracks ;
2026-01-13 11:59:12 +09:00
// 티저 이미지/비디오 조회 (3개 해상도 URL + video_url + media_type 포함)
2026-01-01 17:23:29 +09:00
const [ teasers ] = await pool . query (
2026-01-13 11:59:12 +09:00
"SELECT original_url, medium_url, thumb_url, video_url, media_type FROM album_teasers WHERE album_id = ? ORDER BY sort_order" ,
2026-01-01 17:23:29 +09:00
[ album . id ]
) ;
2026-01-02 09:38:04 +09:00
album . teasers = teasers ;
2026-01-01 17:23:29 +09:00
2026-01-02 09:38:04 +09:00
// 컨셉 포토 조회 (멤버 정보 + 3개 해상도 URL + 크기 정보 포함)
2026-01-01 17:23:29 +09:00
const [ photos ] = await pool . query (
` SELECT
2026-01-02 09:38:04 +09:00
p . id , p . original _url , p . medium _url , p . thumb _url , p . photo _type , p . concept _name , p . sort _order ,
p . width , p . height ,
2026-01-01 17:23:29 +09:00
GROUP _CONCAT ( m . name ORDER BY m . id SEPARATOR ', ' ) as members
FROM album _photos p
LEFT JOIN album _photo _members pm ON p . id = pm . photo _id
LEFT JOIN members m ON pm . member _id = m . id
WHERE p . album _id = ?
GROUP BY p . id
ORDER BY p . sort _order ` ,
[ album . id ]
) ;
// 컨셉별로 그룹화
const conceptPhotos = { } ;
for ( const photo of photos ) {
const concept = photo . concept _name || "Default" ;
if ( ! conceptPhotos [ concept ] ) {
conceptPhotos [ concept ] = [ ] ;
}
conceptPhotos [ concept ] . push ( {
id : photo . id ,
2026-01-02 09:38:04 +09:00
original _url : photo . original _url ,
medium _url : photo . medium _url ,
thumb _url : photo . thumb _url ,
width : photo . width ,
height : photo . height ,
2026-01-01 17:23:29 +09:00
type : photo . photo _type ,
members : photo . members ,
sortOrder : photo . sort _order ,
} ) ;
}
album . conceptPhotos = conceptPhotos ;
return album ;
}
2026-01-01 09:32:38 +09:00
// 전체 앨범 조회 (트랙 포함)
router . get ( "/" , async ( req , res ) => {
try {
const [ albums ] = await pool . query (
2026-01-11 22:04:08 +09:00
"SELECT id, title, folder_name, album_type, album_type_short, release_date, cover_original_url, cover_medium_url, cover_thumb_url FROM albums ORDER BY release_date DESC"
2026-01-01 09:32:38 +09:00
) ;
// 각 앨범에 트랙 정보 추가
for ( const album of albums ) {
const [ tracks ] = await pool . query (
"SELECT id, track_number, title, is_title_track, duration, lyricist, composer, arranger FROM tracks WHERE album_id = ? ORDER BY track_number" ,
[ album . id ]
) ;
album . tracks = tracks ;
}
res . json ( albums ) ;
} catch ( error ) {
console . error ( "앨범 조회 오류:" , error ) ;
res . status ( 500 ) . json ( { error : "앨범 정보를 가져오는데 실패했습니다." } ) ;
}
} ) ;
2026-01-12 17:52:27 +09:00
// 앨범명과 트랙명으로 트랙 상세 조회 (더 구체적인 경로이므로 /by-name/:name보다 앞에 배치)
2026-01-12 17:50:48 +09:00
router . get ( "/by-name/:albumName/track/:trackTitle" , async ( req , res ) => {
try {
const albumName = decodeURIComponent ( req . params . albumName ) ;
const trackTitle = decodeURIComponent ( req . params . trackTitle ) ;
// 앨범 조회
const [ albums ] = await pool . query (
"SELECT * FROM albums WHERE folder_name = ? OR title = ?" ,
[ albumName , albumName ]
) ;
if ( albums . length === 0 ) {
return res . status ( 404 ) . json ( { error : "앨범을 찾을 수 없습니다." } ) ;
}
const album = albums [ 0 ] ;
// 해당 앨범의 트랙 조회
const [ tracks ] = await pool . query (
"SELECT * FROM tracks WHERE album_id = ? AND title = ?" ,
[ album . id , trackTitle ]
) ;
if ( tracks . length === 0 ) {
return res . status ( 404 ) . json ( { error : "트랙을 찾을 수 없습니다." } ) ;
}
const track = tracks [ 0 ] ;
// 앨범의 다른 트랙 목록 조회
const [ otherTracks ] = await pool . query (
"SELECT id, track_number, title, is_title_track, duration FROM tracks WHERE album_id = ? ORDER BY track_number" ,
[ album . id ]
) ;
res . json ( {
... 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 ,
} ) ;
} catch ( error ) {
console . error ( "트랙 조회 오류:" , error ) ;
res . status ( 500 ) . json ( { error : "트랙 정보를 가져오는데 실패했습니다." } ) ;
}
} ) ;
2026-01-12 17:52:27 +09:00
// 앨범 folder_name 또는 title로 조회
router . get ( "/by-name/:name" , async ( req , res ) => {
try {
const name = decodeURIComponent ( req . params . name ) ;
// folder_name 또는 title로 검색 (PC는 title, 모바일은 folder_name 사용)
const [ albums ] = await pool . query (
"SELECT * FROM albums WHERE folder_name = ? OR title = ?" ,
[ name , name ]
) ;
if ( albums . length === 0 ) {
return res . status ( 404 ) . json ( { error : "앨범을 찾을 수 없습니다." } ) ;
}
const album = await getAlbumDetails ( albums [ 0 ] ) ;
res . json ( album ) ;
} catch ( error ) {
console . error ( "앨범 조회 오류:" , error ) ;
res . status ( 500 ) . json ( { error : "앨범 정보를 가져오는데 실패했습니다." } ) ;
}
} ) ;
// ID로 앨범 조회
router . get ( "/:id" , async ( req , res ) => {
try {
const [ albums ] = await pool . query ( "SELECT * FROM albums WHERE id = ?" , [
req . params . id ,
] ) ;
if ( albums . length === 0 ) {
return res . status ( 404 ) . json ( { error : "앨범을 찾을 수 없습니다." } ) ;
}
const album = await getAlbumDetails ( albums [ 0 ] ) ;
res . json ( album ) ;
} catch ( error ) {
console . error ( "앨범 조회 오류:" , error ) ;
res . status ( 500 ) . json ( { error : "앨범 정보를 가져오는데 실패했습니다." } ) ;
}
} ) ;
2026-01-01 09:32:38 +09:00
export default router ;