[2024.05.31 TIL] 내일배움캠프 33일차 (팀프로젝트 발제, SA 작성)

My_Code·2024년 6월 1일
0

TIL

목록 보기
43/113
post-thumbnail

본 내용은 내일배움캠프에서 활동한 내용을 기록한 글입니다.


💻 TIL(Today I Learned)

📌 Today I Done

✏️ S.A 작성하기

  • 1) 프로젝트 아이디어 구상하기

    • 프로젝트 명 : 둥근 마켓
    • 소개 : 현재 서비스 중인 “당근 마켓” 사이트를 오마주한 뉴스피드 프로젝트
  • 2) 와이어프레임

  • 3) ERD DIAGRAM

  • 4) GitHub Rules

    • git commit rule.

    • 이슈 번호 - 브랜치 연결

      • issue ex) feat/로그인 API 구현
      • branch ex) feat/login-api#4
    • 모든 사항 issue 에서 브랜치 생성 후 진행.

    • dev, main 브랜치에 푸쉬 금지.

  • 5) Code Convention

    • 변수, 함수 : camel case (ex. userCreateValidation)

    • 상수 : 대문자 + snake case (ex. DB_CONNECTION_URL)

    • schema : snake case (ex. social_comment_like)

    • 파일명 : kebab case ( ex. require-access-token.middelware.js )

    • 모든 숫자, 문자, 불리언을 객체화 후 상수 관련 폴더에 저장한 뒤 import 로 사용.

    • prettierrc ( yarn add -D prettierrc )

    export const HTTP_STATUS = {
      OK: 200, // 호출에 성공했을 때
      CREATED: 201, // 생성에 성공했을 때
      BAD_REQUEST: 400, // 사용자가 잘못 했을 때 (예: 입력 값을 빠뜨렸을 때)
    };
    
    return res.status(HTTP_STATUS.CREATED)
    • prettierrc ( yarn add -D prettierrc )
    {
        "printWidth": 100,
        "trailingComma": "es5",
        "tabWidth": 2,
        "semi": true,
        "singleQuote": true,
        "arrowParens": "always"
    }
    • ESLint ( yarn add -D eslint )
    export default [
      { languageOptions: { globals: globals.node } },
      pluginJs.configs.recommended,
      {
        rules: {
          // 없으면 기본값 'error', warn, off 설정가능
          'no-unused-vars': 'warn',
        },
      },
    ];
    // eslint-disable-next-line no-unused-vars -> 예외 처리

✏️ 기본 파일 구조 만들기

.
├── node_modules
├── prisma
│ └── schema.prisma
├── src
│ ├── constants
│ ├── middlewares
│ ├── routers
│ ├── utils
│ └── app.js
├── .env
├── .env.example
├── .gitignore
├── .prettierrc
├── package.json
├── README.md
└── yarn.lock

✏️ 상태 코드, 각종 메세지 객체화 하기

  • 상태 코드 객체화
export const HTTP_STATUS = {
  OK: 200, // 호출에 성공했을 때
  CREATED: 201, // 생성에 성공했을 때
  BAD_REQUEST: 400, // 사용자가 잘못 했을 때 (예: 입력 값을 빠뜨렸을 때)
  UNAUTHORIZED: 401, // 인증 실패 unauthenticated (예: 비밀번호가 틀렸을 때)
  FORBIDDEN: 403, // 인가 실패 unauthorized (예: 접근 권한이 없을 때)
  NOT_FOUND: 404, // 데이터가 없는 경우
  CONFLICT: 409, // 충돌이 발생했을 때 (예: 이메일 중복)
  INTERNAL_SERVER_ERROR: 500, // 예상치 못한 에러가 발생했을 때
};
  • 메세지 객체화
export const MESSAGES = {
  AUTH: {
    COMMON: {
      UNAUTHORIZED: '인증 정보가 유효하지 않습니다.',
      FORBIDDEN: '접근 권한이 없습니다.',
      JWT: {
        NO_TOKEN: '인증 정보가 없습니다.',
        NOT_SUPPORTED_TYPE: '지원하지 않는 인증 방식입니다.',
        EXPIRED: '인증 정보가 만료되었습니다.',
        NO_USER: '인증 정보와 일치하는 사용자가 없습니다.',
        INVALID: '인증 정보가 유효하지 않습니다.',
        ETC: '비정상적인 요청입니다.',
        DISCARDED_TOKEN: '폐기 된 인증 정보입니다.',
      },
    },
  },
  USER: {
    COMMON: {
      EMAIL: {
        BASE: '이메일은 문자열이어야 합니다.',
        EMAIL: '이메일의 형식이 올바르지 않습니다',
        REQUIRED: '이메일을 입력해주세요.',
        DUPLICATED: '이미 가입 된 사용자입니다.',
      },
      PASSWORD: {
        BASE: '비밀번호는 문자열이어야 합니다.',
        REQUIRED: 'You Should have to enter the password.',
        MIN: '비밀번호는 6자리 이상이어야 합니다.',
      },
      PASSWORD_CONFIRM: {
        BASE: '비밀번호 확인은 문자열이어야 합니다.',
        REQUIRED: 'You Should have to enter the passwordCheck.',
        MIN: '비밀번호는 6자리 이상이어야 합니다.',
      },
      NICKNAME: {
        BASE: '닉네임은 문자열이어야 합니다.',
        REQUIRED: '닉네임을 입력해주세요.',
      },
      REGION: {
        BASE: '지역명은 문자열이어야 합니다.',
        REQUIRED: '지역명을 입력해주세요.',
      },
      AGE: {
        BASE: '나이는 정수를 입력해주세요.',
        REQUIRED: '나이를 입력해주세요.',
      },
      GENDER: {
        BASE: '성별은 문자열이어야 합니다.',
        REQUIRED: '성별을 입력해주세요.',
        ONLY: '성별은 [MALE, FEMALE] 중 하나여야 합니다.',
      },
    },
    SIGN_UP: {
      EMAIL: {
        DUPLICATED: 'This email or nickname are already exist.',
        INCONSISTENT: 'Passwords do not match.',
      },
      SUCCEED: 'Sign-up succeed',
    },
    SIGN_IN: {
      SUCCEED: 'Sign-in succeed',
    },
    SIGN_OUT: {},
    TOKEN_REFRESH: {},
  },
  TRADE: {
    COMMON: {},
    CREATE: {},
    READ: {},
    UPDATE: {},
    DELETE: {},
  },
};

✏️ 로그인, 회원 가입 유효성 검사 구현

  • 로그인 유효성 검사
import Joi from 'joi';
import { MESSAGES } from '../../constants/message.constant.js';
import { PASSWORD_MIN_LENGTH, TLDS, MIN_DOMAIN_SEGMENTS } from '../../constants/auth.constant.js';

// 로그인 유효성 검사
export const signInValidator = async (req, res, next) => {
  try {
    const signInSchema = Joi.object({
      email: Joi.string()
        .email({ minDomainSegments: MIN_DOMAIN_SEGMENTS, tlds: { allow: TLDS } })
        .required()
        .messages({
          'string.base': MESSAGES.USER.COMMON.EMAIL.BASE,
          'string.empty': MESSAGES.USER.COMMON.EMAIL.REQUIRED,
          'string.email': MESSAGES.USER.COMMON.EMAIL.EMAIL,
          'any.required': MESSAGES.USER.COMMON.EMAIL.REQUIRED,
        }),
      password: Joi.string().min(PASSWORD_MIN_LENGTH).required().messages({
        'string.base': MESSAGES.USER.COMMON.PASSWORD.BASE,
        'string.min': MESSAGES.USER.COMMON.PASSWORD.MIN,
        'string.empty': MESSAGES.USER.COMMON.PASSWORD.REQUIRED,
        'any.required': MESSAGES.USER.COMMON.PASSWORD.REQUIRED,
      }),
    });
    await signInSchema.validateAsync(req.body);
    next();
  } catch (err) {
    next(err);
  }
};
  • 회원 가입 유효성 검사
import Joi from 'joi';
import { MESSAGES } from '../../constants/message.constant.js';
import { PASSWORD_MIN_LENGTH, TLDS, MIN_DOMAIN_SEGMENTS } from '../../constants/auth.constant.js';
import { USER_GENDER } from '../../constants/user-gender.constant.js';

// 회원가입 유효성 검사 Joi 스키마
export const signUpValidator = async (req, res, next) => {
  try {
    const signUpSchema = Joi.object({
      email: Joi.string()
        .email({ minDomainSegments: MIN_DOMAIN_SEGMENTS, tlds: { allow: TLDS } })
        .required()
        .messages({
          'string.base': MESSAGES.USER.COMMON.EMAIL.BASE,
          'string.empty': MESSAGES.USER.COMMON.EMAIL.REQUIRED,
          'string.email': MESSAGES.USER.COMMON.EMAIL.EMAIL,
          'any.required': MESSAGES.USER.COMMON.EMAIL.REQUIRED,
        }),
      nickname: Joi.string().required().messages({
        'string.base': MESSAGES.USER.COMMON.NICKNAME.BASE,
        'string.empty': MESSAGES.USER.COMMON.NICKNAME.REQUIRED,
        'any.required': MESSAGES.USER.COMMON.NICKNAME.REQUIRED,
      }),
      password: Joi.string().min(PASSWORD_MIN_LENGTH).required().messages({
        'string.base': MESSAGES.USER.COMMON.PASSWORD.BASE,
        'string.min': MESSAGES.USER.COMMON.PASSWORD.MIN,
        'string.empty': MESSAGES.USER.COMMON.PASSWORD.REQUIRED,
        'any.required': MESSAGES.USER.COMMON.PASSWORD.REQUIRED,
      }),
      passwordCheck: Joi.string().min(PASSWORD_MIN_LENGTH).required().messages({
        'string.base': MESSAGES.USER.COMMON.PASSWORD_CONFIRM.BASE,
        'string.min': MESSAGES.USER.COMMON.PASSWORD_CONFIRM.MIN,
        'string.empty': MESSAGES.USER.COMMON.PASSWORD_CONFIRM.REQUIRED,
        'any.required': MESSAGES.USER.COMMON.PASSWORD_CONFIRM.REQUIRED,
      }),
      region: Joi.string().required().messages({
        'string.base': MESSAGES.USER.COMMON.REGION.BASE,
        'string.empty': MESSAGES.USER.COMMON.REGION.REQUIRED,
        'any.required': MESSAGES.USER.COMMON.REGION.REQUIRED,
      }),
      age: Joi.number().required().messages({
        'number.base': MESSAGES.USER.COMMON.AGE.BASE,
        'number.empty': MESSAGES.USER.COMMON.AGE.REQUIRED,
        'any.required': MESSAGES.USER.COMMON.AGE.REQUIRED,
      }),
      gender: Joi.string()
        .valid(...Object.values(USER_GENDER))
        .required()
        .messages({
          'string.base': MESSAGES.USER.COMMON.GENDER.BASE,
          'string.empty': MESSAGES.USER.COMMON.GENDER.REQUIRED,
          'any.only': MESSAGES.USER.COMMON.GENDER.ONLY,
        }),
    });
    await signUpSchema.validateAsync(req.body);
    next();
  } catch (err) {
    next(err);
  }
};


📌 Tomorrow's Goal

✏️ 팀프로젝트 코드 구현하기

  • 사용자 스키마 수정하기

  • 상품, 상품 사진, 상품 댓글, 구매/판매 테이블 스키마 구현하기

  • 테이블 관계 정의하기



📌 Today's Goal I Done

✔️ S.A 작성하기

  • S.A 작성에서 ERD 작성이 가장 많은 시간을 잡아 먹었음

  • 기존의 과제보다 테이블의 수도 늘어나서 테이블 간의 관계도 복잡해짐

  • 특히 N:M의 관계를 구현하기 위해서 관계 테이블을 만들어서 관계를 형성함

  • 처음에는 명시적으로 관계 테이블을 구현할 계획이었지만 튜터님께서 암시적으로 구현이 가능하다고 말씀해주셔서 도전해볼 계획임



⚠️ 구현 시 발생한 문제

✔️ N:M 관계 vs 1:N 관계의 차이

  • ERD 작성을 하면서 사용자가 게시물에 좋아요를 누를 경우에 대해서 이야기가 나왔음

  • 제일 먼저 떠오른 관계 방식은 N:M 방식이었음

  • 1:N, N:1 관계를 통해서 N:M 관계를 만들어 냈음

  • 위와 같이 user와 trade의 id를 통해 사용자가 어떤 게시물들에 좋아요를 누르는지, 그리고 어떤 사용자들이 해당 게시물에 좋아요를 눌렀는지 알기 위한 관계를 생각했음

  • 위와 같이 관계를 연결함으로써 복잡한 연산이 되지만 관계가 존재하기 때문에 변경 사항에 대해 유연하게 대처가 가능해짐

  • 즉, 관계를 형성하면 종속성을 만들 수 있어서 수정 삭제 시 조금 더 편리해짐

  • 두 번째로 떠오른 방식은 그냥 그냥 명시적으로trade_like를 만들고 관계를 형성하지 않는 방식을 생각했음

  • 위와 같은 방식은 그냥 코드 상에서 parameter와 같은 request 값을 직접 가져와서 trade_like 테이블에 create 하는 방식임

  • 위와 같은 방식을 사용하면 복잡한 관계가 없기에 개발할 때는 편리함

  • 하지만 변경 사항이 발생하면 코드 자체를 고치는 경우가 많아지기 때문에 유지 보수 면에서 조금 떨어짐

  • 팀원들과의 회의에서 결론이 나지 않아서 튜터님이 도움으로 N:M 관계를 만드는 것으로 결정했음

  • 심지어 N:M 방식을 사용해서 trade_like라는 테이블을 직접 만드는 것이 아니라 암시적으로 만들 수 있다는 이야기를 들었음

  • 그래서 아래 참고 자료를 바탕으로 스키마를 구성함

  • https://dodote10.tistory.com/624

profile
조금씩 정리하자!!!

0개의 댓글