Skip to content

Commit

Permalink
Merge pull request #149 from Myongji-Graduate/migration-ax-fetch
Browse files Browse the repository at this point in the history
Migration ax fetch
  • Loading branch information
seonghunYang authored Sep 28, 2024
2 parents 30e3d2e + c3c1db2 commit da8a2cd
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 94 deletions.
2 changes: 1 addition & 1 deletion .husky/prepare-commit-msg
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ commit_msg_file=$1
commit_msg=$(cat "$1")
second_line=$(echo "$commit_msg" | sed -n '2p')

commit_msg_title_regex='(feat|fix|refactor|chore|test|docs|style|rename|setting|remove|build): .{1,100}?$'
commit_msg_title_regex='(feat|fix|refactor|chore|test|docs|style|rename|setting|remove|build): .{1,100}?'


# 제목
Expand Down
81 changes: 23 additions & 58 deletions app/business/services/user/user.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
UserDeleteRequestBody,
ResetPasswordRequestBody,
} from './user.type';
import { httpErrorHandler } from '@/app/utils/http/http-error-handler';
import { fetchAxErrorHandler, httpErrorHandler } from '@/app/utils/http/http-error-handler';
import { BadRequestError, UnauthorizedError } from '@/app/utils/http/http-error';
import {
SignUpFormSchema,
Expand All @@ -23,6 +23,8 @@ import { cookies } from 'next/headers';
import { isValidation } from '@/app/utils/zod/validation.util';
import { redirect } from 'next/navigation';
import { fetchUser } from './user.query';
import fetchAX from 'fetch-ax';
import { instance } from '@/app/utils/api/instance';

function deleteCookies() {
cookies().delete('accessToken');
Expand All @@ -41,21 +43,10 @@ export async function deleteUser(prevState: FormState, formData: FormData): Prom
password: formData.get('password') as string,
};

const response = await fetch(`${API_PATH.user}/me`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${cookies().get('accessToken')?.value}`,
},
body: JSON.stringify(body),
await instance.delete(`${API_PATH.user}/me`, {
data: body,
});

if (response.status !== 200) {
const result = await response.json();
httpErrorHandler(response, result);
}
} catch (error) {
console.log(error);
if (error instanceof BadRequestError) {
// 잘못된 요청 처리 로직
return {
Expand Down Expand Up @@ -91,27 +82,16 @@ export async function authenticate(prevState: FormState, formData: FormData): Pr
const body: SignInRequestBody = {
...validatedFields.data,
};

try {
const response = await fetch(`${API_PATH.auth}/sign-in`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});

const result = await response.json();

httpErrorHandler(response, result);
const { data } = await instance.post(`${API_PATH.auth}/sign-in`, body);

if (isValidation(result, SignInResponseSchema)) {
cookies().set('accessToken', result.accessToken, {
if (isValidation(data, SignInResponseSchema)) {
cookies().set('accessToken', data.accessToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
path: '/',
});
cookies().set('refreshToken', result.refreshToken, {
cookies().set('refreshToken', data.refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
path: '/',
Expand Down Expand Up @@ -154,20 +134,18 @@ export async function authenticate(prevState: FormState, formData: FormData): Pr
export async function refreshToken(): Promise<ValidateTokenResponse | false> {
const refreshToken = cookies().get('refreshToken')?.value;
try {
const response = await fetch(`${API_PATH.auth}/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
const { data } = await fetchAX.post(
`${API_PATH.auth}/token`,
{ refreshToken },
{
responseRejectedInterceptor: (error) => {
fetchAxErrorHandler(error);
},
},
body: JSON.stringify({ refreshToken }),
});

const result = await response.json();
);

httpErrorHandler(response, result);

if (isValidation(result, ValidateTokenResponseSchema)) {
return result;
if (isValidation(data, ValidateTokenResponseSchema)) {
return data;
} else {
throw 'Invalid token response schema.';
}
Expand Down Expand Up @@ -207,18 +185,9 @@ export async function createUser(prevState: FormState, formData: FormData): Prom
};

try {
const response = await fetch(`${API_PATH.user}/sign-up`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
await instance.post(`${API_PATH.user}/sign-up`, body, {
responseType: 'text',
});

if (response.status !== 200) {
const result = await response.json();
httpErrorHandler(response, result);
}
} catch (error) {
if (error instanceof BadRequestError) {
// 잘못된 요청 처리 로직
Expand Down Expand Up @@ -263,12 +232,8 @@ export async function resetPassword(prevState: FormState, formData: FormData): P
passwordCheck,
};
try {
const response = await fetch(`${API_PATH.user}/password`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
await instance.patch(`${API_PATH.user}/password`, body, {
responseType: 'text',
});

return {
Expand Down
40 changes: 10 additions & 30 deletions app/business/services/user/user.query.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
'use server';

import { BadRequestError, UnauthorizedError } from '@/app/utils/http/http-error';
import { httpErrorHandler } from '@/app/utils/http/http-error-handler';
import { isValidation } from '@/app/utils/zod/validation.util';
import { cookies } from 'next/headers';
import { API_PATH } from '../../api-path';
import { InitUserInfoResponse, UserInfoResponse } from './user.type';
import {
Expand All @@ -13,6 +11,7 @@ import {
FindIdResponseSchema,
} from './user.validation';
import { FormState } from '@/app/ui/view/molecule/form/form-root';
import { instance } from '@/app/utils/api/instance';

export async function auth(): Promise<InitUserInfoResponse | UserInfoResponse | undefined> {
try {
Expand All @@ -28,17 +27,10 @@ export async function auth(): Promise<InitUserInfoResponse | UserInfoResponse |

export async function fetchUser(): Promise<InitUserInfoResponse | UserInfoResponse> {
try {
const response = await fetch(`${API_PATH.user}/me`, {
headers: {
Authorization: `Bearer ${cookies().get('accessToken')?.value}`,
},
});
const result = await response.json();
const { data } = await instance.get(`${API_PATH.user}/me`);

httpErrorHandler(response, result);

if (isValidation(result, UserInfoResponseSchema) || isValidation(result, InitUserInfoResponseSchema)) {
return result;
if (isValidation(data, UserInfoResponseSchema) || isValidation(data, InitUserInfoResponseSchema)) {
return data;
} else {
throw 'Invalid user info response schema.';
}
Expand All @@ -63,36 +55,30 @@ export async function findUserToStudentNumber(prevState: FormState, formData: Fo

try {
const { studentNumber } = validatedFields.data;
const response = await fetch(`${API_PATH.user}/${studentNumber}/auth-id`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const response = await instance.get(`${API_PATH.user}/${studentNumber}/auth-id`);

const result = await response.json();
if (response.status === 200)
return {
isSuccess: true,
isFailure: false,
validationError: {},
message: '',
value: result,
value: response.data,
};
else
return {
isSuccess: false,
isFailure: true,
validationError: {},
message: result.message,
message: response.data,
};
} catch (error) {
if (error instanceof BadRequestError) {
return {
isSuccess: false,
isFailure: true,
validationError: {},
message: error.message,
message: '해당 사용자를 찾을 수 없습니다.',
};
} else {
throw error;
Expand All @@ -117,15 +103,9 @@ export async function validateUser(prevState: FormState, formData: FormData): Pr
try {
const { studentNumber, authId } = validatedFields.data;

const response = await fetch(`${API_PATH.user}/${studentNumber}/validate?auth-id=${authId}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
const result = await response.json();
const { data } = await instance.get(`${API_PATH.user}/${studentNumber}/validate?auth-id=${authId}`);

if (result.passedUserValidation)
if (data.passedUserValidation)
return {
isSuccess: true,
isFailure: false,
Expand Down
2 changes: 1 addition & 1 deletion app/ui/user/sign-up-form/sign-up-form.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const FailureSenarioWithDuplicatedStudentNumber: Story = {
await step('회원가입에 실패한다.', async () => {
await waitFor(() => {
expect(args.onSuccess).not.toHaveBeenCalled();
expect(canvas.getByText('이미 가입된 학번입니다.')).toBeInTheDocument();
expect(canvas.getByText('Bad Request')).toBeInTheDocument();
});
});
},
Expand Down
22 changes: 22 additions & 0 deletions app/utils/api/instance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import fetchAx, { FetchAxError } from 'fetch-ax';
import { cookies } from 'next/headers';
import { fetchAxErrorHandler } from '../http/http-error-handler';

export const instance = fetchAx.create({
headers: {
'Content-Type': 'application/json',
},
responseRejectedInterceptor: (error) => {
fetchAxErrorHandler(error);
},
requestInterceptor: (config) => {
const accessToken = cookies().get('accessToken')?.value;
if (accessToken) {
config.headers = {
...config.headers,
Authorization: `Bearer ${accessToken}`,
};
}
return config;
},
});
4 changes: 2 additions & 2 deletions app/utils/api/setup-url.util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const API_URLS = {
BASE_URL: process.env.NEXT_PUBLIC_API_PATH ?? "",
PARSE_API_URL: process.env.NEXT_PUBLIC_PARSE_API_PATH ?? "",
BASE_URL: process.env.NEXT_PUBLIC_API_PATH ?? 'http://localhost:9090',
PARSE_API_URL: process.env.NEXT_PUBLIC_PARSE_API_PATH ?? 'http://localhost:9090/parsePDFtoText',
};

const MOCK_API_URLS = {
Expand Down
46 changes: 46 additions & 0 deletions app/utils/http/http-error-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
UnauthorizedError,
InternetServerError,
} from './http-error';
import { FetchAxError } from 'fetch-ax';

export interface ErrorResponseData {
status: number;
Expand Down Expand Up @@ -54,3 +55,48 @@ export const httpErrorHandler = (response: Response, result: ErrorResponseData)
}
}
};

interface ErrorData {
errorCode: string;
}

export const fetchAxErrorHandler = (error: FetchAxError<ErrorData>) => {
const status = error.statusCode;
const message = error.response.data.errorCode; // 여기서 errorcode를 message로 변환 필요
const response = error.response;

switch (status) {
case HttpStatusCode.Unauthorized:
throw new UnauthorizedError({
message,
response,
});

case HttpStatusCode.BadRequest:
throw new BadRequestError({
message,
response,
});

case HttpStatusCode.Forbidden:
throw new ForbiddenError({
message,
response,
});

case HttpStatusCode.NotFound:
throw new NotFoundError({
message,
response,
});

case HttpStatusCode.InternalServerError:
throw new InternetServerError({
message,
response,
});

default:
throw new HttpError(status, message, response);
}
};
4 changes: 2 additions & 2 deletions app/utils/http/http-error.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
type ErrorConstrutor = {
message?: string;
statusCode?: number;
response?: Response;
response?: Partial<Response>;
};

// Refactor: fetch가 NetworkError랑 TimeoutError 또 자동으로 에러로 던져서 처리가 더 애매하다.
Expand All @@ -23,7 +23,7 @@ export class HttpError extends Error {
constructor(
readonly statusCode?: number,
message?: string,
readonly response?: Response,
readonly response?: Partial<Response>,
) {
super(message);
}
Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"express": "^4.18.2",
"fetch-ax": "^1.0.10",
"jotai": "^2.7.0",
"lucide-react": "^0.336.0",
"next": "14.1.0",
Expand Down

0 comments on commit da8a2cd

Please sign in to comment.