[보드픽] API → Mock up Data로 변경 (2) - 토큰 관련

zzincode·2025년 5월 2일

스위프

목록 보기
12/12
post-thumbnail

서버가 작동하던 홈페이지였을 때, 사용자의 토큰여부에 따라 작동하던 기능들이 였으나 서버가 작동하지 않아 토큰 여부와 상관없이 작동하도록 임시대체하였습니다.

api 이름url대체 여부
Pick on/off/api/pick/{보드게임id}token 기능 생략
MyPick 리스트/api/picktoken 기능 생략
추천보드게임/api/boardgames/recstoken 기능 생략
오늘의 Pick 순위/api/boardgames/today-pick보드픽 회원들의 pick 데이터를 기반으로 추천하는 기능으로, 현재는 사용자들의 정보를 불러올 수 없어 랜덤데이터로 대체하여 작동 중
제안 게임/api/boardgames/suggestion보드픽 회원들의 pick 데이터를 기반으로 추천하는 기능으로, 현재는 사용자들의 정보를 불러올 수 없어 랜덤데이터로 대체하여 작동 중
user/api/user로그인한 회원의 정보를 조회하는 서버 전용 API로, 현재 프론트엔드에서는 호출이 불가능한 상태

MyPick ( + pick on/off)

사용자가 보드게임을 '마이픽(Mypick)'으로 선택하면 해당 게임의 idlocalStorage에 배열로 저장하고,

다시 불러올 때는 이 배열을 기준으로 boardGameData에서 해당 게임 정보를 필터링하여 보여주는 구조였습니다.

//Pick on/off API 대체 코드
export const togglePick = async (id, token) => {
  try {
    const picks = JSON.parse(localStorage.getItem("pick")) || [];
    const isPicked = picks.includes(id);
    const updatedPicks = isPicked
    ? picks.filter((pickId) => pickId !== id)
    : [id, ...picks];
    localStorage.setItem("pick", JSON.stringify(updatedPicks));
    return updatedPicks;
  } catch (error) {
    throw error;
  }
};
//MyPick API 대체 코드
export const getMyPick = async () => {
  try {
    const data = JSON.parse(localStorage.getItem("pick")) || [];
    const pickedData = boardGameData.filter((game) => data.includes(game.id));
    return pickedData;
  } catch (error) {
    throw error;
  }
};

⚠️ 문제 발생

사용자가 저장한 순서와 다르게 게임 id 값 기준으로 정렬되어 리스트가 출력되는 문제가 발생했습니다.

🔍 원인 분석

  • boardGameData는 mock data이기에, 데이터가 배열로 저장된 순서대로 반환되어짐 (API가 아니기 때문에 서버에서 순서를 맞춰주지 않음)
  • filter 는 조건에 해당하는 요소들을 걸러내지만 원본 배열의 순서 반영 ❌

includes()를 사용한 filter()id 기준의 순서만 반영 (사용자가 저장한 순서는 반영되지 않음)

✅ 해결

map(id => find(...)) 을 활용하면

사용자가 선택한 순서를 기준으로 하나씩 게임 내용을 가져오기 때문에 반환하는 배열의 순서가 사용자 선택 순서 배열과 동일해 순서를 유지할 수 있습니다.

export const getMyPick = async () => {
  try {
    const data = JSON.parse(localStorage.getItem("pick")) || [];
    const pickedData = data.map((id) =>
      boardGameData.find((game) => game.id === id)
    );
    return pickedData;
  } catch (error) {
    throw error;
	  }
	};

🔍 알게된 점

  • mock data를 사용할 때정렬 순서를 보장해주는 로직이 필요함
  • filter()는 조건에 맞는 요소를 전부 반환하지만 순서 조정 불가하므로 원하는 순서대로 데이터를 정렬하고 싶다면 map(id => find(...))형식을 활용해야 한다.

추천보드게임(recommand)

백엔드에서 제공받았던 API 설명에

  • 유저가 pick한 게임들의 카테고리들을 가져와 누적 점수표를 만든다.
  • 점수표를 기준으로 나머지 보드게임들의 점수를 매겨 상위 10개를 반환한다
  • 10개 미만 시 랜덤으로 채운다.
  • pick한 값이 없을 시에도 랜덤으로 채워 반환한다.
    는 규칙을 가지고 있다고 하여 이를 기반으로 API 대체코드를 작성하였다.

1. 보드게임 데이터 불러오기

현재 pickedData는 보드게임의 id 값만 저장되어 있기 때문에 전체 데이터에서 해당 아이디의 정보를 가져와야합니다.

 const pickedGames = boardGameData.filter((game) =>
    pickedGameIds.includes(game.id)
  );

2. pick한 게임 카테고리별 점수 누적표

const categoryScores = {};
  pickedGames.forEach((game) => {
    game.boardGameCategories.forEach((category) => {
      categoryScores[category] = (categoryScores[category] || 0) + 1;
      //해당 카테고리가 존재하면 그 값을 가져오고 없으면 0 -> +1 누적
    });
  });

  console.log("Category Scores:", categoryScores); 
  //Category Scores: {전략게임: 2, 롤플레잉: 1, 카드게임: 1, 기억력: 1, 배팅게임: 1}

3. 점수표를 기준으로 나머지 보드게임들의 유사도 점수표

reduce()함수 활용

arr.reduce(
  (acc, cur) => acc + cur, 초기값,
);
	const scoredGames = boardGameData
    .filter((game) => !pickedGameIds.includes(game.id)) //pick되어 있는 보드게임 제외
    .map((game) => {
      const score = game.boardGameCategories.reduce(
        (acc, category) => acc + (categoryScores[category] || 0),
        0
      );
      console.log(`${game.name}${score}`);
      return { ...game, score };
    });

4. 조건에 따라 추천 보드게임 출력

조건 1 : 점수 높은 순으로 정렬해서 추천
조건 2 : 10개 미만시 랜덤
조건 3 : pick한 보드게임 없을 시 랜덤

  1. 조건 2와 조건3이 모두 랜덤 데이터를 반환해야했기에 처음에는 두 조건을 하나로 묶고 싶었지만 문제가 발생하였습니다.

    if (!pickedGameIds || pickedGameIds.length === 0 || topGames.length < 10) {
      return boardGameData.sort(() => Math.random() - 0.5).slice(0, 10);
      }

    → 이유 : 조건이 발생하는 시점과 맥락이 너무 다르기 때문에 같이 작동할 수 없기 때문이다..

    • pick한 값이 없는 경우는 초기 조건
    • 관련된 추천의 수가 적을 때는 계산 결과에 따라 조건식이 작동

    ✅ 해결

    • pick한 게임이 없을 경우
     if (!pickedGameIds || pickedGameIds.length === 0) {
        return boardGameData.sort(() => Math.random() - 0.5).slice(0, 10);
      }
    • 계산된 게임 수가 10개 이상일 경우 → 점수 높은 순으로 정렬
    • 10개 미만일 경우 → 랜덤
    const ScoreGameCount = scoredGames.filter((game) => game.score > 0);
      if (ScoreGameCount.length >= 10) {
        scoredGames.sort((a, b) => b.score - a.score);
        let topGames = scoredGames.slice(0, 10);
        return topGames;
      } else {
        return boardGameData.sort(() => Math.random() - 0.5).slice(0, 10);
      }

    🔍 알게된 점

    1. 의미가 다른 조건은 같은 결과값을 반환한다고 하더라도 하나의 if문 안에 두가지 의미를 넣으면 코드의 의도도 흐려지고 디버깅도 어려워지기 때문에 분리해서 넣어야한다.
    2. 조건을 평가하는 시점이 다른 경우 또한 로직을 따로 분기 처리해야 안정적이다.

전체코드

 export const getRecsGame = (pickedGameIds) => {
   //pick한 게임이 없을 때 랜덤렌더링
   if (!pickedGameIds || pickedGameIds.length === 0) {
     return boardGameData.sort(() => Math.random() - 0.5).slice(0, 10);
   }
 
   // Pick한 게임의 id와 일치하는 게임데이터 불러오기
   const pickedGames = boardGameData.filter((game) =>
     pickedGameIds.includes(game.id)
   );
 
   //1. pick한 게임 카테고리별 점수 누적표
   const categoryScores = {};
   pickedGames.forEach((game) => {
     game.boardGameCategories.forEach((category) => {
       categoryScores[category] = (categoryScores[category] || 0) + 1;
     });
   });
 
   // 2. 점수표를 기준으로 나머지 보드게임들의 점수표
   const scoredGames = boardGameData
     .filter((game) => !pickedGameIds.includes(game.id))
     .map((game) => {
       const score = game.boardGameCategories.reduce(
         (acc, category) => acc + (categoryScores[category] || 0),
         0
       );
       return { ...game, score };
     });
 
   const ScoreGameCount = scoredGames.filter((game) => game.score > 0);
   if (ScoreGameCount.length >= 10) {
     scoredGames.sort((a, b) => b.score - a.score);
     let topGames = scoredGames.slice(0, 10);
     return topGames;
   } else {
     return boardGameData.sort(() => Math.random() - 0.5).slice(0, 10);
   }
 };

결과화면

0개의 댓글