From 503976b3ee7d2698c60f672d94aaf56177c14ad1 Mon Sep 17 00:00:00 2001 From: jhynsoo Date: Sun, 15 Sep 2024 00:39:34 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20get=20method=20API=20=EC=BF=BC=EB=A6=AC?= =?UTF-8?q?=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/api/authApi.ts | 13 +-- frontend/src/api/folderApi.ts | 53 ++++++----- frontend/src/api/imageApi.ts | 8 +- frontend/src/api/lablingApi.ts | 4 +- frontend/src/api/projectApi.ts | 90 +++++++++++-------- frontend/src/api/upload.ts | 1 + frontend/src/api/workspaceApi.ts | 6 +- frontend/src/hooks/useAuthHooks.ts | 52 ++++------- frontend/src/queries/useFolderQuery.ts | 9 ++ .../src/queries/useFolderReviewListQuery.ts | 9 ++ frontend/src/queries/useProfileQuery.ts | 9 ++ frontend/src/queries/useProjectQuery.ts | 9 ++ frontend/src/queries/useWorkspaceListQuery.ts | 9 ++ frontend/src/queries/useWorkspaceQuery.ts | 9 ++ frontend/src/types/index.ts | 48 +++++----- 15 files changed, 194 insertions(+), 135 deletions(-) create mode 100644 frontend/src/queries/useFolderQuery.ts create mode 100644 frontend/src/queries/useFolderReviewListQuery.ts create mode 100644 frontend/src/queries/useProfileQuery.ts create mode 100644 frontend/src/queries/useProjectQuery.ts create mode 100644 frontend/src/queries/useWorkspaceListQuery.ts create mode 100644 frontend/src/queries/useWorkspaceQuery.ts diff --git a/frontend/src/api/authApi.ts b/frontend/src/api/authApi.ts index 32e3aa2..d13a41e 100644 --- a/frontend/src/api/authApi.ts +++ b/frontend/src/api/authApi.ts @@ -1,11 +1,14 @@ import api from '@/api/axiosConfig'; +import { MemberResponse, RefreshTokenResponse } from '@/types'; export async function reissueToken() { - return api.post('/auth/reissue', null, { withCredentials: true }); + return api.post('/auth/reissue', null, { withCredentials: true }).then(({ data }) => data); } -export async function fetchProfile() { - return api.get('/auth/profile', { - withCredentials: true, - }); +export async function getProfile() { + return api + .get('/auth/profile', { + withCredentials: true, + }) + .then(({ data }) => data); } diff --git a/frontend/src/api/folderApi.ts b/frontend/src/api/folderApi.ts index b9ad8b6..ef11908 100644 --- a/frontend/src/api/folderApi.ts +++ b/frontend/src/api/folderApi.ts @@ -1,37 +1,42 @@ import api from '@/api/axiosConfig'; -import { FolderRequestDTO } from '@/types'; +import { FolderRequest, FolderResponse } from '@/types'; export async function fetchFolder(projectId: number, folderId: number, memberId: number) { - return api.get(`/projects/${projectId}/folders/${folderId}`, { - params: { memberId }, - }); + return api + .get(`/projects/${projectId}/folders/${folderId}`, { + params: { memberId }, + }) + .then(({ data }) => data); } -export async function updateFolder( - projectId: number, - folderId: number, - memberId: number, - folderData: FolderRequestDTO -) { - return api.put(`/projects/${projectId}/folders/${folderId}`, folderData, { - params: { memberId }, - }); +export async function updateFolder(projectId: number, folderId: number, memberId: number, folderData: FolderRequest) { + return api + .put(`/projects/${projectId}/folders/${folderId}`, folderData, { + params: { memberId }, + }) + .then(({ data }) => data); } export async function deleteFolder(projectId: number, folderId: number, memberId: number) { - return api.delete(`/projects/${projectId}/folders/${folderId}`, { - params: { memberId }, - }); + return api + .delete(`/projects/${projectId}/folders/${folderId}`, { + params: { memberId }, + }) + .then(({ data }) => data); } -export async function createFolder(projectId: number, memberId: number, folderData: FolderRequestDTO) { - return api.post(`/projects/${projectId}/folders`, folderData, { - params: { memberId }, - }); +export async function createFolder(projectId: number, memberId: number, folderData: FolderRequest) { + return api + .post(`/projects/${projectId}/folders`, folderData, { + params: { memberId }, + }) + .then(({ data }) => data); } -export async function fetchFolderReviewList(projectId: number, folderId: number, memberId: number) { - return api.get(`/projects/${projectId}/folders/${folderId}/review`, { - params: { memberId }, - }); +export async function getFolderReviewList(projectId: number, folderId: number, memberId: number) { + return api + .get(`/projects/${projectId}/folders/${folderId}/review`, { + params: { memberId }, + }) + .then(({ data }) => data); } diff --git a/frontend/src/api/imageApi.ts b/frontend/src/api/imageApi.ts index f90e792..949d5ea 100644 --- a/frontend/src/api/imageApi.ts +++ b/frontend/src/api/imageApi.ts @@ -1,7 +1,7 @@ import api from '@/api/axiosConfig'; -import { ImageMoveRequestDTO, ImageStatusChangeRequestDTO } from '@/types'; +import { ImageMoveRequest, ImageStatusChangeRequest } from '@/types'; -export async function fetchImage(projectId: number, folderId: number, imageId: number, memberId: number) { +export async function getImage(projectId: number, folderId: number, imageId: number, memberId: number) { return api.get(`/projects/${projectId}/folders/${folderId}/images/${imageId}`, { params: { memberId }, }); @@ -12,7 +12,7 @@ export async function moveImage( folderId: number, imageId: number, memberId: number, - moveRequest: ImageMoveRequestDTO + moveRequest: ImageMoveRequest ) { return api.put(`/projects/${projectId}/folders/${folderId}/images/${imageId}`, moveRequest, { params: { memberId }, @@ -30,7 +30,7 @@ export async function changeImageStatus( folderId: number, imageId: number, memberId: number, - statusChangeRequest: ImageStatusChangeRequestDTO + statusChangeRequest: ImageStatusChangeRequest ) { return api.put(`/projects/${projectId}/folders/${folderId}/images/${imageId}/status`, statusChangeRequest, { params: { memberId }, diff --git a/frontend/src/api/lablingApi.ts b/frontend/src/api/lablingApi.ts index a147cef..22a12b3 100644 --- a/frontend/src/api/lablingApi.ts +++ b/frontend/src/api/lablingApi.ts @@ -1,7 +1,7 @@ import api from '@/api/axiosConfig'; -import { LabelingRequestDTO } from '@/types'; +import { LabelingRequest } from '@/types'; -export async function saveImageLabels(projectId: number, imageId: number, memberId: number, data: LabelingRequestDTO) { +export async function saveImageLabels(projectId: number, imageId: number, memberId: number, data: LabelingRequest) { return api.post(`/projects/${projectId}/label/image/${imageId}`, data, { params: { memberId }, }); diff --git a/frontend/src/api/projectApi.ts b/frontend/src/api/projectApi.ts index 3f3f431..555c770 100644 --- a/frontend/src/api/projectApi.ts +++ b/frontend/src/api/projectApi.ts @@ -1,70 +1,84 @@ import api from '@/api/axiosConfig'; -export async function getProjectApi(projectId: number, memberId: number) { - return api.get(`/projects/${projectId}`, { - params: { memberId }, - }); +export async function getProject(projectId: number, memberId: number) { + return api + .get(`/projects/${projectId}`, { + params: { memberId }, + }) + .then(({ data }) => data); } -export async function updateProjectApi( +export async function updateProject( projectId: number, memberId: number, data: { title: string; projectType: 'classification' | 'detection' | 'segmentation' } ) { - return api.put(`/projects/${projectId}`, data, { - params: { memberId }, - }); + return api + .put(`/projects/${projectId}`, data, { + params: { memberId }, + }) + .then(({ data }) => data); } -export async function deleteProjectApi(projectId: number, memberId: number) { - return api.delete(`/projects/${projectId}`, { - params: { memberId }, - }); +export async function deleteProject(projectId: number, memberId: number) { + return api + .delete(`/projects/${projectId}`, { + params: { memberId }, + }) + .then(({ data }) => data); } -export async function addProjectMemberApi( +export async function addProjectMember( projectId: number, memberId: number, newMemberId: number, privilegeType: string ) { - return api.post( - `/projects/${projectId}/members`, - { memberId: newMemberId, privilegeType }, - { + return api + .post( + `/projects/${projectId}/members`, + { memberId: newMemberId, privilegeType }, + { + params: { memberId }, + } + ) + .then(({ data }) => data); +} + +export async function removeProjectMember(projectId: number, memberId: number, targetMemberId: number) { + return api + .delete(`/projects/${projectId}/members`, { params: { memberId }, - } - ); + data: { memberId: targetMemberId }, + }) + .then(({ data }) => data); } -export async function removeProjectMemberApi(projectId: number, memberId: number, targetMemberId: number) { - return api.delete(`/projects/${projectId}/members`, { - params: { memberId }, - data: { memberId: targetMemberId }, - }); -} - -export async function getAllProjectsApi( +export async function getAllProjects( workspaceId: number, memberId: number, lastProjectId?: number, limit: number = 10 ) { - return api.get(`/workspaces/${workspaceId}/projects`, { - params: { - memberId, - lastProjectId, - limit, - }, - }); + return api + .get(`/workspaces/${workspaceId}/projects`, { + params: { + memberId, + lastProjectId, + limit, + }, + }) + .then(({ data }) => data); } -export async function createProjectApi( +export async function createProject( workspaceId: number, memberId: number, data: { title: string; projectType: 'classification' | 'detection' | 'segmentation' } ) { - return api.post(`/workspaces/${workspaceId}/projects`, data, { - params: { memberId }, - }); + return api + .post(`/workspaces/${workspaceId}/projects`, data, { + params: { memberId }, + }) + .then(({ data }) => data); } diff --git a/frontend/src/api/upload.ts b/frontend/src/api/upload.ts index 889c2b4..25733ad 100644 --- a/frontend/src/api/upload.ts +++ b/frontend/src/api/upload.ts @@ -14,6 +14,7 @@ export async function uploadFilesToProject( formData.append('files', file); }); + // TODO: api.post()를 return하고 사이드 이펙트는 외부에서 처리하도록 수정 return api .post('/projects/{project_id}', formData, { onUploadProgress: (progressEvent: AxiosProgressEvent) => { diff --git a/frontend/src/api/workspaceApi.ts b/frontend/src/api/workspaceApi.ts index dd56e3f..679f713 100644 --- a/frontend/src/api/workspaceApi.ts +++ b/frontend/src/api/workspaceApi.ts @@ -1,5 +1,5 @@ import api from '@/api/axiosConfig'; -import { WorkspaceRequestDTO } from '@/types'; +import { WorkspaceRequest } from '@/types'; export async function fetchWorkspaceList(memberId: number, lastWorkspaceId?: number, limit?: number) { return api.get('/workspaces', { @@ -13,7 +13,7 @@ export async function fetchWorkspace(workspaceId: number, memberId: number) { }); } -export async function updateWorkspace(workspaceId: number, memberId: number, data: WorkspaceRequestDTO) { +export async function updateWorkspace(workspaceId: number, memberId: number, data: WorkspaceRequest) { return api.put(`/workspaces/${workspaceId}`, data, { params: { memberId }, }); @@ -25,7 +25,7 @@ export async function deleteWorkspace(workspaceId: number, memberId: number) { }); } -export async function createWorkspace(memberId: number, data: WorkspaceRequestDTO) { +export async function createWorkspace(memberId: number, data: WorkspaceRequest) { return api.post('/workspaces', data, { params: { memberId }, }); diff --git a/frontend/src/hooks/useAuthHooks.ts b/frontend/src/hooks/useAuthHooks.ts index 24b3342..cf5d40f 100644 --- a/frontend/src/hooks/useAuthHooks.ts +++ b/frontend/src/hooks/useAuthHooks.ts @@ -1,47 +1,31 @@ -import { useQuery, UseQueryResult, useMutation, useQueryClient, UseMutationResult } from '@tanstack/react-query'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import { AxiosError } from 'axios'; import useAuthStore from '@/stores/useAuthStore'; -import { reissueTokenApi, fetchProfileApi } from '@/api/authApi'; -import { SuccessResponse, RefreshTokenResponseDTO, MemberResponseDTO, CustomError } from '@/types'; +import { reissueToken, getProfile } from '@/api/authApi'; +import { CustomError } from '@/types'; import { useState, useEffect } from 'react'; +import useProfileQuery from '@/queries/useProfileQuery'; -export const useReissueToken = (): UseMutationResult< - SuccessResponse, - AxiosError -> => { +export const useReissueToken = () => { const queryClient = useQueryClient(); const { setLoggedIn } = useAuthStore(); return useMutation({ - mutationFn: reissueTokenApi, + mutationFn: reissueToken, onSuccess: (data) => { - if (data.isSuccess && data.data) { - setLoggedIn(true, data.data.accessToken); - queryClient.invalidateQueries({ queryKey: ['profile'] }); - } else { - console.error('토큰 재발급 응답 오류:', data.message); - } - }, - onError: (error) => { - console.error('토큰 재발급 실패:', error?.response?.data?.message || '알 수 없는 오류'); + setLoggedIn(true, data.accessToken); + queryClient.invalidateQueries({ queryKey: ['profile'] }); }, }); }; -export const useProfile = (): UseQueryResult, AxiosError> => { - const { accessToken, setProfile } = useAuthStore(); - - const query = useQuery, AxiosError>({ - queryKey: ['profile'], - queryFn: fetchProfileApi, - enabled: !!accessToken, - select: (data) => data, - }); +export const useProfile = () => { + const { setProfile } = useAuthStore(); + const query = useProfileQuery(); + // TODO: query.data가 변경될 때마다 setProfile을 호출하여 profile 업데이트, useEffect 제거 useEffect(() => { - if (query.data?.isSuccess) { - setProfile(query.data.data); - } + setProfile(query.data); }, [query.data, setProfile]); return query; @@ -53,14 +37,10 @@ export const useFetchProfile = () => { useEffect(() => { if (!profile && !isFetched) { - fetchProfileApi() + getProfile() .then((data) => { - if (data.isSuccess && data.data) { - setProfile(data.data); - setIsFetched(true); - } else { - console.error('프로필 응답 오류:', data.message); - } + setProfile(data); + setIsFetched(true); }) .catch((error: AxiosError) => { alert('프로필을 가져오는 중 오류가 발생했습니다. 다시 시도해주세요.'); diff --git a/frontend/src/queries/useFolderQuery.ts b/frontend/src/queries/useFolderQuery.ts new file mode 100644 index 0000000..59dd2f9 --- /dev/null +++ b/frontend/src/queries/useFolderQuery.ts @@ -0,0 +1,9 @@ +import { fetchFolder } from '@/api/folderApi'; +import { useSuspenseQuery } from '@tanstack/react-query'; + +export default function useFolderQuery(projectId: number, folderId: number, memberId: number) { + return useSuspenseQuery({ + queryKey: ['folder', projectId, folderId, memberId], + queryFn: () => fetchFolder(projectId, folderId, memberId), + }); +} diff --git a/frontend/src/queries/useFolderReviewListQuery.ts b/frontend/src/queries/useFolderReviewListQuery.ts new file mode 100644 index 0000000..2dfcc32 --- /dev/null +++ b/frontend/src/queries/useFolderReviewListQuery.ts @@ -0,0 +1,9 @@ +import { getFolderReviewList } from '@/api/folderApi'; +import { useSuspenseQuery } from '@tanstack/react-query'; + +export function useFolderReviewListQuery(projectId: number, folderId: number, memberId: number) { + return useSuspenseQuery({ + queryKey: ['folderReviewList', projectId, folderId, memberId], + queryFn: () => getFolderReviewList(projectId, folderId, memberId), + }); +} diff --git a/frontend/src/queries/useProfileQuery.ts b/frontend/src/queries/useProfileQuery.ts new file mode 100644 index 0000000..6acc8a5 --- /dev/null +++ b/frontend/src/queries/useProfileQuery.ts @@ -0,0 +1,9 @@ +import { getProfile } from '@/api/authApi'; +import { useSuspenseQuery } from '@tanstack/react-query'; + +export default function useProfileQuery() { + return useSuspenseQuery({ + queryKey: ['profile'], + queryFn: getProfile, + }); +} diff --git a/frontend/src/queries/useProjectQuery.ts b/frontend/src/queries/useProjectQuery.ts new file mode 100644 index 0000000..1a317ec --- /dev/null +++ b/frontend/src/queries/useProjectQuery.ts @@ -0,0 +1,9 @@ +import { getProject } from '@/api/projectApi'; +import { useSuspenseQuery } from '@tanstack/react-query'; + +export default function useProjectQuery(projectId: number, memberId: number) { + return useSuspenseQuery({ + queryKey: ['project', projectId, memberId], + queryFn: () => getProject(projectId, memberId), + }); +} diff --git a/frontend/src/queries/useWorkspaceListQuery.ts b/frontend/src/queries/useWorkspaceListQuery.ts new file mode 100644 index 0000000..1005b9a --- /dev/null +++ b/frontend/src/queries/useWorkspaceListQuery.ts @@ -0,0 +1,9 @@ +import { fetchWorkspaceList } from '@/api/workspaceApi'; +import { useSuspenseQuery } from '@tanstack/react-query'; + +export default function useWorkspaceListQuery(memberId: number, lastWorkspaceId?: number, limit?: number) { + return useSuspenseQuery({ + queryKey: ['workspaceList'], + queryFn: () => fetchWorkspaceList(memberId, lastWorkspaceId, limit), + }); +} diff --git a/frontend/src/queries/useWorkspaceQuery.ts b/frontend/src/queries/useWorkspaceQuery.ts new file mode 100644 index 0000000..bf655a7 --- /dev/null +++ b/frontend/src/queries/useWorkspaceQuery.ts @@ -0,0 +1,9 @@ +import { fetchWorkspace } from '@/api/workspaceApi'; +import { useSuspenseQuery } from '@tanstack/react-query'; + +export default function useWorkspaceQuery(workspaceId: number, memberId: number) { + return useSuspenseQuery({ + queryKey: ['workspace', workspaceId, memberId], + queryFn: () => fetchWorkspace(workspaceId, memberId), + }); +} diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index a272b39..e517683 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -34,50 +34,50 @@ export type Label = { coordinates: Array<[number, number]>; }; -export interface FolderRequestDTO { +export interface FolderRequest { title: string; parentId: number; } -export interface ChildFolderDTO { +export interface ChildFolder { id: number; title: string; } -export interface FolderResponseDTO { +export interface FolderResponse { id: number; title: string; - images: ImageResponseDTO[]; - children: ChildFolderDTO[]; + images: ImageResponse[]; + children: ChildFolder[]; } -export interface ImageResponseDTO { +export interface ImageResponse { id: number; imageTitle: string; imageUrl: string; status: 'PENDING' | 'IN_PROGRESS' | 'NEED_REVIEW' | 'COMPLETED'; } -export interface ImageMoveRequestDTO { +export interface ImageMoveRequest { moveFolderId: number; } -export interface ImageStatusChangeRequestDTO { +export interface ImageStatusChangeRequest { labelStatus: 'PENDING' | 'IN_PROGRESS' | 'NEED_REVIEW' | 'COMPLETED'; } -export interface MemberResponseDTO { +export interface MemberResponse { id: number; nickname: string; profileImage: string; } -export interface WorkspaceRequestDTO { +export interface WorkspaceRequest { title: string; content: string; } -export interface WorkspaceResponseDTO { +export interface WorkspaceResponse { id: number; memberId: string; title: string; @@ -86,8 +86,8 @@ export interface WorkspaceResponseDTO { updatedAt: string; } -export interface WorkspaceListResponseDTO { - workspaceResponses: WorkspaceResponseDTO[]; +export interface WorkspaceListResponse { + workspaceResponses: WorkspaceResponse[]; } export interface SuccessResponse { @@ -98,11 +98,13 @@ export interface SuccessResponse { errors: CustomError[]; isSuccess: boolean; } -export interface ProjectRequestDTO { + +export interface ProjectRequest { title: string; projectType: 'classification' | 'detection' | 'segmentation'; } -export interface ProjectResponseDTO { + +export interface ProjectResponse { id: number; title: string; workspaceId: number; @@ -111,8 +113,8 @@ export interface ProjectResponseDTO { updatedAt: string; } -export interface ProjectListResponseDTO { - workspaceResponses: ProjectResponseDTO[]; +export interface ProjectListResponse { + workspaceResponses: ProjectResponse[]; } export interface CustomError { @@ -140,17 +142,17 @@ export interface BaseResponse { isSuccess: boolean; } -export interface RefreshTokenResponseDTO { +export interface RefreshTokenResponse { accessToken: string; } -export interface CommentRequestDTO { +export interface CommentRequest { content: string; positionX: number; positionY: number; } -export interface CommentResponseDTO { +export interface CommentResponse { id: number; memberId: number; memberNickname: string; @@ -161,18 +163,18 @@ export interface CommentResponseDTO { createTime: string; // 작성 일자 (ISO 8601 형식) } -export interface ProjectMemberRequestDTO { +export interface ProjectMemberRequest { memberId: number; privilegeType: 'ADMIN' | 'MANAGER' | 'EDITOR' | 'VIEWER'; } -export interface LabelingRequestDTO { +export interface LabelingRequest { memberId: number; projectId: number; imageId: number; } -export interface AutoLabelingResponseDTO { +export interface AutoLabelingResponse { imageId: number; imageUrl: string; data: string;