From 84a013eddcb89120f23d8469cff4bc5cee98701e Mon Sep 17 00:00:00 2001 From: Hyosik Philip Joo Date: Mon, 12 Jun 2023 00:31:39 -0700 Subject: [PATCH] =?UTF-8?q?[FEAT]=20=EC=95=B1=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EC=BB=A4=EC=8A=A4=ED=85=80=20=EC=97=90=EB=9F=AC=20=ED=95=B8?= =?UTF-8?q?=EB=93=A4=EB=A7=81=20=EC=A0=81=EC=9A=A9=20(#317)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [ADD] Install Sentry * [FEAT] asyncWrapper 구현 * [FEAT] 에러 핸들러 구현 * [FEAT] 에러 핸들링 미들웨어로 이관 * [FEAT] asyncWrapper에 DB 릴리즈 추가 * [FEAT] 토큰 재발급 API 에러 핸들링 적용 * [FEAT] 500 에러 의외의 에러도 에러 핸들러에서 처리 가능하도록 수정 * [FEAT] 로그인 API 에러 핸들링 적용 * [FEAT] 회원가입 API 에러 핸들링 적용 * [FEAT] 카테고리 별 컨텐츠 조회 API 에러 핸들링 적용 * [FEAT] 카테고리 별 콘텐츠 키워드 검색 API 에러 핸들링 적용 * [FEAT] 카테고리 삭제 API 에러 핸들링, Promise.all 적용 * [FEAT] 카테고리 전체 조회 API 에러 핸들링 적용 * [FEAT] 카테고리 이름 조회 에러 핸들링 적용 * [FEAT] 카테고리 순서 변경 API 에러 핸들링 적용 * [FEAT] 카테고리 수정 API 에러 핸들링 적용 * [FEAT] 카테고리 생성 API 에러 핸들링 적용 * [FEAT] 콘텐츠 소속 카테고리 조회 API 에러 핸들링 적용 * [FEAT] 콘텐츠 카테고리 변경 API 에러 핸들링, Promise.all 적용 * [FEAT] 콘텐츠 조회 여부 토글 API 에러 핸들링 적용 * [FEAT] 콘텐츠 삭제 API 에러 핸들링 적용 * [FEAT] 콘텐츠 전체 조회 API 에러 핸들링 적용 * [FEAT] 콘텐츠 알람 삭제 API 에러 핸들링 적용 * [FEAT] 콘텐츠 알람 전체 조회 API 에러 핸들링 적용 * [FEAT] 콘텐츠 알림 시각 수정 API 에러 핸들링 적용 * [FEAT] 콘텐츠 생성 API 에러 핸들링 적용 * [FEAT] 최근 저장 콘텐츠 조회 API 에러 핸들링 적용 * [FEAT] 콘텐츠 제목 변경 API 에러 핸들링 적용 * [FEAT] 전체 콘텐츠 키워드 검색 API 에러 핸들링 적용 * [FEAT] 봐야 하는 콘텐츠 조회 API 에러 핸들링 적용 * [FEAT] 회원 탈퇴 API 에러 핸들링 적용 * [REMOVE] 불필요한 주석 삭제 * [CHORE] Strict equal 적용 * [FEAT] Actions에 Sentry secret 추가 --- .github/workflows/dev.yml | 2 + .github/workflows/prod.yml | 2 + README.md | 6 +- functions/api/index.js | 21 ++- functions/api/routes/auth/reissueTokenPOST.js | 79 ++++---- functions/api/routes/auth/signinPOST.js | 149 ++++++--------- functions/api/routes/auth/signupPOST.js | 89 ++++----- functions/api/routes/auth/userDELETE.js | 80 +++----- .../api/routes/category/categoryContentGET.js | 98 +++++----- .../category/categoryContentSearchGET.js | 67 +++---- .../api/routes/category/categoryDELETE.js | 34 ++-- functions/api/routes/category/categoryGET.js | 32 +--- .../api/routes/category/categoryNameGET.js | 28 +-- .../api/routes/category/categoryOrderPATCH.js | 35 ++-- .../api/routes/category/categoryPATCH.js | 34 ++-- functions/api/routes/category/categoryPOST.js | 34 ++-- .../api/routes/content/contentCategoryGET.js | 73 +++---- .../routes/content/contentCategoryPATCH.js | 66 +++---- .../api/routes/content/contentCheckPATCH.js | 37 ++-- functions/api/routes/content/contentDELETE.js | 58 +++--- .../api/routes/content/contentListGET.js | 102 +++++----- .../content/contentNotificationDELETE.js | 60 +++--- .../routes/content/contentNotificationGET.js | 58 +++--- .../content/contentNotificationPATCH.js | 78 ++++---- functions/api/routes/content/contentPOST.js | 98 +++++----- .../routes/content/contentRecentListGET.js | 81 ++++---- .../api/routes/content/contentRenamePATCH.js | 47 ++--- .../api/routes/content/contentSearchGET.js | 67 +++---- .../routes/content/contentUnseenListGET.js | 65 +++---- .../api/routes/user/userUpdateFcmTokenPUT.js | 2 +- functions/db/categoryContent.js | 6 +- functions/db/content.js | 6 +- functions/lib/asyncWrapper.js | 13 ++ functions/middlewares/errorHandler.js | 16 ++ functions/package-lock.json | 178 +++++++++++++++++- functions/package.json | 1 + 36 files changed, 874 insertions(+), 1028 deletions(-) create mode 100644 functions/lib/asyncWrapper.js create mode 100644 functions/middlewares/errorHandler.js diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index cf06a5b..a61b740 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -51,6 +51,8 @@ jobs: echo "JWT_ALGORITHM=${{ secrets.JWT_ALGORITHM }}" >> .env.dev echo "JWT_ACCESS_EXPIRE=${{ secrets.JWT_ACCESS_EXPIRE }}" >> .env.dev echo "JWT_REFRESH_EXPIRE=${{ secrets.JWT_REFRESH_EXPIRE }} " >> .env.dev + echo "SENTRY_DSN=${{ secrets.DEV_SENTRY_DSN }} " >> .env.dev + echo "SENTRY_TRACES_SAMPLE_RATE=${{ secrets.DEV_SENTRY_TRACES_SAMPLE_RATE }} " >> .env.dev - name: create .p8 file run: | diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml index 5b4dbb8..bb6f856 100644 --- a/.github/workflows/prod.yml +++ b/.github/workflows/prod.yml @@ -51,6 +51,8 @@ jobs: echo "JWT_ALGORITHM=${{ secrets.JWT_ALGORITHM }}" >> .env.prod echo "JWT_ACCESS_EXPIRE=${{ secrets.JWT_ACCESS_EXPIRE }}" >> .env.prod echo "JWT_REFRESH_EXPIRE=${{ secrets.JWT_REFRESH_EXPIRE }} " >> .env.prod + echo "SENTRY_DSN=${{ secrets.PROD_SENTRY_DSN }} " >> .env.prod + echo "SENTRY_TRACES_SAMPLE_RATE=${{ secrets.PROD_SENTRY_TRACES_SAMPLE_RATE }} " >> .env.prod - name: create .p8 file run: | diff --git a/README.md b/README.md index 800a73e..eeeb9c6 100644 --- a/README.md +++ b/README.md @@ -240,7 +240,7 @@ iOS의 Share Extension, Android의 Intent Filter를 사용하여 홈 화면으 ``` 3. Bracket 사용 시 내부에 주석을 작성한다. ```javascript - if (a == 5) { + if (a === 5) { // 주석 } ``` @@ -264,14 +264,14 @@ iOS의 Share Extension, Android의 Intent Filter를 사용하여 홈 화면으 ``` javascript // 괄호 사용 한칸 띄우고 사용한다. - if (left == true) { + if (left === true) { return; } ``` 3. Bracket 양쪽 사이를 띄어서 사용한다. ``` javascript // 띄어쓰기 - if (a == 5) { // 양쪽 사이로 띄어쓰기 + if (a === 5) { // 양쪽 사이로 띄어쓰기 return; } ``` diff --git a/functions/api/index.js b/functions/api/index.js index 17bc3ec..6e5df4c 100644 --- a/functions/api/index.js +++ b/functions/api/index.js @@ -5,10 +5,27 @@ const cors = require("cors"); const cookieParser = require("cookie-parser"); const hpp = require("hpp"); const helmet = require("helmet"); +const Sentry = require('@sentry/node'); +const errorHandler = require('../middlewares/errorHandler'); // initializing const app = express(); +Sentry.init({ + dsn: process.env.SENTRY_DSN, + environment: `${process.env.NODE_ENV}_app`, + integrations: [ + new Sentry.Integrations.Http({ tracing: true }), + new Sentry.Integrations.Express({ app }), + ...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations(), + ], + tracesSampleRate: process.env.SENTRY_TRACES_SAMPLE_RATE, +}); + +app.use(Sentry.Handlers.requestHandler()); +app.use(Sentry.Handlers.tracingHandler()); + + // Cross-Origin Resource Sharing을 열어주는 미들웨어 // https://even-moon.github.io/2020/05/21/about-cors/ 에서 자세한 정보 확인 app.use(cors()); @@ -27,6 +44,8 @@ app.use(cookieParser()); // 라우팅: routes 폴더로 정리 app.use("/", require("./routes")); +app.use(Sentry.Handlers.errorHandler()); +app.use(errorHandler); // route 폴더에 우리가 지정할 경로가 아닌 다른 경로로 요청이 올 경우 // 잘못된 경로로 요청이 들어왔다는 메세지를 클라이언트에 보냄 @@ -52,6 +71,6 @@ module.exports = functions console.log("\n\n", "[api]", `[${req.method.toUpperCase()}]`, req.originalUrl, req.body); // 맨 위에 선언된 express app 객체를 리턴 - // 요것이 functiobns/index.js 안의 api: require("./api")에 들어가는 것. + // 요것이 functions/index.js 안의 api: require("./api")에 들어가는 것. return app(req, res); }); \ No newline at end of file diff --git a/functions/api/routes/auth/reissueTokenPOST.js b/functions/api/routes/auth/reissueTokenPOST.js index 4366230..b58506b 100644 --- a/functions/api/routes/auth/reissueTokenPOST.js +++ b/functions/api/routes/auth/reissueTokenPOST.js @@ -3,10 +3,9 @@ const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); const db = require('../../../db/db'); const { userDB } = require('../../../db'); -const functions = require('firebase-functions'); -const slackAPI = require('../../../middlewares/slackAPI'); const jwtHandlers = require('../../../lib/jwtHandlers'); const { TOKEN_INVALID, TOKEN_EXPIRED } = require('../../../constants/jwt'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route POST /auth/reissue @@ -14,49 +13,39 @@ const { TOKEN_INVALID, TOKEN_EXPIRED } = require('../../../constants/jwt'); * @access Public */ -module.exports = async (req, res) => { - const { userId } = req.body; - const refreshToken = req.header("refresh-token"); +module.exports = asyncWrapper(async (req, res) => { + const { userId } = req.body; + const refreshToken = req.header("refresh-token"); - const decodedToken = jwtHandlers.verify(refreshToken); - if (decodedToken === TOKEN_EXPIRED) { - // 토큰 만료 - return res.status(statusCode.UNAUTHORIZED).send(util.fail(statusCode.UNAUTHORIZED, responseMessage.TOKEN_EXPIRED)); - } - if (decodedToken === TOKEN_INVALID) { - // 유효하지 않은 토큰 - return res.status(statusCode.UNAUTHORIZED).send(util.fail(statusCode.UNAUTHORIZED, responseMessage.TOKEN_INVALID)); - } - // 토큰 만료, 유효하지 않은 토큰 모두 강제 로그아웃 필요함 + const decodedToken = jwtHandlers.verify(refreshToken); + if (decodedToken === TOKEN_EXPIRED) { + // 토큰 만료 + return res.status(statusCode.UNAUTHORIZED).send(util.fail(statusCode.UNAUTHORIZED, responseMessage.TOKEN_EXPIRED)); + } + if (decodedToken === TOKEN_INVALID) { + // 유효하지 않은 토큰 + return res.status(statusCode.UNAUTHORIZED).send(util.fail(statusCode.UNAUTHORIZED, responseMessage.TOKEN_INVALID)); + } + // 토큰 만료, 유효하지 않은 토큰 모두 강제 로그아웃 필요함 - let client; - try { - client = await db.connect(req); - const user = await userDB.getUser(client, userId); + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; + const user = await userDB.getUser(dbConnection, userId); - // DB user 테이블의 Refresh Token과 클라이언트에게 받아 온 Refresh Token 비교 - if (refreshToken == user.refreshToken) { - // Refresh Token 일치 : Aceess Token, Refresh Token 재발급, DB 업데이트 - const newAccessToken = jwtHandlers.sign(user); - const newRefreshToken = jwtHandlers.signRefresh(); - await userDB.updateRefreshToken(client, user.id, newRefreshToken); - const reissuedTokens = { - 'accessToken' : newAccessToken, - 'refreshToken' : newRefreshToken - }; - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.TOKEN_REISSUE_SUCCESS, reissuedTokens)); - } - else { - // Refresh Token 불일치 : 강제 로그아웃 - return res.status(statusCode.UNAUTHORIZED).send(util.fail(statusCode.UNAUTHORIZED, responseMessage.TOKEN_INVALID)); - } - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - } finally { - client.release(); - } -}; \ No newline at end of file + // DB user 테이블의 Refresh Token과 클라이언트에게 받아 온 Refresh Token 비교 + if (refreshToken === user.refreshToken) { + // Refresh Token 일치 : Aceess Token, Refresh Token 재발급, DB 업데이트 + const newAccessToken = jwtHandlers.sign(user); + const newRefreshToken = jwtHandlers.signRefresh(); + await userDB.updateRefreshToken(dbConnection, user.id, newRefreshToken); + const reissuedTokens = { + 'accessToken' : newAccessToken, + 'refreshToken' : newRefreshToken + }; + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.TOKEN_REISSUE_SUCCESS, reissuedTokens)); + } + else { + // Refresh Token 불일치 : 강제 로그아웃 + return res.status(statusCode.UNAUTHORIZED).send(util.fail(statusCode.UNAUTHORIZED, responseMessage.TOKEN_INVALID)); + } +}); \ No newline at end of file diff --git a/functions/api/routes/auth/signinPOST.js b/functions/api/routes/auth/signinPOST.js index 91ae4ee..54e5a16 100644 --- a/functions/api/routes/auth/signinPOST.js +++ b/functions/api/routes/auth/signinPOST.js @@ -4,11 +4,10 @@ const responseMessage = require("../../../constants/responseMessage"); const kakao = require("../../../lib/kakaoAuth"); const db = require("../../../db/db"); const { userDB } = require("../../../db"); -const functions = require("firebase-functions"); -const slackAPI = require("../../../middlewares/slackAPI"); const jwtHandlers = require("../../../lib/jwtHandlers"); const { modifyFcmToken } = require('../../../lib/pushServerHandlers'); const { getAppleRefreshToken } = require("../../../lib/appleAuth"); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route POST /auth/signin @@ -16,106 +15,82 @@ const { getAppleRefreshToken } = require("../../../lib/appleAuth"); * @access Public */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { fcmToken, kakaoAccessToken, firebaseUID, appleCode } = req.body; - const badRequestMessage = '[ERROR] POST /auth/signin - Bad Request'; - - if (!fcmToken || (!firebaseUID && !kakaoAccessToken)) { - // fcmToken이 없거나 firebaseUID와 kakaoAccessToken이 모두 없을 때 : 에러 - slackAPI.sendMessageToSlack(badRequestMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE)); - } - - if (firebaseUID && kakaoAccessToken) { - // firebaseUID와 kakaoAccessToken이 모두 있을 때 : 에러 - slackAPI.sendMessageToSlack(badRequestMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.OUT_OF_VALUE)); + if ((!fcmToken || (!firebaseUID && !kakaoAccessToken)) // fcmToken이 없거나 firebaseUID와 kakaoAccessToken이 모두 없을 때 + || (firebaseUID && kakaoAccessToken) // firebaseUID와 kakaoAccessToken이 모두 있을 때 + || (firebaseUID && !appleCode)) { // firebaseUID는 있으나 appleCode 가 없을 때 + const badRequestError = new Error(); + badRequestError.statusCode = statusCode.BAD_REQUEST; + badRequestError.responseMessage = (firebaseUID && kakaoAccessToken)? responseMessage.OUT_OF_VALUE : responseMessage.NULL_VALUE; + throw badRequestError; } - if (firebaseUID && !appleCode) { - // firebaseUID 는 있으나 appleCode 가 없을 때 : 에러 - slackAPI.sendMessageToSlack(badRequestMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE)); - } + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; + let isAlreadyUser; - let client; + if (!firebaseUID) { + // firebaseUID가 없을 때 : 카카오 소셜 로그인 + const firebaseUserData = await kakao.createFirebaseToken(kakaoAccessToken); + const { firebaseAuthToken, firebaseUserId } = firebaseUserData; + const kakaoUser = await userDB.getUserByFirebaseId(dbConnection, firebaseUserId); - try { - client = await db.connect(req); - - let isAlreadyUser; + if (!kakaoUser) { + // 신규 사용자 + isAlreadyUser = false; + return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.NO_USER, { isAlreadyUser })); + } + else { + // 기존 사용자 + isAlreadyUser = true; + const accessToken = jwtHandlers.sign({ id: kakaoUser.id, idFirebase: kakaoUser.idFirebase }); + const refreshToken = jwtHandlers.signRefresh(); + const nickname = kakaoUser.nickname; - if (!firebaseUID) { - // firebaseUID가 없을 때 : 카카오 소셜 로그인 - const firebaseUserData = await kakao.createFirebaseToken(kakaoAccessToken); - const { firebaseAuthToken, firebaseUserId } = firebaseUserData; - const kakaoUser = await userDB.getUserByFirebaseId(client, firebaseUserId); + const response = await modifyFcmToken(kakaoUser.mongoUserId, fcmToken); - if (!kakaoUser) { - // 신규 사용자 - isAlreadyUser = false; - return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.NO_USER, { isAlreadyUser })); + if (response.status !== 204) { + return res.status(response.statusCode).send(util.fail(response.statusCode, responseMessage.PUSH_SERVER_ERROR)); } - else { - // 기존 사용자 - isAlreadyUser = true; - const accessToken = jwtHandlers.sign({ id: kakaoUser.id, idFirebase: kakaoUser.idFirebase }); - const refreshToken = jwtHandlers.signRefresh(); - const nickname = kakaoUser.nickname; - const response = await modifyFcmToken(kakaoUser.mongoUserId, fcmToken); + await userDB.updateRefreshToken(dbConnection, kakaoUser.id, refreshToken); - if (response.status != 204) { - return res.status(response.statusCode).send(util.fail(response.statusCode, responseMessage.PUSH_SERVER_ERROR)); - } + return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.SIGNIN_SUCCESS, + { firebaseAuthToken, accessToken, refreshToken, nickname })); + }; + } + else { + // kakaoAccessToken이 없을 때 (!kakaoAccessToken) : 애플 소셜 로그인 + const appleUser = await userDB.getUserByFirebaseId(dbConnection, firebaseUID); - await userDB.updateRefreshToken(client, kakaoUser.id, refreshToken); - - return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.SIGNIN_SUCCESS, - { firebaseAuthToken, accessToken, refreshToken, nickname })); - }; - } + if (!appleUser) { + // 신규 사용자 + isAlreadyUser = false; + return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.NO_USER, { isAlreadyUser })); + } else { - // kakaoAccessToken이 없을 때 (!kakaoAccessToken) : 애플 소셜 로그인 - const appleUser = await userDB.getUserByFirebaseId(client, firebaseUID); - - if (!appleUser) { - // 신규 사용자 - isAlreadyUser = false; - return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.NO_USER, { isAlreadyUser })); - } - else { - // 기존 사용자 - const accessToken = jwtHandlers.sign({ id: appleUser.id, idFirebase: appleUser.idFirebase }); - const refreshToken = jwtHandlers.signRefresh(); - const nickname = appleUser.nickname; - const firebaseAuthToken = ""; + // 기존 사용자 + const accessToken = jwtHandlers.sign({ id: appleUser.id, idFirebase: appleUser.idFirebase }); + const refreshToken = jwtHandlers.signRefresh(); + const nickname = appleUser.nickname; + const firebaseAuthToken = ""; - // apple refresh token 발급 - const appleRefreshToken = await getAppleRefreshToken(appleCode); - await userDB.updateAppleRefreshToken(client, appleUser.id, appleRefreshToken); + // apple refresh token 발급 + const appleRefreshToken = await getAppleRefreshToken(appleCode); + await userDB.updateAppleRefreshToken(dbConnection, appleUser.id, appleRefreshToken); - const response = await modifyFcmToken(appleUser.mongoUserId, fcmToken); + const response = await modifyFcmToken(appleUser.mongoUserId, fcmToken); - if (response.status != 204) { - return res.status(response.statusCode).send(util.fail(response.statusCode, responseMessage.PUSH_SERVER_ERROR)); - } - - await userDB.updateRefreshToken(client, appleUser.id, refreshToken); - - return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.SIGNIN_SUCCESS, - { firebaseAuthToken, accessToken, refreshToken, nickname })); - }; - } - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : "req.user 없음"} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); + if (response.status !== 204) { + return res.status(response.statusCode).send(util.fail(response.statusCode, responseMessage.PUSH_SERVER_ERROR)); + } - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR,responseMessage.INTERNAL_SERVER_ERROR)); - } finally { - client.release(); + await userDB.updateRefreshToken(dbConnection, appleUser.id, refreshToken); + + return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.SIGNIN_SUCCESS, + { firebaseAuthToken, accessToken, refreshToken, nickname })); + }; } -}; +}); diff --git a/functions/api/routes/auth/signupPOST.js b/functions/api/routes/auth/signupPOST.js index edf72a3..ef7fdfa 100644 --- a/functions/api/routes/auth/signupPOST.js +++ b/functions/api/routes/auth/signupPOST.js @@ -4,11 +4,10 @@ const responseMessage = require("../../../constants/responseMessage"); const kakao = require("../../../lib/kakaoAuth"); const db = require("../../../db/db"); const { userDB } = require("../../../db"); -const functions = require("firebase-functions"); -const slackAPI = require("../../../middlewares/slackAPI"); const jwtHandlers = require("../../../lib/jwtHandlers"); const { createPushServerUser } = require("../../../lib/pushServerHandlers"); const { getAppleRefreshToken } = require("../../../lib/appleAuth"); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route POST /auth/signup @@ -16,66 +15,42 @@ const { getAppleRefreshToken } = require("../../../lib/appleAuth"); * @access Public */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { fcmToken, kakaoAccessToken, firebaseUID, appleCode, nickname, email, age, gender, isOption } = req.body; - const badRequestMessage = '[ERROR] POST /auth/signup - Bad Request'; - - if (!fcmToken || (!firebaseUID && !kakaoAccessToken)) { - // fcmToken이 없거나 firebaseUID와 kakaoAccessToken이 모두 없을 때 : 에러 - slackAPI.sendMessageToSlack(badRequestMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE)); - }; - - if (firebaseUID && kakaoAccessToken) { - // firebaseUID와 kakaoAccessToken이 모두 있을 때 : 에러 - slackAPI.sendMessageToSlack(badRequestMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.OUT_OF_VALUE)); - }; - - if (firebaseUID && !appleCode) { - // firebaseUID 는 있으나 appleCode 가 없을 때 : 에러 - slackAPI.sendMessageToSlack(badRequestMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE)); + if ((!fcmToken || (!firebaseUID && !kakaoAccessToken)) // fcmToken이 없거나 firebaseUID와 kakaoAccessToken이 모두 없을 때 + || (firebaseUID && kakaoAccessToken) // firebaseUID와 kakaoAccessToken이 모두 있을 때 + || (firebaseUID && !appleCode)) { // firebaseUID는 있으나 appleCode 가 없을 때 + const badRequestError = new Error(); + badRequestError.statusCode = statusCode.BAD_REQUEST; + badRequestError.responseMessage = (firebaseUID && kakaoAccessToken)? responseMessage.OUT_OF_VALUE : responseMessage.NULL_VALUE; + throw badRequestError; } - let client; - - try { - client = await db.connect(req); - - const mongoId = await createPushServerUser(fcmToken); - - if (!firebaseUID) { - // firebaseUID가 없을 때 : 카카오 소셜 로그인 - const firebaseUserData = await kakao.createFirebaseToken(kakaoAccessToken); - const { firebaseAuthToken, firebaseUserId } = firebaseUserData; + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; + const mongoId = await createPushServerUser(fcmToken); + + if (!firebaseUID) { + // firebaseUID가 없을 때 : 카카오 소셜 로그인 + const firebaseUserData = await kakao.createFirebaseToken(kakaoAccessToken); + const { firebaseAuthToken, firebaseUserId } = firebaseUserData; + const refreshToken = jwtHandlers.signRefresh(); + const kakaoUser = await userDB.addUser(dbConnection, firebaseUserId, nickname, email, age, gender, isOption, mongoId, refreshToken); + const accessToken = jwtHandlers.sign({ id: kakaoUser.id, idFirebase: kakaoUser.idFirebase }); + return res.status(statusCode.CREATED).send(util.success(statusCode.CREATED, responseMessage.SIGNUP_SUCCESS, { firebaseAuthToken, accessToken, refreshToken, nickname })); + } + else { + // kakaoAccessToken이 없을 때 (!kakaoAccessToken) : 애플 소셜 로그인 const refreshToken = jwtHandlers.signRefresh(); - const kakaoUser = await userDB.addUser(client, firebaseUserId, nickname, email, age, gender, isOption, mongoId, refreshToken); - const accessToken = jwtHandlers.sign({ id: kakaoUser.id, idFirebase: kakaoUser.idFirebase }); - return res.status(statusCode.CREATED).send(util.success(statusCode.CREATED, responseMessage.SIGNUP_SUCCESS, { firebaseAuthToken, accessToken, refreshToken, nickname })); - } - else { - // kakaoAccessToken이 없을 때 (!kakaoAccessToken) : 애플 소셜 로그인 - const refreshToken = jwtHandlers.signRefresh(); - const appleUser = await userDB.addUser(client, firebaseUID, nickname, email, age, gender, isOption, mongoId, refreshToken); - const accessToken = jwtHandlers.sign({ id: appleUser.id, idFirebase: appleUser.idFirebase }); - const firebaseAuthToken = ""; - - // apple refresh token 발급 - const appleRefreshToken = await getAppleRefreshToken(appleCode); - await userDB.updateAppleRefreshToken(client, appleUser.id, appleRefreshToken); + const appleUser = await userDB.addUser(dbConnection, firebaseUID, nickname, email, age, gender, isOption, mongoId, refreshToken); + const accessToken = jwtHandlers.sign({ id: appleUser.id, idFirebase: appleUser.idFirebase }); + const firebaseAuthToken = ""; - return res.status(statusCode.CREATED).send(util.success(statusCode.CREATED, responseMessage.SIGNUP_SUCCESS, { firebaseAuthToken, accessToken, refreshToken, nickname })); - } - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : "req.user 없음"} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); + // apple refresh token 발급 + const appleRefreshToken = await getAppleRefreshToken(appleCode); + await userDB.updateAppleRefreshToken(dbConnection, appleUser.id, appleRefreshToken); - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR,responseMessage.INTERNAL_SERVER_ERROR)); - } finally { - client.release(); + return res.status(statusCode.CREATED).send(util.success(statusCode.CREATED, responseMessage.SIGNUP_SUCCESS, { firebaseAuthToken, accessToken, refreshToken, nickname })); } -}; +}); diff --git a/functions/api/routes/auth/userDELETE.js b/functions/api/routes/auth/userDELETE.js index e511fa5..3e6beed 100644 --- a/functions/api/routes/auth/userDELETE.js +++ b/functions/api/routes/auth/userDELETE.js @@ -1,5 +1,3 @@ -const functions = require('firebase-functions'); -const slackAPI = require('../../../middlewares/slackAPI'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); @@ -9,6 +7,8 @@ const { getAuth } = require('firebase-admin/auth'); const { nanoid } = require("nanoid"); const { deletePushUser } = require('../../../lib/pushServerHandlers'); const { revokeAppleToken } = require('../../../lib/appleAuth'); +const asyncWrapper = require('../../../lib/asyncWrapper'); + /** * @route DELETE /auth/user @@ -16,68 +16,38 @@ const { revokeAppleToken } = require('../../../lib/appleAuth'); * @access Public */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { userId } = req.user; - let client; - let deleteUser; - - try { - client = await db.connect(req); - - deleteUser = await userDB.getUser(client, userId); // DB에서 해당 유저 정보 받아 옴 - - if (deleteUser.appleRefreshToken) { - await revokeAppleToken(deleteUser.appleRefreshToken); // 애플 유저라면 애플 계정 연동 해지 - } - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - client.release(); - - return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); // DB 관련 에러 : 500 INTERNAL SERVER ERROR - } + const dbConnection = await db.connect(req); + let deleteUser = await userDB.getUser(dbConnection, userId); // DB에서 해당 유저 정보 받아 옴 + if (deleteUser.appleRefreshToken) { + await revokeAppleToken(deleteUser.appleRefreshToken); // 애플 유저라면 애플 계정 연동 해지 + } try { + // 500 INTERNAL SERVER ERROR가 아닌 경우에만 error에 부가 정보 삽입 await getAuth().deleteUser(deleteUser.idFirebase); // Firebase Auth에서 해당 유저 삭제 } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - client.release(); - if (error.errorInfo.code === 'auth/user-not-found') { - return res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_USER)); // Firebase Auth에 해당 유저 존재하지 않을 경우 : 404 NOT FOUND - } else { - return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); // 기타 Firebase Auth 에러 : 500 INTERNAL SERVER ERROR + // Firebase Auth에 해당 유저 존재하지 않을 경우 : 404 NOT FOUND + error.statusCode = statusCode.NOT_FOUND; + error.responseMessage = responseMessage.NO_USER; } + throw error; } - try { - const randomString = `:${nanoid(10)}`; - await userDB.deleteUser(client, userId, randomString); // DB에서 해당 유저 삭제 - - const response = await deletePushUser(deleteUser.mongoUserId); - if (response.status != 204) { - return res.status(response.statusCode).send(util.fail(response.statusCode, response.statusText)); - } + const randomString = `:${nanoid(10)}`; + await userDB.deleteUser(dbConnection, userId, randomString); // DB에서 해당 유저 삭제 - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.DELETE_USER)); - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); // DB 관련 에러 : 500 INTERNAL SERVER ERROR - } - finally { - client.release(); + const response = await deletePushUser(deleteUser.mongoUserId); + if (response.status !== 204) { + // 푸시 서버 에러 발생 시 푸시 서버 에러임을 명시 + const pushServerError = new Error(); + pushServerError.statusCode = response.statusCode; + pushServerError.responseMessage = response.statusText; + throw pushServerError; } -}; \ No newline at end of file + + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.DELETE_USER)); +}); \ No newline at end of file diff --git a/functions/api/routes/category/categoryContentGET.js b/functions/api/routes/category/categoryContentGET.js index ad5fb4b..03bf601 100644 --- a/functions/api/routes/category/categoryContentGET.js +++ b/functions/api/routes/category/categoryContentGET.js @@ -1,13 +1,12 @@ const _ = require('lodash'); -const functions = require('firebase-functions'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); -const slackAPI = require('../../../middlewares/slackAPI'); const db = require('../../../db/db'); const { categoryContentDB } = require('../../../db'); const dayjs = require('dayjs'); const customParseFormat = require('dayjs/plugin/customParseFormat'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route GET /category/:categoryId @@ -15,70 +14,57 @@ const customParseFormat = require('dayjs/plugin/customParseFormat'); * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { userId } = req.user; const { categoryId } = req.params; const { option, filter } = req.query; let contents = {}; - let client; + let dbConnection = await db.connect(req); + req.dbConnection = dbConnection; dayjs().format(); dayjs.extend(customParseFormat); - try { - client = await db.connect(req); - - if (option == "all") { - // 전체 조회 - contents = await categoryContentDB.getAllCategoryContentByFilter(client, userId, categoryId, filter); - } else if (option == "notified") { - // 알림 설정된 콘텐츠만 조회 - contents = await categoryContentDB.getCategoryContentByFilterAndNotified(client, userId, categoryId, true, filter) + if (option === "all") { + // 전체 조회 + contents = await categoryContentDB.getAllCategoryContentByFilter(dbConnection, userId, categoryId, filter); + } else if (option === "notified") { + // 알림 설정된 콘텐츠만 조회 + contents = await categoryContentDB.getCategoryContentByFilterAndNotified(dbConnection, userId, categoryId, true, filter) + } else { + // is_seen에 따라 조회 + contents = await categoryContentDB.getCategoryContentByFilterAndSeen(dbConnection, userId, categoryId, option, filter); + } + if (filter === "reverse") { + // DESC를 이용했으므로 다시 reverse + contents = contents.reverse(); + } + if (filter === "seen_at") { + // 최근 조회 순 기준인 경우, 조회하지 않은 콘텐츠 제외 + _.remove(contents, function(content) { + return content.isSeen === false; + }); + } + + const result = await Promise.all(contents.map(content => { + /** + * 클라이언트가 사용할 createdAt, notificationTime, seenAt 은 day js로 format 수정 + * 시간이 null이면 빈 문자열로 수정 + */ + content.createdAt = dayjs(`${content.createdAt}`).format("YYYY-MM-DD HH:mm"); + if (content.notificationTime) { + content.notificationTime = dayjs(`${content.notificationTime}`).format("YYYY-MM-DD HH:mm"); } else { - // is_seen에 따라 조회 - contents = await categoryContentDB.getCategoryContentByFilterAndSeen(client, userId, categoryId, option, filter); - } - if (filter == "reverse") { - // DESC를 이용했으므로 다시 reverse - contents = contents.reverse(); + content.notificationTime = ""; } - if (filter == "seen_at") { - // 최근 조회 순 기준인 경우, 조회하지 않은 콘텐츠 제외 - const removedElements = _.remove(contents, function(content) { - return content.isSeen === false; - }); + if (content.seenAt) { + content.seenAt = dayjs(`${content.seenAt}`).format("YYYY-MM-DD HH:mm"); + } else { + content.seenAt = ""; } + return content; + })); - const result = await Promise.all(contents.map(content => { - /** - * 클라이언트가 사용할 createdAt, notificationTime, seenAt 은 day js로 format 수정 - * 시간이 null이면 빈 문자열로 수정 - */ - content.createdAt = dayjs(`${content.createdAt}`).format("YYYY-MM-DD HH:mm"); - if (content.notificationTime) { - content.notificationTime = dayjs(`${content.notificationTime}`).format("YYYY-MM-DD HH:mm"); - } else { - content.notificationTime = ""; - } - if (content.seenAt) { - content.seenAt = dayjs(`${content.seenAt}`).format("YYYY-MM-DD HH:mm"); - } else { - content.seenAt = ""; - } - return content; - })); - - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.READ_CATEGORY_CONTENT_SUCCESS, result)); - - } catch (error) { - console.log(error); - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - } finally { - client.release(); - } -}; \ No newline at end of file + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.READ_CATEGORY_CONTENT_SUCCESS, result)); +}); \ No newline at end of file diff --git a/functions/api/routes/category/categoryContentSearchGET.js b/functions/api/routes/category/categoryContentSearchGET.js index 3d9d276..2e600ee 100644 --- a/functions/api/routes/category/categoryContentSearchGET.js +++ b/functions/api/routes/category/categoryContentSearchGET.js @@ -1,12 +1,11 @@ -const functions = require('firebase-functions'); -const slackAPI = require('../../../middlewares/slackAPI'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); const db = require('../../../db/db'); const { categoryContentDB } = require('../../../db'); const dayjs = require('dayjs'); -const customParseFormat = require('dayjs/plugin/customParseFormat') +const customParseFormat = require('dayjs/plugin/customParseFormat'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route GET /category/search?categoryId=&keyword= @@ -14,7 +13,7 @@ const customParseFormat = require('dayjs/plugin/customParseFormat') * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { categoryId } = req.params; const { keyword } = req.query; @@ -24,40 +23,26 @@ module.exports = async (req, res) => { // 카테고리나 검색 키워드가 없을 때 에러 처리 return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE)); } - let client; - - try { - client = await db.connect(req); - - const contents = await categoryContentDB.searchCategoryContent(client, userId, categoryId, keyword); - - dayjs().format() - dayjs.extend(customParseFormat) - - const result = await Promise.all(contents.map(content => { - // 시간 데이터 dayjs로 format 수정 - content.createdAt = dayjs(`${content.createdAt}`).format("YYYY-MM-DD HH:mm"); // createdAt 수정 - if (content.notificationTime) { - // notificationTime이 존재할 경우, format 수정 - content.notificationTime = dayjs(`${content.notificationTime}`).format("YYYY-MM-DD HH:mm"); - } else { - // notificationTime이 존재하지 않는 경우, null을 빈 문자열로 변경 - content.notificationTime = ""; - } - return content; - })); - - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.KEYWORD_SEARCH_CATEGORY_CONTENT_SUCCESS, result)); - - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - - } finally { - client.release(); - } -}; \ No newline at end of file + let dbConnection = await db.connect(req); + req.dbConnection = dbConnection; + + const contents = await categoryContentDB.searchCategoryContent(dbConnection, userId, categoryId, keyword); + + dayjs().format(); + dayjs.extend(customParseFormat); + + const result = await Promise.all(contents.map(content => { + // 시간 데이터 dayjs로 format 수정 + content.createdAt = dayjs(`${content.createdAt}`).format("YYYY-MM-DD HH:mm"); // createdAt 수정 + if (content.notificationTime) { + // notificationTime이 존재할 경우, format 수정 + content.notificationTime = dayjs(`${content.notificationTime}`).format("YYYY-MM-DD HH:mm"); + } else { + // notificationTime이 존재하지 않는 경우, null을 빈 문자열로 변경 + content.notificationTime = ""; + } + return content; + })); + + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.KEYWORD_SEARCH_CATEGORY_CONTENT_SUCCESS, result)); +}); \ No newline at end of file diff --git a/functions/api/routes/category/categoryDELETE.js b/functions/api/routes/category/categoryDELETE.js index f8c9a18..d43f1c5 100644 --- a/functions/api/routes/category/categoryDELETE.js +++ b/functions/api/routes/category/categoryDELETE.js @@ -1,10 +1,9 @@ -const functions = require('firebase-functions'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); -const slackAPI = require('../../../middlewares/slackAPI'); const db = require('../../../db/db'); const { categoryDB, categoryContentDB, contentDB } = require('../../../db'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route DELETE /category/:categoryId @@ -12,28 +11,17 @@ const { categoryDB, categoryContentDB, contentDB } = require('../../../db'); * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { categoryId } = req.params; const { userId } = req.user; - let client; + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; + await Promise.all([ + categoryDB.deleteCategory(dbConnection, categoryId, userId), // 해당 카테고리 soft delete + contentDB.updateContentIsDeleted(dbConnection, categoryId, userId), // 카테고리 개수가 1개 (해당 카테고리뿐)인 콘텐츠 soft delete + categoryContentDB.deleteCategoryContentByCategoryId(dbConnection, categoryId) // category_content 테이블 내 해당 카테고리 삭제 + ]) - try { - client = await db.connect(req); - - await categoryDB.deleteCategory(client, categoryId, userId); // 해당 카테고리 soft delete - await contentDB.updateContentIsDeleted(client, categoryId, userId); // 카테고리 개수가 1개 (해당 카테고리뿐)인 콘텐츠 soft delete - await categoryContentDB.deleteCategoryContentByCategoryId(client, categoryId); // category_content 테이블 내 해당 카테고리 삭제 - - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.DELETE_ONE_CATEGORY_SUCCESS)); - } catch (error) { - console.log(error); - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - } finally { - client.release(); - } -}; \ No newline at end of file + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.DELETE_ONE_CATEGORY_SUCCESS)); +}); \ No newline at end of file diff --git a/functions/api/routes/category/categoryGET.js b/functions/api/routes/category/categoryGET.js index 6ed19c1..a2d826a 100644 --- a/functions/api/routes/category/categoryGET.js +++ b/functions/api/routes/category/categoryGET.js @@ -1,37 +1,25 @@ -const functions = require('firebase-functions'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); -const slackAPI = require('../../../middlewares/slackAPI'); const db = require('../../../db/db'); const { categoryDB, categoryContentDB } = require('../../../db'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route GET /category * @desc 카테고리 전체 조회 * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { userId } = req.user; - let client; + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; - try { - client = await db.connect(req); - let categories = await categoryDB.getAllCategories(client, userId); - for (let category of categories) { - const categoryContent = await categoryContentDB.getAllCategoryContentByFilter(client, userId, category.id, 'created_at'); - category.contentNumber = categoryContent.length; - } - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.READ_CATEGORY_SUCCESS, categories)); - } catch (error) { - console.log(error); - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - } finally { - client.release(); + let categories = await categoryDB.getAllCategories(dbConnection, userId); + for (let category of categories) { + const categoryContent = await categoryContentDB.getAllCategoryContentByFilter(dbConnection, userId, category.id, 'created_at'); + category.contentNumber = categoryContent.length; } -}; + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.READ_CATEGORY_SUCCESS, categories)); +}); diff --git a/functions/api/routes/category/categoryNameGET.js b/functions/api/routes/category/categoryNameGET.js index f236a5f..83d2968 100644 --- a/functions/api/routes/category/categoryNameGET.js +++ b/functions/api/routes/category/categoryNameGET.js @@ -1,35 +1,23 @@ const _ = require('lodash'); -const functions = require('firebase-functions'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); -const slackAPI = require('../../../middlewares/slackAPI'); const db = require('../../../db/db'); const { categoryDB } = require('../../../db'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route GET /category/name * @desc 카테고리 이름 조회 * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { userId } = req.user; - let client; - - try { - client = await db.connect(req); - const categoryNames = await categoryDB.getCategoryNames(client, userId); - const titles = _.map(categoryNames, 'title'); - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.READ_CATEGORY_NAME_SUCCESS, titles)); - } catch (error) { - console.log(error); - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - } finally { - client.release(); - } -}; \ No newline at end of file + const categoryNames = await categoryDB.getCategoryNames(dbConnection, userId); + const titles = _.map(categoryNames, 'title'); + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.READ_CATEGORY_NAME_SUCCESS, titles)); +}); \ No newline at end of file diff --git a/functions/api/routes/category/categoryOrderPATCH.js b/functions/api/routes/category/categoryOrderPATCH.js index f3ae337..8d34ff1 100644 --- a/functions/api/routes/category/categoryOrderPATCH.js +++ b/functions/api/routes/category/categoryOrderPATCH.js @@ -1,10 +1,9 @@ -const functions = require('firebase-functions'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); -const slackAPI = require('../../../middlewares/slackAPI'); const db = require('../../../db/db'); const { categoryDB } = require('../../../db'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route PATCH /category/order @@ -12,7 +11,7 @@ const { categoryDB } = require('../../../db'); * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { categoryIndexArray } = req.body; const { userId } = req.user; @@ -20,27 +19,15 @@ module.exports = async (req, res) => { return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE)); } - let client; - - try { - client = await db.connect(req); + let dbConnection = await db.connect(req); + req.dbConnection = dbConnection; - for (let orderIndex = 0; orderIndex < categoryIndexArray.length; orderIndex++) { - // 카테고리 배열 속 카테고리 id의 순서대로 변경 - const contentId = categoryIndexArray[orderIndex]; - const updatedCategory = await categoryDB.updateCategoryIndex(client, userId, contentId, orderIndex); - } - - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.UPDATE_CATEGORY_ORDER_SUCCESS)); - } catch (error) { - console.log(error); - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - } finally { - client.release(); + for (let orderIndex = 0; orderIndex < categoryIndexArray.length; orderIndex++) { // TODO: 비동기 처리 + // 카테고리 배열 속 카테고리 id의 순서대로 변경 + const contentId = categoryIndexArray[orderIndex]; + await categoryDB.updateCategoryIndex(dbConnection, userId, contentId, orderIndex); } -}; + + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.UPDATE_CATEGORY_ORDER_SUCCESS)); +}); diff --git a/functions/api/routes/category/categoryPATCH.js b/functions/api/routes/category/categoryPATCH.js index 540e10c..856d28f 100644 --- a/functions/api/routes/category/categoryPATCH.js +++ b/functions/api/routes/category/categoryPATCH.js @@ -1,17 +1,17 @@ -const functions = require('firebase-functions'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); -const slackAPI = require('../../../middlewares/slackAPI'); const db = require('../../../db/db'); const { categoryDB } = require('../../../db'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route PATCH /category/:categoryId * @desc 카테고리 수정 * @access Private */ -module.exports = async (req, res) => { + +module.exports = asyncWrapper(async (req, res) => { const { categoryId } = req.params; const { title, imageId } = req.body; @@ -19,26 +19,14 @@ module.exports = async (req, res) => { return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE)); } - let client; - - try { - client = await db.connect(req); + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; - const updatedCategory = await categoryDB.updateCategory(client, categoryId, title, imageId); - if (!updatedCategory) { - return res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CATEGORY)); - } - - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.UPDATE_ONE_CATEGORY_SUCCESS)); - } catch (error) { - console.log(error); - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - } finally { - client.release(); + const updatedCategory = await categoryDB.updateCategory(dbConnection, categoryId, title, imageId); + if (!updatedCategory) { + return res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CATEGORY)); } -}; + + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.UPDATE_ONE_CATEGORY_SUCCESS)); +}); diff --git a/functions/api/routes/category/categoryPOST.js b/functions/api/routes/category/categoryPOST.js index 451638f..9c88a17 100644 --- a/functions/api/routes/category/categoryPOST.js +++ b/functions/api/routes/category/categoryPOST.js @@ -1,10 +1,10 @@ -const functions = require('firebase-functions'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); -const slackAPI = require('../../../middlewares/slackAPI'); const db = require('../../../db/db'); const { categoryDB } = require('../../../db'); +const asyncWrapper = require('../../../lib/asyncWrapper'); + /** * @route POST /category @@ -12,7 +12,7 @@ const { categoryDB } = require('../../../db'); * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { title, imageId } = req.body; let newIndex = 0; @@ -21,26 +21,14 @@ module.exports = async (req, res) => { } const { userId } = req.user; - let client; - - try { - client = await db.connect(req); + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; - const oldCategory = await categoryDB.getAllCategories(client, userId); + const oldCategory = await categoryDB.getAllCategories(dbConnection, userId); - if(oldCategory.length) { - newIndex = oldCategory[oldCategory.length - 1].orderIndex + 1; // 새 카테고리 인덱스는 마지막 카테고리 인덱스 + 1 - } - await categoryDB.addCategory(client, userId, title, imageId, newIndex); - res.status(statusCode.CREATED).send(util.success(statusCode.CREATED, responseMessage.ADD_ONE_CATEGORY_SUCCESS)); - } catch (error) { - console.log(error); - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - } finally { - client.release(); + if(oldCategory.length) { + newIndex = oldCategory[oldCategory.length - 1].orderIndex + 1; // 새 카테고리 인덱스는 마지막 카테고리 인덱스 + 1 } -}; \ No newline at end of file + await categoryDB.addCategory(dbConnection, userId, title, imageId, newIndex); + res.status(statusCode.CREATED).send(util.success(statusCode.CREATED, responseMessage.ADD_ONE_CATEGORY_SUCCESS)); +}); \ No newline at end of file diff --git a/functions/api/routes/content/contentCategoryGET.js b/functions/api/routes/content/contentCategoryGET.js index 4d5feb1..d9fdfc1 100644 --- a/functions/api/routes/content/contentCategoryGET.js +++ b/functions/api/routes/content/contentCategoryGET.js @@ -1,11 +1,10 @@ const _ = require('lodash'); -const functions = require('firebase-functions'); -const slackAPI = require('../../../middlewares/slackAPI'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); const db = require('../../../db/db'); const { contentDB, categoryDB, categoryContentDB } = require('../../../db'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route GET /content/category/:contentId @@ -13,48 +12,34 @@ const { contentDB, categoryDB, categoryContentDB } = require('../../../db'); * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { contentId } = req.params; const { userId } = req.user; - console.log(contentId); - let client; - - try { - client = await db.connect(req); - - // 콘텐츠가 없거나, 해당 유저의 콘텐츠가 아닌 경우 제한 - const content = await contentDB.getContentById(client, contentId); - if(!content) { - return res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CONTENT)); - } - if (content.userId !== userId) { - return res.status(statusCode.FORBIDDEN).send(util.fail(statusCode.FORBIDDEN, responseMessage.FORBIDDEN)); - } - - const [allCategories, contentCategories] = await Promise.all([ - categoryDB.getAllCategories(client, userId), - categoryContentDB.getCategoryContentByContentId(client, contentId, userId) - ]); - - const data = await Promise.all(allCategories.map(category => { - category.isSelected = false; - - const result = contentCategories.some(cc => cc.id === category.id); - if (result) category.isSelected = true; - - return category; - })); - - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.READ_CONTENT_CATEGORY_SUCCESS, data)); - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - - } finally { - client.release(); + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; + + // 콘텐츠가 없거나, 해당 유저의 콘텐츠가 아닌 경우 제한 + const content = await contentDB.getContentById(dbConnection, contentId); + if (!content) { + return res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CONTENT)); + } + if (content.userId !== userId) { + return res.status(statusCode.FORBIDDEN).send(util.fail(statusCode.FORBIDDEN, responseMessage.FORBIDDEN)); } -} \ No newline at end of file + + const [allCategories, contentCategories] = await Promise.all([ + categoryDB.getAllCategories(dbConnection, userId), + categoryContentDB.getCategoryContentByContentId(dbConnection, contentId, userId) + ]); + + const data = await Promise.all(allCategories.map(category => { + category.isSelected = false; + + const result = contentCategories.some(cc => cc.id === category.id); + if (result) category.isSelected = true; + + return category; + })); + + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.READ_CONTENT_CATEGORY_SUCCESS, data)); +}); \ No newline at end of file diff --git a/functions/api/routes/content/contentCategoryPATCH.js b/functions/api/routes/content/contentCategoryPATCH.js index c7ac09a..77fb4fb 100644 --- a/functions/api/routes/content/contentCategoryPATCH.js +++ b/functions/api/routes/content/contentCategoryPATCH.js @@ -1,11 +1,9 @@ -const functions = require('firebase-functions'); -const slackAPI = require('../../../middlewares/slackAPI'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); const db = require('../../../db/db'); const { categoryDB, categoryContentDB } = require('../../../db'); -const e = require('cors'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route PATCH /content/category @@ -13,7 +11,7 @@ const e = require('cors'); * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { contentId, newCategoryIds } = req.body; const { userId } = req.user; @@ -22,43 +20,31 @@ module.exports = async (req, res) => { return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE)); } - let client; - - try { - client = await db.connect(req); + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; - let flag = true; // flag 변수 결과에 따라 categoryContent를 추가할 지, 에러를 보낼 지 결정 - for (const newCategoryId of newCategoryIds) { - // 카테고리 배열의 id 중 하나라도 유저의 카테고리가 아닐 경우, 에러 전송 - const newCategory = await categoryDB.getCategory(client, newCategoryId); - if (!newCategory || newCategory.userId !== userId) { - // 카테고리가 아예 존재하지 않거나, 해당 유저의 카테고리가 아닌 경우 - flag = false; - } + let flag = true; // flag 변수 결과에 따라 categoryContent를 추가할 지, 에러를 보낼 지 결정 + for (const newCategoryId of newCategoryIds) { + // 카테고리 배열의 id 중 하나라도 유저의 카테고리가 아닐 경우, 에러 전송 + const newCategory = await categoryDB.getCategory(dbConnection, newCategoryId); + if (!newCategory || newCategory.userId !== userId) { + // 카테고리가 아예 존재하지 않거나, 해당 유저의 카테고리가 아닌 경우 + flag = false; } + } - if (flag) { - // 유저가 해당 카테고리를 가지고 있을 때 - const deletedCategoryIds = await categoryContentDB.deleteCategoryContentByContentId(client, contentId); // 기존 카테고리-콘텐츠 관계 모두 삭제 - for (const newCategoryId of newCategoryIds) { - // 새로운 카테고리마다 새로운 카테고리-콘텐츠 관계 생성 - const newCategoryContent = await categoryContentDB.addCategoryContent(client, newCategoryId, contentId); - } - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.UPDATE_CONTENT_CATEGORY_SUCCESS)); - } else { - // 카테고리를 변경할 수 없는 경우, 에러 전송 - res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CATEGORY)); - } - - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - - } finally { - client.release(); + if (flag) { + // 유저가 해당 카테고리를 가지고 있을 때 + const promises = []; + promises.push(categoryContentDB.deleteCategoryContentByContentId(dbConnection, contentId)); // 기존 카테고리-콘텐츠 관계 모두 삭제 + for (const newCategoryId of newCategoryIds) { + // 새로운 카테고리마다 새로운 카테고리-콘텐츠 관계 생성 + promises.push(categoryContentDB.addCategoryContent(dbConnection, newCategoryId, contentId)); + } + await Promise.all(promises) + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.UPDATE_CONTENT_CATEGORY_SUCCESS)); + } else { + // 카테고리를 변경할 수 없는 경우, 에러 전송 + res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CATEGORY)); } -}; \ No newline at end of file +}); \ No newline at end of file diff --git a/functions/api/routes/content/contentCheckPATCH.js b/functions/api/routes/content/contentCheckPATCH.js index 7ca4ced..24fd00f 100644 --- a/functions/api/routes/content/contentCheckPATCH.js +++ b/functions/api/routes/content/contentCheckPATCH.js @@ -1,10 +1,9 @@ -const functions = require('firebase-functions'); -const slackAPI = require('../../../middlewares/slackAPI'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); const db = require('../../../db/db'); const { contentDB } = require('../../../db'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route PATCH /content/check @@ -12,7 +11,7 @@ const { contentDB } = require('../../../db'); * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { contentId } = req.body @@ -21,29 +20,15 @@ module.exports = async (req, res) => { return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE)); } - let client; + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; - try { - client = await db.connect(req); + const content = await contentDB.toggleContent(dbConnection, contentId); - const content = await contentDB.toggleContent(client, contentId); - - if (!content) { - // 특정 콘텐츠 id를 가진 콘텐츠가 존재하지 않을 때 - return res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CONTENT)); - } - - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.TOGGLE_CONTENT_SUCCESS, content)); - - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - - } finally { - client.release(); + if (!content) { + // 특정 콘텐츠 id를 가진 콘텐츠가 존재하지 않을 때 + return res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CONTENT)); } -}; \ No newline at end of file + + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.TOGGLE_CONTENT_SUCCESS, content)); +}); \ No newline at end of file diff --git a/functions/api/routes/content/contentDELETE.js b/functions/api/routes/content/contentDELETE.js index d923126..e0d8265 100644 --- a/functions/api/routes/content/contentDELETE.js +++ b/functions/api/routes/content/contentDELETE.js @@ -1,5 +1,3 @@ -const functions = require('firebase-functions'); -const slackAPI = require('../../../middlewares/slackAPI'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); @@ -9,6 +7,7 @@ const { deleteNotification } = require('../../../lib/pushServerHandlers'); const dayjs = require('dayjs'); const timezone = require('dayjs/plugin/timezone'); const utc = require('dayjs/plugin/utc'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route DELETE /content/:contentId @@ -16,7 +15,7 @@ const utc = require('dayjs/plugin/utc'); * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { contentId } = req.params; const { userId } = req.user; @@ -26,49 +25,36 @@ module.exports = async (req, res) => { return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE)); } - let client; + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; dayjs().format(); dayjs.extend(utc); dayjs.extend(timezone); dayjs.tz.setDefault('Asia/Seoul'); - - try { - client = await db.connect(req); - const content = await contentDB.getContentById(client, contentId); - if (content.userId !== userId) { - return res.status(statusCode.FORBIDDEN).send(util.fail(statusCode.FORBIDDEN, responseMessage.FORBIDDEN)); - } + const content = await contentDB.getContentById(dbConnection, contentId); + if (content.userId !== userId) { + return res.status(statusCode.FORBIDDEN).send(util.fail(statusCode.FORBIDDEN, responseMessage.FORBIDDEN)); + } - const deletedContent = await contentDB.deleteContent(client, contentId, userId); - if (!deletedContent) { - // 대상 콘텐츠가 없는 경우, 콘텐츠 삭제 실패 - return res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CONTENT)); - } + const deletedContent = await contentDB.deleteContent(dbConnection, contentId, userId); + if (!deletedContent) { + // 대상 콘텐츠가 없는 경우, 콘텐츠 삭제 실패 + return res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CONTENT)); + } - if (content.isNotified && content.notificationTime > dayjs().tz().$d) { - // 알림 예정 일 때 푸시서버에서도 삭제 - const response = await deleteNotification(contentId); + if (content.isNotified && content.notificationTime > dayjs().tz().$d) { + // 알림 예정 일 때 푸시서버에서도 삭제 + const response = await deleteNotification(contentId); - if (response.status !== 200) { - return res.status(res.status).send(util.fail(response.status, responseMessage.PUSH_SERVER_ERROR)); - } + if (response.status !== 200) { + return res.status(res.status).send(util.fail(response.status, responseMessage.PUSH_SERVER_ERROR)); } + } - await categoryContentDB.deleteCategoryContentByContentId(client, contentId); + await categoryContentDB.deleteCategoryContentByContentId(dbConnection, contentId); - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.DELETE_CONTENT_SUCCESS)); - - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - } finally { - client.release(); - } -}; \ No newline at end of file + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.DELETE_CONTENT_SUCCESS)); +}); \ No newline at end of file diff --git a/functions/api/routes/content/contentListGET.js b/functions/api/routes/content/contentListGET.js index da22d41..6b69a53 100644 --- a/functions/api/routes/content/contentListGET.js +++ b/functions/api/routes/content/contentListGET.js @@ -1,13 +1,12 @@ const _ = require('lodash'); -const functions = require('firebase-functions'); -const slackAPI = require('../../../middlewares/slackAPI'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); const db = require('../../../db/db'); const { contentDB } = require('../../../db'); const dayjs = require('dayjs'); -const customParseFormat = require('dayjs/plugin/customParseFormat') +const customParseFormat = require('dayjs/plugin/customParseFormat'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route GET /content @@ -15,70 +14,57 @@ const customParseFormat = require('dayjs/plugin/customParseFormat') * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { userId } = req.user; const { option, filter } = req.query; - let client; - - try { - client = await db.connect(req); - let contents; + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; - if (option == "all") { - // 전체 조회 - contents = await contentDB.getContentsByFilter(client, userId, filter); - } else if (option == "notified") { - // 알림 설정된 콘텐츠만 조회 - contents = await contentDB.getContentsByFilterAndNotified(client, userId, true, filter) + let contents; + if (option === "all") { + // 전체 조회 + contents = await contentDB.getContentsByFilter(dbConnection, userId, filter); + } else if (option === "notified") { + // 알림 설정된 콘텐츠만 조회 + contents = await contentDB.getContentsByFilterAndNotified(dbConnection, userId, true, filter) } else { - // is_seen에 따라 조회 - contents = await contentDB.getContentsByFilterAndSeen(client, userId, option, filter); + // is_seen에 따라 조회 + contents = await contentDB.getContentsByFilterAndSeen(dbConnection, userId, option, filter); } - if (filter == "reverse") { - // DESC를 이용했으므로 다시 reverse - contents = contents.reverse(); + + if (filter === "reverse") { + // DESC를 이용했으므로 다시 reverse + contents = contents.reverse(); } - if (filter == "seen_at") { - // 최근 조회 순 기준인 경우, 조회하지 않은 콘텐츠 제외 - const removedElements = _.remove(contents, function(content) { - return content.isSeen === false; - }); + if (filter === "seen_at") { + // 최근 조회 순 기준인 경우, 조회하지 않은 콘텐츠 제외 + const removedElements = _.remove(contents, function(content) { + return content.isSeen === false; + }); } - dayjs().format() - dayjs.extend(customParseFormat) + dayjs().format() + dayjs.extend(customParseFormat) - const result = await Promise.all(contents.map(content => { - // 시간 데이터 dayjs로 format 수정 - content.createdAt = dayjs(`${content.createdAt}`).format("YYYY-MM-DD HH:mm"); // createdAt 수정 - if (content.notificationTime) { - // notificationTime이 존재할 경우, format 수정 - content.notificationTime = dayjs(`${content.notificationTime}`).format("YYYY-MM-DD HH:mm"); - } else { - // notificationTime이 존재하지 않는 경우, null을 빈 문자열로 변경 - content.notificationTime = ""; - } - if (content.seenAt) { - content.seenAt = dayjs(`${content.seenAt}`).format("YYYY-MM-DD HH:mm"); - } else { - content.seenAt = ""; - } - return content; - })); + const result = await Promise.all(contents.map(content => { + // 시간 데이터 dayjs로 format 수정 + content.createdAt = dayjs(`${content.createdAt}`).format("YYYY-MM-DD HH:mm"); // createdAt 수정 + if (content.notificationTime) { + // notificationTime이 존재할 경우, format 수정 + content.notificationTime = dayjs(`${content.notificationTime}`).format("YYYY-MM-DD HH:mm"); + } else { + // notificationTime이 존재하지 않는 경우, null을 빈 문자열로 변경 + content.notificationTime = ""; + } + if (content.seenAt) { + content.seenAt = dayjs(`${content.seenAt}`).format("YYYY-MM-DD HH:mm"); + } else { + content.seenAt = ""; + } + return content; + })); - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.READ_ALL_CONTENT_SUCCESS , result)); - - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - - } finally { - client.release(); - } -}; \ No newline at end of file + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.READ_ALL_CONTENT_SUCCESS , result)); +}); \ No newline at end of file diff --git a/functions/api/routes/content/contentNotificationDELETE.js b/functions/api/routes/content/contentNotificationDELETE.js index 54ab38b..a7bee4f 100644 --- a/functions/api/routes/content/contentNotificationDELETE.js +++ b/functions/api/routes/content/contentNotificationDELETE.js @@ -1,6 +1,4 @@ const _ = require('lodash'); -const functions = require('firebase-functions'); -const slackAPI = require('../../../middlewares/slackAPI'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); @@ -10,6 +8,8 @@ const { deleteNotification } = require('../../../lib/pushServerHandlers'); const dayjs = require('dayjs'); const timezone = require('dayjs/plugin/timezone'); const utc = require('dayjs/plugin/utc'); +const asyncWrapper = require('../../../lib/asyncWrapper'); + /** * @route DELETE /content/:contentId/notification @@ -17,11 +17,12 @@ const utc = require('dayjs/plugin/utc'); * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { userId } = req.user; const { contentId } = req.params; - let client; + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; dayjs().format(); dayjs.extend(utc); @@ -29,40 +30,27 @@ module.exports = async (req, res) => { dayjs.tz.setDefault('Asia/Seoul'); - try { - client = await db.connect(req); - - const content = await contentDB.getContentById(client, contentId); - if (!content) { - return res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CONTENT)); - } - if (content.userId !== userId) { - return res.status(statusCode.FORBIDDEN).send(util.fail(statusCode.FORBIDDEN, responseMessage.FORBIDDEN)); - } - if (!content.isNotified) { - return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.OUT_OF_VALUE)); - } + const content = await contentDB.getContentById(dbConnection, contentId); + if (!content) { + return res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CONTENT)); + } + if (content.userId !== userId) { + return res.status(statusCode.FORBIDDEN).send(util.fail(statusCode.FORBIDDEN, responseMessage.FORBIDDEN)); + } + if (!content.isNotified) { + return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.OUT_OF_VALUE)); + } - if (content.notificationTime > dayjs().tz().$d) { - // 알림 예정 일 때 푸시서버에서도 삭제 - const response = await deleteNotification(contentId); + if (content.notificationTime > dayjs().tz().$d) { + // 알림 예정 일 때 푸시서버에서도 삭제 + const response = await deleteNotification(contentId); - if (response.status !== 200) { - return res.status(res.status).send(util.fail(response.status, responseMessage.PUSH_SERVER_ERROR)); - } + if (response.status !== 200) { + return res.status(res.status).send(util.fail(response.status, responseMessage.PUSH_SERVER_ERROR)); } + } - await contentDB.updateContentNotification(client, contentId, null, false); - - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.DELETE_CONTENT_NOTIFICATION_SUCCESS)); - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); + await contentDB.updateContentNotification(dbConnection, contentId, null, false); - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - } finally { - client.release(); - } -}; \ No newline at end of file + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.DELETE_CONTENT_NOTIFICATION_SUCCESS)); +}); \ No newline at end of file diff --git a/functions/api/routes/content/contentNotificationGET.js b/functions/api/routes/content/contentNotificationGET.js index 35dec0f..0cf3445 100644 --- a/functions/api/routes/content/contentNotificationGET.js +++ b/functions/api/routes/content/contentNotificationGET.js @@ -1,6 +1,4 @@ const _ = require('lodash'); -const functions = require('firebase-functions'); -const slackAPI = require('../../../middlewares/slackAPI'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); @@ -8,6 +6,7 @@ const db = require('../../../db/db'); const { contentDB } = require('../../../db'); const dayjs = require('dayjs'); const customParseFormat = require('dayjs/plugin/customParseFormat') +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route GET /content/notification?option= @@ -15,49 +14,36 @@ const customParseFormat = require('dayjs/plugin/customParseFormat') * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { userId } = req.user; const { option } = req.query; - let client; + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; dayjs().format() dayjs.extend(customParseFormat) - try { - client = await db.connect(req); - - let contents; + let contents; - if (option === 'before') { - contents = await contentDB.getScheduledContentNotification(client, userId); - } else if(option === 'after') { - contents = await contentDB.getExpiredContentNotification(client, userId); + if (option === 'before') { + contents = await contentDB.getScheduledContentNotification(dbConnection, userId); + } else if(option === 'after') { + contents = await contentDB.getExpiredContentNotification(dbConnection, userId); + } else { + return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.OUT_OF_VALUE)); + } + + const contentList = await Promise.all(contents.map(content => { + if (content.notificationTime) { + content.notificationTime = dayjs(`${content.notificationTime}`).format('YYYY-MM-DD HH:mm'); } else { - return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.OUT_OF_VALUE)); + content.notificationTime = ''; } - const contentList = await Promise.all(contents.map(content => { - if (content.notificationTime) { - content.notificationTime = dayjs(`${content.notificationTime}`).format('YYYY-MM-DD HH:mm'); - } else { - content.notificationTime = ''; - } - - content.createdAt = dayjs(`${content.createdAt}`).format('YYYY-MM-DD'); - return content; - })); + content.createdAt = dayjs(`${content.createdAt}`).format('YYYY-MM-DD'); + return content; + })); - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.READ_CONTENT_NOTIFICATION_SUCCESS, contentList)); - - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - } finally { - client.release(); - } -}; \ No newline at end of file + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.READ_CONTENT_NOTIFICATION_SUCCESS, contentList)); +}); \ No newline at end of file diff --git a/functions/api/routes/content/contentNotificationPATCH.js b/functions/api/routes/content/contentNotificationPATCH.js index 8e39433..c54c794 100644 --- a/functions/api/routes/content/contentNotificationPATCH.js +++ b/functions/api/routes/content/contentNotificationPATCH.js @@ -1,14 +1,13 @@ -const functions = require('firebase-functions'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); -const slackAPI = require('../../../middlewares/slackAPI'); const db = require('../../../db/db'); const { contentDB, userDB } = require('../../../db'); const { modifyNotificationTime, createNotification } = require('../../../lib/pushServerHandlers'); const dayjs = require('dayjs'); const timezone = require('dayjs/plugin/timezone'); const utc = require('dayjs/plugin/utc'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route PATCH /content/:contentId/notification @@ -16,7 +15,7 @@ const utc = require('dayjs/plugin/utc'); * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { contentId } = req.params; const { notificationTime } = req.body; const { userId } = req.user; @@ -25,7 +24,9 @@ module.exports = async (req, res) => { return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE)); } - let client; + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; + let response; dayjs().format(); @@ -34,49 +35,36 @@ module.exports = async (req, res) => { dayjs.tz.setDefault('Asia/Seoul'); - try { - client = await db.connect(req); - - const content = await contentDB.getContentById(client, contentId); - if (!content) { - return res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CONTENT)); - } - if (content.userId !== userId) { - return res.status(statusCode.FORBIDDEN).send(util.fail(statusCode.FORBIDDEN, responseMessage.FORBIDDEN)); - } - - const user = await userDB.getUser(client, userId); + const content = await contentDB.getContentById(dbConnection, contentId); + if (!content) { + return res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CONTENT)); + } + if (content.userId !== userId) { + return res.status(statusCode.FORBIDDEN).send(util.fail(statusCode.FORBIDDEN, responseMessage.FORBIDDEN)); + } - if (content.notificationTime && content.notificationTime > dayjs().tz().$d) { - response = await modifyNotificationTime(contentId, notificationTime); - } else { - const data = { - userId: user.mongoUserId, - time: notificationTime, - ogTitle: content.title, - ogImage: content.image, - url: content.url, - isSeen: content.isSeen, - contentId - }; - response = await createNotification(data); - } - if (response.status !== 200 && response.status !== 201) { - return res.status(response.status).send(util.fail(response.status, responseMessage.PUSH_SERVER_ERROR)); - } + const user = await userDB.getUser(dbConnection, userId); - await contentDB.updateContentNotification(client, contentId, notificationTime, true); + if (content.notificationTime && content.notificationTime > dayjs().tz().$d) { + response = await modifyNotificationTime(contentId, notificationTime); + } else { + const data = { + userId: user.mongoUserId, + time: notificationTime, + ogTitle: content.title, + ogImage: content.image, + url: content.url, + isSeen: content.isSeen, + contentId + }; + response = await createNotification(data); + } + if (response.status !== 200 && response.status !== 201) { + return res.status(response.status).send(util.fail(response.status, responseMessage.PUSH_SERVER_ERROR)); + } - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.UPDATE_CONTENT_NOTIFICATION_SUCCESS)); - } catch (error) { - console.log(error); - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); + await contentDB.updateContentNotification(dbConnection, contentId, notificationTime, true); - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - } finally { - client.release(); - } -}; + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.UPDATE_CONTENT_NOTIFICATION_SUCCESS)); +}); diff --git a/functions/api/routes/content/contentPOST.js b/functions/api/routes/content/contentPOST.js index f9bb868..7d2cbf0 100644 --- a/functions/api/routes/content/contentPOST.js +++ b/functions/api/routes/content/contentPOST.js @@ -1,5 +1,3 @@ -const functions = require('firebase-functions'); -const slackAPI = require('../../../middlewares/slackAPI'); const util = require('../../../lib/util'); const { createNotification } = require('../../../lib/pushServerHandlers'); const statusCode = require('../../../constants/statusCode'); @@ -8,6 +6,7 @@ const db = require('../../../db/db'); const { contentDB, categoryDB, categoryContentDB, userDB } = require('../../../db'); const dotenv = require('dotenv'); const dummyImages = require('../../../constants/dummyImages'); +const asyncWrapper = require('../../../lib/asyncWrapper'); dotenv.config(); @@ -17,7 +16,7 @@ dotenv.config(); * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { title, description, url, isNotified, categoryIds } = req.body; let { image, notificationTime } = req.body; @@ -38,63 +37,50 @@ module.exports = async (req, res) => { notificationTime = null; } - let client; - - try { - client = await db.connect(req); - const user = await userDB.getUser(client, userId); - let flag = true; // flag 변수 결과에 따라 categoryContent를 추가할 지, 에러를 보낼 지 결정 - for (const categoryId of categoryIds) { - // 카테고리 배열의 id 중 하나라도 유저의 카테고리가 아닐 경우, categoryContent를 추가하지 않고 에러 전송 - const category = await categoryDB.getCategory(client, categoryId); - if (!category || category.userId !== userId) { - // 카테고리가 아예 존재하지 않거나, 해당 유저의 카테고리가 아닌 경우 - flag = false; - } + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; + + const user = await userDB.getUser(dbConnection, userId); + let flag = true; // flag 변수 결과에 따라 categoryContent를 추가할 지, 에러를 보낼 지 결정 + for (const categoryId of categoryIds) { + // 카테고리 배열의 id 중 하나라도 유저의 카테고리가 아닐 경우, categoryContent를 추가하지 않고 에러 전송 + const category = await categoryDB.getCategory(dbConnection, categoryId); + if (!category || category.userId !== userId) { + // 카테고리가 아예 존재하지 않거나, 해당 유저의 카테고리가 아닌 경우 + flag = false; } + } - if (flag) { - // 유저가 해당 카테고리를 가지고 있을 때 - const content = await contentDB.addContent(client, userId, title, description, image, url, isNotified, notificationTime); - for (const categoryId of categoryIds) { - // 중복 카테고리 허용 - await categoryContentDB.addCategoryContent(client, categoryId, content.id); + if (flag) { + // 유저가 해당 카테고리를 가지고 있을 때 + const content = await contentDB.addContent(dbConnection, userId, title, description, image, url, isNotified, notificationTime); + for (const categoryId of categoryIds) { + // 중복 카테고리 허용 + await categoryContentDB.addCategoryContent(dbConnection, categoryId, content.id); + }; + + if (user.mongoUserId && content.isNotified) { + const notificationData = { + userId: user.mongoUserId, + contentId: content.id, + ogTitle: content.title, + ogImage: content.image, + url: content.url, + time: notificationTime, + isSeen: false, }; - - if (user.mongoUserId && content.isNotified) { - const notificationData = { - userId: user.mongoUserId, - contentId: content.id, - ogTitle: content.title, - ogImage: content.image, - url: content.url, - time: notificationTime, - isSeen: false, - }; - - const response = await createNotification(notificationData); + + const response = await createNotification(notificationData); - if (response.status !== 201) { - return res.status(response.status).send(util.fail(response.status, responseMessage.PUSH_SERVER_ERROR)); - } + if (response.status !== 201) { + return res.status(response.status).send(util.fail(response.status, responseMessage.PUSH_SERVER_ERROR)); } + } - res.status(statusCode.CREATED).send(util.success(statusCode.CREATED, responseMessage.ADD_ONE_CONTENT_SUCCESS, { contentId : content.id })); - - } else { - // 유저가 해당 카테고리를 가지고 있지 않을 때 - res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CATEGORY)); - } - - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); + res.status(statusCode.CREATED).send(util.success(statusCode.CREATED, responseMessage.ADD_ONE_CONTENT_SUCCESS, { contentId : content.id })); - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - - } finally { - client.release(); - }; -}; \ No newline at end of file + } else { + // 유저가 해당 카테고리를 가지고 있지 않을 때 + res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CATEGORY)); + } +}); \ No newline at end of file diff --git a/functions/api/routes/content/contentRecentListGET.js b/functions/api/routes/content/contentRecentListGET.js index 7ce3ae2..93592ed 100644 --- a/functions/api/routes/content/contentRecentListGET.js +++ b/functions/api/routes/content/contentRecentListGET.js @@ -1,5 +1,3 @@ -const functions = require('firebase-functions'); -const slackAPI = require('../../../middlewares/slackAPI'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); @@ -7,6 +5,7 @@ const db = require('../../../db/db'); const { contentDB, categoryContentDB } = require('../../../db'); const dayjs = require('dayjs'); const customParseFormat = require('dayjs/plugin/customParseFormat'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route GET /content/recent @@ -14,52 +13,38 @@ const customParseFormat = require('dayjs/plugin/customParseFormat'); * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { userId } = req.user; - let client; - - try { - client = await db.connect(req); - - const contents = await contentDB.getRecentContents(client, userId); // 최대 20개까지 조회 - await Promise.all( // 각 콘텐츠가 소속된 카테고리 병렬 탐색 - contents.map(async (content) => { - let categories = await categoryContentDB.getCategoryContentByContentId(client, content.id, userId); - content.firstCategory = categories[0]?.title; - content.extraCategoryCount = categories.length - 1; - return; - }) - ); - - dayjs().format() - dayjs.extend(customParseFormat) - - const result = await Promise.all(contents.map(content => { - // 시간 데이터 dayjs로 format 수정 - content.createdAt = dayjs(`${content.createdAt}`).format("YYYY-MM-DD HH:mm"); // createdAt 수정 - if (content.notificationTime) { - // notificationTime이 존재할 경우, format 수정 - content.notificationTime = dayjs(`${content.notificationTime}`).format("YYYY-MM-DD HH:mm"); - } else { - // notificationTime이 존재하지 않는 경우, null을 빈 문자열로 변경 - content.notificationTime = ""; - } - return content; - })); - - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.READ_RECENT_SAVED_CONTENT_SUCCESS, result)); - - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - - } finally { - client.release(); - } -}; \ No newline at end of file + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; + + const contents = await contentDB.getRecentContents(dbConnection, userId); // 최대 20개까지 조회 + await Promise.all( // 각 콘텐츠가 소속된 카테고리 병렬 탐색 + contents.map(async (content) => { + let categories = await categoryContentDB.getCategoryContentByContentId(dbConnection, content.id, userId); + content.firstCategory = categories[0]?.title; + content.extraCategoryCount = categories.length - 1; + return; + }) + ); + + dayjs().format() + dayjs.extend(customParseFormat) + + const result = await Promise.all(contents.map(content => { + // 시간 데이터 dayjs로 format 수정 + content.createdAt = dayjs(`${content.createdAt}`).format("YYYY-MM-DD HH:mm"); // createdAt 수정 + if (content.notificationTime) { + // notificationTime이 존재할 경우, format 수정 + content.notificationTime = dayjs(`${content.notificationTime}`).format("YYYY-MM-DD HH:mm"); + } else { + // notificationTime이 존재하지 않는 경우, null을 빈 문자열로 변경 + content.notificationTime = ""; + } + return content; + })); + + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.READ_RECENT_SAVED_CONTENT_SUCCESS, result)); +}); \ No newline at end of file diff --git a/functions/api/routes/content/contentRenamePATCH.js b/functions/api/routes/content/contentRenamePATCH.js index 280b503..df52d93 100644 --- a/functions/api/routes/content/contentRenamePATCH.js +++ b/functions/api/routes/content/contentRenamePATCH.js @@ -1,5 +1,3 @@ -const functions = require('firebase-functions'); -const slackAPI = require('../../../middlewares/slackAPI'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); @@ -9,6 +7,7 @@ const { modifyContentTitle } = require('../../../lib/pushServerHandlers'); const dayjs = require('dayjs'); const timezone = require('dayjs/plugin/timezone'); const utc = require('dayjs/plugin/utc'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route PATCH /content/title/:contentId @@ -16,7 +15,7 @@ const utc = require('dayjs/plugin/utc'); * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { contentId } = req.params; const { newTitle } = req.body; @@ -26,7 +25,8 @@ module.exports = async (req, res) => { return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE)); } - let client; + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; dayjs().format(); dayjs.extend(utc); @@ -34,34 +34,19 @@ module.exports = async (req, res) => { dayjs.tz.setDefault('Asia/Seoul'); - try { - client = await db.connect(req); + const content = await contentDB.renameContent(dbConnection, contentId, newTitle); - const content = await contentDB.renameContent(client, contentId, newTitle); - - if (!content) { - // 대상 콘텐츠가 없는 경우, 콘텐츠 제목 변경 실패 - return res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CONTENT)); - } + if (!content) { + // 대상 콘텐츠가 없는 경우, 콘텐츠 제목 변경 실패 + return res.status(statusCode.NOT_FOUND).send(util.fail(statusCode.NOT_FOUND, responseMessage.NO_CONTENT)); + } - if (content.isNotified && content.notificationTime > dayjs().tz().$d) { - const response = await modifyContentTitle(contentId, newTitle); - if (response.status !== 204) { - return res.status(res.status).send(util.fail(response.status, responseMessage.PUSH_SERVER_ERROR)); - } + if (content.isNotified && content.notificationTime > dayjs().tz().$d) { + const response = await modifyContentTitle(contentId, newTitle); + if (response.status !== 204) { + return res.status(res.status).send(util.fail(response.status, responseMessage.PUSH_SERVER_ERROR)); } - - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.RENAME_CONTENT_SUCCESS)); - - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - - } finally { - client.release(); } -}; \ No newline at end of file + + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.RENAME_CONTENT_SUCCESS)); +}); \ No newline at end of file diff --git a/functions/api/routes/content/contentSearchGET.js b/functions/api/routes/content/contentSearchGET.js index cba091a..4b323cc 100644 --- a/functions/api/routes/content/contentSearchGET.js +++ b/functions/api/routes/content/contentSearchGET.js @@ -1,12 +1,11 @@ -const functions = require('firebase-functions'); -const slackAPI = require('../../../middlewares/slackAPI'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); const db = require('../../../db/db'); const { contentDB } = require('../../../db'); const dayjs = require('dayjs'); -const customParseFormat = require('dayjs/plugin/customParseFormat') +const customParseFormat = require('dayjs/plugin/customParseFormat'); +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route GET /content/search?keyword= @@ -14,7 +13,7 @@ const customParseFormat = require('dayjs/plugin/customParseFormat') * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { keyword } = req.query; const { userId } = req.user; @@ -23,40 +22,26 @@ module.exports = async (req, res) => { // 검색 키워드가 없을 때 에러 처리 return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE)); } - let client; - - try { - client = await db.connect(req); - - const contents = await contentDB.searchContent(client, userId, keyword); - - dayjs().format() - dayjs.extend(customParseFormat) - - const result = await Promise.all(contents.map(content => { - // 시간 데이터 dayjs로 format 수정 - content.createdAt = dayjs(`${content.createdAt}`).format("YYYY-MM-DD HH:mm"); // createdAt 수정 - if (content.notificationTime) { - // notificationTime이 존재할 경우, format 수정 - content.notificationTime = dayjs(`${content.notificationTime}`).format("YYYY-MM-DD HH:mm"); - } else { - // notificationTime이 존재하지 않는 경우, null을 빈 문자열로 변경 - content.notificationTime = ""; - } - return content; - })); - - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.KEYWORD_SEARCH_CONTENT_SUCCESS, result)); - - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - - } finally { - client.release(); - } -}; \ No newline at end of file + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; + + const contents = await contentDB.searchContent(dbConnection, userId, keyword); + + dayjs().format() + dayjs.extend(customParseFormat) + + const result = await Promise.all(contents.map(content => { + // 시간 데이터 dayjs로 format 수정 + content.createdAt = dayjs(`${content.createdAt}`).format("YYYY-MM-DD HH:mm"); // createdAt 수정 + if (content.notificationTime) { + // notificationTime이 존재할 경우, format 수정 + content.notificationTime = dayjs(`${content.notificationTime}`).format("YYYY-MM-DD HH:mm"); + } else { + // notificationTime이 존재하지 않는 경우, null을 빈 문자열로 변경 + content.notificationTime = ""; + } + return content; + })); + + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.KEYWORD_SEARCH_CONTENT_SUCCESS, result)); +}); \ No newline at end of file diff --git a/functions/api/routes/content/contentUnseenListGET.js b/functions/api/routes/content/contentUnseenListGET.js index 3415791..e9ec045 100644 --- a/functions/api/routes/content/contentUnseenListGET.js +++ b/functions/api/routes/content/contentUnseenListGET.js @@ -1,5 +1,3 @@ -const functions = require('firebase-functions'); -const slackAPI = require('../../../middlewares/slackAPI'); const util = require('../../../lib/util'); const statusCode = require('../../../constants/statusCode'); const responseMessage = require('../../../constants/responseMessage'); @@ -7,6 +5,7 @@ const db = require('../../../db/db'); const { contentDB } = require('../../../db'); const dayjs = require('dayjs'); const customParseFormat = require('dayjs/plugin/customParseFormat') +const asyncWrapper = require('../../../lib/asyncWrapper'); /** * @route GET /content/unseen @@ -14,44 +13,30 @@ const customParseFormat = require('dayjs/plugin/customParseFormat') * @access Private */ -module.exports = async (req, res) => { +module.exports = asyncWrapper(async (req, res) => { const { userId } = req.user; - let client; - - try { - client = await db.connect(req); - - const contents = await contentDB.getUnseenContents(client, userId); - - dayjs().format() - dayjs.extend(customParseFormat) - - const result = await Promise.all(contents.map(content => { - // 시간 데이터 dayjs로 format 수정 - content.createdAt = dayjs(`${content.createdAt}`).format("YYYY-MM-DD HH:mm"); // createdAt 수정 - if (content.notificationTime) { - // notificationTime이 존재할 경우, format 수정 - content.notificationTime = dayjs(`${content.notificationTime}`).format("YYYY-MM-DD HH:mm"); - } else { - // notificationTime이 존재하지 않는 경우, null을 빈 문자열로 변경 - content.notificationTime = ""; - } - return content; - })); - - res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.READ_UNSEEN_CONTENT_SUCCESS, result)); - - } catch (error) { - functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); - console.log(error); - const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; - slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); - - res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR)); - - } finally { - client.release(); - } -}; \ No newline at end of file + const dbConnection = await db.connect(req); + req.dbConnection = dbConnection; + + const contents = await contentDB.getUnseenContents(dbConnection, userId); + + dayjs().format() + dayjs.extend(customParseFormat) + + const result = await Promise.all(contents.map(content => { + // 시간 데이터 dayjs로 format 수정 + content.createdAt = dayjs(`${content.createdAt}`).format("YYYY-MM-DD HH:mm"); // createdAt 수정 + if (content.notificationTime) { + // notificationTime이 존재할 경우, format 수정 + content.notificationTime = dayjs(`${content.notificationTime}`).format("YYYY-MM-DD HH:mm"); + } else { + // notificationTime이 존재하지 않는 경우, null을 빈 문자열로 변경 + content.notificationTime = ""; + } + return content; + })); + + res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.READ_UNSEEN_CONTENT_SUCCESS, result)); +}); \ No newline at end of file diff --git a/functions/api/routes/user/userUpdateFcmTokenPUT.js b/functions/api/routes/user/userUpdateFcmTokenPUT.js index be55cc2..44175dd 100644 --- a/functions/api/routes/user/userUpdateFcmTokenPUT.js +++ b/functions/api/routes/user/userUpdateFcmTokenPUT.js @@ -28,7 +28,7 @@ module.exports = async (req, res) => { const response = await modifyFcmToken(user.mongoUserId, fcmToken); - if (response.status != 204) { + if (response.status !== 204) { return res.status(response.statusCode).send(util.fail(response.statusCode, response.statusText)); } diff --git a/functions/db/categoryContent.js b/functions/db/categoryContent.js index 7236d98..cca9464 100644 --- a/functions/db/categoryContent.js +++ b/functions/db/categoryContent.js @@ -2,7 +2,7 @@ const _ = require('lodash'); const convertSnakeToCamel = require('../lib/convertSnakeToCamel'); const getAllCategoryContentByFilter = async (client, userId, categoryId, filter) => { - if (filter == "reverse") { + if (filter === "reverse") { // API 로직에서 reverse 할 것이므로 created_at 기준으로 정렬한다. filter = "created_at"; } @@ -21,7 +21,7 @@ const getAllCategoryContentByFilter = async (client, userId, categoryId, filter) }; const getCategoryContentByFilterAndNotified = async (client, userId, categoryId, option, filter) => { - if (filter == "reverse") { + if (filter === "reverse") { // API 로직에서 reverse 할 것이므로 createdAt 기준으로 정렬한다. filter = "created_at"; } @@ -40,7 +40,7 @@ const getCategoryContentByFilterAndNotified = async (client, userId, categoryId, }; const getCategoryContentByFilterAndSeen = async (client, userId, categoryId, option, filter) => { - if (filter == "reverse") { + if (filter === "reverse") { // API 로직에서 reverse 할 것이므로 createdAt 기준으로 정렬한다. filter = "created_at"; } diff --git a/functions/db/content.js b/functions/db/content.js index 4a0cc11..820755c 100644 --- a/functions/db/content.js +++ b/functions/db/content.js @@ -55,7 +55,7 @@ const toggleContent = async (client, contentId) => { }; const getContentsByFilter = async (client, userId, filter) => { - if (filter == "reverse") { + if (filter === "reverse") { // API 로직에서 reverse 할 것이므로 created_at 기준으로 정렬한다. filter = "created_at"; } @@ -72,7 +72,7 @@ const getContentsByFilter = async (client, userId, filter) => { }; const getContentsByFilterAndNotified = async (client, userId, option, filter) => { - if (filter == "reverse") { + if (filter === "reverse") { // API 로직에서 reverse 할 것이므로 created_at 기준으로 정렬한다. filter = "created_at"; } @@ -89,7 +89,7 @@ const getContentsByFilterAndNotified = async (client, userId, option, filter) => }; const getContentsByFilterAndSeen = async (client, userId, option, filter) => { - if (filter == "reverse") { + if (filter === "reverse") { // API 로직에서 reverse 할 것이므로 createdAt 기준으로 정렬한다. filter = "created_at"; } diff --git a/functions/lib/asyncWrapper.js b/functions/lib/asyncWrapper.js new file mode 100644 index 0000000..d25de34 --- /dev/null +++ b/functions/lib/asyncWrapper.js @@ -0,0 +1,13 @@ +module.exports = (fn) => { + return async (req, res, next) => { + try { + await fn(req, res, next); + } catch (error) { + next(error); + } finally { + if (req.dbConnection) { + req.dbConnection.release(); + } + } + }; +}; diff --git a/functions/middlewares/errorHandler.js b/functions/middlewares/errorHandler.js new file mode 100644 index 0000000..0c529a4 --- /dev/null +++ b/functions/middlewares/errorHandler.js @@ -0,0 +1,16 @@ +const functions = require('firebase-functions'); +const slackAPI = require('./slackAPI'); +const util = require('../lib/util'); +const statusCode = require('../constants/statusCode'); +const responseMessage = require('../constants/responseMessage'); + +module.exports = (error, req, res, next) => { + const errorStatusCode = error.statusCode ? error.statusCode : statusCode.INTERNAL_SERVER_ERROR; + const errorResponseMessage = error.responseMessage ? error.responseMessage : responseMessage.INTERNAL_SERVER_ERROR; + functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`); + console.log(error); + const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${req.user ? `uid:${req.user.userId}` : 'req.user 없음'} ${JSON.stringify(error)}`; + slackAPI.sendMessageToSlack(slackMessage, slackAPI.WEB_HOOK_ERROR_MONITORING); + + res.status(errorStatusCode).send(util.fail(errorStatusCode, errorResponseMessage)); +} \ No newline at end of file diff --git a/functions/package-lock.json b/functions/package-lock.json index d9a373b..f58a923 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -6,6 +6,7 @@ "": { "name": "functions", "dependencies": { + "@sentry/node": "^7.52.1", "axios": "^0.24.0", "busboy": "^0.3.1", "cookie-parser": "^1.4.6", @@ -470,6 +471,91 @@ "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", "optional": true }, + "node_modules/@sentry-internal/tracing": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.52.1.tgz", + "integrity": "sha512-6N99rE+Ek0LgbqSzI/XpsKSLUyJjQ9nychViy+MP60p1x+hllukfTsDbNtUNrPlW0Bx+vqUrWKkAqmTFad94TQ==", + "dependencies": { + "@sentry/core": "7.52.1", + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry-internal/tracing/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@sentry/core": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.52.1.tgz", + "integrity": "sha512-36clugQu5z/9jrit1gzI7KfKbAUimjRab39JeR0mJ6pMuKLTTK7PhbpUAD4AQBs9qVeXN2c7h9SVZiSA0UDvkg==", + "dependencies": { + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/core/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@sentry/node": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.52.1.tgz", + "integrity": "sha512-n3frjYbkY/+eZ5RTQMaipv6Hh9w3ia40GDeRK6KJQit7OLKLmXisD+FsdYzm8Jc784csSvb6HGGVgqLpO1p9Og==", + "dependencies": { + "@sentry-internal/tracing": "7.52.1", + "@sentry/core": "7.52.1", + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/@sentry/types": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.52.1.tgz", + "integrity": "sha512-OMbGBPrJsw0iEXwZ2bJUYxewI1IEAU2e1aQGc0O6QW5+6hhCh+8HO8Xl4EymqwejjztuwStkl6G1qhK+Q0/Row==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/utils": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.52.1.tgz", + "integrity": "sha512-MPt1Xu/jluulknW8CmZ2naJ53jEdtdwCBSo6fXJvOTI0SDqwIPbXDVrsnqLAhVJuIN7xbkj96nuY/VBR6S5sWg==", + "dependencies": { + "@sentry/types": "7.52.1", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/utils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@sindresorhus/is": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", @@ -687,7 +773,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "optional": true, "dependencies": { "debug": "4" }, @@ -2829,7 +2914,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "optional": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -3318,6 +3402,11 @@ "node": ">=8" } }, + "node_modules/lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -5534,6 +5623,84 @@ "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", "optional": true }, + "@sentry-internal/tracing": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.52.1.tgz", + "integrity": "sha512-6N99rE+Ek0LgbqSzI/XpsKSLUyJjQ9nychViy+MP60p1x+hllukfTsDbNtUNrPlW0Bx+vqUrWKkAqmTFad94TQ==", + "requires": { + "@sentry/core": "7.52.1", + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@sentry/core": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.52.1.tgz", + "integrity": "sha512-36clugQu5z/9jrit1gzI7KfKbAUimjRab39JeR0mJ6pMuKLTTK7PhbpUAD4AQBs9qVeXN2c7h9SVZiSA0UDvkg==", + "requires": { + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@sentry/node": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.52.1.tgz", + "integrity": "sha512-n3frjYbkY/+eZ5RTQMaipv6Hh9w3ia40GDeRK6KJQit7OLKLmXisD+FsdYzm8Jc784csSvb6HGGVgqLpO1p9Og==", + "requires": { + "@sentry-internal/tracing": "7.52.1", + "@sentry/core": "7.52.1", + "@sentry/types": "7.52.1", + "@sentry/utils": "7.52.1", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@sentry/types": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.52.1.tgz", + "integrity": "sha512-OMbGBPrJsw0iEXwZ2bJUYxewI1IEAU2e1aQGc0O6QW5+6hhCh+8HO8Xl4EymqwejjztuwStkl6G1qhK+Q0/Row==" + }, + "@sentry/utils": { + "version": "7.52.1", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.52.1.tgz", + "integrity": "sha512-MPt1Xu/jluulknW8CmZ2naJ53jEdtdwCBSo6fXJvOTI0SDqwIPbXDVrsnqLAhVJuIN7xbkj96nuY/VBR6S5sWg==", + "requires": { + "@sentry/types": "7.52.1", + "tslib": "^1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, "@sindresorhus/is": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", @@ -5725,7 +5892,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "optional": true, "requires": { "debug": "4" } @@ -7339,7 +7505,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "optional": true, "requires": { "agent-base": "6", "debug": "4" @@ -7736,6 +7901,11 @@ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" }, + "lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==" + }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", diff --git a/functions/package.json b/functions/package.json index f844701..6099093 100644 --- a/functions/package.json +++ b/functions/package.json @@ -16,6 +16,7 @@ }, "main": "index.js", "dependencies": { + "@sentry/node": "^7.52.1", "axios": "^0.24.0", "busboy": "^0.3.1", "cookie-parser": "^1.4.6",