![[인프런 워밍업 스터디 클럽 3기 풀스택] 3주차 발자국](https://cdn.inflearn.com/public/files/blogs/dec6d3c6-ba42-48a9-a6dd-38a909271d45/336235.png)
[인프런 워밍업 스터디 클럽 3기 풀스택] 3주차 발자국
29일 전
학습 내용
인프런 워밍업 클럽 스터디 3주차로,
이번 주는 넷플릭스 프로젝트를 다루는 시간이었다.
useInfiniteQuery와 Jotai(recoil 대체 전역상태 라이브러리)를 사용해볼 수 있었다.
미션 3 구현 내용
Netflix 중 찜하기 관련 기능
포인트 1: favorites 테이블 추가
actions/favoriteActions.ts
"use server";
import {
createServerSupabaseClient,
handleError,
PostgrestError,
} from "@next-inflearn/supabase";
// Movie 타입 정의
export type Movie = {
id: number;
image_url: string;
overview: string;
popularity: number;
release_date: string;
title: string;
vote_average: number;
// Movie 타입에 favorites 필드 추가
favorites?: {
// optional field로 추가
id: number;
} | null;
};
// SearchMoviesResponse 타입 정의
export type SearchMoviesResponse = {
data: Movie[];
page: number;
pageSize: number;
hasNextPage: boolean;
};
// 에러 케이스를 위한 타입 정의
type SearchMoviesError = {
data: never[];
count: number;
page: null;
pageSize: null;
error: PostgrestError;
};
// 성공 케이스를 위한 타입 정의
type SearchMoviesSuccess = {
data: Movie[];
page: number;
pageSize: number;
hasNextPage: boolean;
};
export async function searchMovies({
search,
page,
pageSize,
}: {
search: string;
page: number;
pageSize: number;
}): Promise<SearchMoviesSuccess> {
const supabase = await createServerSupabaseClient();
// 현재 사용자 정보 가져오기
const {
data: { user },
} = await supabase.auth.getUser();
// 쿼리 설정
const query = supabase
.from("movie")
.select(
user
? `
*,
favorites!left (
id
)
`
: "*", // 로그인하지 않은 경우 favorites 정보를 가져오지 않음
{ count: "exact" }
)
.ilike("title", `%${search}%`)
.order("popularity", { ascending: false });
// 로그인한 경우 현재 사용자의 즐겨찾기만 조회하도록 필터링
if (user) {
query.eq("favorites.user_id", user.id);
}
const { data, count, error } = await query.range(
(page - 1) * pageSize,
page * pageSize - 1
);
const hasNextPage = count ? count > page * pageSize : false;
if (error) {
return {
data: [],
page,
pageSize,
hasNextPage: false,
};
}
// 반환된 데이터를 Movie 타입에 맞게 변환
const moviesWithFavorites = (data || []).map((movie: any) => ({
...movie,
favorites: movie.favorites?.[0] || null, // 즐겨찾기 정보 포함
}));
return {
data: moviesWithFavorites,
page,
pageSize,
hasNextPage,
};
}
export async function getMovie(id: string) {
const supabase = await createServerSupabaseClient();
const { data, error } = await supabase
.from("movie")
.select("*")
.eq("id", id)
.maybeSingle();
handleError(error);
return data;
}
포인트 2: profiles 테이블 추가
utils/AuthProvider.tsx
"use client";
import { useEffect } from "react";
import { createBrowserSupabaseClient } from "@next-inflearn/supabase";
import { useSetAtom } from "jotai";
import { userAtom } from "@/utils/jotai/atoms";
export function AuthProvider({ children }: { children: React.ReactNode }) {
const setUser = useSetAtom(userAtom);
const supabase = createBrowserSupabaseClient();
useEffect(() => {
// 현재 세션 확인
supabase.auth.getSession().then(({ data: { session } }) => {
setUser(session?.user ?? null);
});
// Auth 상태 변경 구독
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setUser(session?.user ?? null);
});
return () => subscription.unsubscribe();
}, [supabase, setUser]);
return children;
}
포인트 3: movies / favorites / profiles 테이블 연결
favorites 테이블 내
profiles 테이블 내
회고
시간이 부족하다는 핑계로,
더 디벨롭할 수 있는 부분이 많음에도 불구하고 생각했던 것들을 다 구현하진 못했던 것 같다.
다만, 이렇게 틀을 갖춰놧으니 추후에 추가적인 기능을 정의해서 구현해보기 너무 좋을 것 같다.
댓글을 작성해보세요.