Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<link rel="stylesheet" href="./src/styles/main.css" />
<link rel="stylesheet" href="./src/styles/tab.css" />
<link rel="stylesheet" href="./src/styles/thumbnail.css" />
<link rel="stylesheet" href="./src/styles/modal.css" />
<title>영화 리뷰</title>
</head>
<body>
Expand Down
18 changes: 17 additions & 1 deletion src/types/api.ts → src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,20 @@ interface MovieResponse {
total_results: number;
}

export type { Movie, MovieResponse };
interface Genre {
id: number;
name: string;
}

interface MovieDetail {
id: number;
title: string;
poster_path: string;
backdrop_path: string;
release_date: string;
genres: Genre[];
vote_average: number;
overview: string;
}

export type { Movie, MovieResponse, Genre, MovieDetail };
20 changes: 20 additions & 0 deletions src/api/movieApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { apiRequest } from "../utils/api";
import { MovieResponse, MovieDetail } from "./api";

export const fetchPopularMovies = (page: number): Promise<MovieResponse> =>
apiRequest<MovieResponse>({
url: `/movie/popular?language=ko-KR&page=${page}`,
});

export const fetchSearchMovies = (
query: string,
page: number,
): Promise<MovieResponse> =>
apiRequest<MovieResponse>({
url: `/search/movie?language=ko-KR&query=${encodeURIComponent(query)}&page=${page}`,
});

export const fetchMovieDetail = (id: number): Promise<MovieDetail> =>
apiRequest<MovieDetail>({
url: `/movie/${id}?language=ko-KR`,
});
Comment on lines +4 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

❓ Verification inconclusive

Script executed:

#!/bin/bash
set -e

# apiRequest가 HTTP status 실패를 throw 하는지 확인
rg -n --type=ts -C4 'export const apiRequest|res\.ok|throw new Error|res\.json\('

# movieApi 호출부에서 실패 분기 처리가 있는지 확인
rg -n --type=ts -C3 'fetchPopularMovies\(|fetchSearchMovies\(|fetchMovieDetail\('

Repository: woowacourse/javascript-movie-review


Repository: woowacourse/javascript-movie-review
Exit code: 0

stdout:

src/utils/api.ts-2-  url: string;
src/utils/api.ts-3-  method?: "GET" | "POST" | "PUT" | "DELETE";
src/utils/api.ts-4-}
src/utils/api.ts-5-
src/utils/api.ts:6:export const apiRequest = async <T>({
src/utils/api.ts-7-  url,
src/utils/api.ts-8-  method = "GET",
src/utils/api.ts-9-}: FetchOptions): Promise<T> => {
src/utils/api.ts-10-  return await fetch(`${import.meta.env.VITE_BASE_URL}${url}`, {
--
src/utils/api.ts-12-    headers: {
src/utils/api.ts-13-      Authorization: `Bearer ${import.meta.env.VITE_TMDB_API_KEY}`,
src/utils/api.ts-14-      "Content-Type": "application/json",
src/utils/api.ts-15-    },
src/utils/api.ts:16:  }).then((res) => res.json());
src/utils/api.ts-17-};
src/main.ts-18-  // 카드 클릭 이벤트 핸들러
src/main.ts-19-  const onMovieClick = async (id: number) => {
src/main.ts-20-    try {
src/main.ts:21:      const detail = await fetchMovieDetail(id);
src/main.ts-22-      modal.open({
src/main.ts-23-        id: detail.id,
src/main.ts-24-        title: detail.title,
--
src/features/search.ts-19-  mainEl.appendChild(skeleton);
src/features/search.ts-20-
src/features/search.ts-21-  let page = 1;
src/features/search.ts:22:  const data = await fetchSearchMovies(query, page);
src/features/search.ts-23-  skeleton.remove();
src/features/search.ts-24-
src/features/search.ts-25-  if (data.results.length === 0) {
--
src/features/search.ts-40-    const moreSkeleton = createSkeleton();
src/features/search.ts-41-    mainEl.appendChild(moreSkeleton);
src/features/search.ts-42-
src/features/search.ts:43:    const nextData = await fetchSearchMovies(query, page);
src/features/search.ts-44-    moreSkeleton.replaceWith(createMovieList(nextData.results, onMovieClick));
src/features/search.ts-45-
src/features/search.ts-46-    if (nextData.total_pages <= page) stop();
--
src/features/popular.ts-10-): Promise<() => void> => {
src/features/popular.ts-11-  let page = 1;
src/features/popular.ts-12-
src/features/popular.ts:13:  const data = await fetchPopularMovies(page);
src/features/popular.ts-14-  skeletonEls.replaceWith(createMovieList(data.results, onMovieClick));
src/features/popular.ts-15-
src/features/popular.ts-16-  if (data.total_pages <= page) return () => {};
--
src/features/popular.ts-21-    const skeleton = createSkeleton();
src/features/popular.ts-22-    mainEl.appendChild(skeleton);
src/features/popular.ts-23-
src/features/popular.ts:24:    const nextData = await fetchPopularMovies(page);
src/features/popular.ts-25-    skeleton.replaceWith(createMovieList(nextData.results, onMovieClick));
src/features/popular.ts-26-
src/features/popular.ts-27-    if (nextData.total_pages <= page) stop();
--
src/features/movieRenderer.ts-17-  mainEl.append(skeletonEls, loadMoreBtnEl);
src/features/movieRenderer.ts-18-
src/features/movieRenderer.ts-19-  const data = query
src/features/movieRenderer.ts:20:    ? await fetchSearchMovies(query, page)
src/features/movieRenderer.ts:21:    : await fetchPopularMovies(page);
src/features/movieRenderer.ts-22-
src/features/movieRenderer.ts-23-  if (data) {
src/features/movieRenderer.ts-24-    loadMoreBtnEl.disabled = false;

HTTP 오류 응답이 성공으로 전파될 위험이 있습니다.

apiRequest 구현을 보니 res.ok 검증이 없어서, 4xx/5xx 상태 코드도 응답의 JSON 파싱에 성공하면 그대로 반환됩니다. 이렇게 되면 호출부에서 예상한 MovieResponseMovieDetail 타입 구조와 일치하지 않는 에러 응답을 받게 되어 런타임 오류를 유발할 수 있어요.

현재 fetchPopularMovies, fetchSearchMovies, fetchMovieDetail을 호출하는 대부분의 곳에서 try/catch로 감싸지 않고 있다는 점도 고려해보세요. 다음을 점검해보시길 권합니다:

  • apiRequest에서 HTTP 상태 코드를 어떻게 검증할지
  • 에러 발생 시 호출부에서 이를 적절히 처리할 수 있도록 설계할지
  • 현재 설계에서 정말로 모든 호출부가 에러에 안전한지
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/movieApi.ts` around lines 4 - 20, apiRequest currently returns parsed
JSON even for 4xx/5xx responses; update apiRequest to check response.ok and, if
false, parse the error body (safely with try/catch) and throw a descriptive
error (preferably a small ApiError class containing status, statusText and
parsed body) so that fetchPopularMovies, fetchSearchMovies and fetchMovieDetail
will reject on HTTP errors instead of returning error payloads as success;
ensure the thrown error preserves the original response info for caller handling
and keep the existing function signatures for those three fetch* helpers.

Loading