Skip to content

Commit 8a41cce

Browse files
fehmerMiodec
andauthored
fix: past leaderboard not fetching the users rank (@fehmer) (monkeytypegame#6289)
Show the users ranking for the last day on the daily and for the last week on the weekly leaderboard correctly. - Fix request query schema for the [daily rank](https://api.monkeytype.com/docs/internal#tag/leaderboards/operation/leaderboards.getDailyRank) having pagination - Fix request query schema for the [weekly rank](https://api.monkeytype.com/docs/internal#tag/leaderboards/operation/leaderboards.getWeeklyXpRank) missing the `weeksBefore` parameter - Fix frontend to include the `daysBefore` or `weeksBefore` parameter on `rank` calls --------- Co-authored-by: Miodec <[email protected]>
1 parent 0b840d2 commit 8a41cce

File tree

4 files changed

+113
-28
lines changed

4 files changed

+113
-28
lines changed

backend/__tests__/api/controllers/leaderboard.spec.ts

+73-5
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,8 @@ describe("Loaderboard Controller", () => {
10671067
beforeEach(async () => {
10681068
getXpWeeklyLeaderboardMock.mockReset();
10691069
await weeklyLeaderboardEnabled(true);
1070+
vi.useFakeTimers();
1071+
vi.setSystemTime(1722606812000);
10701072
});
10711073

10721074
it("fails withouth authentication", async () => {
@@ -1109,6 +1111,47 @@ describe("Loaderboard Controller", () => {
11091111

11101112
expect(getRankMock).toHaveBeenCalledWith(uid, lbConf);
11111113
});
1114+
1115+
it("should get for last week", async () => {
1116+
//GIVEN
1117+
const lbConf = (await configuration).leaderboards.weeklyXp;
1118+
1119+
const resultData: XpLeaderboardEntry = {
1120+
totalXp: 100,
1121+
rank: 1,
1122+
timeTypedSeconds: 100,
1123+
uid: "user1",
1124+
name: "user1",
1125+
discordId: "discordId",
1126+
discordAvatar: "discordAvatar",
1127+
lastActivityTimestamp: 1000,
1128+
};
1129+
const getRankMock = vi.fn();
1130+
getRankMock.mockResolvedValue(resultData);
1131+
getXpWeeklyLeaderboardMock.mockReturnValue({
1132+
getRank: getRankMock,
1133+
} as any);
1134+
1135+
//WHEN
1136+
const { body } = await mockApp
1137+
.get("/leaderboards/xp/weekly/rank")
1138+
.query({ weeksBefore: 1 })
1139+
.set("authorization", `Uid ${uid}`)
1140+
.expect(200);
1141+
1142+
//THEN
1143+
expect(body).toEqual({
1144+
message: "Weekly xp leaderboard rank retrieved",
1145+
data: resultData,
1146+
});
1147+
1148+
expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(
1149+
lbConf,
1150+
1721606400000
1151+
);
1152+
1153+
expect(getRankMock).toHaveBeenCalledWith(uid, lbConf);
1154+
});
11121155
it("fails if daily leaderboards are disabled", async () => {
11131156
await weeklyLeaderboardEnabled(false);
11141157

@@ -1122,6 +1165,36 @@ describe("Loaderboard Controller", () => {
11221165
);
11231166
});
11241167

1168+
it("fails for weeksBefore not one", async () => {
1169+
const { body } = await mockApp
1170+
.get("/leaderboards/xp/weekly/rank")
1171+
.set("authorization", `Uid ${uid}`)
1172+
.query({
1173+
weeksBefore: 2,
1174+
})
1175+
.expect(422);
1176+
1177+
expect(body).toEqual({
1178+
message: "Invalid query schema",
1179+
validationErrors: ['"weeksBefore" Invalid literal value, expected 1'],
1180+
});
1181+
});
1182+
1183+
it("fails for unknown query", async () => {
1184+
const { body } = await mockApp
1185+
.get("/leaderboards/xp/weekly/rank")
1186+
.set("authorization", `Uid ${uid}`)
1187+
.query({
1188+
extra: "value",
1189+
})
1190+
.expect(422);
1191+
1192+
expect(body).toEqual({
1193+
message: "Invalid query schema",
1194+
validationErrors: ["Unrecognized key(s) in object: 'extra'"],
1195+
});
1196+
});
1197+
11251198
it("fails while leaderboard is missing", async () => {
11261199
//GIVEN
11271200
getXpWeeklyLeaderboardMock.mockReturnValue(null);
@@ -1130,11 +1203,6 @@ describe("Loaderboard Controller", () => {
11301203
const { body } = await mockApp
11311204
.get("/leaderboards/xp/weekly/rank")
11321205
.set("authorization", `Uid ${uid}`)
1133-
.query({
1134-
language: "english",
1135-
mode: "time",
1136-
mode2: "60",
1137-
})
11381206
.expect(404);
11391207

11401208
expect(body.message).toEqual("XP leaderboard for this week not found.");

backend/src/api/controllers/leaderboard.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import MonkeyError from "../../utils/error";
55
import * as DailyLeaderboards from "../../utils/daily-leaderboards";
66
import * as WeeklyXpLeaderboard from "../../services/weekly-xp-leaderboard";
77
import {
8+
DailyLeaderboardQuery,
89
GetDailyLeaderboardQuery,
910
GetDailyLeaderboardRankQuery,
1011
GetDailyLeaderboardResponse,
@@ -14,6 +15,7 @@ import {
1415
GetLeaderboardRankResponse,
1516
GetLeaderboardResponse as GetLeaderboardResponse,
1617
GetWeeklyXpLeaderboardQuery,
18+
GetWeeklyXpLeaderboardRankQuery,
1719
GetWeeklyXpLeaderboardRankResponse,
1820
GetWeeklyXpLeaderboardResponse,
1921
} from "@monkeytype/contracts/leaderboards";
@@ -73,7 +75,7 @@ export async function getRankFromLeaderboard(
7375
}
7476

7577
function getDailyLeaderboardWithError(
76-
{ language, mode, mode2, daysBefore }: GetDailyLeaderboardRankQuery,
78+
{ language, mode, mode2, daysBefore }: DailyLeaderboardQuery,
7779
config: Configuration["dailyLeaderboards"]
7880
): DailyLeaderboards.DailyLeaderboard {
7981
const customTimestamp =
@@ -187,12 +189,13 @@ export async function getWeeklyXpLeaderboardResults(
187189
}
188190

189191
export async function getWeeklyXpLeaderboardRank(
190-
req: MonkeyRequest
192+
req: MonkeyRequest<GetWeeklyXpLeaderboardRankQuery>
191193
): Promise<GetWeeklyXpLeaderboardRankResponse> {
192194
const { uid } = req.ctx.decodedToken;
193195

194196
const weeklyXpLeaderboard = getWeeklyXpLeaderboardWithError(
195-
req.ctx.configuration.leaderboards.weeklyXp
197+
req.ctx.configuration.leaderboards.weeklyXp,
198+
req.query.weeksBefore
196199
);
197200
const rankEntry = await weeklyXpLeaderboard.getRank(
198201
uid,

frontend/src/ts/pages/leaderboards.ts

+14-12
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,7 @@ async function requestData(update = false): Promise<void> {
246246
requests.rank = Ape.leaderboards.getDailyRank({
247247
query: {
248248
...baseQuery,
249+
daysBefore: state.yesterday ? 1 : undefined,
249250
},
250251
});
251252
}
@@ -324,7 +325,11 @@ async function requestData(update = false): Promise<void> {
324325
});
325326

326327
if (isAuthenticated() && state.userData === null) {
327-
requests.rank = Ape.leaderboards.getWeeklyXpRank();
328+
requests.rank = Ape.leaderboards.getWeeklyXpRank({
329+
query: {
330+
weeksBefore: state.lastWeek ? 1 : undefined,
331+
},
332+
});
328333
}
329334

330335
const [dataResponse, rankResponse] = await Promise.all([
@@ -653,8 +658,14 @@ function fillUser(): void {
653658
}
654659

655660
if (isAuthenticated() && state.type === "daily" && state.userData === null) {
661+
let str = `Not qualified`;
662+
663+
if (!state.yesterday) {
664+
str += ` (min speed required: ${state.minWpm} wpm)`;
665+
}
666+
656667
$(".page.pageLeaderboards .bigUser").html(
657-
`<div class="warning">Not qualified (min speed required: ${state.minWpm} wpm)</div>`
668+
`<div class="warning">${str}</div>`
658669
);
659670
return;
660671
}
@@ -670,15 +681,6 @@ function fillUser(): void {
670681
return;
671682
}
672683

673-
if (
674-
(state.type === "weekly" && state.lastWeek) ||
675-
(state.type === "daily" && state.yesterday)
676-
) {
677-
$(".page.pageLeaderboards .bigUser").addClass("hidden");
678-
$(".page.pageLeaderboards .tableAndUser > .divider").removeClass("hidden");
679-
return;
680-
}
681-
682684
if (state.type === "allTime" || state.type === "daily") {
683685
if (!state.userData || !state.count) {
684686
$(".page.pageLeaderboards .bigUser").addClass("hidden");
@@ -839,7 +841,7 @@ function fillUser(): void {
839841
<div class="sub">${formatted.time}</div>
840842
</div>
841843
<div class="stat wide">
842-
<div class="title">date</div>
844+
<div class="title">last activity</div>
843845
<div class="value">${format(
844846
userData.lastActivityTimestamp,
845847
"dd MMM yyyy HH:mm"

packages/contracts/src/leaderboards.ts

+20-8
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,14 @@ export type GetLeaderboardRankResponse = z.infer<
6262

6363
//--------------------------------------------------------------------------
6464

65-
export const GetDailyLeaderboardQuerySchema = LanguageAndModeQuerySchema.merge(
66-
PaginationQuerySchema
67-
).extend({
65+
export const DailyLeaderboardQuerySchema = LanguageAndModeQuerySchema.extend({
6866
daysBefore: z.literal(1).optional(),
6967
});
68+
export type DailyLeaderboardQuery = z.infer<typeof DailyLeaderboardQuerySchema>;
69+
70+
export const GetDailyLeaderboardQuerySchema = DailyLeaderboardQuerySchema.merge(
71+
PaginationQuerySchema
72+
);
7073
export type GetDailyLeaderboardQuery = z.infer<
7174
typeof GetDailyLeaderboardQuerySchema
7275
>;
@@ -82,10 +85,8 @@ export type GetDailyLeaderboardResponse = z.infer<
8285

8386
//--------------------------------------------------------------------------
8487

85-
export const GetDailyLeaderboardRankQuerySchema =
86-
LanguageAndModeQuerySchema.merge(PaginationQuerySchema).extend({
87-
daysBefore: z.literal(1).optional(),
88-
});
88+
export const GetDailyLeaderboardRankQuerySchema = DailyLeaderboardQuerySchema;
89+
8990
export type GetDailyLeaderboardRankQuery = z.infer<
9091
typeof GetDailyLeaderboardRankQuerySchema
9192
>;
@@ -98,9 +99,13 @@ export type GetLeaderboardDailyRankResponse = z.infer<
9899

99100
//--------------------------------------------------------------------------
100101

101-
export const GetWeeklyXpLeaderboardQuerySchema = PaginationQuerySchema.extend({
102+
const WeeklyXpLeaderboardQuerySchema = z.object({
102103
weeksBefore: z.literal(1).optional(),
103104
});
105+
106+
export const GetWeeklyXpLeaderboardQuerySchema =
107+
WeeklyXpLeaderboardQuerySchema.merge(PaginationQuerySchema);
108+
104109
export type GetWeeklyXpLeaderboardQuery = z.infer<
105110
typeof GetWeeklyXpLeaderboardQuerySchema
106111
>;
@@ -115,6 +120,12 @@ export type GetWeeklyXpLeaderboardResponse = z.infer<
115120

116121
//--------------------------------------------------------------------------
117122

123+
export const GetWeeklyXpLeaderboardRankQuerySchema =
124+
WeeklyXpLeaderboardQuerySchema;
125+
export type GetWeeklyXpLeaderboardRankQuery = z.infer<
126+
typeof GetWeeklyXpLeaderboardRankQuerySchema
127+
>;
128+
118129
export const GetWeeklyXpLeaderboardRankResponseSchema =
119130
responseWithNullableData(XpLeaderboardEntrySchema);
120131
export type GetWeeklyXpLeaderboardRankResponse = z.infer<
@@ -210,6 +221,7 @@ export const leaderboardsContract = c.router(
210221
"Get the rank of the current user on the weekly xp leaderboard",
211222
method: "GET",
212223
path: "/xp/weekly/rank",
224+
query: GetWeeklyXpLeaderboardRankQuerySchema.strict(),
213225
responses: {
214226
200: GetWeeklyXpLeaderboardRankResponseSchema,
215227
},

0 commit comments

Comments
 (0)