Skip to content

Commit

Permalink
Refactor/#511 프론트의 토큰 만료 검증로직을 삭제하고 Axios를 도입한다. (#554)
Browse files Browse the repository at this point in the history
* config: axios 설치

* feat: axios 인스턴스 및 인터셉터 구현

* refactor: 엑세스 토큰 refresh remote 함수 분리

* refactor: 분리되지 않은 type, remote 함수 분리

* refactor: remote 함수의 fetcher 의존성 제거 및 인스턴스 적용

* refactor: 로그인 remote 함수 분리

* feat: 메인 페이지 장르별 fetch msw 구현

누락된 구현 추가

* chore: 불필요한 파일 제거

* chore: remote 함수 중복 래핑 hook 삭제 및 코드 이동

* refactor: remote 함수 query parameter 처리 방식 통일

* chore: import 방식 변경

* chore: auth 관련 remote함수 auth/하위로 이동

* fix: refresh 요청 API 명세에 맞게 로직 수정

* refactor: 최종 만료시 로그인 페이지 리다이렉트 처리

* refactor: 타입 분리

* refactor: 인터셉터 refresh 중복 요청 방지 기능 추가

* refactor: 에러응답 타입 분리

* chore: fetcher 및 토큰 만료 검증 파일 제거

* refactor: 함수 네이밍 개선

* chore: 주석 수정

* refactor: promise 변수 null 초기화 코드 위치 이동

* style: promise 변수 라인 변경

* refactor: config type 변경 및 린트 주석 제거
  • Loading branch information
Creative-Lee authored Dec 19, 2023
1 parent 5b336df commit 7a7d161
Show file tree
Hide file tree
Showing 34 changed files with 370 additions and 244 deletions.
26 changes: 26 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@types/styled-components": "^5.1.26",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"axios": "^1.6.1",
"babel-loader": "^9.1.3",
"copy-webpack-plugin": "^11.0.0",
"core-js": "^3.31.1",
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/features/auth/remotes/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { client, clientBasic } from '@/shared/remotes/axios';
import type { AccessTokenRes } from '../types/auth.type';

export const getAccessToken = async (platform: string, code: string) => {
const { data } = await client.get<AccessTokenRes>(`/login/${platform}`, {
params: { code },
});

return data;
};

export const postRefreshAccessToken = async () => {
const { data } = await clientBasic.post<AccessTokenRes>('/reissue');

return data;
};
3 changes: 3 additions & 0 deletions frontend/src/features/auth/types/auth.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface AccessTokenRes {
accessToken: string;
}
14 changes: 7 additions & 7 deletions frontend/src/features/comments/components/CommentForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@ import Avatar from '@/shared/components/Avatar';
import useModal from '@/shared/components/Modal/hooks/useModal';
import useToastContext from '@/shared/components/Toast/hooks/useToastContext';
import { useMutation } from '@/shared/hooks/useMutation';
import fetcher from '@/shared/remotes';
import { postComment } from '../remotes/comments';

interface CommentFormProps {
getComment: () => Promise<void>;
getComments: () => Promise<void>;
songId: number;
partId: number;
}

const CommentForm = ({ getComment, songId, partId }: CommentFormProps) => {
const CommentForm = ({ getComments, songId, partId }: CommentFormProps) => {
const [newComment, setNewComment] = useState('');
const { isOpen, closeModal: closeLoginModal, openModal: openLoginModal } = useModal();
const { user } = useAuthContext();

const isLoggedIn = !!user;

const { mutateData } = useMutation(() =>
fetcher(`/songs/${songId}/parts/${partId}/comments`, 'POST', { content: newComment.trim() })
const { mutateData: postNewComment } = useMutation(() =>
postComment(songId, partId, newComment.trim())
);

const { showToast } = useToastContext();
Expand All @@ -38,11 +38,11 @@ const CommentForm = ({ getComment, songId, partId }: CommentFormProps) => {
const submitNewComment: React.FormEventHandler<HTMLFormElement> = async (event) => {
event.preventDefault();

await mutateData();
await postNewComment();

showToast('댓글이 등록되었습니다.');
resetNewComment();
getComment();
await getComments();
};

return (
Expand Down
17 changes: 5 additions & 12 deletions frontend/src/features/comments/components/CommentList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,23 @@ import useModal from '@/shared/components/Modal/hooks/useModal';
import Spacing from '@/shared/components/Spacing';
import SRHeading from '@/shared/components/SRHeading';
import useFetch from '@/shared/hooks/useFetch';
import fetcher from '@/shared/remotes';
import { getComments } from '../remotes/comments';
import Comment from './Comment';
import CommentForm from './CommentForm';

interface Comment {
id: number;
content: string;
createdAt: string;
writerNickname: string;
}

interface CommentListProps {
songId: number;
partId: number;
}

const CommentList = ({ songId, partId }: CommentListProps) => {
const { isOpen, openModal, closeModal } = useModal(false);
const { data: comments, fetchData: getComment } = useFetch<Comment[]>(() =>
fetcher(`/songs/${songId}/parts/${partId}/comments`, 'GET')
const { data: comments, fetchData: refetchComments } = useFetch(() =>
getComments(songId, partId)
);

useEffect(() => {
getComment();
refetchComments();
}, [partId]);

if (!comments) {
Expand Down Expand Up @@ -73,7 +66,7 @@ const CommentList = ({ songId, partId }: CommentListProps) => {
))}
</Comments>
<Spacing direction="vertical" size={8} />
<CommentForm getComment={getComment} songId={songId} partId={partId} />
<CommentForm getComments={refetchComments} songId={songId} partId={partId} />
</BottomSheet>
</>
);
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/features/comments/remotes/comments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { client } from '@/shared/remotes/axios';
import type { Comment } from '../types/comment.type';

export const postComment = async (songId: number, partId: number, content: string) => {
await client.post(`/songs/${songId}/parts/${partId}/comments`, { content });
};

export const getComments = async (songId: number, partId: number) => {
const { data } = await client.get<Comment[]>(`/songs/${songId}/parts/${partId}/comments`);

return data;
};
6 changes: 6 additions & 0 deletions frontend/src/features/comments/types/comment.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Comment {
id: number;
content: string;
createdAt: string;
writerNickname: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ import { useNavigate } from 'react-router-dom';
import styled from 'styled-components';
import { useAuthContext } from '@/features/auth/components/AuthProvider';
import useCollectingPartContext from '@/features/killingParts/hooks/useCollectingPartContext';
import { usePostKillingPart } from '@/features/killingParts/remotes/usePostKillingPart';
import useVideoPlayerContext from '@/features/youtube/hooks/useVideoPlayerContext';
import useModal from '@/shared/components/Modal/hooks/useModal';
import Modal from '@/shared/components/Modal/Modal';
import Spacing from '@/shared/components/Spacing';
import { useMutation } from '@/shared/hooks/useMutation';
import { toPlayingTimeText } from '@/shared/utils/convertTime';
import { postKillingPart } from '../remotes/killingPart';

const RegisterPart = () => {
const { isOpen, openModal, closeModal } = useModal();
const { user } = useAuthContext();
const { interval, partStartTime, songId } = useCollectingPartContext();
const video = useVideoPlayerContext();
const { createKillingPart } = usePostKillingPart();
const { mutateData: createKillingPart } = useMutation(postKillingPart);
const navigate = useNavigate();

// 현재 useMutation 훅이 response 객체를 리턴하지 않고 내부적으로 처리합니다.
Expand Down
6 changes: 0 additions & 6 deletions frontend/src/features/killingParts/hooks/killingPart.ts

This file was deleted.

18 changes: 18 additions & 0 deletions frontend/src/features/killingParts/remotes/killingPart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { client } from '@/shared/remotes/axios';
import type { KillingPartPostRequest } from '@/shared/types/killingPart';
import type { SongInfo } from '@/shared/types/song';

// PartCollectingPage에 존재하던 remote 함수입니다.
// useFetch<SongInfo>(() => fetcher(`/songs/${songId}`, 'GET')) 로직에서 분리하였습니다.
// SongInfo type에는 killingPart[] 필드가 있는데, 마이파트 수집용 `노래 1개` 조회에서 해당 타입이 사용되고 있습니다.
// 추후 수정되어야 합니다.

export const getSong = async (songId: number) => {
const { data } = await client.get<SongInfo>(`/songs/${songId}`);

return data;
};

export const postKillingPart = async (songId: number, body: KillingPartPostRequest) => {
await client.post(`/songs/${songId}/member-parts`, body);
};

This file was deleted.

4 changes: 2 additions & 2 deletions frontend/src/features/member/components/MyPartList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export type LikeKillingPart = Pick<
const MyPartList = () => {
const [tab, setTab] = useState<MyPageTab>('Like');

const { data: likes } = useFetch<LikeKillingPart[]>(getLikeParts);
const { data: myParts } = useFetch<LikeKillingPart[]>(getMyParts);
const { data: likes } = useFetch(getLikeParts);
const { data: myParts } = useFetch(getMyParts);

if (!likes || !myParts) {
return null;
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/features/member/remotes/member.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import fetcher from '@/shared/remotes';
import { client } from '@/shared/remotes/axios';

export const deleteMember = (memberId: number | undefined) => () => {
return fetcher(`/members/${memberId}`, 'DELETE');
export const deleteMember = async (memberId: number) => {
await client.delete(`/members/${memberId}`);
};
6 changes: 4 additions & 2 deletions frontend/src/features/member/remotes/memberParts.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import fetcher from '@/shared/remotes';
import { client } from '@/shared/remotes/axios';

export const deleteMemberParts = (partId: number) => fetcher(`/member-parts/${partId}`, 'DELETE');
export const deleteMemberParts = async (partId: number) => {
await client.delete(`/member-parts/${partId}`);
};
15 changes: 12 additions & 3 deletions frontend/src/features/member/remotes/myPage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import fetcher from '@/shared/remotes';
import { client } from '@/shared/remotes/axios';
import type { LikeKillingPart } from '../components/MyPartList';

export const getLikeParts = () => fetcher('/my-page/like-parts', 'GET');
export const getLikeParts = async () => {
const { data } = await client.get<LikeKillingPart[]>('/my-page/like-parts');

export const getMyParts = () => fetcher('/my-page/my-parts', 'GET');
return data;
};

export const getMyParts = async () => {
const { data } = await client.get<LikeKillingPart[]>('/my-page/my-parts');

return data;
};
26 changes: 19 additions & 7 deletions frontend/src/features/search/remotes/search.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import fetcher from '@/shared/remotes';
import { client } from '@/shared/remotes/axios';
import type { SingerDetail } from '../../singer/types/singer.type';
import type { SingerSearchPreview } from '../types/search.type';

export const getSingerSearchPreview = async (query: string): Promise<SingerSearchPreview[]> => {
const encodedQuery = encodeURIComponent(query);
return await fetcher(`/search?keyword=${encodedQuery}&type=singer`, 'GET');
export const getSingerSearchPreview = async (query: string) => {
const { data } = await client.get<SingerSearchPreview[]>(`/search`, {
params: {
keyword: query,
type: 'singer',
},
});

return data;
};

export const getSingerSearch = async (query: string): Promise<SingerDetail[]> => {
const encodedQuery = encodeURIComponent(query);
return await fetcher(`/search?keyword=${encodedQuery}&type=singer&type=song`, 'GET');
export const getSingerSearch = async (query: string) => {
const params = new URLSearchParams();
params.append('keyword', query);
params.append('type', 'singer');
params.append('type', 'song');

const { data } = await client.get<SingerDetail[]>(`/search`, { params });

return data;
};
8 changes: 5 additions & 3 deletions frontend/src/features/singer/remotes/singer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import fetcher from '@/shared/remotes';
import { client } from '@/shared/remotes/axios';
import type { SingerDetail } from '../types/singer.type';

export const getSingerDetail = async (singerId: number): Promise<SingerDetail> => {
return await fetcher(`/singers/${singerId}`, 'GET');
export const getSingerDetail = async (singerId: number) => {
const { data } = await client.get<SingerDetail>(`/singers/${singerId}`);

return data;
};
4 changes: 2 additions & 2 deletions frontend/src/features/songs/components/SongItem.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import popularSongs from '@/mocks/fixtures/popularSongs.json';
import highLikedSongs from '@/mocks/fixtures/highLikedSongs.json';
import SongItem from './SongItem';
import type { Meta, StoryObj } from '@storybook/react';

Expand All @@ -11,7 +11,7 @@ export default meta;

type Story = StoryObj<typeof SongItem>;

const { title, singer, albumCoverUrl, totalLikeCount } = popularSongs[0];
const { title, singer, albumCoverUrl, totalLikeCount } = highLikedSongs[0];

export const Default: Story = {
args: {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/features/songs/remotes/likes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import fetcher from '@/shared/remotes';
import { client } from '@/shared/remotes/axios';

export const putKillingPartLikes = async (songId: number, partId: number, likeStatus: boolean) => {
return await fetcher(`/songs/${songId}/parts/${partId}/likes`, 'PUT', { likeStatus });
await client.put(`/songs/${songId}/parts/${partId}/likes`, { likeStatus });
};
18 changes: 11 additions & 7 deletions frontend/src/features/songs/remotes/song.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import fetcher from '@/shared/remotes';
import { client } from '@/shared/remotes/axios';
import type { Genre, Song } from '../types/Song.type';
import type { RecentSong } from '@/shared/types/song';

// 메인 케러셀 최신순 노래 n개 조회 api - 쿼리파람 없는경우, 응답 기본값은 5개입니다.
export const getRecentSongs = async (songCount?: number): Promise<RecentSong[]> => {
const query = songCount ? `?size=${songCount}` : '';
export const getRecentSongs = async (songCount?: number) => {
const { data } = await client.get<RecentSong[]>(`/songs/recent`, {
params: { size: songCount },
});

return await fetcher(`/songs/recent${query}`, 'GET');
return data;
};

export const getHighLikedSongs = async (genre: Genre): Promise<Song[]> => {
const query = genre === 'ALL' ? '' : `?genre=${genre}`;
export const getHighLikedSongs = async (genre: Genre) => {
const { data } = await client.get<Song[]>(`/songs/high-liked`, {
params: { genre: genre === 'ALL' ? null : genre },
});

return await fetcher(`/songs/high-liked${query}`, 'GET');
return data;
};
Loading

0 comments on commit 7a7d161

Please sign in to comment.