Skip to content

Commit e06f7f4

Browse files
authored
refactor: add trycatch util (@Miodec) (monkeytypegame#6492)
Adds trycatch util to cleanup try catch code.
1 parent a59f99a commit e06f7f4

File tree

24 files changed

+331
-203
lines changed

24 files changed

+331
-203
lines changed

backend/src/api/controllers/result.ts

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import {
6363
checkCompatibility,
6464
stringToFunboxNames,
6565
} from "@monkeytype/funbox";
66+
import { tryCatch } from "@monkeytype/util/trycatch";
6667

6768
try {
6869
if (!anticheatImplemented()) throw new Error("undefined");
@@ -318,13 +319,7 @@ export async function addResult(
318319
// );
319320
// return res.status(400).json({ message: "Time traveler detected" });
320321

321-
//get latest result ordered by timestamp
322-
let lastResultTimestamp: null | number = null;
323-
try {
324-
lastResultTimestamp = (await ResultDAL.getLastResult(uid)).timestamp;
325-
} catch (e) {
326-
//
327-
}
322+
const { data: lastResult } = await tryCatch(ResultDAL.getLastResult(uid));
328323

329324
//convert result test duration to miliseconds
330325
completedEvent.timestamp = Math.floor(Date.now() / 1000) * 1000;
@@ -333,13 +328,13 @@ export async function addResult(
333328
const testDurationMilis = completedEvent.testDuration * 1000;
334329
const incompleteTestsMilis = completedEvent.incompleteTestSeconds * 1000;
335330
const earliestPossible =
336-
(lastResultTimestamp ?? 0) + testDurationMilis + incompleteTestsMilis;
331+
(lastResult?.timestamp ?? 0) + testDurationMilis + incompleteTestsMilis;
337332
const nowNoMilis = Math.floor(Date.now() / 1000) * 1000;
338-
if (lastResultTimestamp && nowNoMilis < earliestPossible - 1000) {
333+
if (lastResult?.timestamp && nowNoMilis < earliestPossible - 1000) {
339334
void addLog(
340335
"invalid_result_spacing",
341336
{
342-
lastTimestamp: lastResultTimestamp,
337+
lastTimestamp: lastResult.timestamp,
343338
earliestPossible,
344339
now: nowNoMilis,
345340
testDuration: testDurationMilis,
@@ -786,17 +781,16 @@ async function calculateXp(
786781
const accuracyModifier = (acc - 50) / 50;
787782

788783
let dailyBonus = 0;
789-
let lastResultTimestamp: number | undefined;
784+
const { data: lastResult, error: getLastResultError } = await tryCatch(
785+
ResultDAL.getLastResult(uid)
786+
);
790787

791-
try {
792-
const { timestamp } = await ResultDAL.getLastResult(uid);
793-
lastResultTimestamp = timestamp;
794-
} catch (err) {
795-
Logger.error(`Could not fetch last result: ${err}`);
788+
if (getLastResultError) {
789+
Logger.error(`Could not fetch last result: ${getLastResultError}`);
796790
}
797791

798-
if (lastResultTimestamp) {
799-
const lastResultDay = getStartOfDayTimestamp(lastResultTimestamp);
792+
if (lastResult?.timestamp) {
793+
const lastResultDay = getStartOfDayTimestamp(lastResult.timestamp);
800794
const today = getCurrentDayTimestamp();
801795
if (lastResultDay !== today) {
802796
const proportionalXp = Math.round(currentTotalXp * 0.05);

backend/src/api/controllers/user.ts

Lines changed: 34 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,11 @@ import {
8888
} from "@monkeytype/contracts/users";
8989
import { MILLISECONDS_IN_DAY } from "@monkeytype/util/date-and-time";
9090
import { MonkeyRequest } from "../types";
91+
import { tryCatch } from "@monkeytype/util/trycatch";
9192

9293
async function verifyCaptcha(captcha: string): Promise<void> {
93-
let verified = false;
94-
try {
95-
verified = await verify(captcha);
96-
} catch (e) {
97-
//fetch to recaptcha api can sometimes fail
94+
const { data: verified, error } = await tryCatch(verify(captcha));
95+
if (error) {
9896
throw new MonkeyError(
9997
422,
10098
"Request to the Captcha API failed, please try again later"
@@ -177,18 +175,19 @@ export async function sendVerificationEmail(
177175
);
178176
}
179177

180-
let link = "";
181-
try {
182-
link = await FirebaseAdmin()
178+
const { data: link, error } = await tryCatch(
179+
FirebaseAdmin()
183180
.auth()
184181
.generateEmailVerificationLink(email, {
185182
url: isDevEnvironment()
186183
? "http://localhost:3000"
187184
: "https://monkeytype.com",
188-
});
189-
} catch (e) {
190-
if (isFirebaseError(e)) {
191-
if (e.errorInfo.code === "auth/user-not-found") {
185+
})
186+
);
187+
188+
if (error) {
189+
if (isFirebaseError(error)) {
190+
if (error.errorInfo.code === "auth/user-not-found") {
192191
throw new MonkeyError(
193192
500,
194193
"Auth user not found when the user was found in the database. Contact support with this error message and your email",
@@ -198,11 +197,11 @@ export async function sendVerificationEmail(
198197
}),
199198
userInfo.uid
200199
);
201-
} else if (e.errorInfo.code === "auth/too-many-requests") {
200+
} else if (error.errorInfo.code === "auth/too-many-requests") {
202201
throw new MonkeyError(429, "Too many requests. Please try again later");
203202
} else if (
204-
e.errorInfo.code === "auth/internal-error" &&
205-
e.errorInfo.message.toLowerCase().includes("too_many_attempts")
203+
error.errorInfo.code === "auth/internal-error" &&
204+
error.errorInfo.message.toLowerCase().includes("too_many_attempts")
206205
) {
207206
throw new MonkeyError(
208207
429,
@@ -212,12 +211,12 @@ export async function sendVerificationEmail(
212211
throw new MonkeyError(
213212
500,
214213
"Firebase failed to generate an email verification link: " +
215-
e.errorInfo.message,
216-
JSON.stringify(e)
214+
error.errorInfo.message,
215+
JSON.stringify(error)
217216
);
218217
}
219218
} else {
220-
const message = getErrorMessage(e);
219+
const message = getErrorMessage(error);
221220
if (message === undefined) {
222221
throw new MonkeyError(
223222
500,
@@ -233,12 +232,13 @@ export async function sendVerificationEmail(
233232
throw new MonkeyError(
234233
500,
235234
"Failed to generate an email verification link: " + message,
236-
(e as Error).stack
235+
error.stack
237236
);
238237
}
239238
}
240239
}
241240
}
241+
242242
await emailQueue.sendVerificationEmail(email, userInfo.name, link);
243243

244244
return new MonkeyResponse("Email sent", null);
@@ -259,22 +259,20 @@ export async function sendForgotPasswordEmail(
259259
export async function deleteUser(req: MonkeyRequest): Promise<MonkeyResponse> {
260260
const { uid } = req.ctx.decodedToken;
261261

262-
let userInfo:
263-
| Pick<UserDAL.DBUser, "banned" | "name" | "email" | "discordId">
264-
| undefined;
265-
266-
try {
267-
userInfo = await UserDAL.getPartialUser(uid, "delete user", [
262+
const { data: userInfo, error } = await tryCatch(
263+
UserDAL.getPartialUser(uid, "delete user", [
268264
"banned",
269265
"name",
270266
"email",
271267
"discordId",
272-
]);
273-
} catch (e) {
274-
if (e instanceof MonkeyError && e.status === 404) {
268+
])
269+
);
270+
271+
if (error) {
272+
if (error instanceof MonkeyError && error.status === 404) {
275273
//userinfo was already deleted. We ignore this and still try to remove the other data
276274
} else {
277-
throw e;
275+
throw error;
278276
}
279277
}
280278

@@ -533,12 +531,12 @@ function getRelevantUserInfo(user: UserDAL.DBUser): RelevantUserInfo {
533531
export async function getUser(req: MonkeyRequest): Promise<GetUserResponse> {
534532
const { uid } = req.ctx.decodedToken;
535533

536-
let userInfo: UserDAL.DBUser;
537-
try {
538-
userInfo = await UserDAL.getUser(uid, "get user");
539-
} catch (e) {
540-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
541-
if (e.status === 404) {
534+
const { data: userInfo, error } = await tryCatch(
535+
UserDAL.getUser(uid, "get user")
536+
);
537+
538+
if (error) {
539+
if (error instanceof MonkeyError && error.status === 404) {
542540
//if the user is in the auth system but not in the db, its possible that the user was created by bypassing captcha
543541
//since there is no data in the database anyway, we can just delete the user from the auth system
544542
//and ask them to sign up again
@@ -564,7 +562,7 @@ export async function getUser(req: MonkeyRequest): Promise<GetUserResponse> {
564562
}
565563
}
566564
} else {
567-
throw e;
565+
throw error;
568566
}
569567
}
570568

backend/src/api/routes/swagger.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ import { getMiddleware as getSwaggerMiddleware } from "swagger-stats";
33
import { isDevEnvironment } from "../../utils/misc";
44
import { readFileSync } from "fs";
55
import Logger from "../../utils/logger";
6+
import { tryCatchSync } from "@monkeytype/util/trycatch";
67

78
function addSwaggerMiddlewares(app: Application): void {
89
const openApiSpec = __dirname + "/../../../dist/static/api/openapi.json";
9-
let spec = {};
10-
try {
11-
spec = JSON.parse(readFileSync(openApiSpec, "utf8")) as string;
12-
} catch (err) {
10+
11+
const { data: spec, error } = tryCatchSync(
12+
() =>
13+
JSON.parse(readFileSync(openApiSpec, "utf8")) as Record<string, unknown>
14+
);
15+
16+
if (error) {
1317
Logger.warning(
1418
`Cannot read openApi specification from ${openApiSpec}. Swagger stats will not fully work.`
1519
);
@@ -21,7 +25,7 @@ function addSwaggerMiddlewares(app: Application): void {
2125
uriPath: "/stats",
2226
authentication: !isDevEnvironment(),
2327
apdexThreshold: 100,
24-
swaggerSpec: spec,
28+
swaggerSpec: spec ?? {},
2529
onAuthenticate: (_req, username, password) => {
2630
return (
2731
username === process.env["STATS_USERNAME"] &&

backend/src/dal/new-quotes.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SimpleGit, simpleGit } from "simple-git";
1+
import { simpleGit } from "simple-git";
22
import { Collection, ObjectId } from "mongodb";
33
import path from "path";
44
import { existsSync, writeFileSync } from "fs";
@@ -10,6 +10,7 @@ import { ApproveQuote, Quote } from "@monkeytype/contracts/schemas/quotes";
1010
import { WithObjectId } from "../utils/misc";
1111
import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json";
1212
import { z } from "zod";
13+
import { tryCatchSync } from "@monkeytype/util/trycatch";
1314

1415
const JsonQuoteSchema = z.object({
1516
text: z.string(),
@@ -28,12 +29,12 @@ const QuoteDataSchema = z.object({
2829

2930
const PATH_TO_REPO = "../../../../monkeytype-new-quotes";
3031

31-
let git: SimpleGit | undefined;
32-
try {
33-
git = simpleGit(path.join(__dirname, PATH_TO_REPO));
34-
} catch (e) {
35-
console.error(`Failed to initialize git: ${e}`);
36-
git = undefined;
32+
const { data: git, error } = tryCatchSync(() =>
33+
simpleGit(path.join(__dirname, PATH_TO_REPO))
34+
);
35+
36+
if (error) {
37+
console.error(`Failed to initialize git: ${error}`);
3738
}
3839

3940
type AddQuoteReturn = {
@@ -145,7 +146,7 @@ export async function approve(
145146
editSource: string | undefined,
146147
name: string
147148
): Promise<ApproveReturn> {
148-
if (git === undefined) throw new MonkeyError(500, "Git not available.");
149+
if (git === null) throw new MonkeyError(500, "Git not available.");
149150
//check mod status
150151
const targetQuote = await getNewQuoteCollection().findOne({
151152
_id: new ObjectId(quoteId),

backend/src/dal/result.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import {
88
import MonkeyError from "../utils/error";
99
import * as db from "../init/db";
1010

11-
import { getUser, getTags, DBUser } from "./user";
11+
import { getUser, getTags } from "./user";
1212
import { DBResult } from "../utils/result";
13+
import { tryCatch } from "@monkeytype/util/trycatch";
1314

1415
export const getResultCollection = (): Collection<DBResult> =>
1516
db.collection<DBResult>("results");
@@ -18,12 +19,8 @@ export async function addResult(
1819
uid: string,
1920
result: DBResult
2021
): Promise<{ insertedId: ObjectId }> {
21-
let user: DBUser | null = null;
22-
try {
23-
user = await getUser(uid, "add result");
24-
} catch (e) {
25-
user = null;
26-
}
22+
const { data: user } = await tryCatch(getUser(uid, "add result"));
23+
2724
if (!user) throw new MonkeyError(404, "User not found", "add result");
2825
if (result.uid === undefined) result.uid = uid;
2926
// result.ir = true;

backend/src/init/email-client.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { recordEmail } from "../utils/prometheus";
88
import type { EmailTaskContexts, EmailType } from "../queues/email-queue";
99
import { isDevEnvironment } from "../utils/misc";
1010
import { getErrorMessage } from "../utils/error";
11+
import { tryCatch } from "@monkeytype/util/trycatch";
1112

1213
type EmailMetadata = {
1314
subject: string;
@@ -109,14 +110,15 @@ export async function sendEmail(
109110

110111
type Result = { response: string; accepted: string[] };
111112

112-
let result: Result;
113-
try {
114-
result = (await transporter.sendMail(mailOptions)) as Result;
115-
} catch (e) {
113+
const { data: result, error } = await tryCatch(
114+
transporter.sendMail(mailOptions) as Promise<Result>
115+
);
116+
117+
if (error) {
116118
recordEmail(templateName, "fail");
117119
return {
118120
success: false,
119-
message: getErrorMessage(e) ?? "Unknown error",
121+
message: getErrorMessage(error) ?? "Unknown error",
120122
};
121123
}
122124

backend/src/services/weekly-xp-leaderboard.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { getCurrentWeekTimestamp } from "@monkeytype/util/date-and-time";
1111
import MonkeyError from "../utils/error";
1212
import { omit } from "lodash";
1313
import { parseWithSchema as parseJsonWithSchema } from "@monkeytype/util/json";
14+
import { tryCatchSync } from "@monkeytype/util/trycatch";
1415

1516
type AddResultOpts = {
1617
entry: RedisXpLeaderboardEntry;
@@ -225,11 +226,11 @@ export class WeeklyXpLeaderboard {
225226
return null;
226227
}
227228

228-
// safely parse the result with error handling
229-
let parsed: RedisXpLeaderboardEntry;
230-
try {
231-
parsed = parseJsonWithSchema(result, RedisXpLeaderboardEntrySchema);
232-
} catch (error) {
229+
const { data: parsed, error } = tryCatchSync(() =>
230+
parseJsonWithSchema(result, RedisXpLeaderboardEntrySchema)
231+
);
232+
233+
if (error) {
233234
throw new MonkeyError(
234235
500,
235236
`Failed to parse leaderboard entry: ${

frontend/src/ts/controllers/challenge-controller.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
} from "@monkeytype/contracts/schemas/configs";
2020
import { Mode } from "@monkeytype/contracts/schemas/shared";
2121
import { CompletedEvent } from "@monkeytype/contracts/schemas/results";
22+
import { tryCatch } from "@monkeytype/util/trycatch";
2223

2324
let challengeLoading = false;
2425

@@ -219,11 +220,9 @@ export async function setup(challengeName: string): Promise<boolean> {
219220

220221
UpdateConfig.setFunbox("none");
221222

222-
let list;
223-
try {
224-
list = await JSONData.getChallengeList();
225-
} catch (e) {
226-
const message = Misc.createErrorMessage(e, "Failed to setup challenge");
223+
const { data: list, error } = await tryCatch(JSONData.getChallengeList());
224+
if (error) {
225+
const message = Misc.createErrorMessage(error, "Failed to setup challenge");
227226
Notifications.add(message, -1);
228227
ManualRestart.set();
229228
setTimeout(() => {

0 commit comments

Comments
 (0)