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

#341 2023 가을학기 추석 홍보 이벤트 위한 transactions #364

Merged
merged 35 commits into from
Sep 17, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f8b7df1
Add: modules/events.js
kmc7468 Sep 13, 2023
b5238d7
Remove: instagramRewardAction
kmc7468 Sep 13, 2023
f047d85
Add: startat field in eventSchema
kmc7468 Sep 13, 2023
f190d7c
Docs: startat field of the response of some endpoint
kmc7468 Sep 13, 2023
13a42ca
Add: check if event can be achieved in eventHandler
kmc7468 Sep 13, 2023
6c387ea
Add: eventIds in loadenv.js
kmc7468 Sep 13, 2023
f6c3b94
Fix: rename AdPushAgreement to adPushAgreement
kmc7468 Sep 13, 2023
6326969
Add: contracts/2023fall.js
kmc7468 Sep 13, 2023
7d943f2
Add: implement firstLoginEvent
kmc7468 Sep 13, 2023
cebd609
Add: logging in eventHandler function
kmc7468 Sep 13, 2023
04b6fe1
Add: implement firstRoomCreationEvent
kmc7468 Sep 13, 2023
153ffb2
Add: nicknameChangingEvent and accountChangingEvent
kmc7468 Sep 13, 2023
69c0f81
Add: implement payingEvent and sendingEvent
kmc7468 Sep 13, 2023
74206dd
Add: implement payingAndSendingEvent
kmc7468 Sep 13, 2023
ce6a30a
Fix: error occuring when eventMode === undefined
kmc7468 Sep 13, 2023
b5dc891
chore: add jsdoc to 2023fall event logic
cokia Sep 14, 2023
0d5d0e1
Refactor: use eventName instead of eventId in eventHandler
kmc7468 Sep 14, 2023
ee6dace
Refactor: calling method of contracts
kmc7468 Sep 14, 2023
f07ba8d
Remove: importing logger in lottery/index.js
kmc7468 Sep 14, 2023
ee3c625
Docs: describe type of roomObject in contracts/2023fall.js
kmc7468 Sep 14, 2023
9ed166c
Refactor: make shorter some code related to contracts
kmc7468 Sep 15, 2023
431af7b
Refactor: store event data in 2023fall.js instead of database
kmc7468 Sep 16, 2023
868d9b7
Merge branch 'dev' into #341-detect-events
kmc7468 Sep 16, 2023
103b46a
Fix: error occuring when eventMode === undefined
kmc7468 Sep 16, 2023
b5a1f77
Fix: rename event to quest
kmc7468 Sep 16, 2023
c0d6649
Refactor: rename doneat to createAt in transactionSchema
kmc7468 Sep 16, 2023
5c2ac3c
Docs: add description about completeQuest function
kmc7468 Sep 16, 2023
e514be4
Add: check current time in some functions
kmc7468 Sep 16, 2023
54457fd
Fix: bug related to firstLoginEvent
kmc7468 Sep 16, 2023
39816d2
Add: buildQuests function in modules/quests.js
kmc7468 Sep 16, 2023
5f5b103
Fix: accountChangingQuest completion condition
kmc7468 Sep 17, 2023
8558aa3
Merge branch 'dev' into #341-detect-events
kmc7468 Sep 17, 2023
23ed5d9
Add: ticket1 can be the reward of quests
kmc7468 Sep 17, 2023
fce261f
Docs: describe reward field
kmc7468 Sep 17, 2023
fdcf582
Remove: itemType field in transactionSchema
kmc7468 Sep 17, 2023
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
16 changes: 16 additions & 0 deletions loadenv.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
kmc7468 marked this conversation as resolved.
Show resolved Hide resolved
};
35 changes: 21 additions & 14 deletions src/lottery/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,13 @@ const {
transactionModel,
} = require("./modules/stores/mongo");

const { eventMode } = require("../../loadenv");
const { buildResource } = require("../modules/adminResource");
const { instagramRewardAction } = require("./modules/admin");
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 검증
Expand All @@ -27,15 +23,26 @@ 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());

const contracts = eventMode ? require(`./modules/contracts/${eventMode}`) : {};
const getContract = (name) => {
kmc7468 marked this conversation as resolved.
Show resolved Hide resolved
const contract = contracts[name];
if (contract) return contract;

if (eventMode) {
logger.error(`Contract ${name}를 찾을 수 없습니다.`);
}
return () => null;
};

module.exports = {
checkReward,
lotteryRouter,
resources: [eventStatusResource, ...otherResources],
resources,
getContract,
};
74 changes: 0 additions & 74 deletions src/lottery/modules/admin.js

This file was deleted.

86 changes: 86 additions & 0 deletions src/lottery/modules/contracts/2023fall.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
const { eventIds } = require("../../../../loadenv");
const { eventHandler } = require("../events");

// 로그인할 때마다 호출해 주세요.
// 사용된 곳: auth/tryLogin, auth.mobile/tokenLoginHandler
const requestFirstLoginEvent = async (userId) => {
return await eventHandler(userId, eventIds.firstLogin);
};

// 정산 요청 또는 송금이 이루어질 때마다 호출해 주세요.
// 사용된 곳: rooms/commitPaymentHandler, rooms/settlementHandler
const requestPayingAndSendingEvent = 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, eventIds.payingAndSending)
)
);
};

// 방을 만들 때마다 호출해 주세요.
// 사용된 곳: rooms/createHandler
const requestFirstRoomCreation = async (userId) => {
return await eventHandler(userId, eventIds.firstRoomCreation);
};

const requestRoomSharingEvent = async () => {
// TODO
};

// 정산 요청이 이루어질 때마다 호출해 주세요.
// 사용된 곳: rooms/commitPaymentHandler
const requestPayingEvent = async (userId, roomObject) => {
if (roomObject.part.length < 2) return null;

return await eventHandler(userId, eventIds.paying);
};

// 송금이 이루어질 때마다 호출해 주세요.
// 사용된 곳: rooms/settlementHandler
const requestSendingEvent = async (userId, roomObject) => {
if (roomObject.part.length < 2) return null;

return await eventHandler(userId, eventIds.sending);
};

// 닉네임을 변경할 때마다 호출해 주세요.
// 사용된 곳: users/editNicknameHandler
const requestNicknameChangingEvent = async (userId) => {
return await eventHandler(userId, eventIds.nicknameChanging);
};

// 계좌를 변경할 때마다 호출해 주세요.
// 사용된 곳: users/editAccountHandler
const requestAccountChangingEvent = async (userId) => {
return await eventHandler(userId, eventIds.accountChanging);
};

const requestAdPushAgreementEvent = async () => {
// TODO
14KGun marked this conversation as resolved.
Show resolved Hide resolved
};

const requestEventSharingOnInstagram = async () => {
// TODO
};

const requestPurchaseSharingOnInstagram = async () => {
// TODO
};

module.exports = {
requestFirstLoginEvent,
requestPayingAndSendingEvent,
requestFirstRoomCreation,
requestRoomSharingEvent,
requestPayingEvent,
requestSendingEvent,
requestNicknameChangingEvent,
requestAccountChangingEvent,
requestAdPushAgreementEvent,
requestEventSharingOnInstagram,
requestPurchaseSharingOnInstagram,
};
Copy link
Member

Choose a reason for hiding this comment

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

흠...ㅋㅋㅋㅋ 나중에 컨벤션 바꿔서 module.exports.requestFirstLoginEvent = async () => { ... 이런식으로 수정되면 더 깔끔할 것 같긴하네요..

@withSang 나중에 타입스크립트 도입할 때에는 es6 import export 해주세요 ㅠㅜ

77 changes: 77 additions & 0 deletions src/lottery/modules/events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const {
eventStatusModel,
eventModel,
transactionModel,
} = require("./stores/mongo");
const logger = require("../../modules/logger");

const eventHandler = async (userId, eventId) => {
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 now = Date.now();
if (now < event.startat || now > event.expireat) {
logger.info(
`eventHandler(userId=${userId}, eventId=${eventId}) 함수가 종료되었습니다: 달성할 수 있는 기간이 아닙니다.`
);
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();

logger.info(
`eventHandler(userId=${userId}, eventId=${eventId}) 함수가 종료되었습니다: 성공했습니다.`
);
return {
event,
transactionId: transaction._id,
};
Copy link
Member

Choose a reason for hiding this comment

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

return value가 지금은 안 쓰이는 것 같은데 추가하신 이유가 혹시 있을지 궁금해요!

Copy link
Member Author

Choose a reason for hiding this comment

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

현재는 삭제된 instagramRewardAction 쪽에서 transactionId를 이용하여 Admin Log를 남겼었습니다. 나중에 필요한 일이 있을까봐 반환값은 그대로 유지하였습니다!

} catch (err) {
logger.error(
`eventHandler(userId=${userId}, eventId=${eventId}) 함수에서 예외가 발생했습니다: ${err}`
);
return null;
}
};

module.exports = {
eventHandler,
};
4 changes: 4 additions & 0 deletions src/lottery/modules/stores/mongo.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ const eventSchema = Schema({
min: 0,
validate: integerValidator,
},
startat: {
type: Date,
required: true,
},
expireat: {
type: Date,
required: true,
Expand Down
5 changes: 5 additions & 0 deletions src/lottery/routes/docs/globalState.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ globalStateDocs[`${apiPrefix}/`] = {
description: "최대 달성 가능 횟수",
example: 1,
},
startat: {
type: "string",
description: "달성할 수 있는 처음 시각",
example: "2023-01-01 00:00:00",
},
expireat: {
kmc7468 marked this conversation as resolved.
Show resolved Hide resolved
type: "string",
description: "달성할 수 있는 마지막 시각",
Expand Down
5 changes: 5 additions & 0 deletions src/lottery/routes/docs/transactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ transactionsDocs[`${apiPrefix}/`] = {
description: "최대 달성 가능 횟수",
example: 1,
},
startat: {
type: "string",
description: "달성할 수 있는 처음 시각",
example: "2023-01-01 00:00:00",
},
expireat: {
type: "string",
description: "달성할 수 있는 마지막 시각",
Expand Down
2 changes: 2 additions & 0 deletions src/modules/adminResource.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ const defaultActionLogFeature = buildFeature({
});

const recordActionAfterHandler = (actions) => async (res, req, context) => {
if (!res.response) return res;
14KGun marked this conversation as resolved.
Show resolved Hide resolved

const actionsWrapper = Array.isArray(actions) ? actions : [actions];
for (const action of actionsWrapper) {
if (typeof action === "string") {
Expand Down
7 changes: 7 additions & 0 deletions src/services/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ const {
const jwt = require("../modules/auths/jwt");
const logger = require("../modules/logger");

// 이벤트 코드입니다.
const { getContract } = require("../lottery");

// SPARCS SSO
const Client = require("../modules/auths/sparcssso");
const client = new Client(sparcsssoEnv?.id, sparcsssoEnv?.key);
Expand Down Expand Up @@ -91,6 +94,10 @@ const tryLogin = async (req, res, userData, redirectOrigin, redirectPath) => {
}

login(req, userData.sid, user.id, user._id, user.name);

// 이벤트 코드입니다.
await getContract("requestFirstLoginEvent")(user._id);

res.redirect(new URL(redirectPath, redirectOrigin).href);
} catch (err) {
logger.error(err);
Expand Down
Loading