Skip to content

Commit fd0959c

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

File tree

10 files changed

+168
-67
lines changed

10 files changed

+168
-67
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/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 & 7 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,10 +26,7 @@ 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

@@ -40,8 +37,8 @@ function mergeConfigurations(
4037
const baseValue = base[key] as object;
4138
const sourceValue = source[key] as object;
4239

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

4643
if (isBaseValueObject && isSourceValueObject) {
4744
merge(baseValue, sourceValue);

backend/src/init/redis.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import fs from "fs";
2-
import _ from "lodash";
32
import { join } from "path";
43
import IORedis, { Redis } from "ioredis";
54
import Logger from "../utils/logger";
@@ -50,10 +49,14 @@ const REDIS_SCRIPTS_DIRECTORY_PATH = join(__dirname, "../../redis-scripts");
5049
function loadScripts(client: IORedis.Redis): void {
5150
const scriptFiles = fs.readdirSync(REDIS_SCRIPTS_DIRECTORY_PATH);
5251

52+
const toCamelCase = (kebab: string): string => {
53+
return kebab.replace(/-([a-z])/g, (_, char: string) => char.toUpperCase());
54+
};
55+
5356
scriptFiles.forEach((scriptFile) => {
5457
const scriptPath = join(REDIS_SCRIPTS_DIRECTORY_PATH, scriptFile);
5558
const scriptSource = fs.readFileSync(scriptPath, "utf-8");
56-
const scriptName = _.camelCase(scriptFile.split(".")[0]);
59+
const scriptName = toCamelCase(scriptFile.split(".")[0] as string);
5760

5861
client.defineCommand(scriptName, {
5962
lua: scriptSource,

backend/src/utils/misc.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { MILLISECONDS_IN_DAY } from "@monkeytype/util/date-and-time";
22
import { roundTo2 } from "@monkeytype/util/numbers";
3-
import _ from "lodash";
43
import uaparser from "ua-parser-js";
54
import { MonkeyRequest } from "../api/types";
65
import { ObjectId } from "mongodb";
@@ -97,7 +96,7 @@ export function flattenObjectDeep(
9796

9897
const newPrefix = prefix.length > 0 ? `${prefix}.${key}` : key;
9998

100-
if (_.isPlainObject(value)) {
99+
if (isPlainObject(value)) {
101100
const flattened = flattenObjectDeep(value as Record<string, unknown>);
102101
const flattenedKeys = Object.keys(flattened);
103102

@@ -252,3 +251,11 @@ export function omit<T extends object, K extends keyof T>(
252251
}
253252
return result;
254253
}
254+
255+
export function isPlainObject(value: unknown): boolean {
256+
return (
257+
value !== null &&
258+
typeof value === "object" &&
259+
Object.getPrototypeOf(value) === Object.prototype
260+
);
261+
}

0 commit comments

Comments
 (0)