Skip to content

Commit

Permalink
Merge pull request #409 from sparcs-kaist/dev
Browse files Browse the repository at this point in the history
Main branch update from Dev branch
  • Loading branch information
14KGun authored Sep 26, 2023
2 parents c2e7311 + f6b55d0 commit 3536e72
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 39 deletions.
35 changes: 35 additions & 0 deletions src/lottery/middlewares/checkBanned.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const { eventStatusModel } = require("../modules/stores/mongo");
const logger = require("../../modules/logger");

/**
* ์‚ฌ์šฉ์ž๊ฐ€ ์ฐจ๋‹จ ๋˜์—ˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค.
* ์ฐจ๋‹จ๋œ ์‚ฌ์šฉ์ž๋Š” ์ด๋ฒคํŠธ์— ํ•œํ•˜์—ฌ ์„œ๋น„์Šค ์ด์šฉ์— ์ œ์žฌ๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.
* @param {*} req eventStatus๊ฐ€ ์„ฑ๊ณต์ ์ผ ๊ฒฝ์šฐ req.eventStatus = eventStatus๋กœ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค.
* @param {*} res
* @param {*} next
* @returns
*/
const checkBanned = async (req, res, next) => {
try {
const eventStatus = await eventStatusModel
.findOne({ userId: req.userOid })
.lean();
if (!eventStatus) {
return res
.status(400)
.json({ error: "checkBanned: nonexistent eventStatus" });
}
if (eventStatus.isBanned) {
return res.status(400).json({ error: "checkBanned: banned user" });
}
req.eventStatus = eventStatus;
next();
} catch (err) {
logger.error(err);
res.error(500).json({
error: "checkBanned: internal server error",
});
}
};

module.exports = checkBanned;
4 changes: 2 additions & 2 deletions src/lottery/modules/quests.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ const buildQuests = (quests) => {
*/
const completeQuest = async (userId, timestamp, quest) => {
try {
// 1๋‹จ๊ณ„: ์œ ์ €์˜ EventStatus๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
// 1๋‹จ๊ณ„: ์œ ์ €์˜ EventStatus๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ๋ธ”๋ก๋“œ๋ฆฌ์ŠคํŠธ์ธ์ง€๋„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
const eventStatus = await eventStatusModel.findOne({ userId }).lean();
if (!eventStatus) return null;
if (!eventStatus || eventStatus.isBanned) return null;

// 2๋‹จ๊ณ„: ์ด๋ฒคํŠธ ๊ธฐ๊ฐ„์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
if (timestamp >= eventPeriod.endAt || timestamp < eventPeriod.startAt) {
Expand Down
3 changes: 3 additions & 0 deletions src/lottery/modules/stores/mongo.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ const eventStatusSchema = Schema({
min: 0,
validate: integerValidator,
},
isBanned: {
type: Boolean,
},
});

const questSchema = Schema({
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 @@ -120,6 +120,11 @@ globalStateDocs[`${apiPrefix}/`] = {
},
},
},
isBanned: {
type: "boolean",
description: "ํ•ด๋‹น ์œ ์ € ์ œ์žฌ ๋Œ€์ƒ ์—ฌ๋ถ€",
example: false,
},
},
},
},
Expand Down
19 changes: 19 additions & 0 deletions src/lottery/routes/docs/items.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,25 @@ itemsDocs[`${apiPrefix}/purchase/:itemId`] = {
},
},
},
400: {
description:
"checkBanned์—์„œ ์ด๋ฒคํŠธ์— ๋™์˜ํ•˜์ง€ ์•Š์€ ์‚ฌ๋žŒ๊ณผ ์ œ์žฌ ๋Œ€์ƒ์„ ์„ ๋ณ„ํ•ฉ๋‹ˆ๋‹ค.",
content: {
"application/json": {
schema: {
type: "object",
required: ["error"],
properties: {
error: {
type: "string",
description: "",
example: "checkBanned: banned user",
},
},
},
},
},
},
},
},
};
Expand Down
19 changes: 19 additions & 0 deletions src/lottery/routes/docs/quests.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,25 @@ eventsDocs[`${apiPrefix}/complete/:questId`] = {
},
},
},
400: {
description:
"checkBanned์—์„œ ์ด๋ฒคํŠธ์— ๋™์˜ํ•˜์ง€ ์•Š์€ ์‚ฌ๋žŒ๊ณผ ์ œ์žฌ ๋Œ€์ƒ์„ ์„ ๋ณ„ํ•ฉ๋‹ˆ๋‹ค.",
content: {
"application/json": {
schema: {
type: "object",
required: ["error"],
properties: {
error: {
type: "string",
description: "",
example: "checkBanned: banned user",
},
},
},
},
},
},
},
},
};
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 @@ -8,8 +8,9 @@ const itemsSchema = require("./docs/itemsSchema");

router.get("/list", itemsHandlers.listHandler);

// ์•„๋ž˜์˜ Endpoint ์ ‘๊ทผ ์‹œ ๋กœ๊ทธ์ธ ๋ฐ ์‹œ๊ฐ ์ฒดํฌ ํ•„์š”
// ์•„๋ž˜์˜ Endpoint ์ ‘๊ทผ ์‹œ ๋กœ๊ทธ์ธ, ๋ธ”๋ก๋“œ๋ฆฌ์ŠคํŠธ ๋ฐ ์‹œ๊ฐ ์ฒดํฌ ํ•„์š”
router.use(require("../../middlewares/auth"));
router.use(require("../middlewares/checkBanned"));
router.use(require("../middlewares/timestampValidator"));

router.post(
Expand Down
1 change: 1 addition & 0 deletions src/lottery/routes/quests.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const { validateParams } = require("../../middlewares/ajv");
const questsSchema = require("./docs/questsSchema");

router.use(require("../../middlewares/auth"));
router.use(require("../middlewares/checkBanned"));
router.use(require("../middlewares/timestampValidator"));

router.post(
Expand Down
8 changes: 1 addition & 7 deletions src/lottery/services/items.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,6 @@ const listHandler = async (_, res) => {

const purchaseHandler = async (req, res) => {
try {
const eventStatus = await eventStatusModel.findOne({ userId: req.userOid });
if (!eventStatus)
return res
.status(400)
.json({ error: "Items/Purchase : nonexistent eventStatus" });

const { itemId } = req.params;
const item = await itemModel.findOne({ _id: itemId }).lean();
if (!item)
Expand All @@ -138,7 +132,7 @@ const purchaseHandler = async (req, res) => {
// ๊ตฌ๋งค ๊ฐ€๋Šฅ ์กฐ๊ฑด: ํฌ๋ ˆ๋”ง์ด ์ถฉ๋ถ„ํ•˜๋ฉฐ, ์žฌ๊ณ ๊ฐ€ ๋‚จ์•„์žˆ์œผ๋ฉฐ, ํŒ๋งค ์ค‘์ธ ์•„์ดํ…œ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
if (item.isDisabled)
return res.status(400).json({ error: "Items/Purchase : disabled item" });
if (eventStatus.creditAmount < item.price)
if (req.eventStatus.creditAmount < item.price)
return res
.status(400)
.json({ error: "Items/Purchase : not enough credit" });
Expand Down
42 changes: 13 additions & 29 deletions src/lottery/services/publicNotice.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,30 +65,27 @@ const getRecentPurchaceItemListHandler = async (req, res) => {
}
};

const calculateProbabilityV2 = (users, weightSum, base, weight) => {
const calculateProbabilityV2 = (users, weightSum, weight) => {
// ์œ ์ € ์ˆ˜๊ฐ€ ์ƒํ’ˆ ์ˆ˜๋ณด๋‹ค ์ ๊ฑฐ๋‚˜ ๊ฐ™์œผ๋ฉด ๋ฌด์กฐ๊ฑด ์ƒํ’ˆ์„ ๋ฐ›๊ฒŒ๋œ๋‹ค.
if (users.length <= 15) return 1;

/**
* ๊ฒฝํ—˜์ ์œผ๋กœ ๋ฐœ๊ฒฌํ•œ ์‚ฌ์‹ค
* ์‹คํ—˜์ ์œผ๋กœ ๋ฐœ๊ฒฌํ•œ ์‚ฌ์‹ค
*
* p๋ฅผ ์—์–ดํŒŸ ๋‹น์ฒจ ํ™•๋ฅ , M์„ ์ „์ฒด ํ‹ฐ์ผ“ ์ˆ˜๋ผ๊ณ  ํ•˜์ž.
* ๋ชจ๋“  ์œ ์ €์˜ p๊ฐ’์ด 1/15 ๋ฏธ๋งŒ์ผ ๊ฒฝ์šฐ, ์‹ค์ œ ๋‹น์ฒจ ํ™•๋ฅ ์€ 15p์ด๋‹ค.
* ๊ทธ๋ ‡์ง€ ์•Š์€ ๊ฒฝ์šฐ, ์‹ค์ œ ๋‹น์ฒจ ํ™•๋ฅ ์€ 1-a^Mp๊ผด์˜ ์ง€์ˆ˜ํ•จ์ˆ˜๋ฅผ ๋”ฐ๋ฅธ๋‹ค. (Note: Mp๋Š” ํ‹ฐ์ผ“ ์ˆ˜์ด๋‹ค.)
*
* ๊ณ„์‚ฐ ๊ณผ์ •
*
* a๋Š” ์œ ์ € ์ˆ˜, ์ „์ฒด ํ‹ฐ์ผ“ ์ˆ˜, ํ‹ฐ์ผ“ ๋ถ„ํฌ์— ์˜ํ•ด ๊ฒฐ์ •๋˜๋Š” ๊ฐ’์œผ๋กœ, ํ˜„์‹ค์ ์œผ๋กœ ๊ณ„์‚ฐํ•˜๊ธฐ ์–ด๋ ต๋‹ค.
* ๋”ฐ๋ผ์„œ, ๋ชจ๋“  ์œ ์ €๊ฐ€ ๊ฐ™์€ ์ˆ˜์˜ ํ‹ฐ์ผ“์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ  a๋ฅผ ๊ณ„์‚ฐํ•œ ๋’ค, ์ด๋ฅผ ํ™•๋ฅ  ๊ณ„์‚ฐ์— ์‚ฌ์šฉํ•œ๋‹ค.
* x๋ฅผ ํ‹ฐ์ผ“ ์ˆ˜๋ผ๊ณ  ํ•˜๋ฉด, ์‹ค์ œ ๋‹น์ฒจ ํ™•๋ฅ ์€ 1-a^x๊ผด์˜ ์ง€์ˆ˜ํ•จ์ˆ˜๋ฅผ ๋”ฐ๋ฅด๋Š” ๊ฒƒ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ํ†ตํ•ด ๋ฐœ๊ฒฌํ•˜์˜€๋‹ค.
* ์ด๋•Œ a๋Š” ์ „์ฒด ์œ ์ € ์ˆ˜, ์ „์ฒด ํ‹ฐ์ผ“ ์ˆ˜, ๊ฐ ์œ ์ €์˜ ํ‹ฐ์ผ“ ์ˆ˜์— ์˜ํ•ด ๊ฒฐ์ •๋˜๋Š” ๊ฐ’์ด๋‹ค.
*
* a๊ฐ’์˜ ๊ณ„์‚ฐ ๊ณผ์ •
*
* N์„ ์œ ์ € ์ˆ˜๋ผ๊ณ  ํ•˜์ž. ๋ชจ๋“  ์œ ์ €๊ฐ€ ๊ฐ™์€ ์ˆ˜์˜ ํ‹ฐ์ผ“ M/N๊ฐœ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ณ  ํ•˜์ž.
* ์ด๋•Œ ๊ธฐ๋Œ€๋˜๋Š” ๋‹น์ฒจ ํ™•๋ฅ ์€ ์ง๊ด€์ ์œผ๋กœ 15/N์ž„์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ์ฆ‰, 1-a^(M/N) = 15/N์ด๋‹ค.
* a์— ๋Œ€ํ•ด ์ •๋ฆฌํ•˜๋ฉด, a = (1-15/N)^(N/M)์ž„์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
* ๋งค๋ฒˆ a๊ฐ’์„ ์ •ํ™•ํ•˜๊ฒŒ ๊ณ„์‚ฐํ•˜๋Š” ๊ฒƒ์€ ํ˜„์‹ค์ ์œผ๋กœ ์–ด๋ ต๋‹ค.
* ๋”ฐ๋ผ์„œ, ๋ชจ๋“  ์œ ์ €๊ฐ€ ๊ฐ™์€ ์ˆ˜์˜ ํ‹ฐ์ผ“์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ  a๋ฅผ ๊ณ„์‚ฐํ•œ ๋’ค, ์ด๋ฅผ ํ™•๋ฅ  ๊ณ„์‚ฐ์— ์‚ฌ์šฉํ•œ๋‹ค.
* M์„ ์ „์ฒด ํ‹ฐ์ผ“ ์ˆ˜, N์„ ์ „์ฒด ์œ ์ € ์ˆ˜๋ผ๊ณ  ํ•˜์ž.
* ๋ชจ๋“  ์œ ์ €๊ฐ€ ๊ฐ™์€ ์ˆ˜์˜ ํ‹ฐ์ผ“ M/N๊ฐœ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด, ํ•œ ์œ ์ €๊ฐ€ ์ƒํ’ˆ์— ๋‹น์ฒจ๋  ํ™•๋ฅ ์€ 15/N์ž„์„ ์ง๊ด€์ ์œผ๋กœ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
* ์‹ค์ œ ๋‹น์ฒจ ํ™•๋ฅ ์€ 1-a^x๊ผด์˜ ์ง€์ˆ˜ํ•จ์ˆ˜๋ฅผ ๋”ฐ๋ฅด๋ฏ€๋กœ, 1-a^(M/N) = 15/N์ด๋ผ๋Š” ์‹์„ ์„ธ์šธ ์ˆ˜ ์žˆ๋‹ค.
* a์— ๋Œ€ํ•ด ์ •๋ฆฌํ•˜๋ฉด, a = (1-15/N)^(N/M)์„ ์–ป๋Š”๋‹ค.
*/
if (base !== null) return 1 - Math.pow(base, weight);
else return (weight / weightSum) * 15;
const base = Math.pow(1 - 15 / users.length, users.length / weightSum);
return 1 - Math.pow(base, weight);
};

const getTicketLeaderboardHandler = async (req, res) => {
Expand Down Expand Up @@ -128,13 +125,6 @@ const getTicketLeaderboardHandler = async (req, res) => {
},
[0, 0, 0]
);

const isExponential =
sortedUsers.find((user) => user.weight >= weightSum / 15) !== undefined;
const base = isExponential
? Math.pow(1 - 15 / users.length, users.length / weightSum)
: null;

const leaderboard = await Promise.all(
sortedUsers.slice(0, 20).map(async (user) => {
const userInfo = await userModel.findOne({ _id: user.userId }).lean();
Expand All @@ -148,12 +138,7 @@ const getTicketLeaderboardHandler = async (req, res) => {
ticket1Amount: user.ticket1Amount,
ticket2Amount: user.ticket2Amount,
probability: user.weight / weightSum,
probabilityV2: calculateProbabilityV2(
users,
weightSum,
base,
user.weight
),
probabilityV2: calculateProbabilityV2(users, weightSum, user.weight),
};
})
);
Expand All @@ -174,7 +159,6 @@ const getTicketLeaderboardHandler = async (req, res) => {
probabilityV2: calculateProbabilityV2(
users,
weightSum,
base,
sortedUsers[rank].weight
),
}
Expand Down

0 comments on commit 3536e72

Please sign in to comment.