Skip to content

Commit 78dd31e

Browse files
committed
refactor: remove lodash omit from backend (@fehmer)
1 parent 05c1b9e commit 78dd31e

File tree

8 files changed

+105
-39
lines changed

8 files changed

+105
-39
lines changed

backend/__tests__/utils/misc.spec.ts

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, it, expect, afterAll, vi } from "vitest";
22
import _ from "lodash";
3-
import * as misc from "../../src/utils/misc";
3+
import * as Misc from "../../src/utils/misc";
44
import { ObjectId } from "mongodb";
55

66
describe("Misc Utils", () => {
@@ -32,7 +32,7 @@ describe("Misc Utils", () => {
3232
_.each(testCases, (testCase, pattern) => {
3333
const { cases, expected } = testCase;
3434
_.each(cases, (caseValue, index) => {
35-
expect(misc.matchesAPattern(caseValue, pattern)).toBe(expected[index]);
35+
expect(Misc.matchesAPattern(caseValue, pattern)).toBe(expected[index]);
3636
});
3737
});
3838
});
@@ -80,7 +80,7 @@ describe("Misc Utils", () => {
8080
];
8181

8282
_.each(testCases, ({ wpm, acc, timestamp, expectedScore }) => {
83-
expect(misc.kogascore(wpm, acc, timestamp)).toBe(expectedScore);
83+
expect(Misc.kogascore(wpm, acc, timestamp)).toBe(expectedScore);
8484
});
8585
});
8686

@@ -109,7 +109,7 @@ describe("Misc Utils", () => {
109109
];
110110

111111
_.each(testCases, ({ input, expected }) => {
112-
expect(misc.identity(input)).toEqual(expected);
112+
expect(Misc.identity(input)).toEqual(expected);
113113
});
114114
});
115115

@@ -178,7 +178,7 @@ describe("Misc Utils", () => {
178178
];
179179

180180
_.each(testCases, ({ obj, expected }) => {
181-
expect(misc.flattenObjectDeep(obj)).toEqual(expected);
181+
expect(Misc.flattenObjectDeep(obj)).toEqual(expected);
182182
});
183183
});
184184

@@ -215,7 +215,7 @@ describe("Misc Utils", () => {
215215
];
216216

217217
testCases.forEach(({ input, expected }) => {
218-
expect(misc.sanitizeString(input)).toEqual(expected);
218+
expect(Misc.sanitizeString(input)).toEqual(expected);
219219
});
220220
});
221221

@@ -284,7 +284,7 @@ describe("Misc Utils", () => {
284284
];
285285

286286
testCases.forEach(({ input, output }) => {
287-
expect(misc.getOrdinalNumberString(input)).toEqual(output);
287+
expect(Misc.getOrdinalNumberString(input)).toEqual(output);
288288
});
289289
});
290290
it("formatSeconds", () => {
@@ -298,45 +298,45 @@ describe("Misc Utils", () => {
298298
expected: "1.08 minutes",
299299
},
300300
{
301-
seconds: misc.HOUR_IN_SECONDS,
301+
seconds: Misc.HOUR_IN_SECONDS,
302302
expected: "1 hour",
303303
},
304304
{
305-
seconds: misc.DAY_IN_SECONDS,
305+
seconds: Misc.DAY_IN_SECONDS,
306306
expected: "1 day",
307307
},
308308
{
309-
seconds: misc.WEEK_IN_SECONDS,
309+
seconds: Misc.WEEK_IN_SECONDS,
310310
expected: "1 week",
311311
},
312312
{
313-
seconds: misc.YEAR_IN_SECONDS,
313+
seconds: Misc.YEAR_IN_SECONDS,
314314
expected: "1 year",
315315
},
316316
{
317-
seconds: 2 * misc.YEAR_IN_SECONDS,
317+
seconds: 2 * Misc.YEAR_IN_SECONDS,
318318
expected: "2 years",
319319
},
320320
{
321-
seconds: 4 * misc.YEAR_IN_SECONDS,
321+
seconds: 4 * Misc.YEAR_IN_SECONDS,
322322
expected: "4 years",
323323
},
324324
{
325-
seconds: 3 * misc.WEEK_IN_SECONDS,
325+
seconds: 3 * Misc.WEEK_IN_SECONDS,
326326
expected: "3 weeks",
327327
},
328328
{
329-
seconds: misc.MONTH_IN_SECONDS * 4,
329+
seconds: Misc.MONTH_IN_SECONDS * 4,
330330
expected: "4 months",
331331
},
332332
{
333-
seconds: misc.MONTH_IN_SECONDS * 11,
333+
seconds: Misc.MONTH_IN_SECONDS * 11,
334334
expected: "11 months",
335335
},
336336
];
337337

338338
testCases.forEach(({ seconds, expected }) => {
339-
expect(misc.formatSeconds(seconds)).toBe(expected);
339+
expect(Misc.formatSeconds(seconds)).toBe(expected);
340340
});
341341
});
342342

@@ -347,14 +347,14 @@ describe("Misc Utils", () => {
347347
test: "test",
348348
number: 1,
349349
};
350-
expect(misc.replaceObjectId(fromDatabase)).toStrictEqual({
350+
expect(Misc.replaceObjectId(fromDatabase)).toStrictEqual({
351351
_id: fromDatabase._id.toHexString(),
352352
test: "test",
353353
number: 1,
354354
});
355355
});
356356
it("ignores null values", () => {
357-
expect(misc.replaceObjectId(null)).toBeNull();
357+
expect(Misc.replaceObjectId(null)).toBeNull();
358358
});
359359
});
360360

@@ -371,7 +371,7 @@ describe("Misc Utils", () => {
371371
number: 2,
372372
};
373373
expect(
374-
misc.replaceObjectIds([fromDatabase, fromDatabase2])
374+
Misc.replaceObjectIds([fromDatabase, fromDatabase2])
375375
).toStrictEqual([
376376
{
377377
_id: fromDatabase._id.toHexString(),
@@ -386,7 +386,56 @@ describe("Misc Utils", () => {
386386
]);
387387
});
388388
it("handles undefined", () => {
389-
expect(misc.replaceObjectIds(undefined as any)).toBeUndefined();
389+
expect(Misc.replaceObjectIds(undefined as any)).toBeUndefined();
390+
});
391+
});
392+
393+
describe("omit()", () => {
394+
it("should omit a single key", () => {
395+
const input = { a: 1, b: 2, c: 3 };
396+
const result = Misc.omit(input, "b");
397+
expect(result).toEqual({ a: 1, c: 3 });
398+
});
399+
400+
it("should omit multiple keys", () => {
401+
const input = { a: 1, b: 2, c: 3, d: 4 };
402+
const result = Misc.omit(input, "a", "d");
403+
expect(result).toEqual({ b: 2, c: 3 });
404+
});
405+
406+
it("should return the same object if no keys are omitted", () => {
407+
const input = { x: 1, y: 2 };
408+
const result = Misc.omit(input);
409+
expect(result).toEqual({ x: 1, y: 2 });
410+
});
411+
412+
it("should not mutate the original object", () => {
413+
const input = { foo: "bar", baz: "qux" };
414+
const copy = { ...input };
415+
Misc.omit(input, "baz");
416+
expect(input).toEqual(copy);
417+
});
418+
419+
it("should ignore keys that do not exist", () => {
420+
const input = { a: 1, b: 2 };
421+
const result = Misc.omit(input, "c" as any); // allow a non-existing key
422+
expect(result).toEqual({ a: 1, b: 2 });
423+
});
424+
425+
it("should work with different value types", () => {
426+
const input = {
427+
str: "hello",
428+
num: 123,
429+
bool: true,
430+
obj: { x: 1 },
431+
arr: [1, 2, 3],
432+
};
433+
const result = Misc.omit(input, "bool", "arr");
434+
expect(result).toEqual({
435+
str: "hello",
436+
num: 123,
437+
obj: { x: 1 },
438+
});
390439
});
391440
});
392441
});

backend/src/api/controllers/ape-key.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import _ from "lodash";
21
import { randomBytes } from "crypto";
32
import { hash } from "bcrypt";
43
import * as ApeKeysDAL from "../../dal/ape-keys";
54
import MonkeyError from "../../utils/error";
65
import { MonkeyResponse } from "../../utils/monkey-response";
7-
import { base64UrlEncode } from "../../utils/misc";
6+
import { base64UrlEncode, omit } from "../../utils/misc";
87
import { ObjectId } from "mongodb";
98

109
import {
@@ -18,7 +17,7 @@ import { ApeKey } from "@monkeytype/schemas/ape-keys";
1817
import { MonkeyRequest } from "../types";
1918

2019
function cleanApeKey(apeKey: ApeKeysDAL.DBApeKey): ApeKey {
21-
return _.omit(apeKey, "hash", "_id", "uid", "useCount");
20+
return omit(apeKey, "hash", "_id", "uid", "useCount") as ApeKey;
2221
}
2322

2423
export async function getApeKeys(
@@ -27,7 +26,9 @@ export async function getApeKeys(
2726
const { uid } = req.ctx.decodedToken;
2827

2928
const apeKeys = await ApeKeysDAL.getApeKeys(uid);
30-
const cleanedKeys = _(apeKeys).keyBy("_id").mapValues(cleanApeKey).value();
29+
const cleanedKeys: Record<string, ApeKey> = Object.fromEntries(
30+
apeKeys.map((item) => [String(item._id), cleanApeKey(item)])
31+
);
3132

3233
return new MonkeyResponse("ApeKeys retrieved", cleanedKeys);
3334
}

backend/src/api/controllers/leaderboard.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import _ from "lodash";
21
import { MonkeyResponse } from "../../utils/monkey-response";
32
import * as LeaderboardsDAL from "../../dal/leaderboards";
43
import MonkeyError from "../../utils/error";
@@ -26,6 +25,7 @@ import {
2625
MILLISECONDS_IN_DAY,
2726
} from "@monkeytype/util/date-and-time";
2827
import { MonkeyRequest } from "../types";
28+
import { omit } from "../../utils/misc";
2929

3030
export async function getLeaderboard(
3131
req: MonkeyRequest<GetLeaderboardQuery>
@@ -57,7 +57,7 @@ export async function getLeaderboard(
5757
}
5858

5959
const count = await LeaderboardsDAL.getCount(mode, mode2, language);
60-
const normalizedLeaderboard = leaderboard.map((it) => _.omit(it, ["_id"]));
60+
const normalizedLeaderboard = leaderboard.map((it) => omit(it, "_id"));
6161

6262
return new MonkeyResponse("Leaderboard retrieved", {
6363
count,

backend/src/api/controllers/user.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import * as DiscordUtils from "../../utils/discord";
99
import {
1010
buildAgentLog,
1111
getFrontendUrl,
12+
omit,
1213
replaceObjectId,
1314
replaceObjectIds,
1415
sanitizeString,
@@ -511,7 +512,8 @@ type RelevantUserInfo = Omit<
511512
>;
512513

513514
function getRelevantUserInfo(user: UserDAL.DBUser): RelevantUserInfo {
514-
return _.omit(user, [
515+
return omit(
516+
user,
515517
"bananas",
516518
"lbPersonalBests",
517519
"inbox",
@@ -522,8 +524,8 @@ function getRelevantUserInfo(user: UserDAL.DBUser): RelevantUserInfo {
522524
"note",
523525
"ips",
524526
"testActivity",
525-
"suspicious",
526-
]) as RelevantUserInfo;
527+
"suspicious"
528+
) as RelevantUserInfo;
527529
}
528530

529531
export async function getUser(req: MonkeyRequest): Promise<GetUserResponse> {

backend/src/dal/leaderboards.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,18 @@ export async function get(
4444
const limit = pageSize;
4545

4646
try {
47-
const preset = await getCollection({ language, mode, mode2 })
47+
const result = await getCollection({ language, mode, mode2 })
4848
.find()
4949
.sort({ rank: 1 })
5050
.skip(skip)
5151
.limit(limit)
5252
.toArray();
5353

5454
if (!premiumFeaturesEnabled) {
55-
return preset.map((it) => omit(it, "isPremium"));
55+
return result.map((it) => omit(it, "isPremium"));
5656
}
5757

58-
return preset;
58+
return result;
5959
} catch (e) {
6060
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
6161
if (e.error === 175) {

backend/src/dal/user.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,13 @@ export type DBUser = Omit<
5454
inbox?: MonkeyMail[];
5555
ips?: string[];
5656
canReport?: boolean;
57+
nameHistory?: string[];
5758
lastNameChange?: number;
5859
canManageApeKeys?: boolean;
5960
bananas?: number;
6061
testActivity?: CountByYearAndDay;
6162
suspicious?: boolean;
63+
note?: string;
6264
};
6365

6466
const SECONDS_PER_HOUR = 3600;

backend/src/init/configuration.ts

Lines changed: 4 additions & 4 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 } from "../utils/misc";
5+
import { identity, 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";
@@ -81,9 +81,9 @@ export async function getLiveConfiguration(): Promise<Configuration> {
8181
const liveConfiguration = await configurationCollection.findOne();
8282

8383
if (liveConfiguration) {
84-
const baseConfiguration = _.cloneDeep(BASE_CONFIGURATION);
84+
const baseConfiguration = structuredClone(BASE_CONFIGURATION);
8585

86-
const liveConfigurationWithoutId = _.omit(
86+
const liveConfigurationWithoutId = omit(
8787
liveConfiguration,
8888
"_id"
8989
) as Configuration;
@@ -129,7 +129,7 @@ export async function patchConfiguration(
129129
configurationUpdates: PartialConfiguration
130130
): Promise<boolean> {
131131
try {
132-
const currentConfiguration = _.cloneDeep(configuration);
132+
const currentConfiguration = structuredClone(configuration);
133133
mergeConfigurations(currentConfiguration, configurationUpdates);
134134

135135
await db

backend/src/utils/misc.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { MILLISECONDS_IN_DAY } from "@monkeytype/util/date-and-time";
22
import { roundTo2 } from "@monkeytype/util/numbers";
3-
import _, { omit } from "lodash";
3+
import _ from "lodash";
44
import uaparser from "ua-parser-js";
55
import { MonkeyRequest } from "../api/types";
66
import { ObjectId } from "mongodb";
@@ -220,8 +220,8 @@ export function replaceObjectId<T extends { _id: ObjectId }>(
220220
return null;
221221
}
222222
const result = {
223+
...data,
223224
_id: data._id.toString(),
224-
...omit(data, "_id"),
225225
} as T & { _id: string };
226226
return result;
227227
}
@@ -240,3 +240,15 @@ export function replaceObjectIds<T extends { _id: ObjectId }>(
240240
export type WithObjectId<T extends { _id: string }> = Omit<T, "_id"> & {
241241
_id: ObjectId;
242242
};
243+
244+
export function omit<T extends object, K extends keyof T>(
245+
obj: T,
246+
...keys: K[]
247+
): Omit<T, K> {
248+
const result = { ...obj };
249+
for (const key of keys) {
250+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
251+
delete result[key];
252+
}
253+
return result;
254+
}

0 commit comments

Comments
 (0)