diff --git a/src/api/core/index.ts b/src/api/core/index.ts index 4a142b60..51c9e952 100644 --- a/src/api/core/index.ts +++ b/src/api/core/index.ts @@ -2,6 +2,8 @@ import { notFound, redirect } from 'next/navigation'; import axios from 'axios'; +import { CommonErrorResponse, CommonSuccessResponse } from '@/types/service/common'; + export const baseAPI = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_BASE_URL, timeout: 20000, @@ -45,6 +47,44 @@ baseAPI.interceptors.response.use( } } - return Promise.reject(error); + const errorResponse: CommonErrorResponse = error.response?.data || { + type: 'about:blank', + title: 'Network Error', + status: 0, + detail: '서버와 연결할 수 없습니다.', + instance: error.config?.url || '', + errorCode: 'NETWORK_ERROR', + }; + + throw errorResponse; }, ); + +// 공통 응답 형식 처리를 위한 api 헬퍼 +export const api = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + get: async (url: string, config?: any): Promise => { + const response = await baseAPI.get>(url, config); + return response.data.data; + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + post: async (url: string, data?: any, config?: any): Promise => { + const response = await baseAPI.post>(url, data, config); + return response.data.data; + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + put: async (url: string, data?: any, config?: any): Promise => { + const response = await baseAPI.put>(url, data, config); + return response.data.data; + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + delete: async (url: string, config?: any): Promise => { + const response = await baseAPI.delete>(url, config); + return response.data.data; + }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + patch: async (url: string, data?: any, config?: any): Promise => { + const response = await baseAPI.patch>(url, data, config); + return response.data.data; + }, +}; diff --git a/src/api/service/user-service/index.ts b/src/api/service/user-service/index.ts index a43dbfc8..129c4f8b 100644 --- a/src/api/service/user-service/index.ts +++ b/src/api/service/user-service/index.ts @@ -1,55 +1,20 @@ -import { baseAPI } from '@/api/core'; +import { api } from '@/api/core'; import { Follow, GetUserPayload, UpdateMePayload, User } from '@/types/service/user'; export const userServiceRemote = () => ({ // 1. 사용자 단건 조회 - getUser: async (payload: GetUserPayload) => { - try { - const response = await baseAPI.get(`/users/${payload.userId}`); - return response.data; - } catch (error) { - throw error; - } - }, + getUser: async (payload: GetUserPayload) => api.get(`/users/${payload.userId}`), // 2. 프로필 편집 - updateMe: async (payload: UpdateMePayload) => { - try { - const response = await baseAPI.patch('/users', payload); - return response.data; - } catch (error) { - throw error; - } - }, + updateMe: async (payload: UpdateMePayload) => api.patch('/users', payload), // 3. 회원탈퇴 - deleteMe: async () => { - try { - const response = await baseAPI.delete(`/users`); - return response.data; - } catch (error) { - throw error; - } - }, + deleteMe: async () => api.delete(`/users`), // 4. 사용자 프로필 이미지 변경 // 5. 사용자 팔로우 - followUser: async (payload: Follow) => { - try { - const response = await baseAPI.post(`/follows`, payload); - return response.data; - } catch (error) { - throw error; - } - }, + followUser: async (payload: Follow) => api.post(`/follows`, payload), // 6. 사용자 언팔로우 - unfollowUser: async (payload: Follow) => { - try { - const response = await baseAPI.delete(`/follows/${payload.followeeId}`); - return response.data; - } catch (error) { - throw error; - } - }, + unfollowUser: async (payload: Follow) => api.delete(`/follows/${payload.followeeId}`), }); diff --git a/src/mock/service/common/common-mock.ts b/src/mock/service/common/common-mock.ts new file mode 100644 index 00000000..cc818087 --- /dev/null +++ b/src/mock/service/common/common-mock.ts @@ -0,0 +1,26 @@ +import { CommonSuccessResponse } from '@/types/service/common'; + +export const createMockSuccessResponse = (data: T): CommonSuccessResponse => ({ + status: 200, + message: '요청이 정상적으로 처리되었습니다.', + data, +}); + +interface CreateErrorResponseType { + status: number; + detail: string; + errorCode: string; +} + +export const createMockErrorResponse = ({ + status, + detail, + errorCode, +}: CreateErrorResponseType) => ({ + type: `https://example.com/errors/${errorCode}`, + title: errorCode.toUpperCase(), + status, + detail, + instance: '/api/test', + errorCode, +}); diff --git a/src/mock/service/user/users-handler.ts b/src/mock/service/user/users-handler.ts index 764482a2..d1eb26bc 100644 --- a/src/mock/service/user/users-handler.ts +++ b/src/mock/service/user/users-handler.ts @@ -2,37 +2,47 @@ import { http, HttpResponse } from 'msw'; import { User } from '@/types/service/user'; +import { createMockErrorResponse, createMockSuccessResponse } from '../common/common-mock'; import { mockUserItems } from './users-mock'; -const getUserItemMock = http.get(`*/api/v1/users/:userId`, ({ params }) => { +const getUserItemMock = http.get(`*/users/:userId`, ({ params }) => { const id = Number(params.userId); const user = mockUserItems.find((item) => item.id === id); if (!user) { - return HttpResponse.json({ message: '존재하지 않는 유저입니다' }, { status: 404 }); + return HttpResponse.json( + createMockErrorResponse({ + status: 404, + detail: '존재하지 않는 유저입니다.', + errorCode: 'U001', + }), + { status: 404 }, + ); } - return HttpResponse.json(user); + return HttpResponse.json(createMockSuccessResponse(user)); }); -const updateUserItemMock = http.patch(`*/api/v1/users`, async ({ request }) => { +const updateUserItemMock = http.patch(`*/users`, async ({ request }) => { const body = (await request.json()) as User; - return HttpResponse.json({ - ...mockUserItems[0], - ...body, - }); + return HttpResponse.json( + createMockSuccessResponse({ + ...mockUserItems[0], + ...body, + }), + ); }); -const deleteUserItemMock = http.delete(`*/api/v1/users`, async () => { - return new HttpResponse(null, { status: 204 }); +const deleteUserItemMock = http.delete(`*/users`, async () => { + return HttpResponse.json(createMockSuccessResponse(null)); }); -const followUserItemMock = http.post(`*/api/v1/follows`, async () => { - return new HttpResponse(null, { status: 204 }); +const followUserItemMock = http.post(`*/follows`, async () => { + return HttpResponse.json(createMockSuccessResponse(null)); }); -const unfollowUserItemMock = http.delete(`*/api/v1/follows/:followId`, async () => { - return new HttpResponse(null, { status: 204 }); +const unfollowUserItemMock = http.delete(`*/follows/:followId`, async () => { + return HttpResponse.json(createMockSuccessResponse(null)); }); export const userHandlers = [ diff --git a/src/types/react-query.d.ts b/src/types/react-query.d.ts new file mode 100644 index 00000000..552307a0 --- /dev/null +++ b/src/types/react-query.d.ts @@ -0,0 +1,10 @@ +// types/react-query.d.ts +import { CommonErrorResponse } from './service/common'; + +import '@tanstack/react-query'; + +declare module '@tanstack/react-query' { + interface Register { + defaultError: CommonErrorResponse; + } +} diff --git a/src/types/service/common.ts b/src/types/service/common.ts index 68eca4a0..0624fe19 100644 --- a/src/types/service/common.ts +++ b/src/types/service/common.ts @@ -6,3 +6,9 @@ export interface CommonErrorResponse { instance: string; errorCode?: string; } + +export interface CommonSuccessResponse { + status: number; + message: string; + data: T; +}