[프로젝트] 메인 프로젝트 : 4주차 일지

Jade·2023년 1월 26일
2

프로젝트

목록 보기
14/28

배포링크

지금 절찬 피드백 받는 중 !!!!

저희가 배포라는 걸 했는데요?
분명히 배포 전 테스트를 열심히 했는데...
왜 a-z까지 수정사항이 나오는 걸가요? ㅋ.ㅋ.ㅋ.ㅋ.ㅋ.ㅋ?



🟡 Main-Project Goals

  • 기획부터 디자인, 개발, 배포까지 ! = 우리 팀만의 어플리케이션 개발
  • 우리 '쓰앵님'조가 만들기로 한 어플리케이션 이름은 '과외차이'
  • 팀장 직책을 자원해서 맡은 만큼 항상 팀원들보다 '조금 더!' 하기
  • 기능 구현 내에서 도전 과제 설정 및 수행
  • 단순한 기능 구현을 넘어 클린 코드 작성, 효율적 코드 작성에 신경쓰기
  • as always 소통에 힘쓰자
  • 매주 일지 남기기


🟡 Process

  • 기본적인 API 연결 후 에러 핸들링
  • API 작업 깃허브 환상의 Merge쇼
  • 소셜 로그인(OAuth) 연결
  • 첫 배포 준비 및 웹 페이지 동작 테스트, 수정
  • 배포 후 수정 사항 점검, 수정
  • github actions를 이용해 지속적 배포 완료
  • 멘토님께 배포된 페이지 보여드리고 피드백 받음


🌕 Hard Points & Solutions

❤️‍🔥 설 연휴에 감기 몸살을 얻어 온 똥멍청이가 있다?💩

이번주의 힘든 점이 뭐였냐고 한다면... 단연코 몸상태를 꼽을 것이다...
건강하지 않을 때 얼마나 고생하는지를 알고 있었기 때문에 주 3회 운동도 꼬옥 하고 있었고, 먹는 것도 야채 위주로 많이 챙겨먹으려고 노력했다.

그럼에도 불구하고 늦게 자는 날들이 많아지고, 수면 부족 때문에 몸이 점점 지쳐가는 걸 느끼긴 했었는데... 이게 이렇게 돌아올 줄이야...😂

팀원들에게 쉬라고 쉬라고 이야기는 했었지 나는 그래도 좀 더 하는 게 맞지 않나 하는 생각이 들어서 좀 오버해서 일을 한 날도 없다곤 말 못하겠고, 워낙에 팀원들이 다 의욕 넘치는 분들이라 더 달렸던 것도 있는 것 같다. 이걸 제동을 걸어주는 게 팀장의 역할이기도 할 텐데 그걸 그냥 몸빵으로 냅다 '안 쉬면 일케 됨'하고 보여주는 바보가... ㅋㅋ...

첨엔 상태가 너무 심상치 않아서 코로나 검사 + 비싼 3만원짜리 독감 검사도 받았는데... 흠. 결과는 그냥 감기 몸살이었다... (💰내 돈...) 아무튼 팀원들의 배려로 충전의 시간도 가졌고, 병원 주사와 약빨로 살아나서 다시 설쳐대는 중이다. 몸 관리 정말 잘 해야지... 면역력 떨어지는 데는 정말 잠 못자는 게 제일이다 싶다.



❤️‍🔥 수많은 알 수 없는 수정의 길 속에 희미한 빛을 난 쫓아가 🥹

대강의 API 연결이 끝나고 나서도 끝났다 싶으면 찾아오는 수정 사항들 때문에 다들 질리도록 수정을 했는데, 질린다는 생각이 들면서도 한편으론 이게 현업에 가서도 할 일이 아닐까...하는 생각도 했다.

실제로 내가 처음부터 어플리케이션을 만들어갈 기회가 있을 수도 있겠지만, 보통은 어떤 회사에 들어가서 이미 만들어진 어플리케이션에 대해 공부하고, 수정점들을 찾아서 수정하는 일들을 더 많이 하지 않을까?

수정 사항이 끊임없이 나오는 게 힘든 이유가 말 그대로 '끝이 없다'는 생각이 때문인 것 같기도 하다. 지금 하는 메인 프로젝트 역시 계속해서 수정해나간다면 끝도 없이 수정을 할 수도 있을 텐데 그건 완성도의 문제일 수도 있지만, 어느 순간이 되면 업데이트의 영역이 될 것이므로 어느순간부터는 수정사항 하나 하나를 하나의 목표들로 삼는 것이 좋을 것 같다는 생각도 든다.

아마 현업에 나가서 일을 하다보면 또 좋은 마인드셋이 생기지 않을까. (미루기 아님.)



useLocation이 필요하다고 생각한 건 useParams를 버리기 위해서였다.
이전까지는 useParams를 이용해 클라이언트 Url의 파라미터들을 받아와서 (ex: profileId, tutoringId 값들) API 통신이나 다른 기능 구현에 사용했는데, 생각해보니 이게 맞나 싶은거다.

profileId나 userId가 url에 노출이 된다...?🤔

물론 우리 프로젝트를 누가 해킹하겠냐만은... 그래도 가능하면 노출되지 않는 게 좋지 않을까 싶은 생각에 useParams를 사용하지 않도록 해보자!라고 의견이 모였다.

팀내 도사님이 useLocation을 사용하면 된다고 금방 해답을 찾아오셨고, 도사님이 시험삼아 적용해본 것을 보고 나도 다른 섹션에 적용해보기로 했다. 처음이다보니 조금 헤매기는 했지만, 성공했을 때 useParams를 벗어날 수 있다는 사실이 넘나 기뻤으므로... 사용법을 공유해보려고 한다.

우선 useLocation을 사용하는 법은 react-router-dom에서 useLocation을 import 해 온 후 변수를 선언하고 useLocation()을 할당해주면 된다.

import { useLocation } from 'react-router-dom';

function App() {
  let location = useLocation();

 //...
  
  );
}

실제 적용은 아래와 같이 했다.

TutoringList 내부의 Tutoring 컴포넌트는 누르면 Link를 이용해 페이지를 이동한다.
<Link to="/tutoring" state={{ tutoringId : tutoringId }}>
이때 Link에 경로 외에도 state를 설정해줄 수 있다.
navigate도 비슷한 방법으로 구현할 수 있다.
navigate("경로", {state: { tutoringId : tutoringId },})

//import 문들은 생략 
const Tutoring = ({ tutoring }) => {
  const {
    tutoringId,
    tutorName,
    tuteeName,
    tutoringTitle,
    tutoringStatus,
    createAt,
    updateAt,
  } = tutoring;

 //중략

  return (
    <li
      className={`${styles.tutoring} ${
        tutoringStatus === 'FINISH' && styles.finish
      }`}
    >
      <Link to="/tutoring" state={{ tutoringId }}>
        <div>
          <h4>
            {sessionStorage.getItem('userStatus') === 'TUTOR'
              ? tuteeName
              : tutorName}
            | {tutoringTitle}
          </h4>
          {createAt === '' ? undefined : (
            <span>
              {new Date(createAt).toLocaleDateString()} ~{' '}
              {tutoringStatus === 'FINISH' &&
                new Date(updateAt).toLocaleDateString()}
            </span>
          )}
          {tutoringStatus === 'UNCHECK' && userStatus === 'TUTEE' && (
            <span className={styles.noti} />
          )}
        </div>
      </Link>
    </li>
  );
};

Tutoring.propTypes = {
  tutoring: PropType.object,
};

export default Tutoring;

이름이... 컴포넌트와 페이지 폴더를 나누다보니 같아졌는데...
아래 Tutoring은 실제로는 페이지이고, 이 페이지에서 과외를 관리할 수 있다.
여기서는 useLocation의 state에서 tutoringId를 꺼내와서 사용할 수 있다.

//import 문들은 생략 

const Tutoring = () => {
  const [tutoring, setTutoring] = useState({});
  const [pageInfo, setPageInfo] = useState({
    page: 1,
    size: 5,
    totalElements: 1,
    totalPages: 1,
  });

  //프로필 아이디는 전역으로 관리하고 있어서 recoil 상태에서 가져온다 
  const { profileId } = useRecoilValue(Profile);

  //tutoringId는 과외 관리 페이지로 이동할 때 Link의 state에 담겨온다. 
  const tutoringId = useLocation().state.tutoringId;

  const getTutoringData = async () => {
    await axios
      .get(`tutoring/details/${profileId}/${tutoringId}`)
      .then(({ data }) => {
        setTutoring(data.data);
        setPageInfo(data.pageInfo);
      })
      .catch((err) => console.log(err));
  };

  useEffect(() => {
    getTutoringData();
  }, []);

 
 //후략



❤️‍🔥 백엔드 : OAuth를 구현할 건데요? 이거 서버에서 다 해줄게요. (프론트팀 : 🧐 네?)

백엔드 팀원들과 함께 실시간으로 OAuth 를 적용해 구글 로그인과 카카오 로그인을 구현했다. 일반적인(사실 일반적이라기 보다는 코드스테이츠 수업에서 배운 내용) OAuth 구현방식과 조금 다른 점이 있다면 우리 팀의 소셜 로그인은 백엔드에서 대부분의 대부분을 컨트롤 해주시는 방식을 사용하기로 해서 “클라이언트에서 소셜 OAuth 서버에 연결되고, 거기서 리다이렉션으로 코드를 받은 뒤 그 코드를 백엔드 서버로 보내고, 백엔드 서버가 인증 서버에 그 코드를 가지고 인증을 받고, 회원 정보를 받아오는 방식”이 아닌, “서버에서 바로 OAuth로 요청을 보내고, 거기서 받은 인증 정보들로 백엔드 서버에서 클라이언트로 보내주는 방식”을 사용했다.

이런 방식이 생소했기 때문에 백엔드에서 처음 만들어주신 API를 봤을 때는 해당 주소를 그냥 브라우저에 넣으면 로그인 과정을 거쳐서 결과값이 브라우저에 바로 띄워져서 그럼 이게 일종의 get요청이고 get에 대한 결과물이 브라우저에 담긴거니까 저 주소를 갖고 axios 요청을 보낸 뒤 응답을 받으면 되겠다고 생각했지만 이렇게 하니 CORS 에러가 뜨게 된다.

이 링크에서 볼 수 있는 cors에러와 동일했고 이 링크와 아래의 두 링크들을 중점적으로 참고한뒤에는 get 요청으로 request를 보낼 게 아니고 그냥 a 태그로 연결해봐야겠다고 생각했다.

참고 링크2

참고 링크3

예를 들어서 로그인 폼 컴포넌트에서 button을 눌러서 onClick 핸들러로 OAuth 서버와 연결하는 것이 아닌 우리 백엔드 서버에서 받은 응답을 a 태그로 연결하고 있음.

//자세한 코드는 지움 일부만 발췌 

<a href="서버 링크가 여기 들어감/oauth2/authorization/kakao" className={styles.kakaoLoginButton}>
  <span>카카오 로그인</span>
</a>

다만 헤맸던 점이 있다면 클라이언트에서는 요청을 해야만 응답데이터를 받아서 사용할 수 있는데, a 태그는 요청이 아니기 때문에 백에서 주는 응답데이터나 헤더에서 정보를 받아올 수 없다는 것이었다.

머리를 싸매고 고민하던 우리가 이 문제를 해결한 방법은 서버에서 파라미터로 액세스 토큰, userStaus(튜터인지 튜티인지), userId, 에러인 경우 error cod, message 등을 보내주면 클라이언트에서 그걸 꺼내서 쓰는 방식이었다.

이를 위해서 아래와 같이 OAuth 페이지를 만들었다.

import { useSetRecoilState } from 'recoil';
import Profile from '../recoil/profile';
import { useNavigate } from 'react-router-dom';
import { useEffect } from 'react';
import ModalState from '../recoil/modal';

const OAuth = () => {
  const setProfile = useSetRecoilState(Profile);
  const setModal = useSetRecoilState(ModalState);
  const navigate = useNavigate();

  const urlParams = new URL(location.href).searchParams;
  const Authorization = urlParams.get('Authorization');
  const userStatus = urlParams.get('userStatus');
  const userId = urlParams.get('userId');
  const code = urlParams.get('code');
  const message = urlParams.get('message');

  //동일한 이메일로 일반 계정이 존재할 때 소셜 로그인 시 409에러가 뜸 
  //해당 에러를 핸들링하기 위함 
  useEffect(() => {
    if (code === '409' && message === 'USER EMAIL EXISTS') {
      navigate('/login');
      setModal({
        isOpen: true,
        modalType: 'alert',
        props: {
          text: '이미 일반회원으로 가입된 계정입니다.\n일반 로그인을 이용해주세요.',
        },
      });
      return;
    }

    sessionStorage.setItem('authorization', Authorization);
    sessionStorage.setItem('userId', userId);
    sessionStorage.setItem('userStatus', userStatus);

    setProfile((prev) => ({
      ...prev,
      isLogin: true,
      userStatus,
    }));

    if (userStatus === 'NONE') {
      navigate('/login');
      console.log('회원정보 입력 필요');
    } else {
      navigate('/');
    }
  });

  return;
};

export default OAuth;

일단 이런 방식으로 OAuth 로그인이 해결이 되긴 했지만, 이렇게 파라미터로 유저의 엑세스 토큰이나 인증정보를 받아오는 게 좋은 방식인지 확신이 서지 않아서 멘토링 때 멘토님께 여쭤보았다.

멘토님께서는 액세스 토큰까지는 받아오되, 서버에 클라이언트가 해당 엑세스 토큰으로 다시 유저를 확인하는 요청을 보내서 다시 응답으로 엑세스 토큰, 유저 정보를 받아오는 것도 좋을 것 같다고 하셨다.



❤️‍🔥 배포: 자니...? 나 배포야... (나 : 호달달 😱)

일단은 AWS의 S3를 이용해서 테스트 배포를 했었고, 한 번 배포를 해본 뒤 큰 문제가 없음을 확인하고 나서 지속적 배포를 하기로 결정했다.

github Actions을 써본지가 오래되어서... 조금 걱정이 되었지만..
내가 써놓은 지속적 배포 관련 게시글도 참고하고... 백엔드 분들이 이미 배포를 해두신 상태여서 해당 yml 파일을 참고도 해가면서 작업했다.

실제 적용 (추가 예정)시 신경썼던 점

  • env 파일은 gitignore 파일이라 push되지 않으므로 env 파일을 생성해주는 절차가 필요하다
  • cache를 사용해서 npm install 하는 시간을 줄일 수 있다.
    캐시 설정 시 참고한 블로그
name: 실행할 액션의 이름을 입력하는 곳 

# 액션이 실행되는 조건(이벤트)
on:
  push:
    branches:
      - dev # dev 브랜치에 push가 생길 때 실행된다.
    path:
      - "client/**" #액션이 해당 경로에서 생기면 실행되

# 액션 워크플로
# 워크플로의 이름을 key로 지정한다. 여러 개의 워크플로가 들어갈 수도 있다.
# 각각의 jobs는 독립된 환경(인스턴스)에서 실행된다.
# 병렬 처리가 가능해 효율을 높일 수 있다.
jobs:
  build: # job의 이름
    runs-on: ubuntu-latest # 액션을 실행 할 OS 지정
    steps: # 인스턴스에서 실행 할 각 절차
      - name: Checkout source code. # Github Checkout Action : 레포지토리에서 코드를 내려받는다.
        uses: actions/checkout@v3
      - name: Get npm cache directory #npm 캐시 디렉토리에서 캐시를 가져오는 단계 
        id: npm-cache-dir
        shell: bash
        run: echo "dir=$(npm config get cache)" >> ${GITHUB_OUTPUT}
      - name: Cache node modules # node modules 캐싱 단계 
        uses: actions/cache@v3
        with:
          path: ${{ steps.npm-cache-dir.outputs.dir }}
          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-
      - name: Install dependencies # npm package install
        run: npm install
        working-directory: ./client # 경로를 지정할 수도 있다.
      - name: Set .env # .env 파일 생성
        run: |
          echo "REACT_APP_BASE_URL=${{ secrets.BASE_URL }}" >> .env
          cat .env
        working-directory: ./client
      - name: Build # react buld 실행
        run: npm run build
        working-directory: ./client
      - name: SHOW AWS CLI VERSION # 기본적으로 설치된 AWS CLI의 버전 확인
        env: # 레포지토리의 secret을 이용해 env로 지정할 수 있다.
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_FE_ACCESS_KEY }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_FE_SECRET_KEY }}
          AWS_EC2_METADATA_DISABLED: true
        run: |
          aws --version
      - name: Sync Bucket # AWS S3 버켓에 Build된 파일 업로드
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_FE_ACCESS_KEY }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_FE_SECRET_KEY }}
          AWS_EC2_METADATA_DISABLED: true
        run: |
          aws s3 sync \
            --region ap-northeast-2 \
            build s3://tutordiff.site \
            --delete
        working-directory: ./client

캐시 설정을 하니... 아래 이미지와 같이 install dependencies를 하는 시간이 18초로 줄었다...! 원래는 34초 정도 나왔는데 이전에도 그렇게 오래 걸린 건 아니겠지만 그래도 실제로 캐시 설정을 해보면서 이렇게 시간이 주는구나~ 마엄으로! 느낄 수 있었다...ㅎㅎ

빌드가 빠른 번들링 앱으로 vite를 사용하기도 한다고 팀원이 소개해주셔서 기록해본다.
공식 문서



🟡 Needs

  • 멘토님과 발견한 수정 사항들 수정
  • 데모 데이 준비 및 문서 작성 마무리
  • 메뉴얼 작성
  • 기술 발표 준비
profile
키보드로 그려내는 일

0개의 댓글