[Project] 숭고 Soongo - 백앤드 진행편

숨고 사이트를 클론하기로 결정한 이유

첫번째 프로젝트로 경력이 있는 두 분과 잘하시는 분들 두 분이 같은 팀원이 되어서 "숨은 고수"로 우리를 표현하고 싶었다. 😎
그리고 숨고 사이트가 회원가입이 일반, 고수로 이분화되어 있었고
일반회원이 원하는 서비스를 고수가 제공하는 서비스와 매칭해주는 시스템이 백엔드 공부하기에 너무 좋아 보였다!


내가 맡은 역할

고수 회원 가입 API

  1. 숭고 사이트에 한번도 가입하지 않은 사람은 바로 가입할 수 있다.
  2. 일반 회원으로 가입했던 사람도 로그인한 상태에서 고수로 전환하기를 선택하면 고수로 가입할 수 있었다.
  3. 고수로 가입한 회원은 일반 회원의 기능을 모두 이용할 수 있는데, 일반 회원으로 전환하는 버튼을 누르면 로그인한 상태에서 이동이 가능하다.

프로젝트 첫날 모델링 회의를 할 때 먼저 일반회원, 고수를 먼저 만들고 시작했다.
컬럼명은 줄임말을 쓰지 않고 자세하게 적는 게 좋다고 한다. 그래서 최대한 친절하게 만들었다. 이렇게 하니까 확실히 바로 보고 이해하기에 수월했다.

관계형 DB를 모델링할때는 1:1 관계 설정은 지양해야 한다고 들었지만 일반 회원과 고수를 분리해야 각각 연관된 테이블과 관계 설정을 할 수 있어서 분리했다. 가입시 선택한 카테고리, 주소 정보를 전달 받으면 각각 테이블에서 찾아서 중간 테이블에 저장하는 코드를 작성했다.


// 1. 로그인 토큰이 없는 경우
if (typeof token === "undefined") {
  // 이메일과 번호로 가입된 사용자인지 확인
  const userInfo = await UserDao.findUserInfo(email, phoneNumber)

  // 로그인을 하지 않았지만, 일반 회원이나 고수로 이미 가입한 사용자인지 확인
  if (userInfo.length !== 0) {
    const isExistingMaster = await MasterDao.findMasterInfo(userInfo[0].id)
    if (isExistingMaster.length !== 0) {
      throw await errorGenerator({
        statusCode: 400,
        message: "EXISTING_MASTER",
      })
    } else {
      throw await errorGenerator({
        statusCode: 400,
        message: "EXISTING_USER!_PLEASE_LOGIN",
      })
    }
  }

  // 일반 회원으로 가입한 적이 없고 고수로 가입 신청한 경우, user 테이블에 추가
  const hashedPW = bc.hashSync(password, bc.genSaltSync())
  await UserDao.createUserDirectMaster(name, email, hashedPW, phoneNumber)
  
// 2. 로그인 토큰이 있는 경우
} else {
  try {
    // 토큰을 가지고 유저가 맞는지 확인
    const isValidUser = jwt.verify(token, process.env.SECRET_KEY)
    await UserDao.insertPhoneNum(isValidUser.id, phoneNumber)
    
  } catch (error) {
    throw await errorGenerator({
      statusCode: 400,
      message: "INVALID_USER",
    })
  }
}
// 3. 이후에는 고수와 연관된 테이블에 정보 추가

고수 후기 API

  1. 일반 회원은 본인과 매칭된 고수에게 후기를 남길 수 있다.
  2. 고수 상세 페이지에서 고수의 평점과 후기를 볼 수 있다.

후기 테이블 하나만 더 생성하고 일반 회원과 고수 id를 외래키로 설정해서 각각 1:N관계로 만들 수 있었다.
그리고 리뷰 추가 버튼은 매칭 후 생성되기 때문에 일반 회원 id값을 로그인 토큰에서 받아서 사용하면 된다. 하지만 우리팀은 리뷰 추가 기능을 다음으로 미뤘기 때문에 고수 id를 받아서 그 고수의 리뷰만 찾아서 보내주는 기능만 만들었다.


const sendPreview = async (masterId) => {
  try {
    // masterId가 master테이블에 있는지 확인
    const isMaster = await MasterDao.isMaster(masterId)
    if (isMaster.length === 0) {
      throw await errorGenerator({ 
        statusCode:400, message: "MASTER_DOES_NOT_EXIST" 
      })
    }

    const reviews = await ReviewDao.sendPreview(masterId)

    return reviews
  } catch (error) {
    throw await error
  }
}

프로젝트하면서 배운 것들


1. 관계형 DB 1:1 관계와 1:N 관계의 차이

여러 테이블 간 관계 설정을 하기 위해서는 꼭 중간 테이블을 만들어야 한다고 생각했는데, 고수가 주소를 하나만 갖는다는 점을 생각해 보니 고수 테이블과 주소 테이블은 1:1 관계라서 중간 테이블이 필요 없었다. 그래서 중간 테이블은 삭제하게 되었다. 중간테이블은 N:M 관계일때 유용한 테이블이니까!

2. name대신 id 전달받기

프론트단에서 가입시 선택한 주소 정보를 "경기", "일산동구" 이렇게 문자열로 받아야 된다고 생각했는데 id로 전달 받을 수 있었다! 화면 단위로만 생각하다보니 깊게 생각하지 못했다.

3. prisma는 착한 친구다.

prisma 내장 함수를 이용하면 가독성도 훨씬 좋고 세미 콜론 빠드렸다고 소름 돋을 일도 없다..

// prisma 내장 함수
const getAddress = async () => {
  return await prisma.address.findMany({
    select: {
      id: true,
      name: true,
      detailAddress: {
        select: {
          id: true,
          name: true,
        },
      },
    },
  });
};

// SQL 쿼리문
const sendMasterAddress = async (id) => {
  return await prisma.$queryRaw`
    select a.name as address, d.name as detail_address
    from address a, detail_address d, masters m 
    where m.id = ${id}
    and m.address_id = a.id
    and m.detail_address_id = d.id;
  `;
};

4. 미들웨어를 쓰면 좋은점, 무한스크롤 하는 방법

프로젝트 후기에서 작성할 예정ㅎ



더 해보고 싶은 것들

  • 카카오 로그인 API, 지도 API를 적용해서 카카오 간편 로그인 기능, 고수의 이동 가능 거리 선택 기능 넣기
  • multer 라이브러리를 이용해서 사용자가 업로드한 사진을 서버에 사용자별 폴더에 저장하고 그 주소를 DB에 저장하기
  • Query Parameters 사용하기!
  • GIT 더 자세하게 공부해서 GIT공포증 없애기..

좋은 웹페이지 즐겨찾기