From cc3880816aeaee6d2cb81444df03429592a950d8 Mon Sep 17 00:00:00 2001 From: Sainan Date: Mon, 24 Mar 2025 12:53:09 +0100 Subject: [PATCH 1/9] feat: daily race leaderboards --- .../stats/leaderboardController.ts | 15 ++++++ src/models/leaderboardModel.ts | 17 +++++++ src/routes/stats.ts | 7 +-- src/services/leaderboardService.ts | 49 +++++++++++++++++++ src/services/statsService.ts | 3 ++ src/types/leaderboardTypes.ts | 15 ++++++ 6 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 src/controllers/stats/leaderboardController.ts create mode 100644 src/models/leaderboardModel.ts create mode 100644 src/services/leaderboardService.ts create mode 100644 src/types/leaderboardTypes.ts diff --git a/src/controllers/stats/leaderboardController.ts b/src/controllers/stats/leaderboardController.ts new file mode 100644 index 00000000..bff1a786 --- /dev/null +++ b/src/controllers/stats/leaderboardController.ts @@ -0,0 +1,15 @@ +import { getLeaderboard } from "@/src/services/leaderboardService"; +import { RequestHandler } from "express"; + +export const leaderboardController: RequestHandler = async (req, res) => { + const payload = JSON.parse(String(req.body)) as ILeaderboardRequest; + res.json({ + results: await getLeaderboard(payload.field, payload.before, payload.after) + }); +}; + +interface ILeaderboardRequest { + field: string; + before: number; + after: number; +} diff --git a/src/models/leaderboardModel.ts b/src/models/leaderboardModel.ts new file mode 100644 index 00000000..d6d031e9 --- /dev/null +++ b/src/models/leaderboardModel.ts @@ -0,0 +1,17 @@ +import { model, Schema } from "mongoose"; +import { ILeaderboardEntryDatabase } from "../types/leaderboardTypes"; + +const leaderboardEntrySchema = new Schema( + { + leaderboard: { type: String, required: true }, + displayName: { type: String, required: true }, + score: { type: Number, required: true }, + expiry: { type: Date, required: true } + }, + { id: false } +); + +leaderboardEntrySchema.index({ leaderboard: 1 }); +leaderboardEntrySchema.index({ expiry: 1 }, { expireAfterSeconds: 0 }); // With this, MongoDB will automatically delete expired entries. + +export const Leaderboard = model("Leaderboard", leaderboardEntrySchema); diff --git a/src/routes/stats.ts b/src/routes/stats.ts index 59290675..11705259 100644 --- a/src/routes/stats.ts +++ b/src/routes/stats.ts @@ -1,11 +1,12 @@ -import { viewController } from "../controllers/stats/viewController"; -import { uploadController } from "@/src/controllers/stats/uploadController"; - import express from "express"; +import { viewController } from "@/src/controllers/stats/viewController"; +import { uploadController } from "@/src/controllers/stats/uploadController"; +import { leaderboardController } from "@/src/controllers/stats/leaderboardController"; const statsRouter = express.Router(); statsRouter.get("/view.php", viewController); statsRouter.post("/upload.php", uploadController); +statsRouter.post("/leaderboardWeekly.php", leaderboardController); export { statsRouter }; diff --git a/src/services/leaderboardService.ts b/src/services/leaderboardService.ts new file mode 100644 index 00000000..0f8dbf96 --- /dev/null +++ b/src/services/leaderboardService.ts @@ -0,0 +1,49 @@ +import { toOid } from "../helpers/inventoryHelpers"; +import { Leaderboard } from "../models/leaderboardModel"; +import { ILeaderboardEntryClient } from "../types/leaderboardTypes"; + +export const submitLeaderboardScore = async ( + leaderboard: string, + displayName: string, + score: number +): Promise => { + const schedule = leaderboard.split(".")[0] as "daily" | "weekly"; + let expiry: Date; + if (schedule == "daily") { + expiry = new Date(Math.trunc(Date.now() / 86400000) * 86400000 + 86400000); + } else { + const EPOCH = 1734307200 * 1000; // Monday + const day = Math.trunc((Date.now() - EPOCH) / 86400000); + const week = Math.trunc(day / 7); + const weekStart = EPOCH + week * 604800000; + const weekEnd = weekStart + 604800000; + expiry = new Date(weekEnd); + } + await Leaderboard.findOneAndUpdate( + { leaderboard, displayName }, + { $max: { score }, $set: { expiry } }, + { upsert: true } + ); +}; + +export const getLeaderboard = async ( + leaderboard: string, + before: number, + after: number +): Promise => { + const entries = await Leaderboard.find({ leaderboard }) + .sort({ score: -1 }) + .skip(before) + .limit(after - before); + const res: ILeaderboardEntryClient[] = []; + let r = before; + for (const entry of entries) { + res.push({ + _id: toOid(entry._id), + s: entry.score, + r: ++r, + n: entry.displayName + }); + } + return res; +}; diff --git a/src/services/statsService.ts b/src/services/statsService.ts index ef467723..efc1c8de 100644 --- a/src/services/statsService.ts +++ b/src/services/statsService.ts @@ -11,6 +11,7 @@ import { } from "@/src/types/statTypes"; import { logger } from "@/src/utils/logger"; import { addEmailItem, getInventory } from "@/src/services/inventoryService"; +import { submitLeaderboardScore } from "./leaderboardService"; export const createStats = async (accountId: string): Promise => { const stats = new Stats({ accountOwnerId: accountId }); @@ -301,6 +302,8 @@ export const updateStats = async (accountOwnerId: string, payload: IStatsUpdate) } else { playerStats.Races.set(race, { highScore }); } + + await submitLeaderboardScore("daily.accounts." + race, payload.displayName, highScore); } break; diff --git a/src/types/leaderboardTypes.ts b/src/types/leaderboardTypes.ts new file mode 100644 index 00000000..b1fb253c --- /dev/null +++ b/src/types/leaderboardTypes.ts @@ -0,0 +1,15 @@ +import { IOid } from "./commonTypes"; + +export interface ILeaderboardEntryDatabase { + leaderboard: string; + displayName: string; + score: number; + expiry: Date; +} + +export interface ILeaderboardEntryClient { + _id: IOid; + s: number; // score + r: number; // rank + n: string; // displayName +} -- 2.47.2 From 1109d8e0ffffc096fd29e9e5210e404f9839300d Mon Sep 17 00:00:00 2001 From: Sainan Date: Mon, 24 Mar 2025 14:03:26 +0100 Subject: [PATCH 2/9] maintain logging for leaderboard request body for now --- src/controllers/stats/leaderboardController.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/stats/leaderboardController.ts b/src/controllers/stats/leaderboardController.ts index bff1a786..f6486ce0 100644 --- a/src/controllers/stats/leaderboardController.ts +++ b/src/controllers/stats/leaderboardController.ts @@ -1,7 +1,9 @@ import { getLeaderboard } from "@/src/services/leaderboardService"; +import { logger } from "@/src/utils/logger"; import { RequestHandler } from "express"; 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) -- 2.47.2 From ea201e3be84c9c8b46ee45a884ccb943ad715baa Mon Sep 17 00:00:00 2001 From: Sainan Date: Mon, 24 Mar 2025 14:17:07 +0100 Subject: [PATCH 3/9] fix _id field in response --- src/controllers/custom/deleteAccountController.ts | 2 ++ src/models/leaderboardModel.ts | 1 + src/services/leaderboardService.ts | 6 +++--- src/services/statsService.ts | 7 ++++++- src/types/leaderboardTypes.ts | 5 +++-- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/controllers/custom/deleteAccountController.ts b/src/controllers/custom/deleteAccountController.ts index 63ade312..d7df8670 100644 --- a/src/controllers/custom/deleteAccountController.ts +++ b/src/controllers/custom/deleteAccountController.ts @@ -8,6 +8,7 @@ import { PersonalRooms } from "@/src/models/personalRoomsModel"; import { Ship } from "@/src/models/shipModel"; import { Stats } from "@/src/models/statsModel"; import { GuildMember } from "@/src/models/guildModel"; +import { Leaderboard } from "@/src/models/leaderboardModel"; export const deleteAccountController: RequestHandler = async (req, res) => { const accountId = await getAccountIdForRequest(req); @@ -17,6 +18,7 @@ export const deleteAccountController: RequestHandler = async (req, res) => { GuildMember.deleteMany({ accountId: accountId }), Inbox.deleteMany({ ownerId: accountId }), Inventory.deleteOne({ accountOwnerId: accountId }), + Leaderboard.deleteMany({ accountId: accountId }), Loadout.deleteOne({ loadoutOwnerId: accountId }), PersonalRooms.deleteOne({ personalRoomsOwnerId: accountId }), Ship.deleteMany({ ShipOwnerId: accountId }), diff --git a/src/models/leaderboardModel.ts b/src/models/leaderboardModel.ts index d6d031e9..0c8cc60d 100644 --- a/src/models/leaderboardModel.ts +++ b/src/models/leaderboardModel.ts @@ -5,6 +5,7 @@ const leaderboardEntrySchema = new Schema( { leaderboard: { type: String, required: true }, displayName: { type: String, required: true }, + accountId: { type: Schema.Types.ObjectId, required: true }, score: { type: Number, required: true }, expiry: { type: Date, required: true } }, diff --git a/src/services/leaderboardService.ts b/src/services/leaderboardService.ts index 0f8dbf96..a8d933f7 100644 --- a/src/services/leaderboardService.ts +++ b/src/services/leaderboardService.ts @@ -1,9 +1,9 @@ -import { toOid } from "../helpers/inventoryHelpers"; import { Leaderboard } from "../models/leaderboardModel"; import { ILeaderboardEntryClient } from "../types/leaderboardTypes"; export const submitLeaderboardScore = async ( leaderboard: string, + accountId: string, displayName: string, score: number ): Promise => { @@ -21,7 +21,7 @@ export const submitLeaderboardScore = async ( } await Leaderboard.findOneAndUpdate( { leaderboard, displayName }, - { $max: { score }, $set: { expiry } }, + { $max: { score }, $set: { accountId, expiry } }, { upsert: true } ); }; @@ -39,7 +39,7 @@ export const getLeaderboard = async ( let r = before; for (const entry of entries) { res.push({ - _id: toOid(entry._id), + _id: entry.accountId.toString(), s: entry.score, r: ++r, n: entry.displayName diff --git a/src/services/statsService.ts b/src/services/statsService.ts index efc1c8de..a9115e7f 100644 --- a/src/services/statsService.ts +++ b/src/services/statsService.ts @@ -303,7 +303,12 @@ export const updateStats = async (accountOwnerId: string, payload: IStatsUpdate) playerStats.Races.set(race, { highScore }); } - await submitLeaderboardScore("daily.accounts." + race, payload.displayName, highScore); + await submitLeaderboardScore( + "daily.accounts." + race, + accountOwnerId, + payload.displayName, + highScore + ); } break; diff --git a/src/types/leaderboardTypes.ts b/src/types/leaderboardTypes.ts index b1fb253c..c2fec490 100644 --- a/src/types/leaderboardTypes.ts +++ b/src/types/leaderboardTypes.ts @@ -1,14 +1,15 @@ -import { IOid } from "./commonTypes"; +import { Types } from "mongoose"; export interface ILeaderboardEntryDatabase { leaderboard: string; + accountId: Types.ObjectId; displayName: string; score: number; expiry: Date; } export interface ILeaderboardEntryClient { - _id: IOid; + _id: string; // player id s: number; // score r: number; // rank n: string; // displayName -- 2.47.2 From 2fb2213c593253a76b30cd68ef32cb2d3713a059 Mon Sep 17 00:00:00 2001 From: Sainan Date: Mon, 24 Mar 2025 14:51:13 +0100 Subject: [PATCH 4/9] handle personal leaderboards (pivotId) --- .../stats/leaderboardController.ts | 3 +- src/models/leaderboardModel.ts | 8 +++- src/services/leaderboardService.ts | 42 +++++++++++++++---- 3 files changed, 44 insertions(+), 9 deletions(-) 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..0b301f11 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,42 @@ 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({ + leaderboard, + score: { $gt: pivotDoc.score } + }) + .sort({ score: 1 }) + .limit(before); + const afterDocs = await Leaderboard.find({ + leaderboard, + score: { $lt: pivotDoc.score } + }) + .sort({ score: -1 }) + .limit(after); + entries = [...beforeDocs.reverse(), pivotDoc, ...afterDocs]; + r = + (await Leaderboard.countDocuments({ + leaderboard, + score: { $gt: pivotDoc.score } + })) - beforeDocs.length; + } 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(), -- 2.47.2 From e270ca2f8495b93403f4bec4ceb736eb5d7aab90 Mon Sep 17 00:00:00 2001 From: Sainan Date: Mon, 24 Mar 2025 22:19:54 +0100 Subject: [PATCH 5/9] rename accountId to ownerId --- src/controllers/custom/deleteAccountController.ts | 2 +- src/models/leaderboardModel.ts | 2 +- src/services/leaderboardService.ts | 8 ++++---- src/types/leaderboardTypes.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/controllers/custom/deleteAccountController.ts b/src/controllers/custom/deleteAccountController.ts index d7df8670..e5c466b4 100644 --- a/src/controllers/custom/deleteAccountController.ts +++ b/src/controllers/custom/deleteAccountController.ts @@ -18,7 +18,7 @@ export const deleteAccountController: RequestHandler = async (req, res) => { GuildMember.deleteMany({ accountId: accountId }), Inbox.deleteMany({ ownerId: accountId }), Inventory.deleteOne({ accountOwnerId: accountId }), - Leaderboard.deleteMany({ accountId: accountId }), + Leaderboard.deleteMany({ ownerId: accountId }), Loadout.deleteOne({ loadoutOwnerId: accountId }), PersonalRooms.deleteOne({ personalRoomsOwnerId: accountId }), Ship.deleteMany({ ShipOwnerId: accountId }), diff --git a/src/models/leaderboardModel.ts b/src/models/leaderboardModel.ts index 656631cd..1dc50429 100644 --- a/src/models/leaderboardModel.ts +++ b/src/models/leaderboardModel.ts @@ -5,7 +5,7 @@ const leaderboardEntrySchema = new Schema( { leaderboard: { type: String, required: true }, displayName: { type: String, required: true }, - accountId: { type: Schema.Types.ObjectId, required: true }, + ownerId: { type: Schema.Types.ObjectId, required: true }, score: { type: Number, required: true }, expiry: { type: Date, required: true } }, diff --git a/src/services/leaderboardService.ts b/src/services/leaderboardService.ts index 0b301f11..5827bd6f 100644 --- a/src/services/leaderboardService.ts +++ b/src/services/leaderboardService.ts @@ -3,7 +3,7 @@ import { ILeaderboardEntryClient } from "../types/leaderboardTypes"; export const submitLeaderboardScore = async ( leaderboard: string, - accountId: string, + ownerId: string, displayName: string, score: number ): Promise => { @@ -21,7 +21,7 @@ export const submitLeaderboardScore = async ( } await Leaderboard.findOneAndUpdate( { leaderboard, displayName }, - { $max: { score }, $set: { accountId, expiry } }, + { $max: { score }, $set: { ownerId, expiry } }, { upsert: true } ); }; @@ -35,7 +35,7 @@ export const getLeaderboard = async ( let entries: TLeaderboardEntryDocument[]; let r: number; if (pivotId) { - const pivotDoc = await Leaderboard.findOne({ leaderboard, accountId: pivotId }); + const pivotDoc = await Leaderboard.findOne({ leaderboard, ownerId: pivotId }); if (!pivotDoc) { return []; } @@ -67,7 +67,7 @@ export const getLeaderboard = async ( const res: ILeaderboardEntryClient[] = []; for (const entry of entries) { res.push({ - _id: entry.accountId.toString(), + _id: entry.ownerId.toString(), s: entry.score, r: ++r, n: entry.displayName diff --git a/src/types/leaderboardTypes.ts b/src/types/leaderboardTypes.ts index c2fec490..b769c1cd 100644 --- a/src/types/leaderboardTypes.ts +++ b/src/types/leaderboardTypes.ts @@ -2,14 +2,14 @@ import { Types } from "mongoose"; export interface ILeaderboardEntryDatabase { leaderboard: string; - accountId: Types.ObjectId; displayName: string; + ownerId: Types.ObjectId; score: number; expiry: Date; } export interface ILeaderboardEntryClient { - _id: string; // player id + _id: string; // owner id s: number; // score r: number; // rank n: string; // displayName -- 2.47.2 From 46cdb44f59d44d442d632ae7d9662121ab9b24b6 Mon Sep 17 00:00:00 2001 From: Sainan Date: Mon, 24 Mar 2025 22:42:45 +0100 Subject: [PATCH 6/9] track DojoObstacleScore on leaderboards --- src/services/statsService.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/services/statsService.ts b/src/services/statsService.ts index a9115e7f..153c98d4 100644 --- a/src/services/statsService.ts +++ b/src/services/statsService.ts @@ -316,9 +316,19 @@ export const updateStats = async (accountOwnerId: string, payload: IStatsUpdate) case "ZephyrScore": case "SentinelGameScore": case "CaliberChicksScore": + playerStats[category] ??= 0; + if (data > playerStats[category]) playerStats[category] = data as number; + break; + case "DojoObstacleScore": playerStats[category] ??= 0; if (data > playerStats[category]) playerStats[category] = data as number; + await submitLeaderboardScore( + "weekly.accounts." + category, + accountOwnerId, + payload.displayName, + data as number + ); break; case "OlliesCrashCourseScore": -- 2.47.2 From 942f062e907c88e4568fd7a85d138c30cddc0670 Mon Sep 17 00:00:00 2001 From: Sainan Date: Mon, 24 Mar 2025 23:00:23 +0100 Subject: [PATCH 7/9] add guildId to optionally narrow results --- src/controllers/stats/leaderboardController.ts | 3 ++- src/models/leaderboardModel.ts | 4 +++- src/services/leaderboardService.ts | 17 +++++++++++------ src/services/statsService.ts | 3 ++- src/types/leaderboardTypes.ts | 3 ++- 5 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/controllers/stats/leaderboardController.ts b/src/controllers/stats/leaderboardController.ts index 7a292cfb..b76e5e16 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, payload.pivotId) + results: await getLeaderboard(payload.field, payload.before, payload.after, payload.guildId, payload.pivotId) }); }; @@ -14,5 +14,6 @@ interface ILeaderboardRequest { field: string; before: number; after: number; + guildId?: string; pivotId?: string; } diff --git a/src/models/leaderboardModel.ts b/src/models/leaderboardModel.ts index 1dc50429..5de2f608 100644 --- a/src/models/leaderboardModel.ts +++ b/src/models/leaderboardModel.ts @@ -4,15 +4,17 @@ import { ILeaderboardEntryDatabase } from "../types/leaderboardTypes"; const leaderboardEntrySchema = new Schema( { leaderboard: { type: String, required: true }, - displayName: { type: String, required: true }, ownerId: { type: Schema.Types.ObjectId, required: true }, + displayName: { type: String, required: true }, score: { type: Number, required: true }, + guildId: Schema.Types.ObjectId, expiry: { type: Date, required: true } }, { id: false } ); leaderboardEntrySchema.index({ leaderboard: 1 }); +leaderboardEntrySchema.index({ leaderboard: 1, ownerId: 1 }, { unique: true }); leaderboardEntrySchema.index({ expiry: 1 }, { expireAfterSeconds: 0 }); // With this, MongoDB will automatically delete expired entries. export const Leaderboard = model("Leaderboard", leaderboardEntrySchema); diff --git a/src/services/leaderboardService.ts b/src/services/leaderboardService.ts index 5827bd6f..011a3f62 100644 --- a/src/services/leaderboardService.ts +++ b/src/services/leaderboardService.ts @@ -5,7 +5,8 @@ export const submitLeaderboardScore = async ( leaderboard: string, ownerId: string, displayName: string, - score: number + score: number, + guildId?: string ): Promise => { const schedule = leaderboard.split(".")[0] as "daily" | "weekly"; let expiry: Date; @@ -20,8 +21,8 @@ export const submitLeaderboardScore = async ( expiry = new Date(weekEnd); } await Leaderboard.findOneAndUpdate( - { leaderboard, displayName }, - { $max: { score }, $set: { ownerId, expiry } }, + { leaderboard, ownerId }, + { $max: { score }, $set: { displayName, guildId, expiry } }, { upsert: true } ); }; @@ -30,23 +31,26 @@ export const getLeaderboard = async ( leaderboard: string, before: number, after: number, - pivotId: string | undefined = undefined + guildId?: string, + pivotId?: string ): Promise => { let entries: TLeaderboardEntryDocument[]; let r: number; if (pivotId) { - const pivotDoc = await Leaderboard.findOne({ leaderboard, ownerId: pivotId }); + const pivotDoc = await Leaderboard.findOne({ leaderboard, guildId, ownerId: pivotId }); if (!pivotDoc) { return []; } const beforeDocs = await Leaderboard.find({ leaderboard, + guildId, score: { $gt: pivotDoc.score } }) .sort({ score: 1 }) .limit(before); const afterDocs = await Leaderboard.find({ leaderboard, + guildId, score: { $lt: pivotDoc.score } }) .sort({ score: -1 }) @@ -55,10 +59,11 @@ export const getLeaderboard = async ( r = (await Leaderboard.countDocuments({ leaderboard, + guildId, score: { $gt: pivotDoc.score } })) - beforeDocs.length; } else { - entries = await Leaderboard.find({ leaderboard }) + entries = await Leaderboard.find({ leaderboard, guildId }) .sort({ score: -1 }) .skip(before) .limit(after - before); diff --git a/src/services/statsService.ts b/src/services/statsService.ts index 153c98d4..2c34997a 100644 --- a/src/services/statsService.ts +++ b/src/services/statsService.ts @@ -327,7 +327,8 @@ export const updateStats = async (accountOwnerId: string, payload: IStatsUpdate) "weekly.accounts." + category, accountOwnerId, payload.displayName, - data as number + data as number, + payload.guildId ); break; diff --git a/src/types/leaderboardTypes.ts b/src/types/leaderboardTypes.ts index b769c1cd..5173a3a3 100644 --- a/src/types/leaderboardTypes.ts +++ b/src/types/leaderboardTypes.ts @@ -2,9 +2,10 @@ import { Types } from "mongoose"; export interface ILeaderboardEntryDatabase { leaderboard: string; - displayName: string; ownerId: Types.ObjectId; + displayName: string; score: number; + guildId?: Types.ObjectId; expiry: Date; } -- 2.47.2 From 02b443712af703c63365e510c933a5d3c65c9f3c Mon Sep 17 00:00:00 2001 From: Sainan Date: Mon, 24 Mar 2025 23:04:22 +0100 Subject: [PATCH 8/9] fix MongoDB taking `guildId: undefined` as a filter --- src/services/leaderboardService.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/services/leaderboardService.ts b/src/services/leaderboardService.ts index 011a3f62..afbe1623 100644 --- a/src/services/leaderboardService.ts +++ b/src/services/leaderboardService.ts @@ -34,23 +34,26 @@ export const getLeaderboard = async ( guildId?: string, pivotId?: string ): Promise => { + const filter: { leaderboard: string; guildId?: string } = { leaderboard }; + if (guildId) { + filter.guildId = guildId; + } + let entries: TLeaderboardEntryDocument[]; let r: number; if (pivotId) { - const pivotDoc = await Leaderboard.findOne({ leaderboard, guildId, ownerId: pivotId }); + const pivotDoc = await Leaderboard.findOne({ ...filter, ownerId: pivotId }); if (!pivotDoc) { return []; } const beforeDocs = await Leaderboard.find({ - leaderboard, - guildId, + ...filter, score: { $gt: pivotDoc.score } }) .sort({ score: 1 }) .limit(before); const afterDocs = await Leaderboard.find({ - leaderboard, - guildId, + ...filter, score: { $lt: pivotDoc.score } }) .sort({ score: -1 }) @@ -58,12 +61,11 @@ export const getLeaderboard = async ( entries = [...beforeDocs.reverse(), pivotDoc, ...afterDocs]; r = (await Leaderboard.countDocuments({ - leaderboard, - guildId, + ...filter, score: { $gt: pivotDoc.score } })) - beforeDocs.length; } else { - entries = await Leaderboard.find({ leaderboard, guildId }) + entries = await Leaderboard.find(filter) .sort({ score: -1 }) .skip(before) .limit(after - before); -- 2.47.2 From 2b7031502518831fe61ee3a78dbf32358d0f5c30 Mon Sep 17 00:00:00 2001 From: Sainan Date: Mon, 24 Mar 2025 23:05:45 +0100 Subject: [PATCH 9/9] track OlliesCrashCourseScore on leaderboards --- src/services/statsService.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/services/statsService.ts b/src/services/statsService.ts index 2c34997a..a6ca826c 100644 --- a/src/services/statsService.ts +++ b/src/services/statsService.ts @@ -349,6 +349,12 @@ export const updateStats = async (accountOwnerId: string, payload: IStatsUpdate) ); } if (data > playerStats[category]) playerStats[category] = data as number; + await submitLeaderboardScore( + "weekly.accounts." + category, + accountOwnerId, + payload.displayName, + data as number + ); break; default: -- 2.47.2