[Team Project] Spotify API 활용한 음악 공유 커뮤니티 (1)

liinyeye·2024년 7월 9일
0

Project

목록 보기
26/44
post-thumbnail

프로젝트 개요

  • 프로젝트 명 : CyTunes 🎵

  • 소개

    • 한 줄 정리 : 음악 공유 커뮤니티로, 사용자들끼리 음악을 추천하고 확인할 수 있으며 검색을 통해 다른 사용자와 아티스트 그리고 노래를 찾아볼 수 있습니다.
    • 내용
      • music community는 사용자들이 음악을 추천하고 확인할 수 있는 공간입니다.
      • 이 커뮤니티에서는 다양한 장르와 아티스트의 음악을 함께 나누며 서로의 음악 취향을 공유할 수 있습니다.
      • 사용자는 검색 기능을 통해 다른 사용자와 아티스트, 그리고 노래를 쉽게 찾아볼 수 있습니다.
      • 또한, 사용자들끼리의 소통을 통해 새로운 음악을 발견하고 감상할 수 있는 기회를 제공합니다.
      • Spoify API를 사용하여 아티스트, 플레이리스트, 트랙 정보 등을 확인할 수 있으며, 플레이어로 음악을 바로 재생할 수 있습니다.
  • 맡은 기능 : 메인 페이지 (2명)

    • 공용 레이아웃
    • spotify api로 플레이리스트 & 트랙 가져와서 ui 그리기 (인기차트, 아티스트, 뮤직 플레이어)
    • 헤더, 푸더, 사이드바
    • 상단 메인 이미지 ui

메인 페이지


Spotify Api로 데이터 불러오기

토큰 가져오기

.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별 트랙은 플레이리스트 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 });
  }
};
profile
웹 프론트엔드 UXUI

0개의 댓글