diff --git a/src/controllers/stats/leaderboardController.ts b/src/controllers/stats/leaderboardController.ts index f6486ce0..7a292cfb 100644 --- a/src/controllers/stats/leaderboardController.ts +++ b/src/controllers/stats/leaderboardController.ts @@ -6,7 +6,7 @@ export const leaderboardController: RequestHandler = async (req, res) => { logger.debug(`data provided to ${req.path}: ${String(req.body)}`); const payload = JSON.parse(String(req.body)) as ILeaderboardRequest; res.json({ - results: await getLeaderboard(payload.field, payload.before, payload.after) + results: await getLeaderboard(payload.field, payload.before, payload.after, payload.pivotId) }); }; @@ -14,4 +14,5 @@ interface ILeaderboardRequest { field: string; before: number; after: number; + pivotId?: string; } diff --git a/src/models/leaderboardModel.ts b/src/models/leaderboardModel.ts index 0c8cc60d..656631cd 100644 --- a/src/models/leaderboardModel.ts +++ b/src/models/leaderboardModel.ts @@ -1,4 +1,4 @@ -import { model, Schema } from "mongoose"; +import { Document, model, Schema, Types } from "mongoose"; import { ILeaderboardEntryDatabase } from "../types/leaderboardTypes"; const leaderboardEntrySchema = new Schema( @@ -16,3 +16,9 @@ leaderboardEntrySchema.index({ leaderboard: 1 }); leaderboardEntrySchema.index({ expiry: 1 }, { expireAfterSeconds: 0 }); // With this, MongoDB will automatically delete expired entries. export const Leaderboard = model("Leaderboard", leaderboardEntrySchema); + +// eslint-disable-next-line @typescript-eslint/ban-types +export type TLeaderboardEntryDocument = Document & { + _id: Types.ObjectId; + __v: number; +} & ILeaderboardEntryDatabase; diff --git a/src/services/leaderboardService.ts b/src/services/leaderboardService.ts index a8d933f7..6a3657e6 100644 --- a/src/services/leaderboardService.ts +++ b/src/services/leaderboardService.ts @@ -1,4 +1,4 @@ -import { Leaderboard } from "../models/leaderboardModel"; +import { Leaderboard, TLeaderboardEntryDocument } from "../models/leaderboardModel"; import { ILeaderboardEntryClient } from "../types/leaderboardTypes"; export const submitLeaderboardScore = async ( @@ -29,14 +29,39 @@ export const submitLeaderboardScore = async ( export const getLeaderboard = async ( leaderboard: string, before: number, - after: number + after: number, + pivotId: string | undefined = undefined ): Promise => { - const entries = await Leaderboard.find({ leaderboard }) - .sort({ score: -1 }) - .skip(before) - .limit(after - before); + let entries: TLeaderboardEntryDocument[]; + let r: number; + if (pivotId) { + const pivotDoc = await Leaderboard.findOne({ leaderboard, accountId: pivotId }); + if (!pivotDoc) { + return []; + } + const beforeDocs = await Leaderboard.find({ + score: { $gt: pivotDoc.score } + }) + .sort({ score: 1 }) + .limit(before); + const afterDocs = await Leaderboard.find({ + score: { $lt: pivotDoc.score } + }) + .sort({ score: -1 }) + .limit(after); + entries = [...beforeDocs.reverse(), pivotDoc, ...afterDocs]; + r = await Leaderboard.countDocuments({ + leaderboard: leaderboard, + score: { $gt: pivotDoc.score } + }); + } else { + entries = await Leaderboard.find({ leaderboard }) + .sort({ score: -1 }) + .skip(before) + .limit(after - before); + r = before; + } const res: ILeaderboardEntryClient[] = []; - let r = before; for (const entry of entries) { res.push({ _id: entry.accountId.toString(),