Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix/#439] image upload logic 개편 #467

Merged
merged 40 commits into from
Nov 5, 2024
Merged

Conversation

ljh0608
Copy link
Member

@ljh0608 ljh0608 commented Oct 31, 2024

✨ 해당 이슈 번호 ✨

close #439

todo

해당 로직을 개선하고 디버깅하는 과정에서 발생하는 에러들을 차곡차곡 수정해나가다보니 PR사이즈가 굉장히 커졌습니다.
잘 이해가지 않는 부분은 편하게 질문주세요! 다양한 의견 환영입니다!
image

  • presigned url을 통해 s3에 이미지를 업로드하는 로직이 기존에 글 제출 로직에서가 아닌,
    이미지를 업로드하는 즉시 실행되었는데 이는 리소스의 낭비로 이어지기 때문에 글 제출하기 등 post로직을 실행하기 직전에 s3에 이미지를 업로드하고 성공시에 post, put 등의 mutation api를 실행시키도록 로직 개선
  • presigned url 만료시 자동 재발급 로직 추가
  • presigned url 로직을 공통으로 사용하기 위한 props 추가
  • presigned url을 감싸는 함수 수정 -
  1. 새로 등록한 image file이 있다면 해당 image file을 s3에 업로드
  2. 새로 등록한 image file이 없고 서버에서 전달받은 url이 있는 경우 s3업로드 로직 없이 해당 url 전달 (글 임시저장,글 수정, 글모임 정보수정 시 )

이미지 업로드

  • 이미지 업로드 로직 중 file type검사하는 로직 추가 및 svg등 잘못된 파일 형식 케이스 처리 로직 추가
  • 글쓰기 페이지(글 수정, 삭제, 임시저장), 글모임 생성 페이지(수정) 등에서 다양하게 사용되는 image upload 로직을 커스텀훅으로 분리하여 재사용

글 작성페이지

  • 제출, 임시저장, 수정 로직에 모두 제출 직전 s3업로드되도록 로직 변경
  • 스크롤 관련 useEffect에서 수정, 임시저장, 제출 api 중복 호출하던 로직 삭제

글제출

  • 글 제출 에러 처리 로직 개선 ( 글 제목과 내용이 모두 빈 값이 아닐때만 api요청하도록 )
  • 글쓰기 완료 후 "홈으로가기" 라우팅을 모두 글모임 상세페이지로 라우팅하는 로직으로 변경

임시저장

  • 임시저장 글이 없을 때 MA==이라는 0값을 id로 주는데 해당 케이스 처리 로직 추가
  • 글 임시저장 로직에 s3업로드 로직 추가

글모임 생성, 수정, 삭제

  • 글모임 삭제시 잘못된 query invalidate 로직 수정
  • 글모임 수정시 400error 핸들링
  • 글모임 수정시 글모임 이름 빈 값에 대한 에러 핸들링

📌 내가 알게 된 부분

  • 하나의 함수를 다양한 곳에서 사용할 때는 미리 파악해서 이슈 사이즈를 계산하고 큰 그림을 파악하고 시작하면 좋을 것 같다는 생각을 했습니다. s3업로드 로직이 글 수정, 임시저장, 제출, 글모임 생성, 수정 총 5개의 로직에서 사용하다보니 PR사이즈가 조금 커졌는데 추가로 유저플로우를 중단시킬만큼의 우선순위가 높은 에러들을 발견하여 리팩토링 중 개선하였습니다.

📌 질문할 부분

📌스크린샷(선택)

@ljh0608 ljh0608 added ♻️ Refactor 코드 리팩토링 ⚒️ Fix 기존의 버그 수정 labels Oct 31, 2024
@ljh0608 ljh0608 requested review from moondda and se0jinYoon October 31, 2024 05:26
@ljh0608 ljh0608 self-assigned this Oct 31, 2024
Copy link

vercel bot commented Oct 31, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
mile-client ✅ Ready (Inspect) Visit Preview 💬 Add feedback Nov 5, 2024 9:22am

@github-actions github-actions bot added the size/l size/l label Oct 31, 2024
Copy link
Contributor

@se0jinYoon se0jinYoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 많은 파일을 .. 고생하셨어요 .. 코멘트 한 번만 확인해주세요!~!

await mutate();
setEditBtnActive(false);
alert('글모임 정보가 수정되었습니다.');
const groupImageServerUrl1 = await handleImageUpload(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p5) 이 네이밍 뒤에 1이 붙은 이유는 뭔가요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 변수를 기존에 사용하고 있어서 임시로 변수명을 지었는데 수정을 안했네요! 수정했습니다!

} else {
throw new Error('서버로 보내는 이미지가 undefined 입니다.');
}
} else if (imageUrl === '' || imageUrl === undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3) imageUrl이 undefined일 경우가 있나요 ? 잘못된 파일 올렸을 경우 undefined로 뜨나요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imageUrl은 서버통신해서 받아오거나 useLocation의 location 객체에 접근해서 가져오니 undefinded일 경우가 있을 것이라고 생각했습니다!

근데 imageUrl을 서버에서 받아오기 전 undefined가 아니면 값을 setState해주고 있고
useLocation의 location.state의 값이 없을 때는 null을 반환해서 해당 로직을 undefined가 아닌 null로 변경해주는 것으로 좋겠네요 좋은 리뷰 감사합니다!

previewImgUrl,
);
if (groupImageServerUrl1) {
await mutate(groupImageServerUrl1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p4) 이거 mutate에 alias 적용해서 더 직관적인 이름으로 사용하는게 코드 읽기 좋을 것 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

동의합니다! 변경하겠습니다~

isPublic,
groupId,
});
const {
data: groupNameValidationData,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p4) 이거 밑에서 보니까 객체 타고타고타고 내려가서 값 사용하던데, 애초에 쿼리에서 return할 때 return data = groupNameValidationData?.data?.data? 이런식으로 더 세부적으로 반환하게 하면 좋을 것 같아요
아니면 isValidate만 꺼내서 쓰던가!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은데요?? 가독성을 신경못썼네요 감사합니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영했습니다

}
}
}, [groupNameValidationData, isSuccess]);
const { data } = useFetchGroupInfo(groupId || '');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p4) 여기 data도 네이밍 해주면 좋을듯!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 반영했씁니다!

import { useGetGroupNameValidation } from '../hooks/queries';
import { CurrentPageType } from '../types/stateType';
import CreateGroupTopicModal from './CreateGroupTopicModal';
import createGroupIlust from '/src/assets/images/createGroupIlust.png';

type Setter<T> = (value: T) => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p5) 오호오호

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p5) 이걸 사용하는 페이지가 있어요 ? 나 왜 첨보지

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재는 사용하지 않습니다! 기획쪽 정책이 바뀌었는데 추후 적용될 가능성이 있다고 해서 남겨두었습니다~

Comment on lines +8 to +9
'Content-Type': 'image/jpg',
//'Content-Type': 'multipart/form-data',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p5) 여기 왜 jpg만 받도록 되었나요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서버에서 s3에 업로드하는 이미지를 모두 jpg로 변환하고 있어서 해당 content type을 이렇게 설정해두었습니다 이부분은 서버에 문의해둔 상태이고 추후 변경돼야합니다!

createTempSaveContent({ groupId, topicId, title, content, imageUrl, anonymous }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [QUERY_KEY_POST.getTempSaveFlag, groupId] });
navigate(`/group/${groupId}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p1) 여기 임시저장한 경우 모달버튼 [아니오,예] 중에 예를 눌러야 그룹 페이지로 이동해야하는데 쿼리가 성공하자마자 navigate하면 안되지않나요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇 이 플로우는 임시저장 버튼을 누르면
임시저장 모달 버튼 [아니오, 예] 가 뜨는게 맞고 예를 클릭했을 때의 onSuccess 로직입니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요부분 api통신이 "예" 를 눌렀을 때 실행되는 부분이라 현재 코드가 서진님이 말씀하신 플로우로 작동합니다!

@@ -223,7 +234,7 @@ export const useGetTempSaveContent = (postId: string, isTempClicked: boolean) =>
const { data } = useQuery({
queryKey: [QUERY_KEY_POST.getTempSaveContent, postId],
queryFn: () => fetchTempSaveContent(postId),
enabled: !!isTempClicked,
enabled: !!isTempClicked && postId !== 'MA==',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p1) 얘만 막아둔 이유는 뭐죠 ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서버에서 임시저장 글이 없는 경우 0의 base64 인코딩 값을 의미하는 "MA==" 을 보내줍니다!
이전에는 이 에러처리가 없어서 임시저장이 없을 때 항상 에러를 발생시키고 있기에 해당 postId가 MA==이 아닐때를 넣어두었습니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0을 base64 인코딩한 값이 MA==인데 서버에서는 임시저장한 글이 없는 경우 해당 값을 내려주고 있습니다!
그래서 임시저장한 글이 없는 경우에는 해당 로직이 실행되면 안되기 때문에 MA==을 예외처리 해주었습니다~

Copy link
Contributor

@moondda moondda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셧슴니다~

Comment on lines 124 to 142
useEffect(() => {
if (isSuccess) {
if (groupNameValidationData?.data?.data?.isValidate === true) {
setGroupNameInfoMsg(InputInfoMsg.groupNameAvailable);
setPassDuplicate(true);
} else if (groupNameValidationData?.data?.data?.isValidate === false) {
setGroupNameInfoMsg(InputInfoMsg.groupNameNotAvailable);
setGroupNameValid(false);
}
}
}, [groupNameValidationData, isSuccess]);

useEffect(() => {
if (data?.data) {
setGroupName(data?.data?.moimTitle);
setBeforeGroupName(data?.data?.moimTitle);
setGroupDesc(data?.data?.description);
setIsPublic(data?.data?.isPublic);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p2) 리액트쿼리 내에서 비동기 패칭을 진행해주니까 초기 상태부터 data에서 가져와도 되지않을까요??
const [groupName, setGroupName] = useState(data?.data.moimTitle || ''); 요런느낌으로?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p1) beforeGroupName같은 경우에는 바뀌지 않는 변수이므로 굳이 상태로 관리 안해도 괜찮을것 같습니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p2) 리액트쿼리 내에서 비동기 패칭을 진행해주니까 초기 상태부터 data에서 가져와도 되지않을까요?? const [groupName, setGroupName] = useState(data?.data.moimTitle || ''); 요런느낌으로?

이렇게하면 렌더링 순서 보장이 안되지 않을까?? 라는 생각으로 react query의 동작 원리랑 순서를 확인해 보았는데 react query는 비동기 데이터 패칭 후 연결된 해당 컴포넌트를 리렌더링 합니다
useState의 초기값은 컴포넌트가 처음 렌더링할 때 (마운트 될 때) 한번만 설정되므로 해당 로직으로 추가하면 값을 제대로 받아오지 못합니다!
이는 실제로 코드에 적용하여 확인해보기도 했습니다.
따라서 초기 렌더링에는 빈문자열로 두었다가 비동기 데이터를 불러오는 과정이 성공적으로 이루어졌을 때 useEffect 내에서 setState를 사용하는 것이 더 적합할 것 같다는 생각이며 이때 isSuccess값을 사용하면 더 좋았을 것 같다는 생각이 들어 이부분 수정하려고 합니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p1) beforeGroupName같은 경우에는 바뀌지 않는 변수이므로 굳이 상태로 관리 안해도 괜찮을것 같습니다!

beforeGroupName은 변하지는 않지만 컴포넌트에서 리렌더링시에 계속 기억해줘야하는 메모리변수입니다! 따라서 let으로 값을 선언하고 변경하면 리렌더링시 다시 "" 값으로 돌아가서 정상적인 작동을 하지 않기에 상태로 관리해주어야 한다고 생각해요!

Copy link
Contributor

@moondda moondda Nov 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p2) 리액트쿼리 내에서 비동기 패칭을 진행해주니까 초기 상태부터 data에서 가져와도 되지않을까요?? const [groupName, setGroupName] = useState(data?.data.moimTitle || ''); 요런느낌으로?

이렇게하면 렌더링 순서 보장이 안되지 않을까?? 라는 생각으로 react query의 동작 원리랑 순서를 확인해 보았는데 react query는 비동기 데이터 패칭 후 연결된 해당 컴포넌트를 리렌더링 합니다 useState의 초기값은 컴포넌트가 처음 렌더링할 때 (마운트 될 때) 한번만 설정되므로 해당 로직으로 추가하면 값을 제대로 받아오지 못합니다! 이는 실제로 코드에 적용하여 확인해보기도 했습니다. 따라서 초기 렌더링에는 빈문자열로 두었다가 비동기 데이터를 불러오는 과정이 성공적으로 이루어졌을 때 useEffect 내에서 setState를 사용하는 것이 더 적합할 것 같다는 생각이며 이때 isSuccess값을 사용하면 더 좋았을 것 같다는 생각이 들어 이부분 수정하려고 합니다!

오 상세한 설명 감사합니다!! 사실 useEffect를 쓰지 않고 구현할 수 없을까? 에서 파생된 생각이었는데 렌더링 순서 이슈가 있군요. beforeGroupName도 사실 렌더링 순서만 보장된다면 처음부터 const로 선언하자는 말이었습니다 (안되지만)
두번째로 생각나는건 리액트쿼리의 onSuccess에서 상태 업데이트 함수를 넘기면 비슷한 느낌으로 useEffect없이 되는데 usequery onSuccess가 v5에서 빠져서...쩝 최선의 코드같네요 알겠습니다!!

@@ -196,6 +189,9 @@ const PostPage = () => {
// 에디터 글 내용 태그 제외한 값 (valid 확인용)
const [contentWithoutTag, setContentWithoutTag] = useState('');

const [imageFile, setImageFile] = useState<File | null>(null);
const [postContentId, setPostContentId] = useState<string | undefined>('');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3) postContentId가 undefined인 경우도 생기나요??

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

원래는 있었는데 아래 로직을 수정하면서 없어졌네요!
타입 제거하겠습니다!

    onSuccess: (data) => {
      if (data) {
        setPostContentId(data);
        modalOpen();
      }

@ljh0608 ljh0608 merged commit af2a628 into develop Nov 5, 2024
3 checks passed
@ljh0608 ljh0608 added the 재훈 label Nov 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
⚒️ Fix 기존의 버그 수정 ♻️ Refactor 코드 리팩토링 size/l size/l 재훈
Projects
Status: Done
3 participants