Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion src/entities/post/api/getPosts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { serverFetch } from "@/shared/api/fetcher.server";
import { apiFetch } from "@/shared/api/fetcher";
import { POST_PAGE_SIZE } from "@/entities/post/model/constants/api";
import type { Post } from "@/entities/post/model/types/post";

Expand All @@ -22,7 +23,7 @@ export async function getPosts({
if (keyword) query.append("keyword", keyword);
if (sort) query.append("sort", sort);

const { data } = await serverFetch<{ data: Post[] }>(
const { data } = await apiFetch<{ data: Post[] }>(
`/api/postings?${query.toString()}`,
{ method: "GET" },
);
Expand Down
1 change: 0 additions & 1 deletion src/features/auth/ui/LoginForm/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export const LoginForm = ({
}>("/auth/login", {
method: "POST",
body: JSON.stringify({ email, password }),
noAuth: true,
});

setAccessToken(res.accessToken);
Expand Down
1 change: 0 additions & 1 deletion src/features/auth/ui/SignUpForm/SignUpForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ export const SignUpForm = ({ onSuccess, onError }: SignUpFormProps) => {
const res = await apiFetch("/api/users/", {
method: "POST",
body: JSON.stringify(body),
noAuth: true,
});

console.log("회원가입 성공:", res);
Expand Down
22 changes: 22 additions & 0 deletions src/shared/api/fetcher.server.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,39 @@
import "server-only";

import { cookies } from "next/headers";

const BASE_URL = process.env.NEXT_PUBLIC_API_URL;

export async function serverFetch<T>(
endpoint: string,
options?: RequestInit,
): Promise<T> {
const cookieStore = await cookies();
const cookieHeader = [
cookieStore.get("accessToken")
? `accessToken=${cookieStore.get("accessToken")?.value}`
: "",
cookieStore.get("refreshToken")
? `refreshToken=${cookieStore.get("refreshToken")?.value}`
: "",
]
.filter(Boolean)
.join("; ");

const res = await fetch(`${BASE_URL}${endpoint}`, {
headers: {
"Content-Type": "application/json",
Cookie: cookieHeader,
},
cache: "no-store",
credentials: "include",
...options,
});

if (res.status === 401) {
//TODO: SSR prefetch 시 에러 핸들링 로직 추가
}

if (!res.ok) {
const text = await res.text();
throw new Error(text || `Server API error: ${res.status}`);
Expand Down
51 changes: 12 additions & 39 deletions src/shared/api/fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,35 @@
import { useAuthStore } from "@/features/auth/model/auth.store";
import { refreshAccessToken } from "./refresh";
import { useModalStore } from "@/shared/model/modal.store";

const BASE_URL = process.env.NEXT_PUBLIC_API_URL;

export async function apiFetch<T>(
endpoint: string,
options: RequestInit & { noAuth?: boolean },
options: RequestInit,
): Promise<T> {
const { headers, noAuth, ...restOptions } = options;
const { accessToken, setAccessToken, logout } = useAuthStore.getState();
const { headers, ...restOptions } = options;
const { openModal, closeModal } = useModalStore.getState();

const defaultHeaders: HeadersInit = {
"Content-Type": "application/json",
};

if (!noAuth && typeof window !== "undefined") {
defaultHeaders["Authorization"] = `Bearer ${accessToken}`;
}

let res = await fetch(`${BASE_URL}${endpoint}`, {
const res = await fetch(`${BASE_URL}${endpoint}`, {
headers: { ...defaultHeaders, ...headers },
cache: "no-store",
credentials: "include",
...restOptions,
});

//AccessToken 만료 처리
if (res.status === 401 && !noAuth) {
const newToken = await refreshAccessToken();

if (newToken) {
setAccessToken(newToken);
defaultHeaders["Authorization"] = `Bearer ${newToken}`;

//동일한 경로에 요청 재시도
res = await fetch(`${BASE_URL}${endpoint}`, {
headers: { ...defaultHeaders, ...headers },
cache: "no-store",
credentials: "include",
...restOptions,
});
} else {
logout();
openModal("normal", {
message: "세션이 만료되었습니다. 다시 로그인 해주세요.",
buttonText: "확인",
onClick: () => {
closeModal();
if (typeof window !== "undefined") {
location.replace("/login");
}
},
});

throw new Error("세션이 만료되었습니다. 다시 로그인 해주세요.");
}
if (res.status === 401) {
openModal("normal", {
message: "세션이 만료되었습니다. 다시 로그인 해주세요.",
buttonText: "확인",
onClick: () => {
closeModal();
location.replace("/login");
},
});
}

if (!res.ok) {
Expand Down