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

#541 24-chuseok event ban middleware #544

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from
Open
32 changes: 32 additions & 0 deletions src/lottery/middlewares/eventValidator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const { eventStatusModel } = require("../modules/stores/mongo");
const logger = require("../../modules/logger");

/**
* 사용자가 차단 되었는지 여부를 판단합니다.
* 차단된 사용자는 이벤트에 한하여 서비스 이용에 제재를 받습니다.
* @param {*} req eventStatus가 성공적일 경우 req.eventStatus = eventStatus로 들어갑니다.
* @param {*} res
* @param {*} next
* @returns
*/
const eventValidator = async (req, res, next) => {
try {
const eventStatus = await eventStatusModel
Copy link
Member

Choose a reason for hiding this comment

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

개인적으로는 이렇게 eventStatus를 가져오는 미들웨어보다는 필요한 services에서만 가져다 쓰는게 좋을 것 같다고 생각해요. 기존 코드에 user를 가져오는 미들웨어가 없고 필요한 services에서 직접 조회해서 쓰는 것처럼요. 물론 성능 차이는 거의 없겠지만, service에 따라 eventStatus를 단순히 조회만 하는 경우가 있고, 수정해야 하는 경우도 있는데 후자의 경우에는 (미들웨어가 있으면) query가 2번 발생하게 됩니다.

Copy link
Member

Choose a reason for hiding this comment

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

다른 백엔드 분들은 어케 생각하시는지도 궁금하네요!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

계속 사용하는 것으로 결정

.findOne({ userId: req.userOid })
.lean();
if (!eventStatus) {
return res
.status(400)
.json({ error: "eventValidator: nonexistent eventStatus" });
}
req.eventStatus = eventStatus;
} catch (err) {
logger.error(err);
res.error(500).json({
error: "eventValidator: internal server error",
});
}
next();
Copy link
Member

Choose a reason for hiding this comment

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

에러 발생했을 땐 next를 호출하면 안됩니다!

};

module.exports = eventValidator;
45 changes: 28 additions & 17 deletions src/lottery/modules/contracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ const quests = buildQuests({
* @returns {Promise}
* @usage lottery/globalState - createUserGlobalStateHandler
*/
const completeFirstLoginQuest = async (userId, timestamp) => {
return await completeQuest(userId, timestamp, quests.firstLogin);
const completeFirstLoginQuest = async (req, userId, timestamp) => {
Copy link
Member

Choose a reason for hiding this comment

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

req에 이미 userOid가 포함되어 있어서, req를 받을거라면 userId 매개변수는 필요하지 않을 것 같습니다. (주석도 업데이트 부탁드려요)

Copy link
Contributor Author

@TaehyeonPark TaehyeonPark Sep 10, 2024

Choose a reason for hiding this comment

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

저희 quest 중에서 invitor의 userOid를 받아서 처리해야하는 부분이 있는데, req에는 아마 client의 userOid 값만 있어서 구분을 위해서는 userId parameter가 필요해보여요!

  • src/lottery/services/globalState.js [line: 136--147]

image

return await completeQuest(req, userId, timestamp, quests.firstLogin);
};

/**
Expand All @@ -121,8 +121,8 @@ const completeFirstLoginQuest = async (userId, timestamp) => {
* @description 방을 만들 때마다 호출해 주세요.
* @usage rooms - createHandler
*/
const completeFirstRoomCreationQuest = async (userId, timestamp) => {
return await completeQuest(userId, timestamp, quests.firstRoomCreation);
const completeFirstRoomCreationQuest = async (req, userId, timestamp) => {
return await completeQuest(req, userId, timestamp, quests.firstRoomCreation);
};

/**
Expand All @@ -137,7 +137,12 @@ const completeFirstRoomCreationQuest = async (userId, timestamp) => {
* @description 정산 요청이 이루어질 때마다 호출해 주세요.
* @usage rooms - commitSettlementHandler
*/
const completeFareSettlementQuest = async (userId, timestamp, roomObject) => {
const completeFareSettlementQuest = async (
req,
userId,
timestamp,
roomObject
) => {
logger.info(
`User ${userId} requested to complete fareSettlementQuest in Room ${roomObject._id}`
);
Expand All @@ -150,7 +155,7 @@ const completeFareSettlementQuest = async (userId, timestamp, roomObject) => {
)
return null; // 택시 출발 시각이 이벤트 기간 내에 포함되지 않는 경우 퀘스트 완료 요청을 하지 않습니다.

return await completeQuest(userId, timestamp, quests.fareSettlement);
return await completeQuest(req, userId, timestamp, quests.fareSettlement);
};

/**
Expand All @@ -165,7 +170,7 @@ const completeFareSettlementQuest = async (userId, timestamp, roomObject) => {
* @description 송금이 이루어질 때마다 호출해 주세요.
* @usage rooms - commitPaymentHandler
*/
const completeFarePaymentQuest = async (userId, timestamp, roomObject) => {
const completeFarePaymentQuest = async (req, userId, timestamp, roomObject) => {
logger.info(
`User ${userId} requested to complete farePaymentQuest in Room ${roomObject._id}`
);
Expand All @@ -178,7 +183,7 @@ const completeFarePaymentQuest = async (userId, timestamp, roomObject) => {
)
return null; // 택시 출발 시각이 이벤트 기간 내에 포함되지 않는 경우 퀘스트 완료 요청을 하지 않습니다.

return await completeQuest(userId, timestamp, quests.farePayment);
return await completeQuest(req, userId, timestamp, quests.farePayment);
};

/**
Expand All @@ -189,8 +194,8 @@ const completeFarePaymentQuest = async (userId, timestamp, roomObject) => {
* @description 닉네임을 변경할 때마다 호출해 주세요.
* @usage users - editNicknameHandler
*/
const completeNicknameChangingQuest = async (userId, timestamp) => {
return await completeQuest(userId, timestamp, quests.nicknameChanging);
const completeNicknameChangingQuest = async (req, userId, timestamp) => {
return await completeQuest(req, userId, timestamp, quests.nicknameChanging);
};

/**
Expand All @@ -202,10 +207,15 @@ const completeNicknameChangingQuest = async (userId, timestamp) => {
* @description 계좌를 변경할 때마다 호출해 주세요.
* @usage users - editAccountHandler
*/
const completeAccountChangingQuest = async (userId, timestamp, newAccount) => {
const completeAccountChangingQuest = async (
req,
userId,
timestamp,
newAccount
) => {
if (newAccount === "") return null;

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

/**
Expand All @@ -218,13 +228,14 @@ const completeAccountChangingQuest = async (userId, timestamp, newAccount) => {
* @usage notifications - editOptionsHandler
*/
const completeAdPushAgreementQuest = async (
req,
userId,
timestamp,
advertisement
) => {
if (!advertisement) return null;

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

/**
Expand All @@ -234,8 +245,8 @@ const completeAdPushAgreementQuest = async (
* @returns {Promise}
* @usage lottery/globalState - createUserGlobalStateHandler
*/
const completeEventSharingQuest = async (userId, timestamp) => {
return await completeQuest(userId, timestamp, quests.eventSharing);
const completeEventSharingQuest = async (req, userId, timestamp) => {
return await completeQuest(req, userId, timestamp, quests.eventSharing);
};

/**
Expand All @@ -245,8 +256,8 @@ const completeEventSharingQuest = async (userId, timestamp) => {
* @returns {Promise}
* @description 상품을 구입할 때마다 호출해 주세요.
*/
const completeItemPurchaseQuest = async (userId, timestamp) => {
return await completeQuest(userId, timestamp, quests.itemPurchase);
const completeItemPurchaseQuest = async (req, userId, timestamp) => {
return await completeQuest(req, userId, timestamp, quests.itemPurchase);
};

module.exports = {
Expand Down
10 changes: 8 additions & 2 deletions src/lottery/modules/quests.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const logger = require("../../modules/logger");
const mongoose = require("mongoose");

const { eventConfig } = require("../../../loadenv");
const { validateServiceBanRecord } = require("../../modules/ban");
const eventPeriod = eventConfig && {
startAt: new Date(eventConfig.period.startAt),
endAt: new Date(eventConfig.period.endAt),
Expand Down Expand Up @@ -64,11 +65,16 @@ const buildQuests = (quests) => {
* @param {number} quest.maxCount - 퀘스트의 최대 완료 가능 횟수입니다.
* @returns {Object|null} 성공한 경우 Object를, 실패한 경우 null을 반환합니다. 이미 최대 완료 횟수에 도달했거나, 퀘스트가 원격으로 비활성화된 경우에도 실패로 처리됩니다.
*/
const completeQuest = async (userId, timestamp, quest) => {
const completeQuest = async (req, userId, timestamp, quest) => {
try {
// 1단계: 유저의 EventStatus를 가져옵니다. 블록드리스트인지도 확인합니다.
const eventStatus = await eventStatusModel.findOne({ userId }).lean();
if (!eventStatus || eventStatus.isBanned) return null;
const banErrorMessage = await validateServiceBanRecord(
req,
eventConfig.mode
);

if (!eventStatus || !!banErrorMessage) return null;

// 2단계: 이벤트 기간인지 확인합니다.
if (
Expand Down
3 changes: 2 additions & 1 deletion src/lottery/routes/invites.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ router.get(

// 아래의 Endpoint 접근 시 로그인, 차단 여부 및 시각 체크 필요
router.use(require("../../middlewares/auth"));
router.use(require("../middlewares/checkBanned"));
router.use(require("../../middlewares/ban"));
router.use(require("../middlewares/eventValidator"));
router.use(require("../middlewares/timestampValidator"));

router.post("/create", invitesHandlers.createInviteUrlHandler);
Expand Down
3 changes: 2 additions & 1 deletion src/lottery/routes/items.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ router.get(

// 아래의 Endpoint 접근 시 로그인, 차단 여부 및 시각 체크 필요
router.use(require("../../middlewares/auth"));
router.use(require("../middlewares/checkBanned"));
router.use(require("../../middlewares/ban"));
router.use(require("../middlewares/eventValidator"));
router.use(require("../middlewares/timestampValidator"));

router.post(
Expand Down
3 changes: 2 additions & 1 deletion src/lottery/routes/quests.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const questsHandlers = require("../services/quests");

// 아래의 Endpoint 접근 시 로그인, 차단 여부 및 시각 체크 필요
router.use(require("../../middlewares/auth"));
router.use(require("../middlewares/checkBanned"));
router.use(require("../../middlewares/ban"));
router.use(require("../middlewares/eventValidator"));
router.use(require("../middlewares/timestampValidator"));

router.post(
Expand Down
3 changes: 2 additions & 1 deletion src/lottery/routes/transactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ const router = express.Router();

const transactionsHandlers = require("../services/transactions");

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

router.get("/", transactionsHandlers.getUserTransactionsHandler);

Expand Down
18 changes: 13 additions & 5 deletions src/lottery/services/globalState.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { nodeEnv } = require("../../../loadenv");

const { eventConfig } = require("../../../loadenv");
const contracts = require("../modules/contracts");
const { validateServiceBanRecord } = require("../../modules/ban");
const quests = Object.values(contracts.quests);

// 아래의 함수는 2024 추석 이벤트에서 사용되지 않습니다.
Expand Down Expand Up @@ -85,11 +86,13 @@ const createUserGlobalStateHandler = async (req, res) => {
const inviterStatus =
req.body.inviter &&
(await eventStatusModel.findById(req.body.inviter).lean());
const banErrorMessage = await validateServiceBanRecord(
req,
eventConfig.mode
);
if (
req.body.inviter &&
(!inviterStatus ||
inviterStatus.isBanned ||
!inviterStatus.isInviteUrlEnabled)
(!inviterStatus || !!banErrorMessage || !inviterStatus.isInviteUrlEnabled)
)
return res.status(400).json({
error: "GlobalState/create : invalid inviter",
Expand Down Expand Up @@ -128,11 +131,16 @@ const createUserGlobalStateHandler = async (req, res) => {
await eventStatus.save();

// 퀘스트를 완료 처리합니다.
await contracts.completeFirstLoginQuest(req.userOid, req.timestamp);
await contracts.completeFirstLoginQuest(req, req.userOid, req.timestamp);

if (inviterStatus) {
await contracts.completeEventSharingQuest(req.userOid, req.timestamp);
await contracts.completeEventSharingQuest(
req,
req.userOid,
req.timestamp
);
await contracts.completeEventSharingQuest(
req,
inviterStatus.userId,
req.timestamp
);
Expand Down
7 changes: 6 additions & 1 deletion src/lottery/services/invites.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const { userModel } = require("../../modules/stores/mongo");
const logger = require("../../modules/logger");

const { eventConfig } = require("../../../loadenv");
const { validateServiceBanRecord } = require("../../modules/ban");

const searchInviterHandler = async (req, res) => {
try {
Expand All @@ -13,9 +14,13 @@ const searchInviterHandler = async (req, res) => {
const inviterStatus = await eventStatusModel
.findById(req.params.inviter)
.lean();
const banErrorMessage = await validateServiceBanRecord(
req,
eventConfig.mode
);
if (
!inviterStatus ||
inviterStatus.isBanned ||
!!banErrorMessage ||
!inviterStatus.isInviteUrlEnabled
)
return res
Expand Down
1 change: 1 addition & 0 deletions src/lottery/services/items.js
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ const purchaseItemHandler = async (req, res) => {

// 4단계: 퀘스트를 완료 처리합니다.
await contracts.completeItemPurchaseQuest(
req,
req.userOid,
transaction.createdAt
);
Expand Down
10 changes: 6 additions & 4 deletions src/middlewares/ban.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const { eventConfig } = require("../../loadenv");
const { validateServiceBanRecord } = require("../modules/ban");

const serviceMapper = new Map([
Expand All @@ -6,10 +7,11 @@ const serviceMapper = new Map([
]);

const banMiddleware = async (req, res, next) => {
const banErrorMessage = await validateServiceBanRecord(
req,
serviceMapper.get(req.originalUrl)
);
let service = serviceMapper.get(req.originalUrl);
if (!service && !!eventConfig && req.originalUrl.includes(eventConfig.mode)) {
service = eventConfig.mode;
}
const banErrorMessage = await validateServiceBanRecord(req, service);
if (banErrorMessage !== undefined) {
return res.status(400).json({ error: banErrorMessage });
}
Expand Down
2 changes: 1 addition & 1 deletion src/modules/stores/mongo.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const banSchema = Schema({
// 필요시 이곳에 정지를 시킬 서비스를 추가함.
enum: [
"service", // service: 방 생성/참여 제한
"2023-fall-event", // xxxx-xxxx-event: 특정 이벤트 참여 제한
"2024fall", // 2024fall: 가을학기 추석 이벤트 참여 제한
],
},
});
Expand Down
1 change: 1 addition & 0 deletions src/services/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ const editOptionsHandler = async (req, res) => {

// 이벤트 코드입니다.
await contracts?.completeAdPushAgreementQuest(
req,
req.userOid,
req.timestamp,
options.advertisement
Expand Down
8 changes: 7 additions & 1 deletion src/services/rooms.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ const createHandler = async (req, res) => {
const roomObjectFormated = formatSettlement(roomObject);

// 이벤트 코드입니다.
await contracts?.completeFirstRoomCreationQuest(req.userOid, req.timestamp);
await contracts?.completeFirstRoomCreationQuest(
req,
req.userOid,
req.timestamp
);

return res.send(roomObjectFormated);
} catch (err) {
Expand Down Expand Up @@ -587,6 +591,7 @@ const commitSettlementHandler = async (req, res) => {

// 이벤트 코드입니다.
await contracts?.completeFareSettlementQuest(
req,
req.userOid,
req.timestamp,
roomObject
Expand Down Expand Up @@ -660,6 +665,7 @@ const commitPaymentHandler = async (req, res) => {

// 이벤트 코드입니다.
await contracts?.completeFarePaymentQuest(
req,
req.userOid,
req.timestamp,
roomObject
Expand Down
2 changes: 2 additions & 0 deletions src/services/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const editNicknameHandler = async (req, res) => {
if (result) {
// 이벤트 코드입니다.
await contracts?.completeNicknameChangingQuest(
req,
req.userOid,
req.timestamp
);
Expand Down Expand Up @@ -80,6 +81,7 @@ const editAccountHandler = async (req, res) => {
if (result) {
// 이벤트 코드입니다.
await contracts?.completeAccountChangingQuest(
req,
req.userOid,
req.timestamp,
newAccount
Expand Down
Loading