diff --git a/backend/__tests__/dal/leaderboards.spec.ts b/backend/__tests__/dal/leaderboards.spec.ts index b02f83e662cc..732548c43044 100644 --- a/backend/__tests__/dal/leaderboards.spec.ts +++ b/backend/__tests__/dal/leaderboards.spec.ts @@ -280,7 +280,7 @@ function expectedLbEntry( // @ts-expect-error user.lbPersonalBests?.time[Number.parseInt(time)].english; - return { + const entry = { rank, uid: user.uid, name: user.name, @@ -294,6 +294,15 @@ function expectedLbEntry( badgeId, isPremium, }; + + if (badgeId === undefined) { + delete entry.badgeId; + } + if (isPremium === undefined) { + delete entry.isPremium; + } + + return entry; } async function createUser( diff --git a/backend/src/dal/leaderboards.ts b/backend/src/dal/leaderboards.ts index a724c4de1f7d..c9fafc6189bb 100644 --- a/backend/src/dal/leaderboards.ts +++ b/backend/src/dal/leaderboards.ts @@ -123,96 +123,104 @@ export async function update( const lbCollectionName = `leaderboards.${language}.${mode}.${mode2}`; const minTimeTyping = (await getCachedConfiguration(true)).leaderboards .minTimeTyping; - const lb = db.collection("users").aggregate( - [ - { - $match: { - [`${key}.wpm`]: { - $gt: 0, - }, - [`${key}.acc`]: { - $gt: 0, - }, - [`${key}.timestamp`]: { - $gt: 0, - }, - banned: { - $ne: true, - }, - lbOptOut: { - $ne: true, - }, - needsToChangeName: { - $ne: true, - }, - timeTyping: { - $gt: isDevEnvironment() ? 0 : minTimeTyping, - }, - }, + const pipeline = [ + { + $match: { + [`${key}.wpm`]: { $gt: 0 }, + [`${key}.acc`]: { $gt: 0 }, + [`${key}.timestamp`]: { $gt: 0 }, + banned: { $ne: true }, + lbOptOut: { $ne: true }, + needsToChangeName: { $ne: true }, + timeTyping: { $gt: isDevEnvironment() ? 0 : minTimeTyping }, }, - { - $sort: { - [`${key}.wpm`]: -1, - [`${key}.acc`]: -1, - [`${key}.timestamp`]: -1, + }, + + { + $project: { + _id: 0, + [`${key}.wpm`]: 1, + [`${key}.acc`]: 1, + [`${key}.raw`]: 1, + [`${key}.consistency`]: { + $ifNull: [`$${key}.consistency`, "$$REMOVE"], }, - }, - { - $project: { - _id: 0, - [`${key}.wpm`]: 1, - [`${key}.acc`]: 1, - [`${key}.raw`]: 1, - [`${key}.consistency`]: 1, - [`${key}.timestamp`]: 1, - uid: 1, - name: 1, - discordId: 1, - discordAvatar: 1, - inventory: 1, - premium: 1, + [`${key}.timestamp`]: 1, + uid: 1, + name: 1, + discordId: 1, + discordAvatar: 1, + inventory: 1, + premium: 1, + sortKey: { + $add: [ + { $multiply: [`${key}.wpm`, 1e19] }, //maybe use $convert: {input: `${key}.wpm`,to: "double",onError: null,}, + { $multiply: [`${key}.acc`, 1e16] }, + [`${key}.timestamp`], + ], }, }, + }, + //sort by wpm, acc, timestamp descending, add rank number + { + $setWindowFields: { + sortBy: { sortKey: -1 }, + output: { "user.rank": { $documentNumber: {} } }, + }, + }, + { + $addFields: { + "user.uid": "$uid", + "user.name": "$name", + "user.discordId": { $ifNull: ["$discordId", "$$REMOVE"] }, + "user.discordAvatar": { $ifNull: ["$discordAvatar", "$$REMOVE"] }, + "user.badgeId": { + $ifNull: [ + { + $first: { + $map: { + input: { + $filter: { + input: "$inventory.badges", + as: "badge", + cond: { $eq: ["$$badge.selected", true] }, + }, + }, + as: "selectedBadge", + in: "$$selectedBadge.id", + }, + }, + }, + "$$REMOVE", + ], + }, - { - $addFields: { - "user.uid": "$uid", - "user.name": "$name", - "user.discordId": { $ifNull: ["$discordId", "$$REMOVE"] }, - "user.discordAvatar": { $ifNull: ["$discordAvatar", "$$REMOVE"] }, - [`${key}.consistency`]: { - $ifNull: [`$${key}.consistency`, "$$REMOVE"], - }, - calculated: { - $function: { - lang: "js", - args: [ - "$premium.expirationTimestamp", - "$$NOW", - "$inventory.badges", + "user.isPremium": { + $cond: { + if: { + $or: [ + { $eq: ["$premium.expirationTimestamp", -1] }, + { $gt: ["$premium.expirationTimestamp", { $toLong: "$$NOW" }] }, ], - body: `function(expiration, currentTime, badges) { - try {row_number+= 1;} catch (e) {row_number= 1;} - var badgeId = undefined; - if(badges)for(let i=0; icurrentTime) || undefined; - return {rank:row_number,badgeId, isPremium}; - }`, }, + // oxlint-disable-next-line no-thenable + then: true, + else: "$$REMOVE", }, }, }, - { - $replaceWith: { - $mergeObjects: [`$${key}`, "$user", "$calculated"], - }, + }, + { + $replaceWith: { + $mergeObjects: [`$${key}`, "$user"], }, - { $out: lbCollectionName }, - ], - { allowDiskUse: true } - ); + }, + { $out: lbCollectionName }, + ]; + + const lb = db + .collection("users") + .aggregate(pipeline, { allowDiskUse: true }); const start1 = performance.now(); await lb.toArray();