diff --git a/api/dailies.ts b/api/dailies.ts index 7e13fae..5def8e9 100644 --- a/api/dailies.ts +++ b/api/dailies.ts @@ -109,7 +109,7 @@ export interface DailyWebhookOptions { /** * question is the daily question. */ - question: api.LCQuestion; + question: api.Question; /** * season is the season to recap. diff --git a/lib/leaderboard/denokv/denokv_leaderboard_client.ts b/lib/leaderboard/denokv/denokv_leaderboard_client.ts index bc5e559..ffd97c3 100644 --- a/lib/leaderboard/denokv/denokv_leaderboard_client.ts +++ b/lib/leaderboard/denokv/denokv_leaderboard_client.ts @@ -2,7 +2,7 @@ import { DAY, ulid, WEEK } from "lc-dailies/deps.ts"; import type * as api from "lc-dailies/api/mod.ts"; import type { LeaderboardClient } from "lc-dailies/lib/leaderboard/mod.ts"; import { sync } from "lc-dailies/lib/leaderboard/mod.ts"; -import type { LCClient } from "lc-dailies/lib/lc/mod.ts"; +import { LCClient } from "lc-dailies/lib/lc/mod.ts"; /** * DenoKvLeaderboardClient is the client for the leaderboard. @@ -101,27 +101,29 @@ export class DenoKvLeaderboardClient implements LeaderboardClient { } public async register( - discord_user_id: string, - lc_username: string, + playerID: string, + lcUsername: string, ): Promise { - const key: Deno.KvKey = [LeaderboardKvPrefix.PLAYERS, discord_user_id]; + const key: Deno.KvKey = [LeaderboardKvPrefix.PLAYERS, playerID]; const playerResult = await this.kv.get(key); if (playerResult.value) { throw new Error("Player already registered"); } // Verify the user with Leetcode. - const isVerified = await this.lc.verifyUser(lc_username); + const isVerified = await this.lc.verifyUser(lcUsername); if (!isVerified) { throw new Error("Failed to verify user with Leetcode"); } // Register the player. - const player: api.Player = { discord_user_id, lc_username }; const registerResult = await this.kv .atomic() .check(playerResult) - .set(key, player) + .set( + key, + { discord_user_id: playerID, lc_username: lcUsername }, + ) .commit(); if (!registerResult.ok) { throw new Error("Failed to register player"); @@ -130,10 +132,14 @@ export class DenoKvLeaderboardClient implements LeaderboardClient { return { ok: true }; } - /** - * sync syncs the leaderboard with Leetcode. - */ - public async sync(seasonID?: string): Promise { + public async sync( + seasonID?: string, + referenceDate = new Date(), + ): Promise { + // startOfWeekUTC is the start of the season in UTC. + const startOfWeekUTC = getStartOfWeek(this.restartMs, referenceDate); + + // Get the season. let season: api.Season; let seasonResult: Deno.KvEntryMaybe | null = null; if (seasonID) { @@ -144,29 +150,36 @@ export class DenoKvLeaderboardClient implements LeaderboardClient { if (!seasonResult.value) { throw new Error("Season not found"); } + + season = seasonResult.value; } else { seasonResult = await this.getLatestSeasonFromKv(); season = seasonResult?.value ? seasonResult.value - : makeEmptySeason(getStartOfWeek(this.restartMs)); + : makeEmptySeason(startOfWeekUTC); } + // Sync the season. const players = await this.listPlayers(); - season = await sync({ - lcClient: this.lc, - players, - season: season!, - }); + season = await sync({ lcClient: this.lc, players, season }); + // Update the season if it is the latest season. + const isLatestSeason = + new Date(startOfWeekUTC) === new Date(season.start_date); + if (isLatestSeason) { + await this.updateLatestSeason(season, seasonResult); + } + + // Return a sync response. return { season }; } public async getSeason( - season_id: string, + seasonID: string, ): Promise { const seasonResult = await this.kv.get([ LeaderboardKvPrefix.SEASONS, - season_id, + seasonID, ]); return seasonResult.value; } @@ -181,6 +194,11 @@ export class DenoKvLeaderboardClient implements LeaderboardClient { return seasons; } + + public async getLatestSeason(): Promise { + const seasonResult = await this.getLatestSeasonFromKv(); + return seasonResult?.value ?? null; + } } /** diff --git a/lib/leaderboard/denokv/denokv_leaderboard_client_test.ts b/lib/leaderboard/denokv/denokv_leaderboard_client_test.ts index 9962bf5..2f4512d 100644 --- a/lib/leaderboard/denokv/denokv_leaderboard_client_test.ts +++ b/lib/leaderboard/denokv/denokv_leaderboard_client_test.ts @@ -54,13 +54,12 @@ Deno.test("DenoKvLeaderboardClient", async (t) => { }); }); - await t.step("submit", async () => { - const result = await client.submit( - FAKE_DISCORD_USER_ID, - fake_lc.FAKE_RECENT_SUBMISSION_ID, - new Date(fake_lc.FAKE_LC_QUESTION_DATE), + await t.step("sync", async () => { + const syncResponse = await client.sync( + undefined, + FAKE_SEASON_START_DATE, ); - assertEquals(result.ok, true); + assertSeasonsEqual(syncResponse.season, FAKE_SEASON); }); let seasonID: string | undefined; diff --git a/lib/leaderboard/leaderboard_client.ts b/lib/leaderboard/leaderboard_client.ts index cd82eec..7d2ef8f 100644 --- a/lib/leaderboard/leaderboard_client.ts +++ b/lib/leaderboard/leaderboard_client.ts @@ -15,7 +15,7 @@ export interface LeaderboardClient { /** * sync syncs the leaderboard with Leetcode. */ - sync(season_id?: string): Promise; + sync(season_id?: string, reference_date?: Date): Promise; /** * getLatestSeason gets the latest season. diff --git a/lib/leaderboard/sync.ts b/lib/leaderboard/sync.ts index 4561433..b8f5513 100644 --- a/lib/leaderboard/sync.ts +++ b/lib/leaderboard/sync.ts @@ -91,31 +91,29 @@ export async function sync(options: SyncOptions): Promise { // Store the question in the season. options.season.questions[questionName] ??= recentDailyQuestion; - - // Store the earliest submission of the player. - options.season.submissions[playerID] ??= {}; - options.season.submissions[playerID][questionName] = { - id: lcSubmission.id, - date: submissionDate.toUTCString(), - }; } - // Calculate the scores of the players. - options.season.scores = calculateScores( - makeDefaultCalculateScoresOptions( - options.season.players, - options.season.questions, - options.season.submissions, - ), - ); + // Store the earliest submission of the player. + options.season.submissions[playerID] ??= {}; + options.season.submissions[playerID][questionName] = { + id: lcSubmission.id, + date: submissionDate.toUTCString(), + }; - // // Get the submissions of the players in the date range. - // // Calculate the scores of the players. - // // Create a season with the scores, submissions, and questions. - // return makeEmptySeason(startDate.getTime()); + // Store the player in the season if it is not in the season. + options.season.players[playerID] ??= player; } } + // Calculate the scores of the players. + options.season.scores = calculateScores( + makeDefaultCalculateScoresOptions( + options.season.players, + options.season.questions, + options.season.submissions, + ), + ); + return options.season; }