diff --git a/backend/api/controllers/leaderboards.js b/backend/api/controllers/leaderboards.js new file mode 100644 index 000000000..6a211ee73 --- /dev/null +++ b/backend/api/controllers/leaderboards.js @@ -0,0 +1,38 @@ +const LeaderboardsDAO = require("../../dao/leaderboards"); + +class LeaderboardsController { + static async get(req, res, next) { + try { + const { language, mode, mode2 } = req.query; + if (!language || !mode || !mode2) { + return res.status(400).json({ + message: "Missing parameters", + }); + } + let retval = await LeaderboardsDAO.get(mode, mode2, language); + retval.forEach((item) => { + delete item.uid; + }); + return res.status(200).json(retval); + } catch (e) { + return next(e); + } + } + + static async update(req, res, next) { + try { + const { language, mode, mode2 } = req.body; + if (!language || !mode || !mode2) { + return res.status(400).json({ + message: "Missing parameters", + }); + } + let retval = await LeaderboardsDAO.update(mode, mode2, language); + return res.status(200).json(retval); + } catch (e) { + return next(e); + } + } +} + +module.exports = LeaderboardsController; diff --git a/backend/api/routes/leaderboards.js b/backend/api/routes/leaderboards.js new file mode 100644 index 000000000..ea2a9df6b --- /dev/null +++ b/backend/api/routes/leaderboards.js @@ -0,0 +1,22 @@ +const { authenticateRequest } = require("../../middlewares/auth"); +const LeaderboardsController = require("../controllers/leaderboards"); +const RateLimit = require("../../middlewares/rate-limit"); + +const { Router } = require("express"); + +const router = Router(); + +router.get( + "/", + RateLimit.limit1persec, + authenticateRequest, + LeaderboardsController.get +); + +router.post( + "/debug_update", + RateLimit.limit1persec, + LeaderboardsController.update +); + +module.exports = router; diff --git a/backend/dao/leaderboards.js b/backend/dao/leaderboards.js new file mode 100644 index 000000000..17cde5852 --- /dev/null +++ b/backend/dao/leaderboards.js @@ -0,0 +1,84 @@ +const MonkeyError = require("../handlers/error"); +const { mongoDB } = require("../init/mongodb"); +const { ObjectID } = require("mongodb"); + +class LeaderboardsDAO { + static async get(mode, mode2, language) { + const preset = await mongoDB() + .collection(`leaderboards.${language}.${mode}.${mode2}`) + .find() + .toArray(); + return preset; + } + + static async getRank(mode, mode2, language, uid) { + const res = await mongoDB() + .collection(`leaderboards.${language}.${mode}.${mode2}`) + .findOne({ uid }); + return res.rank; + } + + static async update(mode, mode2, language, uid = undefined) { + let str = `lbPersonalBests.${mode}.${mode2}.${language}`; + let lb = await mongoDB() + .collection("users") + .aggregate([ + { + $match: { + [str]: { + $exists: true, + }, + }, + }, + { + $set: { + [str + ".uid"]: "$uid", + [str + ".name"]: "$name", + }, + }, + { + $replaceRoot: { + newRoot: "$" + str, + }, + }, + { + $sort: { + wpm: -1, + acc: -1, + timestamp: -1, + }, + }, + ]) + .toArray(); + + let rerval = undefined; + lb.forEach((lbEntry, index) => { + lbEntry.rank = index + 1; + if (uid && lbEntry.uid === uid) { + rerval = index + 1; + } + }); + + try { + await mongoDB() + .collection(`leaderboards.${language}.${mode}.${mode2}`) + .drop(); + } catch (e) {} + await mongoDB() + .collection(`leaderboards.${language}.${mode}.${mode2}`) + .insertMany(lb); + + if (rerval) { + return { + message: "Successfully updated leaderboard", + rank: retval, + }; + } else { + return { + message: "Successfully updated leaderboard", + }; + } + } +} + +module.exports = LeaderboardsDAO; diff --git a/backend/dao/user.js b/backend/dao/user.js index 7767c16cc..7893ed514 100644 --- a/backend/dao/user.js +++ b/backend/dao/user.js @@ -191,6 +191,11 @@ class UsersDAO { await mongoDB() .collection("users") .updateOne({ uid }, { $set: { personalBests: pb.obj } }); + if (pb.lbPb) { + await mongoDB() + .collection("users") + .updateOne({ uid }, { $set: { lbPersonalBests: pb.lbPb } }); + } return true; } else { return false; diff --git a/backend/handlers/pb.js b/backend/handlers/pb.js index 7a31e3415..cc703e2d5 100644 --- a/backend/handlers/pb.js +++ b/backend/handlers/pb.js @@ -96,20 +96,50 @@ module.exports = { }); } - let topIndex = 0; - let topWpm = 0; - obj[mode][mode2].forEach((pb, index) => { - delete pb.best; - if (pb.wpm > topWpm) { - topIndex = index; - topWpm = pb.wpm; + let lbPb; + if (isPb && mode === "time" && (mode2 == "15" || mode2 == "60")) { + lbPb = { + time: { + 15: {}, + 60: {}, + }, + }; + let bestForEveryLanguage = {}; + if (obj?.time?.[15]) { + obj.time[15].forEach((pb) => { + if (!bestForEveryLanguage[pb.language]) { + bestForEveryLanguage[pb.language] = pb; + } else { + if (bestForEveryLanguage[pb.language].wpm < pb.wpm) { + bestForEveryLanguage[pb.language] = pb; + } + } + }); + Object.keys(bestForEveryLanguage).forEach((key) => { + lbPb.time[15][key] = bestForEveryLanguage[key]; + }); + bestForEveryLanguage = {}; } - }); - obj[mode][mode2][topIndex].best = true; + if (obj?.time?.[60]) { + obj.time[60].forEach((pb) => { + if (!bestForEveryLanguage[pb.language]) { + bestForEveryLanguage[pb.language] = pb; + } else { + if (bestForEveryLanguage[pb.language].wpm < pb.wpm) { + bestForEveryLanguage[pb.language] = pb; + } + } + }); + Object.keys(bestForEveryLanguage).forEach((key) => { + lbPb.time[60][key] = bestForEveryLanguage[key]; + }); + } + } return { isPb, obj, + lbPb, }; }, }; diff --git a/backend/server.js b/backend/server.js index 02074d351..b87c39c23 100644 --- a/backend/server.js +++ b/backend/server.js @@ -32,6 +32,8 @@ const quoteRatings = require("./api/routes/quote-ratings"); app.use("/quote-ratings", quoteRatings); const psaRouter = require("./api/routes/psa"); app.use("/psa", psaRouter); +const leaderboardsRouter = require("./api/routes/leaderboards"); +app.use("/leaderboards", leaderboardsRouter); app.use(function (e, req, res, next) { let uid = undefined; @@ -70,4 +72,56 @@ app.listen(PORT, async () => { credential: admin.credential.cert(serviceAccount), }); console.log("Database Connected"); + + // refactor(); }); + +async function refactor() { + let users = await mongoDB().collection("users").find({}).toArray(); + + for (let user of users) { + let obj = user.personalBests; + + lbPb = { + time: { + 15: {}, + 60: {}, + }, + }; + let bestForEveryLanguage = {}; + if (obj?.time?.[15]) { + obj.time[15].forEach((pb) => { + if (!bestForEveryLanguage[pb.language]) { + bestForEveryLanguage[pb.language] = pb; + } else { + if (bestForEveryLanguage[pb.language].wpm < pb.wpm) { + bestForEveryLanguage[pb.language] = pb; + } + } + }); + Object.keys(bestForEveryLanguage).forEach((key) => { + lbPb.time[15][key] = bestForEveryLanguage[key]; + }); + bestForEveryLanguage = {}; + } + if (obj?.time?.[60]) { + obj.time[60].forEach((pb) => { + if (!bestForEveryLanguage[pb.language]) { + bestForEveryLanguage[pb.language] = pb; + } else { + if (bestForEveryLanguage[pb.language].wpm < pb.wpm) { + bestForEveryLanguage[pb.language] = pb; + } + } + }); + Object.keys(bestForEveryLanguage).forEach((key) => { + lbPb.time[60][key] = bestForEveryLanguage[key]; + }); + } + + await mongoDB() + .collection("users") + .updateOne({ _id: user._id }, { $set: { lbPersonalBests: lbPb } }); + console.log(`updated ${user.name}`); + } +}