From f8b7df185d9b67fc19d76c56704ac1c5d06d1540 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 16:14:03 +0900 Subject: [PATCH 01/33] Add: modules/events.js --- src/lottery/modules/admin.js | 77 ++++++++++++----------------------- src/lottery/modules/events.js | 50 +++++++++++++++++++++++ src/modules/adminResource.js | 2 + 3 files changed, 79 insertions(+), 50 deletions(-) create mode 100644 src/lottery/modules/events.js diff --git a/src/lottery/modules/admin.js b/src/lottery/modules/admin.js index 337d7d96..e95d1066 100644 --- a/src/lottery/modules/admin.js +++ b/src/lottery/modules/admin.js @@ -1,65 +1,42 @@ -const { useUserCreditAmount } = require("./credit"); -const { transactionModel } = require("./stores/mongo"); const { recordAction } = require("../../modules/adminResource"); const { eventEnv } = require("../../../loadenv"); -/** eventId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. */ -const creditTransfer = async (userId, amount, eventId, comment) => { - const user = await useUserCreditAmount(userId); - await user.update(amount); - - const transaction = new transactionModel({ - type: "get", - amount, - userId, - event: eventId, - comment, - }); - await transaction.save(); - - return transaction._id; -}; - -/** itemId가 없는 경우 null이 아닌 undefined를 넣어야 합니다. */ -/** itemType이 없는 경우 null이 아닌 undefined를 넣어야 합니다. */ -const creditWithdraw = async (userId, amount, itemId, itemType, comment) => { - const user = await useUserCreditAmount(userId); - await user.update(-amount); - - const transaction = new transactionModel({ - type: "use", - amount, - userId, - item: itemId, - itemType, - comment, - }); - await transaction.save(); - - return transaction._id; -}; +const { eventHandler } = require("./events"); const instagramRewardActionHandler = async (req, res, context) => { - const transactionId = await creditTransfer( + const result = await eventHandler( context?.record?.params?.userId, - eventEnv.instagramReward, - eventEnv.instagramEventId, - eventEnv.instagramComment + eventEnv.instagramEventId ); - - let record = context.record.toJSON(context.currentAdmin); - record.params.creditAmount += eventEnv.instagramReward; - - return { - record, - transactionId, - }; + const record = context.record.toJSON(context.currentAdmin); + + if (result) { + record.params.creditAmount += result.event.rewardAmount; + + return { + record, + notice: { + message: "성공적으로 보상을 지급했습니다.", + }, + response: { + transactionId: result.transactionId, + }, + }; + } else + return { + record, + notice: { + message: "보상을 지급하지 못했습니다. 이미 보상을 받은 유저입니다.", + type: "error", + }, + }; }; const instagramRewardActionLogs = [ "update", { action: "create", - target: (res, req, context) => `Transaction(_id = ${res.transactionId})`, + target: (res, req, context) => + `Transaction(_id = ${res.response.transactionId})`, }, ]; diff --git a/src/lottery/modules/events.js b/src/lottery/modules/events.js new file mode 100644 index 00000000..c62e565e --- /dev/null +++ b/src/lottery/modules/events.js @@ -0,0 +1,50 @@ +const { + eventStatusModel, + eventModel, + transactionModel, +} = require("./stores/mongo"); +const logger = require("../../modules/logger"); + +const eventHandler = async (userId, eventId) => { + const event = await eventModel.findOne({ _id: eventId }).lean(); + if (!event) { + logger.error(`알 수 없는 이벤트 ID 입니다: ${eventId}`); // 프로그래머의 실수로 인해서만 발생하므로 logger를 통해 오류를 알립니다. + return null; + } + + const eventStatus = await eventStatusModel.findOne({ userId }).lean(); + const eventCount = eventStatus.eventList.filter( + (event) => event.toString() === eventId + ).length; + if (eventCount >= event.maxCount) return null; // 이미 최대로 달성한 이벤트입니다. + + await eventStatusModel.updateOne( + { userId }, + { + $inc: { + creditAmount: event.rewardAmount, + }, + $push: { + eventList: eventId, + }, + } + ); + + const transaction = new transactionModel({ + type: "get", + amount: event.rewardAmount, + userId, + event: eventId, + comment: `${event.name} 달성 - ${event.rewardAmount}개 획득`, + }); + await transaction.save(); + + return { + event, + transactionId: transaction._id, + }; +}; + +module.exports = { + eventHandler, +}; diff --git a/src/modules/adminResource.js b/src/modules/adminResource.js index 655e928b..b75013b3 100644 --- a/src/modules/adminResource.js +++ b/src/modules/adminResource.js @@ -57,6 +57,8 @@ const defaultActionLogFeature = buildFeature({ }); const recordActionAfterHandler = (actions) => async (res, req, context) => { + if (!res.response) return res; + const actionsWrapper = Array.isArray(actions) ? actions : [actions]; for (const action of actionsWrapper) { if (typeof action === "string") { From b5238d78a05748686c21e64d5f109305e266ed04 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 16:16:07 +0900 Subject: [PATCH 02/33] Remove: instagramRewardAction --- src/lottery/index.js | 15 +++++------ src/lottery/modules/admin.js | 51 ------------------------------------ 2 files changed, 7 insertions(+), 59 deletions(-) delete mode 100644 src/lottery/modules/admin.js diff --git a/src/lottery/index.js b/src/lottery/index.js index d06be299..9ca0f3eb 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -7,7 +7,6 @@ const { } = require("./modules/stores/mongo"); const { buildResource } = require("../modules/adminResource"); -const { instagramRewardAction } = require("./modules/admin"); // [Routes] 기존 docs 라우터의 docs extend require("./routes/docs")(); @@ -27,15 +26,15 @@ lotteryRouter.use("/global-state", require("./routes/globalState")); lotteryRouter.use("/transactions", require("./routes/transactions")); lotteryRouter.use("/items", require("./routes/items")); -const eventStatusResource = buildResource([instagramRewardAction])( - eventStatusModel -); -const otherResources = [eventModel, itemModel, transactionModel].map( - buildResource() -); +const resources = [ + eventStatusModel, + eventModel, + itemModel, + transactionModel, +].map(buildResource()); module.exports = { checkReward, lotteryRouter, - resources: [eventStatusResource, ...otherResources], + resources, }; diff --git a/src/lottery/modules/admin.js b/src/lottery/modules/admin.js deleted file mode 100644 index e95d1066..00000000 --- a/src/lottery/modules/admin.js +++ /dev/null @@ -1,51 +0,0 @@ -const { recordAction } = require("../../modules/adminResource"); -const { eventEnv } = require("../../../loadenv"); - -const { eventHandler } = require("./events"); - -const instagramRewardActionHandler = async (req, res, context) => { - const result = await eventHandler( - context?.record?.params?.userId, - eventEnv.instagramEventId - ); - const record = context.record.toJSON(context.currentAdmin); - - if (result) { - record.params.creditAmount += result.event.rewardAmount; - - return { - record, - notice: { - message: "성공적으로 보상을 지급했습니다.", - }, - response: { - transactionId: result.transactionId, - }, - }; - } else - return { - record, - notice: { - message: "보상을 지급하지 못했습니다. 이미 보상을 받은 유저입니다.", - type: "error", - }, - }; -}; -const instagramRewardActionLogs = [ - "update", - { - action: "create", - target: (res, req, context) => - `Transaction(_id = ${res.response.transactionId})`, - }, -]; - -const instagramRewardAction = recordAction( - "instagramReward", - instagramRewardActionHandler, - instagramRewardActionLogs -); - -module.exports = { - instagramRewardAction, -}; From f047d853d7193fe31e5134b3388f72dc8ab3c549 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 17:58:55 +0900 Subject: [PATCH 03/33] Add: startat field in eventSchema --- src/lottery/modules/stores/mongo.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index b5e6f127..91dafbad 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -42,6 +42,10 @@ const eventSchema = Schema({ min: 0, validate: integerValidator, }, + startat: { + type: Date, + required: true, + }, expireat: { type: Date, required: true, From f190d7c5aecc2fddeb8c9745b41ec80b3ec2383c Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 18:06:43 +0900 Subject: [PATCH 04/33] Docs: startat field of the response of some endpoint --- src/lottery/routes/docs/globalState.js | 5 +++++ src/lottery/routes/docs/transactions.js | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 3ebb2f97..f236a7ed 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -66,6 +66,11 @@ globalStateDocs[`${apiPrefix}/`] = { description: "최대 달성 가능 횟수", example: 1, }, + startat: { + type: "string", + description: "달성할 수 있는 처음 시각", + example: "2023-01-01 00:00:00", + }, expireat: { type: "string", description: "달성할 수 있는 마지막 시각", diff --git a/src/lottery/routes/docs/transactions.js b/src/lottery/routes/docs/transactions.js index a51e5dbb..5ceb7e01 100644 --- a/src/lottery/routes/docs/transactions.js +++ b/src/lottery/routes/docs/transactions.js @@ -62,6 +62,11 @@ transactionsDocs[`${apiPrefix}/`] = { description: "최대 달성 가능 횟수", example: 1, }, + startat: { + type: "string", + description: "달성할 수 있는 처음 시각", + example: "2023-01-01 00:00:00", + }, expireat: { type: "string", description: "달성할 수 있는 마지막 시각", From 13a42caa7f1cff82fc57a0b7e90b5f9851ef80ef Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 18:36:05 +0900 Subject: [PATCH 05/33] Add: check if event can be achieved in eventHandler --- src/lottery/modules/events.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lottery/modules/events.js b/src/lottery/modules/events.js index c62e565e..e5b0cca4 100644 --- a/src/lottery/modules/events.js +++ b/src/lottery/modules/events.js @@ -18,6 +18,9 @@ const eventHandler = async (userId, eventId) => { ).length; if (eventCount >= event.maxCount) return null; // 이미 최대로 달성한 이벤트입니다. + const now = Date.now(); + if (now < event.startat || now > event.expireat) return null; + await eventStatusModel.updateOne( { userId }, { From 6c387ea9ccbc78b448d94878708865fcf3ec7dfe Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 22:41:25 +0900 Subject: [PATCH 06/33] Add: eventIds in loadenv.js --- loadenv.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/loadenv.js b/loadenv.js index 2bce4fb9..0bef02f3 100644 --- a/loadenv.js +++ b/loadenv.js @@ -39,4 +39,20 @@ module.exports = { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, eventMode: undefined, + eventIds: { + // required if eventMode === "2023fall" + firstLogin: process.env.EVENT_2023FALL_FIRST_LOGIN_ID, + payingAndSending: process.env.EVENT_2023FALL_PAYING_AND_SENDING_ID, + firstRoomCreation: process.env.EVENT_2023FALL_FIRST_ROOM_CREATION_ID, + roomSharing: process.env.EVENT_2023FALL_ROOM_SHARING_ID, + paying: process.env.EVENT_2023FALL_PAYING_ID, + sending: process.env.EVENT_2023FALL_SENDING_ID, + nicknameChanging: process.env.EVENT_2023FALL_NICKNAME_CHANGING_ID, + accountChanging: process.env.EVENT_2023FALL_ACCOUNT_CHANGING_ID, + AdPushAgreement: process.env.EVENT_2023FALL_AD_PUSH_AGREEMENT_ID, + eventSharingOnInstagram: + process.env.EVENT_2023FALL_EVENT_SHARING_ON_INSTAGRAM_ID, + purchaseSharingOnInstagram: + process.env.EVENT_2023FALL_PURCHASE_SHARING_ON_INSTAGRAM_ID, + }, }; From f6c3b944204ccabc3aa98d440e483e82cfaaee69 Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 22:44:38 +0900 Subject: [PATCH 07/33] Fix: rename AdPushAgreement to adPushAgreement --- loadenv.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loadenv.js b/loadenv.js index 0bef02f3..5ce5827d 100644 --- a/loadenv.js +++ b/loadenv.js @@ -49,7 +49,7 @@ module.exports = { sending: process.env.EVENT_2023FALL_SENDING_ID, nicknameChanging: process.env.EVENT_2023FALL_NICKNAME_CHANGING_ID, accountChanging: process.env.EVENT_2023FALL_ACCOUNT_CHANGING_ID, - AdPushAgreement: process.env.EVENT_2023FALL_AD_PUSH_AGREEMENT_ID, + adPushAgreement: process.env.EVENT_2023FALL_AD_PUSH_AGREEMENT_ID, eventSharingOnInstagram: process.env.EVENT_2023FALL_EVENT_SHARING_ON_INSTAGRAM_ID, purchaseSharingOnInstagram: From 6326969fdbdbaf5e5e5aa67e0f8d691657f5a86e Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 22:53:07 +0900 Subject: [PATCH 08/33] Add: contracts/2023fall.js --- src/lottery/index.js | 4 ++ src/lottery/modules/contracts/2023fall.js | 64 +++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/lottery/modules/contracts/2023fall.js diff --git a/src/lottery/index.js b/src/lottery/index.js index 9ca0f3eb..26d2195f 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -6,6 +6,7 @@ const { transactionModel, } = require("./modules/stores/mongo"); +const { eventMode } = requires("../../loadenv"); const { buildResource } = require("../modules/adminResource"); // [Routes] 기존 docs 라우터의 docs extend @@ -33,8 +34,11 @@ const resources = [ transactionModel, ].map(buildResource()); +const contracts = require(`./modules/contracts/${eventMode}`); + module.exports = { checkReward, lotteryRouter, resources, + contracts, }; diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js new file mode 100644 index 00000000..a4cda756 --- /dev/null +++ b/src/lottery/modules/contracts/2023fall.js @@ -0,0 +1,64 @@ +const { eventIds } = require("../../../../loadenv"); +const { eventHandler } = require("../events"); + +// 로그인할 때마다 호출해 주세요. +const requestFirstLoginEvent = async (userId) => { + return await eventHandler(userId, eventIds.firstLogin); +}; + +const requestPayingAndSendingEvent = async () => { + // TODO +}; + +// 방을 만들 때마다 호출해 주세요. +const requestFirstRoomCreation = async (userId) => { + return await eventHandler(userId, eventIds.firstRoomCreation); +}; + +const requestRoomSharingEvent = async () => { + // TODO +}; + +const requestPayingEvent = async () => { + // TODO +}; + +const requestSendingEvent = async () => { + // TODO +}; + +// 닉네임을 변경할 때마다 호출해 주세요. +const requestNicknameChangingEvent = async (userId) => { + return await eventHandler(userId, eventIds.nicknameChanging); +}; + +// 계좌를 변경할 때마다 호출해 주세요. +const requestAccountChangingEvent = async (userId) => { + return await eventHandler(userId, eventIds.accountChanging); +}; + +const requestAdPushAgreementEvent = async () => { + // TODO +}; + +const requestEventSharingOnInstagram = async () => { + // TODO +}; + +const requestPurchaseSharingOnInstagram = async () => { + // TODO +}; + +module.exports = { + requestFirstLoginEvent, + requestPayingAndSendingEvent, + requestFirstRoomCreation, + requestRoomSharingEvent, + requestPayingEvent, + requestSendingEvent, + requestNicknameChangingEvent, + requestAccountChangingEvent, + requestAdPushAgreementEvent, + requestEventSharingOnInstagram, + requestPurchaseSharingOnInstagram, +}; From 7d943f2a478a1745974cedfc51af8df5f36992bd Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 23:02:49 +0900 Subject: [PATCH 09/33] Add: implement firstLoginEvent --- src/lottery/index.js | 2 +- src/lottery/modules/contracts/2023fall.js | 1 + src/services/auth.js | 3 +++ src/services/auth.mobile.js | 3 +++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lottery/index.js b/src/lottery/index.js index 26d2195f..e06c2988 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -6,7 +6,7 @@ const { transactionModel, } = require("./modules/stores/mongo"); -const { eventMode } = requires("../../loadenv"); +const { eventMode } = require("../../loadenv"); const { buildResource } = require("../modules/adminResource"); // [Routes] 기존 docs 라우터의 docs extend diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index a4cda756..1a9bc97d 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -2,6 +2,7 @@ const { eventIds } = require("../../../../loadenv"); const { eventHandler } = require("../events"); // 로그인할 때마다 호출해 주세요. +// 사용된 곳: auth/tryLogin, auth.mobile/tryLoginHandler const requestFirstLoginEvent = async (userId) => { return await eventHandler(userId, eventIds.firstLogin); }; diff --git a/src/services/auth.js b/src/services/auth.js index bab3fc71..9a3fef85 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -16,6 +16,8 @@ const { const jwt = require("../modules/auths/jwt"); const logger = require("../modules/logger"); +const { contracts } = require("../lottery"); + // SPARCS SSO const Client = require("../modules/auths/sparcssso"); const client = new Client(sparcsssoEnv?.id, sparcsssoEnv?.key); @@ -91,6 +93,7 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { } login(req, userData.sid, user.id, user._id, user.name); + await contracts.requestFirstLoginEvent(user._id); res.redirect(new URL(redirectPath, redirectOrigin).href); } catch (err) { logger.error(err); diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index fac66032..731ea64d 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -10,6 +10,8 @@ const logger = require("../modules/logger"); const { TOKEN_EXPIRED, TOKEN_INVALID } = require("../../loadenv").jwt; +const { contracts } = require("../lottery"); + const tokenLoginHandler = async (req, res) => { const { accessToken, deviceToken } = req.query; try { @@ -36,6 +38,7 @@ const tokenLoginHandler = async (req, res) => { login(req, user.sid, user.id, user._id, user.name); req.session.isApp = true; req.session.deviceToken = deviceToken; + await contracts.requestFirstLoginEvent(user._id); return res.status(200).json({ message: "success" }); } catch (e) { logger.error(e); From cebd609b0b2da45c3c4cd6f58140e7b8ae70d89c Mon Sep 17 00:00:00 2001 From: static Date: Wed, 13 Sep 2023 23:13:17 +0900 Subject: [PATCH 10/33] Add: logging in eventHandler function --- src/lottery/modules/events.js | 100 +++++++++++++++++++++------------- 1 file changed, 62 insertions(+), 38 deletions(-) diff --git a/src/lottery/modules/events.js b/src/lottery/modules/events.js index e5b0cca4..03e17957 100644 --- a/src/lottery/modules/events.js +++ b/src/lottery/modules/events.js @@ -6,46 +6,70 @@ const { const logger = require("../../modules/logger"); const eventHandler = async (userId, eventId) => { - const event = await eventModel.findOne({ _id: eventId }).lean(); - if (!event) { - logger.error(`알 수 없는 이벤트 ID 입니다: ${eventId}`); // 프로그래머의 실수로 인해서만 발생하므로 logger를 통해 오류를 알립니다. - return null; - } + try { + logger.info( + `eventHandler(userId=${userId}, eventId=${eventId}) 함수가 호출되었습니다.` + ); + + const event = await eventModel.findOne({ _id: eventId }).lean(); + if (!event) { + logger.error(`알 수 없는 이벤트 ID 입니다: ${eventId}`); // 프로그래머의 실수로 인해서만 발생하므로 logger를 통해 오류를 알립니다. + return null; + } + + const eventStatus = await eventStatusModel.findOne({ userId }).lean(); + const eventCount = eventStatus.eventList.filter( + (event) => event.toString() === eventId + ).length; + if (eventCount >= event.maxCount) { + logger.info( + `eventHandler(userId=${userId}, eventId=${eventId}) 함수가 종료되었습니다: 이미 최대로 달성한 이벤트입니다.` + ); + return null; + } - const eventStatus = await eventStatusModel.findOne({ userId }).lean(); - const eventCount = eventStatus.eventList.filter( - (event) => event.toString() === eventId - ).length; - if (eventCount >= event.maxCount) return null; // 이미 최대로 달성한 이벤트입니다. - - const now = Date.now(); - if (now < event.startat || now > event.expireat) return null; - - await eventStatusModel.updateOne( - { userId }, - { - $inc: { - creditAmount: event.rewardAmount, - }, - $push: { - eventList: eventId, - }, + const now = Date.now(); + if (now < event.startat || now > event.expireat) { + logger.info( + `eventHandler(userId=${userId}, eventId=${eventId}) 함수가 종료되었습니다: 달성할 수 있는 기간이 아닙니다.` + ); + return null; } - ); - - const transaction = new transactionModel({ - type: "get", - amount: event.rewardAmount, - userId, - event: eventId, - comment: `${event.name} 달성 - ${event.rewardAmount}개 획득`, - }); - await transaction.save(); - - return { - event, - transactionId: transaction._id, - }; + + await eventStatusModel.updateOne( + { userId }, + { + $inc: { + creditAmount: event.rewardAmount, + }, + $push: { + eventList: eventId, + }, + } + ); + + const transaction = new transactionModel({ + type: "get", + amount: event.rewardAmount, + userId, + event: eventId, + comment: `${event.name} 달성 - ${event.rewardAmount}개 획득`, + }); + await transaction.save(); + + logger.info( + `eventHandler(userId=${userId}, eventId=${eventId}) 함수가 종료되었습니다: 성공했습니다.` + ); + return { + event, + transactionId: transaction._id, + }; + } catch (err) { + logger.error( + `eventHandler(userId=${userId}, eventId=${eventId}) 함수에서 예외가 발생했습니다: ${err}` + ); + return null; + } }; module.exports = { From 04b6fe1f57d5eb4521681dfeddfaab5cf93dd00f Mon Sep 17 00:00:00 2001 From: static Date: Thu, 14 Sep 2023 00:26:10 +0900 Subject: [PATCH 11/33] Add: implement firstRoomCreationEvent --- src/lottery/modules/contracts/2023fall.js | 13 +++++++++++-- src/services/auth.js | 4 ++++ src/services/auth.mobile.js | 4 ++++ src/services/rooms.js | 10 +++++++++- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 1a9bc97d..b0d62503 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,9 +1,11 @@ -const { eventIds } = require("../../../../loadenv"); +const { eventMode, eventIds } = require("../../../../loadenv"); const { eventHandler } = require("../events"); // 로그인할 때마다 호출해 주세요. -// 사용된 곳: auth/tryLogin, auth.mobile/tryLoginHandler +// 사용된 곳: auth/tryLogin, auth.mobile/tokenLoginHandler const requestFirstLoginEvent = async (userId) => { + if (eventMode !== "2023fall") return null; + return await eventHandler(userId, eventIds.firstLogin); }; @@ -12,7 +14,10 @@ const requestPayingAndSendingEvent = async () => { }; // 방을 만들 때마다 호출해 주세요. +// 사용된 곳: rooms/createHandler const requestFirstRoomCreation = async (userId) => { + if (eventMode !== "2023fall") return null; + return await eventHandler(userId, eventIds.firstRoomCreation); }; @@ -30,11 +35,15 @@ const requestSendingEvent = async () => { // 닉네임을 변경할 때마다 호출해 주세요. const requestNicknameChangingEvent = async (userId) => { + if (eventMode !== "2023fall") return null; + return await eventHandler(userId, eventIds.nicknameChanging); }; // 계좌를 변경할 때마다 호출해 주세요. const requestAccountChangingEvent = async (userId) => { + if (eventMode !== "2023fall") return null; + return await eventHandler(userId, eventIds.accountChanging); }; diff --git a/src/services/auth.js b/src/services/auth.js index 9a3fef85..230e4d72 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -16,6 +16,7 @@ const { const jwt = require("../modules/auths/jwt"); const logger = require("../modules/logger"); +// 이벤트 코드입니다. const { contracts } = require("../lottery"); // SPARCS SSO @@ -93,7 +94,10 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { } login(req, userData.sid, user.id, user._id, user.name); + + // 이벤트 코드입니다. await contracts.requestFirstLoginEvent(user._id); + res.redirect(new URL(redirectPath, redirectOrigin).href); } catch (err) { logger.error(err); diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index 731ea64d..6ff4e00f 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -10,6 +10,7 @@ const logger = require("../modules/logger"); const { TOKEN_EXPIRED, TOKEN_INVALID } = require("../../loadenv").jwt; +// 이벤트 코드입니다. const { contracts } = require("../lottery"); const tokenLoginHandler = async (req, res) => { @@ -38,7 +39,10 @@ const tokenLoginHandler = async (req, res) => { login(req, user.sid, user.id, user._id, user.name); req.session.isApp = true; req.session.deviceToken = deviceToken; + + // 이벤트 코드입니다. await contracts.requestFirstLoginEvent(user._id); + return res.status(200).json({ message: "success" }); } catch (e) { logger.error(e); diff --git a/src/services/rooms.js b/src/services/rooms.js index 0456d534..6c7c32fb 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -11,6 +11,9 @@ const { getIsOver, } = require("../modules/populates/rooms"); +// 이벤트 코드입니다. +const { contracts } = require("../lottery"); + const createHandler = async (req, res) => { const { name, from, to, time, maxPartLength } = req.body; @@ -81,7 +84,12 @@ const createHandler = async (req, res) => { }); const roomObject = (await room.populate(roomPopulateOption)).toObject(); - return res.send(formatSettlement(roomObject)); + const roomObjectFormated = formatSettlement(roomObject); + + // 이벤트 코드입니다. + await contracts.requestFirstRoomCreation(user._id); + + return res.send(roomObjectFormated); } catch (err) { logger.error(err); res.status(500).json({ From 153ffb274628ea319b695bb0c0353193debc269f Mon Sep 17 00:00:00 2001 From: static Date: Thu, 14 Sep 2023 00:41:38 +0900 Subject: [PATCH 12/33] Add: nicknameChangingEvent and accountChangingEvent --- src/lottery/modules/contracts/2023fall.js | 2 + src/services/users.js | 71 +++++++++++++---------- 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index b0d62503..f88c2711 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -34,6 +34,7 @@ const requestSendingEvent = async () => { }; // 닉네임을 변경할 때마다 호출해 주세요. +// 사용된 곳: users/editNicknameHandler const requestNicknameChangingEvent = async (userId) => { if (eventMode !== "2023fall") return null; @@ -41,6 +42,7 @@ const requestNicknameChangingEvent = async (userId) => { }; // 계좌를 변경할 때마다 호출해 주세요. +// 사용된 곳: users/editAccountHandler const requestAccountChangingEvent = async (userId) => { if (eventMode !== "2023fall") return null; diff --git a/src/services/users.js b/src/services/users.js index 77af8c7d..ce51ea49 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -2,6 +2,9 @@ const { userModel } = require("../modules/stores/mongo"); const logger = require("../modules/logger"); const aws = require("../modules/stores/aws"); +// 이벤트 코드입니다. +const { contracts } = require("../lottery"); + const agreeOnTermsOfServiceHandler = async (req, res) => { try { let user = await userModel.findOne({ id: req.userId }); @@ -34,43 +37,47 @@ const getAgreeOnTermsOfServiceHandler = async (req, res) => { }; const editNicknameHandler = async (req, res) => { - const newNickname = req.body.nickname; + try { + const newNickname = req.body.nickname; + const result = await userModel.findOneAndUpdate( + { id: req.userId }, + { nickname: newNickname } + ); - // 닉네임을 갱신하고 결과를 반환 - await userModel - .findOneAndUpdate({ id: req.userId }, { nickname: newNickname }) - .then((result) => { - if (result) { - res - .status(200) - .send("User/editNickname : edit user nickname successful"); - } else { - res.status(400).send("User/editNickname : such user id does not exist"); - } - }) - .catch((err) => { - logger.error(err); - res.status(500).send("User/editNickname : internal server error"); - }); + if (result) { + // 이벤트 코드입니다. + await contracts.requestNicknameChangingEvent(req.userOid); + + res.status(200).send("User/editNickname : edit user nickname successful"); + } else { + res.status(400).send("User/editNickname : such user id does not exist"); + } + } catch (err) { + logger.error(err); + res.status(500).send("User/editNickname : internal server error"); + } }; const editAccountHandler = async (req, res) => { - const newAccount = req.body.account; + try { + const newAccount = req.body.account; + const result = await userModel.findOneAndUpdate( + { id: req.userId }, + { account: newAccount } + ); - // 계좌번호를 갱신하고 결과를 반환 - await userModel - .findOneAndUpdate({ id: req.userId }, { account: newAccount }) - .then((result) => { - if (result) { - res.status(200).send("User/editAccount : edit user account successful"); - } else { - res.status(400).send("User/editAccount : such user id does not exist"); - } - }) - .catch((err) => { - logger.error(err); - res.status(500).send("User/editAccount : internal server error"); - }); + if (result) { + // 이벤트 코드입니다. + await contracts.requestAccountChangingEvent(req.userOid); + + res.status(200).send("User/editAccount : edit user account successful"); + } else { + res.status(400).send("User/editAccount : such user id does not exist"); + } + } catch (err) { + logger.error(err); + res.status(500).send("User/editAccount : internal server error"); + } }; const editProfileImgGetPUrlHandler = async (req, res) => { From 69c0f8157512b084176c49586795377f8d005350 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 14 Sep 2023 01:31:08 +0900 Subject: [PATCH 13/33] Add: implement payingEvent and sendingEvent --- src/lottery/modules/contracts/2023fall.js | 18 ++++++++++++++---- src/services/rooms.js | 6 ++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index f88c2711..6877cedf 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -25,12 +25,22 @@ const requestRoomSharingEvent = async () => { // TODO }; -const requestPayingEvent = async () => { - // TODO +// 정산 요청이 이루어질 때마다 호출해 주세요. +// 사용된 곳: rooms/commitPaymentHandler +const requestPayingEvent = async (userId, roomObject) => { + if (eventMode !== "2023fall") return null; + if (roomObject.part.length < 2) return null; + + return await eventHandler(userId, eventIds.paying); }; -const requestSendingEvent = async () => { - // TODO +// 송금이 이루어질 때마다 호출해 주세요. +// 사용된 곳: rooms/settlementHandler +const requestSendingEvent = async (userId, roomObject) => { + if (eventMode !== "2023fall") return null; + if (roomObject.part.length < 2) return null; + + return await eventHandler(userId, eventIds.sending); }; // 닉네임을 변경할 때마다 호출해 주세요. diff --git a/src/services/rooms.js b/src/services/rooms.js index 6c7c32fb..51056084 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -491,6 +491,9 @@ const commitPaymentHandler = async (req, res) => { authorId: user._id, }); + // 이벤트 코드입니다. + await contracts.requestPayingEvent(req.userOid, roomObject); + // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); } catch (err) { @@ -557,6 +560,9 @@ const settlementHandler = async (req, res) => { authorId: user._id, }); + // 이벤트 코드입니다. + await contracts.requestSendingEvent(req.userOid, roomObject); + // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); } catch (err) { From 74206ddc78aefd632f8380716909fbbad1acae04 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 14 Sep 2023 02:42:52 +0900 Subject: [PATCH 14/33] Add: implement payingAndSendingEvent --- src/lottery/modules/contracts/2023fall.js | 15 +++++++++++++-- src/services/rooms.js | 2 ++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 6877cedf..9f7ca22f 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -9,8 +9,19 @@ const requestFirstLoginEvent = async (userId) => { return await eventHandler(userId, eventIds.firstLogin); }; -const requestPayingAndSendingEvent = async () => { - // TODO +// 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요. +// 사용된 곳: rooms/commitPaymentHandler, rooms/settlementHandler +const requestPayingAndSendingEvent = async (roomObject) => { + if (eventMode !== "2023fall") return null; + if (roomObject.part.length < 2) return null; + if (roomObject.part.length > roomObject.settlementTotal) return null; + + return await Promise.all( + roomObject.part.map( + async (participant) => + await eventHandler(participant.user._id, eventIds.payingAndSending) + ) + ); }; // 방을 만들 때마다 호출해 주세요. diff --git a/src/services/rooms.js b/src/services/rooms.js index 51056084..f4d738ca 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -493,6 +493,7 @@ const commitPaymentHandler = async (req, res) => { // 이벤트 코드입니다. await contracts.requestPayingEvent(req.userOid, roomObject); + await contracts.requestPayingAndSendingEvent(roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); @@ -562,6 +563,7 @@ const settlementHandler = async (req, res) => { // 이벤트 코드입니다. await contracts.requestSendingEvent(req.userOid, roomObject); + await contracts.requestPayingAndSendingEvent(roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); From ce6a30a809dd7a492d68880d4bc15f0ec81b3830 Mon Sep 17 00:00:00 2001 From: static Date: Thu, 14 Sep 2023 03:09:28 +0900 Subject: [PATCH 15/33] Fix: error occuring when eventMode === undefined --- src/lottery/index.js | 20 ++++++++++++-------- src/lottery/modules/contracts/2023fall.js | 13 +------------ src/services/auth.js | 4 ++-- src/services/auth.mobile.js | 4 ++-- src/services/rooms.js | 12 ++++++------ src/services/users.js | 6 +++--- 6 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/lottery/index.js b/src/lottery/index.js index e06c2988..c4b060ce 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -8,15 +8,11 @@ const { const { eventMode } = require("../../loadenv"); const { buildResource } = require("../modules/adminResource"); +const logger = require("../modules/logger"); // [Routes] 기존 docs 라우터의 docs extend require("./routes/docs")(); -// [Middleware] 목표 달성 여부 검증 -const checkReward = (req, res, next) => { - next(); -}; - const lotteryRouter = express.Router(); // [Middleware] 모든 API 요청에 대하여 origin 검증 @@ -34,11 +30,19 @@ const resources = [ transactionModel, ].map(buildResource()); -const contracts = require(`./modules/contracts/${eventMode}`); +const contracts = eventMode ? require(`./modules/contracts/${eventMode}`) : {}; +const getContract = (name) => { + const contract = contracts[name]; + if (contract) return contract; + + if (eventMode) { + logger.error(`Contract ${name}를 찾을 수 없습니다.`); + } + return () => null; +}; module.exports = { - checkReward, lotteryRouter, resources, - contracts, + getContract, }; diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 9f7ca22f..2d487c1f 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,18 +1,15 @@ -const { eventMode, eventIds } = require("../../../../loadenv"); +const { eventIds } = require("../../../../loadenv"); const { eventHandler } = require("../events"); // 로그인할 때마다 호출해 주세요. // 사용된 곳: auth/tryLogin, auth.mobile/tokenLoginHandler const requestFirstLoginEvent = async (userId) => { - if (eventMode !== "2023fall") return null; - return await eventHandler(userId, eventIds.firstLogin); }; // 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요. // 사용된 곳: rooms/commitPaymentHandler, rooms/settlementHandler const requestPayingAndSendingEvent = async (roomObject) => { - if (eventMode !== "2023fall") return null; if (roomObject.part.length < 2) return null; if (roomObject.part.length > roomObject.settlementTotal) return null; @@ -27,8 +24,6 @@ const requestPayingAndSendingEvent = async (roomObject) => { // 방을 만들 때마다 호출해 주세요. // 사용된 곳: rooms/createHandler const requestFirstRoomCreation = async (userId) => { - if (eventMode !== "2023fall") return null; - return await eventHandler(userId, eventIds.firstRoomCreation); }; @@ -39,7 +34,6 @@ const requestRoomSharingEvent = async () => { // 정산 요청이 이루어질 때마다 호출해 주세요. // 사용된 곳: rooms/commitPaymentHandler const requestPayingEvent = async (userId, roomObject) => { - if (eventMode !== "2023fall") return null; if (roomObject.part.length < 2) return null; return await eventHandler(userId, eventIds.paying); @@ -48,7 +42,6 @@ const requestPayingEvent = async (userId, roomObject) => { // 송금이 이루어질 때마다 호출해 주세요. // 사용된 곳: rooms/settlementHandler const requestSendingEvent = async (userId, roomObject) => { - if (eventMode !== "2023fall") return null; if (roomObject.part.length < 2) return null; return await eventHandler(userId, eventIds.sending); @@ -57,16 +50,12 @@ const requestSendingEvent = async (userId, roomObject) => { // 닉네임을 변경할 때마다 호출해 주세요. // 사용된 곳: users/editNicknameHandler const requestNicknameChangingEvent = async (userId) => { - if (eventMode !== "2023fall") return null; - return await eventHandler(userId, eventIds.nicknameChanging); }; // 계좌를 변경할 때마다 호출해 주세요. // 사용된 곳: users/editAccountHandler const requestAccountChangingEvent = async (userId) => { - if (eventMode !== "2023fall") return null; - return await eventHandler(userId, eventIds.accountChanging); }; diff --git a/src/services/auth.js b/src/services/auth.js index 230e4d72..43d62bf9 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -17,7 +17,7 @@ const jwt = require("../modules/auths/jwt"); const logger = require("../modules/logger"); // 이벤트 코드입니다. -const { contracts } = require("../lottery"); +const { getContract } = require("../lottery"); // SPARCS SSO const Client = require("../modules/auths/sparcssso"); @@ -96,7 +96,7 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { login(req, userData.sid, user.id, user._id, user.name); // 이벤트 코드입니다. - await contracts.requestFirstLoginEvent(user._id); + await getContract("requestFirstLoginEvent")(user._id); res.redirect(new URL(redirectPath, redirectOrigin).href); } catch (err) { diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index 6ff4e00f..e4b4b894 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -11,7 +11,7 @@ const logger = require("../modules/logger"); const { TOKEN_EXPIRED, TOKEN_INVALID } = require("../../loadenv").jwt; // 이벤트 코드입니다. -const { contracts } = require("../lottery"); +const { getContract } = require("../lottery"); const tokenLoginHandler = async (req, res) => { const { accessToken, deviceToken } = req.query; @@ -41,7 +41,7 @@ const tokenLoginHandler = async (req, res) => { req.session.deviceToken = deviceToken; // 이벤트 코드입니다. - await contracts.requestFirstLoginEvent(user._id); + await getContract("requestFirstLoginEvent")(user._id); return res.status(200).json({ message: "success" }); } catch (e) { diff --git a/src/services/rooms.js b/src/services/rooms.js index f4d738ca..b4367bff 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -12,7 +12,7 @@ const { } = require("../modules/populates/rooms"); // 이벤트 코드입니다. -const { contracts } = require("../lottery"); +const { getContract } = require("../lottery"); const createHandler = async (req, res) => { const { name, from, to, time, maxPartLength } = req.body; @@ -87,7 +87,7 @@ const createHandler = async (req, res) => { const roomObjectFormated = formatSettlement(roomObject); // 이벤트 코드입니다. - await contracts.requestFirstRoomCreation(user._id); + await getContract("requestFirstRoomCreation")(user._id); return res.send(roomObjectFormated); } catch (err) { @@ -492,8 +492,8 @@ const commitPaymentHandler = async (req, res) => { }); // 이벤트 코드입니다. - await contracts.requestPayingEvent(req.userOid, roomObject); - await contracts.requestPayingAndSendingEvent(roomObject); + await getContract("requestPayingEvent")(req.userOid, roomObject); + await getContract("requestPayingAndSendingEvent")(roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); @@ -562,8 +562,8 @@ const settlementHandler = async (req, res) => { }); // 이벤트 코드입니다. - await contracts.requestSendingEvent(req.userOid, roomObject); - await contracts.requestPayingAndSendingEvent(roomObject); + await getContract("requestSendingEvent")(req.userOid, roomObject); + await getContract("requestPayingAndSendingEvent")(roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); diff --git a/src/services/users.js b/src/services/users.js index ce51ea49..1ec9978d 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -3,7 +3,7 @@ const logger = require("../modules/logger"); const aws = require("../modules/stores/aws"); // 이벤트 코드입니다. -const { contracts } = require("../lottery"); +const { getContract } = require("../lottery"); const agreeOnTermsOfServiceHandler = async (req, res) => { try { @@ -46,7 +46,7 @@ const editNicknameHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await contracts.requestNicknameChangingEvent(req.userOid); + await getContract("requestNicknameChangingEvent")(req.userOid); res.status(200).send("User/editNickname : edit user nickname successful"); } else { @@ -68,7 +68,7 @@ const editAccountHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await contracts.requestAccountChangingEvent(req.userOid); + await getContract("requestAccountChangingEvent")(req.userOid); res.status(200).send("User/editAccount : edit user account successful"); } else { From b5dc891322ec7ce404c38fe92d9bfa830d07a729 Mon Sep 17 00:00:00 2001 From: cokia Date: Thu, 14 Sep 2023 22:47:44 +0900 Subject: [PATCH 16/33] chore: add jsdoc to 2023fall event logic --- src/lottery/modules/contracts/2023fall.js | 64 ++++++++++++++++++----- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 2d487c1f..20ed8227 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,14 +1,23 @@ const { eventIds } = require("../../../../loadenv"); const { eventHandler } = require("../events"); -// 로그인할 때마다 호출해 주세요. -// 사용된 곳: auth/tryLogin, auth.mobile/tokenLoginHandler +/** + * @param {string} userId + * @returns {Promise} + * @description 로그인할 때마다 호출해 주세요. + * @usage auth/tryLogin, auth/mobile/tokenLoginHandler + */ const requestFirstLoginEvent = async (userId) => { return await eventHandler(userId, eventIds.firstLogin); }; -// 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요. -// 사용된 곳: rooms/commitPaymentHandler, rooms/settlementHandler +/** + * + * @param {*} roomObject + * @returns {Promise} + * @description 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요. + * @usage rooms/commitPaymentHandler, rooms/settlementHandler + */ const requestPayingAndSendingEvent = async (roomObject) => { if (roomObject.part.length < 2) return null; if (roomObject.part.length > roomObject.settlementTotal) return null; @@ -21,8 +30,13 @@ const requestPayingAndSendingEvent = async (roomObject) => { ); }; -// 방을 만들 때마다 호출해 주세요. -// 사용된 곳: rooms/createHandler +/** + * + * @param {*} userId + * @returns {Promise} + * @description 방을 만들 때마다 호출해 주세요. + * @usage rooms/createHandler + */ const requestFirstRoomCreation = async (userId) => { return await eventHandler(userId, eventIds.firstRoomCreation); }; @@ -31,30 +45,52 @@ const requestRoomSharingEvent = async () => { // TODO }; -// 정산 요청이 이루어질 때마다 호출해 주세요. -// 사용된 곳: rooms/commitPaymentHandler +/** + * + * @param {*} userId + * @param {*} roomObject + * @returns {Promise} + * @description 정산 요청이 이루어질 때마다 호출해 주세요. + * @usage rooms/commitPaymentHandler + */ const requestPayingEvent = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; return await eventHandler(userId, eventIds.paying); }; -// 송금이 이루어질 때마다 호출해 주세요. -// 사용된 곳: rooms/settlementHandler +/** + * + * @param {*} userId + * @param {*} roomObject + * @returns {Promise} + * @description 송금이 이루어질 때마다 호출해 주세요. + * @usage rooms/settlementHandler + */ const requestSendingEvent = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; return await eventHandler(userId, eventIds.sending); }; -// 닉네임을 변경할 때마다 호출해 주세요. -// 사용된 곳: users/editNicknameHandler +/** + * + * @param {*} userId + * @returns {Promise} + * @description 닉네임을 변경할 때마다 호출해 주세요. + * @usage users/editNicknameHandler + */ const requestNicknameChangingEvent = async (userId) => { return await eventHandler(userId, eventIds.nicknameChanging); }; -// 계좌를 변경할 때마다 호출해 주세요. -// 사용된 곳: users/editAccountHandler +/** + * + * @param {*} userId + * @returns {Promise} + * @description 계좌를 변경할 때마다 호출해 주세요. + * @usage users/editAccountHandler + */ const requestAccountChangingEvent = async (userId) => { return await eventHandler(userId, eventIds.accountChanging); }; From 0d5d0e18cd67bed7a9ccd1b77f62843a5029860f Mon Sep 17 00:00:00 2001 From: static Date: Thu, 14 Sep 2023 23:36:43 +0900 Subject: [PATCH 17/33] Refactor: use eventName instead of eventId in eventHandler --- loadenv.js | 16 --------------- src/lottery/modules/contracts/2023fall.js | 18 +++++++++-------- src/lottery/modules/events.js | 24 ++++++++++++----------- src/lottery/modules/stores/mongo.js | 1 + 4 files changed, 24 insertions(+), 35 deletions(-) diff --git a/loadenv.js b/loadenv.js index 5ce5827d..2bce4fb9 100644 --- a/loadenv.js +++ b/loadenv.js @@ -39,20 +39,4 @@ module.exports = { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, eventMode: undefined, - eventIds: { - // required if eventMode === "2023fall" - firstLogin: process.env.EVENT_2023FALL_FIRST_LOGIN_ID, - payingAndSending: process.env.EVENT_2023FALL_PAYING_AND_SENDING_ID, - firstRoomCreation: process.env.EVENT_2023FALL_FIRST_ROOM_CREATION_ID, - roomSharing: process.env.EVENT_2023FALL_ROOM_SHARING_ID, - paying: process.env.EVENT_2023FALL_PAYING_ID, - sending: process.env.EVENT_2023FALL_SENDING_ID, - nicknameChanging: process.env.EVENT_2023FALL_NICKNAME_CHANGING_ID, - accountChanging: process.env.EVENT_2023FALL_ACCOUNT_CHANGING_ID, - adPushAgreement: process.env.EVENT_2023FALL_AD_PUSH_AGREEMENT_ID, - eventSharingOnInstagram: - process.env.EVENT_2023FALL_EVENT_SHARING_ON_INSTAGRAM_ID, - purchaseSharingOnInstagram: - process.env.EVENT_2023FALL_PURCHASE_SHARING_ON_INSTAGRAM_ID, - }, }; diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 20ed8227..226c1aaf 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,4 +1,3 @@ -const { eventIds } = require("../../../../loadenv"); const { eventHandler } = require("../events"); /** @@ -8,7 +7,7 @@ const { eventHandler } = require("../events"); * @usage auth/tryLogin, auth/mobile/tokenLoginHandler */ const requestFirstLoginEvent = async (userId) => { - return await eventHandler(userId, eventIds.firstLogin); + return await eventHandler(userId, "이벤트 기간 첫 로그인"); }; /** @@ -25,7 +24,10 @@ const requestPayingAndSendingEvent = async (roomObject) => { return await Promise.all( roomObject.part.map( async (participant) => - await eventHandler(participant.user._id, eventIds.payingAndSending) + await eventHandler( + participant.user._id, + "2명 이상 탑승한 방에서 정산/송금 완료" + ) ) ); }; @@ -38,7 +40,7 @@ const requestPayingAndSendingEvent = async (roomObject) => { * @usage rooms/createHandler */ const requestFirstRoomCreation = async (userId) => { - return await eventHandler(userId, eventIds.firstRoomCreation); + return await eventHandler(userId, "첫 방 개설"); }; const requestRoomSharingEvent = async () => { @@ -56,7 +58,7 @@ const requestRoomSharingEvent = async () => { const requestPayingEvent = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; - return await eventHandler(userId, eventIds.paying); + return await eventHandler(userId, "2명 이상 탑승한 방에서 정산하기"); }; /** @@ -70,7 +72,7 @@ const requestPayingEvent = async (userId, roomObject) => { const requestSendingEvent = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; - return await eventHandler(userId, eventIds.sending); + return await eventHandler(userId, "2명 이상 탑승한 방에서 송금하기"); }; /** @@ -81,7 +83,7 @@ const requestSendingEvent = async (userId, roomObject) => { * @usage users/editNicknameHandler */ const requestNicknameChangingEvent = async (userId) => { - return await eventHandler(userId, eventIds.nicknameChanging); + return await eventHandler(userId, "닉네임 변경"); }; /** @@ -92,7 +94,7 @@ const requestNicknameChangingEvent = async (userId) => { * @usage users/editAccountHandler */ const requestAccountChangingEvent = async (userId) => { - return await eventHandler(userId, eventIds.accountChanging); + return await eventHandler(userId, "계좌 등록 또는 수정"); }; const requestAdPushAgreementEvent = async () => { diff --git a/src/lottery/modules/events.js b/src/lottery/modules/events.js index 03e17957..caa9cff6 100644 --- a/src/lottery/modules/events.js +++ b/src/lottery/modules/events.js @@ -5,25 +5,27 @@ const { } = require("./stores/mongo"); const logger = require("../../modules/logger"); -const eventHandler = async (userId, eventId) => { +const eventHandler = async (userId, eventName) => { try { logger.info( - `eventHandler(userId=${userId}, eventId=${eventId}) 함수가 호출되었습니다.` + `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 호출되었습니다.` ); - const event = await eventModel.findOne({ _id: eventId }).lean(); + const event = await eventModel.findOne({ name: eventName }).lean(); if (!event) { - logger.error(`알 수 없는 이벤트 ID 입니다: ${eventId}`); // 프로그래머의 실수로 인해서만 발생하므로 logger를 통해 오류를 알립니다. + logger.error( + `eventHandler(userId=${userId}, eventName="${eventName}") 함수에서 예외가 발생했습니다: 알 수 없는 이벤트 이름입니다.` + ); return null; } const eventStatus = await eventStatusModel.findOne({ userId }).lean(); const eventCount = eventStatus.eventList.filter( - (event) => event.toString() === eventId + (achievedEventId) => achievedEventId.toString() === event.id.toString() ).length; if (eventCount >= event.maxCount) { logger.info( - `eventHandler(userId=${userId}, eventId=${eventId}) 함수가 종료되었습니다: 이미 최대로 달성한 이벤트입니다.` + `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 종료되었습니다: 이미 최대로 달성한 이벤트입니다.` ); return null; } @@ -31,7 +33,7 @@ const eventHandler = async (userId, eventId) => { const now = Date.now(); if (now < event.startat || now > event.expireat) { logger.info( - `eventHandler(userId=${userId}, eventId=${eventId}) 함수가 종료되었습니다: 달성할 수 있는 기간이 아닙니다.` + `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 종료되었습니다: 달성할 수 있는 기간이 아닙니다.` ); return null; } @@ -43,7 +45,7 @@ const eventHandler = async (userId, eventId) => { creditAmount: event.rewardAmount, }, $push: { - eventList: eventId, + eventList: event._id, }, } ); @@ -52,13 +54,13 @@ const eventHandler = async (userId, eventId) => { type: "get", amount: event.rewardAmount, userId, - event: eventId, + event: event._id, comment: `${event.name} 달성 - ${event.rewardAmount}개 획득`, }); await transaction.save(); logger.info( - `eventHandler(userId=${userId}, eventId=${eventId}) 함수가 종료되었습니다: 성공했습니다.` + `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 종료되었습니다: 성공했습니다.` ); return { event, @@ -66,7 +68,7 @@ const eventHandler = async (userId, eventId) => { }; } catch (err) { logger.error( - `eventHandler(userId=${userId}, eventId=${eventId}) 함수에서 예외가 발생했습니다: ${err}` + `eventHandler(userId=${userId}, eventName="${eventName}") 함수에서 예외가 발생했습니다: ${err}` ); return null; } diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 91dafbad..c2a6ab2c 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -29,6 +29,7 @@ const eventSchema = Schema({ name: { type: String, required: true, + unique: true, }, rewardAmount: { type: Number, From ee6dace7c9c4cfbed13f8df3054dfce75dbbef1e Mon Sep 17 00:00:00 2001 From: static Date: Fri, 15 Sep 2023 01:00:18 +0900 Subject: [PATCH 18/33] Refactor: calling method of contracts --- src/lottery/index.js | 15 ++++----------- src/lottery/modules/events.js | 7 ++++++- src/services/auth.js | 4 ++-- src/services/auth.mobile.js | 4 ++-- src/services/rooms.js | 20 ++++++++++++++------ src/services/users.js | 10 +++++++--- 6 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/lottery/index.js b/src/lottery/index.js index c4b060ce..0663051f 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -30,19 +30,12 @@ const resources = [ transactionModel, ].map(buildResource()); -const contracts = eventMode ? require(`./modules/contracts/${eventMode}`) : {}; -const getContract = (name) => { - const contract = contracts[name]; - if (contract) return contract; - - if (eventMode) { - logger.error(`Contract ${name}를 찾을 수 없습니다.`); - } - return () => null; -}; +const contracts = eventMode + ? require(`./modules/contracts/${eventMode}`) + : undefined; module.exports = { lotteryRouter, resources, - getContract, + contracts, }; diff --git a/src/lottery/modules/events.js b/src/lottery/modules/events.js index caa9cff6..dc447930 100644 --- a/src/lottery/modules/events.js +++ b/src/lottery/modules/events.js @@ -17,11 +17,16 @@ const eventHandler = async (userId, eventName) => { `eventHandler(userId=${userId}, eventName="${eventName}") 함수에서 예외가 발생했습니다: 알 수 없는 이벤트 이름입니다.` ); return null; + } else if (event.isDisabled) { + logger.info( + `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 종료되었습니다: 달성할 수 없는 이벤트입니다.` + ); + return null; } const eventStatus = await eventStatusModel.findOne({ userId }).lean(); const eventCount = eventStatus.eventList.filter( - (achievedEventId) => achievedEventId.toString() === event.id.toString() + (achievedEventId) => achievedEventId.toString() === event._id.toString() ).length; if (eventCount >= event.maxCount) { logger.info( diff --git a/src/services/auth.js b/src/services/auth.js index 43d62bf9..69e89318 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -17,7 +17,7 @@ const jwt = require("../modules/auths/jwt"); const logger = require("../modules/logger"); // 이벤트 코드입니다. -const { getContract } = require("../lottery"); +const { contracts } = require("../lottery"); // SPARCS SSO const Client = require("../modules/auths/sparcssso"); @@ -96,7 +96,7 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { login(req, userData.sid, user.id, user._id, user.name); // 이벤트 코드입니다. - await getContract("requestFirstLoginEvent")(user._id); + contracts ? await contracts.requestFirstLoginEvent(user._id) : undefined; res.redirect(new URL(redirectPath, redirectOrigin).href); } catch (err) { diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index e4b4b894..7b125ac2 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -11,7 +11,7 @@ const logger = require("../modules/logger"); const { TOKEN_EXPIRED, TOKEN_INVALID } = require("../../loadenv").jwt; // 이벤트 코드입니다. -const { getContract } = require("../lottery"); +const { contracts } = require("../lottery"); const tokenLoginHandler = async (req, res) => { const { accessToken, deviceToken } = req.query; @@ -41,7 +41,7 @@ const tokenLoginHandler = async (req, res) => { req.session.deviceToken = deviceToken; // 이벤트 코드입니다. - await getContract("requestFirstLoginEvent")(user._id); + contracts ? await contracts.requestFirstLoginEvent(user._id) : undefined; return res.status(200).json({ message: "success" }); } catch (e) { diff --git a/src/services/rooms.js b/src/services/rooms.js index b4367bff..7149b24d 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -12,7 +12,7 @@ const { } = require("../modules/populates/rooms"); // 이벤트 코드입니다. -const { getContract } = require("../lottery"); +const { contracts } = require("../lottery"); const createHandler = async (req, res) => { const { name, from, to, time, maxPartLength } = req.body; @@ -87,7 +87,7 @@ const createHandler = async (req, res) => { const roomObjectFormated = formatSettlement(roomObject); // 이벤트 코드입니다. - await getContract("requestFirstRoomCreation")(user._id); + contracts ? await contracts.requestFirstRoomCreation(user._id) : undefined; return res.send(roomObjectFormated); } catch (err) { @@ -492,8 +492,12 @@ const commitPaymentHandler = async (req, res) => { }); // 이벤트 코드입니다. - await getContract("requestPayingEvent")(req.userOid, roomObject); - await getContract("requestPayingAndSendingEvent")(roomObject); + contracts + ? await contracts.requestPayingEvent(user._id, roomObject) + : undefined; + contracts + ? await contracts.requestPayingAndSendingEvent(roomObject) + : undefined; // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); @@ -562,8 +566,12 @@ const settlementHandler = async (req, res) => { }); // 이벤트 코드입니다. - await getContract("requestSendingEvent")(req.userOid, roomObject); - await getContract("requestPayingAndSendingEvent")(roomObject); + contracts + ? await contracts.requestSendingEvent(user._id, roomObject) + : undefined; + contracts + ? await contracts.requestPayingAndSendingEvent(roomObject) + : undefined; // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); diff --git a/src/services/users.js b/src/services/users.js index 1ec9978d..df5b8a21 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -3,7 +3,7 @@ const logger = require("../modules/logger"); const aws = require("../modules/stores/aws"); // 이벤트 코드입니다. -const { getContract } = require("../lottery"); +const { contracts } = require("../lottery"); const agreeOnTermsOfServiceHandler = async (req, res) => { try { @@ -46,7 +46,9 @@ const editNicknameHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await getContract("requestNicknameChangingEvent")(req.userOid); + contracts + ? await contracts.requestNicknameChangingEvent(req.userOid) + : undefined; res.status(200).send("User/editNickname : edit user nickname successful"); } else { @@ -68,7 +70,9 @@ const editAccountHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await getContract("requestAccountChangingEvent")(req.userOid); + contracts + ? await contracts.requestAccountChangingEvent(req.userOid) + : undefined; res.status(200).send("User/editAccount : edit user account successful"); } else { From f07ba8d33819574d5d28b129e459bbafd906eb87 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 15 Sep 2023 01:04:14 +0900 Subject: [PATCH 19/33] Remove: importing logger in lottery/index.js --- src/lottery/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lottery/index.js b/src/lottery/index.js index 0663051f..0dd69f5c 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -8,7 +8,6 @@ const { const { eventMode } = require("../../loadenv"); const { buildResource } = require("../modules/adminResource"); -const logger = require("../modules/logger"); // [Routes] 기존 docs 라우터의 docs extend require("./routes/docs")(); From ee3c625e2986a43ef9aaba600ee22bd9c5131085 Mon Sep 17 00:00:00 2001 From: static Date: Fri, 15 Sep 2023 04:04:10 +0900 Subject: [PATCH 20/33] Docs: describe type of roomObject in contracts/2023fall.js --- src/lottery/modules/contracts/2023fall.js | 43 +++++++++++++---------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 226c1aaf..6cd91fe1 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,18 +1,21 @@ const { eventHandler } = require("../events"); +const mongoose = require("mongoose"); -/** - * @param {string} userId - * @returns {Promise} - * @description 로그인할 때마다 호출해 주세요. - * @usage auth/tryLogin, auth/mobile/tokenLoginHandler +/** + * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @returns {Promise} + * @description 로그인할 때마다 호출해 주세요. + * @usage auth/tryLogin, auth/mobile/tokenLoginHandler */ const requestFirstLoginEvent = async (userId) => { return await eventHandler(userId, "이벤트 기간 첫 로그인"); }; /** - * - * @param {*} roomObject + * + * @param {Object} roomObject - 방의 정보입니다. + * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. + * @param {number} roomObject.settlementTotal - 정산 또는 송금이 완료된 참여자 수입니다. * @returns {Promise} * @description 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요. * @usage rooms/commitPaymentHandler, rooms/settlementHandler @@ -33,8 +36,8 @@ const requestPayingAndSendingEvent = async (roomObject) => { }; /** - * - * @param {*} userId + * + * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 방을 만들 때마다 호출해 주세요. * @usage rooms/createHandler @@ -48,9 +51,10 @@ const requestRoomSharingEvent = async () => { }; /** - * - * @param {*} userId - * @param {*} roomObject + * + * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @param {Object} roomObject - 방의 정보입니다. + * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} * @description 정산 요청이 이루어질 때마다 호출해 주세요. * @usage rooms/commitPaymentHandler @@ -62,9 +66,10 @@ const requestPayingEvent = async (userId, roomObject) => { }; /** - * - * @param {*} userId - * @param {*} roomObject + * + * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @param {Object} roomObject - 방의 정보입니다. + * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} * @description 송금이 이루어질 때마다 호출해 주세요. * @usage rooms/settlementHandler @@ -76,8 +81,8 @@ const requestSendingEvent = async (userId, roomObject) => { }; /** - * - * @param {*} userId + * + * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 닉네임을 변경할 때마다 호출해 주세요. * @usage users/editNicknameHandler @@ -87,8 +92,8 @@ const requestNicknameChangingEvent = async (userId) => { }; /** - * - * @param {*} userId + * + * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 계좌를 변경할 때마다 호출해 주세요. * @usage users/editAccountHandler From 9ed166ca026be5c5f184a7b3cbc9ae2eb1c7f85b Mon Sep 17 00:00:00 2001 From: static Date: Fri, 15 Sep 2023 23:19:07 +0900 Subject: [PATCH 21/33] Refactor: make shorter some code related to contracts --- src/lottery/index.js | 4 +--- src/lottery/modules/contracts/2023fall.js | 8 +------- src/services/auth.js | 2 +- src/services/auth.mobile.js | 2 +- src/services/rooms.js | 18 +++++------------- src/services/users.js | 8 ++------ 6 files changed, 11 insertions(+), 31 deletions(-) diff --git a/src/lottery/index.js b/src/lottery/index.js index 0dd69f5c..1ecef91d 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -29,9 +29,7 @@ const resources = [ transactionModel, ].map(buildResource()); -const contracts = eventMode - ? require(`./modules/contracts/${eventMode}`) - : undefined; +const contracts = eventMode && require(`./modules/contracts/${eventMode}`); module.exports = { lotteryRouter, diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 6cd91fe1..8b40a706 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -5,14 +5,13 @@ const mongoose = require("mongoose"); * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 로그인할 때마다 호출해 주세요. - * @usage auth/tryLogin, auth/mobile/tokenLoginHandler + * @usage auth/tryLogin, auth.mobile/tokenLoginHandler */ const requestFirstLoginEvent = async (userId) => { return await eventHandler(userId, "이벤트 기간 첫 로그인"); }; /** - * * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @param {number} roomObject.settlementTotal - 정산 또는 송금이 완료된 참여자 수입니다. @@ -36,7 +35,6 @@ const requestPayingAndSendingEvent = async (roomObject) => { }; /** - * * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 방을 만들 때마다 호출해 주세요. @@ -51,7 +49,6 @@ const requestRoomSharingEvent = async () => { }; /** - * * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. @@ -66,7 +63,6 @@ const requestPayingEvent = async (userId, roomObject) => { }; /** - * * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. @@ -81,7 +77,6 @@ const requestSendingEvent = async (userId, roomObject) => { }; /** - * * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 닉네임을 변경할 때마다 호출해 주세요. @@ -92,7 +87,6 @@ const requestNicknameChangingEvent = async (userId) => { }; /** - * * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 계좌를 변경할 때마다 호출해 주세요. diff --git a/src/services/auth.js b/src/services/auth.js index 69e89318..7b0851f1 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -96,7 +96,7 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { login(req, userData.sid, user.id, user._id, user.name); // 이벤트 코드입니다. - contracts ? await contracts.requestFirstLoginEvent(user._id) : undefined; + await contracts?.requestFirstLoginEvent(user._id); res.redirect(new URL(redirectPath, redirectOrigin).href); } catch (err) { diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index 7b125ac2..e2303961 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -41,7 +41,7 @@ const tokenLoginHandler = async (req, res) => { req.session.deviceToken = deviceToken; // 이벤트 코드입니다. - contracts ? await contracts.requestFirstLoginEvent(user._id) : undefined; + await contracts?.requestFirstLoginEvent(user._id); return res.status(200).json({ message: "success" }); } catch (e) { diff --git a/src/services/rooms.js b/src/services/rooms.js index 7149b24d..08f0776e 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -87,7 +87,7 @@ const createHandler = async (req, res) => { const roomObjectFormated = formatSettlement(roomObject); // 이벤트 코드입니다. - contracts ? await contracts.requestFirstRoomCreation(user._id) : undefined; + await contracts?.requestFirstRoomCreation(user._id); return res.send(roomObjectFormated); } catch (err) { @@ -492,12 +492,8 @@ const commitPaymentHandler = async (req, res) => { }); // 이벤트 코드입니다. - contracts - ? await contracts.requestPayingEvent(user._id, roomObject) - : undefined; - contracts - ? await contracts.requestPayingAndSendingEvent(roomObject) - : undefined; + await contracts?.requestPayingEvent(user._id, roomObject); + await contracts?.requestPayingAndSendingEvent(roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); @@ -566,12 +562,8 @@ const settlementHandler = async (req, res) => { }); // 이벤트 코드입니다. - contracts - ? await contracts.requestSendingEvent(user._id, roomObject) - : undefined; - contracts - ? await contracts.requestPayingAndSendingEvent(roomObject) - : undefined; + await contracts?.requestSendingEvent(user._id, roomObject); + await contracts?.requestPayingAndSendingEvent(roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); diff --git a/src/services/users.js b/src/services/users.js index df5b8a21..d37bf502 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -46,9 +46,7 @@ const editNicknameHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - contracts - ? await contracts.requestNicknameChangingEvent(req.userOid) - : undefined; + await contracts?.requestNicknameChangingEvent(req.userOid); res.status(200).send("User/editNickname : edit user nickname successful"); } else { @@ -70,9 +68,7 @@ const editAccountHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - contracts - ? await contracts.requestAccountChangingEvent(req.userOid) - : undefined; + await contracts?.requestAccountChangingEvent(req.userOid); res.status(200).send("User/editAccount : edit user account successful"); } else { From 431af7bb9067b4d185436184206de88ac74451f6 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 14:08:46 +0900 Subject: [PATCH 22/33] Refactor: store event data in 2023fall.js instead of database --- src/lottery/modules/contracts/2023fall.js | 72 ++++++++++++++++--- src/lottery/modules/events.js | 45 ++++-------- src/lottery/modules/populates/transactions.js | 1 - src/lottery/modules/stores/mongo.js | 32 ++------- src/lottery/routes/docs/globalState.js | 21 +----- src/lottery/routes/docs/transactions.js | 44 ++---------- src/lottery/services/globalState.js | 6 +- 7 files changed, 90 insertions(+), 131 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 8b40a706..4bd19fb0 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,6 +1,60 @@ const { eventHandler } = require("../events"); const mongoose = require("mongoose"); +const events = { + firstLogin: { + name: "이벤트 기간 첫 로그인", + rewardAmount: 150, + }, + payingAndSending: { + name: "2명 이상 탑승한 방에서 정산/송금 완료", + rewardAmount: 300, + maxCount: 3, + }, + firstRoomCreation: { + name: "첫 방 개설", + rewardAmount: 50, + }, + roomSharing: { + name: "방 공유하기", + rewardAmount: 50, + }, + paying: { + name: "2명 이상 탑승한 방에서 정산하기", + rewardAmount: 100, + maxCount: 3, + }, + sending: { + name: "2명 이상 탑승한 방에서 송금하기", + rewardAmount: 50, + maxCount: 3, + }, + nicknameChaning: { + name: "닉네임 변경", + rewardAmount: 50, + }, + accountChanging: { + name: "계좌 등록 또는 변경", + rewardAmount: 50, + }, + adPushAgreement: { + name: "광고성 푸시 알림 수신 동의", + rewardAmount: 50, + }, + eventSharingOnInstagram: { + name: "이벤트 인스타그램 스토리에 공유", + rewardAmount: 100, + }, + purchaseSharingOnInstagram: { + name: "아이템 구매 후 인스타그램 스토리에 공유", + rewardAmount: 100, + }, +}; + +for (const [id, event] of Object.entries(events)) { + event["id"] = id; // TODO: 외 event.id로는 않돼지???? +} + /** * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} @@ -8,7 +62,7 @@ const mongoose = require("mongoose"); * @usage auth/tryLogin, auth.mobile/tokenLoginHandler */ const requestFirstLoginEvent = async (userId) => { - return await eventHandler(userId, "이벤트 기간 첫 로그인"); + return await eventHandler(userId, events.firstLogin); }; /** @@ -26,10 +80,7 @@ const requestPayingAndSendingEvent = async (roomObject) => { return await Promise.all( roomObject.part.map( async (participant) => - await eventHandler( - participant.user._id, - "2명 이상 탑승한 방에서 정산/송금 완료" - ) + await eventHandler(participant.user._id, events.payingAndSending) ) ); }; @@ -41,7 +92,7 @@ const requestPayingAndSendingEvent = async (roomObject) => { * @usage rooms/createHandler */ const requestFirstRoomCreation = async (userId) => { - return await eventHandler(userId, "첫 방 개설"); + return await eventHandler(userId, events.firstRoomCreation); }; const requestRoomSharingEvent = async () => { @@ -59,7 +110,7 @@ const requestRoomSharingEvent = async () => { const requestPayingEvent = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; - return await eventHandler(userId, "2명 이상 탑승한 방에서 정산하기"); + return await eventHandler(userId, events.paying); }; /** @@ -73,7 +124,7 @@ const requestPayingEvent = async (userId, roomObject) => { const requestSendingEvent = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; - return await eventHandler(userId, "2명 이상 탑승한 방에서 송금하기"); + return await eventHandler(userId, events.sending); }; /** @@ -83,7 +134,7 @@ const requestSendingEvent = async (userId, roomObject) => { * @usage users/editNicknameHandler */ const requestNicknameChangingEvent = async (userId) => { - return await eventHandler(userId, "닉네임 변경"); + return await eventHandler(userId, events.nicknameChaning); }; /** @@ -93,7 +144,7 @@ const requestNicknameChangingEvent = async (userId) => { * @usage users/editAccountHandler */ const requestAccountChangingEvent = async (userId) => { - return await eventHandler(userId, "계좌 등록 또는 수정"); + return await eventHandler(userId, events.accountChanging); }; const requestAdPushAgreementEvent = async () => { @@ -109,6 +160,7 @@ const requestPurchaseSharingOnInstagram = async () => { }; module.exports = { + events, requestFirstLoginEvent, requestPayingAndSendingEvent, requestFirstRoomCreation, diff --git a/src/lottery/modules/events.js b/src/lottery/modules/events.js index dc447930..18cbd9f2 100644 --- a/src/lottery/modules/events.js +++ b/src/lottery/modules/events.js @@ -5,41 +5,23 @@ const { } = require("./stores/mongo"); const logger = require("../../modules/logger"); -const eventHandler = async (userId, eventName) => { +const eventHandler = async (userId, event) => { try { - logger.info( - `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 호출되었습니다.` - ); - - const event = await eventModel.findOne({ name: eventName }).lean(); - if (!event) { - logger.error( - `eventHandler(userId=${userId}, eventName="${eventName}") 함수에서 예외가 발생했습니다: 알 수 없는 이벤트 이름입니다.` - ); - return null; - } else if (event.isDisabled) { - logger.info( - `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 종료되었습니다: 달성할 수 없는 이벤트입니다.` - ); - return null; - } - const eventStatus = await eventStatusModel.findOne({ userId }).lean(); const eventCount = eventStatus.eventList.filter( - (achievedEventId) => achievedEventId.toString() === event._id.toString() + (achievedEventId) => achievedEventId === event.id ).length; - if (eventCount >= event.maxCount) { + const eventMaxCount = event.maxCount ? event.maxCount : 1; + if (eventCount >= eventMaxCount) { logger.info( - `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 종료되었습니다: 이미 최대로 달성한 이벤트입니다.` + `User ${userId} already achieved ${event.id}Event ${eventCount} times` ); return null; } - const now = Date.now(); - if (now < event.startat || now > event.expireat) { - logger.info( - `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 종료되었습니다: 달성할 수 있는 기간이 아닙니다.` - ); + const eventDoc = await eventModel.findOne({ id: event.id }).lean(); + if (eventDoc && eventDoc.isDisabled) { + logger.info(`User ${userId} failed to achieve disabled ${event.id}Event`); return null; } @@ -50,7 +32,7 @@ const eventHandler = async (userId, eventName) => { creditAmount: event.rewardAmount, }, $push: { - eventList: event._id, + eventList: event.id, }, } ); @@ -59,21 +41,20 @@ const eventHandler = async (userId, eventName) => { type: "get", amount: event.rewardAmount, userId, - event: event._id, + eventId: event.id, comment: `${event.name} 달성 - ${event.rewardAmount}개 획득`, }); await transaction.save(); - logger.info( - `eventHandler(userId=${userId}, eventName="${eventName}") 함수가 종료되었습니다: 성공했습니다.` - ); + logger.info(`User ${userId} successfully achieved ${event.id}Event`); return { event, transactionId: transaction._id, }; } catch (err) { + logger.error(err); logger.error( - `eventHandler(userId=${userId}, eventName="${eventName}") 함수에서 예외가 발생했습니다: ${err}` + `User ${userId} failed to achieve ${event.id}Event due to exception` ); return null; } diff --git a/src/lottery/modules/populates/transactions.js b/src/lottery/modules/populates/transactions.js index f4206d87..9f3a958e 100644 --- a/src/lottery/modules/populates/transactions.js +++ b/src/lottery/modules/populates/transactions.js @@ -1,5 +1,4 @@ const transactionPopulateOption = [ - { path: "event" }, { path: "item", select: "name imageUrl price description isDisabled stock itemType", diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index c2a6ab2c..cf695808 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -13,9 +13,8 @@ const eventStatusSchema = Schema({ required: true, }, eventList: { - type: [Schema.Types.ObjectId], + type: [String], default: [], - ref: "Event", }, creditAmount: { type: Number, @@ -26,34 +25,14 @@ const eventStatusSchema = Schema({ }); const eventSchema = Schema({ - name: { + id: { type: String, required: true, unique: true, }, - rewardAmount: { - type: Number, - required: true, - min: 0, - validate: integerValidator, - }, - maxCount: { - type: Number, - default: 1, - min: 0, - validate: integerValidator, - }, - startat: { - type: Date, - required: true, - }, - expireat: { - type: Date, - required: true, - }, isDisabled: { type: Boolean, - default: false, + required: true, }, }); @@ -120,9 +99,8 @@ const transactionSchema = Schema({ ref: "User", required: true, }, - event: { - type: Schema.Types.ObjectId, - ref: "Event", + eventId: { + type: String, }, item: { type: Schema.Types.ObjectId, diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index f236a7ed..d58c41ab 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -46,10 +46,10 @@ globalStateDocs[`${apiPrefix}/`] = { items: { type: "object", properties: { - _id: { + id: { type: "string", - description: "Event의 ObjectId", - example: "OBJECT ID", + description: "Event의 Id", + example: "EVENT ID", }, name: { type: "string", @@ -66,21 +66,6 @@ globalStateDocs[`${apiPrefix}/`] = { description: "최대 달성 가능 횟수", example: 1, }, - startat: { - type: "string", - description: "달성할 수 있는 처음 시각", - example: "2023-01-01 00:00:00", - }, - expireat: { - type: "string", - description: "달성할 수 있는 마지막 시각", - example: "2023-01-01 00:00:00", - }, - isDisabled: { - type: "boolean", - description: "달성 불가능 여부", - example: false, - }, }, }, }, diff --git a/src/lottery/routes/docs/transactions.js b/src/lottery/routes/docs/transactions.js index 5ceb7e01..3a804448 100644 --- a/src/lottery/routes/docs/transactions.js +++ b/src/lottery/routes/docs/transactions.js @@ -37,47 +37,11 @@ transactionsDocs[`${apiPrefix}/`] = { description: "재화의 변화량의 절댓값", example: 50, }, - event: { - type: "object", + eventId: { + type: "string", description: - "Transaction과 관련된 이벤트의 Object. 이벤트와 관련된 Transaction인 경우에만 포함됩니다.", - properties: { - _id: { - type: "string", - description: "Event의 ObjectId", - example: "OBJECT ID", - }, - name: { - type: "string", - description: "이벤트의 이름", - example: "최초 로그인 이벤트", - }, - rewardAmount: { - type: "number", - description: "달성 보상", - example: 100, - }, - maxCount: { - type: "number", - description: "최대 달성 가능 횟수", - example: 1, - }, - startat: { - type: "string", - description: "달성할 수 있는 처음 시각", - example: "2023-01-01 00:00:00", - }, - expireat: { - type: "string", - description: "달성할 수 있는 마지막 시각", - example: "2023-01-01 00:00:00", - }, - isDisabled: { - type: "boolean", - description: "달성 불가능 여부", - example: false, - }, - }, + "Transaction과 관련된 이벤트의 Id. 이벤트와 관련된 Transaction인 경우에만 포함됩니다.", + example: "EVENT ID", }, item: { type: "object", diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index b771430e..d566e610 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -1,11 +1,12 @@ const { eventStatusModel, - eventModel, transactionModel, - itemModel, } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); +const { eventMode } = require("../../../loadenv"); +const { events } = require(`../modules/contracts/${eventMode}`); + const getUserGlobalStateHandler = async (req, res) => { try { let eventStatus = await eventStatusModel @@ -38,7 +39,6 @@ const getUserGlobalStateHandler = async (req, res) => { }, itemType: 2, }); - const events = await eventModel.find({}, "-__v").lean(); res.json({ creditAmount: eventStatus.creditAmount, From 103b46a173163a9758ad93783d846d901b5d44db Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 14:57:59 +0900 Subject: [PATCH 23/33] Fix: error occuring when eventMode === undefined --- src/lottery/modules/contracts/2023fall.js | 10 +++++++++- src/lottery/modules/events.js | 3 +-- src/lottery/routes/docs/globalState.js | 8 ++++++++ src/lottery/services/globalState.js | 4 +++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 330747ab..51fe1e1f 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -7,6 +7,7 @@ const events = { description: "", imageUrl: "", rewardAmount: 150, + maxCount: 1, }, payingAndSending: { name: "2명 이상 탑승한 방에서 정산/송금 완료", @@ -20,12 +21,14 @@ const events = { description: "", imageUrl: "", rewardAmount: 50, + maxCount: 1, }, roomSharing: { name: "방 공유하기", description: "", imageUrl: "", rewardAmount: 50, + maxCount: 1, }, paying: { name: "2명 이상 탑승한 방에서 정산하기", @@ -46,35 +49,40 @@ const events = { description: "", imageUrl: "", rewardAmount: 50, + maxCount: 1, }, accountChanging: { name: "계좌 등록 또는 변경", description: "", imageUrl: "", rewardAmount: 50, + maxCount: 1, }, adPushAgreement: { name: "광고성 푸시 알림 수신 동의", description: "", imageUrl: "", rewardAmount: 50, + maxCount: 1, }, eventSharingOnInstagram: { name: "이벤트 인스타그램 스토리에 공유", description: "", imageUrl: "", rewardAmount: 100, + maxCount: 1, }, purchaseSharingOnInstagram: { name: "아이템 구매 후 인스타그램 스토리에 공유", description: "", imageUrl: "", rewardAmount: 100, + maxCount: 1, }, }; for (const [id, event] of Object.entries(events)) { - event["id"] = id; // TODO: 외 event.id로는 않돼지???? + event.id = id; } /** diff --git a/src/lottery/modules/events.js b/src/lottery/modules/events.js index 18cbd9f2..7880d53a 100644 --- a/src/lottery/modules/events.js +++ b/src/lottery/modules/events.js @@ -11,8 +11,7 @@ const eventHandler = async (userId, event) => { const eventCount = eventStatus.eventList.filter( (achievedEventId) => achievedEventId === event.id ).length; - const eventMaxCount = event.maxCount ? event.maxCount : 1; - if (eventCount >= eventMaxCount) { + if (eventCount >= event.maxCount) { logger.info( `User ${userId} already achieved ${event.id}Event ${eventCount} times` ); diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 5a3f1240..01d65fa2 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -53,6 +53,14 @@ globalStateDocs[`${apiPrefix}/`] = { description: "Event의 배열", items: { type: "object", + required: [ + "id", + "name", + "description", + "imageUrl", + "rewardAmount", + "maxCount", + ], properties: { id: { type: "string", diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index d566e610..f9680bd2 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -5,7 +5,9 @@ const { const logger = require("../../modules/logger"); const { eventMode } = require("../../../loadenv"); -const { events } = require(`../modules/contracts/${eventMode}`); +const events = eventMode + ? Object.values(require(`../modules/contracts/${eventMode}`).events) + : undefined; const getUserGlobalStateHandler = async (req, res) => { try { From b5a1f773e81803b23b75fa04cb9d50f58d343c73 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 21:17:02 +0900 Subject: [PATCH 24/33] Fix: rename event to quest --- src/lottery/index.js | 4 +- src/lottery/modules/contracts/2023fall.js | 80 +++++++++++------------ src/lottery/modules/events.js | 64 ------------------ src/lottery/modules/quests.js | 66 +++++++++++++++++++ src/lottery/modules/stores/mongo.js | 16 ++--- src/lottery/routes/docs/globalState.js | 28 ++++---- src/lottery/routes/docs/transactions.js | 6 +- src/lottery/services/globalState.js | 8 +-- src/services/auth.js | 2 +- src/services/auth.mobile.js | 2 +- src/services/rooms.js | 10 +-- src/services/users.js | 4 +- 12 files changed, 142 insertions(+), 148 deletions(-) delete mode 100644 src/lottery/modules/events.js create mode 100644 src/lottery/modules/quests.js diff --git a/src/lottery/index.js b/src/lottery/index.js index 1ecef91d..71f5cf94 100644 --- a/src/lottery/index.js +++ b/src/lottery/index.js @@ -1,7 +1,7 @@ const express = require("express"); const { eventStatusModel, - eventModel, + questModel, itemModel, transactionModel, } = require("./modules/stores/mongo"); @@ -24,7 +24,7 @@ lotteryRouter.use("/items", require("./routes/items")); const resources = [ eventStatusModel, - eventModel, + questModel, itemModel, transactionModel, ].map(buildResource()); diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 51fe1e1f..01b05186 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,7 +1,7 @@ -const { eventHandler } = require("../events"); +const { completeQuest } = require("../quests"); const mongoose = require("mongoose"); -const events = { +const quests = { firstLogin: { name: "이벤트 기간 첫 로그인", description: "", @@ -81,18 +81,18 @@ const events = { }, }; -for (const [id, event] of Object.entries(events)) { - event.id = id; +for (const [id, quest] of Object.entries(quests)) { + quest.id = id; } /** - * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 로그인할 때마다 호출해 주세요. * @usage auth/tryLogin, auth.mobile/tokenLoginHandler */ -const requestFirstLoginEvent = async (userId) => { - return await eventHandler(userId, events.firstLogin); +const completeFirstLoginQuest = async (userId) => { + return await completeQuest(userId, quests.firstLogin); }; /** @@ -103,103 +103,103 @@ const requestFirstLoginEvent = async (userId) => { * @description 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요. * @usage rooms/commitPaymentHandler, rooms/settlementHandler */ -const requestPayingAndSendingEvent = async (roomObject) => { +const completePayingAndSendingQuest = async (roomObject) => { if (roomObject.part.length < 2) return null; if (roomObject.part.length > roomObject.settlementTotal) return null; return await Promise.all( roomObject.part.map( async (participant) => - await eventHandler(participant.user._id, events.payingAndSending) + await completeQuest(participant.user._id, quests.payingAndSending) ) ); }; /** - * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 방을 만들 때마다 호출해 주세요. * @usage rooms/createHandler */ -const requestFirstRoomCreation = async (userId) => { - return await eventHandler(userId, events.firstRoomCreation); +const completeFirstRoomCreationQuest = async (userId) => { + return await completeQuest(userId, quests.firstRoomCreation); }; -const requestRoomSharingEvent = async () => { +const completeRoomSharingQuest = async () => { // TODO }; /** - * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} * @description 정산 요청이 이루어질 때마다 호출해 주세요. * @usage rooms/commitPaymentHandler */ -const requestPayingEvent = async (userId, roomObject) => { +const completePayingQuest = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; - return await eventHandler(userId, events.paying); + return await completeQuest(userId, quests.paying); }; /** - * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} * @description 송금이 이루어질 때마다 호출해 주세요. * @usage rooms/settlementHandler */ -const requestSendingEvent = async (userId, roomObject) => { +const completeSendingQuest = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; - return await eventHandler(userId, events.sending); + return await completeQuest(userId, quests.sending); }; /** - * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 닉네임을 변경할 때마다 호출해 주세요. * @usage users/editNicknameHandler */ -const requestNicknameChangingEvent = async (userId) => { - return await eventHandler(userId, events.nicknameChaning); +const completeNicknameChangingQuest = async (userId) => { + return await completeQuest(userId, quests.nicknameChaning); }; /** - * @param {string|mongoose.Types.ObjectId} userId - 이벤트를 달성한 사용자의 ObjectId입니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. * @returns {Promise} * @description 계좌를 변경할 때마다 호출해 주세요. * @usage users/editAccountHandler */ -const requestAccountChangingEvent = async (userId) => { - return await eventHandler(userId, events.accountChanging); +const completeAccountChangingQuest = async (userId) => { + return await completeQuest(userId, quests.accountChanging); }; -const requestAdPushAgreementEvent = async () => { +const completeAdPushAgreementQuest = async () => { // TODO }; -const requestEventSharingOnInstagram = async () => { +const completeEventSharingOnInstagramQuest = async () => { // TODO }; -const requestPurchaseSharingOnInstagram = async () => { +const completePurchaseSharingOnInstagramQuest = async () => { // TODO }; module.exports = { - events, - requestFirstLoginEvent, - requestPayingAndSendingEvent, - requestFirstRoomCreation, - requestRoomSharingEvent, - requestPayingEvent, - requestSendingEvent, - requestNicknameChangingEvent, - requestAccountChangingEvent, - requestAdPushAgreementEvent, - requestEventSharingOnInstagram, - requestPurchaseSharingOnInstagram, + quests, + completeFirstLoginQuest, + completePayingAndSendingQuest, + completeFirstRoomCreationQuest, + completeRoomSharingQuest, + completePayingQuest, + completeSendingQuest, + completeNicknameChangingQuest, + completeAccountChangingQuest, + completeAdPushAgreementQuest, + completeEventSharingOnInstagramQuest, + completePurchaseSharingOnInstagramQuest, }; diff --git a/src/lottery/modules/events.js b/src/lottery/modules/events.js deleted file mode 100644 index 7880d53a..00000000 --- a/src/lottery/modules/events.js +++ /dev/null @@ -1,64 +0,0 @@ -const { - eventStatusModel, - eventModel, - transactionModel, -} = require("./stores/mongo"); -const logger = require("../../modules/logger"); - -const eventHandler = async (userId, event) => { - try { - const eventStatus = await eventStatusModel.findOne({ userId }).lean(); - const eventCount = eventStatus.eventList.filter( - (achievedEventId) => achievedEventId === event.id - ).length; - if (eventCount >= event.maxCount) { - logger.info( - `User ${userId} already achieved ${event.id}Event ${eventCount} times` - ); - return null; - } - - const eventDoc = await eventModel.findOne({ id: event.id }).lean(); - if (eventDoc && eventDoc.isDisabled) { - logger.info(`User ${userId} failed to achieve disabled ${event.id}Event`); - return null; - } - - await eventStatusModel.updateOne( - { userId }, - { - $inc: { - creditAmount: event.rewardAmount, - }, - $push: { - eventList: event.id, - }, - } - ); - - const transaction = new transactionModel({ - type: "get", - amount: event.rewardAmount, - userId, - eventId: event.id, - comment: `${event.name} 달성 - ${event.rewardAmount}개 획득`, - }); - await transaction.save(); - - logger.info(`User ${userId} successfully achieved ${event.id}Event`); - return { - event, - transactionId: transaction._id, - }; - } catch (err) { - logger.error(err); - logger.error( - `User ${userId} failed to achieve ${event.id}Event due to exception` - ); - return null; - } -}; - -module.exports = { - eventHandler, -}; diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js new file mode 100644 index 00000000..33ec9d5f --- /dev/null +++ b/src/lottery/modules/quests.js @@ -0,0 +1,66 @@ +const { + eventStatusModel, + questModel, + transactionModel, +} = require("./stores/mongo"); +const logger = require("../../modules/logger"); + +const completeQuest = async (userId, quest) => { + try { + const eventStatus = await eventStatusModel.findOne({ userId }).lean(); + const questCount = eventStatus.completedQuests.filter( + (completedQuestId) => completedQuestId === quest.id + ).length; + if (questCount >= quest.maxCount) { + logger.info( + `User ${userId} already completed ${quest.id}Quest ${questCount} times` + ); + return null; + } + + const questDoc = await questModel.findOne({ id: quest.id }).lean(); + if (questDoc && questDoc.isDisabled) { + logger.info( + `User ${userId} failed to complete disabled ${quest.id}Quest` + ); + return null; + } + + await eventStatusModel.updateOne( + { userId }, + { + $inc: { + creditAmount: quest.rewardAmount, + }, + $push: { + completedQuests: quest.id, + }, + } + ); + + const transaction = new transactionModel({ + type: "get", + amount: quest.rewardAmount, + userId, + questId: quest.id, + comment: `${quest.name} 달성 - ${quest.rewardAmount}개 획득`, + }); + await transaction.save(); + + logger.info(`User ${userId} successfully completed ${quest.id}Quest`); + return { + quest, + transactionId: transaction._id, + }; + } catch (err) { + logger.error(err); + logger.error( + `User ${userId} failed to complete ${quest.id}Quest due to exception` + ); + return null; + } +}; + +module.exports = { + completeQuest, +}; diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 13ce77eb..2be710b8 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -12,7 +12,7 @@ const eventStatusSchema = Schema({ ref: "User", required: true, }, - eventList: { + completedQuests: { type: [String], default: [], }, @@ -24,7 +24,7 @@ const eventStatusSchema = Schema({ }, }); -const eventSchema = Schema({ +const questSchema = Schema({ id: { type: String, required: true, @@ -34,14 +34,6 @@ const eventSchema = Schema({ type: Boolean, required: true, }, - imageUrl: { - type: String, - required: true, - }, - description: { - type: String, - required: true, - }, }); const itemSchema = Schema({ @@ -107,7 +99,7 @@ const transactionSchema = Schema({ ref: "User", required: true, }, - eventId: { + questId: { type: String, }, item: { @@ -130,7 +122,7 @@ transactionSchema.set("timestamps", { module.exports = { eventStatusModel: mongoose.model("EventStatus", eventStatusSchema), - eventModel: mongoose.model("Event", eventSchema), + questModel: mongoose.model("Quest", questSchema), itemModel: mongoose.model("Item", itemSchema), transactionModel: mongoose.model("Transaction", transactionSchema), }; diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 01d65fa2..296e0de9 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -7,7 +7,7 @@ globalStateDocs[`${apiPrefix}/`] = { tags: [`${apiPrefix}`], summary: "Frontend에서 Global state로 관리하는 정보 반환", description: - "유저의 재화 개수, 이벤트 달성 상태, 추첨권 개수 등 Frontend에서 Global state로 관리할 정보를 가져옵니다. 유저에 대한 EventStatus Document가 없을 경우 새롭게 생성하며, 유일한 생성 지점입니다.", + "유저의 재화 개수, 퀘스트 달성 상태 등 Frontend에서 Global state로 관리할 정보를 가져옵니다. 유저에 대한 EventStatus Document가 없을 경우 새롭게 생성하며, 유일한 생성 지점입니다.", responses: { 200: { description: "", @@ -17,10 +17,10 @@ globalStateDocs[`${apiPrefix}/`] = { type: "object", required: [ "creditAmount", - "eventStatus", + "completedQuests", "ticket1Amount", "ticket2Amount", - "events", + "quests", ], properties: { creditAmount: { @@ -28,14 +28,14 @@ globalStateDocs[`${apiPrefix}/`] = { description: "재화 개수. 0 이상입니다.", example: 10000, }, - eventStatus: { + completedQuests: { type: "array", description: - "유저가 달성한 이벤트의 배열. 여러 번 달성할 수 있는 이벤트의 경우 배열 내에 같은 이벤트가 여러 번 포함될 수 있습니다.", + "유저가 달성한 퀘스트의 배열. 여러 번 달성할 수 있는 퀘스트의 경우 배열 내에 같은 퀘스트가 여러 번 포함됩니다.", items: { type: "string", - description: "Event의 ObjectId", - example: "OBJECT ID", + description: "Quest의 Id", + example: "QUEST ID", }, }, ticket1Amount: { @@ -48,9 +48,9 @@ globalStateDocs[`${apiPrefix}/`] = { description: "고급 티켓의 개수. 0 이상입니다.", example: 10, }, - events: { + quests: { type: "array", - description: "Event의 배열", + description: "Quest의 배열", items: { type: "object", required: [ @@ -64,17 +64,17 @@ globalStateDocs[`${apiPrefix}/`] = { properties: { id: { type: "string", - description: "Event의 Id", - example: "EVENT ID", + description: "Quest의 Id", + example: "QUEST ID", }, name: { type: "string", - description: "이벤트의 이름", - example: "최초 로그인 이벤트", + description: "퀘스트의 이름", + example: "최초 로그인 퀘스트", }, description: { type: "string", - description: "이벤트의 설명", + description: "퀘스트의 설명", example: "처음으로 이벤트 기간 중 Taxi에 로그인하면 송편을 드립니다.", }, diff --git a/src/lottery/routes/docs/transactions.js b/src/lottery/routes/docs/transactions.js index 936b925a..42545722 100644 --- a/src/lottery/routes/docs/transactions.js +++ b/src/lottery/routes/docs/transactions.js @@ -39,11 +39,11 @@ transactionsDocs[`${apiPrefix}/`] = { description: "재화의 변화량의 절댓값", example: 50, }, - eventId: { + questId: { type: "string", description: - "Transaction과 관련된 이벤트의 Id. 이벤트와 관련된 Transaction인 경우에만 포함됩니다.", - example: "EVENT ID", + "Transaction과 관련된 퀘스트의 Id. 퀘스트와 관련된 Transaction인 경우에만 포함됩니다.", + example: "QUEST ID", }, item: { $ref: "#/components/schemas/relatedItem", diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index f9680bd2..79c391fd 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -5,8 +5,8 @@ const { const logger = require("../../modules/logger"); const { eventMode } = require("../../../loadenv"); -const events = eventMode - ? Object.values(require(`../modules/contracts/${eventMode}`).events) +const quests = eventMode + ? Object.values(require(`../modules/contracts/${eventMode}`).quests) : undefined; const getUserGlobalStateHandler = async (req, res) => { @@ -44,10 +44,10 @@ const getUserGlobalStateHandler = async (req, res) => { res.json({ creditAmount: eventStatus.creditAmount, - eventStatus: eventStatus.eventList.map((id) => id.toString()), + completedQuests: eventStatus.completedQuests, ticket1Amount, ticket2Amount, - events, + quests, }); } catch (err) { logger.error(err); diff --git a/src/services/auth.js b/src/services/auth.js index 7b0851f1..6e6b09ca 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -96,7 +96,7 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { login(req, userData.sid, user.id, user._id, user.name); // 이벤트 코드입니다. - await contracts?.requestFirstLoginEvent(user._id); + await contracts?.completeFirstLoginQuest(user._id); res.redirect(new URL(redirectPath, redirectOrigin).href); } catch (err) { diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index e2303961..90c9a337 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -41,7 +41,7 @@ const tokenLoginHandler = async (req, res) => { req.session.deviceToken = deviceToken; // 이벤트 코드입니다. - await contracts?.requestFirstLoginEvent(user._id); + await contracts?.completeFirstLoginQuest(user._id); return res.status(200).json({ message: "success" }); } catch (e) { diff --git a/src/services/rooms.js b/src/services/rooms.js index 08f0776e..eb158278 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -87,7 +87,7 @@ const createHandler = async (req, res) => { const roomObjectFormated = formatSettlement(roomObject); // 이벤트 코드입니다. - await contracts?.requestFirstRoomCreation(user._id); + await contracts?.completeFirstRoomCreationQuest(user._id); return res.send(roomObjectFormated); } catch (err) { @@ -492,8 +492,8 @@ const commitPaymentHandler = async (req, res) => { }); // 이벤트 코드입니다. - await contracts?.requestPayingEvent(user._id, roomObject); - await contracts?.requestPayingAndSendingEvent(roomObject); + await contracts?.completePayingQuest(user._id, roomObject); + await contracts?.completePayingAndSendingQuest(roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); @@ -562,8 +562,8 @@ const settlementHandler = async (req, res) => { }); // 이벤트 코드입니다. - await contracts?.requestSendingEvent(user._id, roomObject); - await contracts?.requestPayingAndSendingEvent(roomObject); + await contracts?.completeSendingQuest(user._id, roomObject); + await contracts?.completePayingAndSendingQuest(roomObject); // 수정한 방 정보를 반환합니다. res.send(formatSettlement(roomObject, { isOver: true })); diff --git a/src/services/users.js b/src/services/users.js index d37bf502..7eada946 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -46,7 +46,7 @@ const editNicknameHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await contracts?.requestNicknameChangingEvent(req.userOid); + await contracts?.completeNicknameChangingQuest(req.userOid); res.status(200).send("User/editNickname : edit user nickname successful"); } else { @@ -68,7 +68,7 @@ const editAccountHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await contracts?.requestAccountChangingEvent(req.userOid); + await contracts?.completeAccountChangingQuest(req.userOid); res.status(200).send("User/editAccount : edit user account successful"); } else { From c0d6649bdf31fc3778d6de974cef9985b1276ce1 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 21:21:56 +0900 Subject: [PATCH 25/33] Refactor: rename doneat to createAt in transactionSchema --- src/lottery/modules/stores/mongo.js | 2 +- src/lottery/routes/docs/transactions.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 2be710b8..53db8155 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -116,7 +116,7 @@ const transactionSchema = Schema({ }, }); transactionSchema.set("timestamps", { - createdAt: "doneat", + createdAt: "createAt", updatedAt: false, }); diff --git a/src/lottery/routes/docs/transactions.js b/src/lottery/routes/docs/transactions.js index 42545722..60e7435b 100644 --- a/src/lottery/routes/docs/transactions.js +++ b/src/lottery/routes/docs/transactions.js @@ -21,7 +21,7 @@ transactionsDocs[`${apiPrefix}/`] = { description: "유저의 재화 입출금 기록의 배열", items: { type: "object", - required: ["_id", "type", "amount", "comment", "doneat"], + required: ["_id", "type", "amount", "comment", "createAt"], properties: { _id: { type: "string", @@ -53,7 +53,7 @@ transactionsDocs[`${apiPrefix}/`] = { description: "입출금 내역에 대한 설명", example: "랜덤 상자 구입 - 50개 차감", }, - doneat: { + createAt: { type: "string", description: "입출금이 일어난 시각", example: "2023-01-01 00:00:00", From 5c2ac3c4587fc4ae84ef3b58042c9674796920fc Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 21:35:45 +0900 Subject: [PATCH 26/33] Docs: add description about completeQuest function --- src/lottery/modules/contracts/2023fall.js | 19 +++++++++++++------ src/lottery/modules/quests.js | 11 +++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 01b05186..935d0a1e 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -86,7 +86,8 @@ for (const [id, quest] of Object.entries(quests)) { } /** - * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. + * firstLogin 퀘스트의 완료를 요청합니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @returns {Promise} * @description 로그인할 때마다 호출해 주세요. * @usage auth/tryLogin, auth.mobile/tokenLoginHandler @@ -96,6 +97,7 @@ const completeFirstLoginQuest = async (userId) => { }; /** + * payingAndSending 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이거나, 모든 참가자가 정산 또는 송금을 완료하지 않았다면 요청하지 않습니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @param {number} roomObject.settlementTotal - 정산 또는 송금이 완료된 참여자 수입니다. @@ -116,7 +118,8 @@ const completePayingAndSendingQuest = async (roomObject) => { }; /** - * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. + * firstRoomCreation 퀘스트의 완료를 요청합니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @returns {Promise} * @description 방을 만들 때마다 호출해 주세요. * @usage rooms/createHandler @@ -130,7 +133,8 @@ const completeRoomSharingQuest = async () => { }; /** - * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. + * paying 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} @@ -144,7 +148,8 @@ const completePayingQuest = async (userId, roomObject) => { }; /** - * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. + * sending 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @param {Object} roomObject - 방의 정보입니다. * @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다. * @returns {Promise} @@ -158,7 +163,8 @@ const completeSendingQuest = async (userId, roomObject) => { }; /** - * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. + * nicknameChaning 퀘스트의 완료를 요청합니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @returns {Promise} * @description 닉네임을 변경할 때마다 호출해 주세요. * @usage users/editNicknameHandler @@ -168,7 +174,8 @@ const completeNicknameChangingQuest = async (userId) => { }; /** - * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 달성한 사용자의 ObjectId입니다. + * accountChanging 퀘스트의 완료를 요청합니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @returns {Promise} * @description 계좌를 변경할 때마다 호출해 주세요. * @usage users/editAccountHandler diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index 33ec9d5f..b1ebf390 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -4,7 +4,18 @@ const { transactionModel, } = require("./stores/mongo"); const logger = require("../../modules/logger"); +const mongoose = require("mongoose"); +/** + * 퀘스트 완료를 요청합니다. + * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {Object} quest - 퀘스트의 정보입니다. + * @param {string} quest.id - 퀘스트의 Id입니다. + * @param {string} quest.name - 퀘스트의 이름입니다. + * @param {number} quest.rewardAmount - 퀘스트의 완료 보상입니다. + * @param {number} quest.maxCount - 퀘스트의 최대 완료 가능 횟수입니다. + * @returns {Object|null} 성공한 경우 Object를, 실패한 경우 null을 반환합니다. 이미 최대 완료 횟수에 도달했거나, 퀘스트가 원격으로 비활성화 된 경우에도 실패로 처리됩니다. + */ const completeQuest = async (userId, quest) => { try { const eventStatus = await eventStatusModel.findOne({ userId }).lean(); From e514be4fd6cbc9ad875b5e5ef49aa91dc1b1cd5c Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 22:13:35 +0900 Subject: [PATCH 27/33] Add: check current time in some functions --- src/lottery/modules/contracts/2023fall.js | 7 +++++++ src/lottery/modules/quests.js | 13 ++++++++++++ src/lottery/services/items.js | 24 +++++++++++++++++------ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 935d0a1e..9332937f 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,6 +1,7 @@ const { completeQuest } = require("../quests"); const mongoose = require("mongoose"); +/** 전체 퀘스트 목록입니다. */ const quests = { firstLogin: { name: "이벤트 기간 첫 로그인", @@ -85,6 +86,11 @@ for (const [id, quest] of Object.entries(quests)) { quest.id = id; } +const eventPeriod = { + start: new Date("2023-09-25T00:00:00+09:00"), // Inclusive + end: new Date("2023-10-10T00:00:00+09:00"), // Exclusive +}; + /** * firstLogin 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. @@ -198,6 +204,7 @@ const completePurchaseSharingOnInstagramQuest = async () => { module.exports = { quests, + eventPeriod, completeFirstLoginQuest, completePayingAndSendingQuest, completeFirstRoomCreationQuest, diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index b1ebf390..a67881e4 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -6,6 +6,11 @@ const { const logger = require("../../modules/logger"); const mongoose = require("mongoose"); +const { eventMode } = require("../../../loadenv"); +const eventPeriod = eventMode + ? require(`../modules/contracts/${eventMode}`).eventPeriod + : undefined; + /** * 퀘스트 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. @@ -18,6 +23,14 @@ const mongoose = require("mongoose"); */ const completeQuest = async (userId, quest) => { try { + const now = Date.now(); + if (now >= eventPeriod.end || now < eventPeriod.start) { + logger.info( + `User ${userId} failed to complete auto-disabled ${quest.id}Quest` + ); + return null; + } + const eventStatus = await eventStatusModel.findOne({ userId }).lean(); const questCount = eventStatus.completedQuests.filter( (completedQuestId) => completedQuestId === quest.id diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index d47c2479..78cc79db 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -2,8 +2,16 @@ const { itemModel, transactionModel } = require("../modules/stores/mongo"); const logger = require("../../modules/logger"); const { useUserCreditAmount } = require("../modules/credit"); +const { eventMode } = require("../../../loadenv"); +const eventPeriod = eventMode + ? require(`../modules/contracts/${eventMode}`).eventPeriod + : undefined; + const getRandomItem = async (req, depth) => { - if (depth >= 10) return null; + if (depth >= 10) { + logger.error(`User ${req.userOid} failed to open random box`); + return null; + } const items = await itemModel .find({ @@ -22,10 +30,9 @@ const getRandomItem = async (req, depth) => { .join(","); logger.info( - `[RandomBox] getRandomItem(depth=${depth}) is called by the user(id=${req.userOid}).` - ); - logger.info( - `[RandomBox] randomItems of the user(id=${req.userOid}) is [${dumpRandomItems}].` + `User ${req.userOid}'s ${ + depth + 1 + }th random box probability is: [${dumpRandomItems}]` ); if (randomItems.length === 0) return null; @@ -65,8 +72,9 @@ const getRandomItem = async (req, depth) => { return newRandomItem; } catch (err) { + logger.error(err); logger.warn( - `[RandomBox] getRandomItem(depth=${depth}) by the user(id=${req.userOid}) failed due to ${err}.` + `User ${req.userOid}'s ${depth + 1}th random box failed due to exception` ); return await getRandomItem(req, depth + 1); @@ -87,6 +95,10 @@ const listHandler = async (_, res) => { const purchaseHandler = async (req, res) => { try { + const now = Date.now(); + if (now >= eventPeriod.end || now < eventPeriod.start) + return res.status(400).json({ error: "Items/Purchase : out of date" }); + const { itemId } = req.params; const item = await itemModel.findOne({ _id: itemId }).lean(); if (!item) From 54457fd4664b958a15e1d03fca7145fc922baa5b Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 23:09:26 +0900 Subject: [PATCH 28/33] Fix: bug related to firstLoginEvent --- src/lottery/modules/contracts/2023fall.js | 18 +++++++++++------- src/lottery/modules/quests.js | 17 ++++++++++------- src/lottery/routes/docs/globalState.js | 2 +- src/lottery/services/globalState.js | 2 -- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 9332937f..c8f4774a 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -99,7 +99,7 @@ const eventPeriod = { * @usage auth/tryLogin, auth.mobile/tokenLoginHandler */ const completeFirstLoginQuest = async (userId) => { - return await completeQuest(userId, quests.firstLogin); + return await completeQuest(userId, eventPeriod, quests.firstLogin); }; /** @@ -118,7 +118,11 @@ const completePayingAndSendingQuest = async (roomObject) => { return await Promise.all( roomObject.part.map( async (participant) => - await completeQuest(participant.user._id, quests.payingAndSending) + await completeQuest( + participant.user._id, + eventPeriod, + quests.payingAndSending + ) ) ); }; @@ -131,7 +135,7 @@ const completePayingAndSendingQuest = async (roomObject) => { * @usage rooms/createHandler */ const completeFirstRoomCreationQuest = async (userId) => { - return await completeQuest(userId, quests.firstRoomCreation); + return await completeQuest(userId, eventPeriod, quests.firstRoomCreation); }; const completeRoomSharingQuest = async () => { @@ -150,7 +154,7 @@ const completeRoomSharingQuest = async () => { const completePayingQuest = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; - return await completeQuest(userId, quests.paying); + return await completeQuest(userId, eventPeriod, quests.paying); }; /** @@ -165,7 +169,7 @@ const completePayingQuest = async (userId, roomObject) => { const completeSendingQuest = async (userId, roomObject) => { if (roomObject.part.length < 2) return null; - return await completeQuest(userId, quests.sending); + return await completeQuest(userId, eventPeriod, quests.sending); }; /** @@ -176,7 +180,7 @@ const completeSendingQuest = async (userId, roomObject) => { * @usage users/editNicknameHandler */ const completeNicknameChangingQuest = async (userId) => { - return await completeQuest(userId, quests.nicknameChaning); + return await completeQuest(userId, eventPeriod, quests.nicknameChaning); }; /** @@ -187,7 +191,7 @@ const completeNicknameChangingQuest = async (userId) => { * @usage users/editAccountHandler */ const completeAccountChangingQuest = async (userId) => { - return await completeQuest(userId, quests.accountChanging); + return await completeQuest(userId, eventPeriod, quests.accountChanging); }; const completeAdPushAgreementQuest = async () => { diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index a67881e4..3b98811d 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -6,14 +6,12 @@ const { const logger = require("../../modules/logger"); const mongoose = require("mongoose"); -const { eventMode } = require("../../../loadenv"); -const eventPeriod = eventMode - ? require(`../modules/contracts/${eventMode}`).eventPeriod - : undefined; - /** * 퀘스트 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {Object} eventPeriod - 이벤트의 기간입니다. + * @param {Date} eventPeriod.start - 이벤트의 시작 시각(Inclusive)입니다. + * @param {Date} eventPeriod.end - 이벤트의 종료 시각(Exclusive)입니다. * @param {Object} quest - 퀘스트의 정보입니다. * @param {string} quest.id - 퀘스트의 Id입니다. * @param {string} quest.name - 퀘스트의 이름입니다. @@ -21,7 +19,7 @@ const eventPeriod = eventMode * @param {number} quest.maxCount - 퀘스트의 최대 완료 가능 횟수입니다. * @returns {Object|null} 성공한 경우 Object를, 실패한 경우 null을 반환합니다. 이미 최대 완료 횟수에 도달했거나, 퀘스트가 원격으로 비활성화 된 경우에도 실패로 처리됩니다. */ -const completeQuest = async (userId, quest) => { +const completeQuest = async (userId, eventPeriod, quest) => { try { const now = Date.now(); if (now >= eventPeriod.end || now < eventPeriod.start) { @@ -31,7 +29,12 @@ const completeQuest = async (userId, quest) => { return null; } - const eventStatus = await eventStatusModel.findOne({ userId }).lean(); + let eventStatus = await eventStatusModel.findOne({ userId }).lean(); + if (!eventStatus) { + eventStatus = new eventStatusModel({ userId }); + await eventStatus.save(); + } + const questCount = eventStatus.completedQuests.filter( (completedQuestId) => completedQuestId === quest.id ).length; diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index 296e0de9..f05630db 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -7,7 +7,7 @@ globalStateDocs[`${apiPrefix}/`] = { tags: [`${apiPrefix}`], summary: "Frontend에서 Global state로 관리하는 정보 반환", description: - "유저의 재화 개수, 퀘스트 달성 상태 등 Frontend에서 Global state로 관리할 정보를 가져옵니다. 유저에 대한 EventStatus Document가 없을 경우 새롭게 생성하며, 유일한 생성 지점입니다.", + "유저의 재화 개수, 퀘스트 달성 상태 등 Frontend에서 Global state로 관리할 정보를 가져옵니다. 유저에 대한 EventStatus Document가 없을 경우 새롭게 생성합니다.", responses: { 200: { description: "", diff --git a/src/lottery/services/globalState.js b/src/lottery/services/globalState.js index 79c391fd..7f801cb8 100644 --- a/src/lottery/services/globalState.js +++ b/src/lottery/services/globalState.js @@ -15,8 +15,6 @@ const getUserGlobalStateHandler = async (req, res) => { .findOne({ userId: req.userOid }) .lean(); if (!eventStatus) { - // User마다 EventStatus를 가져야 하고, 현재 Taxi에는 회원 탈퇴 시스템이 없으므로, EventStatus가 없으면 새롭게 생성하도록 구현합니다. - // EventStatus의 생성은 이곳에서만 이루어집니다!! eventStatus = new eventStatusModel({ userId: req.userOid, }); From 39816d20d408b3493b8a9a889382bf06f78d4896 Mon Sep 17 00:00:00 2001 From: static Date: Sat, 16 Sep 2023 23:30:33 +0900 Subject: [PATCH 29/33] Add: buildQuests function in modules/quests.js --- src/lottery/modules/contracts/2023fall.js | 20 ++++---------------- src/lottery/modules/quests.js | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index c8f4774a..0121c3c1 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -1,14 +1,13 @@ -const { completeQuest } = require("../quests"); +const { buildQuests, completeQuest } = require("../quests"); const mongoose = require("mongoose"); /** 전체 퀘스트 목록입니다. */ -const quests = { +const quests = buildQuests({ firstLogin: { name: "이벤트 기간 첫 로그인", description: "", imageUrl: "", rewardAmount: 150, - maxCount: 1, }, payingAndSending: { name: "2명 이상 탑승한 방에서 정산/송금 완료", @@ -22,14 +21,12 @@ const quests = { description: "", imageUrl: "", rewardAmount: 50, - maxCount: 1, }, roomSharing: { name: "방 공유하기", description: "", imageUrl: "", rewardAmount: 50, - maxCount: 1, }, paying: { name: "2명 이상 탑승한 방에서 정산하기", @@ -50,44 +47,35 @@ const quests = { description: "", imageUrl: "", rewardAmount: 50, - maxCount: 1, }, accountChanging: { name: "계좌 등록 또는 변경", description: "", imageUrl: "", rewardAmount: 50, - maxCount: 1, }, adPushAgreement: { name: "광고성 푸시 알림 수신 동의", description: "", imageUrl: "", rewardAmount: 50, - maxCount: 1, }, eventSharingOnInstagram: { name: "이벤트 인스타그램 스토리에 공유", description: "", imageUrl: "", rewardAmount: 100, - maxCount: 1, }, purchaseSharingOnInstagram: { name: "아이템 구매 후 인스타그램 스토리에 공유", description: "", imageUrl: "", rewardAmount: 100, - maxCount: 1, }, -}; - -for (const [id, quest] of Object.entries(quests)) { - quest.id = id; -} +}); const eventPeriod = { - start: new Date("2023-09-25T00:00:00+09:00"), // Inclusive + start: new Date("2023-09-10T00:00:00+09:00"), // Inclusive end: new Date("2023-10-10T00:00:00+09:00"), // Exclusive }; diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index 3b98811d..3303fa05 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -6,6 +6,27 @@ const { const logger = require("../../modules/logger"); const mongoose = require("mongoose"); +const requiredQuestFields = ["name", "description", "imageUrl", "rewardAmount"]; +const buildQuests = (quests) => { + for (const [id, quest] of Object.entries(quests)) { + const hasError = requiredQuestFields.reduce((before, field) => { + if (quest[field] !== undefined) return before; + + logger.error(`There is no ${field} field in ${id}Quest`); + return true; + }, false); + if (hasError) return null; + + quest.id = id; + + if (!quest.maxCount) { + quest.maxCount = 1; + } + } + + return quests; +}; + /** * 퀘스트 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. @@ -89,5 +110,6 @@ const completeQuest = async (userId, eventPeriod, quest) => { }; module.exports = { + buildQuests, completeQuest, }; From 5f5b10345b755c7c9c7ab037a62e4cc641db4f65 Mon Sep 17 00:00:00 2001 From: static Date: Sun, 17 Sep 2023 17:28:30 +0900 Subject: [PATCH 30/33] Fix: accountChangingQuest completion condition --- src/lottery/modules/contracts/2023fall.js | 7 ++++--- src/services/auth.js | 6 ------ src/services/auth.mobile.js | 6 ------ src/services/users.js | 2 +- 4 files changed, 5 insertions(+), 16 deletions(-) diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 0121c3c1..4e4f9846 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -83,8 +83,6 @@ const eventPeriod = { * firstLogin 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. * @returns {Promise} - * @description 로그인할 때마다 호출해 주세요. - * @usage auth/tryLogin, auth.mobile/tokenLoginHandler */ const completeFirstLoginQuest = async (userId) => { return await completeQuest(userId, eventPeriod, quests.firstLogin); @@ -174,11 +172,14 @@ const completeNicknameChangingQuest = async (userId) => { /** * accountChanging 퀘스트의 완료를 요청합니다. * @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다. + * @param {string} newAccount - 변경된 계좌입니다. * @returns {Promise} * @description 계좌를 변경할 때마다 호출해 주세요. * @usage users/editAccountHandler */ -const completeAccountChangingQuest = async (userId) => { +const completeAccountChangingQuest = async (userId, newAccount) => { + if (newAccount === "") return null; + return await completeQuest(userId, eventPeriod, quests.accountChanging); }; diff --git a/src/services/auth.js b/src/services/auth.js index 6e6b09ca..83b598de 100644 --- a/src/services/auth.js +++ b/src/services/auth.js @@ -16,9 +16,6 @@ const { const jwt = require("../modules/auths/jwt"); const logger = require("../modules/logger"); -// 이벤트 코드입니다. -const { contracts } = require("../lottery"); - // SPARCS SSO const Client = require("../modules/auths/sparcssso"); const client = new Client(sparcsssoEnv?.id, sparcsssoEnv?.key); @@ -95,9 +92,6 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => { login(req, userData.sid, user.id, user._id, user.name); - // 이벤트 코드입니다. - await contracts?.completeFirstLoginQuest(user._id); - res.redirect(new URL(redirectPath, redirectOrigin).href); } catch (err) { logger.error(err); diff --git a/src/services/auth.mobile.js b/src/services/auth.mobile.js index 90c9a337..0e537b33 100644 --- a/src/services/auth.mobile.js +++ b/src/services/auth.mobile.js @@ -10,9 +10,6 @@ const logger = require("../modules/logger"); const { TOKEN_EXPIRED, TOKEN_INVALID } = require("../../loadenv").jwt; -// 이벤트 코드입니다. -const { contracts } = require("../lottery"); - const tokenLoginHandler = async (req, res) => { const { accessToken, deviceToken } = req.query; try { @@ -40,9 +37,6 @@ const tokenLoginHandler = async (req, res) => { req.session.isApp = true; req.session.deviceToken = deviceToken; - // 이벤트 코드입니다. - await contracts?.completeFirstLoginQuest(user._id); - return res.status(200).json({ message: "success" }); } catch (e) { logger.error(e); diff --git a/src/services/users.js b/src/services/users.js index 7eada946..b7b47e41 100644 --- a/src/services/users.js +++ b/src/services/users.js @@ -68,7 +68,7 @@ const editAccountHandler = async (req, res) => { if (result) { // 이벤트 코드입니다. - await contracts?.completeAccountChangingQuest(req.userOid); + await contracts?.completeAccountChangingQuest(req.userOid, newAccount); res.status(200).send("User/editAccount : edit user account successful"); } else { From 23ed5d9fbc3eab9225e90b1d4842f71b93cce164 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 18 Sep 2023 01:10:35 +0900 Subject: [PATCH 31/33] Add: ticket1 can be the reward of quests --- src/lottery/modules/contracts/2023fall.js | 26 ++++---- src/lottery/modules/credit.js | 24 ------- src/lottery/modules/quests.js | 77 ++++++++++++++++++----- src/lottery/services/items.js | 4 +- 4 files changed, 78 insertions(+), 53 deletions(-) delete mode 100644 src/lottery/modules/credit.js diff --git a/src/lottery/modules/contracts/2023fall.js b/src/lottery/modules/contracts/2023fall.js index 4e4f9846..f5994d51 100644 --- a/src/lottery/modules/contracts/2023fall.js +++ b/src/lottery/modules/contracts/2023fall.js @@ -7,75 +7,77 @@ const quests = buildQuests({ name: "이벤트 기간 첫 로그인", description: "", imageUrl: "", - rewardAmount: 150, + reward: { + ticket1: 1, + }, }, payingAndSending: { name: "2명 이상 탑승한 방에서 정산/송금 완료", description: "", imageUrl: "", - rewardAmount: 300, + reward: 300, maxCount: 3, }, firstRoomCreation: { name: "첫 방 개설", description: "", imageUrl: "", - rewardAmount: 50, + reward: 50, }, roomSharing: { name: "방 공유하기", description: "", imageUrl: "", - rewardAmount: 50, + reward: 50, }, paying: { name: "2명 이상 탑승한 방에서 정산하기", description: "", imageUrl: "", - rewardAmount: 100, + reward: 100, maxCount: 3, }, sending: { name: "2명 이상 탑승한 방에서 송금하기", description: "", imageUrl: "", - rewardAmount: 50, + reward: 50, maxCount: 3, }, nicknameChaning: { name: "닉네임 변경", description: "", imageUrl: "", - rewardAmount: 50, + reward: 50, }, accountChanging: { name: "계좌 등록 또는 변경", description: "", imageUrl: "", - rewardAmount: 50, + reward: 50, }, adPushAgreement: { name: "광고성 푸시 알림 수신 동의", description: "", imageUrl: "", - rewardAmount: 50, + reward: 50, }, eventSharingOnInstagram: { name: "이벤트 인스타그램 스토리에 공유", description: "", imageUrl: "", - rewardAmount: 100, + reward: 100, }, purchaseSharingOnInstagram: { name: "아이템 구매 후 인스타그램 스토리에 공유", description: "", imageUrl: "", - rewardAmount: 100, + reward: 100, }, }); const eventPeriod = { - start: new Date("2023-09-10T00:00:00+09:00"), // Inclusive + start: new Date("2023-09-25T00:00:00+09:00"), // Inclusive end: new Date("2023-10-10T00:00:00+09:00"), // Exclusive }; diff --git a/src/lottery/modules/credit.js b/src/lottery/modules/credit.js deleted file mode 100644 index 2349d0c4..00000000 --- a/src/lottery/modules/credit.js +++ /dev/null @@ -1,24 +0,0 @@ -const { eventStatusModel } = require("../modules/stores/mongo"); - -const useUserCreditAmount = async (userId) => { - const eventStatus = await eventStatusModel.findOne({ userId }).lean(); - if (!eventStatus) return null; - - return { - amount: eventStatus.creditAmount, - update: async (delta) => { - await eventStatusModel.updateOne( - { _id: eventStatus._id }, - { - $inc: { - creditAmount: delta, - }, - } - ); - }, - }; -}; - -module.exports = { - useUserCreditAmount, -}; diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index 3303fa05..ebc0df05 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -1,14 +1,16 @@ const { eventStatusModel, questModel, + itemModel, transactionModel, } = require("./stores/mongo"); const logger = require("../../modules/logger"); const mongoose = require("mongoose"); -const requiredQuestFields = ["name", "description", "imageUrl", "rewardAmount"]; +const requiredQuestFields = ["name", "description", "imageUrl", "reward"]; const buildQuests = (quests) => { for (const [id, quest] of Object.entries(quests)) { + // quest에 필수 필드가 모두 포함되어 있는지 확인합니다. const hasError = requiredQuestFields.reduce((before, field) => { if (quest[field] !== undefined) return before; @@ -17,11 +19,23 @@ const buildQuests = (quests) => { }, false); if (hasError) return null; + // quest.id 필드를 설정합니다. quest.id = id; - if (!quest.maxCount) { - quest.maxCount = 1; + // quest.reward가 number인 경우, object로 변환합니다. + if (typeof quest.reward === "number") { + const credit = quest.reward; + quest.reward = { + credit, + }; } + + // quest.reward에 누락된 필드가 있는 경우, 기본값(0)으로 설정합니다. + quest.reward.credit = quest.reward.credit || 0; + quest.reward.ticket1 = quest.reward.ticket1 || 0; + + // quest.maxCount가 없는 경우, 기본값(1)으로 설정합니다. + quest.maxCount = quest.maxCount || 1; } return quests; @@ -36,12 +50,15 @@ const buildQuests = (quests) => { * @param {Object} quest - 퀘스트의 정보입니다. * @param {string} quest.id - 퀘스트의 Id입니다. * @param {string} quest.name - 퀘스트의 이름입니다. - * @param {number} quest.rewardAmount - 퀘스트의 완료 보상입니다. + * @param {Object} quest.reward - 퀘스트의 완료 보상입니다. + * @param {number} quest.reward.credit - 퀘스트의 완료 보상 중 재화의 양입니다. + * @param {number} quest.reward.ticket1 - 퀘스트의 완료 보상 중 일반 티켓의 개수입니다. * @param {number} quest.maxCount - 퀘스트의 최대 완료 가능 횟수입니다. * @returns {Object|null} 성공한 경우 Object를, 실패한 경우 null을 반환합니다. 이미 최대 완료 횟수에 도달했거나, 퀘스트가 원격으로 비활성화 된 경우에도 실패로 처리됩니다. */ const completeQuest = async (userId, eventPeriod, quest) => { try { + // 1단계: 이벤트 기간인지 확인합니다. const now = Date.now(); if (now >= eventPeriod.end || now < eventPeriod.start) { logger.info( @@ -50,12 +67,14 @@ const completeQuest = async (userId, eventPeriod, quest) => { return null; } + // 2단계: 유저의 EventStatus를 가져옵니다. 없으면 새롭게 생성합니다. let eventStatus = await eventStatusModel.findOne({ userId }).lean(); if (!eventStatus) { eventStatus = new eventStatusModel({ userId }); await eventStatus.save(); } + // 3단계: 유저의 퀘스트 달성 횟수를 확인합니다. const questCount = eventStatus.completedQuests.filter( (completedQuestId) => completedQuestId === quest.id ).length; @@ -66,19 +85,28 @@ const completeQuest = async (userId, eventPeriod, quest) => { return null; } + // 4단계: 원격으로 비활성화된 퀘스트인지 확인합니다. + // 비활성화된 퀘스트만 DB에 저장할 것이기 때문에, questDoc이 null이어도 오류를 발생시키면 안됩니다. const questDoc = await questModel.findOne({ id: quest.id }).lean(); - if (questDoc && questDoc.isDisabled) { + if (questDoc?.isDisabled) { logger.info( `User ${userId} failed to complete disabled ${quest.id}Quest` ); return null; } + // 5단계: 완료 보상 중 티켓이 있는 경우, 티켓 정보를 가져옵니다. + const ticket1 = + quest.reward.ticket1 && (await itemModel.findOne({ itemType: 1 }).lean()); + if (quest.reward.ticket1 && !ticket1) throw "Fail to find ticket1"; + + // 6단계: 유저의 EventStatus를 업데이트합니다. await eventStatusModel.updateOne( { userId }, { $inc: { - creditAmount: quest.rewardAmount, + creditAmount: quest.reward.credit, + ticket1Amount: quest.reward.ticket1, }, $push: { completedQuests: quest.id, @@ -86,19 +114,38 @@ const completeQuest = async (userId, eventPeriod, quest) => { } ); - const transaction = new transactionModel({ - type: "get", - amount: quest.rewardAmount, - userId, - questId: quest.id, - comment: `${quest.name} 달성 - ${quest.rewardAmount}개 획득`, - }); - await transaction.save(); + // 7단계: Transaction을 생성합니다. + const transactionsId = []; + if (quest.reward.credit) { + const transaction = new transactionModel({ + type: "get", + amount: quest.reward.credit, + userId, + questId: quest.id, + comment: `"${quest.name}" 퀘스트를 완료해 송편 ${quest.reward.credit}개를 획득했습니다.`, + }); + await transaction.save(); + + transactionsId.push(transaction._id); + } + if (quest.reward.ticket1) { + const transaction = new transactionModel({ + type: "use", + amount: 0, + userId, + questId: quest.id, + item: ticket1._id, + comment: `"${quest.name}" 퀘스트를 완료해 "${ticket1.name}" ${quest.reward.ticket1}개를 획득했습니다.`, + }); + await transaction.save(); + + transactionsId.push(transaction._id); + } logger.info(`User ${userId} successfully completed ${quest.id}Quest`); return { quest, - transactionId: transaction._id, + transactionsId, }; } catch (err) { logger.error(err); diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 4c124eb2..1a11e5ba 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -92,7 +92,7 @@ const getRandomItem = async (req, depth) => { userId: req.userOid, item: randomItem._id, itemType: randomItem.itemType, - comment: `랜덤박스에서 ${randomItem.name} 획득 - 0개 차감`, + comment: `랜덤 박스에서 "${randomItem.name}" 1개를 획득했습니다.`, }); await transaction.save(); @@ -175,7 +175,7 @@ const purchaseHandler = async (req, res) => { userId: req.userOid, item: item._id, itemType: item.itemType, - comment: `${item.name} 구입 - ${item.price}개 차감`, + comment: `송편 ${item.price}개를 사용해 "${item.name}" 1개를 획득했습니다.`, }); await transaction.save(); From fce261fbee41e5cb9909be6468c6c5c2a1d05480 Mon Sep 17 00:00:00 2001 From: static Date: Mon, 18 Sep 2023 01:16:37 +0900 Subject: [PATCH 32/33] Docs: describe reward field --- src/lottery/modules/quests.js | 2 +- src/lottery/routes/docs/globalState.js | 28 ++++++++++++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/lottery/modules/quests.js b/src/lottery/modules/quests.js index ebc0df05..50371daa 100644 --- a/src/lottery/modules/quests.js +++ b/src/lottery/modules/quests.js @@ -74,7 +74,7 @@ const completeQuest = async (userId, eventPeriod, quest) => { await eventStatus.save(); } - // 3단계: 유저의 퀘스트 달성 횟수를 확인합니다. + // 3단계: 유저의 퀘스트 완료 횟수를 확인합니다. const questCount = eventStatus.completedQuests.filter( (completedQuestId) => completedQuestId === quest.id ).length; diff --git a/src/lottery/routes/docs/globalState.js b/src/lottery/routes/docs/globalState.js index f05630db..29173691 100644 --- a/src/lottery/routes/docs/globalState.js +++ b/src/lottery/routes/docs/globalState.js @@ -7,7 +7,7 @@ globalStateDocs[`${apiPrefix}/`] = { tags: [`${apiPrefix}`], summary: "Frontend에서 Global state로 관리하는 정보 반환", description: - "유저의 재화 개수, 퀘스트 달성 상태 등 Frontend에서 Global state로 관리할 정보를 가져옵니다. 유저에 대한 EventStatus Document가 없을 경우 새롭게 생성합니다.", + "유저의 재화 개수, 퀘스트 완료 상태 등 Frontend에서 Global state로 관리할 정보를 가져옵니다. 유저에 대한 EventStatus Document가 없을 경우 새롭게 생성합니다.", responses: { 200: { description: "", @@ -31,7 +31,7 @@ globalStateDocs[`${apiPrefix}/`] = { completedQuests: { type: "array", description: - "유저가 달성한 퀘스트의 배열. 여러 번 달성할 수 있는 퀘스트의 경우 배열 내에 같은 퀘스트가 여러 번 포함됩니다.", + "유저가 완료한 퀘스트의 배열. 여러 번 완료할 수 있는 퀘스트의 경우 배열 내에 같은 퀘스트가 여러 번 포함됩니다.", items: { type: "string", description: "Quest의 Id", @@ -58,7 +58,7 @@ globalStateDocs[`${apiPrefix}/`] = { "name", "description", "imageUrl", - "rewardAmount", + "reward", "maxCount", ], properties: { @@ -83,14 +83,26 @@ globalStateDocs[`${apiPrefix}/`] = { description: "이미지 썸네일 URL", example: "THUMBNAIL URL", }, - rewardAmount: { - type: "number", - description: "달성 보상", - example: 100, + reward: { + type: "object", + description: "완료 보상", + required: ["credit", "ticket1"], + properties: { + credit: { + type: "number", + description: "완료 보상 중 재화의 개수입니다.", + example: 100, + }, + ticket1: { + type: "number", + description: "완료 보상 중 일반 티켓의 개수입니다.", + example: 1, + }, + }, }, maxCount: { type: "number", - description: "최대 달성 가능 횟수", + description: "최대 완료 가능 횟수", example: 1, }, }, From fdcf58217da17ca06f4527afeedde2f9174030aa Mon Sep 17 00:00:00 2001 From: static Date: Mon, 18 Sep 2023 01:40:31 +0900 Subject: [PATCH 33/33] Remove: itemType field in transactionSchema --- src/lottery/modules/stores/mongo.js | 4 ---- src/lottery/services/items.js | 2 -- src/lottery/services/transactions.js | 2 +- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/lottery/modules/stores/mongo.js b/src/lottery/modules/stores/mongo.js index 2e32978d..296db681 100644 --- a/src/lottery/modules/stores/mongo.js +++ b/src/lottery/modules/stores/mongo.js @@ -118,10 +118,6 @@ const transactionSchema = Schema({ type: Schema.Types.ObjectId, ref: "Item", }, - itemType: { - type: Number, - enum: [0, 1, 2, 3], - }, comment: { type: String, required: true, diff --git a/src/lottery/services/items.js b/src/lottery/services/items.js index 1a11e5ba..36b4bf10 100644 --- a/src/lottery/services/items.js +++ b/src/lottery/services/items.js @@ -91,7 +91,6 @@ const getRandomItem = async (req, depth) => { amount: 0, userId: req.userOid, item: randomItem._id, - itemType: randomItem.itemType, comment: `랜덤 박스에서 "${randomItem.name}" 1개를 획득했습니다.`, }); await transaction.save(); @@ -174,7 +173,6 @@ const purchaseHandler = async (req, res) => { amount: item.price, userId: req.userOid, item: item._id, - itemType: item.itemType, comment: `송편 ${item.price}개를 사용해 "${item.name}" 1개를 획득했습니다.`, }); await transaction.save(); diff --git a/src/lottery/services/transactions.js b/src/lottery/services/transactions.js index 2009e540..c151d81f 100644 --- a/src/lottery/services/transactions.js +++ b/src/lottery/services/transactions.js @@ -8,7 +8,7 @@ const getUserTransactionsHandler = async (req, res) => { try { // userId는 이미 Frontend에서 알고 있고, 중복되는 값이므로 제외합니다. const transactions = await transactionModel - .find({ userId: req.userOid }, "-userId -itemType -__v") + .find({ userId: req.userOid }, "-userId -__v") .populate(transactionPopulateOption) .lean(); if (transactions)