refactor: enable no-unsafe-member-access (@miodec) (#5887)

### Description

<!-- Please describe the change(s) made in your PR -->

### Checks

- [ ] Adding quotes?
- [ ] Make sure to include translations for the quotes in the
description (or another comment) so we can verify their content.
- [ ] Adding a language or a theme?
- [ ] If is a language, did you edit `_list.json`, `_groups.json` and
add `languages.json`?
  - [ ] If is a theme, did you add the theme.css?
- Also please add a screenshot of the theme, it would be extra awesome
if you do so!
- [ ] Check if any open issues are related to this PR; if so, be sure to
tag them below.
- [ ] Make sure the PR title follows the Conventional Commits standard.
(https://www.conventionalcommits.org for more info)
- [ ] Make sure to include your GitHub username prefixed with @ inside
parentheses at the end of the PR title.

<!-- label(optional scope): pull request title (@your_github_username)
-->

<!-- I know I know they seem boring but please do them, they help us and
you will find out it also helps you.-->

Closes #

<!-- the issue(s) your PR resolves if any (delete if that is not the
case) -->
<!-- please also reference any issues and or PRs related to your pull
request -->
<!-- Also remove it if you are not following any issues. -->

<!-- pro tip: you can mention an issue, PR, or discussion on GitHub by
referencing its hash number e.g:
[#1234](https://github.com/monkeytypegame/monkeytype/pull/1234) -->

<!-- pro tip: you can press . (dot or period) in the code tab of any
GitHub repo to get access to GitHub's VS Code web editor Enjoy! :) -->
This commit is contained in:
Jack 2024-09-18 12:56:52 +02:00 committed by GitHub
parent c75ba9a2ba
commit 7e703028bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 273 additions and 120 deletions

View file

@ -28,6 +28,7 @@ import { LeaderboardRank } from "@monkeytype/contracts/schemas/leaderboards";
import { randomUUID } from "node:crypto";
import _ from "lodash";
import { MonkeyMail, UserStreak } from "@monkeytype/contracts/schemas/users";
import { isFirebaseError } from "../../../src/utils/error";
const mockApp = request(app);
const configuration = Configuration.getCachedConfiguration();
@ -355,10 +356,16 @@ describe("user controller test", () => {
it("should fail with too many firebase requests", async () => {
//GIVEN
adminGenerateVerificationLinkMock.mockRejectedValue({
code: "auth/internal-error",
message: "TOO_MANY_ATTEMPTS_TRY_LATER",
} as FirebaseError);
const mockFirebaseError = {
code: "auth/too-many-requests",
codePrefix: "auth",
errorInfo: {
code: "auth/too-many-requests",
message: "Too many requests",
},
};
adminGenerateVerificationLinkMock.mockRejectedValue(mockFirebaseError);
expect(isFirebaseError(mockFirebaseError)).toBe(true);
//WHEN
const { body } = await mockApp
@ -371,9 +378,16 @@ describe("user controller test", () => {
});
it("should fail with firebase user not found", async () => {
//GIVEN
adminGenerateVerificationLinkMock.mockRejectedValue({
const mockFirebaseError = {
code: "auth/user-not-found",
} as FirebaseError);
codePrefix: "auth",
errorInfo: {
code: "auth/user-not-found",
message: "User not found",
},
};
adminGenerateVerificationLinkMock.mockRejectedValue(mockFirebaseError);
expect(isFirebaseError(mockFirebaseError)).toBe(true);
//WHEN
const { body } = await mockApp
@ -387,11 +401,13 @@ describe("user controller test", () => {
'Stack: {"decodedTokenEmail":"newuser@mail.com","userInfoEmail":"newuser@mail.com"}'
);
});
it("should fail with firebase errir", async () => {
it("should fail with unknown error", async () => {
//GIVEN
adminGenerateVerificationLinkMock.mockRejectedValue({
message: "Internal error encountered.",
} as FirebaseError);
const mockFirebaseError = {
message: "Internal server error",
};
adminGenerateVerificationLinkMock.mockRejectedValue(mockFirebaseError);
expect(isFirebaseError(mockFirebaseError)).toBe(false);
//WHEN
const { body } = await mockApp
@ -401,7 +417,7 @@ describe("user controller test", () => {
//THEN
expect(body.message).toEqual(
"Firebase failed to generate an email verification link. Please try again later."
"Firebase failed to generate an email verification link: Internal server error"
);
});
});
@ -1039,9 +1055,16 @@ describe("user controller test", () => {
});
it("should fail for duplicate email", async () => {
//GIVEN
authUpdateEmailMock.mockRejectedValue({
const mockFirebaseError = {
code: "auth/email-already-exists",
} as FirebaseError);
codePrefix: "auth",
errorInfo: {
code: "auth/email-already-exists",
message: "Email already exists",
},
};
authUpdateEmailMock.mockRejectedValue(mockFirebaseError);
expect(isFirebaseError(mockFirebaseError)).toBe(true);
//WHEN
const { body } = await mockApp
@ -1062,9 +1085,16 @@ describe("user controller test", () => {
it("should fail for invalid email", async () => {
//GIVEN
authUpdateEmailMock.mockRejectedValue({
const mockFirebaseError = {
code: "auth/invalid-email",
} as FirebaseError);
codePrefix: "auth",
errorInfo: {
code: "auth/invalid-email",
message: "Invalid email",
},
};
authUpdateEmailMock.mockRejectedValue(mockFirebaseError);
expect(isFirebaseError(mockFirebaseError)).toBe(true);
//WHEN
const { body } = await mockApp
@ -1082,9 +1112,16 @@ describe("user controller test", () => {
});
it("should fail for too many requests", async () => {
//GIVEN
authUpdateEmailMock.mockRejectedValue({
const mockFirebaseError = {
code: "auth/too-many-requests",
} as FirebaseError);
codePrefix: "auth",
errorInfo: {
code: "auth/too-many-requests",
message: "Too many requests",
},
};
authUpdateEmailMock.mockRejectedValue(mockFirebaseError);
expect(isFirebaseError(mockFirebaseError)).toBe(true);
//WHEN
const { body } = await mockApp
@ -1102,9 +1139,16 @@ describe("user controller test", () => {
});
it("should fail for unknown user", async () => {
//GIVEN
authUpdateEmailMock.mockRejectedValue({
const mockFirebaseError = {
code: "auth/user-not-found",
} as FirebaseError);
codePrefix: "auth",
errorInfo: {
code: "auth/user-not-found",
message: "User not found",
},
};
authUpdateEmailMock.mockRejectedValue(mockFirebaseError);
expect(isFirebaseError(mockFirebaseError)).toBe(true);
//WHEN
const { body } = await mockApp
@ -1126,7 +1170,12 @@ describe("user controller test", () => {
//GIVEN
authUpdateEmailMock.mockRejectedValue({
code: "auth/invalid-user-token",
} as FirebaseError);
codePrefix: "auth",
errorInfo: {
code: "auth/invalid-user-token",
message: "Invalid user token",
},
});
//WHEN
const { body } = await mockApp

View file

@ -11,7 +11,7 @@ import {
ToggleBanRequest,
ToggleBanResponse,
} from "@monkeytype/contracts/admin";
import MonkeyError from "../../utils/error";
import MonkeyError, { getErrorMessage } from "../../utils/error";
import { Configuration } from "@monkeytype/contracts/schemas/configuration";
import { addImportantLog } from "../../dal/logs";
@ -117,7 +117,10 @@ export async function handleReports(
if (e instanceof MonkeyError) {
throw new MonkeyError(e.status, e.message);
} else {
throw new MonkeyError(500, "Error handling reports: " + e.message);
throw new MonkeyError(
500,
"Error handling reports: " + getErrorMessage(e)
);
}
}
}

View file

@ -247,6 +247,7 @@ async function updateUser(uid: string): Promise<void> {
if (lbPersonalBests[mode.mode][mode.mode2] === undefined)
lbPersonalBests[mode.mode][mode.mode2] = {};
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
lbPersonalBests[mode.mode][mode.mode2][mode.language] = entry;
}

View file

@ -1,6 +1,9 @@
import _ from "lodash";
import * as UserDAL from "../../dal/user";
import MonkeyError from "../../utils/error";
import MonkeyError, {
getErrorMessage,
isFirebaseError,
} from "../../utils/error";
import { MonkeyResponse } from "../../utils/monkey-response";
import * as DiscordUtils from "../../utils/discord";
import {
@ -12,7 +15,6 @@ import {
sanitizeString,
} from "../../utils/misc";
import GeorgeQueue from "../../queues/george-queue";
import { type FirebaseError } from "firebase-admin";
import { deleteAllApeKeys } from "../../dal/ape-keys";
import { deleteAllPresets } from "../../dal/preset";
import { deleteAll as deleteAllResults } from "../../dal/result";
@ -183,31 +185,48 @@ export async function sendVerificationEmail(
: "https://monkeytype.com",
});
} catch (e) {
const firebaseError = e as FirebaseError;
if (
firebaseError.code === "auth/internal-error" &&
firebaseError.message.includes("TOO_MANY_ATTEMPTS_TRY_LATER")
) {
// for some reason this error is not handled with a custom auth/ code, so we have to do it manually
throw new MonkeyError(429, "Too many requests. Please try again later");
}
if (firebaseError.code === "auth/user-not-found") {
throw new MonkeyError(
500,
"Auth user not found when the user was found in the database. Contact support with this error message and your email",
JSON.stringify({
decodedTokenEmail: email,
userInfoEmail: userInfo.email,
stack: e.stack as unknown,
}),
userInfo.uid
);
}
if (firebaseError.message.includes("Internal error encountered.")) {
throw new MonkeyError(
500,
"Firebase failed to generate an email verification link. Please try again later."
);
if (isFirebaseError(e)) {
if (e.errorInfo.code === "auth/user-not-found") {
throw new MonkeyError(
500,
"Auth user not found when the user was found in the database. Contact support with this error message and your email",
JSON.stringify({
decodedTokenEmail: email,
userInfoEmail: userInfo.email,
}),
userInfo.uid
);
} else if (e.errorInfo.code === "auth/too-many-requests") {
throw new MonkeyError(429, "Too many requests. Please try again later");
} else {
throw new MonkeyError(
500,
"Firebase failed to generate an email verification link: " +
e.errorInfo.message
);
}
} else {
const message = getErrorMessage(e);
if (message === undefined) {
throw new MonkeyError(
500,
"Firebase failed to generate an email verification link. Unknown error occured"
);
} else {
if (message.toLowerCase().includes("too_many_attempts")) {
throw new MonkeyError(
429,
"Too many requests. Please try again later"
);
} else {
throw new MonkeyError(
500,
"Firebase failed to generate an email verification link: " +
message,
(e as Error).stack
);
}
}
}
}
await emailQueue.sendVerificationEmail(email, userInfo.name, link);
@ -394,24 +413,26 @@ export async function updateEmail(
await AuthUtil.updateUserEmail(uid, newEmail);
await UserDAL.updateEmail(uid, newEmail);
} catch (e) {
if (e.code === "auth/email-already-exists") {
throw new MonkeyError(
409,
"The email address is already in use by another account"
);
} else if (e.code === "auth/invalid-email") {
throw new MonkeyError(400, "Invalid email address");
} else if (e.code === "auth/too-many-requests") {
throw new MonkeyError(429, "Too many requests. Please try again later");
} else if (e.code === "auth/user-not-found") {
throw new MonkeyError(
404,
"User not found in the auth system",
"update email",
uid
);
} else if (e.code === "auth/invalid-user-token") {
throw new MonkeyError(401, "Invalid user token", "update email", uid);
if (isFirebaseError(e)) {
if (e.code === "auth/email-already-exists") {
throw new MonkeyError(
409,
"The email address is already in use by another account"
);
} else if (e.code === "auth/invalid-email") {
throw new MonkeyError(400, "Invalid email address");
} else if (e.code === "auth/too-many-requests") {
throw new MonkeyError(429, "Too many requests. Please try again later");
} else if (e.code === "auth/user-not-found") {
throw new MonkeyError(
404,
"User not found in the auth system",
"update email",
uid
);
} else if (e.code === "auth/invalid-user-token") {
throw new MonkeyError(401, "Invalid user token", "update email", uid);
}
} else {
throw e;
}
@ -475,6 +496,7 @@ export async function getUser(
try {
userInfo = await UserDAL.getUser(uid, "get user");
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (e.status === 404) {
//if the user is in the auth system but not in the db, its possible that the user was created by bypassing captcha
//since there is no data in the database anyway, we can just delete the user from the auth system
@ -488,6 +510,7 @@ export async function getUser(
uid
);
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (e.code === "auth/user-not-found") {
throw new MonkeyError(
404,

View file

@ -54,6 +54,7 @@ export async function get(
return preset;
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (e.error === 175) {
//QueryPlanKilled, collection was removed during the query
return false;
@ -84,6 +85,7 @@ export async function getRank(
entry: entry !== null ? entry : undefined,
};
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (e.error === 175) {
//QueryPlanKilled, collection was removed during the query
return false;

View file

@ -735,6 +735,7 @@ export async function getPersonalBests(
]);
if (mode2 !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return user.personalBests?.[mode]?.[mode2] as PersonalBest;
}

View file

@ -7,6 +7,7 @@ import { BASE_CONFIGURATION } from "../constants/base-configuration";
import { Configuration } from "@monkeytype/contracts/schemas/configuration";
import { addLog } from "../dal/logs";
import { PartialConfiguration } from "@monkeytype/contracts/configuration";
import { getErrorMessage } from "../utils/error";
const CONFIG_UPDATE_INTERVAL = 10 * 60 * 1000; // 10 Minutes
@ -86,9 +87,10 @@ export async function getLiveConfiguration(): Promise<Configuration> {
}); // Seed the base configuration.
}
} catch (error) {
const errorMessage = getErrorMessage(error) ?? "Unknown error";
void addLog(
"fetch_configuration_failure",
`Could not fetch configuration: ${error.message}`
`Could not fetch configuration: ${errorMessage}`
);
}
@ -104,9 +106,10 @@ async function pushConfiguration(configuration: Configuration): Promise<void> {
await db.collection("configuration").replaceOne({}, configuration);
serverConfigurationUpdated = true;
} catch (error) {
const errorMessage = getErrorMessage(error) ?? "Unknown error";
void addLog(
"push_configuration_failure",
`Could not push configuration: ${error.message}`
`Could not push configuration: ${errorMessage}`
);
}
}
@ -124,9 +127,10 @@ export async function patchConfiguration(
await getLiveConfiguration();
} catch (error) {
const errorMessage = getErrorMessage(error) ?? "Unknown error";
void addLog(
"patch_configuration_failure",
`Could not patch configuration: ${error.message}`
`Could not patch configuration: ${errorMessage}`
);
return false;

View file

@ -6,7 +6,7 @@ import {
type MongoClientOptions,
type WithId,
} from "mongodb";
import MonkeyError from "../utils/error";
import MonkeyError, { getErrorMessage } from "../utils/error";
import Logger from "../utils/logger";
let db: Db;
@ -58,7 +58,7 @@ export async function connect(): Promise<void> {
await mongoClient.connect();
db = mongoClient.db(DB_NAME);
} catch (error) {
Logger.error(error.message as string);
Logger.error(getErrorMessage(error) ?? "Unknown error");
Logger.error(
"Failed to connect to database. Exiting with exit status code 1."
);

View file

@ -7,6 +7,7 @@ import mustache from "mustache";
import { recordEmail } from "../utils/prometheus";
import type { EmailTaskContexts, EmailType } from "../queues/email-queue";
import { isDevEnvironment } from "../utils/misc";
import { getErrorMessage } from "../utils/error";
type EmailMetadata = {
subject: string;
@ -72,7 +73,7 @@ export async function init(): Promise<void> {
Logger.success("Email client configuration verified");
} catch (error) {
transportInitialized = false;
Logger.error(error.message as string);
Logger.error(getErrorMessage(error) ?? "Unknown error");
Logger.error("Failed to verify email client configuration.");
}
}
@ -112,7 +113,7 @@ export async function sendEmail(
recordEmail(templateName, "fail");
return {
success: false,
message: e.message as string,
message: getErrorMessage(e) ?? "Unknown error",
};
}

View file

@ -4,6 +4,7 @@ import { join } from "path";
import IORedis from "ioredis";
import Logger from "../utils/logger";
import { isDevEnvironment } from "../utils/misc";
import { getErrorMessage } from "../utils/error";
let connection: IORedis.Redis;
let connected = false;
@ -53,7 +54,7 @@ export async function connect(): Promise<void> {
connected = true;
} catch (error) {
Logger.error(error.message as string);
Logger.error(getErrorMessage(error) ?? "Unknown error");
if (isDevEnvironment()) {
await connection.quit();
Logger.warning(

View file

@ -19,6 +19,7 @@ import {
RequestAuthenticationOptions,
} from "@monkeytype/contracts/schemas/api";
import { Configuration } from "@monkeytype/contracts/schemas/configuration";
import { getMetadata, TsRestRequestWithCtx } from "./utility";
const DEFAULT_OPTIONS: RequestAuthenticationOptions = {
isGithubWebhook: false,
@ -28,11 +29,6 @@ const DEFAULT_OPTIONS: RequestAuthenticationOptions = {
isPublicOnDev: false,
};
export type TsRestRequestWithCtx = {
ctx: Readonly<MonkeyTypes.Context>;
} & TsRestRequest &
ExpressRequest;
/**
* Authenticate request based on the auth settings of the route.
* By default a Bearer token with user authentication is required.
@ -48,7 +44,7 @@ export function authenticateTsRestRequest<
): Promise<void> => {
const options = {
...DEFAULT_OPTIONS,
...((req.tsRestRoute["metadata"]?.["authenticationOptions"] ??
...((getMetadata(req)["authenticationOptions"] ??
{}) as EndpointMetadata),
};
@ -188,6 +184,7 @@ async function authenticateWithBearerToken(
email: decodedToken.email ?? "",
};
} catch (error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const errorCode = error?.errorInfo?.code as string | undefined;
if (errorCode?.includes("auth/id-token-expired")) {

View file

@ -1,5 +1,4 @@
import type { Response, NextFunction } from "express";
import { TsRestRequestWithCtx } from "./auth";
import { TsRestRequestHandler } from "@ts-rest/express";
import { EndpointMetadata } from "@monkeytype/contracts/schemas/api";
import MonkeyError from "../utils/error";
@ -8,6 +7,7 @@ import {
ConfigurationPath,
RequireConfiguration,
} from "@monkeytype/contracts/require-configuration/index";
import { getMetadata, TsRestRequestWithCtx } from "./utility";
export function verifyRequiredConfiguration<
T extends AppRouter | AppRoute
@ -17,9 +17,7 @@ export function verifyRequiredConfiguration<
_res: Response,
next: NextFunction
): Promise<void> => {
const requiredConfigurations = getRequireConfigurations(
req.tsRestRoute["metadata"] as EndpointMetadata | undefined
);
const requiredConfigurations = getRequireConfigurations(getMetadata(req));
if (requiredConfigurations === undefined) {
next();

View file

@ -1,7 +1,7 @@
import * as db from "../init/db";
import { v4 as uuidv4 } from "uuid";
import Logger from "../utils/logger";
import MonkeyError from "../utils/error";
import MonkeyError, { getErrorMessage } from "../utils/error";
import { incrementBadAuth } from "./rate-limit";
import type { NextFunction, Response } from "express";
import { isCustomCode } from "../constants/monkey-status-codes";
@ -92,7 +92,7 @@ async function errorHandlingMiddleware(
});
} catch (e) {
Logger.error("Logging to db failed.");
Logger.error(e.message as string);
Logger.error(getErrorMessage(e) ?? "Unknown error");
console.error(e);
}
} else {
@ -107,7 +107,7 @@ async function errorHandlingMiddleware(
return;
} catch (e) {
Logger.error("Error handling middleware failed.");
Logger.error(e.message as string);
Logger.error(getErrorMessage(e) ?? "Unknown error");
console.error(e);
}

View file

@ -4,13 +4,13 @@ import type { Response, NextFunction } from "express";
import { getPartialUser } from "../dal/user";
import { isAdmin } from "../dal/admin-uids";
import { TsRestRequestHandler } from "@ts-rest/express";
import { TsRestRequestWithCtx } from "./auth";
import {
EndpointMetadata,
RequestAuthenticationOptions,
PermissionId,
} from "@monkeytype/contracts/schemas/api";
import { isDevEnvironment } from "../utils/misc";
import { getMetadata, TsRestRequestWithCtx } from "./utility";
type RequestPermissionCheck = {
type: "request";
@ -77,9 +77,7 @@ export function verifyPermissions<
_res: Response,
next: NextFunction
): Promise<void> => {
const metadata = req.tsRestRoute["metadata"] as
| EndpointMetadata
| undefined;
const metadata = getMetadata(req);
const requiredPermissionIds = getRequiredPermissionIds(metadata);
if (
requiredPermissionIds === undefined ||

View file

@ -8,8 +8,6 @@ import {
type Options,
} from "express-rate-limit";
import { isDevEnvironment } from "../utils/misc";
import { EndpointMetadata } from "@monkeytype/contracts/schemas/api";
import { TsRestRequestWithCtx } from "./auth";
import { TsRestRequestHandler } from "@ts-rest/express";
import {
limits,
@ -18,6 +16,7 @@ import {
Window,
} from "@monkeytype/contracts/rate-limit/index";
import statuses from "../constants/monkey-status-codes";
import { getMetadata, TsRestRequestWithCtx } from "./utility";
export const REQUEST_MULTIPLIER = isDevEnvironment() ? 100 : 1;
@ -99,8 +98,7 @@ export function rateLimitRequest<
res: Response,
next: NextFunction
): Promise<void> => {
const rateLimit = (req.tsRestRoute["metadata"] as EndpointMetadata)
?.rateLimit;
const rateLimit = getMetadata(req).rateLimit;
if (rateLimit === undefined) {
next();
return;

View file

@ -3,7 +3,12 @@ import type { Request, Response, NextFunction, RequestHandler } from "express";
import { recordClientVersion as prometheusRecordClientVersion } from "../utils/prometheus";
import { isDevEnvironment } from "../utils/misc";
import MonkeyError from "../utils/error";
import { TsRestRequestWithCtx } from "./auth";
import { EndpointMetadata } from "@monkeytype/contracts/schemas/api";
export type TsRestRequestWithCtx = {
ctx: Readonly<MonkeyTypes.Context>;
} & TsRestRequest &
ExpressRequest;
/**
* record the client version from the `x-client-version` or ` client-version` header to prometheus
@ -35,3 +40,8 @@ export function onlyAvailableOnDev(): MonkeyTypes.RequestHandler {
}
};
}
export function getMetadata(req: TsRestRequestWithCtx): EndpointMetadata {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return (req.tsRestRoute["metadata"] ?? {}) as EndpointMetadata;
}

View file

@ -14,6 +14,7 @@ import * as EmailClient from "./init/email-client";
import { init as initFirebaseAdmin } from "./init/firebase-admin";
import { createIndicies as leaderboardDbSetup } from "./dal/leaderboards";
import { createIndicies as blocklistDbSetup } from "./dal/blocklist";
import { getErrorMessage } from "./utils/error";
async function bootServer(port: number): Promise<Server> {
try {
@ -74,7 +75,8 @@ async function bootServer(port: number): Promise<Server> {
recordServerVersion(version);
} catch (error) {
Logger.error("Failed to boot server");
Logger.error(error.message as string);
const message = getErrorMessage(error);
Logger.error(message ?? "Unknown error");
console.error(error);
return process.exit(1);
}

View file

@ -89,7 +89,8 @@ export class WeeklyXpLeaderboard {
const currentEntryTimeTypedSeconds =
currentEntry !== null
? (JSON.parse(currentEntry)?.timeTypedSeconds as number | undefined)
? (JSON.parse(currentEntry) as { timeTypedSeconds: number | undefined })
?.timeTypedSeconds
: undefined;
const totalTimeTypedSeconds =

View file

@ -1,10 +1,9 @@
type ObjectId = import("mongodb").ObjectId;
type ExpressRequest = import("express").Request;
/* eslint-disable @typescript-eslint/no-explicit-any */
type TsRestRequest = import("@ts-rest/express").TsRestRequest<any>;
/* eslint-enable @typescript-eslint/no-explicit-any */
type AppRoute = import("@ts-rest/core").AppRoute;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type TsRestRequest = import("@ts-rest/express").TsRestRequest<any>;
type AppRouter = import("@ts-rest/core").AppRouter;
declare namespace MonkeyTypes {
export type DecodedToken = {

View file

@ -9,6 +9,7 @@ import { type DecodedIdToken, UserRecord } from "firebase-admin/auth";
import { isDevEnvironment } from "./misc";
import emailQueue from "../queues/email-queue";
import * as UserDAL from "../dal/user";
import { isFirebaseError } from "./error";
const tokenCache = new LRUCache<string, DecodedIdToken>({
max: 20000,
@ -105,7 +106,8 @@ export async function sendForgotPasswordEmail(email: string): Promise<void> {
await emailQueue.sendForgotPasswordEmail(email, name, link);
} catch (err) {
if (err.errorInfo?.code !== "auth/user-not-found") {
if (isFirebaseError(err) && err.errorInfo.code !== "auth/user-not-found") {
// eslint-disable-next-line @typescript-eslint/only-throw-error
throw err;
}
}

View file

@ -1,6 +1,53 @@
import { v4 as uuidv4 } from "uuid";
import { isDevEnvironment } from "./misc";
import { MonkeyServerErrorType } from "@monkeytype/contracts/schemas/api";
import { FirebaseError } from "firebase-admin";
type FirebaseErrorParent = {
code: string;
errorInfo: FirebaseError;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function isFirebaseError(err: any): err is FirebaseErrorParent {
return (
typeof err === "object" &&
"code" in err &&
"errorInfo" in err &&
"codePrefix" in err &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
typeof err.errorInfo === "object" &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
"code" in err.errorInfo &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
"message" in err.errorInfo
);
}
export function getErrorMessage(error: unknown): string | undefined {
let message = "";
if (error instanceof Error) {
message = error.message;
} else if (
error !== null &&
typeof error === "object" &&
"message" in error &&
(typeof error.message === "string" || typeof error.message === "number")
) {
message = `${error.message}`;
} else if (typeof error === "string") {
message = error;
} else if (typeof error === "number") {
message = `${error}`;
}
if (message === "") {
return undefined;
}
return message;
}
class MonkeyError extends Error implements MonkeyServerErrorType {
status: number;

View file

@ -180,12 +180,13 @@ export function updateLeaderboardPersonalBests(
if (!shouldUpdateLeaderboardPersonalBests(result)) {
return null;
}
const mode = result.mode;
const mode2 = result.mode2;
const lbPb = lbPersonalBests ?? {};
const mode = result.mode as keyof typeof lbPb;
const mode2 = result.mode2 as unknown as keyof (typeof lbPb)[typeof mode];
lbPb[mode] ??= {};
lbPb[mode][mode2] ??= {};
const bestForEveryLanguage = {};
const bestForEveryLanguage: Record<string, PersonalBest> = {};
(userPersonalBests[mode][mode2] as PersonalBest[]).forEach(
(pb: PersonalBest) => {
const language = pb.language;
@ -198,12 +199,14 @@ export function updateLeaderboardPersonalBests(
}
);
_.each(bestForEveryLanguage, (pb: PersonalBest, language: string) => {
const languageDoesNotExist = lbPb[mode][mode2][language] === undefined;
const languageIsEmpty = _.isEmpty(lbPb[mode][mode2][language]);
const languageDoesNotExist = lbPb[mode][mode2]?.[language] === undefined;
const languageIsEmpty = _.isEmpty(lbPb[mode][mode2]?.[language]);
if (
languageDoesNotExist ||
languageIsEmpty ||
lbPb[mode][mode2][language].wpm < pb.wpm
(languageDoesNotExist ||
languageIsEmpty ||
(lbPb[mode][mode2]?.[language]?.wpm ?? 0) < pb.wpm) &&
lbPb[mode][mode2] !== undefined
) {
lbPb[mode][mode2][language] = pb;
}

View file

@ -215,6 +215,8 @@ export function recordAuthTime(
time: number,
req: Request
): void {
// for some reason route is not in the types
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const reqPath = req.baseUrl + req.route.path;
let normalizedPath = "/";
@ -234,6 +236,8 @@ const requestCountry = new Counter({
});
export function recordRequestCountry(country: string, req: Request): void {
// for some reason route is not in the types
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const reqPath = req.baseUrl + req.route.path;
let normalizedPath = "/";

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { debounce } from "throttle-debounce";
// import * as Numbers from "../utils/numbers";
import * as ConfigEvent from "../observables/config-event";

View file

@ -1,3 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { envConfig } from "../constants/env-config";
const siteKey = envConfig.recaptchaSiteKey;
@ -13,7 +16,6 @@ export function render(
}
//@ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call
const widgetId = grecaptcha.render(element, {
sitekey: siteKey,
callback,

View file

@ -95,7 +95,7 @@ class ChartWithUpdateColors<
id: DatasetIds extends never ? never : "x" | DatasetIds
): DatasetIds extends never ? never : CartesianScaleOptions {
//@ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access
return this.options.scales[id];
}
}
@ -1119,6 +1119,7 @@ async function updateColors<
//@ts-expect-error
chart.data.datasets[0].borderColor = (ctx): string => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const isPb = ctx.raw?.isPb as boolean;
const color = isPb ? textcolor : maincolor;
return color;

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
export function init(): void {
$("head").append(`<script>
!function(e){var s=new XMLHttpRequest;s.open("GET","https://api.enthusiastgaming.net/scripts/cdn.enthusiast.gg/script/eg-aps/release/eg-aps-bootstrap-v2.0.0.bundle.js?site=monkeytype.com",!0),s.onreadystatechange=function(){var t;4==s.readyState&&(200<=s.status&&s.status<300||304==s.status)&&((t=e.createElement("script")).type="text/javascript",t.text=s.responseText,e.head.appendChild(t))},s.send(null)}(document);

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
//@ts-nocheck

View file

@ -46,6 +46,7 @@ window.onerror = function (message, url, line, column, error): void {
window.onunhandledrejection = function (e): void {
if (Misc.isDevEnvironment()) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const message = (e.reason.message ?? e.reason) as string;
Notifications.add(`${message}`, -1, {
customTitle: "DEV: Unhandled rejection",
@ -54,6 +55,7 @@ window.onunhandledrejection = function (e): void {
console.error(e);
}
void log("error", {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
error: (e.reason.stack ?? "") as string,
});
};

View file

@ -426,7 +426,7 @@ function reset(): void {
$(".pageSettings .section[data-config-name='fontFamily'] .buttons").empty();
for (const select of document.querySelectorAll(".pageSettings select")) {
//@ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
select?.slim?.destroy?.();
}
}

View file

@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import * as Notifications from "../elements/notifications";
import * as AdController from "../controllers/ad-controller";
import * as Skeleton from "../utils/skeleton";
@ -38,7 +40,6 @@ export async function show(): Promise<void> {
.removeClass("hidden")
.animate({ opacity: 1 }, 125, () => {
//@ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
window.dataLayer.push({ event: "EG_Video" });
});
}

View file

@ -325,7 +325,9 @@ $("#replayWords").on("click", "letter", (event) => {
const replayWords = document.querySelector("#replayWords");
const words = [...(replayWords?.children ?? [])];
targetWordPos = words.indexOf(event.target.parentNode as HTMLElement);
targetWordPos = words.indexOf(
(event.target as HTMLElement).parentNode as HTMLElement
);
const letters = [...(words[targetWordPos] as HTMLElement).children];
targetCurPos = letters.indexOf(event.target as HTMLElement);

View file

@ -286,6 +286,7 @@ export async function getSection(language: string): Promise<Section> {
sectionReq.onload = (): void => {
if (sectionReq.readyState === 4) {
if (sectionReq.status === 200) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
let sectionText = JSON.parse(sectionReq.responseText).query.pages[
pageid.toString()
].extract as string;

View file

@ -79,10 +79,7 @@ module.exports = {
"@typescript-eslint/no-redundant-type-constituents": "off",
"@typescript-eslint/restrict-plus-operands": "off",
// TODO: enable at some point
"@typescript-eslint/no-unsafe-member-access": "off", //~105
//
"@typescript-eslint/no-unsafe-member-access": "error",
"@typescript-eslint/no-unsafe-call": "error",
"@typescript-eslint/no-unsafe-argument": "error",
"@typescript-eslint/no-unsafe-assignment": "error",