Update PersonalBests Interface (#4158) Ferotiq

* update `PersonalBests` interface

* removed properties

* setting to skeleton if needed

* consistency and add skeleton by default

* consistency

* populate personalBests on user get

* simplification + consistency + small fixes

* protecting against partial object

* removed duplicate

* not optional property

* ensuring personal bests structure while creating user snapshot

* checking pb structure for tags

* missing skeleton

* required personal bests property

* simplify

---------

Co-authored-by: Miodec <jack@monkeytype.com>
This commit is contained in:
Evan 2023-05-03 04:39:32 -05:00 committed by GitHub
parent 3f7f77841b
commit 9408322503
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 192 additions and 154 deletions

View file

@ -224,8 +224,8 @@ describe("UserDal", () => {
time: { 20: [mockPersonalBest] },
words: {},
quote: {},
custom: {},
zen: {},
custom: {},
},
},
}

View file

@ -331,6 +331,14 @@ export async function getUser(
}
}
userInfo.personalBests ??= {
time: {},
words: {},
quote: {},
zen: {},
custom: {},
};
const agentLog = buildAgentLog(req);
Logger.logToDb("user_data_requested", agentLog, uid);

View file

@ -24,6 +24,13 @@ export async function addUser(
email,
uid,
addedAt: Date.now(),
personalBests: {
time: {},
words: {},
quote: {},
zen: {},
custom: {},
},
};
const result = await getUsersCollection().updateOne(
@ -47,11 +54,11 @@ export async function resetUser(uid: string): Promise<void> {
{
$set: {
personalBests: {
custom: {},
quote: {},
time: {},
words: {},
quote: {},
zen: {},
custom: {},
},
lbPersonalBests: {
time: {},
@ -119,11 +126,11 @@ export async function clearPb(uid: string): Promise<void> {
{
$set: {
personalBests: {
custom: {},
quote: {},
time: {},
words: {},
quote: {},
zen: {},
custom: {},
},
lbPersonalBests: {
time: {},
@ -265,14 +272,27 @@ export async function addTag(
name: string
): Promise<MonkeyTypes.UserTag> {
const _id = new ObjectId();
await getUsersCollection().updateOne(
{ uid },
{ $push: { tags: { _id, name } } }
);
return {
const toPush = {
_id,
name,
personalBests: {
time: {},
words: {},
quote: {},
zen: {},
custom: {},
},
};
await getUsersCollection().updateOne(
{ uid },
{
$push: {
tags: toPush,
},
}
);
return toPush;
}
export async function getTags(uid: string): Promise<MonkeyTypes.UserTag[]> {
@ -332,7 +352,17 @@ export async function removeTagPb(uid: string, _id: string): Promise<void> {
uid: uid,
"tags._id": new ObjectId(_id),
},
{ $set: { "tags.$.personalBests": {} } }
{
$set: {
"tags.$.personalBests": {
time: {},
words: {},
quote: {},
zen: {},
custom: {},
},
},
}
);
}
@ -371,20 +401,18 @@ export async function checkIfPb(
return false;
}
let lbPb = user.lbPersonalBests;
if (!lbPb) lbPb = { time: {} };
user.personalBests ??= {
time: {},
custom: {},
quote: {},
words: {},
zen: {},
};
user.lbPersonalBests ??= {
time: {},
};
const pb = checkAndUpdatePb(
user.personalBests ?? {
time: {},
custom: {},
quote: {},
words: {},
zen: {},
},
lbPb,
result
);
const pb = checkAndUpdatePb(user.personalBests, user.lbPersonalBests, result);
if (!pb.isPb) return false;
@ -430,15 +458,15 @@ export async function checkIfTagPb(
const ret: string[] = [];
for (const tag of tagsToCheck) {
const tagPbs: MonkeyTypes.PersonalBests = tag.personalBests ?? {
tag.personalBests ??= {
time: {},
words: {},
quote: {},
zen: {},
custom: {},
quote: {},
};
const tagpb = checkAndUpdatePb(tagPbs, undefined, result);
const tagpb = checkAndUpdatePb(tag.personalBests, undefined, result);
if (tagpb.isPb) {
ret.push(tag._id.toHexString());
await getUsersCollection().updateOne(
@ -459,10 +487,10 @@ export async function resetPb(uid: string): Promise<void> {
$set: {
personalBests: {
time: {},
custom: {},
quote: {},
words: {},
quote: {},
zen: {},
custom: {},
},
},
}

View file

@ -170,7 +170,7 @@ declare namespace MonkeyTypes {
lbPersonalBests?: LbPersonalBests;
name: string;
customThemes?: CustomTheme[];
personalBests?: PersonalBests;
personalBests: PersonalBests;
quoteRatings?: UserQuoteRatings;
startedTests?: number;
tags?: UserTag[];
@ -284,7 +284,7 @@ declare namespace MonkeyTypes {
interface UserTag {
_id: ObjectId;
name: string;
personalBests?: PersonalBests;
personalBests: PersonalBests;
}
interface LeaderboardEntry {
@ -352,17 +352,11 @@ declare namespace MonkeyTypes {
}
interface PersonalBests {
time: {
[key: number]: PersonalBest[];
};
words: {
[key: number]: PersonalBest[];
};
quote: { [quote: string]: PersonalBest[] };
custom: { custom?: PersonalBest[] };
zen: {
zen?: PersonalBest[];
};
time: Record<number, PersonalBest[]>;
words: Record<number, PersonalBest[]>;
quote: Record<string, PersonalBest[]>;
custom: Partial<Record<"custom", PersonalBest[]>>;
zen: Partial<Record<"zen", PersonalBest[]>>;
}
interface ChartData {

View file

@ -3,9 +3,9 @@ export const defaultSnap: MonkeyTypes.Snapshot = {
personalBests: {
time: {},
words: {},
zen: { zen: [] },
quote: { custom: [] },
custom: { custom: [] },
quote: {},
zen: {},
custom: {},
},
name: "",
customThemes: [],

View file

@ -95,6 +95,18 @@ export async function initSnapshot(): Promise<
snap.name = userData.name;
snap.personalBests = userData.personalBests;
snap.personalBests ??= {
time: {},
words: {},
quote: {},
zen: {},
custom: {},
};
for (const mode of ["time", "words", "quote", "zen", "custom"]) {
snap.personalBests[mode as keyof MonkeyTypes.PersonalBests] ??= {};
}
snap.banned = userData.banned;
snap.lbOptOut = userData.lbOptOut;
snap.verified = userData.verified;
@ -166,6 +178,17 @@ export async function initSnapshot(): Promise<
snap.tags.forEach((tag) => {
tag.display = tag.name.replaceAll("_", " ");
tag.personalBests ??= {
time: {},
words: {},
quote: {},
zen: {},
custom: {},
};
for (const mode of ["time", "words", "quote", "zen", "custom"]) {
tag.personalBests[mode as keyof MonkeyTypes.PersonalBests] ??= {};
}
});
snap.tags = snap.tags?.sort((a, b) => {
@ -577,30 +600,21 @@ export async function saveLocalPB<M extends MonkeyTypes.Mode>(
function cont(): void {
if (!dbSnapshot) return;
let found = false;
if (dbSnapshot.personalBests === undefined) {
dbSnapshot.personalBests = {
time: {},
words: {},
zen: { zen: [] },
quote: { custom: [] },
custom: { custom: [] },
};
}
if (dbSnapshot.personalBests[mode] === undefined) {
if (mode === "zen") {
dbSnapshot.personalBests["zen"] = { zen: [] };
} else {
dbSnapshot.personalBests[mode as Exclude<typeof mode, "zen">] = {
custom: [],
};
}
}
dbSnapshot.personalBests ??= {
time: {},
words: {},
quote: {},
zen: {},
custom: {},
};
if (dbSnapshot.personalBests[mode][mode2] === undefined) {
dbSnapshot.personalBests[mode][mode2] =
[] as unknown as MonkeyTypes.PersonalBests[M][keyof MonkeyTypes.PersonalBests[M]];
}
dbSnapshot.personalBests[mode] ??= {
[mode2]: [],
};
dbSnapshot.personalBests[mode][mode2] ??=
[] as unknown as MonkeyTypes.PersonalBests[M][keyof MonkeyTypes.PersonalBests[M]];
(
dbSnapshot.personalBests[mode][
@ -630,15 +644,15 @@ export async function saveLocalPB<M extends MonkeyTypes.Mode>(
mode2
] as unknown as MonkeyTypes.PersonalBest[]
).push({
language: language,
difficulty: difficulty,
lazyMode: lazyMode,
punctuation: punctuation,
wpm: wpm,
acc: acc,
raw: raw,
language,
difficulty,
lazyMode,
punctuation,
wpm,
acc,
raw,
timestamp: Date.now(),
consistency: consistency,
consistency,
});
}
}
@ -666,34 +680,34 @@ export async function getLocalTagPB<M extends MonkeyTypes.Mode>(
if (filteredtag === undefined) return ret;
if (filteredtag.personalBests === undefined) {
filteredtag.personalBests = {
time: {},
words: {},
zen: { zen: [] },
quote: { custom: [] },
custom: { custom: [] },
};
}
filteredtag.personalBests ??= {
time: {},
words: {},
quote: {},
zen: {},
custom: {},
};
try {
const personalBests = (filteredtag.personalBests[mode][mode2] ??
[]) as MonkeyTypes.PersonalBest[];
filteredtag.personalBests[mode] ??= {
[mode2]: [],
};
personalBests.forEach((pb) => {
if (
filteredtag.personalBests[mode][mode2] ??=
[] as unknown as MonkeyTypes.PersonalBests[M][keyof MonkeyTypes.PersonalBests[M]];
const personalBests = (filteredtag.personalBests[mode][mode2] ??
[]) as MonkeyTypes.PersonalBest[];
ret =
personalBests.find(
(pb) =>
pb.punctuation == punctuation &&
pb.difficulty == difficulty &&
pb.language == language &&
(pb.lazyMode === lazyMode ||
(pb.lazyMode === undefined && lazyMode === false))
) {
ret = pb.wpm;
}
});
} catch (e) {
console.log(e);
}
)?.wpm ?? 0;
return ret;
}
@ -721,22 +735,24 @@ export async function saveLocalTagPB<M extends MonkeyTypes.Mode>(
(t) => t._id === tagId
)[0] as MonkeyTypes.Tag;
if (!filteredtag.personalBests) {
filteredtag.personalBests = {
time: {},
words: {},
zen: { zen: [] },
quote: { custom: [] },
custom: { custom: [] },
};
}
filteredtag.personalBests ??= {
time: {},
words: {},
quote: {},
zen: {},
custom: {},
};
filteredtag.personalBests[mode] ??= {
[mode2]: [],
};
filteredtag.personalBests[mode][mode2] ??=
[] as unknown as MonkeyTypes.PersonalBests[M][keyof MonkeyTypes.PersonalBests[M]];
try {
let found = false;
if (filteredtag.personalBests[mode][mode2] === undefined) {
filteredtag.personalBests[mode][mode2] =
[] as unknown as MonkeyTypes.PersonalBests[M][keyof MonkeyTypes.PersonalBests[M]];
}
(
filteredtag.personalBests[mode][
mode2
@ -765,15 +781,15 @@ export async function saveLocalTagPB<M extends MonkeyTypes.Mode>(
mode2
] as unknown as MonkeyTypes.PersonalBest[]
).push({
language: language,
difficulty: difficulty,
lazyMode: lazyMode,
punctuation: punctuation,
wpm: wpm,
acc: acc,
raw: raw,
language,
difficulty,
lazyMode,
punctuation,
wpm,
acc,
raw,
timestamp: Date.now(),
consistency: consistency,
consistency,
});
}
} catch (e) {
@ -781,17 +797,10 @@ export async function saveLocalTagPB<M extends MonkeyTypes.Mode>(
filteredtag.personalBests = {
time: {},
words: {},
zen: { zen: [] },
quote: { custom: [] },
custom: { custom: [] },
quote: {},
zen: {},
custom: {},
};
if (mode === "zen") {
filteredtag.personalBests["zen"] = { zen: [] };
} else {
filteredtag.personalBests[mode as Exclude<typeof mode, "zen">] = {
custom: [],
};
}
filteredtag.personalBests[mode][mode2] = [
{
language: language,

View file

@ -101,6 +101,13 @@ async function apply(): Promise<void> {
display: propTagName,
name: response.data.name,
_id: response.data._id,
personalBests: {
time: {},
words: {},
quote: {},
zen: {},
custom: {},
},
});
Settings.update();
}
@ -145,9 +152,9 @@ async function apply(): Promise<void> {
tag.personalBests = {
time: {},
words: {},
custom: { custom: [] },
zen: { zen: [] },
quote: { custom: [] },
quote: {},
zen: {},
custom: {},
};
}
});

View file

@ -16,11 +16,9 @@ function update(mode: MonkeyTypes.Mode): void {
const snapshot = DB.getSnapshot();
if (!snapshot) return;
const allmode2 = (
snapshot.personalBests === undefined
? undefined
: snapshot.personalBests[mode]
) as { [quote: string]: PersonalBest[] } | undefined;
const allmode2 = snapshot.personalBests?.[mode] as
| Record<string, PersonalBest[]>
| undefined;
if (allmode2 === undefined) return;

View file

@ -868,9 +868,9 @@ list["clearTagPb"] = new SimplePopup(
tag.personalBests = {
time: {},
words: {},
zen: { zen: [] },
quote: { custom: [] },
custom: { custom: [] },
quote: {},
zen: {},
custom: {},
};
$(
`.pageSettings .section.tags .tagsList .tag[id="${tagId}"] .clearPbButton`
@ -949,9 +949,9 @@ list["resetPersonalBests"] = new SimplePopup(
snapshot.personalBests = {
time: {},
words: {},
zen: { zen: [] },
quote: { custom: [] },
custom: { custom: [] },
quote: {},
zen: {},
custom: {},
};
} catch (e) {
Loader.hide();

View file

@ -295,24 +295,18 @@ declare namespace MonkeyTypes {
}
interface PersonalBests {
time: {
[key: number]: PersonalBest[];
};
words: {
[key: number]: PersonalBest[];
};
quote: { [quote: string]: PersonalBest[] };
custom: { custom: PersonalBest[] };
zen: {
zen: PersonalBest[];
};
time: Record<number, PersonalBest[]>;
words: Record<number, PersonalBest[]>;
quote: Record<string, PersonalBest[]>;
custom: Partial<Record<"custom", PersonalBest[]>>;
zen: Partial<Record<"zen", PersonalBest[]>>;
}
interface Tag {
_id: string;
name: string;
display: string;
personalBests?: PersonalBests;
personalBests: PersonalBests;
active?: boolean;
}
@ -550,7 +544,7 @@ declare namespace MonkeyTypes {
quoteRatings?: QuoteRatings;
results?: Result<Mode>[];
verified?: boolean;
personalBests?: PersonalBests;
personalBests: PersonalBests;
name: string;
customThemes: CustomTheme[];
presets?: Preset[];