Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
37 changes: 0 additions & 37 deletions src/_apis/auth/auth-apis.tsx

This file was deleted.

20 changes: 20 additions & 0 deletions src/_apis/auth/login-apis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { fetchApi } from '@/src/utils/api';
import { LoginRequest, LoginResponse } from '@/src/types/auth';

export function login(data: LoginRequest): Promise<{ data: LoginResponse }> {
return fetchApi<{ data: LoginResponse; headers: Headers }>(
'/auths/login',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
},
5000,
true,
).then((response) => {
const token = response.headers.get('Authorization');
return { data: { token } };
});
}
Comment on lines +4 to +20
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

토큰 처리와 에러 핸들링의 개선이 필요합니다.

다음 사항들을 고려해주시기 바랍니다:

  1. Authorization 헤더가 없는 경우에 대한 처리가 필요합니다.
  2. 입력값 검증이 누락되어 있습니다.
  3. 토큰을 안전하게 처리하는 방법을 검토해야 합니다.

다음과 같이 개선하는 것을 제안드립니다:

 export function login(data: LoginRequest): Promise<{ data: LoginResponse }> {
+  if (!data.email || !data.password) {
+    throw new Error('이메일과 비밀번호는 필수입니다.');
+  }
   return fetchApi<{ data: LoginResponse; headers: Headers }>(
     '/auths/login',
     {
       method: 'POST',
       headers: {
         'Content-Type': 'application/json',
       },
       body: JSON.stringify(data),
     },
     5000,
     true,
   ).then((response) => {
     const token = response.headers.get('Authorization');
+    if (!token) {
+      throw new Error('인증 토큰을 받지 못했습니다.');
+    }
     return { data: { token } };
   });
 }

Committable suggestion skipped: line range outside the PR's diff.

Empty file added src/_apis/auth/logout-apis.ts
Empty file.
20 changes: 20 additions & 0 deletions src/_apis/auth/signup-apis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { fetchApi } from '@/src/utils/api';
import { SignupRequest, SignupResponse } from '@/src/types/auth';

export function signup(data: SignupRequest): Promise<{ data: SignupResponse }> {
return fetchApi<{ data: SignupResponse; headers: Headers }>(
'/auths/signup',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
},
5000,
true,
).then((response) => {
const token = response.headers.get('Authorization');
return { data: { token } };
});
}
Comment on lines +4 to +20
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

에러 처리 로직 추가 필요

현재 구현에서 다음과 같은 개선사항이 필요합니다:

  1. API 호출 실패 시 에러 처리가 없습니다
  2. Authorization 헤더가 없을 경우의 처리가 없습니다
  3. 타임아웃이 5초로 설정되어 있는데, 네트워크 상태가 좋지 않은 경우 문제가 될 수 있습니다

다음과 같이 수정하는 것을 제안드립니다:

 export function signup(data: SignupRequest): Promise<{ data: SignupResponse }> {
   return fetchApi<{ data: SignupResponse; headers: Headers }>(
     '/auths/signup',
     {
       method: 'POST',
       headers: {
         'Content-Type': 'application/json',
       },
       body: JSON.stringify(data),
     },
-    5000,
+    10000,
     true,
   ).then((response) => {
     const token = response.headers.get('Authorization');
+    if (!token) {
+      throw new Error('인증 토큰을 받지 못했습니다');
+    }
     return { data: { token } };
+  }).catch((error) => {
+    if (error.name === 'AbortError') {
+      throw new Error('요청 시간이 초과되었습니다');
+    }
+    throw error;
   });
 }
📝 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.

Suggested change
export function signup(data: SignupRequest): Promise<{ data: SignupResponse }> {
return fetchApi<{ data: SignupResponse; headers: Headers }>(
'/auths/signup',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
},
5000,
true,
).then((response) => {
const token = response.headers.get('Authorization');
return { data: { token } };
});
}
export function signup(data: SignupRequest): Promise<{ data: SignupResponse }> {
return fetchApi<{ data: SignupResponse; headers: Headers }>(
'/auths/signup',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
},
10000,
true,
).then((response) => {
const token = response.headers.get('Authorization');
if (!token) {
throw new Error('인증 토큰을 받지 못했습니다');
}
return { data: { token } };
}).catch((error) => {
if (error.name === 'AbortError') {
throw new Error('요청 시간이 초과되었습니다');
}
throw error;
});
}

11 changes: 11 additions & 0 deletions src/_apis/auth/user-apis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { fetchApi } from '@/src/utils/api';
import { User } from '@/src/types/auth';

export function getUser(): Promise<{ data: User }> {
return fetchApi<{ data: User }>('/auths/user', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
}
56 changes: 0 additions & 56 deletions src/_queries/auth/auth-queries.tsx

This file was deleted.

16 changes: 16 additions & 0 deletions src/_queries/auth/login-queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useMutation } from '@tanstack/react-query';
import { login } from '@/src/_apis/auth/login-apis';
import { ApiError } from '@/src/utils/api';
import { LoginRequest, LoginResponse } from '@/src/types/auth';
import { useHandleAuthSuccess } from './use-handle-auth-success';

export function usePostLoginQuery() {
const handleAuthSuccess = useHandleAuthSuccess();

return useMutation<{ data: LoginResponse }, ApiError, LoginRequest>({
mutationFn: login,
onSuccess: async (response) => {
await handleAuthSuccess(response.data.token);
},
});
}
Empty file.
16 changes: 16 additions & 0 deletions src/_queries/auth/signup-queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { useMutation } from '@tanstack/react-query';
import { signup } from '@/src/_apis/auth/signup-apis';
import { ApiError } from '@/src/utils/api';
import { SignupRequest, SignupResponse } from '@/src/types/auth';
import { useHandleAuthSuccess } from './use-handle-auth-success';

export function usePostSignupQuery() {
const handleAuthSuccess = useHandleAuthSuccess();

return useMutation<{ data: SignupResponse }, ApiError, SignupRequest>({
mutationFn: signup,
onSuccess: async (response) => {
await handleAuthSuccess(response.data.token);
},
});
}
24 changes: 24 additions & 0 deletions src/_queries/auth/use-handle-auth-success.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useQueryClient } from '@tanstack/react-query';
import { useAuthStore } from '@/src/store/use-auth-store';
import { User } from '@/src/types/auth';
import { getUserQuery } from './user-apis';

export function useHandleAuthSuccess() {
const queryClient = useQueryClient();
const { login, setUser } = useAuthStore();

return async function handleAuthSuccess(token: string | null) {
if (!token) {
throw new Error('토큰이 없습니다');
}

try {
const accessToken = token.replace(/^Bearer\s/, '');
login(accessToken);
const user: User = await queryClient.fetchQuery(getUserQuery());
setUser(user);
} catch (error) {
throw new Error('사용자 상태 업데이트 실패');
}
};
Comment on lines +10 to +23
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

토큰 처리 및 에러 핸들링 개선 필요

현재 구현에서 몇 가지 개선이 필요한 부분이 있습니다:

  1. 토큰 형식 검증이 미흡합니다
  2. 에러 메시지가 너무 일반적입니다
  3. 실패 시 정리(cleanup) 로직이 없습니다

다음과 같이 개선하는 것을 제안합니다:

   return async function handleAuthSuccess(token: string | null) {
     if (!token) {
-      throw new Error('토큰이 없습니다');
+      throw new Error('인증 토큰이 제공되지 않았습니다');
     }
+    
+    const tokenFormat = /^Bearer\s[\w-]+\.[\w-]+\.[\w-]+$/;
+    if (!tokenFormat.test(token)) {
+      throw new Error('유효하지 않은 토큰 형식입니다');
+    }

     try {
       const accessToken = token.replace(/^Bearer\s/, '');
       login(accessToken);
       const user: User = await queryClient.fetchQuery(getUserQuery());
       setUser(user);
     } catch (error) {
-      throw new Error('사용자 상태 업데이트 실패');
+      // 실패 시 정리
+      login(null);
+      setUser(null);
+      throw new Error(
+        error instanceof Error
+          ? `인증 처리 실패: ${error.message}`
+          : '알 수 없는 인증 오류가 발생했습니다'
+      );
     }
   };

토큰 관련 로직을 별도의 유틸리티 함수로 분리하는 것을 고려해보세요. 이렇게 하면 재사용성이 높아지고 테스트가 용이해집니다.

📝 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.

Suggested change
return async function handleAuthSuccess(token: string | null) {
if (!token) {
throw new Error('토큰이 없습니다');
}
try {
const accessToken = token.replace(/^Bearer\s/, '');
login(accessToken);
const user: User = await queryClient.fetchQuery(getUserQuery());
setUser(user);
} catch (error) {
throw new Error('사용자 상태 업데이트 실패');
}
};
return async function handleAuthSuccess(token: string | null) {
if (!token) {
throw new Error('인증 토큰이 제공되지 않았습니다');
}
const tokenFormat = /^Bearer\s[\w-]+\.[\w-]+\.[\w-]+$/;
if (!tokenFormat.test(token)) {
throw new Error('유효하지 않은 토큰 형식입니다');
}
try {
const accessToken = token.replace(/^Bearer\s/, '');
login(accessToken);
const user: User = await queryClient.fetchQuery(getUserQuery());
setUser(user);
} catch (error) {
// 실패 시 정리
login(null);
setUser(null);
throw new Error(
error instanceof Error
? `인증 처리 실패: ${error.message}`
: '알 수 없는 인증 오류가 발생했습니다'
);
}
};

}
11 changes: 11 additions & 0 deletions src/_queries/auth/user-apis.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { getUser } from '@/src/_apis/auth/user-apis';
import { transformKeysToCamel } from '@/src/utils/transform-keys';
import { User } from '@/src/types/auth';

export function getUserQuery() {
return {
queryKey: ['user'],
queryFn: getUser,
select: (data: User) => transformKeysToCamel(data),
};
}
2 changes: 1 addition & 1 deletion src/app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React from 'react';
import { useForm } from 'react-hook-form';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { usePostLoginQuery } from '@/src/_queries/auth/auth-queries';
import { usePostLoginQuery } from '@/src/_queries/auth/login-queries';
import LoginForm, { LoginFormValues } from './_component/login-form';

export default function LoginPage() {
Expand Down
2 changes: 1 addition & 1 deletion src/app/(auth)/signup/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React from 'react';
import { useForm } from 'react-hook-form';
import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { usePostSignupQuery } from '@/src/_queries/auth/auth-queries';
import { usePostSignupQuery } from '@/src/_queries/auth/signup-queries';
import SignupForm, { SignupFormValues } from './_component/signup-form';

export default function LoginPage() {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

컴포넌트 이름이 기능과 일치하지 않습니다

LoginPage라는 컴포넌트 이름이 회원가입 기능을 담당하는 페이지의 실제 목적과 일치하지 않습니다. 이는 코드의 가독성과 유지보수성을 저해할 수 있습니다.

다음과 같이 수정하는 것을 제안합니다:

-export default function LoginPage() {
+export default function SignupPage() {
📝 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.

Suggested change
export default function LoginPage() {
export default function SignupPage() {

Expand Down
6 changes: 4 additions & 2 deletions src/utils/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export async function fetchApi<T>(
url: string,
options: RequestInit = {},
timeout = 5000,
isAuth = false,
): Promise<T> {
const controller = new AbortController();
const { signal } = controller;
Expand Down Expand Up @@ -50,8 +51,9 @@ export async function fetchApi<T>(

throw new ApiError(response.status, errorMessage, errorDetail);
}

return { data: await response.json() } as T;
const data = await response.json();
if (isAuth) return { data, headers: response.headers } as T;
return { data } as T;
Comment on lines +54 to +56
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

타입 안전성 개선이 필요합니다.

현재 구현에서 발견된 잠재적인 문제점들:

  1. as T 타입 캐스팅은 런타임에서 타입 안전성을 보장하지 않습니다.
  2. 반환 타입이 isAuth 값에 따라 달라지는데, 이를 타입 시스템에서 정확하게 표현하지 못하고 있습니다.

다음과 같은 개선을 제안드립니다:

type AuthResponse<T> = {
  data: T;
  headers: Headers;
};

type NonAuthResponse<T> = {
  data: T;
};

export async function fetchApi<T>(
  url: string,
  options: RequestInit = {},
  timeout = 5000,
  isAuth = false,
): Promise<isAuth extends true ? AuthResponse<T> : NonAuthResponse<T>> {
  // ... existing implementation ...
}

이렇게 수정하면:

  1. 조건부 타입을 통해 isAuth 값에 따른 반환 타입이 명확해집니다.
  2. 타입 안전성이 향상됩니다.

} catch (error) {
if (error instanceof Error) {
if (error.name === 'AbortError') throw new ApiError(408, 'Request timeout');
Expand Down