2025-08-29 10:32:21 +09:00
|
|
|
/**
|
|
|
|
|
* API 호출을 위한 편의 함수
|
|
|
|
|
*
|
|
|
|
|
* @template T - 응답 데이터의 타입
|
|
|
|
|
* @param path - API 엔드포인트 경로 (예: '/users', '/users/1')
|
2025-09-03 17:17:53 +09:00
|
|
|
* @param options - 요청 및 에러 처리 옵션
|
2025-08-29 10:32:21 +09:00
|
|
|
* @returns Promise<T> - API 응답 데이터
|
|
|
|
|
*
|
|
|
|
|
* @example
|
2025-09-24 16:25:30 +09:00
|
|
|
* // GET 요청 (기본 - 전역 로딩 자동 적용)
|
2025-08-29 10:32:21 +09:00
|
|
|
* const users = await useApi<User[]>('/users')
|
|
|
|
|
*
|
2025-09-24 16:25:30 +09:00
|
|
|
* // POST 요청 (커스텀 로딩 메시지)
|
2025-08-29 10:32:21 +09:00
|
|
|
* const newUser = await useApi<User>('/users', {
|
|
|
|
|
* method: 'POST',
|
2025-09-24 16:25:30 +09:00
|
|
|
* body: { name: 'John', email: 'john@example.com' },
|
|
|
|
|
* loadingMessage: '사용자를 생성하는 중...'
|
2025-08-29 10:32:21 +09:00
|
|
|
* })
|
|
|
|
|
*
|
2025-09-24 16:25:30 +09:00
|
|
|
* // 전역 로딩 없이 API 호출
|
|
|
|
|
* const data = await useApi<User[]>('/users', {
|
|
|
|
|
* useGlobalLoading: false
|
|
|
|
|
* })
|
2025-08-29 10:32:21 +09:00
|
|
|
*
|
2025-09-03 17:17:53 +09:00
|
|
|
* // 에러를 직접 처리
|
|
|
|
|
* try {
|
|
|
|
|
* const data = await useApi<User[]>('/users', { handleError: false })
|
|
|
|
|
* } catch (error) {
|
|
|
|
|
* // 직접 에러 처리
|
|
|
|
|
* }
|
2025-08-29 10:32:21 +09:00
|
|
|
*
|
|
|
|
|
* // FormData 업로드
|
|
|
|
|
* const formData = new FormData()
|
|
|
|
|
* formData.append('file', file)
|
2025-09-24 16:25:30 +09:00
|
|
|
* await useApi('/upload', {
|
|
|
|
|
* method: 'POST',
|
|
|
|
|
* body: formData,
|
|
|
|
|
* loadingMessage: '파일을 업로드하는 중...'
|
|
|
|
|
* })
|
2025-08-29 10:32:21 +09:00
|
|
|
*/
|
2025-09-03 17:17:53 +09:00
|
|
|
export const useApi = async <T>(
|
|
|
|
|
path: string,
|
|
|
|
|
options?: {
|
|
|
|
|
// API 요청 옵션
|
|
|
|
|
method?: string;
|
|
|
|
|
body?: any;
|
|
|
|
|
headers?: Record<string, string>;
|
|
|
|
|
// 에러 처리 옵션
|
|
|
|
|
handleError?: boolean; // true: 에러를 null로 반환, false: 에러를 다시 던짐
|
|
|
|
|
showAlert?: boolean; // true: 에러 시 alert 표시
|
2025-09-24 16:25:30 +09:00
|
|
|
// 로딩 옵션
|
|
|
|
|
loadingMessage?: string; // 로딩 메시지
|
|
|
|
|
useGlobalLoading?: boolean; // 전역 로딩 사용 여부 (기본값: true)
|
2025-09-03 17:17:53 +09:00
|
|
|
}
|
|
|
|
|
): Promise<T> => {
|
2025-09-24 16:25:30 +09:00
|
|
|
const { withLoading } = useLoading();
|
2025-09-03 17:17:53 +09:00
|
|
|
|
2025-09-24 16:25:30 +09:00
|
|
|
// API 호출 로직을 별도 함수로 분리
|
|
|
|
|
const apiCall = async (): Promise<T> => {
|
|
|
|
|
const { $api } = useNuxtApp();
|
2025-09-03 17:17:53 +09:00
|
|
|
|
2025-09-24 16:25:30 +09:00
|
|
|
// 기본값 설정
|
|
|
|
|
const {
|
|
|
|
|
method = "GET",
|
|
|
|
|
body,
|
|
|
|
|
headers,
|
|
|
|
|
handleError = true,
|
|
|
|
|
showAlert = true,
|
|
|
|
|
} = options || {};
|
|
|
|
|
|
|
|
|
|
// API 요청 옵션 구성
|
|
|
|
|
const apiOpts = {
|
|
|
|
|
method,
|
|
|
|
|
...(body && { body }),
|
|
|
|
|
...(headers && { headers }),
|
|
|
|
|
};
|
2025-09-03 17:17:53 +09:00
|
|
|
|
2025-09-26 09:02:47 +09:00
|
|
|
return ($api as any)(path, apiOpts).catch(async (error: any) => {
|
|
|
|
|
const status = error.response?.status;
|
|
|
|
|
const message = error.response._data.message;
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
status === 401 &&
|
|
|
|
|
["JWT_TOKEN_EXPIRED", "INVALID_CLIENT_IP", "JWT_TOKEN_NULL"].includes(
|
|
|
|
|
message
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
const userStore = useUserStore();
|
|
|
|
|
userStore.user = null;
|
|
|
|
|
userStore.isLoggedIn = false;
|
|
|
|
|
await navigateTo(`/auth-error?type=${message}`);
|
|
|
|
|
throw error;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-24 16:25:30 +09:00
|
|
|
// 사용자에게 알림 표시
|
|
|
|
|
if (showAlert) {
|
2025-09-26 09:02:47 +09:00
|
|
|
let description =
|
2025-09-24 16:25:30 +09:00
|
|
|
status === 404
|
|
|
|
|
? "요청한 리소스를 찾을 수 없습니다."
|
|
|
|
|
: status === 500
|
|
|
|
|
? "서버 오류가 발생했습니다."
|
|
|
|
|
: "요청 처리 중 오류가 발생했습니다.";
|
2025-09-03 17:17:53 +09:00
|
|
|
|
2025-09-24 16:25:30 +09:00
|
|
|
// 서버에서 온 에러 메시지가 있으면 우선 사용
|
|
|
|
|
if (error.response?._data?.description) {
|
2025-09-26 09:02:47 +09:00
|
|
|
description = error.response._data.description;
|
2025-09-24 16:25:30 +09:00
|
|
|
}
|
|
|
|
|
|
2025-09-26 09:02:47 +09:00
|
|
|
alert(description);
|
2025-09-03 17:17:53 +09:00
|
|
|
}
|
|
|
|
|
|
2025-09-24 16:25:30 +09:00
|
|
|
// 에러 처리 방식에 따라 반환
|
|
|
|
|
if (handleError) {
|
|
|
|
|
return null as T; // 에러를 null로 반환
|
|
|
|
|
} else {
|
|
|
|
|
throw error; // 에러를 다시 던짐
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
2025-09-03 17:17:53 +09:00
|
|
|
|
2025-09-24 16:25:30 +09:00
|
|
|
// 전역 로딩 사용 여부 확인 (기본값: true)
|
|
|
|
|
const shouldUseLoading = options?.useGlobalLoading !== false;
|
|
|
|
|
|
|
|
|
|
if (shouldUseLoading) {
|
|
|
|
|
// 전역 로딩과 함께 API 호출
|
|
|
|
|
return await withLoading(
|
|
|
|
|
apiCall,
|
|
|
|
|
options?.loadingMessage || "데이터를 불러오는 중..."
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
// 전역 로딩 없이 API 호출
|
|
|
|
|
return await apiCall();
|
|
|
|
|
}
|
2025-08-28 16:00:25 +09:00
|
|
|
};
|