diff --git a/frontend/src/api/authApi.ts b/frontend/src/api/authApi.ts new file mode 100644 index 0000000..993f63a --- /dev/null +++ b/frontend/src/api/authApi.ts @@ -0,0 +1,34 @@ +import api from '@/api/axiosConfig'; +import { AxiosError } from 'axios'; + +export const reissueTokenApi = async () => { + try { + const response = await api.post('/api/auth/reissue', null, { + withCredentials: true, + }); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + console.error('토큰 재발급 실패:', error.response?.data?.message || '알 수 없는 오류'); + } else { + console.error('알 수 없는 오류가 발생했습니다.'); + } + throw error; + } +}; + +export const fetchProfileApi = async () => { + try { + const response = await api.get('/api/auth/profile', { + withCredentials: true, + }); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + console.error('사용자 정보 가져오기 실패:', error.response?.data?.message || '알 수 없는 오류'); + } else { + console.error('알 수 없는 오류가 발생했습니다.'); + } + throw error; + } +}; diff --git a/frontend/src/api/axiosConfig.ts b/frontend/src/api/axiosConfig.ts new file mode 100644 index 0000000..ab7892d --- /dev/null +++ b/frontend/src/api/axiosConfig.ts @@ -0,0 +1,75 @@ +import axios from 'axios'; +import useAuthStore from '@/stores/useAuthStore'; + +const baseURL = 'https://j11s002.p.ssafy.io'; + +const api = axios.create({ + baseURL, + withCredentials: true, +}); + +api.interceptors.request.use((config) => { + const token = localStorage.getItem('accessToken'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +api.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config; + + if (error.response?.status === 401 && originalRequest._retry) { + console.error('토큰 재발급 중 401 오류가 발생했습니다. 로그아웃 처리합니다.'); + alert('세션이 만료되었습니다. 다시 로그인해 주세요.'); + useAuthStore.getState().clearAuth(); + window.location.href = '/'; + return Promise.reject(error); + } + + if (error.response?.status === 401 && !originalRequest._retry) { + originalRequest._retry = true; + + try { + const response = await api.post('/api/auth/reissue', null, { + withCredentials: true, + }); + + if (!response.data?.data?.accessToken) { + alert('잘못된 토큰 재발급 응답입니다. 다시 로그인해 주세요.'); + useAuthStore.getState().clearAuth(); + window.location.href = '/'; + return Promise.reject(new Error('Invalid token reissue response')); + } + + const newAccessToken = response.data.data.accessToken; + + useAuthStore.getState().setLoggedIn(true, newAccessToken); + localStorage.setItem('accessToken', newAccessToken); + originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; + + return api(originalRequest); + } catch (reissueError) { + console.error('토큰 재발급 실패:', reissueError); + alert('토큰 재발급에 실패했습니다. 다시 로그인해 주세요.'); + useAuthStore.getState().clearAuth(); + window.location.href = '/'; + return Promise.reject(reissueError); + } + } + + if (error.response?.status === 400) { + alert('잘못된 요청입니다. 다시 시도해 주세요.'); + } else if (error.response?.status === 403) { + alert('권한이 없습니다. 다시 로그인해 주세요.'); + useAuthStore.getState().clearAuth(); + window.location.href = '/'; + } + + return Promise.reject(error); + } +); + +export default api; diff --git a/frontend/src/api/projectApi.ts b/frontend/src/api/projectApi.ts new file mode 100644 index 0000000..0de9e50 --- /dev/null +++ b/frontend/src/api/projectApi.ts @@ -0,0 +1,153 @@ +import api from '@/api/axiosConfig'; +import { AxiosError } from 'axios'; + +export const getProjectApi = async (projectId: number, memberId: number) => { + try { + const response = await api.get(`/api/projects/${projectId}`, { + params: { + memberId, + }, + }); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + console.error('프로젝트 조회 실패:', error.response?.data?.message || '알 수 없는 오류'); + } else { + console.error('알 수 없는 오류가 발생했습니다.'); + } + throw error; + } +}; + +export const updateProjectApi = async ( + projectId: number, + memberId: number, + data: { title: string; projectType: string } +) => { + try { + const response = await api.put(`/api/projects/${projectId}`, data, { + params: { + memberId, + }, + }); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + console.error('프로젝트 수정 실패:', error.response?.data?.message || '알 수 없는 오류'); + } else { + console.error('알 수 없는 오류가 발생했습니다.'); + } + throw error; + } +}; + +export const deleteProjectApi = async (projectId: number, memberId: number) => { + try { + const response = await api.delete(`/api/projects/${projectId}`, { + params: { + memberId, + }, + }); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + console.error('프로젝트 삭제 실패:', error.response?.data?.message || '알 수 없는 오류'); + } else { + console.error('알 수 없는 오류가 발생했습니다.'); + } + throw error; + } +}; + +export const addProjectMemberApi = async ( + projectId: number, + memberId: number, + newMemberId: number, + privilegeType: string +) => { + try { + const response = await api.post( + `/api/projects/${projectId}/members`, + { memberId: newMemberId, privilegeType }, + { + params: { + memberId, + }, + } + ); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + console.error('프로젝트 멤버 추가 실패:', error.response?.data?.message || '알 수 없는 오류'); + } else { + console.error('알 수 없는 오류가 발생했습니다.'); + } + throw error; + } +}; + +export const removeProjectMemberApi = async (projectId: number, memberId: number, targetMemberId: number) => { + try { + const response = await api.delete(`/api/projects/${projectId}/members`, { + params: { + memberId, + }, + data: { memberId: targetMemberId }, + }); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + console.error('프로젝트 멤버 제거 실패:', error.response?.data?.message || '알 수 없는 오류'); + } else { + console.error('알 수 없는 오류가 발생했습니다.'); + } + throw error; + } +}; + +export const getAllProjectsApi = async ( + workspaceId: number, + memberId: number, + lastProjectId?: number, + limit: number = 10 +) => { + try { + const response = await api.get(`/api/workspaces/${workspaceId}/projects`, { + params: { + memberId, + lastProjectId, + limit, + }, + }); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + console.error('프로젝트 목록 조회 실패:', error.response?.data?.message || '알 수 없는 오류'); + } else { + console.error('알 수 없는 오류가 발생했습니다.'); + } + throw error; + } +}; + +export const createProjectApi = async ( + workspaceId: number, + memberId: number, + data: { title: string; projectType: string } +) => { + try { + const response = await api.post(`/api/workspaces/${workspaceId}/projects`, data, { + params: { + memberId, + }, + }); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + console.error('프로젝트 생성 실패:', error.response?.data?.message || '알 수 없는 오류'); + } else { + console.error('알 수 없는 오류가 발생했습니다.'); + } + throw error; + } +}; diff --git a/frontend/src/api/workspaceApi.ts b/frontend/src/api/workspaceApi.ts new file mode 100644 index 0000000..5348974 --- /dev/null +++ b/frontend/src/api/workspaceApi.ts @@ -0,0 +1,145 @@ +import api from '@/api/axiosConfig'; +import { AxiosError } from 'axios'; + +interface Workspace { + id: number; + memberId: string; + title: string; + content: string; + createdAt: string; + updatedAt: string; +} +interface GetAllWorkspacesResponse { + status: number; + code: number; + message: string; + data: { + workspaceResponses: Workspace[]; + }; + errors: Array<{ + field: string; + code: string; + message: string; + objectName: string; + }>; + isSuccess: boolean; +} +export const getWorkspaceApi = async (workspaceId: number, memberId: number) => { + try { + const response = await api.get(`/api/workspaces/${workspaceId}`, { + params: { memberId }, + }); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + console.error('API 요청 실패:', error.response?.data?.message || '알 수 없는 오류'); + } else { + console.error('알 수 없는 오류가 발생했습니다.'); + } + throw error; + } +}; + +export const updateWorkspaceApi = async ( + workspaceId: number, + memberId: number, + data: { title: string; content: string } +) => { + try { + const response = await api.put(`/api/workspaces/${workspaceId}`, data, { + params: { memberId }, + }); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + console.error('API 요청 실패:', error.response?.data?.message || '알 수 없는 오류'); + } else { + console.error('알 수 없는 오류가 발생했습니다.'); + } + throw error; + } +}; + +export const deleteWorkspaceApi = async (workspaceId: number, memberId: number) => { + try { + const response = await api.delete(`/api/workspaces/${workspaceId}`, { + params: { memberId }, + }); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + console.error('API 요청 실패:', error.response?.data?.message || '알 수 없는 오류'); + } else { + console.error('알 수 없는 오류가 발생했습니다.'); + } + throw error; + } +}; + +export const getAllWorkspacesApi = async ( + memberId: number, + lastWorkspaceId?: number, + limit?: number +): Promise => { + try { + const response = await api.get('/api/workspaces', { + params: { memberId, lastWorkspaceId, limit }, + }); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + console.error('API 요청 실패:', error.response?.data?.message || '알 수 없는 오류'); + } else { + console.error('알 수 없는 오류가 발생했습니다.'); + } + throw error; + } +}; + +export const createWorkspaceApi = async (memberId: number, data: { title: string; content: string }) => { + try { + const response = await api.post('/api/workspaces', data, { + params: { memberId }, + }); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + console.error('API 요청 실패:', error.response?.data?.message || '알 수 없는 오류'); + } else { + console.error('알 수 없는 오류가 발생했습니다.'); + } + throw error; + } +}; + +export const addWorkspaceMemberApi = async (workspaceId: number, memberId: number, newMemberId: number) => { + try { + const response = await api.post(`/api/workspaces/${workspaceId}/members/${newMemberId}`, null, { + params: { memberId }, + }); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + console.error('API 요청 실패:', error.response?.data?.message || '알 수 없는 오류'); + } else { + console.error('알 수 없는 오류가 발생했습니다.'); + } + throw error; + } +}; + +export const removeWorkspaceMemberApi = async (workspaceId: number, memberId: number, targetMemberId: number) => { + try { + const response = await api.delete(`/api/workspaces/${workspaceId}/members/${targetMemberId}`, { + params: { memberId }, + }); + return response.data; + } catch (error) { + if (error instanceof AxiosError) { + console.error('API 요청 실패:', error.response?.data?.message || '알 수 없는 오류'); + } else { + console.error('알 수 없는 오류가 발생했습니다.'); + } + throw error; + } +};