[FEAT] axios 기반 공통 API 레이어 구축#8
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
📝 WalkthroughWalkthrough기존 분산된 fetch 기반 API 호출들을 axios 공통 인스턴스로 통일하고, 공통 헤더·에러 처리를 중앙화합니다. 응답 인터셉터로 401/403 에러를 처리하며, 기능별 타입화된 API 모듈들로 구조화했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as React Component
participant Interceptor as HTTP Interceptor
participant Backend as Backend Server
Client->>Interceptor: API 요청<br/>(axios.get/post/patch)
Interceptor->>Backend: HTTP 요청 전송<br/>(withCredentials: true)
alt 성공 (2xx)
Backend-->>Interceptor: 응답 반환
Interceptor-->>Client: 파싱된 데이터 반환
else 401 Unauthorized
Backend-->>Interceptor: 401 응답
Interceptor->>Interceptor: openLoginModal 호출
Interceptor->>Client: 토스트 메시지 표시<br/>(에러 rethrow)
else 403 Forbidden
Backend-->>Interceptor: 403 응답
Interceptor->>Client: 로컬라이즈된 토스트<br/>에러 메시지 표시<br/>(에러 rethrow)
else 5xx Server Error
Backend-->>Interceptor: 5xx 응답
Interceptor->>Client: 서버 에러 토스트<br/>메시지 표시<br/>(에러 rethrow)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~35 분 Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 20 minutes and 51 seconds.Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/pages/AdminPayments.jsx (2)
75-83:⚠️ Potential issue | 🔴 Critical | ⚡ Quick win
listPayments응답 언랩 누락으로 페이지네이션/리스트가 깨질 수 있습니다.현재는 AxiosResponse를 바로 쓰고 있어
totalPages,page가 항상 기본값으로 떨어지거나,items가 배열이 아니게 될 수 있습니다.🔧 제안 수정
- const res = await adminApi.listPayments({ + const res = await adminApi.listPayments({ token, page: p, limit: 20, ...Object.fromEntries(Object.entries(filters).filter(([_, v]) => v)), }); - setItems(res?.data || []); - setTotalPages(res?.totalPages || 1); - setPage(res?.page || p); + const body = res?.data ?? {}; + setItems(Array.isArray(body?.data) ? body.data : []); + setTotalPages(body?.totalPages || 1); + setPage(body?.page || p);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/AdminPayments.jsx` around lines 75 - 83, The code is using the raw Axios response (res) instead of unwrapping res.data, causing page/list values to fall back to defaults or be invalid; update the adminApi.listPayments handling to read the payload from res.data (e.g., const payload = res?.data || {}), then setItems using a safe check (ensure payload.items is an array, otherwise []), setTotalPages using a numeric fallback from payload.totalPages (default 1), and setPage using a numeric fallback from payload.page (default p); refer to adminApi.listPayments, res, setItems, setTotalPages, and setPage when making these changes.
342-349:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
getPaymentStats도 AxiosResponse 언랩이 필요합니다.
setStats(s)로 저장하면 실제 통계 객체 대신 응답 래퍼가 들어가서 카드/테이블이 비정상 표시됩니다.s.data를 저장하도록 맞춰 주세요.🔧 제안 수정
- const s = await adminApi.getPaymentStats({ + const s = await adminApi.getPaymentStats({ token, from: filters.from, to: filters.to, buyerId: filters.buyerId, companyId: filters.companyId, }); - setStats(s); + setStats(s?.data ?? { since: "", byStatus: {}, byCurrency: {}, byCurrencyStatus: {} });Also applies to: 388-395, 413-420, 438-445, 469-476
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/AdminPayments.jsx` around lines 342 - 349, The response wrapper from Axios is being stored instead of the actual payload: change usages where you call adminApi.getPaymentStats and immediately call setStats(s) to instead setStats(s.data); apply the same unwrapping (.data) to the other adminApi.* calls in this file so their corresponding setters (e.g., replace setPayments(response) with setPayments(response.data), setTopBuyers(...)->setTopBuyers(... .data), setTopCompanies(...)->setTopCompanies(... .data), and any count/stat setters) receive the raw data rather than the AxiosResponse object.
🧹 Nitpick comments (1)
src/apis/modules/consultations.ts (1)
35-36: ⚡ Quick win하드코딩 경로를 ENDPOINTS로 통일해 주세요.
Line 35-36만 문자열 리터럴 경로를 직접 사용하고 있어, 이번 PR의 “경로 상수 중앙화” 목표와 어긋납니다.
수정 예시
createRequest: (data: Record<string, unknown>) => - http.post("/consultants/requests", data), + http.post(ENDPOINTS.consultations.requests, data), };// src/apis/endpoints.ts (추가 예시) consultations: { root: "/consultations", status: (id: string) => `/consultations/${id}/status`, requests: "/consultants/requests", }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/apis/modules/consultations.ts` around lines 35 - 36, The createRequest function in src/apis/modules/consultations.ts currently uses a hardcoded path string "/consultants/requests"; replace that literal with the centralized endpoint constant (e.g., ENDPOINTS.consultations.requests) so the module uses the project's endpoint constants; update the createRequest implementation to call http.post(ENDPOINTS.consultations.requests, data) and ensure ENDPOINTS (or the exported endpoints object) is imported at the top of the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/apis/modules/payments.ts`:
- Around line 13-16: The create method in payments.ts accepts an idempotencyKey
but currently allows empty or whitespace-only strings; update the create
function to validate idempotencyKey (e.g., trim() and check length > 0) and
reject/throw an error immediately if it's empty or only whitespace before
calling http.post (reference the create function and the header
"Idempotency-Key"); ensure the header is only sent when the key is valid and
return a clear error message like "Idempotency key is required" so callers can
handle the failure.
- Around line 13-29: The payment API calls (create, getById, refresh,
getSummary, getRecent) expect the payload directly but the HTTP response
interceptor still returns the full AxiosResponse (res => res), causing consumers
to read undefined (they access res._id or res.payment._id); update the response
interceptor in the HTTP client module so it returns the response body (res.data)
instead of the entire AxiosResponse, and ensure the http.post/http.get generic
typings propagate the data type (or alternatively map http.post/get calls inside
the payments module to return response.data) so callers of
create/getById/refresh/getSummary/getRecent receive the actual payload.
In `@src/App.jsx`:
- Around line 126-127: buyersApi.list() returns a paginated AxiosResponse so
accessing res.data?.[0] is wrong and yields undefined; update the code that
assigns buyer (around the buyersApi.list call) to read the first item from the
paginated payload — i.e., use res.data?.data?.[0] (preserving optional chaining)
when setting buyer so the demo login can find the first buyer.
In `@src/hooks/usePayments.js`:
- Line 39: mutationFn 호출에서 idemKey가 비어 있으면 런타임에서 차단해야 합니다: usePayments.js의
mutationFn (현재: mutationFn: ({ payload, idemKey }) =>
paymentsApi.create(payload, idemKey)) 시작 부분에서 idemKey를 검사하고 비어있으면 예외를 던지거나 에러를
반환하도록 추가해 주세요(예: throw new Error 또는 반환된 Promise.reject). 이렇게 하면
paymentsApi.create가 빈 idemKey로 호출되는 것을 방지하고 호출자에게 명확한 실패 이유를 제공합니다.
In `@src/pages/BuyerForm.jsx`:
- Around line 53-54: The issue is that buyersApi.create returns an AxiosResponse
so res?._id is undefined; change the id extraction to read the response payload
(e.g., use res?.data?._id or destructure const { data } = res and use data._id)
in BuyerForm.jsx where you call buyersApi.create(payload) so id is set from the
actual response body, or alternatively update buyersApi.create to return
res.data directly and then keep id = res?._id.
In `@src/pages/ContactPage.jsx`:
- Around line 24-25: The payment ID lookup is using the raw Axios response shape
incorrectly (res?.payment?._id || res?._id) while earlier code uses res?.data;
update the logic to consistently read the payload from res.data (e.g. const
payload = res?.data || {}; then use payload.payment?._id || payload._id) so the
navigation to /payments/checkout/{id} receives the actual ID returned by
matchesApi.list / payment API; adjust any references to res?.payment or res?._id
to use payload.payment and payload._id instead.
In `@src/stores/authStore.js`:
- Around line 9-13: clearAuth currently only resets buyerId and buyerName which
can leave the login modal open; update the clearAuth setter to also set
loginModalOpen: false so the modal state is cleared when clearing auth. Locate
the clearAuth function in the auth store (clearAuth: () => set({...})) and
include loginModalOpen: false in the object returned by set; keep other helpers
(setAuth, openLoginModal, closeLoginModal) unchanged.
---
Outside diff comments:
In `@src/pages/AdminPayments.jsx`:
- Around line 75-83: The code is using the raw Axios response (res) instead of
unwrapping res.data, causing page/list values to fall back to defaults or be
invalid; update the adminApi.listPayments handling to read the payload from
res.data (e.g., const payload = res?.data || {}), then setItems using a safe
check (ensure payload.items is an array, otherwise []), setTotalPages using a
numeric fallback from payload.totalPages (default 1), and setPage using a
numeric fallback from payload.page (default p); refer to adminApi.listPayments,
res, setItems, setTotalPages, and setPage when making these changes.
- Around line 342-349: The response wrapper from Axios is being stored instead
of the actual payload: change usages where you call adminApi.getPaymentStats and
immediately call setStats(s) to instead setStats(s.data); apply the same
unwrapping (.data) to the other adminApi.* calls in this file so their
corresponding setters (e.g., replace setPayments(response) with
setPayments(response.data), setTopBuyers(...)->setTopBuyers(... .data),
setTopCompanies(...)->setTopCompanies(... .data), and any count/stat setters)
receive the raw data rather than the AxiosResponse object.
---
Nitpick comments:
In `@src/apis/modules/consultations.ts`:
- Around line 35-36: The createRequest function in
src/apis/modules/consultations.ts currently uses a hardcoded path string
"/consultants/requests"; replace that literal with the centralized endpoint
constant (e.g., ENDPOINTS.consultations.requests) so the module uses the
project's endpoint constants; update the createRequest implementation to call
http.post(ENDPOINTS.consultations.requests, data) and ensure ENDPOINTS (or the
exported endpoints object) is imported at the top of the file.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: eb71f537-6fd1-4657-8e85-9eb3be0f42c8
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (39)
.env.examplepackage.jsonsrc/App.jsxsrc/apis/admin.jssrc/apis/analytics.jssrc/apis/buyers.jssrc/apis/client.jssrc/apis/companies.jssrc/apis/consultants.jssrc/apis/endpoints.tssrc/apis/http.tssrc/apis/index.tssrc/apis/matches.jssrc/apis/modules/admin.tssrc/apis/modules/analytics.tssrc/apis/modules/auth.tssrc/apis/modules/buyers.tssrc/apis/modules/companies.tssrc/apis/modules/consultations.tssrc/apis/modules/matches.tssrc/apis/modules/partners.tssrc/apis/modules/payments.tssrc/apis/payments.jssrc/apis/types.tssrc/hooks/useAnalytics.jssrc/hooks/useBuyers.jssrc/hooks/useCompanies.jssrc/hooks/useConsultations.jssrc/hooks/useMatches.jssrc/hooks/usePayments.jssrc/pages/AdminMatches.jsxsrc/pages/AdminPayments.jsxsrc/pages/AdminStats.jsxsrc/pages/BuyerForm.jsxsrc/pages/ContactPage.jsxsrc/pages/Matches.jsxsrc/pages/PartnerSearch.jsxsrc/pages/PaymentCheckout.jsxsrc/stores/authStore.js
💤 Files with no reviewable changes (8)
- src/apis/matches.js
- src/apis/admin.js
- src/apis/companies.js
- src/apis/consultants.js
- src/apis/payments.js
- src/apis/analytics.js
- src/apis/client.js
- src/apis/buyers.js
| const res = await buyersApi.list({ limit: 1 }); | ||
| const buyer = res.data?.[0]; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# buyersApi.list 반환 계약 확인 및 호출부 매칭 확인
fd -i "http.ts" src | xargs -r sed -n '1,220p'
fd -i "buyers.ts" src/apis/modules | xargs -r sed -n '1,220p'
rg -n -C2 'buyersApi\.list\(|res\.data\?\.\[0\]|payload\?\.data\?\.\[0\]' srcRepository: K-Statra/frontend
Length of output: 2170
🏁 Script executed:
# Find PaginatedResponse type definition
rg -n "type PaginatedResponse|interface PaginatedResponse" src
# Check the types file
fd -i "types.ts" src/apis | xargs -r cat
# Check how useBuyers hook handles the response
cat -n src/hooks/useBuyers.js
# Check other usages of buyersApi.list() response
rg -n -A5 "buyersApi\.list" src --type js --type jsx --type tsRepository: K-Statra/frontend
Length of output: 680
Line 127의 응답 구조 접근이 잘못되어 demo 로그인이 항상 실패합니다.
buyersApi.list()은 AxiosResponse<PaginatedResponse<unknown>>을 반환하므로, res.data는 { page, limit, total, totalPages, data: T[] } 구조입니다. 현재 res.data?.[0]은 객체의 첫 번째 요소를 접근하려는 것이므로 항상 undefined를 반환합니다. 올바른 접근은 res.data?.data?.[0]입니다.
수정 제안
const res = await buyersApi.list({ limit: 1 });
- const buyer = res.data?.[0];
+ const buyer = res.data?.data?.[0];📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const res = await buyersApi.list({ limit: 1 }); | |
| const buyer = res.data?.[0]; | |
| const res = await buyersApi.list({ limit: 1 }); | |
| const buyer = res.data?.data?.[0]; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/App.jsx` around lines 126 - 127, buyersApi.list() returns a paginated
AxiosResponse so accessing res.data?.[0] is wrong and yields undefined; update
the code that assigns buyer (around the buyersApi.list call) to read the first
item from the paginated payload — i.e., use res.data?.data?.[0] (preserving
optional chaining) when setting buyer so the demo login can find the first
buyer.
| const res = await buyersApi.create(payload); | ||
| const id = res?._id; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# buyersApi.create 반환 shape 확인 (success interceptor 포함)
fd -i "http.ts" src | xargs -r sed -n '1,220p'
fd -i "buyers.ts" src/apis/modules | xargs -r sed -n '1,220p'
rg -n -C2 'buyersApi\.create\(|res\?\._id|res\.data\?\._id' srcRepository: K-Statra/frontend
Length of output: 3104
AxiosResponse에서 데이터를 언래핑하지 않으면 _id가 undefined가 되어 리다이렉트가 동작하지 않습니다.
buyersApi.create는 http.post를 반환하며, 응답 인터셉터가 전체 AxiosResponse 객체를 반환하므로 라인 54의 res?._id는 항상 undefined입니다. 실제 데이터는 res.data._id에 위치하기 때문에 id는 falsy 값이 되어 이후 리다이렉트 조건을 만족하지 못합니다.
수정 제안
const res = await buyersApi.create(payload);
- const id = res?._id;
+ const id = res?.data?._id;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const res = await buyersApi.create(payload); | |
| const id = res?._id; | |
| const res = await buyersApi.create(payload); | |
| const id = res?.data?._id; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/BuyerForm.jsx` around lines 53 - 54, The issue is that
buyersApi.create returns an AxiosResponse so res?._id is undefined; change the
id extraction to read the response payload (e.g., use res?.data?._id or
destructure const { data } = res and use data._id) in BuyerForm.jsx where you
call buyersApi.create(payload) so id is set from the actual response body, or
alternatively update buyersApi.create to return res.data directly and then keep
id = res?._id.
| const res = await matchesApi.list(buyerId, 50); | ||
| let arr = res?.data || []; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# matches/payments 모듈 및 interceptor의 반환 shape 확인
fd -i "http.ts" src | xargs -r sed -n '1,240p'
fd -i "matches.ts" src/apis/modules | xargs -r sed -n '1,220p'
fd -i "payments.ts" src/apis/modules | xargs -r sed -n '1,260p'
rg -n -C2 'matchesApi\.list\(|paymentsApi\.create\(|res\?\.data \|\| \[\]|res\?\.payment\?\._id' src/pages/ContactPage.jsxRepository: K-Statra/frontend
Length of output: 2965
🏁 Script executed:
rg -n "payment\?\._id|\.payment|paymentPayload|CreatePaymentDto" src --type ts --type tsx -A2 -B2Repository: K-Statra/frontend
Length of output: 89
🏁 Script executed:
fd -i "types" src/apis | head -5
rg "interface.*Payment|type.*Payment" src --type ts -A5 | head -50Repository: K-Statra/frontend
Length of output: 400
🏁 Script executed:
cat src/apis/types.tsRepository: K-Statra/frontend
Length of output: 282
🏁 Script executed:
rg -n "payment" src/pages/ContactPage.jsx -i -C3 | head -60Repository: K-Statra/frontend
Length of output: 1143
🏁 Script executed:
rg "interface.*Payment|type.*Payment|export.*Payment" src/apis --type ts -A8Repository: K-Statra/frontend
Length of output: 886
🏁 Script executed:
rg "res\.data\?\.payment|paymentPayload|Payment.*Response" src --type ts -C2Repository: K-Statra/frontend
Length of output: 43
🏁 Script executed:
rg -n "\.checkout|nav\(|navigate\(" src/pages/ContactPage.jsx -C1Repository: K-Statra/frontend
Length of output: 351
🏁 Script executed:
# Check if there are any other files handling payment responses to see the pattern
rg "paymentsApi\." src --type ts -A3 | head -40Repository: K-Statra/frontend
Length of output: 43
결제 응답 파싱이 axios 계약을 위반하여 결제 흐름이 실패합니다.
Line 123에서 res?.payment?._id || res?._id로 접근하는데, http 인터셉터는 AxiosResponse를 그대로 반환하므로 실제 페이로드는 res.data 안에 있습니다. Line 25에서는 올바르게 res?.data로 접근하지만, Line 123은 일관성 없이 res.payment를 직접 접근하려 하므로 항상 undefined가 되어 결제 후 /payments/checkout/{id} 네비게이션이 실패합니다.
수정 제안
- const pid = res?.payment?._id || res?._id;
+ const pid = res?.data?.payment?._id || res?.data?._id;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/ContactPage.jsx` around lines 24 - 25, The payment ID lookup is
using the raw Axios response shape incorrectly (res?.payment?._id || res?._id)
while earlier code uses res?.data; update the logic to consistently read the
payload from res.data (e.g. const payload = res?.data || {}; then use
payload.payment?._id || payload._id) so the navigation to
/payments/checkout/{id} receives the actual ID returned by matchesApi.list /
payment API; adjust any references to res?.payment or res?._id to use
payload.payment and payload._id instead.
🔍 PR 요약
🧾 관련 이슈
🛠️ 주요 변경 사항
pnpm add axios,pnpm add react-hot-toastsrc/apis/http.ts생성 (axios 인스턴스,withCredentials: true, 응답 인터셉터)src/apis/types.ts생성 (PaginatedResponse, ApiError 타입)src/apis/endpoints.ts생성 (API 경로 상수)src/apis/modules/모듈 생성 (auth, payments, companies, partners, matches, buyers, consultations, admin, analytics)src/apis/index.ts생성 (모듈 일괄 export)src/stores/authStore.js에openLoginModal,closeLoginModal액션 추가src/apis/*.jsfetch 기반 파일 전체 제거 및 import 경로 교체.env,.env.example에VITE_API_BASE_URL추가🧠 의도 및 배경
withCredentials: true설정Idempotency-Key헤더 필수 처리 유지Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항