본 내용은 내일배움캠프에서 활동한 내용을 기록한 글입니다.
1) 프로젝트 아이디어 구상하기
2) 와이어프레임
3) ERD DIAGRAM
4) GitHub Rules
git commit rule.
이슈 번호 - 브랜치 연결
모든 사항 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)
{
"printWidth": 100,
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"arrowParens": "always"
}
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);
}
};
사용자 스키마 수정하기
상품, 상품 사진, 상품 댓글, 구매/판매 테이블 스키마 구현하기
테이블 관계 정의하기
S.A 작성에서 ERD 작성이 가장 많은 시간을 잡아 먹었음
기존의 과제보다 테이블의 수도 늘어나서 테이블 간의 관계도 복잡해짐
특히 N:M의 관계를 구현하기 위해서 관계 테이블을 만들어서 관계를 형성함
처음에는 명시적으로 관계 테이블을 구현할 계획이었지만 튜터님께서 암시적으로 구현이 가능하다고 말씀해주셔서 도전해볼 계획임
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라는 테이블을 직접 만드는 것이 아니라 암시적으로 만들 수 있다는 이야기를 들었음
그래서 아래 참고 자료를 바탕으로 스키마를 구성함