Skip to content
Merged
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
19 changes: 10 additions & 9 deletions backend/src/dal/leaderboards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,16 @@ export async function getCount(
cachedCounts.set(key, count);
return count;
} else {
return (
await aggregateWithAcceptedConnections(
{
collectionName: getCollectionName({ language, mode, mode2 }),
uid,
},
[{ $project: { _id: true } }],
)
).length;
const result = await aggregateWithAcceptedConnections<{
total: number;
}>(
{
collectionName: getCollectionName({ language, mode, mode2 }),
uid,
},
[{ $count: "total" }],
);
return result[0]?.total ?? 0;
}
}
}
Expand Down
1 change: 0 additions & 1 deletion backend/src/dal/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ export async function addImportantLog(
message: string | Record<string, unknown>,
uid = "",
): Promise<void> {
console.log("log", event, message, uid);
await insertIntoDb(event, message, uid, true);
}

Expand Down
15 changes: 6 additions & 9 deletions backend/src/dal/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,17 +493,14 @@ export async function checkIfPb(

if (!pb.isPb) return false;

await getUsersCollection().updateOne(
{ uid },
{ $set: { personalBests: pb.personalBests } },
);

const setFields: Record<string, unknown> = {
personalBests: pb.personalBests,
};
if (pb.lbPersonalBests) {
await getUsersCollection().updateOne(
{ uid },
{ $set: { lbPersonalBests: pb.lbPersonalBests } },
);
setFields["lbPersonalBests"] = pb.lbPersonalBests;
}

await getUsersCollection().updateOne({ uid }, { $set: setFields });
return true;
}

Expand Down
1 change: 1 addition & 0 deletions frontend/__tests__/controllers/preset-controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ describe("PresetController", () => {

dbGetSnapshotMock.mockReturnValue({} as any);
configApplyMock.mockResolvedValue();
tagsClearMock.mockResolvedValue();
});

it("should apply for full preset", async () => {
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"zod-urlsearchparams": "0.0.16"
},
"devDependencies": {
"@actions/core": "3.0.1",
"@eslint/json": "1.2.0",
"@fortawesome/fontawesome-free": "5.15.4",
"@monkeytype/oxlint-config": "workspace:*",
Expand Down
25 changes: 23 additions & 2 deletions frontend/scripts/check-assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ 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";
import * as ghCore from "@actions/core";

const stepSummary = ghCore.summary;

class Problems<K extends string, T extends string> {
private type: string;
Expand Down Expand Up @@ -51,10 +54,21 @@ class Problems<K extends string, T extends string> {
return Object.keys(this.problems).length !== 0;
}
public toString(): string {
stepSummary.addHeading(`${this.type} Checks`, 2);
if (!this.hasError()) {
stepSummary.addRaw("✅ all checks passed").addEOL();
return `${this.type} are all \u001b[32mvalid\u001b[0m`;
}

Object.entries(this.problems).forEach(([key, problems]) => {
let label: string = this.labels[key as T] ?? `${key}`;
stepSummary
.addRaw(`❌ ${label}`)
.addEOL()
.addList(problems as string[])
.addEOL();
});

return (
`${this.type} are \u001b[31minvalid\u001b[0m\n` +
Object.entries(this.problems)
Expand Down Expand Up @@ -513,8 +527,15 @@ async function main(): Promise<void> {
}

if (tasks.size > 0) {
await Promise.all([...tasks].map(async (validator) => validator()));
return;
const results = await Promise.allSettled(
[...tasks].map(async (validator) => validator()),
);

await stepSummary.write();

if (results.find((it) => it.status === "rejected") !== undefined) {
throw new Error("One or more checks failed.");
}
}
}
void main();
7 changes: 6 additions & 1 deletion frontend/src/styles/media-queries.scss
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ body {

@media (prefers-reduced-motion) {
body:not(.ignore-reduced-motion)
*:not(.fa-spin, .animate-\[loader\], .preloader) {
*:not(
.fa-spin,
.animate-\[loader\],
.preloader,
.typed-effect-fade .word.typed
) {
animation: none !important;
transition: none !important;

Expand Down
54 changes: 54 additions & 0 deletions frontend/src/ts/ape/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { GetUserResponse } from "@monkeytype/contracts/users";
import Ape from ".";
import { createEffectOn } from "../hooks/effects";
import { isAuthenticated } from "../states/core";
import { SnapshotInitError } from "../utils/snapshot-init-error";

type CacheType = GetUserResponse["data"];

let fetchPromise: Promise<void> | null = null;
let cache: CacheType | undefined = undefined;

export async function fetchUserFromApi(): Promise<CacheType | undefined> {
await sync();
return cache;
}

async function sync(): Promise<void> {
if (!isAuthenticated()) {
return;
}

if (cache !== undefined) return;

fetchPromise ??= (async () => {
const response = await Ape.users.get();

if (response.status !== 200) {
throw new SnapshotInitError(
`${response.body.message} (user)`,
response.status,
);
}

cache = response.body.data;
})();

try {
await fetchPromise;
} finally {
fetchPromise = null;
}
}

function reset(): void {
cache = undefined;
fetchPromise = null;
}

// clear cache + reset promise on logout
createEffectOn(isAuthenticated, (isAuthenticated) => {
if (!isAuthenticated) {
reset();
}
});
6 changes: 5 additions & 1 deletion frontend/src/ts/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from "firebase/auth";

import Ape from "./ape";
import { waitForPresetsReady } from "./collections/presets";
import { updateFromServer as updateConfigFromServer } from "./config/remote";
import * as DB from "./db";
import { authEvent } from "./events/auth";
Expand All @@ -30,6 +31,7 @@ import {
showSuccessNotification,
} from "./states/notifications";
import { createErrorMessage } from "./utils/error";
import { SnapshotInitError } from "./utils/snapshot-init-error";

export type AuthResult =
| {
Expand Down Expand Up @@ -64,6 +66,8 @@ async function getDataAndInit(): Promise<boolean> {
try {
console.log("getting account data");
const snapshot = await DB.initSnapshot();
//TODO: always load presets for now, remove when __nonReactive is removed from presets collection
await waitForPresetsReady();

if (snapshot === false) {
throw new Error(
Expand All @@ -77,7 +81,7 @@ async function getDataAndInit(): Promise<boolean> {
return true;
} catch (error) {
console.error(error);
if (error instanceof DB.SnapshotInitError) {
if (error instanceof SnapshotInitError) {
if (error.responseCode === 429) {
showNoticeNotification(
"Doing so will save you bandwidth, make the next test be ready faster and will not sign you out (which could mean your new personal best would not save to your account).",
Expand Down
34 changes: 16 additions & 18 deletions frontend/src/ts/collections/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { queryClient } from "../queries";
import { baseKey } from "../queries/utils/keys";
import { ConfigGroupName } from "@monkeytype/schemas/configs";
import { tempId } from "./utils/misc";
import { isAuthenticated } from "../states/core";
import { replaceUnderscoresWithSpaces } from "../utils/strings";

export type PresetItem = Preset;

Expand All @@ -29,13 +31,21 @@ export function usePresetsLiveQuery() {
const presetsCollection = createCollection(
queryCollectionOptions({
staleTime: Infinity,
startSync: true,
queryKey: queryKeys.root(),

queryClient,
getKey: (it) => it._id,
queryFn: async () => {
return [] as PresetItem[];
if (!isAuthenticated()) return [];
const response = await Ape.presets.get();

if (response.status !== 200) {
throw new Error("Error fetching presets:" + response.body.message);
}

return response.body.data.map((it) => ({
...it,
name: replaceUnderscoresWithSpaces(it.name),
}));
},
}),
);
Expand Down Expand Up @@ -149,6 +159,9 @@ const actions = {
};

// --- Public API ---
export async function waitForPresetsReady(): Promise<void> {
await presetsCollection.stateWhenReady();
}

function getPresets(): PresetItem[] {
return [...presetsCollection.values()].sort((a, b) =>
Expand All @@ -160,21 +173,6 @@ function getPreset(id: string): PresetItem | undefined {
return presetsCollection.get(id);
}

export function fillPresetsCollection(presets: Preset[]): void {
const presetItems = presets.map((preset) => ({
_id: preset._id,
name: preset.name.replace(/_/g, " "),
config: preset.config,
settingGroups: preset.settingGroups,
}));

presetsCollection.utils.writeBatch(() => {
presetItems.forEach((item) => {
presetsCollection.utils.writeInsert(item);
});
});
}

export async function addPreset(
params: ActionType["addPreset"],
): Promise<void> {
Expand Down
20 changes: 8 additions & 12 deletions frontend/src/ts/collections/result-filter-presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
replaceUnderscoresWithSpaces,
} from "../utils/strings";
import { tempId } from "./utils/misc";
import { fetchUserFromApi } from "../ape/user";

const queryKeys = {
root: () => [...baseKey("resultFilterPresets", { isUserSpecific: true })],
Expand All @@ -27,8 +28,13 @@ const resultFilterPresetsCollection = createCollection(
queryClient,
getKey: (it) => it._id,
queryFn: async () => {
//return emtpy array. We load the user with the snapshot and fill the collection from there
return [] as ResultFilters[];
const userData = await fetchUserFromApi();
if (userData === undefined) return [];

return (userData.resultFilterPresets ?? []).map((it) => ({
...it,
name: replaceUnderscoresWithSpaces(it.name),
}));
},
}),
);
Expand Down Expand Up @@ -118,13 +124,3 @@ export async function deleteResultFilterPreset(
const transaction = actions.deleteResultFilterPreset(params);
await transaction.isPersisted.promise;
}

export function fillResultFilterPresetsCollection(
presets: ResultFilters[],
): void {
resultFilterPresetsCollection.utils.writeBatch(() => {
presets
.map((it) => ({ ...it, name: replaceUnderscoresWithSpaces(it.name) }))
.forEach((it) => resultFilterPresetsCollection.utils.writeInsert(it));
});
}
29 changes: 10 additions & 19 deletions frontend/src/ts/collections/tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import { Difficulty } from "@monkeytype/schemas/configs";
import { Language } from "@monkeytype/schemas/languages";
import { tempId } from "./utils/misc";
import { fetchUserFromApi } from "../ape/user";

export type TagItem = UserTag & { active: boolean };

Expand All @@ -31,13 +32,19 @@ const queryKeys = {
const tagsCollection = createCollection(
queryCollectionOptions({
staleTime: Infinity,
startSync: true,
queryKey: queryKeys.root(),

queryClient,
getKey: (it) => it._id,
queryFn: async () => {
return [] as TagItem[];
const activeIds = activeTagsLS.get();
const userData = await fetchUserFromApi();
if (userData === undefined) return [];

return (userData.tags ?? []).map((tag) => ({
...tag,
name: tag.name.replace(/_/g, " "),
active: activeIds.includes(tag._id),
}));
},
}),
);
Expand Down Expand Up @@ -205,22 +212,6 @@ function getActiveTags(): TagItem[] {
return getTags().filter((tag) => tag.active);
}

export function fillTagsCollection(userTags: UserTag[]): void {
const activeIds = activeTagsLS.get();

const tagItems = userTags.map((tag) => ({
...tag,
name: tag.name.replace(/_/g, " "),
active: activeIds.includes(tag._id),
}));

tagsCollection.utils.writeBatch(() => {
tagItems.forEach((item) => {
tagsCollection.utils.writeInsert(item);
});
});
}

// --- Active state ---

const activeTagsLS = new LocalStorageWithSchema({
Expand Down
Loading
Loading