Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions backend/__tests__/__integration__/dal/user.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1326,6 +1326,35 @@ describe("UserDal", () => {
expect(read.discordAvatar).toBeUndefined();
});
});

describe("updateChallenge", () => {
it("throws for nonexisting user", async () => {
await expect(async () =>
UserDAL.updateChallenge("unknown", "69"),
).rejects.toThrow("User not found\nStack: update challenge");
});
it("should update", async () => {
//given
vi.useFakeTimers();
const { uid } = await UserTestData.createUser({
challenges: {
"100hours": {},
"250hours": { addedAt: 1 },
},
});

//when
await UserDAL.updateChallenge(uid, "69");

//then
const read = await UserDAL.getUser(uid, "read");
expect(read.challenges).toEqual({
"100hours": {},
"250hours": { addedAt: 1 },
"69": { addedAt: Date.now() },
});
});
});
describe("updateInbox", () => {
it("claims rewards on read", async () => {
//GIVEN
Expand Down
36 changes: 36 additions & 0 deletions backend/__tests__/api/controllers/result.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as ResultDal from "../../../src/dal/result";
import * as UserDal from "../../../src/dal/user";
import * as LogsDal from "../../../src/dal/logs";
import * as PublicDal from "../../../src/dal/public";
import * as GeorgeQueue from "../../../src/queues/george-queue";
import { ObjectId } from "mongodb";
import { mockAuthenticateWithApeKey } from "../../__testData__/auth";
import { enableRateLimitExpects } from "../../__testData__/rate-limit";
Expand Down Expand Up @@ -583,6 +584,11 @@ describe("result controller test", () => {
const userCheckIfPbMock = vi.spyOn(UserDal, "checkIfPb");
const userIncrementXpMock = vi.spyOn(UserDal, "incrementXp");
const userUpdateTypingStatsMock = vi.spyOn(UserDal, "updateTypingStats");
const userUpdateChallengeMock = vi.spyOn(UserDal, "updateChallenge");
const georgeAwardChallengeMock = vi.spyOn(
GeorgeQueue.default,
"awardChallenge",
);
const resultAddMock = vi.spyOn(ResultDal, "addResult");
const publicUpdateStatsMock = vi.spyOn(PublicDal, "updateStats");

Expand All @@ -597,6 +603,8 @@ describe("result controller test", () => {
userCheckIfPbMock,
userIncrementXpMock,
userUpdateTypingStatsMock,
userUpdateChallengeMock,
georgeAwardChallengeMock,
resultAddMock,
publicUpdateStatsMock,
].forEach((it) => it.mockClear());
Expand All @@ -605,6 +613,8 @@ describe("result controller test", () => {
userUpdateStreakMock.mockResolvedValue(0);
userCheckIfTagPbMock.mockResolvedValue([]);
userCheckIfPbMock.mockResolvedValue(true);
userUpdateChallengeMock.mockResolvedValue();
georgeAwardChallengeMock.mockResolvedValue();
resultAddMock.mockResolvedValue({ insertedId });
userIncrementXpMock.mockResolvedValue();
});
Expand Down Expand Up @@ -687,6 +697,32 @@ describe("result controller test", () => {
15.1 + 2 - 5, //duration + incompleteTestSeconds-afk
);
});

it("should add result with challenge", async () => {
//GIVEN
userGetMock.mockClear();
userGetMock.mockResolvedValue({
uid,
name: "bob",
discordId: "discordId",
} as any);

const completedEvent = buildCompletedEvent({
challenge: "69",
});
//WHEN
await mockApp
.post("/results")
.set("Authorization", `Bearer ${uid}`)
.send({
result: completedEvent,
})
.expect(200);

//THEN
expect(userUpdateChallengeMock).toHaveBeenCalledWith(uid, "69");
expect(georgeAwardChallengeMock).toHaveBeenCalledWith("discordId", "69");
});
it("should fail if result saving is disabled", async () => {
//GIVEN
await enableResultsSaving(false);
Expand Down
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
},
"dependencies": {
"@date-fns/utc": "1.2.0",
"@monkeytype/challenges": "workspace:*",
"@monkeytype/contracts": "workspace:*",
"@monkeytype/funbox": "workspace:*",
"@monkeytype/schemas": "workspace:*",
Expand Down
9 changes: 5 additions & 4 deletions backend/src/api/controllers/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,11 +459,12 @@ export async function addResult(
if (
completedEvent.challenge !== null &&
completedEvent.challenge !== undefined &&
AutoRoleList.includes(completedEvent.challenge) &&
user.discordId !== undefined &&
user.discordId !== ""
AutoRoleList.includes(completedEvent.challenge)
) {
void GeorgeQueue.awardChallenge(user.discordId, completedEvent.challenge);
await UserDAL.updateChallenge(uid, completedEvent.challenge);
if (user.discordId !== undefined && user.discordId !== "") {
void GeorgeQueue.awardChallenge(user.discordId, completedEvent.challenge);
}
} else {
delete completedEvent.challenge;
}
Expand Down
43 changes: 5 additions & 38 deletions backend/src/constants/auto-roles.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,5 @@
export default [
"oneHourWarrior",
"doubleDown",
"tripleTrouble",
"quad",
"trueSimp",
"bigramSalad",
"simp",
"antidiseWhat",
"whatsThisWebsiteCalledAgain",
"developd",
"slowAndSteady",
"speedSpacer",
"iveGotThePower",
"accuracyExpert",
"accuracyMaster",
"accuracyGod",
"jolly",
"gottaCatchEmAll",
"rapGod",
"navySeal",
"rollercoaster",
"oneHourMirror",
"chooChoo",
"earfquake",
"simonSez",
"accountant",
"hidden",
"iCanSeeTheFuture",
"whatAreWordsAtThisPoint",
"specials",
"aeiou",
"asciiWarrior",
"iKiNdAlIkEhOwInEfFiCiEnTqWeRtYiS",
"oneNauseousMonkey",
"69",
"englishMaster",
];
import { getChallenges } from "@monkeytype/challenges";

export default getChallenges()
.filter((it) => it.settings?.autoRole)
.map((it) => it.name);
12 changes: 12 additions & 0 deletions backend/src/dal/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { Configuration } from "@monkeytype/schemas/configuration";
import { isToday, isYesterday } from "@monkeytype/util/date-and-time";
import GeorgeQueue from "../queues/george-queue";
import { aggregateWithAcceptedConnections } from "./connections";
import { ChallengeName } from "@monkeytype/schemas/challenges";

export type DBUserTag = WithObjectId<UserTag>;

Expand Down Expand Up @@ -630,6 +631,17 @@ export async function unlinkDiscord(uid: string): Promise<void> {
);
}

export async function updateChallenge(
uid: string,
challengeName: ChallengeName,
): Promise<void> {
await updateUser(
{ uid },
{ $set: { [`challenges.${challengeName}`]: { addedAt: Date.now() } } },
{ stack: "update challenge" },
);
}

export async function incrementBananas(
uid: string,
wpm: number,
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"dependencies": {
"@date-fns/utc": "1.2.0",
"@leonabcd123/modern-caps-lock": "3.1.3",
"@monkeytype/challenges": "workspace:*",
"@monkeytype/contracts": "workspace:*",
"@monkeytype/funbox": "workspace:*",
"@monkeytype/schemas": "workspace:*",
Expand Down
29 changes: 1 addition & 28 deletions frontend/scripts/check-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* Example usage in root or frontend:
* pnpm check-assets (npm run check-assets)
* pnpm check-assets -- -- quotes others (npm run check-assets -- -- quotes others)
* pnpm check-assets -- -- challenges sound -p (npm run check-assets -- -- challenges sound -p)
*/

import * as fs from "fs";
Expand All @@ -18,7 +17,6 @@ import { KnownFontName } from "@monkeytype/schemas/fonts";
import { Fonts } from "../src/ts/constants/fonts";
import { themes, ThemeSchema, ThemesList } from "../src/ts/constants/themes";
import { z } from "zod";
import { ChallengeSchema, Challenge } from "@monkeytype/schemas/challenges";
import { LayoutObject, LayoutObjectSchema } from "@monkeytype/schemas/layouts";
import { QuoteDataSchema, QuoteData } from "@monkeytype/schemas/quotes";
import { clickSoundConfig } from "../src/ts/constants/sounds";
Expand Down Expand Up @@ -99,24 +97,6 @@ function findDuplicates<T>(items: T[]): T[] {
return Array.from(duplicates);
}

async function validateChallenges(): Promise<void> {
const problems = new Problems<"_list.json", never>("Challenges", {});

const challengesData = JSON.parse(
fs.readFileSync("./static/challenges/_list.json", {
encoding: "utf8",
flag: "r",
}),
) as Challenge;
const validationResult = z.array(ChallengeSchema).safeParse(challengesData);
problems.addValidation("_list.json", validationResult);

console.log(problems.toString());
if (problems.hasError()) {
throw new Error("challenges with errors");
}
}

async function validateLayouts(): Promise<void> {
const problems = new Problems<Layout, "_additional">("Layouts", {
_additional:
Expand Down Expand Up @@ -496,17 +476,10 @@ async function main(): Promise<void> {
quotes: [validateQuotes],
languages: [validateLanguages],
layouts: [validateLayouts],
challenges: [validateChallenges],
fonts: [validateFonts],
themes: [validateThemes],
sounds: [validateSounds],
others: [
validateChallenges,
validateLayouts,
validateFonts,
validateThemes,
validateSounds,
],
others: [validateLayouts, validateFonts, validateThemes, validateSounds],
};

// flags
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/ts/auth.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PasswordSchema } from "@monkeytype/schemas/users";
import { typedKeys } from "@monkeytype/util/objects";
import { tryCatch } from "@monkeytype/util/trycatch";
import { FirebaseError } from "firebase/app";
import {
Expand Down Expand Up @@ -41,7 +42,6 @@ import {
} from "./states/notifications";
import { isDevEnvironment } from "./utils/env";
import { createErrorMessage } from "./utils/error";
import { typedKeys } from "./utils/misc";
import { SnapshotInitError } from "./utils/snapshot-init-error";
import { OneOf } from "./utils/types";

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/ts/commandline/commandline-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import { getActivePage, isAuthenticated } from "../states/core";
import { Fonts } from "../constants/fonts";
import { KnownFontName } from "@monkeytype/schemas/fonts";
import * as UI from "../ui";
import { typedKeys } from "../utils/misc";
import { Validation } from "../types/validation";
import { typedKeys } from "@monkeytype/util/objects";

//TODO: remove display property and instead use optionsMetadata from configMetadata
// eventually this file should be fully merged into config metadata, probably under the 'commandline' property
Expand Down
23 changes: 1 addition & 22 deletions frontend/src/ts/commandline/lists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,10 @@ import CustomThemesListCommands from "./lists/custom-themes-list";
import PresetsCommands from "./lists/presets";
import FunboxCommands from "./lists/funbox";
import ThemesCommands from "./lists/themes";
import LoadChallengeCommands, {
update as updateLoadChallengeCommands,
} from "./lists/load-challenge";
import LoadChallengeCommands from "./lists/load-challenge";

import { Config } from "../config/store";
import { setConfig } from "../config/setters";
import * as getErrorMessage from "../utils/error";
import * as JSONData from "../utils/json-data";
import { randomizeTheme } from "../controllers/theme-controller";
import { showModal } from "../states/modals";
import {
Expand All @@ -41,20 +37,6 @@ import {
import { applyConfigFromJson } from "../config/lifecycle";
import { lastEventLog } from "../test/test-state";

const challengesPromise = JSONData.getChallengeList();
challengesPromise
.then((challenges) => {
updateLoadChallengeCommands(challenges);
})
.catch((e: unknown) => {
console.error(
getErrorMessage.createErrorMessage(
e,
"Failed to update challenges commands",
),
);
});

const adsCommands = buildCommands("ads");

export const commands: CommandsSubgroup = {
Expand Down Expand Up @@ -406,8 +388,6 @@ export function doesListExist(listName: string): boolean {
export async function getList(
listName: CommandlineListKey | ConfigKey,
): Promise<CommandsSubgroup> {
await Promise.allSettled([challengesPromise]);

const subGroup = subgroupByConfigKey[listName];
if (subGroup !== undefined) {
return subGroup;
Expand Down Expand Up @@ -451,7 +431,6 @@ export function getTopOfStack(): CommandsSubgroup {

let singleList: CommandsSubgroup | undefined;
export async function getSingleSubgroup(): Promise<CommandsSubgroup> {
await Promise.allSettled([challengesPromise]);
const singleCommands: Command[] = [];
for (const command of commands.list) {
const ret = buildSingleListCommands(command);
Expand Down
35 changes: 15 additions & 20 deletions frontend/src/ts/commandline/lists/load-challenge.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { navigate } from "../../controllers/route-controller";
import { getChallenges } from "@monkeytype/challenges";
import * as ChallengeController from "../../controllers/challenge-controller";
import { navigate } from "../../controllers/route-controller";
import * as TestLogic from "../../test/test-logic";
import { capitalizeFirstLetterOfEachWord } from "../../utils/strings";
import { Command, CommandsSubgroup } from "../types";
import { Challenge } from "@monkeytype/schemas/challenges";

const subgroup: CommandsSubgroup = {
title: "Load challenge...",
list: [],
list: getChallenges()
.filter((it) => it.settings !== undefined)
.map((challenge) => ({
id: `loadChallenge${capitalizeFirstLetterOfEachWord(challenge.name)}`,
display: challenge.display,
exec: async (): Promise<void> => {
await navigate("/");
await ChallengeController.setup(challenge.name);
TestLogic.restart({
nosave: true,
});
},
})),
};

const commands: Command[] = [
Expand All @@ -19,21 +31,4 @@ const commands: Command[] = [
},
];

function update(challenges: Challenge[]): void {
challenges.forEach((challenge) => {
subgroup.list.push({
id: `loadChallenge${capitalizeFirstLetterOfEachWord(challenge.name)}`,
display: challenge.display,
exec: async (): Promise<void> => {
await navigate("/");
await ChallengeController.setup(challenge.name);
TestLogic.restart({
nosave: true,
});
},
});
});
}

export default commands;
export { update };
Loading
Loading