오늘의 식단 및 운동 목록 패치하기

Project Diary/Next.js + Prisma + MariaDB (KiloFlow)

이 글에서는 Kiloflow 프로젝트에서 메인 페이지에 오늘의 식단과 운동 목록을 패치하고 표시하는 기능을 구현하는 방법을 다룹니다.


 

프론트엔드

 

1. 메인페이지 컴포넌트 (index.tsx)

 

1-1 상태값 정의

const [currentUser, setCurrentUser] = useState<User | null>(null); // 현재 사용자 상태
const [currentTab, setCurrentTab] = useState<Tab>('week'); // 현재 탭 상태
const [selectedDate, setSelectedDate] = useState(dayjs().toDate()); // 선택된 날짜 상태
const [todayList, setTodayList] = useState('food'); // 오늘의 목록 상태
const [todayFoodData, setTodayFoodData] = useState([]); // 오늘의 식단 데이터 상태
const [todayExerciseData, setTodayExerciseData] = useState([]); // 오늘의 운동 데이터 상태

 

2-1 사용자 정보 가져오기

useEffect(() => {
  const fetchCurrentUser = async () => {
    const token = localStorage.getItem('token');
    if (token) {
      const res = await fetch('/api/auth/me', {
        headers: {
          Authorization: `Bearer ${token}`,
        },
      });

      if (res.ok) {
        const data = await res.json();
        setCurrentUser(data.user); // 현재 사용자 설정
        setDailyCalories(data.userProfile.daily_calories); // 일일 권장 섭취 칼로리 설정
      } else {
        localStorage.removeItem('token');
        router.push('/auth/login');
      }
    } else {
      router.push('/auth/login');
    }
  };

  fetchCurrentUser();
}, [router]);

 

1-3 오늘의 식단 데이터 가져오기

const fetchTodayData = async () => {
  const foodRes = await fetch(
    `/api/food/todayFood?user_id=${currentUser.user_id}&date=${dayjs(
      selectedDate
    ).format('YYYY-MM-DD')}`
  );
  const foodData = await foodRes.json();
  setTodayFoodData(foodData); // 오늘의 식단 데이터 설정
};
  • foodRes : 오늘의 식단 데이터를 가져오기 위한 API 호출입니다.
  • foodData : API 응답 데이터를 JSON 형태로 파싱합니다.
  • setTodayFoodData : 오늘의 식단 데이터를 상태로 설정합니다.

1-4 오늘의 운동 데이터 가져오기

const exerciseRes = await fetch(
  `/api/exercise/todayExercise?user_id=${
    currentUser.user_id
  }&date=${dayjs(selectedDate).format('YYYY-MM-DD')}`
);
const exerciseData = await exerciseRes.json();
setTodayExerciseData(exerciseData); // 오늘의 운동 데이터 설정
  • exerciseRes : 오늘의 운동 데이터를 가져오기 위한 API 호출입니다.
  • exerciseData : API 응답 데이터를 JSON 형태로 파싱합니다.
  • setTodayExerciseData: 오늘의 운동 데이터를 상태로 설정합니다.

1-5 오늘의 식단 목록 렌더링

{todayList === 'food' && (
  <TodayFoodList
    foodData={todayFoodData}
    currentUser={currentUser.user_id}
  />
)}
  • todayList === 'food' : 오늘의 목록이 식단인 경우에만 TodayFoodList 컴포넌트를 렌더링합니다.
  • foodData : 오늘의 식단 데이터를 TodayFoodList 컴포넌트에 프롭스로 전달합니다.
  • currentUser : 현재 사용자 ID를 TodayFoodList 컴포넌트에 프롭스로 전달합니다.

1-6 오늘의 운동 목록 렌더링

{todayList === 'exercise' && (
  <TodayExerciseList
    exerciseData={todayExerciseData}
    currentUser={currentUser.user_id}
  />
)}
  • todayList === 'exercise' : 오늘의 목록이 운동인 경우에만 TodayExerciseList 컴포넌트를 렌더링합니다.
  • exerciseData : 오늘의 운동 데이터를 TodayExerciseList 컴포넌트에 프롭스로 전달합니다.
  • currentUser : 현재 사용자 ID를 TodayExerciseList 컴포넌트에 프롭스로 전달합니다.

 

2. TodayFoodList 컴포넌트 (todayFoodList.tsx)

 

2-1 컴포넌트 정의

const TodayFoodList: React.FC<TodayFoodListProps> = ({
  foodData,
  currentUser,
}) => {
  // 컴포넌트 로직 및 렌더링
}
  • TodayFoodListProps 인터페이스 정의 :
interface TodayFoodListProps {
  foodData: Food[];
  currentUser: number;
}

interface Food {
  food_id: string;
  name: string;
  calorie: number;
  carb: number;
  pro: number;
  fat: number;
  img: string;
  added_at: string;
}
  • TodayFoodListProps : TodayFoodList 컴포넌트가 받는 프롭스의 타입을 정의합니다.
    - foodData : 오늘의 식단 데이터를 포함
    - currentUser : 현재 사용자 ID를 포함
  • React.FC<TodayFoodListProps>
    - React.FC는 Function Component의 약자로, 함수형 컴포넌트를 정의할 때 사용됩니다.

 

3. TodayExerciseList 컴포넌트 (todayExerciseList.tsx)

 

3-1 컴포넌트 정의

const TodayExerciseList: React.FC<TodayExerciseListProps> = ({
  exerciseData,
  currentUser,
}) => {
  // 컴포넌트 로직 및 렌더링
}
  • TodayExerciseListProps 인터페이스 정의
interface TodayExerciseListProps {
  exerciseData: Exercise[];
  currentUser: number;
}

interface Exercise {
  exercise_id: number;
  name: string;
  MET: string;
  duration: number;
  calories: number;
  added_at: string;
}
  • TodayExerciseListProps : TodayExerciseList 컴포넌트가 받는 프롭스의 타입을 정의합니다.
    - exerciseData : 오늘의 운동 데이터를 포함
    - currentUser : 현재 사용자 ID를 포함
  • React.FC<TodayExerciseListProps> : 함수형 컴포넌트를 정의

 

백엔드

 

1. todayFood   API 엔드포인트

 

1-1 날짜 설정

const selectedDate = new Date(date as string); // 선택된 날짜 생성
const startOfDay = new Date(selectedDate.setHours(0, 0, 0, 0)); // 하루의 시작 시간 설정
const endOfDay = new Date(selectedDate.setHours(23, 59, 59, 999)); // 하루의 끝 시간 설정

하루의 시작 시간과 끝 시간을 설정하여 해당 날짜에 추가된 데이터를 조회합니다.

 

1-2 오늘의 식단 데이터베이스 조회

const todayFoods = await prisma.todayFood.findMany({
  where: {
    user_id: Number(user_id),
    added_at: {
      gte: startOfDay, // 시작 시간 이상
      lte: endOfDay, // 끝 시간 이하
    },
  },
});

오늘 추가된 모든 식단 데이터를 조회합니다.

 

1-3 사용자 정의 음식 데이터베이스 조회

const userFoods = await prisma.userFoodList.findMany({
  where: {
    food_id: {
      in: todayFoods
        .filter((food) => food.food_id.startsWith("user_"))
        .map((food) => food.food_id),
    },
  },
});

사용자 정의 음식 데이터를 조회합니다.

 

food_id가 user_로 시작하는 이유

  • 사용자 정의 음식 ID가 user_로 시작되도록 설정했기 때문입니다.
  • 외부 API 음식과 구분하기 위해 필요

1-4 외부 API 음식 데이터 필터링

const externalFoods = todayFoods.filter((food) =>
  food.food_id.startsWith("RCP_")
);

const rcpSeqs = externalFoods.map((food) => food.food_id.split("_")[1]); // 외부 API 음식 ID 추출

외부 API에서 가져온 음식 데이터를 필터링하고, 필요한 ID를 추출합니다.

 

food_id가 RCP_로 시작하는 이유
외부 API 음식 ID가 RCP_로 시작되도록 설정했기 때문입니다.


1-5 외부 API 호출

const response = await fetch(
  `http://openapi.foodsafetykorea.go.kr/api/{개인키}/COOKRCP01/json/1/20`
);

if (!response.ok) {
  throw new Error("외부 API에서 데이터를 불러오는 데 실패했습니다.");
}

const data = await response.json(); // 외부 API 응답 데이터 파싱
const allApiFoods = data.COOKRCP01.row; // API 음식 데이터 추출

외부 API를 호출하여 음식 데이터를 가져옵니다.

 

1-6 모든 음식 데이터 통합

const allFoodData = [
  ...userFoods.map((food) => ({
    food_id: food.food_id,
    name: food.menu,
    calorie: food.calorie,
    carb: food.carb,
    pro: food.pro,
    fat: food.fat,
    img: food.img,
    added_at: todayFoods.find(
      (todayFood) => todayFood.food_id === food.food_id
    )?.added_at,
  })),
  ...externalFoodData,
];

return res.status(200).json(allFoodData);

사용자 정의 음식 데이터와 외부 API 음식 데이터를 통합하여 반환합니다.

오늘의 식단 데이터에는 음식 이름, 칼로리, 탄수화물, 단백질, 지방 정보가 포함되어야 하므로, 이를 통합하여 반환합니다.

 

 

2. todayExercise  API 엔드포인트

 

2-1 날짜 설정

const selectedDate = new Date(date as string); // 선택된 날짜 생성
const startOfDay = new Date(selectedDate.setHours(0, 0, 0, 0)); // 하루의 시작 시간 설정
const endOfDay = new Date(selectedDate.setHours(23, 59, 59, 999)); // 하루의 끝 시간 설정

 

2-2 오늘의 운동 데이터베이스 조회

const todayExercises = await prisma.todayExercise.findMany({
  where: {
    user_id: Number(user_id),
    added_at: {
      gte: startOfDay, // 시작 시간 이상
      lte: endOfDay, // 끝 시간 이하
    },
  },
});

오늘 추가된 모든 운동 데이터를 조회합니다.

 

2-3 외부 API 호출

const response = await fetch(
  "https://api.odcloud.kr/api/v1/uddi:?page=1&perPage=15&serviceKey={개인키}"
);

if (!response.ok) {
  throw new Error("외부 API에서 데이터를 불러오는 데 실패했습니다.");
}

const data = await response.json(); // 외부 API 응답 데이터 파싱
const allApiExercises: Exercise[] = data.data; // API 운동 데이터 추출

 

2-4 각 운동 항목에 ID 부여

allApiExercises.forEach((exercise, index) => {
  exercise.id = index + 1;
});

각 운동 항목에 고유 ID를 부여합니다.

 

2-5 운동 데이터 매핑

const exerciseData = exerciseIds
  .map((id) => {
    const apiExercise = allApiExercises.find(
      (exercise) => exercise.id === id
    );
    if (!apiExercise) {
      return null;
    }
    const addedExercise = todayExercises.find(
      (exercise) => exercise.exercise_id === id
    );

    return {
      exercise_id: id,
      name: apiExercise.운동명,
      MET: apiExercise.MET계수,
      duration: addedExercise?.duration,
      calories: addedExercise?.calories,
      added_at: addedExercise?.added_at,
    };
  })
  .filter(Boolean);

return res.status(200).json(exerciseData); // 운동 데이터 반환

외부 API에서 가져온 운동 데이터를 매핑하여 반환합니다.

오늘의 운동 데이터에는 이름과 MET계수 정보가 포함되어야 하므로, 이를 통합하여 반환합니다.