식단관리 - Part3 추천/비추천 기능
ㆍProject Diary/Next.js + Prisma + MariaDB (KiloFlow)
이 글에서는 Kiloflow 프로젝트에서 사용자가 등록한 음식에 대해 다른 유저들이 추천 또는 비추천을 할 수 있는 기능을 구현하는 방법을 설명합니다. 이 기능을 통해 사용자는 음식에 대한 피드백을 제공하고, 다른 사용자들이 이를 참고할 수 있습니다.
데이터베이스 스키마
users 테이블
model users {
user_id Int @id @default(autoincrement())
email String @unique
password String
nickname String
profile_image String @default("default_image_url")
isInitialSetupComplete Boolean @default(false)
created_at DateTime @default(now())
userProfile UserProfile?
recommends recommend[] // 추천 및 비추천 데이터
userFoodList userFoodList[]
todayFood todayFood[]
@@index([user_id])
}
userFoodList 테이블
model userFoodList {
food_id String @id
user_id Int
menu String
calorie Int
carb Int
pro Int
fat Int
img String
food_seq String @unique
recommends recommend[] // 추천 및 비추천 데이터
user users @relation(fields: [user_id], references: [user_id])
@@index([user_id])
@@index([food_id])
}
recommend 테이블
model recommend {
id Int @id @default(autoincrement())
user_id Int
food_id String
recommend String
user users @relation(fields: [user_id], references: [user_id], onDelete: Cascade)
food userFoodList @relation(fields: [food_id], references: [food_id], onDelete: Cascade)
@@index([user_id])
@@index([food_id])
}
프론트엔드
음식 상세 페이지 컴포넌트
1. 추천/비추천 상태관리
const [recommend, setRecommend] = useState(''); // 현재 사용자의 추천 상태
const [allRecommend, setAllRecommend] = useState(0); // 전체 추천 수
const [upRecommend, setUpRecommend] = useState(0); // 추천(up) 수
const [currentUserRecommend, setCurrentUserRecommend] = useState(''); // 현재 사용자의 추천 상태
const { id, name, img } = JSON.parse(router.query.data as string) as FoodData;
const [currentUserId, setCurrentUserID] = useState(0);
2. 추천/비추천 클릭 핸들러
// 추천/비추천 클릭 핸들러
const clickThumb = async (thumb: string) => {
try {
const res = await fetch('/api/food/recommend-click', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
thumb,
currentUserId,
id,
}),
});
if (res.ok) {
const rec = await res.json();
setRecommend(rec.message); // 추천 상태 업데이트
router.back();
} else {
alert('추천에 실패했습니다.');
}
} catch (err) {
alert('추천에 실패했습니다.');
}
};
3. 추천 리스트 불러오기
// 추천 리스트 불러오기
useEffect(() => {
const fetchRecommendList = async () => {
try {
const res = await fetch('/api/food/recommend-list', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ id }),
});
if (res.ok) {
const data = await res.json();
const currentUserRecommendData = data.data.find(
(val: Recommendation) => val.user_id === currentUserId
);
currentUserRecommendData
? setCurrentUserRecommend(currentUserRecommendData.recommend)
: setCurrentUserRecommend('');
setUpRecommend(
data.data.filter((val: Recommendation) => val.recommend === 'up')
.length
);
setAllRecommend(data.data.length);
} else {
alert('추천 목록을 불러오는 데 실패했습니다.');
}
} catch (err) {
console.log(err);
}
};
if (id.startsWith('user')) {
fetchRecommendList();
}
}, [id, currentUserId, recommend]);
컴포넌트가 마운트될 때 추천 리스트를 불러와 현재 사용자의 추천 상태와 전체 추천 비율을 계산합니다.
백엔드
추천/비추천 API엔드포인트
1. 요청 메서드 확인 및 기존 추천 기록 확인
import type { NextApiRequest, NextApiResponse } from 'next';
import prisma from '../../../lib/prisma';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
// POST 요청이 아닌 경우 405 에러 반환
return res.status(405).json({ error: `Method ${req.method} Not Allowed` });
}
try {
const { thumb, currentUserId, id } = req.body;
// 기존 추천 기록이 있는지 확인
const existingRecommendation = await prisma.recommend.findFirst({
where: {
user_id: currentUserId,
food_id: id,
},
});
if (existingRecommendation) {
// 기존 추천 기록의 recommend 값과 thumb 값이 같은지 확인
if (existingRecommendation.recommend === thumb) {
// 값이 같으면 해당 데이터를 삭제
await prisma.recommend.delete({
where: {
id: existingRecommendation.id,
user_id: currentUserId,
},
});
return res.status(200).json({ message: '중복된 데이터 삭제' });
} else {
// 값이 다르면 recommend 값을 thumb으로 업데이트
await prisma.recommend.update({
where: {
id: existingRecommendation.id,
user_id: currentUserId,
},
data: {
recommend: thumb,
},
});
return res.status(200).json({ message: thumb });
}
}
// 추천 기록이 존재하지 않는 경우 새로운 레코드를 생성
await prisma.recommend.create({
data: {
user_id: currentUserId,
food_id: id,
recommend: thumb,
},
});
return res.status(200).json({ message: thumb });
} catch (error: any) {
console.log('서버에러', error);
return res
.status(500)
.json({ error: '서버에서 오류가 발생했습니다.', details: error.message });
}
}
- 요청 메서드 확인: POST 요청이 아닌 경우 405 에러를 반환합니다.
- 기존 추천 기록 확인: 사용자가 이미 추천/비추천을 한 기록이 있는지 확인합니다. 만약 기존 기록이 있다면, 새로운 추천 상태와 비교하여 업데이트하거나 삭제합니다.
- 새로운 레코드 생성: 기존 기록이 없는 경우 새로운 추천/비추천 레코드를 생성합니다.
2. 추천/비추천 목록 API 핸들러
import type { NextApiRequest, NextApiResponse } from 'next';
import prisma from '../../../lib/prisma';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
// POST 요청이 아닌 경우 405 에러 반환
return res.status(405).json({ error: `Method ${req.method} Not Allowed` });
}
try {
const { id } = req.body;
// 음식에 대한 모든 추천 기록 불러오기
const recommendationData = await prisma.recommend.findMany({
where: {
food_id: id,
},
});
return res.status(200).json({ data: recommendationData });
} catch (error: any) {
console.log('서버에러', error);
return res
.status(500)
.json({ error: '서버에서 오류가 발생했습니다.', details: error.message });
}
}
- 요청 메서드 확인: POST 요청이 아닌 경우 405 에러를 반환합니다.
- 추천 기록 불러오기: 특정 음식에 대한 모든 추천/비추천 기록을 데이터베이스에서 불러옵니다.
- 응답 반환: 불러온 추천 기록 데이터를 클라이언트에 반환합니다.
요약
- 추천/비추천 컴포넌트 :
사용자가 추천 또는 비추천 아이콘을 클릭하면 해당 음식에 대한 추천 또는 비추천 상태가 변경됩니다. 이는 clickThumb 함수에서 처리됩니다. - 추천/비추천 API 핸들러 :
recommend-click API는 사용자의 추천/비추천 상태를 업데이트하거나 삭제합니다. recommend-list API는 특정 음식에 대한 모든 추천/비추천 기록을 불러옵니다. - 추천/비추천 목록 불러오기 :
음식 상세 페이지가 로드될 때 useEffect 훅을 사용하여 추천/비추천 목록을 불러와 현재 사용자의 추천 상태와 전체 추천 비율을 계산합니다.