[2024.05.30 TIL] 내일배움캠프 32일차 (개인과제 해설 영상, 코드 리팩토링)

My_Code·2024년 5월 30일
0

TIL

목록 보기
42/112
post-thumbnail

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


💻 TIL(Today I Learned)

📌 Today I Done

✏️ 문자열들의 객체화

  • 기존 Joi의 message는 전부 직접 문자열을 작성했음

  • 그런데 튜터님께서 문자열들을 객체화시켜서 관리하는 방법을 알려주심

  • 생각보다 노가다였지만 이 방법을 사용하면 중복 문제도 해결되고 문자열을 바꾸더라도 하나씩 바꿔줄 필요가 없음

  • 즉, 상수들을 중앙화해서 관리하는 것임

// src/constants/message.constant.js

export const MESSAGES = {
    AUTH: {
        COMMON: {
            EMAIL: {
                BASE: '이메일은 문자열이어야 합니다.',
                EMAIL: '이메일의 형식이 올바르지 않습니다',
                REQUIRED: '이메일을 입력해주세요.',
                DUPLICATED: '이미 가입 된 사용자입니다.',
            },
            PASSWORD: {
                BASE: '비밀번호는 문자열이어야 합니다.',
                REQUIRED: '비밀번호를 입력해주세요.',
                PATTERN: '비밀번호가 형식에 맞지 않습니다. (영문, 숫자, 특수문자 포함 6~15자)',
            },
            PASSWORD_CONFIRM: {
                BASE: '비밀번호 확인은 문자열이어야 합니다.',
                REQUIRED: '비밀번호 확인을 입력해주세요.',
                PATTERN: '비밀번호 확인의 형식이 맞지 않습니다. (영문, 숫자, 특수문자 포함 6~15자)',
                INCONSISTENT: '입력 한 두 비밀번호가 일치하지 않습니다.',
            },
            NAME: {
                BASE: '이름은 문자열이어야 합니다.',
                REQUIRED: '이름을 입력해주세요.',
            },
            AGE: {
                BASE: '나이는 정수를 입력해주세요.',
                REQUIRED: '나이를 입력해주세요.',
            },
            GENDER: {
                BASE: '성별은 문자열이어야 합니다.',
                ONLY: '성별은 [MALE, FEMALE] 중 하나여야 합니다.',
            },
            PROFILE_IMAGE: {
                BASE: '프로필 사진은 문자열이어야 합니다.',
                REQUIRED: '프로필 사진을 입력해주세요.',
            },
            UNAUTHORIZED: '인증 정보가 유효하지 않습니다.',
            FORBIDDEN: '접근 권한이 없습니다.',
            JWT: {
                NO_TOKEN: '인증 정보가 없습니다.',
                NOT_SUPPORTED_TYPE: '지원하지 않는 인증 방식입니다.',
                EXPIRED: '인증 정보가 만료되었습니다.',
                NO_USER: '인증 정보와 일치하는 사용자가 없습니다.',
                INVALID: '인증 정보가 유효하지 않습니다.',
                ETC: '비정상적인 요청입니다.',
                DISCARDED_TOKEN: '폐기 된 인증 정보입니다.',
            },
        },
        SIGN_UP: {
            SUCCEED: '회원가입에 성공했습니다.',
        },
        SIGN_IN: {
            SUCCEED: '로그인에 성공했습니다.',
        },
        SIGN_OUT: {
            SUCCEED: '로그아웃 되었습니다.',
        },
        TOKEN_REFRESH: {
            SUCCEED: '토큰 재발급에 성공했습니다.',
        },
    },
    ...
};
// src/schemas/joi.schema.js

// 로그인 유효성 검사
export const signInSchema = Joi.object({
    email: Joi.string()
        .email({ minDomainSegments: 2, tlds: { allow: ['com', 'net', 'kr'] } })
        .required()
        .messages({
            'string.base': MESSAGES.AUTH.COMMON.EMAIL.BASE,
            'string.empty': MESSAGES.AUTH.COMMON.EMAIL.REQUIRED,
            'string.email': MESSAGES.AUTH.COMMON.EMAIL.EMAIL,
            'any.required': MESSAGES.AUTH.COMMON.EMAIL.REQUIRED,
        }),
    password: Joi.string().required().pattern(new RegExp('^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{6,15}$')).messages({
        'string.base': MESSAGES.AUTH.COMMON.PASSWORD.BASE,
        'string.empty': MESSAGES.AUTH.COMMON.PASSWORD.REQUIRED,
        'any.required': MESSAGES.AUTH.COMMON.PASSWORD.REQUIRED,
        'string.pattern.base': MESSAGES.AUTH.COMMON.PASSWORD.PATTERN,
    }),
});

✏️ Prisma 스키마 수정

  • 기존에 파스칼 케이스, 카멜 케이스가 혼용되었던 양식을 변경함

  • 테이블 구조 자체가 바뀌었기 때문에 기존에 있던 데이터는 다 날라감

// 변경 전
model Resumes {
  resumeId  Int      @id @default(autoincrement()) @map("resumeId")
  UserId    Int      @map("UserId") // Users 테이블을 참조하는 외래키
  title     String   @map("title")
  introduce String   @map("introduce") @db.Text
  state     String   @default("APPLY") @map("state")
  createdAt DateTime @default(now()) @map("createdAt")
  updatedAt DateTime @updatedAt @map("updatedAt")

  ResumeHistory ResumeHistories[] // 1개의 이력서에는 여러 개의 이력서 로그 기록이 존재 (1:N 관계 형성)

  // Users 테이블과의 관계 설정
  User Users @relation(fields: [UserId], references: [userId], onDelete: Cascade)

  @@map("Resumes")
}
// 변경 후
model Resume {
  resumeId  Int      @id @default(autoincrement()) @map("resume_id")
  UserId    Int      @map("user_id") // User 테이블을 참조하는 외래키
  title     String   @map("title")
  introduce String   @map("introduce") @db.Text
  state     String   @default("APPLY") @map("state")
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

  ResumeHistory ResumeHistory[] // 1개의 이력서에는 여러 개의 이력서 로그 기록이 존재 (1:N 관계 형성)

  // User 테이블과의 관계 설정
  User User @relation(fields: [UserId], references: [userId], onDelete: Cascade)

  @@map("resumes")
}

✏️ Prisma where절 컨트롤하기

  • 기존에는 where절을 역할에 따라 컨트롤하기 위해서 whereAND문법과 이중 삼항 연산을 이용해서 복잡하게 구현함

  • 튜터님이 짠 코드를 보니 코드는 길어지지만 가독성이 엄청 좋았음

  • where절에는 객체가 들어가기에 whereCondition이라는 객체를 만들어서 조건문을 통해서 값을 결정함

  • 그리고 sort 쿼리에 desc, acs 둘 다 아닐 경우에 대해서 처리하지 않아서 추가함

// 변경 전

// src/routers/resumes.router.js
// 이력서 목록 조회 API
router.get('/resumes', authMiddleware, async (req, res) => {
    // 사용자를 가져옴
    const user = req.user;
    // 정렬 조건을 req.query로 가져옴
    const sortType = req.query.sort.toLowerCase();
    // 필터링 조건을 가져옴
    const stateFilter = req.query.status.toUpperCase();

    const resumes = await prisma.resumes.findMany({
        where: {
            // AND 배열 연산을 통해서 필터링
            AND: [user.role === 'RECRUITER' ? {} : { UserId: +user.userId }, stateFilter === '' ? {} : { state: stateFilter }],
        },
        select: {
            resumeId: true,
            User: { select: { name: true } },
            title: true,
            introduce: true,
            state: true,
            createdAt: true,
            updatedAt: true,
        },
        orderBy: { createdAt: sortType },
    });

    return res.status(200).json({ status: 200, message: '이력서 목록 조회에 성공했습니다.', data: { resumes } });
});
// 변경 후

// src/routers/resumes.router.js
// 이력서 목록 조회 API
router.get('/', async (req, res) => {
    // 사용자를 가져옴
    const user = req.user;
    // 정렬 조건을 req.query로 가져옴
    let sortType = req.query.sort.toLowerCase();

    if (sortType !== 'desc' || sortType !== 'asc') {
        sortType = 'desc';
    }

    const whereCondition = {};
    // 채용 담당자인 경우
    if (user.role === USER_ROLE.RECRUITER) {
        // 필터링 조건을 가져옴
        const stateFilter = req.query.status.toUpperCase();

        if (stateFilter) {
            whereCondition.state = stateFilter;
        }
    }
    // 채용 담당자가 아닌 경우
    else {
        whereCondition.UserId = user.userId;
    }

    let resumes = await prisma.resume.findMany({
        where: whereCondition,
        include: {
            User: true,
        },
        orderBy: { createdAt: sortType },
    });

    resumes = resumes.map((resume) => {
        return {
            resumeId: resume.resumeId,
            userName: resume.User.name,
            title: resume.title,
            introduce: resume.introduce,
            state: resume.state,
            createdAt: resume.createdAt,
            updatedAt: resume.updatedAt,
        };
    });

    return res.status(HTTP_STATUS.OK).json({ status: HTTP_STATUS.OK, message: MESSAGES.RESUMES.READ.LIST.SUCCEED, data: { resumes } });
});


📌 Tomorrow's Goal

✏️ 팀프로젝트 설계 회의 진행하기

  • 드디어 팀프로젝트 발제가 되는 날

  • 주말과 공휴일이 팀프로젝트 기간 중에 있기에 생각보다 시간이 촉박함

  • 그렇기에 주말, 공휴일을 따지지 않고 작업을 진행할 필요가 있어 보임

  • 일단 발제 문서는 나왔기에 간단하게 기능별로 역할을 나눠봄

    1. 회원가입, 이메일 인증, 프로필 관리
    2. 로그인, 인증, 인가, 로그아웃
    3. 게시물 CRUD, 뉴스피드 (get, 필요하면
      recruiter 같은 관리자 권한일 때도)
    4. 댓글, 댓글 좋아요 / 취소
    5. 미들웨어, 조이, DB connect, 라우터관리,
      schema(prisma), app.js 관리, 패키지 관리,
      상태코드, constants 관리

✏️ SQL 강의 시청하기

  • 계속해서 Node.js 강의와 예비군 개인 과제 등등으로 너무 바쁜 시간을 보냄

  • 그래도 개인과제 해설이 끝나면 약간의 시간을 이용해서 SQL 강의 시청하기



📌 Today's Goal I Done

✔️ 개인과제 해설 영상 시청

  • 해설 강의를 통해서 문자열이나 숫자 같은 상수들을 관리하는 법을 알게 되었음

  • 기존에는 Joi에서 사용하는 message에 문자열을 직접 넣었음

  • 튜터님께서 공통으로도 사용되는 문자열들을 관리하기 위해서 그러한 문자열들을 객체로 관리하도록 코드를 구현하는게 유지보수에 좋다고 했음

  • 그리고 Prisma의 where절의 조건을 역할에 따라 다르게 적용하는 법 역시 생각도 못한 방법이었기에 굉장히 유용했음



⚠️ 구현 시 발생한 문제

✔️ 리눅스 서버에 Prisma 스키마 변경하고 데이터베이스 실행 안됨

  • 기존에 데이터베이스에서 파스칼 케이스, 카멜 케이스를 혼용에서 사용함

  • 그래서 해설 강의를 기반으로 데이터베이스를 싹 뜯어 고침

  • 데이터가 날라가는 건 어쩔 수 없다고 생각하고 Prisma 스키마를 수정함

  • 전부 수정 후 리눅스 서버에서도 git pull로 최신화 함

  • 그러고 코드를 수정하니 위와 같은 에러가 발생함

  • 분명 제대로 수정된 스키마로 최신화 했고, DB도 바뀐 컬럼으로 적용되었는데 똑같은 에러가 발생했음

  • 로컬에서 진행했던 과정을 다시 살펴보니 정말 간단한 이유였음

  • 바로 npx prisma db push를 리눅스 서버에서 실행시켜주지 않아서 발생한 문제였음

  • 근데 생각해보면 npx prisma db push 를 통해서 DB가 업데이트 되었는데 리눅스 서버에서도 이 과정이 필요한 걸까?

  • 로컬 서버, 리눅스 서버 모두 AWS의 RDS에 연결되어 있는데?

  • 인터넷과 Chat-GPT를 사용해서 이유를 찾아봤지만 뚜렷한 해답은 없었음

  • 인터넷과 Chat-GPT에서 말하는 이유는 다음과 같음

    • Prisma 클라이언트 라이브러리 버전 불일치
    • 캐시된 스키마 파일 사용
    • 코드 또는 환경 설정 누락
    • 리눅스 서버의 Prisma 스키마 동기화 지연
  • 결론, 같은 클라우드 DB를 사용해서 위와 같은 에러가 발생하면 리눅스 서버에서도 한 번 더 동기화 시켜주는 게 좋음

  • 그리고 동기화 작업 시 npx prisma db push 보다는 npx prisma migrate 명령이 더 좋다고 함

  • (추가) 팀원분께서 비슷한 상황에 대해서 말씀해 주셨음

  • https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/generating-prisma-client

  • 스키마 같은 경우 node_module 밑에 Prisma client에 있는데 여길 통해서 쿼리 작업이 진행됨

  • 그런데 단지 pull를 통해서 최신을 받아오면 node_module은 받아오지 않기 때문에 쿼리 작업을 담당하는 Prisma client는 리눅스 서버에서 최신화되지 않는다는 이야기 같음

profile
조금씩 정리하자!!!

0개의 댓글