Skip to content

Commit

Permalink
Feat: #194 유저 프로필 이미지 조회 기능 구현 (#235)
Browse files Browse the repository at this point in the history
* Feat: #211 유저 프로필 이미지 업로드 기능 구현

* Feat: #211 DB 명세서에 따라 프로필 사진 저장 방식 변경

* Feat: #211 유저 프로필 이미지 저장 형식을 UUID로 변경

* Chore: #211 코드리뷰 반영 수정

* Chore: #211 코드리뷰 반영 수정

* Chore: #211 토큰에서 유저 정보를 추출할 수 없을 때 오류 추가

* Feat: #211 이미지 업로드 시 서버 측에서 UUID를 가져와 zustand 스토어에 저장하도록 수정

* Feat: #194 프로필 이미지 조회 기능 구현

* Chore: #194 파일 경로 수정

* Chore: #194 불필요한 쿼리 함수 삭제

* Chore: #194 msw에서 더미 jwt 코드 삭제 및 접근권한 에러 코드 변경
  • Loading branch information
Yoonyesol authored Oct 21, 2024
1 parent 7eefbba commit cdba4c0
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 21 deletions.
20 changes: 19 additions & 1 deletion src/components/user/auth-form/ProfileImageContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { FaRegTrashCan } from 'react-icons/fa6';
import { convertBytesToString } from '@utils/converter';
import { USER_SETTINGS } from '@constants/settings';
import { JPG, PNG, SVG, WEBP } from '@constants/mimeFileType';
import useAxios from '@hooks/useAxios';
import useToast from '@hooks/useToast';
import { useUploadProfileImage } from '@hooks/query/useUserQuery';
import useStore from '@stores/useStore';
import { getProfileImage } from '@services/userService';

type ProfileImageContainerProps = {
imageUrl: string | null;
Expand All @@ -15,8 +17,24 @@ type ProfileImageContainerProps = {

export default function ProfileImageContainer({ imageUrl, setImageUrl }: ProfileImageContainerProps) {
const { toastWarn } = useToast();
const { mutate: uploadImageMutate } = useUploadProfileImage();
const { editUserInfo, userInfo } = useStore();
const { mutate: uploadImageMutate } = useUploadProfileImage();
const { fetchData } = useAxios(getProfileImage);
const { toastError } = useToast();

useEffect(() => {
const handleGetProfileImage = async (uploadName: string) => {
const response = await fetchData(uploadName);
if (response == null)
return toastError('프로필 이미지 조회 도중 문제가 발생했습니다. 잠시 후 다시 시도해주세요.');

const blob = new Blob([response.data], { type: response.headers['content-type'] });
const profileImageUrl = URL.createObjectURL(blob);
setImageUrl(profileImageUrl);
};

if (userInfo.profileImageName) handleGetProfileImage(userInfo.profileImageName);
}, [userInfo.profileImageName]);

useEffect(() => {
return () => {
Expand Down
55 changes: 36 additions & 19 deletions src/mocks/services/userServiceHandler.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import { http, HttpResponse } from 'msw';
import {
JWT_TOKEN_DUMMY,
PROFILE_IMAGE_DUMMY,
ROLE_DUMMY,
TEAM_DUMMY,
TEAM_USER_DUMMY,
USER_DUMMY,
} from '@mocks/mockData';
import { PROFILE_IMAGE_DUMMY, ROLE_DUMMY, TEAM_DUMMY, TEAM_USER_DUMMY, USER_DUMMY } from '@mocks/mockData';
import { NICKNAME_REGEX } from '@constants/regex';
import { convertTokenToUserId } from '@utils/converter';
import { fileNameParser } from '@utils/fileNameParser';
Expand Down Expand Up @@ -84,22 +77,12 @@ const userServiceHandler = [
if (!file) return new HttpResponse(null, { status: 400 });
if (!(file instanceof File)) return new HttpResponse('업로드된 문서는 파일이 아닙니다.', { status: 400 });

let userId;
// ToDo: 추후 삭제
if (accessToken === JWT_TOKEN_DUMMY) {
const payload = JWT_TOKEN_DUMMY.split('.')[1];
userId = Number(payload.replace('mocked-payload-', ''));
} else {
// 토큰에서 userId 추출
userId = convertTokenToUserId(accessToken);
}

const userId = convertTokenToUserId(accessToken);
if (!userId) {
return HttpResponse.json({ message: '토큰에 포함된 유저 정보가 존재하지 않습니다.' }, { status: 401 });
}

const userIndex = USER_DUMMY.findIndex((user) => user.userId === userId);

if (userIndex === -1) {
return HttpResponse.json(
{ message: '해당 사용자를 찾을 수 없습니다. 입력 정보를 확인해 주세요.' },
Expand Down Expand Up @@ -127,6 +110,40 @@ const userServiceHandler = [

return HttpResponse.json({ imageName: uploadName }, { status: 200 });
}),
// 유저 프로필 이미지 조회 API
http.get(`${BASE_URL}/file/profile/:fileName`, async ({ request, params }) => {
const { fileName } = params;

const accessToken = request.headers.get('Authorization');
if (!accessToken) return new HttpResponse(null, { status: 401 });

const userId = convertTokenToUserId(accessToken);
if (!userId) {
return HttpResponse.json({ message: '토큰에 유저 정보가 존재하지 않습니다.' }, { status: 401 });
}

const userIndex = USER_DUMMY.findIndex((user) => user.userId === userId);
if (userIndex === -1) {
return HttpResponse.json(
{ message: '해당 사용자를 찾을 수 없습니다. 입력 정보를 확인해 주세요.' },
{ status: 401 },
);
}

const decodedFileName = decodeURIComponent(fileName.toString());
const fileInfo = PROFILE_IMAGE_DUMMY.find((file) => file.uploadName === decodedFileName);
if (!fileInfo) return new HttpResponse(null, { status: 404 });

if (fileInfo.userId !== Number(userId))
return HttpResponse.json({ message: '해당 파일에 접근 권한이 없습니다.' }, { status: 403 });

const buffer = await fileInfo.file.arrayBuffer();
return HttpResponse.arrayBuffer(buffer, {
headers: {
'Content-Type': fileInfo.file.type,
},
});
}),
// 전체 팀 목록 조회 API (가입한 팀, 대기중인 팀)
http.get(`${BASE_URL}/user/team`, ({ request }) => {
const accessToken = request.headers.get('Authorization');
Expand Down
18 changes: 17 additions & 1 deletion src/services/userService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export async function updateLinks(links: EditUserLinksForm, axiosConfig: AxiosRe
}

/**
* 유저 프로필 업로드 API
* 유저 프로필 이미지 업로드 API
*
* @export
* @async
Expand All @@ -44,6 +44,22 @@ export async function uploadProfileImage(file: File, axiosConfig: AxiosRequestCo
return authAxios.postForm(`/user/profile/image`, fileFormData, axiosConfig);
}

/**
* 유저 프로필 이미지 조회 API
*
* @export
* @async
* @param {string} fileName - 파일명
* @param {AxiosRequestConfig} [axiosConfig={}] - axios 요청 옵션 설정 객체
* @returns {Promise<AxiosResponse<Blob>>}
*/
export async function getProfileImage(fileName: string, axiosConfig: AxiosRequestConfig = {}) {
return authAxios.get<Blob>(`/file/profile/${fileName}`, {
...axiosConfig,
responseType: 'blob',
});
}

/**
* 유저 목록을 검색하는 API
*
Expand Down

0 comments on commit cdba4c0

Please sign in to comment.