-
Notifications
You must be signed in to change notification settings - Fork 0
feat: books 도메인 API 연동 #105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fde2ba1
bc69244
4ca579f
3c120b4
ccfd27e
24c3bde
4b2817e
8836709
860200c
8dc269a
4617896
fcfeb0b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import { apiClient } from '../index'; | ||
|
|
||
| // 책 상세 정보 타입 | ||
| export interface BookDetail { | ||
| title: string; | ||
| imageUrl: string; | ||
| authorName: string; | ||
| publisher: string; | ||
| isbn: string; | ||
| description: string; | ||
| recruitingRoomCount: number; | ||
| readCount: number; | ||
| isSaved: boolean; | ||
| } | ||
|
|
||
| // API 응답 타입 | ||
| export interface BookDetailResponse { | ||
| isSuccess: boolean; | ||
| code: number; | ||
| message: string; | ||
| data: BookDetail; | ||
| } | ||
|
|
||
| export const getBookDetail = async (isbn: string): Promise<BookDetailResponse> => { | ||
| try { | ||
| const response = await apiClient.get<BookDetailResponse>(`/books/${isbn}`); | ||
|
|
||
| return response.data; | ||
| } catch (error) { | ||
| console.error('책 상세 정보 API 오류:', error); | ||
| throw error; | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { apiClient } from '../index'; | ||
|
|
||
| // 인기 검색 도서 타입 | ||
| export interface MostSearchedBook { | ||
| rank: number; | ||
| title: string; | ||
| imageUrl: string; | ||
| isbn: string; | ||
| } | ||
|
|
||
| // API 응답 데이터 타입 | ||
| export interface MostSearchedBooksData { | ||
| bookList: MostSearchedBook[]; | ||
| } | ||
|
|
||
| // API 응답 타입 | ||
| export interface MostSearchedBooksResponse { | ||
| isSuccess: boolean; | ||
| code: number; | ||
| message: string; | ||
| data: MostSearchedBooksData; | ||
| } | ||
|
|
||
| export const getMostSearchedBooks = async (): Promise<MostSearchedBooksResponse> => { | ||
| try { | ||
| const response = await apiClient.get<MostSearchedBooksResponse>('/books/most-searched'); | ||
| return response.data; | ||
| } catch (error) { | ||
| console.error('인기 검색 도서 조회 API 오류:', error); | ||
| throw error; | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import { apiClient } from '../index'; | ||
|
|
||
| // 모집중인 모임방 타입 | ||
| export interface RecruitingRoom { | ||
| roomId: number; | ||
| bookImageUrl: string; | ||
| roomName: string; | ||
| memberCount: number; | ||
| recruitCount: number; | ||
| deadlineEndDate: string; | ||
| } | ||
|
|
||
| // API 응답 데이터 타입 | ||
| export interface RecruitingRoomsData { | ||
| recruitingRoomList: RecruitingRoom[]; | ||
| totalRoomCount: number; | ||
| nextCursor: string; | ||
| isLast: boolean; | ||
| } | ||
|
|
||
| // API 응답 타입 | ||
| export interface RecruitingRoomsResponse { | ||
| isSuccess: boolean; | ||
| code: number; | ||
| message: string; | ||
| data: RecruitingRoomsData; | ||
| } | ||
|
|
||
| export const getRecruitingRooms = async (isbn: string): Promise<RecruitingRoomsResponse> => { | ||
| try { | ||
| const response = await apiClient.get<RecruitingRoomsResponse>( | ||
| `/books/${isbn}/recruiting-rooms`, | ||
| ); | ||
|
|
||
| return response.data; | ||
| } catch (error) { | ||
| console.error('모집중인 모임방 조회 API 오류:', error); | ||
| throw error; | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,70 @@ | ||||||||||||||||||||||||||||||||||||||||||
| import { apiClient } from '../index'; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // 검색된 책 타입 (API 응답에서 받는 형태) | ||||||||||||||||||||||||||||||||||||||||||
| export interface BookSearchItem { | ||||||||||||||||||||||||||||||||||||||||||
| title: string; | ||||||||||||||||||||||||||||||||||||||||||
| imageUrl: string; | ||||||||||||||||||||||||||||||||||||||||||
| authorName: string; | ||||||||||||||||||||||||||||||||||||||||||
| publisher: string; | ||||||||||||||||||||||||||||||||||||||||||
| isbn: string; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // 검색된 책 타입 (컴포넌트에서 사용하는 형태) | ||||||||||||||||||||||||||||||||||||||||||
| export interface SearchedBook { | ||||||||||||||||||||||||||||||||||||||||||
| id: number; | ||||||||||||||||||||||||||||||||||||||||||
| title: string; | ||||||||||||||||||||||||||||||||||||||||||
| author: string; | ||||||||||||||||||||||||||||||||||||||||||
| publisher: string; | ||||||||||||||||||||||||||||||||||||||||||
| coverUrl: string; | ||||||||||||||||||||||||||||||||||||||||||
| isbn: string; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+12
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainSearchedBook 타입 중복 정의 제거 UI 레이어(src/pages/search/Search.tsx)에서도 같은 이름의 SearchedBook 인터페이스가 존재합니다. 중복 정의는 드리프트를 유발합니다. 공용 타입 모듈(예: src/types/book.ts)로 옮기고 단일 출처를 사용하세요. 중복 정의 및 사용처를 점검하는 스크립트: 🏁 Script executed: #!/bin/bash
# SearchedBook 타입 선언 및 사용 위치 점검
rg -n --no-heading "interface SearchedBook|type SearchedBook" -A 2 -B 2
rg -n --no-heading "SearchedBook" -A 2 -B 2Length of output: 8084 SearchedBook 타입 공통 모듈로 이동 및 중복 정의 제거 필요 UI 레이어와 API 레이어에서 각각 정의된 • src/api/books/getSearchBooks.ts 제안된 작업 순서:
이후 빌드 에러가 없는지, 컴포넌트들이 올바르게 타입을 참조하는지 검증해 주세요. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // API 응답 데이터 타입 | ||||||||||||||||||||||||||||||||||||||||||
| export interface SearchBooksData { | ||||||||||||||||||||||||||||||||||||||||||
| searchResult: BookSearchItem[]; | ||||||||||||||||||||||||||||||||||||||||||
| page: number; | ||||||||||||||||||||||||||||||||||||||||||
| size: number; | ||||||||||||||||||||||||||||||||||||||||||
| totalElements: number; | ||||||||||||||||||||||||||||||||||||||||||
| totalPages: number; | ||||||||||||||||||||||||||||||||||||||||||
| last: boolean; | ||||||||||||||||||||||||||||||||||||||||||
| first: boolean; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // API 응답 타입 | ||||||||||||||||||||||||||||||||||||||||||
| export interface SearchBooksResponse { | ||||||||||||||||||||||||||||||||||||||||||
| isSuccess: boolean; | ||||||||||||||||||||||||||||||||||||||||||
| code: number; | ||||||||||||||||||||||||||||||||||||||||||
| message: string; | ||||||||||||||||||||||||||||||||||||||||||
| data: SearchBooksData; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export const getSearchBooks = async ( | ||||||||||||||||||||||||||||||||||||||||||
| query: string, | ||||||||||||||||||||||||||||||||||||||||||
| page: number = 1, | ||||||||||||||||||||||||||||||||||||||||||
| isFinalized: boolean = false, | ||||||||||||||||||||||||||||||||||||||||||
| ): Promise<SearchBooksResponse> => { | ||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||
| const response = await apiClient.get<SearchBooksResponse>('/books', { | ||||||||||||||||||||||||||||||||||||||||||
| params: { | ||||||||||||||||||||||||||||||||||||||||||
| keyword: query.trim(), | ||||||||||||||||||||||||||||||||||||||||||
| page: page, | ||||||||||||||||||||||||||||||||||||||||||
| isFinalized: isFinalized, | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||
| console.error('책 검색 API 오류:', error); | ||||||||||||||||||||||||||||||||||||||||||
| throw error; | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export const convertToSearchedBooks = (apiBooks: BookSearchItem[]): SearchedBook[] => { | ||||||||||||||||||||||||||||||||||||||||||
| return apiBooks.map((book, index) => ({ | ||||||||||||||||||||||||||||||||||||||||||
| id: index + 1, | ||||||||||||||||||||||||||||||||||||||||||
| title: book.title, | ||||||||||||||||||||||||||||||||||||||||||
| author: book.authorName, | ||||||||||||||||||||||||||||||||||||||||||
| publisher: book.publisher, | ||||||||||||||||||||||||||||||||||||||||||
| coverUrl: book.imageUrl, | ||||||||||||||||||||||||||||||||||||||||||
| isbn: book.isbn, | ||||||||||||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+61
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainid로 index 사용은 불안정 — 키/식별자 용도로 ISBN 사용 권장 id를 index 기반으로 생성하면 페이지네이션/정렬 변경 시 불안정합니다. 가능하면 React key와 식별자는 ISBN을 사용하세요. 현재 UI에서 key로 id를 쓰지 않는지 확인해 주세요. 검증 스크립트(리스트 렌더링에서 book.id를 key로 쓰는지 검색): 🏁 Script executed: #!/bin/bash
# book.id를 React key로 사용하는 구문 탐색
rg -n --no-heading "key=\{[^}]*book\.id[^}]*\}" -A 2 -B 2Length of output: 1167 페이지네이션·정렬 안정화를 위해 현재 수정 제안: // File: src/api/books/getSearchBooks.ts
-export const convertToSearchedBooks = (apiBooks: BookSearchItem[]): SearchedBook[] => {
- return apiBooks.map((book, index) => ({
- id: index + 1,
+export const convertToSearchedBooks = (apiBooks: BookSearchItem[]): SearchedBook[] => {
+ return apiBooks.map((book) => ({
+ id: book.isbn, // index → ISBN으로 대체
title: book.title,
author: book.authorName,
publisher: book.publisher,
coverUrl: book.imageUrl,
- isbn: book.isbn,
+ isbn: book.isbn, // id와 중복되지 않도록, 필요한 경우 타입도 함께 검토
}));
};• BookSearchResult.tsx (34) 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,32 @@ | ||||||||||||||||||||||||||||||||||||||||
| import { apiClient } from '../index'; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // 북마크 요청 타입 | ||||||||||||||||||||||||||||||||||||||||
| export interface SaveBookRequest { | ||||||||||||||||||||||||||||||||||||||||
| type: boolean; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // 북마크 응답 데이터 타입 | ||||||||||||||||||||||||||||||||||||||||
| export interface SaveBookData { | ||||||||||||||||||||||||||||||||||||||||
| isbn: string; | ||||||||||||||||||||||||||||||||||||||||
| isSaved: boolean; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // 북마크 응답 타입 | ||||||||||||||||||||||||||||||||||||||||
| export interface SaveBookResponse { | ||||||||||||||||||||||||||||||||||||||||
| isSuccess: boolean; | ||||||||||||||||||||||||||||||||||||||||
| code: number; | ||||||||||||||||||||||||||||||||||||||||
| message: string; | ||||||||||||||||||||||||||||||||||||||||
| data: SaveBookData; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| export const postSaveBook = async (isbn: string, type: boolean): Promise<SaveBookResponse> => { | ||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||
| const response = await apiClient.post<SaveBookResponse>(`/books/${isbn}/saved`, { | ||||||||||||||||||||||||||||||||||||||||
| type: type, | ||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||
| return response.data; | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+22
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 요청 바디 타입 적용 및 ISBN URL 인코딩
적용 diff: -export const postSaveBook = async (isbn: string, type: boolean): Promise<SaveBookResponse> => {
+export const postSaveBook = async (isbn: string, type: boolean): Promise<SaveBookResponse> => {
try {
- const response = await apiClient.post<SaveBookResponse>(`/books/${isbn}/saved`, {
- type: type,
- });
+ const payload: SaveBookRequest = { type };
+ const response = await apiClient.post<SaveBookResponse>(
+ `/books/${encodeURIComponent(isbn)}/saved`,
+ payload,
+ );
return response.data;
} catch (error) {
console.error('책 저장 API 오류:', error);
throw error;
}
};📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||
| console.error('책 저장 API 오류:', error); | ||||||||||||||||||||||||||||||||||||||||
| throw error; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -63,7 +63,7 @@ const Router = () => { | |
| <Route path="feed/:feedId" element={<FeedDetailPage />} /> | ||
| <Route path="search" element={<Search />} /> | ||
| <Route path="search/applybook" element={<ApplyBook />} /> | ||
| <Route path="search/book" element={<SearchBook />} /> | ||
| <Route path="search/book/:isbn" element={<SearchBook />} /> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chain동적 라우트 전환 LGTM. 경로 충돌 및 URL 인코딩 점검 권장
다음 스크립트로 링크/네비게이션에서 인코딩 누락 가능성을 훑어볼 수 있습니다. 🏁 Script executed: #!/bin/bash
# /search/book/${...} 사용 지점 나열
rg -n -S "/search/book/\\$\\{" src -g "src/**/*.tsx" -H
echo
echo "encodeURIComponent 사용 지점"
rg -n -S "encodeURIComponent\\(" src -g "src/**/*.tsx" -HLength of output: 403 동적 라우트 전환 LGTM – URL 인코딩 적용 필요
수정 대상 위치:
예시 변경(diff): - navigate(`/search/book/${isbn}`);
+ navigate(`/search/book/${encodeURIComponent(isbn)}`);- onClick={() => navigate(`/search/book/${book.isbn}`)}
+ onClick={() => navigate(`/search/book/${encodeURIComponent(book.isbn)}`)}
🤖 Prompt for AI Agents |
||
| <Route path="search/book/group" element={<SearchBookGroup />} /> | ||
| <Route path="otherfeed/:userId" element={<OtherFeedPage />} /> | ||
| <Route path="follow/:type/:userId" element={<FollowerListPage />} /> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
ISBN을 URL 인코딩하고 로그에 컨텍스트 추가
특수문자(하이픈 등) 포함 가능성을 고려해 경로 파라미터는 인코딩하는 게 안전합니다. 에러 로그에 isbn을 포함해 디버깅 가시성을 높이십시오.
export const getBookDetail = async (isbn: string): Promise<BookDetailResponse> => { try { - const response = await apiClient.get<BookDetailResponse>(`/books/${isbn}`); + const response = await apiClient.get<BookDetailResponse>(`/books/${encodeURIComponent(isbn)}`); return response.data; } catch (error) { - console.error('책 상세 정보 API 오류:', error); + console.error(`책 상세 정보 API 오류 (isbn: ${isbn}):`, error); throw error; } };📝 Committable suggestion
🤖 Prompt for AI Agents