-
Notifications
You must be signed in to change notification settings - Fork 3
로그인/회원가입 API #122
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
로그인/회원가입 API #122
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| 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 } }; | ||
| }); | ||
| } | ||
| 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
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. 에러 처리 로직 추가 필요 현재 구현에서 다음과 같은 개선사항이 필요합니다:
다음과 같이 수정하는 것을 제안드립니다: 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
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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', | ||
| }, | ||
| }); | ||
| } |
This file was deleted.
| 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); | ||
| }, | ||
| }); | ||
| } |
| 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); | ||
| }, | ||
| }); | ||
| } |
| 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
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. 토큰 처리 및 에러 핸들링 개선 필요 현재 구현에서 몇 가지 개선이 필요한 부분이 있습니다:
다음과 같이 개선하는 것을 제안합니다: 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
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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), | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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() { | ||||||
|
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 컴포넌트 이름이 기능과 일치하지 않습니다
다음과 같이 수정하는 것을 제안합니다: -export default function LoginPage() {
+export default function SignupPage() {📝 Committable suggestion
Suggested change
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -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
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 타입 안전성 개선이 필요합니다. 현재 구현에서 발견된 잠재적인 문제점들:
다음과 같은 개선을 제안드립니다: 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 ...
}이렇게 수정하면:
|
||
| } catch (error) { | ||
| if (error instanceof Error) { | ||
| if (error.name === 'AbortError') throw new ApiError(408, 'Request timeout'); | ||
|
|
||
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.
토큰 처리와 에러 핸들링의 개선이 필요합니다.
다음 사항들을 고려해주시기 바랍니다:
다음과 같이 개선하는 것을 제안드립니다:
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 } }; }); }