diff --git a/src/app/api/[...path]/route.ts b/src/app/api/[...path]/route.ts index dc72c507..b484b9c2 100644 --- a/src/app/api/[...path]/route.ts +++ b/src/app/api/[...path]/route.ts @@ -8,7 +8,7 @@ type RouteParams = { async function proxyToBackend(request: NextRequest, params: RouteParams) { try { - const path = `/${(params.path ?? []).join('/')}`; + const path = `/api/${(params.path ?? []).join('/')}`; const targetUrl = new URL(path + request.nextUrl.search, process.env.BACKEND_BASE_URL); const proxyRequest = new Request(targetUrl, request); diff --git a/src/app/api/auth/_authFetch.ts b/src/app/api/auth/_authFetch.ts index 207bdaaa..f848e0e8 100644 --- a/src/app/api/auth/_authFetch.ts +++ b/src/app/api/auth/_authFetch.ts @@ -3,7 +3,7 @@ type AuthFetchInit = RequestInit & { }; export async function authFetch(path: string, init: AuthFetchInit = {}) { - const url = new URL(path, process.env.BACKEND_BASE_URL); + const url = new URL(`api/${path}`, process.env.BACKEND_BASE_URL); const headers = new Headers(init.headers ?? {}); diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts index cd2b5c48..d16650b6 100644 --- a/src/app/api/auth/logout/route.ts +++ b/src/app/api/auth/logout/route.ts @@ -14,12 +14,11 @@ export async function POST(request: NextRequest) { }, }); - if (!response.ok) { - const data = await response.json().catch(() => null); - return NextResponse.json(data ?? { message: 'Logout failed' }, { status: response.status }); - } + const res = response.ok + ? NextResponse.json({ ok: true }) + : NextResponse.json({ message: 'Logout failed' }, { status: response.status }); - const res = NextResponse.json({ ok: true }); + // 백엔드 응답과 무관하게 쿠키는 항상 삭제 (토큰 만료 상태에서도 로그아웃 가능하도록) res.cookies.delete({ name: ACCESS_TOKEN_KEY, path: '/' }); res.cookies.delete({ name: REFRESH_TOKEN_KEY, path: '/' }); return res; diff --git a/src/middleware.ts b/src/middleware.ts index b2dfb0dd..1e5e914d 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -121,7 +121,7 @@ export async function middleware(request: NextRequest) { const accessTokenValue = getAccessTokenFromCookies(request); const response = await fetch( - new URL(`${API_URL.LESSON_DETAIL}/${lessonId}`, process.env.NEXT_PUBLIC_DEV_BASE_URL), + new URL(`api/${API_URL.LESSON_DETAIL}/${lessonId}`, process.env.NEXT_PUBLIC_DEV_BASE_URL), { method: 'GET', headers: { diff --git a/src/shared/apis/kyInstance.ts b/src/shared/apis/kyInstance.ts index 92f27a5e..2bb4c99f 100644 --- a/src/shared/apis/kyInstance.ts +++ b/src/shared/apis/kyInstance.ts @@ -5,7 +5,10 @@ import { afterResponse } from '@/shared/apis/afterResponse'; const getPrefixUrl = (): string => { if (isServer) { const base = process.env.BACKEND_BASE_URL; - if (base) return base.endsWith('/') ? base.slice(0, -1) : base; + if (base) { + const normalized = base.endsWith('/') ? base.slice(0, -1) : base; + return `${normalized}/api`; + } } return '/api'; }; diff --git a/src/shared/constants/apiURL.ts b/src/shared/constants/apiURL.ts index b15bf721..d36d5b50 100644 --- a/src/shared/constants/apiURL.ts +++ b/src/shared/constants/apiURL.ts @@ -2,66 +2,66 @@ import type { LessonStatus } from '@/app/my/(instructor)/manage-classes/types/le import type { ReservationStatus } from '@/app/my/(student)/classes/types/reservationStatus'; export const API_URL = { - AUTH_LOGIN: 'api/v1/auth/login', - AUTH_REISSUE: 'api/v1/auth/reissue', - AUTH_LOGOUT: 'api/v1/auth/logout', - AUTH_PHONE_REQUEST: 'api/v1/auth/phone/request', - AUTH_PHONE_VERIFY: 'api/v1/auth/phone/verify', + AUTH_LOGIN: 'v1/auth/login', + AUTH_REISSUE: 'v1/auth/reissue', + AUTH_LOGOUT: 'v1/auth/logout', + AUTH_PHONE_REQUEST: 'v1/auth/phone/request', + AUTH_PHONE_VERIFY: 'v1/auth/phone/verify', - MEMBERS_VALIDATE_WITHDRAW: 'api/v1/auth/validate-withdraw', - MEMBERS_WITHDRAW: 'api/v1/auth/withdraw', - MEMBERS_ONBOARD: 'api/v1/members/onboard', - MEMBERS_ME: 'api/v1/members/me', + MEMBERS_VALIDATE_WITHDRAW: 'v1/auth/validate-withdraw', + MEMBERS_WITHDRAW: 'v1/auth/withdraw', + MEMBERS_ONBOARD: 'v1/members/onboard', + MEMBERS_ME: 'v1/members/me', MEMBERS_RESERVATIONS: (status: ReservationStatus) => { - return status === 'ALL' ? 'api/v1/members/me/reservations' : `api/v1/members/me/reservations?status=${status}`; + return status === 'ALL' ? 'v1/members/me/reservations' : `v1/members/me/reservations?status=${status}`; }, - MEMBERS_RESERVATIONS_CLASS_CARD: 'api/v1/members/me/reservations/{reservationId}/class-card', - MEMBERS_RESERVATIONS_CANCEL: 'api/v1/members/me/reservations/{reservationId}/cancel', - MEMBERS_RESERVATIONS_STATISTICS: 'api/v1/members/me/reservations/statistics', - MEMBERS_RESERVATION_DETAIL: 'api/v1/members/me/reservations', + MEMBERS_RESERVATIONS_CLASS_CARD: 'v1/members/me/reservations/{reservationId}/class-card', + MEMBERS_RESERVATIONS_CANCEL: 'v1/members/me/reservations/{reservationId}/cancel', + MEMBERS_RESERVATIONS_STATISTICS: 'v1/members/me/reservations/statistics', + MEMBERS_RESERVATION_DETAIL: 'v1/members/me/reservations', - TEACHERS: 'api/v1/teachers', - TEACHER_DETAIL: 'api/v1/teachers', - TEACHERS_ME: 'api/v1/teachers/me', - TEACHERS_LESSONS_DETAIL: 'api/v1/teachers/me/lessons', + TEACHERS: 'v1/teachers', + TEACHER_DETAIL: 'v1/teachers', + TEACHERS_ME: 'v1/teachers/me', + TEACHERS_LESSONS_DETAIL: 'v1/teachers/me/lessons', TEACHERS_LESSONS: (status: LessonStatus) => { - return status === 'ALL' ? 'api/v1/teachers/me/lessons' : `api/v1/teachers/me/lessons?status=${status}`; + return status === 'ALL' ? 'v1/teachers/me/lessons' : `v1/teachers/me/lessons?status=${status}`; }, - TEACHER_DETAIL_INTRODUCTION: 'api/v1/teachers/me/detail', - TEACHER_ME_THUMBNAILS: 'api/v1/teachers/me/lessons/thumbnails', - TEACHER_ME_ACCOUNT: 'api/v1/teachers/me/account', - TEACHERS_POPULAR: 'api/v1/teachers/popular', - TEACHERS_SEARCH: 'api/v1/teachers?keyword=:keyword', - TEACHERS_LESSON_STATUS: 'api/v1/teachers/me/lessons/status', + TEACHER_DETAIL_INTRODUCTION: 'v1/teachers/me/detail', + TEACHER_ME_THUMBNAILS: 'v1/teachers/me/lessons/thumbnails', + TEACHER_ME_ACCOUNT: 'v1/teachers/me/account', + TEACHERS_POPULAR: 'v1/teachers/popular', + TEACHERS_SEARCH: 'v1/teachers?keyword=:keyword', + TEACHERS_LESSON_STATUS: 'v1/teachers/me/lessons/status', TEACHERS_LESSON_CHANGE_APPROVE: (lessonId: number, reservationId: number) => - `api/v1/teachers/me/lessons/${lessonId}/${reservationId}/change-approve`, + `v1/teachers/me/lessons/${lessonId}/${reservationId}/change-approve`, TEACHERS_LESSON_CHANGE_CANCEL: (lessonId: number, reservationId: number) => - `api/v1/teachers/me/lessons/${lessonId}/${reservationId}/change-cancel`, - TEACHER_NICKNAME_VALIDATION: 'api/v1/teachers/nickname-validation', + `v1/teachers/me/lessons/${lessonId}/${reservationId}/change-cancel`, + TEACHER_NICKNAME_VALIDATION: 'v1/teachers/nickname-validation', - LESSONS: 'api/v1/lessons', - LESSON_DETAIL: 'api/v1/lessons', - LESSON_UPDATE: 'api/v1/lessons', - LESSONS_LATEST: 'api/v1/lessons/latest', - LESSONS_POPULAR_GENRES: 'api/v1/lessons/popular-genres', - LESSONS_UPCOMING: 'api/v1/lessons/upcoming', - LESSONS_FAVORITES: 'api/v1/lessons/favorites/{lessonId}', - LESSON_RESERVE_PROGRESS: 'api/v1/lessons', - LESSON_RESERVATION: 'api/v2/lessons', - LESSON_RESERVATION_STATUS: 'api/v1/members/me/reservations/status', + LESSONS: 'v1/lessons', + LESSON_DETAIL: 'v1/lessons', + LESSON_UPDATE: 'v1/lessons', + LESSONS_LATEST: 'v1/lessons/latest', + LESSONS_POPULAR_GENRES: 'v1/lessons/popular-genres', + LESSONS_UPCOMING: 'v1/lessons/upcoming', + LESSONS_FAVORITES: 'v1/lessons/favorites/{lessonId}', + LESSON_RESERVE_PROGRESS: 'v1/lessons', + LESSON_RESERVATION: 'v2/lessons', + LESSON_RESERVATION_STATUS: 'v1/members/me/reservations/status', - MY_PAGE_FAVORITES: 'api/v1/mypage/favorites', - MY_PAGE_LESSONS: 'api/v1/mypage/lessons', - MY_PAGE_LESSON_DETAIL: 'api/v1/mypage/lessons/{lessonId}', + MY_PAGE_FAVORITES: 'v1/mypage/favorites', + MY_PAGE_LESSONS: 'v1/mypage/lessons', + MY_PAGE_LESSON_DETAIL: 'v1/mypage/lessons/{lessonId}', - ADVERTISEMENTS: 'api/v1/advertisements', - BANKS: 'api/v1/bank', + ADVERTISEMENTS: 'v1/advertisements', + BANKS: 'v1/bank', SEARCH_LESSONS: - 'api/v1/lessons?genre=:genre&level=:level&startDate=:startDate&endDate=:endDate&sortOption=:sortOption&keyword=:keyword', + 'v1/lessons?genre=:genre&level=:level&startDate=:startDate&endDate=:endDate&sortOption=:sortOption&keyword=:keyword', - LOCATIONS: 'api/v1/locations', + LOCATIONS: 'v1/locations', - AUTH_ROLE: 'api/v1/auth/role', - IMAGES: 'api/v1/images', + AUTH_ROLE: 'v1/auth/role', + IMAGES: 'v1/images', };