Skip to content

Commit e707499

Browse files
authored
Merge pull request #12 from SkyLightQP/feat/error-handling
refactor: add `try-catch` statement in fetching api
2 parents 4c60782 + fa11506 commit e707499

15 files changed

+272
-138
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"clsx": "^2.1.1",
1919
"framer-motion": "^12.0.5",
2020
"next": "15.1.0",
21+
"nextjs-toploader": "^3.7.15",
2122
"notion-client": "^7.1.6",
2223
"notion-types": "^7.1.6",
2324
"react": "^19.0.0",

pnpm-lock.yaml

+23
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/api/index.ts

+32
Original file line numberDiff line numberDiff line change
@@ -1 +1,33 @@
11
export const API_HOST = process.env.NEXT_PUBLIC_API_HOST;
2+
3+
interface ApiOk<Response> {
4+
readonly result: Response;
5+
}
6+
7+
interface ApiFail {
8+
readonly error: {
9+
readonly code: string;
10+
};
11+
}
12+
13+
export type ApiResponse<Response> = Partial<ApiOk<Response> & ApiFail>;
14+
15+
export const createApiFetchError = (): ApiFail => {
16+
return {
17+
error: {
18+
code: 'FETCH_ERROR'
19+
}
20+
};
21+
};
22+
23+
export const interceptResponse = async <T>(
24+
response: Response,
25+
callback?: (data: T) => Promise<void>
26+
): Promise<ApiResponse<T>> => {
27+
const data = await response.json();
28+
if (!response.ok) {
29+
return { error: { code: data.error.code } };
30+
}
31+
if (callback) await callback(data);
32+
return data;
33+
};

src/api/mutation/auth-mutation.ts

+41-26
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,55 @@
11
'use server';
22

3-
import { API_HOST } from '@/api';
3+
import { API_HOST, ApiResponse, createApiFetchError, interceptResponse } from '@/api';
44
import { getCommonFetchConfig } from '@/api/config';
55
import { cookies } from 'next/headers';
66

7-
export const logoutMutation = async (): Promise<void> => {
8-
await fetch(`${API_HOST}/auth/logout`, {
9-
method: 'DELETE',
10-
...(await getCommonFetchConfig())
11-
});
12-
const cookieStore = await cookies();
13-
cookieStore.delete('_maruToken');
7+
export const logoutMutation = async (): Promise<ApiResponse<boolean>> => {
8+
try {
9+
const response = await fetch(`${API_HOST}/auth/logout`, {
10+
method: 'DELETE',
11+
...(await getCommonFetchConfig())
12+
});
13+
return interceptResponse(response, async () => {
14+
const cookieStore = await cookies();
15+
cookieStore.delete('_maruToken');
16+
});
17+
} catch (error) {
18+
return createApiFetchError();
19+
}
1420
};
1521

1622
export interface RegisterMutationParams {
1723
readonly nickname: string;
1824
readonly registerToken: string;
1925
}
20-
export const registerMutation = async ({ nickname, registerToken }: RegisterMutationParams): Promise<void> => {
21-
const response = await fetch(`${API_HOST}/auth/register`, {
22-
method: 'POST',
23-
body: JSON.stringify({ nickname }),
24-
...(await getCommonFetchConfig({
25-
'X-Maru-RegisterToken': registerToken
26-
}))
27-
});
2826

29-
const cookie = response.headers.get('set-cookie');
30-
const token = cookie?.split(';');
31-
token?.forEach((t) => {
32-
const [key, value] = t.split('=');
33-
if (key === '_maruToken') {
34-
const cookieStore = cookies();
35-
cookieStore.then((store) => {
36-
store.set('_maruToken', value);
27+
export const registerMutation = async ({
28+
nickname,
29+
registerToken
30+
}: RegisterMutationParams): Promise<ApiResponse<boolean>> => {
31+
try {
32+
const response = await fetch(`${API_HOST}/auth/register`, {
33+
method: 'POST',
34+
body: JSON.stringify({ nickname }),
35+
...(await getCommonFetchConfig({
36+
'X-Maru-RegisterToken': registerToken
37+
}))
38+
});
39+
return interceptResponse(response, async () => {
40+
const cookie = response.headers.get('set-cookie');
41+
const token = cookie?.split(';');
42+
token?.forEach((t) => {
43+
const [key, value] = t.split('=');
44+
if (key === '_maruToken') {
45+
const cookieStore = cookies();
46+
cookieStore.then((store) => {
47+
store.set('_maruToken', value);
48+
});
49+
}
3750
});
38-
}
39-
});
51+
});
52+
} catch (error) {
53+
return createApiFetchError();
54+
}
4055
};

src/api/mutation/diary-mutation.ts

+38-18
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,58 @@
11
'use server';
22

3-
import { API_HOST } from '@/api';
3+
import { API_HOST, ApiResponse, createApiFetchError, interceptResponse } from '@/api';
44
import { getCommonFetchConfig } from '@/api/config';
55

66
export interface CreateDiaryMutationParams {
77
readonly title: string;
88
readonly content: string;
99
}
10-
export const createDiaryMutation = async ({ title, content }: CreateDiaryMutationParams): Promise<void> => {
11-
await fetch(`${API_HOST}/diary`, {
12-
method: 'POST',
13-
body: JSON.stringify({ title, content }),
14-
...(await getCommonFetchConfig())
15-
});
10+
11+
export const createDiaryMutation = async ({
12+
title,
13+
content
14+
}: CreateDiaryMutationParams): Promise<ApiResponse<void>> => {
15+
try {
16+
const response = await fetch(`${API_HOST}/diary`, {
17+
method: 'POST',
18+
body: JSON.stringify({ title, content }),
19+
...(await getCommonFetchConfig())
20+
});
21+
return interceptResponse(response);
22+
} catch (error) {
23+
return createApiFetchError();
24+
}
1625
};
1726

1827
export interface UpdateDiaryMutationParams {
1928
readonly title: string;
2029
readonly content: string;
2130
}
31+
2232
export const updateDiaryMutation = async (
2333
diaryId: number,
2434
{ title, content }: UpdateDiaryMutationParams
25-
): Promise<void> => {
26-
await fetch(`${API_HOST}/diary/${diaryId}`, {
27-
method: 'PUT',
28-
body: JSON.stringify({ title, content }),
29-
...(await getCommonFetchConfig())
30-
});
35+
): Promise<ApiResponse<void>> => {
36+
try {
37+
const response = await fetch(`${API_HOST}/diary/${diaryId}`, {
38+
method: 'PUT',
39+
body: JSON.stringify({ title, content }),
40+
...(await getCommonFetchConfig())
41+
});
42+
return interceptResponse(response);
43+
} catch (error) {
44+
return createApiFetchError();
45+
}
3146
};
3247

33-
export const deleteDiaryMutation = async (diaryId: number): Promise<void> => {
34-
await fetch(`${API_HOST}/diary/${diaryId}`, {
35-
method: 'DELETE',
36-
...(await getCommonFetchConfig())
37-
});
48+
export const deleteDiaryMutation = async (diaryId: number): Promise<ApiResponse<void>> => {
49+
try {
50+
const response = await fetch(`${API_HOST}/diary/${diaryId}`, {
51+
method: 'DELETE',
52+
...(await getCommonFetchConfig())
53+
});
54+
return interceptResponse(response);
55+
} catch (error) {
56+
return createApiFetchError();
57+
}
3858
};

src/api/mutation/file-mutation.ts

+23-15
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,44 @@
11
'use server';
22

3-
import { API_HOST } from '@/api';
3+
import { API_HOST, ApiResponse, createApiFetchError, interceptResponse } from '@/api';
44
import { getCommonFetchConfig } from '@/api/config';
5-
import { ApiResponse } from '@/api/type/common.type';
6-
import { CreateDiaryMutationParams } from '@/api/mutation/diary-mutation';
75

86
export interface GeneratePutPresignedUrlMutationReturn {
97
readonly url: string;
108
readonly fileName: string;
119
}
10+
1211
export const generatePutPresignedUrlMutation = async (
1312
originalFileName: string
1413
): Promise<ApiResponse<GeneratePutPresignedUrlMutationReturn>> => {
15-
const data = await fetch(`${API_HOST}/file/upload`, {
16-
method: 'POST',
17-
body: JSON.stringify({ originalFileName }),
18-
...(await getCommonFetchConfig())
19-
});
20-
return data.json();
14+
try {
15+
const response = await fetch(`${API_HOST}/file/upload`, {
16+
method: 'POST',
17+
body: JSON.stringify({ originalFileName }),
18+
...(await getCommonFetchConfig())
19+
});
20+
return interceptResponse(response);
21+
} catch (error) {
22+
return createApiFetchError();
23+
}
2124
};
2225

2326
export interface GenerateGetPresignedUrlMutationReturn {
2427
readonly url: string;
2528
readonly fileName: string;
2629
}
30+
2731
export const generateGetPresignedUrlMutation = async (
2832
fileName: string
2933
): Promise<ApiResponse<GenerateGetPresignedUrlMutationReturn>> => {
30-
const data = await fetch(`${API_HOST}/file/download`, {
31-
method: 'POST',
32-
body: JSON.stringify({ fileName }),
33-
...(await getCommonFetchConfig())
34-
});
35-
return data.json();
34+
try {
35+
const response = await fetch(`${API_HOST}/file/download`, {
36+
method: 'POST',
37+
body: JSON.stringify({ fileName }),
38+
...(await getCommonFetchConfig())
39+
});
40+
return interceptResponse(response);
41+
} catch (error) {
42+
return createApiFetchError();
43+
}
3644
};

src/api/mutation/user-mutation.ts

+26-14
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,37 @@
11
'use server';
22

3-
import { API_HOST } from '@/api';
3+
import { API_HOST, ApiResponse, createApiFetchError, interceptResponse } from '@/api';
44
import { getCommonFetchConfig } from '@/api/config';
55
import { cookies } from 'next/headers';
66

77
export interface UpdateUserMutationParams {
88
readonly nickname: string;
99
}
10-
export const updateUserMutation = async ({ nickname }: UpdateUserMutationParams): Promise<void> => {
11-
await fetch(`${API_HOST}/user`, {
12-
method: 'PUT',
13-
body: JSON.stringify({ nickname }),
14-
...(await getCommonFetchConfig())
15-
});
10+
11+
export const updateUserMutation = async ({ nickname }: UpdateUserMutationParams): Promise<ApiResponse<void>> => {
12+
try {
13+
const response = await fetch(`${API_HOST}/user`, {
14+
method: 'PUT',
15+
body: JSON.stringify({ nickname }),
16+
...(await getCommonFetchConfig())
17+
});
18+
return interceptResponse(response);
19+
} catch (error) {
20+
return createApiFetchError();
21+
}
1622
};
1723

18-
export const deleteUserMutation = async (): Promise<void> => {
19-
await fetch(`${API_HOST}/user`, {
20-
method: 'DELETE',
21-
...(await getCommonFetchConfig())
22-
});
23-
const cookieStore = await cookies();
24-
cookieStore.delete('_maruToken');
24+
export const deleteUserMutation = async (): Promise<ApiResponse<void>> => {
25+
try {
26+
const response = await fetch(`${API_HOST}/user`, {
27+
method: 'DELETE',
28+
...(await getCommonFetchConfig())
29+
});
30+
return interceptResponse(response, async () => {
31+
const cookieStore = await cookies();
32+
cookieStore.delete('_maruToken');
33+
});
34+
} catch (error) {
35+
return createApiFetchError();
36+
}
2537
};

0 commit comments

Comments
 (0)