Code Monkey home page Code Monkey logo

runnerbe-server's People

Contributors

dependabot[bot] avatar great-park avatar seungwanryu01 avatar

Stargazers

 avatar  avatar  avatar

Forkers

great-park

runnerbe-server's Issues

ERR_HTTP_HEADERS_SENT

Describe the bug
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client

Screenshots
image

Push alarm 작업 진행 사항

요구사항

image

image

목록

  1. 닉네임 변경 요청 : 회원가입 이후 발송 (0)
  2. 작성자에게 참여 요청 전달 : 누군가 작성자 글에 참여 요청했을 때(0)
  3. 출석 체크 요청 : 모임이 시작되고 나서 발송 *
  4. 출석 체크 완료 : 출석 완료 시 발송(0)
  5. 참여 요청 승인 : 요청한 모임에서 받아줬을 때 발송(0)
  6. 참여 요청 거절 : 요청한 모임에서 거절했을 때 발송(0)
  7. 출석 체크 미완료 : 모임 이후 특정 시간이 지나도 출석 체크 안 했으면 발송 *
  8. 신고 접수되어 정지 : 신고로 인해 관리자가 정지시키면 발송(0)
  9. 메시지 도착 : 채팅에서 메시지 알림 *
  10. 모임 취소 : 모임 취소시 발송 *

현재 3,7,9,10을 제외하면 현 상태에서 개발 가능할 것으로 보인다.

공식 문서를 따라 push alarm을 위한 기본 설정을 진행하고, 필요한 상황에 맞춰서 push alarm을 발송하는 메소드를 호출할 계획이다.
추가로 트랜잭션 처리도 함께 진행할 계획이다.

메인 페이지

요구사항

image

Data

  1. 사용자 위도, 경도(거리순 필터)
  2. 출근 전, 퇴근 후 휴일 테그 존재 -> q.s로 처리 <req.query.runnungTag>
  3. 러너비 가이드 -> 고정 값이므로 프론트에서 처리
  4. 게시글 목록들
  • 글제목
  • 작성자 프로필 사진
  • 작성자 닉네임
  • 러닝 모임 시간
  • 장소 - 행정명
  • 모집 러너 성별
  • 연령대 -작성자가 설정
  • 러너들 직군 코드 -> Running table과 join
  • 마감 여부 (마감 포함)처리
  • 러닝 장소 위도, 경도 (거리순 필터)처리
  • 글 생성시간 (최신순 필터)처리
  • 찜 개수 (찜 많은 순)처리

query string 설계

  1. Controller나 Provider에서 분기 처리하는 방안
  2. Dao단에서 쿼리 parameter를 변수로 처리해서 한 번에 가기

필터 처리
A. 거리순 - 사용자(위도, 경도)[req.body]와 게시글 러닝 모임장소(위도, 경도) => km 환산 거리 계산 쿼리 작성
B. 최신순 - Posting createdAt 정렬
C. 찜많은순 - Bookmark table과 join
D. 마감 포함 여부 - query string , Running table과 join

Header : jwt
body : userLongitude, userLatitude
query string(현안- 1번): whetherEnd(Y, N), Filter_bookMark(Y, N), Filter_time(Y, N), Filter_distance(Y, N)
=> 좀 더 공부해보고 개선할 것.

node-schedule 관련 작업

방향
Date-based Scheduling로 진행

Say you very specifically want a function to execute at 5:30am on December 21, 2012. Remember - in JavaScript - 0 - January, 11 - December.

const schedule = require('node-schedule');
const date = new Date(2012, 11, 21, 5, 30, 0);

const job = schedule.scheduleJob(date, function(){
  console.log('The world is going to end today.');
});

To use current data in the future you can use binding:

const schedule = require('node-schedule');
const date = new Date(2012, 11, 21, 5, 30, 0);
const x = 'Tada!';
const job = schedule.scheduleJob(date, function(y){
  console.log(y);
}.bind(null,x));
x = 'Changing Data';

This will log 'Tada!' when the scheduled Job runs, rather than 'Changing Data', which x changes to immediately after scheduling.

활용

  1. 게시글 작성자에게 러닝 모임 시간에 맞춰서 출석 관리에 대한 푸시 알림 전송
  2. 출석 관리 가능 시간이 끝나면 출석 완료를 검사하여 미완료시 무효처리

reference
https://www.npmjs.com/package/node-schedule
https://github.com/node-schedule/node-schedule

베너 수정사항 및 게시글 삭제 오류 해결

변경 사항

image
image

게시글 삭제 오류 사항 발견

참석자 프로필 사진의 리스트를 넘겨야 하는데, 각 postId에 따라서 해당 리스트를 연결되도록 해야 한다.
ERD를 참고하면, postId로 해당 모임인 gatheringId를 가져온다. 이후 RunningPeople에서 맵핑되는 userId를 모두 가져와서 User 테이블과 join 후 프로필 사진 Url을 가져오도록 했다.
그 과정에서

select U.userId as userId, profileImageUrl from Posting
inner join Running R on Posting.postId = R.postId
inner join RunningPeople RP on R.gatheringId = RP.gatheringId
inner join User U on RP.userId = U.userId
where R.postId = ?;

해당 쿼리를 작성했는데, 일부 postId에서 빈 값으로 반환되는 경우가 있었다. 이는 맵핑되는 gatherId가 실제로 Running에 존재하지 않는 경우였다.
즉, 게시글 삭제 과정이나 생성 과정에서 DB 수정이 누락된 것이다.

RunningPeople

image

Running

image
53번 모임에 대해 신청한 사람들이 3명 있었는데, 나중에 해당 게시글을 삭제하였고, 이때 Running과 Posting 테이블에만 그 결과가 반영되고 RunningPeople은 그대로 남아 있는 것이다.
따라서 게시글 삭제 API에 문제가 있음을 추측할 수 있었다.

try {
    //게시글 있는지 확인
    const checkPostingResult = await postingProvider.checkPosting(postId);
    if (checkPostingResult[0].length == 0)
      return errResponse(baseResponse.POSTING_NOT_VALID_POSTID);

    const connection = await pool.getConnection(async (conn) => conn);
    // 게시글 수정
    const dropPostingResult = await postingDao.dropPosting(connection, postId);
    connection.release();
    return response(baseResponse.SUCCESS);
  } catch (err) {
    logger.error(`App - dropPosting Service error\n: ${err.message}`);
    return errResponse(baseResponse.DB_ERROR);
  }

개발 당시 급하게 만들다 보니 단순히 Posting 테이블만 결과를 반영하였고, 이로 인해 문제가 발생한 것이다.
비록 배포 전 데이터이지만, 테스트 과정에서 혼돈이 생길 수 있기 때문에 오류 데이터들은 모두 삭제해 주었다.

각 테이블에 대해 삭제 쿼리를 구성하고 트랜잭션 처리를 하면 될 것 같다.

작업 사항

  1. 게시글 삭제 API에서 Running, RunningPeople 테이블에도 작업 결과 반영시키도록 수정
  2. 메인 화면 API 프로필 사진 리스트 추가
  3. 게시글 상세페이지 API 프로필 사진 리스트 추가
  4. 마이페이지 API 프로필 사진 리스트 추가
  5. 찜 목록 API 프로필 사진 리스트 추가

accessToken verification

   try {
        let kakao_profile;

        try {
            kakao_profile = await axios.get("https://kapi.kakao.com/v2/user/me", {
                headers: {
                    Authorization: `Bearer ${accessToken}`,
                    "Content-Type": "application/json",
                },
            });
        } catch (err) {
            return res.send(errResponse(baseResponse.ACCESS_TOKEN_IS_NOT_VALID)); // 2085
        }

        const uuid = kakao_profile.id;

-> kakao_profile is undefinded

Transaction 처리 진행 사항

요구사항

여러 테이블에 걸쳐 특정 작업을 한 번에 진행되어야 하는 경우, 중간에 작업이 실패하게 되면 다시 rollback 해야 한다.

예시로, 게시글 삭제를 할 때 이전 이슈와 같이 Posting 테이블에서만 삭제할 것이 아니라 Running, RunningPeople 모두 삭제 작업을 한번에 진행해야 한다.

그래서 transaction을 적용하기로 하였고, 다음과 같이 구조를 새로 설계하였다.

postingService - dropPosting

// 게시글 삭제
exports.dropPosting = async function (postId) {
  // 먼저 pool 생성해서 catch문에서 rollback하도록
  const connection = await pool.getConnection(async (conn) => conn);
  try {
    //게시글 있는지 확인
    const checkPostingResult = await postingProvider.checkPosting(postId);
    if (checkPostingResult[0].length == 0)
      return errResponse(baseResponse.POSTING_NOT_VALID_POSTID);

    connection.beginTransaction(); // 트랜잭션 적용 시작

    // 게시글 수정 - Posting, Running, RunningPeople
    const dropPostingResult = await postingDao.dropPosting(connection, postId);

    await connection.commit(); // 성공시 commit

    return response(baseResponse.SUCCESS);
  } catch (err) {
    await connection.rollback(); // 실패시 rollback
    logger.error(`App - dropPosting Service error\n: ${err.message}`);
    return errResponse(baseResponse.DB_ERROR);
  } finally {
    connection.release();
  }
};

기존에는 try 문 내에서 connection pool을 생성해서 connection을 가져왔고, 그 안에서 해제하였다.
하지만 transaction을 적용하기 위해서는, 에러 발생시 rollback을 시켜야 하고, 이를 위해서는 catch문 안에 rollback 메소드를 호출해야 한다.
따라서 위와 같이 connection pool을 블록 외부에서 생성하고, finally 문에서 해제하도록 한다.
그러면, 호출한 dao에서는 3가지 삭제 작업을 진행하게 될 것이고, 이때 어떤 이유에서 작업이 중단된다면, err response를 던질 것이다.
이때 catch 문 내 rollback 메소드가 호출되면서 DB는 다시 삭제되기 이전 상태로 돌아갈 것이다.
반대로, 아무 에러없이 잘 작동한다면 commit 메소드가 호출되면서 작업 결과가 DB에 반영될 것이다.

게시글 삭제를 비롯하여 모든 API에 대하여 트랜잭션 처리를 진행한다.

Cannot set properties of undefined

Describe the bug

{"level":"error","message":"User-getMain2Login Provider error: Cannot set properties of undefined (setting 'profileUrlList')","timestamp":"2022-07-29 01:57:06"}

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.
image

Desktop (please complete the following information):

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Smartphone (please complete the following information):

  • Device: [e.g. iPhone6]
  • OS: [e.g. iOS8.1]
  • Browser [e.g. stock browser, safari]
  • Version [e.g. 22]

Additional context
Add any other context about the problem here.

메인페이지 - 필터링 검색, 키워드 검색 API 통합하기

문제점 : 필터 검색, 키워드 검색 화면 이후에도 기존 메인페이지의 정렬 조건을 그대로 수행해야 함

메인페이지 API에서 필터링 조건을 추가하여 API를 통합해야 한다.

  1. distanceFilter (0)
    N : 설정 x -> 거리로 필터링 x , 기본 메인화면의 경우에 해당
    value : 입력 받은 값(Km)만큼 거리로 필터링

  2. 성별 필터 genderFilter (0)
    A : 전체, F : 여성, M : 남성
    빈값확인, 유효성확인

  3. 연령대 필터
    최소 연령대
    최대 연령대
    두 가지를 받고 검증 후 쿼리에 추가

  4. 직군필터
    mysql INSTR 함수 사용
    AND INSTR(J.job, ?) > 0

  5. 키워드 필터
    INSTR 함수 사용

MySQL Event Scheduler 관련 현황

  • 매일 자정 유저들의 출석률을 업데이트하는 이벤트 스케줄러 설정
create definer = park@`%` event update_attendance on schedule
    every '1' DAY
        starts '2022-02-27 09:38:24'
    enable
    comment '출석률 업데이트'
    do
    UPDATE User U
INNER JOIN (SELECT userId, (SUM(attendance)/COUNT(gatheringId))*100 as attendance FROM RunningPeople GROUP BY userId) A
ON A.userId = U.userId
SET diligence = A.attendance
WHERE A.userId = U.userId;
  • 매일 새벽 3시 마감된 게시글은 메인 페이지에서 보이지 않도록 status 변경 필요
    현재 마감 처리는 Running 테이블의 whetherEnd = 'Y' 으로 처리 중
    러너비 서비스에서 러닝 모임이 마감되더라도 일정 시간 동안은 노출되는 것으로 기획하였으므로 마감이 되었다고 바로 메인페이지에서 제외할 수 없다.
    따라서 매일 새벽 3시에 whetherEnd = 'Y'인 게시글을 whetherEnd = 'D'로 변경시키고, 메인 페이지 API에서는 D인 게시글을 기본적으로 불러오지 않도록 처리한다.
create definer = park@`%` event update_whetherEnd on schedule
    every '1' DAY
        starts '2022-03-04 03:00:00'
    enable
    comment '마감 게시글 삭제 상태로 업데이트'
    do UPDATE Running SET whetherEnd = 'D' WHERE whetherEnd = 'Y';
create definer = park@`%` event delete_posting on schedule
    every '1' DAY
        starts '2022-03-04 03:00:00'
    enable
    comment '마감된 게시글 비노출'
    do
    UPDATE Posting
    SET status = 'D'
    WHERE status = 'C';

게시글 상세페이지

요구사항

image

글 작성자/ 비작성자 구분 필요
-> postId로 repUserId 갖고 와서 비교 후 response code로 구분하기

Data

  1. 러닝태그
  2. 글제목
  3. 러닝 모임 시간
  4. 러닝 소요 시간
  5. 러너 성별
  6. 연령대
  7. 최대 인원수
  8. 자유내용
  9. 모임 장소 경도
  10. 모임 장소 위도
  11. 참여 러너 정보
  • 닉네임
  • 성별
  • 연령대 - xx대 초반,중반,후반
  • 성실도 - 불량러너(1 - 32), 노력러너(33 - 65), 성실러너(66 - 100)
  • 직군 코드

Header : jwt
body :
path variable : postId, userId
query string :

code : 1003 = 성공, 작성자,
1004 = 성공, 비작성자

jwt 관련 논의 정리

  1. jwt 검증 API 추가
    현재 직장인 인증 시스템은 이메일, 사원증 인증 두 가지.
    이메일 인증의 경우 회원가입 api 요청 당시 인증을 완료한 반면,
    사원증 인증의 경우 관리자가 승인하기까지의 시간차가 발생하므로 회원가입 api 요청 당시 인증이 완료되지 않은 상태이다.
    즉, 회원 인증이 완료되지 않은 상태에서 jwt를 발급받아 마치 인증받은 회원인 것처럼 다른 API를 사용할 수 있는 상황이다.
    따라서 User 테이블의 status 컬럼을 활용하여 인증 여부를 관리하고 앱 실행 시 jwt를 통해 userId를 얻어 쿼리를 통해 유저의 인증 여부를 확인하는 API를 호출하여 위에서 언급한 두 가지 경우 모두 처리할 수 있도록 할 계획이다.

단, 토큰을 반환값으로 주는 것은 보안상 위험하므로 철회한다. (외부에서 해당 API로 userId 넣어서 jwt발급할 가능성)
소셜 로그인과 같은 복잡한 절차를 걸쳐야만 발급하도록 한다.
jwt의 유효기간이 1년으로 충분히 긴 기간동안 유지되므로, 만료됐다면 재로그인

  1. 회원 가입 API
    -> 메일 인증인 경우는 쿼리 동일
    -> 사진 인증의 경우 쿼리에서 status를 W로 변경하는 부분 수정
    *추후 Admin API로 사진 인증 유저 관리
    (User.status : W->Y)

  2. 로그인 API
    uuid 확인
    a. 없다 -> 회원가입 가능 응답
    b. 있다 ->
    *and status == Y -> 로그인 성공, jwt 발급
    *and status == W -> 인증 대기중, 둘러보기(회원가입 요청 X)

내일 직접 개발해보고 내용 추가 예정

쪽지

요구사항

image
image
image
image

  1. 쪽지로 참여신청(대화방 생성)
  2. 대화방 목록 (*최근 대화내용 -> 게시글 제목)
  3. 대화방 상세페이지
  4. 쪽지 보내기
  5. 참여신청 처리(수락/거절)
  6. 쪽지 신고
  7. 쪽지 삭제

전략

1.쪽지 목록
내가 속한 room 정보
그 room에서 수신자 인덱스를 상대방 id로 가져오고
상대방 id로 상대방 유저 정보 가져오기

요청하는 userId로 쿼리 조건문에서
WHERE senderId = ? or receiverId = ?
이렇게 하면 채팅방 정보와 별개로 내가 속한 모든 대화방 조회 가능

async function getMessageList(connection, userId) {
  const query = `
  SELECT R.roomId, title, counterId, nickName, profileImageUrl
  FROM Room R
  INNER JOIN Posting P on R.postId = P.postId
  INNER JOIN (SELECT roomId, case when receiverId = ${userId} then senderId
              when senderId = ${userId} then receiverId
      end as counterId
  FROM Room R
  INNER JOIN Posting P on R.postId = P.postId
  WHERE senderId = ${userId} or receiverId = ${userId}) D on D.roomId = R.roomId
  INNER JOIN User U on U.userId = D.counterId
  WHERE senderId = ${userId} or receiverId = ${userId};
                          `;

  const row = await connection.query(query);

  return row;
}

2.쪽지방 상세 페이지에서
myUserId = req.params.userId 전달받았을때
myUserId == 발신자 Id -> 내가 보낸 쪽지(왼쪽)
myUserId == 수신자 Id -> 상대방이 내게 보낸 쪽지(오른쪽)

내가 반장인지에 따라 result code 구분해서 응답 보내기 (다른 화면 구성)

읽음 처리하기 -> 쪽지 상세 페이지를 호출할 때 마다 room에 속한 모든 message의 읽음 여부를 Y로 처리하면,
쪽지를 마지막에 보낸 사람 입장에서는 현재 수신된 모든 쪽지를 읽었으니 ok
쪽지를 열어보는 사람 입장에서도 상세 페이지를 호출하면서 모든 쪽지를 읽음 처리

3.방 생성시 발신자, 수신자 인덱스 삽입
postId, jwt 주면
postId로 repUserId뽑아서 수신자로, 요청하는 userId 발신자로 삽입

이때 RunnungPeople에서 발신자의 userId가 있고 and whetherAccept가 D이면 신청 불가능
(게시글 상세페이지에서 참여 러너 수는 수락 여부가 Y인 경우만 가져오도록 조치함)

  1. 참여신청처리
    roomId로 반장 Id 뽑아내고jwt로 검증
    거절 : sendId에 맞는 RunningPeople 테이블에 참여 러너의 수락 여부를 D로 변경
    수락 : "" 수락 여부를 Y로 변경

  2. 쪽지 보내기
    수신자의 userId를 가져오는게 문제.
    roomId와 senderId
    해당 roomId로 sender와 receiver id를 차례로 비교하여, 일치하는 경우의 반대의 id를 가져와서 수신자 Id로 사용
    일치하는 경우가 없다면 오류 뱉기

// receiverId 생성 절차
    const senderParams = [roomId, senderId];
    const checkEqualSender = await messageDao.checkSender(
      connection,
      senderParams
    );
    const checkEqualReceiver = await messageDao.checkReceiver(
      connection,
      senderParams
    );
    connection.release();
    //반대로 전달
    if (checkEqualSender.length > 0) {
      const receiverId = await messageDao.getReceiver(connection, senderParams);
      return receiverId;
    } else if (checkEqualReceiver.length > 0) {
      const receiverId = await messageDao.getSender(connection, senderParams);
      return receiverId;
    } else {
      return errResponse(baseResponse.MESSAGE_NOT_MATCH_USERID);
    }

1. 쪽지로 참여신청(대화방 생성)

Header : JWT
Path variable : postId, userId

2. 쪽지 목록

Header : JWT
Path variable : userId

3. 대화방 상세 페이지

Header : JWT
Path variable : roomId, userId

4. 쪽지 보내기

Header : JWT
body : content
Path variable : roomId, userId

5. 참여 신청 처리

Header : JWT
Path variable : roomId, senderId

Can't add new command when connection is in closed state 0 at PromisePoolConnection.beginTransaction

Describe the bug
Issue about connection pool (and maybe nginx load balancing)

To Reproduce
Steps to reproduce the behavior:
connection pool 설정을 수정하여 해결 시도
mysql2 npm document

declare namespace Pool {

    export interface PoolOptions extends Connection.ConnectionOptions {
        /**
         * The milliseconds before a timeout occurs during the connection acquisition. This is slightly different from connectTimeout,
         * because acquiring a pool connection does not always involve making a connection. (Default: 10 seconds)
         */
        acquireTimeout?: number;

        /**
         * Determines the pool's action when no connections are available and the limit has been reached. If true, the pool will queue
         * the connection request and call it when one becomes available. If false, the pool will immediately call back with an error.
         * (Default: true)
         */
        waitForConnections?: boolean;

        /**
         * The maximum number of connections to create at once. (Default: 10)
         */
        connectionLimit?: number;

        /**
         * The maximum number of connection requests the pool will queue before returning an error from getConnection. If set to 0, there
         * is no limit to the number of queued connection requests. (Default: 0)
         */
        queueLimit?: number;

        /**
         * Enable keep-alive on the socket.  It's disabled by default, but the
         * user can enable it and supply an initial delay.
         */
        enableKeepAlive?: true;

        /**
         * If keep-alive is enabled users can supply an initial delay.
         */
        keepAliveInitialDelay?: number;
    }
}

Screenshots
If applicable, add screenshots to help explain your problem.

image

Desktop (please complete the following information):

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Smartphone (please complete the following information):

  • Device: [e.g. iPhone6]
  • OS: [e.g. iOS8.1]
  • Browser [e.g. stock browser, safari]
  • Version [e.g. 22]

Additional context
Add any other context about the problem here.
connection 가져올 때 좀 더 유연한 정책을 적용할 예정

회원가입 data insert

image

  1. 코드 수정 이후로 데이터가 엉뚱한 자리로 들어감
  2. 개인 정보 암호화 부분이 작동 x

Dependency Dashboard

This issue provides visibility into Renovate updates and their statuses. Learn more

This repository currently has no open or pending branches.


  • Check this box to trigger a request for Renovate to run again on this repository

jwt 회원 인증 여부 확인

추가 개발 사항

사원증 인증으로 회원가입 했을 때, 관리자가 승인 완료 후 사용자가 어플에 최초 접속했을 때 "인증이 완료되었습니다"와 같은 안내 문구를 띄워야 함. 이를 위해서 사원증 인증 승인 이후 최초 접속한 경우에 대한 result code를 추가해야함
승인 이후 최초 접속한 것을 어떤 방식으로 구분해야 될 지 좀 더 고민해봐야함

error [err_http_headers_sent]: cannot set headers after they are sent to the client

Describe the bug
A clear and concise description of what the bug is.
`
node:internal/errors:465
ErrorCaptureStackTrace(err);
^

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at new NodeError (node:internal/errors:372:5)
at ServerResponse.setHeader (node:_http_outgoing:576:11)
at ServerResponse.header (C:\Users\LG\Desktop\gitProject\RunnerBe-Server\node_modules\express\lib\response.js:794:10)
at ServerResponse.send (C:\Users\LG\Desktop\gitProject\RunnerBe-Server\node_modules\express\lib\response.js:174:12)
at ServerResponse.json (C:\Users\LG\Desktop\gitProject\RunnerBe-Server\node_modules\express\lib\response.js:278:15)
at ServerResponse.send (C:\Users\LG\Desktop\gitProject\RunnerBe-Server\node_modules\express\lib\response.js:162:21)
at exports.getPosting2 (C:\Users\LG\Desktop\gitProject\RunnerBe-Server\src\app\Posting\postingController.js:467:11)
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
code: 'ERR_HTTP_HEADERS_SENT'
}
`
Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Smartphone (please complete the following information):

  • Device: [e.g. iPhone6]
  • OS: [e.g. iOS8.1]
  • Browser [e.g. stock browser, safari]
  • Version [e.g. 22]

Additional context
Add any other context about the problem here.

쪽지 구현 X ver

02/20 회의 결과 쪽지 기능 삭제 결정

image
쪽지와 참여신청 관련 13 ~~ 17번 API는 CMC 이후 리펙토링에서 사용할 수 있으니 남겨두고, 변경된 기획에 따라서 새로운 API 설계 필요

  1. 참여 신청
    RunningPeople에 해당 인원 삽입 + 푸시알림
    (해당 모임의 수락 여부가 Y인 사람 수와 최대 인원수 비교 검증 추가)
  2. 게시글 상세페이지에서 작성자가 러너들의 신청을 처리할 수 있음
    -> 작성자 일 때 게시글 상세페이지에서 신청하고 수락 대기중인 러너들을 불러오기
  3. 참여 신청 처리 <수락, 거절> query String으로 구분 값 전달

기획 확정된 이후 개발

키워드 검색 오류

Describe the bug
메인페이지에서 키워드 검색 조건이 제대로 전달이 안된다.
키워드 검색 관련 코드를 걷어내면 응답은 정상

관련 핵심 코드

컨트롤러 중

const keywordSearch = req.query.keywordSearch; // N : 필터 x, 그 외 키워드 검색

let keywordCondition = "";
  if (keywordCondition != "N") {
    keywordCondition += `AND INSTR(P.title, '${keywordSearch}') > 0 OR INSTR(P.contents, '${keywordSearch}') > 0`;
  }

Dao 중

WHERE runningTag = "${runningTag}" ${distanceCondition}
  ${whetherEndCondition} ${genderCondition} ${jobCondition} ${ageCondition} ${keywordCondition}

nginx loadbalancing

cluster 에 참여하는 서버 정보와 포트를 upstream 지시자로 설정하며 첫 번째 설정한 서버가 우선적으로 응답을 처리함.

/etc/nginx/sites-available/default


upstream node_proxy {
  server localhost:3000;
  server localhost:3001;
}
server {

  (...)

  location / {
    proxy_pass http://node_proxy;
  }
}

=> 가중치 없음, Round robin(RR) 형식의 load balancing 진행

netstat -tlnp result

image

reference

https://www.lesstif.com/system-admin/nginx-load-balancing-35357063.html

logging 작업

https://github.com/winstonjs/winston

Usage
The recommended way to use winston is to create your own logger. The simplest way to do this is using winston.createLogger:

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  defaultMeta: { service: 'user-service' },
  transports: [
    //
    // - Write all logs with importance level of `error` or less to `error.log`
    // - Write all logs with importance level of `info` or less to `combined.log`
    //
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
  ],
});

//
// If we're not in production then log to the `console` with the format:
// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
//
if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple(),
  }));
}

최근에 504 error가 일어나 서버가 재시작된 것을 발견하였는데, logging을 꼼꼼히 처리하지 않아서 응답이 지연된 원인을 찾지 못했다.
우선 error 발생 가능한 구간은 모두 try catch 문을 통해 예외처리를 실시하여 서버가 터지지 않도록 한다.
설령 막지 못하더라도 pm2에 의해 재시작 되는데, 이때 디버깅을 위해서 log를 남겨야 한다.
따라서 winston을 사용하여 log를 남긴다.
예시)

  try {
    //start Transaction
    connection.beginTransaction();
    const patchPostingParams = [
      title,
      gatheringTime,
      runningTime,
      gatherLongitude,
      gatherLatitude,
      locationInfo,
      runningTag,
      ageMin,
      ageMax,
      peopleNum,
      contents,
      runnerGender,
      postId,
    ];
    //게시글 있는지 확인
    const checkPostingResult = await postingProvider.checkPosting(postId);
    if (checkPostingResult.length === 0)
      return errResponse(baseResponse.POSTING_NOT_VALID_POSTID);

    // 게시글 수정
    const patchPostingResult = await postingDao.patchPosting(
      connection,
      patchPostingParams
    );

    //commit
    await connection.commit();
    return response(baseResponse.SUCCESS);
  } catch (err) {
    //rollback
    await connection.rollback();
    logger.error(`App - patchPosting Service error\n: ${err.message}`);
    return errResponse(baseResponse.DB_ERROR);
  } finally {
    connection.release();
  }

기존에는 try catch과 더불어 log를 남기는 코드가 부분적으로 적용되었는데, 전체 소스 코드를 검토하여 모두 반영하도록 작업을 진행한다.

직군 변경 버그

3개월 확인 처리가 제대로 되지 않음

다른 validation이 작동하기 이전에 checkTerm에서 문제가 됨

-> 쿼리에서 User의 updatedAt을 기준으로 날짜를 계산했기 때문에 직군 변경이 아니라 다른 요소에도 영향을 받아 문제가 된 것

Admin web

요구사항
Runnerbe 서비스의 악성 유저를 차단하기 위한 신고 관리 페이지 개발

  • 관리자 로그인 페이지
  • 게시글 신고 관리 페이지
  • 채팅 신고 관리 페이지

Describe the solution you'd like
bootstrap, javascript

Additional context
Add any other context or screenshots about the feature request here.

Ex) 개발 진행중인 페이지 - 더미데이터 추가
image

메인페이지 쿼리 관련

  SELECT P.postId, P.createdAt as postingTime, postUserId, U.nickName, U.profileImageUrl, title,
          case when date_format(gatheringTime, '%w') = 0
          then date_format(gatheringTime,'%m/%d(일) %p%l:%i')
          else
          case when date_format(gatheringTime, '%w') = 1
          then date_format(gatheringTime,'%m/%d(월) %p%l:%i')
          else
          case when date_format(gatheringTime, '%w') = 2
          then date_format(gatheringTime,'%m/%d(화) %p%l:%i')
          else
          case when date_format(gatheringTime, '%w') = 3
          then date_format(gatheringTime,'%m/%d(수) %p%l:%i')
          else
          case when date_format(gatheringTime, '%w') = 4
          then date_format(gatheringTime,'%m/%d(목) %p%l:%i')
          else
          case when date_format(gatheringTime, '%w') = 5
          then date_format(gatheringTime,'%m/%d(금) %p%l:%i')
          else
          case when date_format(gatheringTime, '%w') = 6
          then date_format(gatheringTime,'%m/%d(토) %p%l:%i')
          end end end end end end end as gatheringTime,
         gatherLongitude, gatherLatitude, locationInfo, runningTag,concat(ageMin,'-',ageMax) as age,
         case when runnerGender='A' then '전체'
         else
         case when runnerGender='M' then '남성'
         else
         case when runnerGender='F' then '여성'
          end end end as gender,
          left((6371 * acos(cos(radians(${userLatitude})) * cos(radians(gatherLatitude)) *
                              cos(radians(gatherLongitude) - radians(${userLongitude})) +
                              sin(radians(${userLatitude})) * sin(radians(gatherLatitude)))), 4) AS DISTANCE,
         count(B.userId) as bookMarkNumber, whetherEnd
  FROM Posting P
  INNER JOIN User U on U.userId = P.postUserId
  INNER JOIN Running R on R.postId = P.postId
  LEFT OUTER JOIN Bookmarks B on P.postId = B.postId
  WHERE runningTag = "${runningTag}"
  ${whetherEndCondition}
  GROUP BY B.postId
  ORDER BY "${sortCondition}";

요일을 한글로 출력하기 위해 좀 더 깔끔한 방안 찾아보기

#직군코드는 쿼리 따로 / 중복제거

SELECT DISTINCT postId, job
FROM RunningPeople RP
inner join Running R on RP.gatheringId = R.gatheringId
inner join User U on RP.userId = U.userId
WHERE  whetherAccept = 'Y';

현재 게시글 당 여러 개의 직군 코드를 한 번에 담을 수 없음
서브쿼리를 사용하더라도 직군 코드를 제외한 나머지 컬럼들이 모두 중복되는 여러 개의 row가 생성됨

한 게시글에 속하는 독립된 직군 코드들을 하나의 배열 담아서 컬럼 하나로 묶을 수 있다면 서브쿼리로 처리할 수 있다.

ex) DEV, DEV, EDU, PSV, DEV, DEV
=> [DEV, EDU, PSV] 하나의 컬럼으로 삽입
이렇게 담더라도 프론트에서 이를 분해할 수 있을 지 불명확함

=> 다른 방안을 찾아볼 것
현재는 게시글 객체와 별도로 직군코드 객체가 분리되니 프론트에서 postId로 맵핑하는 과정이 필요함

메인 페이지 쿼리 개선 진행

문제점

{
    "isSuccess": true,
    "code": 1000,
    "message": "성공",
    "result": {
        "postingResult": [
            {
                "postId": 9,
                "postingTime": "2022-02-10T18:25:58.000Z",
                "postUserId": 2,
                "nickName": "testsdf",
                "profileImageUrl": null,
                "title": "글제목1",
                "gatheringTime": "05/22(일) AM11:22",
                "gatherLongitude": "36.1231231230",
                "gatherLatitude": "36.1231231230",
                "locationInfo": "어디 어디",
                "runningTag": "A",
                "age": "20-65",
                "gender": "남성",
                "DISTANCE": "0",
                "bookMarkNumber": 3,
                "whetherEnd": "N"
            },
            {
                "postId": 10,
                "postingTime": "2021-02-11T06:41:59.000Z",
                "postUserId": 20,
                "nickName": "tsetzzz",
                "profileImageUrl": null,
                "title": "글제목2",
                "gatheringTime": "05/17(화) AM11:22",
                "gatherLongitude": "36.1231230000",
                "gatherLatitude": "36.1232312300",
                "locationInfo": "어디 어디 2",
                "runningTag": "A",
                "age": "25-45",
                "gender": "전체",
                "DISTANCE": "0.01",
                "bookMarkNumber": 2,
                "whetherEnd": "Y"
            },
            {
                "postId": 11,
                "postingTime": "2021-02-01T06:43:24.000Z",
                "postUserId": 21,
                "nickName": "test nick",
                "profileImageUrl": null,
                "title": "글제목3",
                "gatheringTime": "05/21(토) AM11:22",
                "gatherLongitude": "36.2312312300",
                "gatherLatitude": "36.1312312300",
                "locationInfo": "어디 어디 3",
                "runningTag": "A",
                "age": "30-40",
                "gender": "여성",
                "DISTANCE": "9.75",
                "bookMarkNumber": 0,
                "whetherEnd": "N"
            }
        ],
        "jobResult": [
            {
                "postId": 9,
                "job": "교육"
            },
            {
                "postId": 9,
                "job": "개발"
            },
            {
                "postId": 10,
                "job": "개발"
            },
            {
                "postId": 11,
                "job": "공무원"
            }
        ]
    }
}

기존 쿼리에서 데이터를 묶는 방법을 찾지 못해서 job에 대한 데이터를 분리시킴

해결책 : mysql의 GROUP_CONCAT 함수 사용

필수 데이터만을 골라 예시를 만들면,

SELECT DISTINCT postId, GROUP_CONCAT(distinct(job)) as job
FROM RunningPeople RP
inner join Running R on RP.gatheringId = R.gatheringId
inner join User U on RP.userId = U.userId
group by postId;

image
원하는 결과를 얻을 수 있다

프로젝트에 적용시킬 것

pm2 cluster mode

config file을 통해 cluster mode 설정

reference - LINE

https://engineering.linecorp.com/ko/blog/pm2-nodejs

set index.js

let server = express().listen(port, () => {
  process.send("ready"); //ready 전달
});

process.on("SIGINT", async () => {
  await server.close();
  process.exit(0);
});

set ecosystem.config.js

module.exports = {
  apps: [
    {
      name: "runnerbeApp",
      script: "./index.js",
      instances: 0,
      exec_mode: "cluster",
      wait_ready: true, // load하기 전에 ready
      listen_timeout: 50000,
      kill_timeout: 5000, // SIGINT -> SIGKILL 전송까지의 시간
    },
  ],
};
pm2 start ./ecosystem.config.js --watch --ignore-watch="./log/*" -i max

하지만 프리티어 ec2가 1코어 1스레드라 당장은 의미x ,후에 scaling up 대비
image

개인정보 암호화

현재 crypto 라이브러리로 email과 같은 회원의 개인 정보를 암호화 시켜서 DB에 저장하고 있다.

문제점은 현 방식은 단방향 암호화로 보안에 취약하다.

      const hashedEmail = await crypto
        .createHash("sha512")
        .update(officeEmail)

단순 Hash를 통해 암호화 하기 때문에 rainbow table 공격이나 무차별 대입 공격에 취약하다.

따라서 Salt값이나 다중 Hash함수를 통해 보안성을 강화해야한다.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.