Skip to content

Commit a505578

Browse files
committed
remove lodash from config, result, user, config, redis
1 parent b203e21 commit a505578

File tree

13 files changed

+218
-108
lines changed

13 files changed

+218
-108
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { ObjectId } from "mongodb";
2+
import { describe, expect, it } from "vitest";
3+
import * as ConfigDal from "../../../src/dal/config";
4+
5+
const getConfigCollection = ConfigDal.__testing.getConfigCollection;
6+
7+
describe("ConfigDal", () => {
8+
describe("saveConfig", () => {
9+
it("should save and update user configuration correctly", async () => {
10+
//GIVEN
11+
const uid = new ObjectId().toString();
12+
await getConfigCollection().insertOne({
13+
uid,
14+
config: {
15+
ads: "on",
16+
time: 60,
17+
quickTab: true, //legacy value
18+
},
19+
} as any);
20+
21+
//WHEN
22+
await ConfigDal.saveConfig(uid, {
23+
ads: "on",
24+
difficulty: "normal",
25+
} as any);
26+
27+
//WHEN
28+
await ConfigDal.saveConfig(uid, { ads: "off" });
29+
30+
//THEN
31+
const savedConfig = (await ConfigDal.getConfig(
32+
uid
33+
)) as ConfigDal.DBConfig;
34+
35+
expect(savedConfig.config.ads).toBe("off");
36+
expect(savedConfig.config.time).toBe(60);
37+
38+
//should remove legacy values
39+
expect((savedConfig.config as any)["quickTab"]).toBeUndefined();
40+
});
41+
});
42+
});

backend/__tests__/__integration__/dal/result.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ async function createDummyData(
5050
tags: [],
5151
consistency: 100,
5252
keyConsistency: 100,
53-
chartData: { wpm: [], raw: [], err: [] },
53+
chartData: { wpm: [], burst: [], err: [] },
5454
uid,
5555
keySpacingStats: { average: 0, sd: 0 },
5656
keyDurationStats: { average: 0, sd: 0 },

backend/__tests__/__integration__/dal/user.spec.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1180,7 +1180,6 @@ describe("UserDal", () => {
11801180
discordId: "discordId",
11811181
discordAvatar: "discordAvatar",
11821182
});
1183-
11841183
//when
11851184
await UserDAL.linkDiscord(uid, "newId", "newAvatar");
11861185

@@ -1189,6 +1188,21 @@ describe("UserDal", () => {
11891188
expect(read.discordId).toEqual("newId");
11901189
expect(read.discordAvatar).toEqual("newAvatar");
11911190
});
1191+
it("should update without avatar", async () => {
1192+
//given
1193+
const { uid } = await UserTestData.createUser({
1194+
discordId: "discordId",
1195+
discordAvatar: "discordAvatar",
1196+
});
1197+
1198+
//when
1199+
await UserDAL.linkDiscord(uid, "newId");
1200+
1201+
//then
1202+
const read = await UserDAL.getUser(uid, "read");
1203+
expect(read.discordId).toEqual("newId");
1204+
expect(read.discordAvatar).toEqual("discordAvatar");
1205+
});
11921206
});
11931207
describe("unlinkDiscord", () => {
11941208
it("throws for nonexisting user", async () => {

backend/__tests__/utils/misc.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,4 +453,46 @@ describe("Misc Utils", () => {
453453
});
454454
});
455455
});
456+
457+
describe("isPlainObject", () => {
458+
it("should return true for plain objects", () => {
459+
expect(Misc.isPlainObject({})).toBe(true);
460+
expect(Misc.isPlainObject({ a: 1, b: 2 })).toBe(true);
461+
expect(Misc.isPlainObject(Object.create(Object.prototype))).toBe(true);
462+
});
463+
464+
it("should return false for arrays", () => {
465+
expect(Misc.isPlainObject([])).toBe(false);
466+
expect(Misc.isPlainObject([1, 2, 3])).toBe(false);
467+
});
468+
469+
it("should return false for null", () => {
470+
expect(Misc.isPlainObject(null)).toBe(false);
471+
});
472+
473+
it("should return false for primitives", () => {
474+
expect(Misc.isPlainObject(123)).toBe(false);
475+
expect(Misc.isPlainObject("string")).toBe(false);
476+
expect(Misc.isPlainObject(true)).toBe(false);
477+
expect(Misc.isPlainObject(undefined)).toBe(false);
478+
expect(Misc.isPlainObject(Symbol("sym"))).toBe(false);
479+
});
480+
481+
it("should return false for objects with different prototypes", () => {
482+
// oxlint-disable-next-line no-extraneous-class
483+
class MyClass {}
484+
expect(Misc.isPlainObject(new MyClass())).toBe(false);
485+
expect(Misc.isPlainObject(Object.create(null))).toBe(false);
486+
expect(Misc.isPlainObject(new Date())).toBe(false);
487+
expect(Misc.isPlainObject(new Map())).toBe(false);
488+
expect(Misc.isPlainObject(new Set())).toBe(false);
489+
});
490+
491+
it("should return false for functions", () => {
492+
// oxlint-disable-next-line no-empty-function
493+
expect(Misc.isPlainObject(function () {})).toBe(false);
494+
// oxlint-disable-next-line no-empty-function
495+
expect(Misc.isPlainObject(() => {})).toBe(false);
496+
});
497+
});
456498
});

backend/src/api/controllers/user.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ import {
8989
import { MILLISECONDS_IN_DAY } from "@monkeytype/util/date-and-time";
9090
import { MonkeyRequest } from "../types";
9191
import { tryCatch } from "@monkeytype/util/trycatch";
92+
import { PersonalBest } from "@monkeytype/schemas/shared";
9293

9394
async function verifyCaptcha(captcha: string): Promise<void> {
9495
const { data: verified, error } = await tryCatch(verify(captcha));
@@ -930,19 +931,31 @@ export async function getProfile(
930931
lbOptOut,
931932
} = user;
932933

933-
const validTimePbs = {
934-
"15": personalBests?.time?.["15"],
935-
"30": personalBests?.time?.["30"],
936-
"60": personalBests?.time?.["60"],
937-
"120": personalBests?.time?.["120"],
938-
};
939-
const validWordsPbs = {
940-
"10": personalBests?.words?.["10"],
941-
"25": personalBests?.words?.["25"],
942-
"50": personalBests?.words?.["50"],
943-
"100": personalBests?.words?.["100"],
934+
const extractValid = (
935+
src: Record<string, PersonalBest[]>,
936+
validKeys: string[]
937+
): Record<string, PersonalBest[]> => {
938+
return validKeys.reduce((obj, key) => {
939+
if (src?.[key] !== undefined) {
940+
obj[key] = src[key];
941+
}
942+
return obj;
943+
}, {});
944944
};
945945

946+
const validTimePbs = extractValid(personalBests.time, [
947+
"15",
948+
"30",
949+
"60",
950+
"120",
951+
]);
952+
const validWordsPbs = extractValid(personalBests.words, [
953+
"10",
954+
"25",
955+
"50",
956+
"100",
957+
]);
958+
946959
const typingStats = {
947960
completedTests,
948961
startedTests,

backend/src/dal/config.ts

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,47 @@
11
import { Collection, ObjectId, UpdateResult } from "mongodb";
22
import * as db from "../init/db";
3-
import _ from "lodash";
43
import { Config, PartialConfig } from "@monkeytype/schemas/configs";
54

6-
const configLegacyProperties = [
7-
"swapEscAndTab",
8-
"quickTab",
9-
"chartStyle",
10-
"chartAverage10",
11-
"chartAverage100",
12-
"alwaysShowCPM",
13-
"resultFilters",
14-
"chartAccuracy",
15-
"liveSpeed",
16-
"extraTestColor",
17-
"savedLayout",
18-
"showTimerBar",
19-
"showDiscordDot",
20-
"maxConfidence",
21-
"capsLockBackspace",
22-
"showAvg",
23-
"enableAds",
24-
];
25-
26-
type DBConfig = {
5+
const configLegacyProperties: Record<string, ""> = {
6+
"config.swapEscAndTab": "",
7+
"config.quickTab": "",
8+
"config.chartStyle": "",
9+
"config.chartAverage10": "",
10+
"config.chartAverage100": "",
11+
"config.alwaysShowCPM": "",
12+
"config.resultFilters": "",
13+
"config.chartAccuracy": "",
14+
"config.liveSpeed": "",
15+
"config.extraTestColor": "",
16+
"config.savedLayout": "",
17+
"config.showTimerBar": "",
18+
"config.showDiscordDot": "",
19+
"config.maxConfidence": "",
20+
"config.capsLockBackspace": "",
21+
"config.showAvg": "",
22+
"config.enableAds": "",
23+
};
24+
25+
export type DBConfig = {
2726
_id: ObjectId;
2827
uid: string;
2928
config: PartialConfig;
3029
};
3130

32-
// Export for use in tests
33-
export const getConfigCollection = (): Collection<DBConfig> =>
31+
const getConfigCollection = (): Collection<DBConfig> =>
3432
db.collection<DBConfig>("configs");
3533

3634
export async function saveConfig(
3735
uid: string,
3836
config: Partial<Config>
3937
): Promise<UpdateResult> {
40-
const configChanges = _.mapKeys(config, (_value, key) => `config.${key}`);
41-
42-
const unset = _.fromPairs(
43-
_.map(configLegacyProperties, (key) => [`config.${key}`, ""])
44-
) as Record<string, "">;
38+
const configChanges = Object.fromEntries(
39+
Object.entries(config).map(([key, value]) => [`config.${key}`, value])
40+
);
4541

4642
return await getConfigCollection().updateOne(
4743
{ uid },
48-
{ $set: configChanges, $unset: unset },
44+
{ $set: configChanges, $unset: configLegacyProperties },
4945
{ upsert: true }
5046
);
5147
}
@@ -58,3 +54,7 @@ export async function getConfig(uid: string): Promise<DBConfig | null> {
5854
export async function deleteConfig(uid: string): Promise<void> {
5955
await getConfigCollection().deleteOne({ uid });
6056
}
57+
58+
export const __testing = {
59+
getConfigCollection,
60+
};

backend/src/dal/result.ts

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import _ from "lodash";
21
import {
32
Collection,
43
type DeleteResult,
4+
Filter,
55
ObjectId,
66
type UpdateResult,
77
} from "mongodb";
@@ -111,24 +111,21 @@ export async function getResults(
111111
opts?: GetResultsOpts
112112
): Promise<DBResult[]> {
113113
const { onOrAfterTimestamp, offset, limit } = opts ?? {};
114+
115+
const condition: Filter<DBResult> = { uid };
116+
if (onOrAfterTimestamp !== undefined && !isNaN(onOrAfterTimestamp)) {
117+
condition.timestamp = { $gte: onOrAfterTimestamp };
118+
}
119+
114120
let query = getResultCollection()
115-
.find(
116-
{
117-
uid,
118-
...(!_.isNil(onOrAfterTimestamp) &&
119-
!_.isNaN(onOrAfterTimestamp) && {
120-
timestamp: { $gte: onOrAfterTimestamp },
121-
}),
121+
.find(condition, {
122+
projection: {
123+
chartData: 0,
124+
keySpacingStats: 0,
125+
keyDurationStats: 0,
126+
name: 0,
122127
},
123-
{
124-
projection: {
125-
chartData: 0,
126-
keySpacingStats: 0,
127-
keyDurationStats: 0,
128-
name: 0,
129-
},
130-
}
131-
)
128+
})
132129
.sort({ timestamp: -1 });
133130

134131
if (limit !== undefined) {

backend/src/dal/user.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
type UpdateFilter,
1010
type Filter,
1111
} from "mongodb";
12-
import { flattenObjectDeep, WithObjectId } from "../utils/misc";
12+
import { flattenObjectDeep, isPlainObject, WithObjectId } from "../utils/misc";
1313
import { getCachedConfiguration } from "../init/configuration";
1414
import { getDayOfYear } from "date-fns";
1515
import { UTCDate } from "@date-fns/utc";
@@ -601,10 +601,10 @@ export async function linkDiscord(
601601
discordId: string,
602602
discordAvatar?: string
603603
): Promise<void> {
604-
const updates: Partial<DBUser> = _.pickBy(
605-
{ discordId, discordAvatar },
606-
_.identity
607-
);
604+
const updates: Partial<DBUser> = { discordId };
605+
if (discordAvatar !== undefined && discordAvatar !== null)
606+
updates.discordAvatar = discordAvatar;
607+
608608
await updateUser({ uid }, { $set: updates }, { stack: "link discord" });
609609
}
610610

@@ -907,8 +907,7 @@ export async function updateProfile(
907907
): Promise<void> {
908908
const profileUpdates = _.omitBy(
909909
flattenObjectDeep(profileDetailUpdates, "profileDetails"),
910-
(value) =>
911-
value === undefined || (_.isPlainObject(value) && _.isEmpty(value))
910+
(value) => value === undefined || (isPlainObject(value) && _.isEmpty(value))
912911
);
913912

914913
const updates = {

backend/src/init/configuration.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import _ from "lodash";
22
import * as db from "./db";
33
import { ObjectId } from "mongodb";
44
import Logger from "../utils/logger";
5-
import { identity, omit } from "../utils/misc";
5+
import { identity, isPlainObject, omit } from "../utils/misc";
66
import { BASE_CONFIGURATION } from "../constants/base-configuration";
77
import { Configuration } from "@monkeytype/schemas/configuration";
88
import { addLog } from "../dal/logs";
@@ -26,22 +26,18 @@ function mergeConfigurations(
2626
baseConfiguration: Configuration,
2727
liveConfiguration: PartialConfiguration
2828
): void {
29-
if (
30-
!_.isPlainObject(baseConfiguration) ||
31-
!_.isPlainObject(liveConfiguration)
32-
) {
29+
if (!isPlainObject(baseConfiguration) || !isPlainObject(liveConfiguration)) {
3330
return;
3431
}
3532

3633
function merge(base: object, source: object): void {
3734
const commonKeys = _.intersection(_.keys(base), _.keys(source));
38-
3935
commonKeys.forEach((key) => {
4036
const baseValue = base[key] as object;
4137
const sourceValue = source[key] as object;
4238

43-
const isBaseValueObject = _.isPlainObject(baseValue);
44-
const isSourceValueObject = _.isPlainObject(sourceValue);
39+
const isBaseValueObject = isPlainObject(baseValue);
40+
const isSourceValueObject = isPlainObject(sourceValue);
4541

4642
if (isBaseValueObject && isSourceValueObject) {
4743
merge(baseValue, sourceValue);

0 commit comments

Comments
 (0)