mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-03-03 18:25:02 +08:00
fix: past leaderboard not fetching the users rank (@fehmer) (#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 <jack@monkeytype.com>
This commit is contained in:
parent
0b840d2b6b
commit
8a41ccee97
4 changed files with 113 additions and 28 deletions
|
@ -1067,6 +1067,8 @@ describe("Loaderboard Controller", () => {
|
|||
beforeEach(async () => {
|
||||
getXpWeeklyLeaderboardMock.mockReset();
|
||||
await weeklyLeaderboardEnabled(true);
|
||||
vi.useFakeTimers();
|
||||
vi.setSystemTime(1722606812000);
|
||||
});
|
||||
|
||||
it("fails withouth authentication", async () => {
|
||||
|
@ -1109,6 +1111,47 @@ describe("Loaderboard Controller", () => {
|
|||
|
||||
expect(getRankMock).toHaveBeenCalledWith(uid, lbConf);
|
||||
});
|
||||
|
||||
it("should get for last week", async () => {
|
||||
//GIVEN
|
||||
const lbConf = (await configuration).leaderboards.weeklyXp;
|
||||
|
||||
const resultData: XpLeaderboardEntry = {
|
||||
totalXp: 100,
|
||||
rank: 1,
|
||||
timeTypedSeconds: 100,
|
||||
uid: "user1",
|
||||
name: "user1",
|
||||
discordId: "discordId",
|
||||
discordAvatar: "discordAvatar",
|
||||
lastActivityTimestamp: 1000,
|
||||
};
|
||||
const getRankMock = vi.fn();
|
||||
getRankMock.mockResolvedValue(resultData);
|
||||
getXpWeeklyLeaderboardMock.mockReturnValue({
|
||||
getRank: getRankMock,
|
||||
} as any);
|
||||
|
||||
//WHEN
|
||||
const { body } = await mockApp
|
||||
.get("/leaderboards/xp/weekly/rank")
|
||||
.query({ weeksBefore: 1 })
|
||||
.set("authorization", `Uid ${uid}`)
|
||||
.expect(200);
|
||||
|
||||
//THEN
|
||||
expect(body).toEqual({
|
||||
message: "Weekly xp leaderboard rank retrieved",
|
||||
data: resultData,
|
||||
});
|
||||
|
||||
expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(
|
||||
lbConf,
|
||||
1721606400000
|
||||
);
|
||||
|
||||
expect(getRankMock).toHaveBeenCalledWith(uid, lbConf);
|
||||
});
|
||||
it("fails if daily leaderboards are disabled", async () => {
|
||||
await weeklyLeaderboardEnabled(false);
|
||||
|
||||
|
@ -1122,6 +1165,36 @@ describe("Loaderboard Controller", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("fails for weeksBefore not one", async () => {
|
||||
const { body } = await mockApp
|
||||
.get("/leaderboards/xp/weekly/rank")
|
||||
.set("authorization", `Uid ${uid}`)
|
||||
.query({
|
||||
weeksBefore: 2,
|
||||
})
|
||||
.expect(422);
|
||||
|
||||
expect(body).toEqual({
|
||||
message: "Invalid query schema",
|
||||
validationErrors: ['"weeksBefore" Invalid literal value, expected 1'],
|
||||
});
|
||||
});
|
||||
|
||||
it("fails for unknown query", async () => {
|
||||
const { body } = await mockApp
|
||||
.get("/leaderboards/xp/weekly/rank")
|
||||
.set("authorization", `Uid ${uid}`)
|
||||
.query({
|
||||
extra: "value",
|
||||
})
|
||||
.expect(422);
|
||||
|
||||
expect(body).toEqual({
|
||||
message: "Invalid query schema",
|
||||
validationErrors: ["Unrecognized key(s) in object: 'extra'"],
|
||||
});
|
||||
});
|
||||
|
||||
it("fails while leaderboard is missing", async () => {
|
||||
//GIVEN
|
||||
getXpWeeklyLeaderboardMock.mockReturnValue(null);
|
||||
|
@ -1130,11 +1203,6 @@ describe("Loaderboard Controller", () => {
|
|||
const { body } = await mockApp
|
||||
.get("/leaderboards/xp/weekly/rank")
|
||||
.set("authorization", `Uid ${uid}`)
|
||||
.query({
|
||||
language: "english",
|
||||
mode: "time",
|
||||
mode2: "60",
|
||||
})
|
||||
.expect(404);
|
||||
|
||||
expect(body.message).toEqual("XP leaderboard for this week not found.");
|
||||
|
|
|
@ -5,6 +5,7 @@ import MonkeyError from "../../utils/error";
|
|||
import * as DailyLeaderboards from "../../utils/daily-leaderboards";
|
||||
import * as WeeklyXpLeaderboard from "../../services/weekly-xp-leaderboard";
|
||||
import {
|
||||
DailyLeaderboardQuery,
|
||||
GetDailyLeaderboardQuery,
|
||||
GetDailyLeaderboardRankQuery,
|
||||
GetDailyLeaderboardResponse,
|
||||
|
@ -14,6 +15,7 @@ import {
|
|||
GetLeaderboardRankResponse,
|
||||
GetLeaderboardResponse as GetLeaderboardResponse,
|
||||
GetWeeklyXpLeaderboardQuery,
|
||||
GetWeeklyXpLeaderboardRankQuery,
|
||||
GetWeeklyXpLeaderboardRankResponse,
|
||||
GetWeeklyXpLeaderboardResponse,
|
||||
} from "@monkeytype/contracts/leaderboards";
|
||||
|
@ -73,7 +75,7 @@ export async function getRankFromLeaderboard(
|
|||
}
|
||||
|
||||
function getDailyLeaderboardWithError(
|
||||
{ language, mode, mode2, daysBefore }: GetDailyLeaderboardRankQuery,
|
||||
{ language, mode, mode2, daysBefore }: DailyLeaderboardQuery,
|
||||
config: Configuration["dailyLeaderboards"]
|
||||
): DailyLeaderboards.DailyLeaderboard {
|
||||
const customTimestamp =
|
||||
|
@ -187,12 +189,13 @@ export async function getWeeklyXpLeaderboardResults(
|
|||
}
|
||||
|
||||
export async function getWeeklyXpLeaderboardRank(
|
||||
req: MonkeyRequest
|
||||
req: MonkeyRequest<GetWeeklyXpLeaderboardRankQuery>
|
||||
): Promise<GetWeeklyXpLeaderboardRankResponse> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
|
||||
const weeklyXpLeaderboard = getWeeklyXpLeaderboardWithError(
|
||||
req.ctx.configuration.leaderboards.weeklyXp
|
||||
req.ctx.configuration.leaderboards.weeklyXp,
|
||||
req.query.weeksBefore
|
||||
);
|
||||
const rankEntry = await weeklyXpLeaderboard.getRank(
|
||||
uid,
|
||||
|
|
|
@ -246,6 +246,7 @@ async function requestData(update = false): Promise<void> {
|
|||
requests.rank = Ape.leaderboards.getDailyRank({
|
||||
query: {
|
||||
...baseQuery,
|
||||
daysBefore: state.yesterday ? 1 : undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -324,7 +325,11 @@ async function requestData(update = false): Promise<void> {
|
|||
});
|
||||
|
||||
if (isAuthenticated() && state.userData === null) {
|
||||
requests.rank = Ape.leaderboards.getWeeklyXpRank();
|
||||
requests.rank = Ape.leaderboards.getWeeklyXpRank({
|
||||
query: {
|
||||
weeksBefore: state.lastWeek ? 1 : undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const [dataResponse, rankResponse] = await Promise.all([
|
||||
|
@ -653,8 +658,14 @@ function fillUser(): void {
|
|||
}
|
||||
|
||||
if (isAuthenticated() && state.type === "daily" && state.userData === null) {
|
||||
let str = `Not qualified`;
|
||||
|
||||
if (!state.yesterday) {
|
||||
str += ` (min speed required: ${state.minWpm} wpm)`;
|
||||
}
|
||||
|
||||
$(".page.pageLeaderboards .bigUser").html(
|
||||
`<div class="warning">Not qualified (min speed required: ${state.minWpm} wpm)</div>`
|
||||
`<div class="warning">${str}</div>`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
@ -670,15 +681,6 @@ function fillUser(): void {
|
|||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(state.type === "weekly" && state.lastWeek) ||
|
||||
(state.type === "daily" && state.yesterday)
|
||||
) {
|
||||
$(".page.pageLeaderboards .bigUser").addClass("hidden");
|
||||
$(".page.pageLeaderboards .tableAndUser > .divider").removeClass("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.type === "allTime" || state.type === "daily") {
|
||||
if (!state.userData || !state.count) {
|
||||
$(".page.pageLeaderboards .bigUser").addClass("hidden");
|
||||
|
@ -839,7 +841,7 @@ function fillUser(): void {
|
|||
<div class="sub">${formatted.time}</div>
|
||||
</div>
|
||||
<div class="stat wide">
|
||||
<div class="title">date</div>
|
||||
<div class="title">last activity</div>
|
||||
<div class="value">${format(
|
||||
userData.lastActivityTimestamp,
|
||||
"dd MMM yyyy HH:mm"
|
||||
|
|
|
@ -62,11 +62,14 @@ export type GetLeaderboardRankResponse = z.infer<
|
|||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
export const GetDailyLeaderboardQuerySchema = LanguageAndModeQuerySchema.merge(
|
||||
PaginationQuerySchema
|
||||
).extend({
|
||||
export const DailyLeaderboardQuerySchema = LanguageAndModeQuerySchema.extend({
|
||||
daysBefore: z.literal(1).optional(),
|
||||
});
|
||||
export type DailyLeaderboardQuery = z.infer<typeof DailyLeaderboardQuerySchema>;
|
||||
|
||||
export const GetDailyLeaderboardQuerySchema = DailyLeaderboardQuerySchema.merge(
|
||||
PaginationQuerySchema
|
||||
);
|
||||
export type GetDailyLeaderboardQuery = z.infer<
|
||||
typeof GetDailyLeaderboardQuerySchema
|
||||
>;
|
||||
|
@ -82,10 +85,8 @@ export type GetDailyLeaderboardResponse = z.infer<
|
|||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
export const GetDailyLeaderboardRankQuerySchema =
|
||||
LanguageAndModeQuerySchema.merge(PaginationQuerySchema).extend({
|
||||
daysBefore: z.literal(1).optional(),
|
||||
});
|
||||
export const GetDailyLeaderboardRankQuerySchema = DailyLeaderboardQuerySchema;
|
||||
|
||||
export type GetDailyLeaderboardRankQuery = z.infer<
|
||||
typeof GetDailyLeaderboardRankQuerySchema
|
||||
>;
|
||||
|
@ -98,9 +99,13 @@ export type GetLeaderboardDailyRankResponse = z.infer<
|
|||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
export const GetWeeklyXpLeaderboardQuerySchema = PaginationQuerySchema.extend({
|
||||
const WeeklyXpLeaderboardQuerySchema = z.object({
|
||||
weeksBefore: z.literal(1).optional(),
|
||||
});
|
||||
|
||||
export const GetWeeklyXpLeaderboardQuerySchema =
|
||||
WeeklyXpLeaderboardQuerySchema.merge(PaginationQuerySchema);
|
||||
|
||||
export type GetWeeklyXpLeaderboardQuery = z.infer<
|
||||
typeof GetWeeklyXpLeaderboardQuerySchema
|
||||
>;
|
||||
|
@ -115,6 +120,12 @@ export type GetWeeklyXpLeaderboardResponse = z.infer<
|
|||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
export const GetWeeklyXpLeaderboardRankQuerySchema =
|
||||
WeeklyXpLeaderboardQuerySchema;
|
||||
export type GetWeeklyXpLeaderboardRankQuery = z.infer<
|
||||
typeof GetWeeklyXpLeaderboardRankQuerySchema
|
||||
>;
|
||||
|
||||
export const GetWeeklyXpLeaderboardRankResponseSchema =
|
||||
responseWithNullableData(XpLeaderboardEntrySchema);
|
||||
export type GetWeeklyXpLeaderboardRankResponse = z.infer<
|
||||
|
@ -210,6 +221,7 @@ export const leaderboardsContract = c.router(
|
|||
"Get the rank of the current user on the weekly xp leaderboard",
|
||||
method: "GET",
|
||||
path: "/xp/weekly/rank",
|
||||
query: GetWeeklyXpLeaderboardRankQuerySchema.strict(),
|
||||
responses: {
|
||||
200: GetWeeklyXpLeaderboardRankResponseSchema,
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue