Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#372 이벤트 참여 동의 여부 확인 #373

Merged
merged 13 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 61 additions & 17 deletions src/lottery/modules/contracts/2023fall.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const quests = buildQuests({
reward: 50,
maxCount: 3,
},
nicknameChaning: {
withSang marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nicknameChanging: {
name: "닉네임 변경",
description: "",
imageUrl: "",
Expand Down Expand Up @@ -84,22 +84,25 @@ const eventPeriod = {
/**
* firstLogin 퀘스트의 완료를 요청합니다.
* @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다.
* @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다.
* @returns {Promise}
* @usage lottery/globalState/createUserGlobalStateHandler
*/
const completeFirstLoginQuest = async (userId) => {
return await completeQuest(userId, eventPeriod, quests.firstLogin);
const completeFirstLoginQuest = async (userId, timestamp) => {
return await completeQuest(userId, timestamp, eventPeriod, quests.firstLogin);
};

/**
* payingAndSending 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이거나, 모든 참가자가 정산 또는 송금을 완료하지 않았다면 요청하지 않습니다.
* @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다.
* @param {Object} roomObject - 방의 정보입니다.
* @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다.
* @param {number} roomObject.settlementTotal - 정산 또는 송금이 완료된 참여자 수입니다.
* @returns {Promise}
* @description 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요.
* @usage rooms/commitPaymentHandler, rooms/settlementHandler
*/
const completePayingAndSendingQuest = async (roomObject) => {
const completePayingAndSendingQuest = async (timestamp, roomObject) => {
if (roomObject.part.length < 2) return null;
if (roomObject.part.length > roomObject.settlementTotal) return null;

Expand All @@ -108,6 +111,7 @@ const completePayingAndSendingQuest = async (roomObject) => {
async (participant) =>
await completeQuest(
participant.user._id,
timestamp,
eventPeriod,
quests.payingAndSending
)
Expand All @@ -118,12 +122,18 @@ const completePayingAndSendingQuest = async (roomObject) => {
/**
* firstRoomCreation 퀘스트의 완료를 요청합니다.
* @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다.
* @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다.
* @returns {Promise}
* @description 방을 만들 때마다 호출해 주세요.
* @usage rooms/createHandler
*/
const completeFirstRoomCreationQuest = async (userId) => {
return await completeQuest(userId, eventPeriod, quests.firstRoomCreation);
const completeFirstRoomCreationQuest = async (userId, timestamp) => {
return await completeQuest(
userId,
timestamp,
eventPeriod,
quests.firstRoomCreation
);
};

const completeRoomSharingQuest = async () => {
Expand All @@ -133,60 +143,94 @@ const completeRoomSharingQuest = async () => {
/**
* paying 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다.
* @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다.
* @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다.
* @param {Object} roomObject - 방의 정보입니다.
* @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다.
* @returns {Promise}
* @description 정산 요청이 이루어질 때마다 호출해 주세요.
* @usage rooms/commitPaymentHandler
*/
const completePayingQuest = async (userId, roomObject) => {
const completePayingQuest = async (userId, timestamp, roomObject) => {
if (roomObject.part.length < 2) return null;

return await completeQuest(userId, eventPeriod, quests.paying);
return await completeQuest(userId, timestamp, eventPeriod, quests.paying);
};

/**
* sending 퀘스트의 완료를 요청합니다. 방의 참가자 수가 2명 미만이면 요청하지 않습니다.
* @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다.
* @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다.
* @param {Object} roomObject - 방의 정보입니다.
* @param {Array<{ user: mongoose.Types.ObjectId }>} roomObject.part - 참여자 목록입니다.
* @returns {Promise}
* @description 송금이 이루어질 때마다 호출해 주세요.
* @usage rooms/settlementHandler
*/
const completeSendingQuest = async (userId, roomObject) => {
const completeSendingQuest = async (userId, timestamp, roomObject) => {
if (roomObject.part.length < 2) return null;

return await completeQuest(userId, eventPeriod, quests.sending);
return await completeQuest(userId, timestamp, eventPeriod, quests.sending);
};

/**
* nicknameChaning 퀘스트의 완료를 요청합니다.
* nicknameChanging 퀘스트의 완료를 요청합니다.
* @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다.
* @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다.
* @returns {Promise}
* @description 닉네임을 변경할 때마다 호출해 주세요.
* @usage users/editNicknameHandler
*/
const completeNicknameChangingQuest = async (userId) => {
return await completeQuest(userId, eventPeriod, quests.nicknameChaning);
const completeNicknameChangingQuest = async (userId, timestamp) => {
return await completeQuest(
userId,
timestamp,
eventPeriod,
quests.nicknameChanging
);
};

/**
* accountChanging 퀘스트의 완료를 요청합니다.
* @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다.
* @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다.
* @param {string} newAccount - 변경된 계좌입니다.
* @returns {Promise}
* @description 계좌를 변경할 때마다 호출해 주세요.
* @usage users/editAccountHandler
*/
const completeAccountChangingQuest = async (userId, newAccount) => {
const completeAccountChangingQuest = async (userId, timestamp, newAccount) => {
if (newAccount === "") return null;

return await completeQuest(userId, eventPeriod, quests.accountChanging);
return await completeQuest(
userId,
timestamp,
eventPeriod,
quests.accountChanging
);
};

const completeAdPushAgreementQuest = async () => {
// TODO
/**
* adPushAgreementQuest 퀘스트의 완료를 요청합니다.
* @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다.
* @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다.
* @param {boolean} advertisement - 변경된 광고성 알림 수신 동의 여부입니다.
* @returns {Promise}
* @description 알림 옵션을 변경할 때마다 호출해 주세요.
* @usage notifications/editOptionsHandler
*/
const completeAdPushAgreementQuest = async (
userId,
timestamp,
advertisement
) => {
if (!advertisement) return null;

return await completeQuest(
userId,
timestamp,
eventPeriod,
quests.adPushAgreement
);
};

const completeEventSharingOnInstagramQuest = async () => {
Expand Down
19 changes: 8 additions & 11 deletions src/lottery/modules/quests.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const buildQuests = (quests) => {
/**
* 퀘스트 완료를 요청합니다.
* @param {string|mongoose.Types.ObjectId} userId - 퀘스트를 완료한 사용자의 ObjectId입니다.
* @param {number|Date} timestamp - 퀘스트 완료를 요청한 시각입니다.
* @param {Object} eventPeriod - 이벤트의 기간입니다.
* @param {Date} eventPeriod.start - 이벤트의 시작 시각(Inclusive)입니다.
* @param {Date} eventPeriod.end - 이벤트의 종료 시각(Exclusive)입니다.
Expand All @@ -56,24 +57,20 @@ const buildQuests = (quests) => {
* @param {number} quest.maxCount - 퀘스트의 최대 완료 가능 횟수입니다.
* @returns {Object|null} 성공한 경우 Object를, 실패한 경우 null을 반환합니다. 이미 최대 완료 횟수에 도달했거나, 퀘스트가 원격으로 비활성화 된 경우에도 실패로 처리됩니다.
*/
const completeQuest = async (userId, eventPeriod, quest) => {
const completeQuest = async (userId, timestamp, eventPeriod, quest) => {
try {
// 1단계: 이벤트 기간인지 확인합니다.
const now = Date.now();
if (now >= eventPeriod.end || now < eventPeriod.start) {
// 1단계: 유저의 EventStatus를 가져옵니다.
const eventStatus = await eventStatusModel.findOne({ userId }).lean();
if (!eventStatus) return null;

// 2단계: 이벤트 기간인지 확인합니다.
if (timestamp >= eventPeriod.end || timestamp < eventPeriod.start) {
logger.info(
`User ${userId} failed to complete auto-disabled ${quest.id}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
Expand Down
36 changes: 35 additions & 1 deletion src/lottery/routes/docs/globalState.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ globalStateDocs[`${apiPrefix}/`] = {
tags: [`${apiPrefix}`],
summary: "Frontend에서 Global state로 관리하는 정보 반환",
description:
"유저의 재화 개수, 퀘스트 완료 상태 등 Frontend에서 Global state로 관리할 정보를 가져옵니다. 유저에 대한 EventStatus Document가 없을 경우 새롭게 생성합니다.",
"유저의 재화 개수, 퀘스트 완료 상태 등 Frontend에서 Global state로 관리할 정보를 가져옵니다.",
responses: {
200: {
description: "",
Expand All @@ -16,13 +16,19 @@ globalStateDocs[`${apiPrefix}/`] = {
schema: {
type: "object",
required: [
"isAgree",
"creditAmount",
"completedQuests",
"ticket1Amount",
"ticket2Amount",
"quests",
],
properties: {
isAgreeOnTermsOfEvent: {
type: "boolean",
description: "유저의 이벤트 참여 동의 여부",
example: true,
},
creditAmount: {
type: "number",
description: "재화 개수. 0 이상입니다.",
Expand Down Expand Up @@ -116,5 +122,33 @@ globalStateDocs[`${apiPrefix}/`] = {
},
},
};
globalStateDocs[`${apiPrefix}/create`] = {
get: {
tags: [`${apiPrefix}`],
summary: "Frontend에서 Global state로 관리하는 정보 생성",
description:
"유저의 재화 개수, 퀘스트 완료 상태 등 Frontend에서 Global state로 관리할 정보를 생성합니다.",
responses: {
200: {
description: "",
content: {
"application/json": {
schema: {
type: "object",
required: ["result"],
properties: {
result: {
type: "boolean",
description: "성공 여부. 항상 true입니다.",
example: true,
},
},
},
},
},
},
},
},
};

module.exports = globalStateDocs;
11 changes: 11 additions & 0 deletions src/lottery/routes/docs/itemsSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,17 @@ const itemsSchema = {
...itemBase,
description: "랜덤박스를 구입한 경우에만 포함됩니다.",
},
purchaseHandler: {
type: "object",
required: ["itemId"],
properties: {
itemId: {
type: "string",
pattern: "^[a-fA-F\\d]{24}$",
},
},
errorMessage: "validation: bad request",
},
};

module.exports = itemsSchema;
6 changes: 4 additions & 2 deletions src/lottery/routes/globalState.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ const express = require("express");
const router = express.Router();
const globalStateHandlers = require("../services/globalState");

// 라우터 접근 시 로그인 필요
router.get("/", globalStateHandlers.getUserGlobalStateHandler);

// 아래의 Endpoint 접근 시 로그인 필요
router.use(require("../../middlewares/auth"));
kmc7468 marked this conversation as resolved.
Show resolved Hide resolved

router.get("/", globalStateHandlers.getUserGlobalStateHandler);
router.post("/create", globalStateHandlers.createUserGlobalStateHandler);

module.exports = router;
14 changes: 12 additions & 2 deletions src/lottery/routes/items.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@ const express = require("express");

const router = express.Router();
const itemsHandlers = require("../services/items");
const auth = require("../../middlewares/auth");

const { validateParams } = require("../../middlewares/ajv");
const itemsSchema = require("./docs/itemsSchema");

router.get("/list", itemsHandlers.listHandler);
router.post("/purchase/:itemId", auth, itemsHandlers.purchaseHandler);

// 아래의 Endpoint 접근 시 로그인 필요
router.use(require("../../middlewares/auth"));

router.post(
"/purchase/:itemId",
validateParams(itemsSchema.purchaseHandler),
itemsHandlers.purchaseHandler
);

module.exports = router;
Loading