From ae898d01ad68d6bcfd45da2ed985cf354c0b0156 Mon Sep 17 00:00:00 2001 From: caadiq Date: Thu, 1 Jan 2026 17:23:29 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AlbumGallery.jsx: useEffect 의존성 버그 수정 (id→name) - AlbumGallery.jsx: 미사용 useMemo import 제거 - albums.js: 중복 코드를 getAlbumDetails 헬퍼 함수로 추출 - albums.js: 163줄 → 115줄 (48줄 감소) --- backend/routes/albums.js | 152 +++++++++---------------- frontend/src/pages/pc/AlbumGallery.jsx | 4 +- 2 files changed, 56 insertions(+), 100 deletions(-) diff --git a/backend/routes/albums.js b/backend/routes/albums.js index 14ce709..bbc0bcb 100644 --- a/backend/routes/albums.js +++ b/backend/routes/albums.js @@ -3,10 +3,59 @@ import pool from "../lib/db.js"; const router = express.Router(); +// 앨범 상세 정보 조회 헬퍼 함수 (트랙, 티저, 컨셉포토 포함) +async function getAlbumDetails(album) { + // 트랙 정보 조회 (가사 포함) + const [tracks] = await pool.query( + "SELECT * FROM tracks WHERE album_id = ? ORDER BY track_number", + [album.id] + ); + album.tracks = tracks; + + // 티저 이미지 조회 + const [teasers] = await pool.query( + "SELECT image_url FROM album_teasers WHERE album_id = ? ORDER BY sort_order", + [album.id] + ); + album.teasers = teasers.map((t) => t.image_url); + + // 컨셉 포토 조회 (멤버 정보 포함) + const [photos] = await pool.query( + `SELECT + p.id, p.photo_url, p.photo_type, p.concept_name, p.sort_order, + 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, + url: photo.photo_url, + type: photo.photo_type, + members: photo.members, + sortOrder: photo.sort_order, + }); + } + album.conceptPhotos = conceptPhotos; + + return album; +} + // 전체 앨범 조회 (트랙 포함) router.get("/", async (req, res) => { try { - // 앨범 목록 조회 const [albums] = await pool.query( "SELECT id, title, album_type, album_type_short, release_date, cover_url FROM albums ORDER BY release_date DESC" ); @@ -27,10 +76,9 @@ router.get("/", async (req, res) => { } }); -// 앨범명(slug)으로 조회 +// 앨범명으로 조회 router.get("/by-name/:name", async (req, res) => { try { - // URL 디코딩된 앨범명으로 조회 const albumName = decodeURIComponent(req.params.name); const [albums] = await pool.query("SELECT * FROM albums WHERE title = ?", [ albumName, @@ -40,53 +88,7 @@ router.get("/by-name/:name", async (req, res) => { return res.status(404).json({ error: "앨범을 찾을 수 없습니다." }); } - const album = albums[0]; - - // 트랙 정보 조회 (가사 포함) - const [tracks] = await pool.query( - "SELECT * FROM tracks WHERE album_id = ? ORDER BY track_number", - [album.id] - ); - album.tracks = tracks; - - // 티저 이미지 조회 - const [teasers] = await pool.query( - "SELECT image_url FROM album_teasers WHERE album_id = ? ORDER BY sort_order", - [album.id] - ); - album.teasers = teasers.map((t) => t.image_url); - - // 컨셉 포토 조회 (멤버 정보 포함) - const [photos] = await pool.query( - `SELECT - p.id, p.photo_url, p.photo_type, p.concept_name, p.sort_order, - 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, - url: photo.photo_url, - type: photo.photo_type, - members: photo.members, - sortOrder: photo.sort_order, - }); - } - album.conceptPhotos = conceptPhotos; - + const album = await getAlbumDetails(albums[0]); res.json(album); } catch (error) { console.error("앨범 조회 오류:", error); @@ -94,7 +96,7 @@ router.get("/by-name/:name", async (req, res) => { } }); -// 특정 앨범 조회 (트랙 및 상세 정보 포함) +// ID로 앨범 조회 router.get("/:id", async (req, res) => { try { const [albums] = await pool.query("SELECT * FROM albums WHERE id = ?", [ @@ -105,53 +107,7 @@ router.get("/:id", async (req, res) => { return res.status(404).json({ error: "앨범을 찾을 수 없습니다." }); } - const album = albums[0]; - - // 트랙 정보 조회 (가사 포함) - const [tracks] = await pool.query( - "SELECT * FROM tracks WHERE album_id = ? ORDER BY track_number", - [album.id] - ); - album.tracks = tracks; - - // 티저 이미지 조회 - const [teasers] = await pool.query( - "SELECT image_url FROM album_teasers WHERE album_id = ? ORDER BY sort_order", - [album.id] - ); - album.teasers = teasers.map((t) => t.image_url); - - // 컨셉 포토 조회 (멤버 정보 포함) - const [photos] = await pool.query( - `SELECT - p.id, p.photo_url, p.photo_type, p.concept_name, p.sort_order, - 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, - url: photo.photo_url, - type: photo.photo_type, - members: photo.members, - sortOrder: photo.sort_order, - }); - } - album.conceptPhotos = conceptPhotos; - + const album = await getAlbumDetails(albums[0]); res.json(album); } catch (error) { console.error("앨범 조회 오류:", error); diff --git a/frontend/src/pages/pc/AlbumGallery.jsx b/frontend/src/pages/pc/AlbumGallery.jsx index 9db000e..e6bc633 100644 --- a/frontend/src/pages/pc/AlbumGallery.jsx +++ b/frontend/src/pages/pc/AlbumGallery.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback, useMemo } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; import { X, ChevronLeft, ChevronRight, Download } from 'lucide-react'; @@ -72,7 +72,7 @@ function AlbumGallery() { console.error('앨범 데이터 로드 오류:', error); setLoading(false); }); - }, [id]); + }, [name]); // 라이트박스 열기 const openLightbox = (index) => {