프로젝트 명 : CyTunes 🎵
소개
맡은 기능 : 메인 페이지 (2명)
메인 페이지
.env.local 파일
SPOTIFY_CLIENT_ID=본인 프로젝트 id
SPOTIFY_CLIENT_SECRET=본인 프로젝트 시크릿 키
lib/spotify.ts 파일
현재는 api를 요청할 때마다 api토큰을 새로 발급해서 가져오는 로직인데, 비효율적인 코드라 이후에 액세스토큰을 스토리지에 저장하는 방식으로 변경할 예정이다.
import axios from "axios";
export const getAccessToken = async (): Promise<string> => {
const client_id = process.env.SPOTIFY_CLIENT_ID;
const client_secret = process.env.SPOTIFY_CLIENT_SECRET;
if (!client_id || !client_secret) {
throw new Error("Spotify credentials are not set.");
}
const response = await axios.post("https://accounts.spotify.com/api/token", "grant_type=client_credentials", {
headers: {
Authorization: "Basic " + Buffer.from(client_id + ":" + client_secret).toString("base64")
}
});
return response.data.access_token;
};
트랙 데이터를 가져오기 위해서는 트랙별 id가 필요하다. 따라서 props로 params 객체를 받아 id 값을 확인할 수 있다.
import { getAccessToken } from "@/lib/spotify";
import { SpotifyTrack } from "@/types/spotify.type";
import axios from "axios";
import { NextRequest, NextResponse } from "next/server";
export const GET = async (request: NextRequest, { params }: { params: { id: string } }) => {
try {
const token = await getAccessToken();
const response = await axios.get<SpotifyTrack>(`https://api.spotify.com/v1/tracks/${params.id}`, {
headers: { Authorization: `Bearer ${token}` },
params: { market: "KR" }
});
return NextResponse.json(response.data);
} catch (error) {
console.error("Error fetching track:", error);
return NextResponse.json({ error: "Failed to fetch track data" }, { status: 500 });
}
};
해당 플레이리스트 데이터를 별도로 id가 필요하지 않기 때문에 props를 별도로 받아올 필요가 없다. 다만 받아온 데이터가 배열 형태이기 때문에 데이터를 전달하기 이전에 map매서드로 원하는 데이터를 객체 형태로 지정해서 넘겨주면 된다.
import { getAccessToken } from "@/lib/spotify";
import { SpotifyFeaturedPlaylists } from "@/types/spotify.type";
import axios from "axios";
import { NextResponse } from "next/server";
export const GET = async () => {
try {
const token = await getAccessToken();
const response = await axios.get<SpotifyFeaturedPlaylists>(`https://api.spotify.com/v1/browse/featured-playlists`, {
headers: { Authorization: `Bearer ${token}` },
params: {
country: "KR",
limit: 10,
locale: "ko_KR"
}
});
const featuredPlaylists = response.data.playlists.items.map((playlist) => ({
id: playlist.id,
name: playlist.name,
description: playlist.description,
imageUrl: playlist.images[0]?.url,
tracksCount: playlist.tracks.total,
trackLink: playlist.external_urls.spotify
}));
return NextResponse.json(featuredPlaylists);
} catch (error) {
console.error("Error fetching track:", error);
return NextResponse.json({ error: "Failed to fetch playlists data" }, { status: 500 });
}
};
플레이리스트 id별 트랙은 플레이리스트 id와 플레이리스트 데이터 모두 필요하다. 따라서 병렬로 api를 처리해주기 위해 Promise.all을 사용했다.
만약 플레이리스트에 들어간 트랙만 필요하다면 Promise.all을 사용할 필요 없이 for...of문을 사용해 https://api.spotify.com/v1/playlists/${playlistId}/tracks
api만 호출해서 데이터를 받아오면 된다.
다만 플레이리스트 id는 원하는 플레이리스트만 가져오기 위해 id값을 supabase에 넣고 관리해주기로 했다. 현재는 테스트를 위해 하드 코딩으로 넣어뒀지만, 이후에는 supabase에서 id값을 가져오는 로직을 추가할 예정이다.
import { getAccessToken } from "@/lib/spotify";
import { SpotifyPlaylist, SpotifyTrack, SpotifyPlaylistTracks } from "@/types/spotify.type";
import axios from "axios";
import { NextResponse } from "next/server";
// 예시 id 가져와야함.
const PLAYLIST_IDS = ["56AF0dTLXpcrAYfJhMSAdt", "1Owx9OwxqogNfpSu8yIWKx"];
export const GET = async () => {
const accessToken = await getAccessToken();
if (!accessToken) {
console.error("Access token is missing");
return NextResponse.json({ error: "Access token is missing" }, { status: 500 });
}
try {
const playlistsWithTracks = await Promise.all(
PLAYLIST_IDS.map(async (playlistId) => {
try {
const [playlistResponse, tracksResponse] = await Promise.all([
axios.get<SpotifyPlaylist>(`https://api.spotify.com/v1/playlists/${playlistId}`, {
headers: { Authorization: `Bearer ${accessToken}` }
}),
axios.get<{ items: { track: SpotifyTrack }[] }>(
`https://api.spotify.com/v1/playlists/${playlistId}/tracks`,
{
headers: { Authorization: `Bearer ${accessToken}` },
params: {
fields: "items(track(id,name,preview_url,external_urls,artists(id,name),album(id,name,images)))",
limit: 8
}
}
)
]);
const processedPlaylist: SpotifyPlaylistTracks = {
id: playlistResponse.data.id,
name: playlistResponse.data.name,
external_urls: {
spotify: playlistResponse.data.external_urls.spotify
},
tracks: tracksResponse.data.items.map((item) => ({
...item.track,
preview_url: item.track.preview_url ?? "none",
external_urls: {
spotify: item.track.external_urls.spotify
}
}))
};
return processedPlaylist;
} catch (error) {
console.error(`Error fetching playlist ${playlistId}:`, error);
return null; // 개별 플레이리스트 오류 시 null 반환
}
})
);
// null 값 (오류 발생한 플레이리스트) 제거
const validPlaylists = playlistsWithTracks.filter((playlist) => playlist !== null);
return NextResponse.json(validPlaylists);
} catch (error) {
console.error("Error fetching track:", error);
return NextResponse.json({ error: "Failed to fetch playlists data" }, { status: 500 });
}
};