aboutsummaryrefslogtreecommitdiffhomepage
path: root/backend
diff options
context:
space:
mode:
authorJack <[email protected]>2024-09-13 13:18:06 +0200
committerGitHub <[email protected]>2024-09-13 13:18:06 +0200
commit955eeae2a7659d9541fc4a54d1dff8a4ea433c40 (patch)
treece25c239dd63c27fd4c5725291e38b0bdbe383e8 /backend
parente19b3e3e8b5f9fa1fb8a0e8e4c37abb28a63c4e2 (diff)
downloadmonkeytype-955eeae2a7659d9541fc4a54d1dff8a4ea433c40.tar.gz
monkeytype-955eeae2a7659d9541fc4a54d1dff8a4ea433c40.zip
refactor: enable no-unsafe-assignment rule (@miodec) (#5874)
Co-authored-by: Nad Alaba <[email protected]> Co-authored-by: Christian Fehmer <[email protected]> Co-authored-by: Igor Bedesqui <[email protected]> Co-authored-by: amarnathsama <[email protected]>
Diffstat (limited to 'backend')
-rw-r--r--backend/__tests__/utils/pb.spec.ts96
-rw-r--r--backend/src/api/controllers/user.ts2
-rw-r--r--backend/src/api/routes/swagger.ts2
-rw-r--r--backend/src/api/ts-rest-adapter.ts2
-rw-r--r--backend/src/dal/new-quotes.ts26
-rw-r--r--backend/src/dal/result.ts2
-rw-r--r--backend/src/init/configuration.ts6
-rw-r--r--backend/src/init/email-client.ts8
-rw-r--r--backend/src/init/firebase-admin.ts6
-rw-r--r--backend/src/middlewares/auth.ts16
-rw-r--r--backend/src/middlewares/configuration.ts5
-rw-r--r--backend/src/services/weekly-xp-leaderboard.ts12
-rw-r--r--backend/src/utils/daily-leaderboards.ts6
-rw-r--r--backend/src/utils/misc.ts2
-rw-r--r--backend/src/utils/pb.ts39
-rw-r--r--backend/src/workers/email-worker.ts13
-rw-r--r--backend/src/workers/later-worker.ts4
17 files changed, 176 insertions, 71 deletions
diff --git a/backend/__tests__/utils/pb.spec.ts b/backend/__tests__/utils/pb.spec.ts
index 28c9f77f3..79c80693d 100644
--- a/backend/__tests__/utils/pb.spec.ts
+++ b/backend/__tests__/utils/pb.spec.ts
@@ -57,10 +57,15 @@ describe("Pb Utils", () => {
mode2: "15",
} as unknown as Result<Mode>;
- const run = pb.checkAndUpdatePb(userPbs, undefined, result);
+ const run = pb.checkAndUpdatePb(
+ userPbs,
+ {} as MonkeyTypes.LbPersonalBests,
+ result
+ );
expect(run.isPb).toBe(true);
expect(run.personalBests?.["time"]?.["15"]?.[0]).not.toBe(undefined);
+ expect(run.lbPersonalBests).not.toBe({});
});
it("should not override default pb when saving numbers test", () => {
const userPbs: PersonalBests = {
@@ -111,4 +116,93 @@ describe("Pb Utils", () => {
);
});
});
+ describe("updateLeaderboardPersonalBests", () => {
+ const userPbs: PersonalBests = {
+ time: {
+ "15": [
+ {
+ acc: 100,
+ consistency: 100,
+ difficulty: "normal",
+ lazyMode: false,
+ language: "english",
+ numbers: false,
+ punctuation: false,
+ raw: 100,
+ timestamp: 0,
+ wpm: 100,
+ },
+ {
+ acc: 100,
+ consistency: 100,
+ difficulty: "normal",
+ lazyMode: false,
+ language: "spanish",
+ numbers: false,
+ punctuation: false,
+ raw: 100,
+ timestamp: 0,
+ wpm: 100,
+ },
+ ],
+ },
+ words: {},
+ custom: {},
+ quote: {},
+ zen: {},
+ };
+ it("should update leaderboard personal bests if they dont exist or the structure is incomplete", () => {
+ const lbpbstartingvalues = [
+ undefined,
+ {},
+ { time: {} },
+ { time: { "15": {} } },
+ { time: { "15": { english: {} } } },
+ ];
+
+ const result15 = {
+ mode: "time",
+ mode2: "15",
+ } as unknown as Result<Mode>;
+
+ for (const lbPb of lbpbstartingvalues) {
+ const lbPbPb = pb.updateLeaderboardPersonalBests(
+ userPbs,
+ _.cloneDeep(lbPb) as MonkeyTypes.LbPersonalBests,
+ result15
+ );
+
+ expect(lbPbPb).toEqual({
+ time: {
+ "15": {
+ english: {
+ acc: 100,
+ consistency: 100,
+ difficulty: "normal",
+ lazyMode: false,
+ language: "english",
+ numbers: false,
+ punctuation: false,
+ raw: 100,
+ timestamp: 0,
+ wpm: 100,
+ },
+ spanish: {
+ acc: 100,
+ consistency: 100,
+ difficulty: "normal",
+ lazyMode: false,
+ language: "spanish",
+ numbers: false,
+ punctuation: false,
+ raw: 100,
+ timestamp: 0,
+ wpm: 100,
+ },
+ },
+ },
+ });
+ }
+ });
+ });
});
diff --git a/backend/src/api/controllers/user.ts b/backend/src/api/controllers/user.ts
index d4b38a291..33f3e4268 100644
--- a/backend/src/api/controllers/user.ts
+++ b/backend/src/api/controllers/user.ts
@@ -198,7 +198,7 @@ export async function sendVerificationEmail(
JSON.stringify({
decodedTokenEmail: email,
userInfoEmail: userInfo.email,
- stack: e.stack,
+ stack: e.stack as unknown,
}),
userInfo.uid
);
diff --git a/backend/src/api/routes/swagger.ts b/backend/src/api/routes/swagger.ts
index 0bd008129..ba4b90c5a 100644
--- a/backend/src/api/routes/swagger.ts
+++ b/backend/src/api/routes/swagger.ts
@@ -8,7 +8,7 @@ function addSwaggerMiddlewares(app: Application): void {
const openApiSpec = __dirname + "/../../static/api/openapi.json";
let spec = {};
try {
- spec = JSON.parse(readFileSync(openApiSpec, "utf8"));
+ spec = JSON.parse(readFileSync(openApiSpec, "utf8")) as string;
} catch (err) {
Logger.warning(
`Cannot read openApi specification from ${openApiSpec}. Swagger stats will not fully work.`
diff --git a/backend/src/api/ts-rest-adapter.ts b/backend/src/api/ts-rest-adapter.ts
index d86575651..a48313473 100644
--- a/backend/src/api/ts-rest-adapter.ts
+++ b/backend/src/api/ts-rest-adapter.ts
@@ -22,7 +22,7 @@ export function callController<
query: all.query as TQuery,
params: all.params as TParams,
raw: all.req,
- ctx: all.req["ctx"],
+ ctx: all.req["ctx"] as MonkeyTypes.Context,
};
const result = await handler(req);
diff --git a/backend/src/dal/new-quotes.ts b/backend/src/dal/new-quotes.ts
index 357cce367..8814cebf6 100644
--- a/backend/src/dal/new-quotes.ts
+++ b/backend/src/dal/new-quotes.ts
@@ -8,6 +8,20 @@ import MonkeyError from "../utils/error";
import { compareTwoStrings } from "string-similarity";
import { ApproveQuote, Quote } from "@monkeytype/contracts/schemas/quotes";
+type JsonQuote = {
+ text: string;
+ britishText?: string;
+ source: string;
+ length: number;
+ id: number;
+};
+
+type QuoteData = {
+ language: string;
+ quotes: JsonQuote[];
+ groups: [number, number][];
+};
+
const PATH_TO_REPO = "../../../../monkeytype-new-quotes";
let git;
@@ -71,11 +85,11 @@ export async function add(
let similarityScore = -1;
if (existsSync(fileDir)) {
const quoteFile = await readFile(fileDir);
- const quoteFileJSON = JSON.parse(quoteFile.toString());
+ const quoteFileJSON = JSON.parse(quoteFile.toString()) as QuoteData;
quoteFileJSON.quotes.every((old) => {
- if (compareTwoStrings(old.text as string, quote.text) > 0.9) {
+ if (compareTwoStrings(old.text, quote.text) > 0.9) {
duplicateId = old.id;
- similarityScore = compareTwoStrings(old.text as string, quote.text);
+ similarityScore = compareTwoStrings(old.text, quote.text);
return false;
}
return true;
@@ -155,9 +169,9 @@ export async function approve(
await git.pull("upstream", "master");
if (existsSync(fileDir)) {
const quoteFile = await readFile(fileDir);
- const quoteObject = JSON.parse(quoteFile.toString());
+ const quoteObject = JSON.parse(quoteFile.toString()) as QuoteData;
quoteObject.quotes.every((old) => {
- if (compareTwoStrings(old.text as string, quote.text) > 0.8) {
+ if (compareTwoStrings(old.text, quote.text) > 0.8) {
throw new MonkeyError(409, "Duplicate quote");
}
});
@@ -168,7 +182,7 @@ export async function approve(
}
});
quote.id = maxid + 1;
- quoteObject.quotes.push(quote);
+ quoteObject.quotes.push(quote as JsonQuote);
writeFileSync(fileDir, JSON.stringify(quoteObject, null, 2));
message = `Added quote to ${language}.json.`;
} else {
diff --git a/backend/src/dal/result.ts b/backend/src/dal/result.ts
index 57946bf7e..dd1d80364 100644
--- a/backend/src/dal/result.ts
+++ b/backend/src/dal/result.ts
@@ -87,7 +87,7 @@ export async function getLastResult(
export async function getResultByTimestamp(
uid: string,
- timestamp
+ timestamp: number
): Promise<MonkeyTypes.DBResult | null> {
return await getResultCollection().findOne({ uid, timestamp });
}
diff --git a/backend/src/init/configuration.ts b/backend/src/init/configuration.ts
index 662a2f191..6a59d4df1 100644
--- a/backend/src/init/configuration.ts
+++ b/backend/src/init/configuration.ts
@@ -25,14 +25,14 @@ function mergeConfigurations(
const commonKeys = _.intersection(_.keys(base), _.keys(source));
commonKeys.forEach((key) => {
- const baseValue = base[key];
- const sourceValue = source[key];
+ const baseValue = base[key] as object;
+ const sourceValue = source[key] as object;
const isBaseValueObject = _.isPlainObject(baseValue);
const isSourceValueObject = _.isPlainObject(sourceValue);
if (isBaseValueObject && isSourceValueObject) {
- merge(baseValue as object, sourceValue as object);
+ merge(baseValue, sourceValue);
} else if (identity(baseValue) === identity(sourceValue)) {
base[key] = sourceValue;
}
diff --git a/backend/src/init/email-client.ts b/backend/src/init/email-client.ts
index 3ccaa2a2e..3c4874392 100644
--- a/backend/src/init/email-client.ts
+++ b/backend/src/init/email-client.ts
@@ -103,14 +103,16 @@ export async function sendEmail(
html: template,
};
- let result;
+ type Result = { response: string; accepted: string[] };
+
+ let result: Result;
try {
- result = await transporter.sendMail(mailOptions);
+ result = (await transporter.sendMail(mailOptions)) as Result;
} catch (e) {
recordEmail(templateName, "fail");
return {
success: false,
- message: e.message,
+ message: e.message as string,
};
}
diff --git a/backend/src/init/firebase-admin.ts b/backend/src/init/firebase-admin.ts
index 1e1687658..d380eab31 100644
--- a/backend/src/init/firebase-admin.ts
+++ b/backend/src/init/firebase-admin.ts
@@ -29,11 +29,9 @@ export function init(): void {
encoding: "utf8",
flag: "r",
})
- );
+ ) as ServiceAccount;
admin.initializeApp({
- credential: admin.credential.cert(
- serviceAccount as unknown as ServiceAccount
- ),
+ credential: admin.credential.cert(serviceAccount),
});
Logger.success("Firebase app initialized");
}
diff --git a/backend/src/middlewares/auth.ts b/backend/src/middlewares/auth.ts
index 15d1fab99..b2aff30c8 100644
--- a/backend/src/middlewares/auth.ts
+++ b/backend/src/middlewares/auth.ts
@@ -188,33 +188,27 @@ async function authenticateWithBearerToken(
email: decodedToken.email ?? "",
};
} catch (error) {
- const errorCode = error?.errorInfo?.code;
+ const errorCode = error?.errorInfo?.code as string | undefined;
- if (errorCode?.includes("auth/id-token-expired") as boolean | undefined) {
+ if (errorCode?.includes("auth/id-token-expired")) {
throw new MonkeyError(
401,
"Token expired - please login again",
"authenticateWithBearerToken"
);
- } else if (
- errorCode?.includes("auth/id-token-revoked") as boolean | undefined
- ) {
+ } else if (errorCode?.includes("auth/id-token-revoked")) {
throw new MonkeyError(
401,
"Token revoked - please login again",
"authenticateWithBearerToken"
);
- } else if (
- errorCode?.includes("auth/user-not-found") as boolean | undefined
- ) {
+ } else if (errorCode?.includes("auth/user-not-found")) {
throw new MonkeyError(
404,
"User not found",
"authenticateWithBearerToken"
);
- } else if (
- errorCode?.includes("auth/argument-error") as boolean | undefined
- ) {
+ } else if (errorCode?.includes("auth/argument-error")) {
throw new MonkeyError(
400,
"Incorrect Bearer token format",
diff --git a/backend/src/middlewares/configuration.ts b/backend/src/middlewares/configuration.ts
index 256618efa..07e8f233a 100644
--- a/backend/src/middlewares/configuration.ts
+++ b/backend/src/middlewares/configuration.ts
@@ -53,11 +53,12 @@ function getValue(
path: ConfigurationPath
): boolean {
const keys = (path as string).split(".");
- let result = configuration;
+ let result: unknown = configuration;
for (const key of keys) {
- if (result === undefined || result === null)
+ if (result === undefined || result === null) {
throw new MonkeyError(500, `Invalid configuration path: "${path}"`);
+ }
result = result[key];
}
diff --git a/backend/src/services/weekly-xp-leaderboard.ts b/backend/src/services/weekly-xp-leaderboard.ts
index e52b56ad8..2933d7f4a 100644
--- a/backend/src/services/weekly-xp-leaderboard.ts
+++ b/backend/src/services/weekly-xp-leaderboard.ts
@@ -129,14 +129,14 @@ export class WeeklyXpLeaderboard {
this.getThisWeeksXpLeaderboardKeys();
// @ts-expect-error
- const [results, scores]: string[][] = await connection.getResults(
+ const [results, scores] = (await connection.getResults(
2, // How many of the arguments are redis keys (https://redis.io/docs/manual/programmability/lua-api/)
weeklyXpLeaderboardScoresKey,
weeklyXpLeaderboardResultsKey,
minRank,
maxRank,
"true"
- );
+ )) as string[][];
if (results === undefined) {
throw new Error(
@@ -183,13 +183,17 @@ export class WeeklyXpLeaderboard {
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
// @ts-ignore
- const [[, rank], [, totalXp], [, count], [, result]] = await connection
+ const [[, rank], [, totalXp], [, count], [, result]] = (await connection
.multi()
.zrevrank(weeklyXpLeaderboardScoresKey, uid)
.zscore(weeklyXpLeaderboardScoresKey, uid)
.zcard(weeklyXpLeaderboardScoresKey)
.hget(weeklyXpLeaderboardResultsKey, uid)
- .exec();
+ .exec()) as [
+ [null, number | null],
+ [null, string | null],
+ [null, number | null]
+ ];
if (rank === null) {
return null;
diff --git a/backend/src/utils/daily-leaderboards.ts b/backend/src/utils/daily-leaderboards.ts
index b99fed503..e77bfe401 100644
--- a/backend/src/utils/daily-leaderboards.ts
+++ b/backend/src/utils/daily-leaderboards.ts
@@ -121,14 +121,14 @@ export class DailyLeaderboard {
this.getTodaysLeaderboardKeys();
// @ts-expect-error
- const [results]: string[][] = await connection.getResults(
+ const [results] = (await connection.getResults(
2,
leaderboardScoresKey,
leaderboardResultsKey,
minRank,
maxRank,
"false"
- );
+ )) as string[][];
if (results === undefined) {
throw new Error(
@@ -198,7 +198,7 @@ export class DailyLeaderboard {
count: count ?? 0,
rank: rank + 1,
entry: {
- ...JSON.parse(result ?? "null"),
+ ...(JSON.parse(result ?? "null") as LeaderboardEntry),
},
};
}
diff --git a/backend/src/utils/misc.ts b/backend/src/utils/misc.ts
index 985784642..1db2bcafc 100644
--- a/backend/src/utils/misc.ts
+++ b/backend/src/utils/misc.ts
@@ -285,7 +285,7 @@ export function formatSeconds(
}
export function intersect<T>(a: T[], b: T[], removeDuplicates = false): T[] {
- let t;
+ let t: T[];
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
if (b.length > a.length) (t = b), (b = a), (a = t); // indexOf to loop over shorter
const filtered = a.filter(function (e) {
diff --git a/backend/src/utils/pb.ts b/backend/src/utils/pb.ts
index b46f5e0f2..383daa7cb 100644
--- a/backend/src/utils/pb.ts
+++ b/backend/src/utils/pb.ts
@@ -59,7 +59,14 @@ export function checkAndUpdatePb(
}
if (!_.isNil(lbPersonalBests)) {
- updateLeaderboardPersonalBests(userPb, lbPersonalBests, result);
+ const newLbPb = updateLeaderboardPersonalBests(
+ userPb,
+ lbPersonalBests,
+ result
+ );
+ if (newLbPb !== null) {
+ lbPersonalBests = newLbPb;
+ }
}
return {
@@ -165,26 +172,20 @@ function buildPersonalBest(result: Result): PersonalBest {
};
}
-function updateLeaderboardPersonalBests(
+export function updateLeaderboardPersonalBests(
userPersonalBests: PersonalBests,
lbPersonalBests: MonkeyTypes.LbPersonalBests,
result: Result
-): void {
+): MonkeyTypes.LbPersonalBests | null {
if (!shouldUpdateLeaderboardPersonalBests(result)) {
- return;
+ return null;
}
-
const mode = result.mode;
const mode2 = result.mode2;
-
- lbPersonalBests[mode] = lbPersonalBests[mode] ?? {};
- const lbMode2 = lbPersonalBests[mode][mode2] as MonkeyTypes.LbPersonalBests;
- if (lbMode2 === undefined || Array.isArray(lbMode2)) {
- lbPersonalBests[mode][mode2] = {};
- }
-
+ const lbPb = lbPersonalBests ?? {};
+ lbPb[mode] ??= {};
+ lbPb[mode][mode2] ??= {};
const bestForEveryLanguage = {};
-
userPersonalBests[mode][mode2].forEach((pb: PersonalBest) => {
const language = pb.language;
if (
@@ -194,18 +195,18 @@ function updateLeaderboardPersonalBests(
bestForEveryLanguage[language] = pb;
}
});
-
_.each(bestForEveryLanguage, (pb: PersonalBest, language: string) => {
- const languageDoesNotExist =
- lbPersonalBests[mode][mode2][language] === undefined;
-
+ const languageDoesNotExist = lbPb[mode][mode2][language] === undefined;
+ const languageIsEmpty = _.isEmpty(lbPb[mode][mode2][language]);
if (
languageDoesNotExist ||
- lbPersonalBests[mode][mode2][language].wpm < pb.wpm
+ languageIsEmpty ||
+ lbPb[mode][mode2][language].wpm < pb.wpm
) {
- lbPersonalBests[mode][mode2][language] = pb;
+ lbPb[mode][mode2][language] = pb;
}
});
+ return lbPb;
}
function shouldUpdateLeaderboardPersonalBests(result: Result): boolean {
diff --git a/backend/src/workers/email-worker.ts b/backend/src/workers/email-worker.ts
index a6d70db56..4cf03915c 100644
--- a/backend/src/workers/email-worker.ts
+++ b/backend/src/workers/email-worker.ts
@@ -2,18 +2,15 @@ import _ from "lodash";
import IORedis from "ioredis";
import { Worker, Job, type ConnectionOptions } from "bullmq";
import Logger from "../utils/logger";
-import EmailQueue, {
- type EmailTaskContexts,
- type EmailType,
-} from "../queues/email-queue";
+import EmailQueue, { EmailTask, type EmailType } from "../queues/email-queue";
import { sendEmail } from "../init/email-client";
import { recordTimeToCompleteJob } from "../utils/prometheus";
import { addLog } from "../dal/logs";
-async function jobHandler(job: Job): Promise<void> {
- const type: EmailType = job.data.type;
- const email: string = job.data.email;
- const ctx: EmailTaskContexts[typeof type] = job.data.ctx;
+async function jobHandler(job: Job<EmailTask<EmailType>>): Promise<void> {
+ const type = job.data.type;
+ const email = job.data.email;
+ const ctx = job.data.ctx;
Logger.info(`Starting job: ${type}`);
diff --git a/backend/src/workers/later-worker.ts b/backend/src/workers/later-worker.ts
index 2621cd53c..a669a3b3e 100644
--- a/backend/src/workers/later-worker.ts
+++ b/backend/src/workers/later-worker.ts
@@ -180,8 +180,8 @@ async function handleWeeklyXpLeaderboardResults(
await addToInboxBulk(mailEntries, inboxConfig);
}
-async function jobHandler(job: Job): Promise<void> {
- const { taskName, ctx }: LaterTask<LaterTaskType> = job.data;
+async function jobHandler(job: Job<LaterTask<LaterTaskType>>): Promise<void> {
+ const { taskName, ctx } = job.data;
Logger.info(`Starting job: ${taskName}`);