mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-12-10 21:36:52 +08:00
Enable strict null checks in backend (#2639)
* Enable strict null checks in backend * Fix * Use non-null assertion * Add none
This commit is contained in:
parent
ff0ee93fe4
commit
3240abc22e
16 changed files with 52 additions and 54 deletions
|
|
@ -231,7 +231,7 @@ class ResultController {
|
|||
}
|
||||
|
||||
let isPb = false;
|
||||
let tagPbs = [];
|
||||
let tagPbs: any[] = [];
|
||||
|
||||
if (!result.bailedOut) {
|
||||
[isPb, tagPbs] = await Promise.all([
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import _ from "lodash";
|
||||
import users from "./users";
|
||||
import configs from "./configs";
|
||||
import results from "./results";
|
||||
|
|
@ -8,7 +9,7 @@ import quotes from "./quotes";
|
|||
import apeKeys from "./ape-keys";
|
||||
import { asyncHandler } from "../../middlewares/api-utils";
|
||||
import { MonkeyResponse } from "../../utils/monkey-response";
|
||||
import { Application, NextFunction, Response } from "express";
|
||||
import { Application, NextFunction, Response, Router } from "express";
|
||||
import swStats from "swagger-stats";
|
||||
import SwaggerSpec from "../../swagger.json";
|
||||
|
||||
|
|
@ -51,10 +52,10 @@ function addApiRoutes(app: Application): void {
|
|||
if (process.env.MODE === "dev") {
|
||||
return;
|
||||
}
|
||||
const authHeader = rrr.http.request.headers.authorization ?? "None";
|
||||
const authHeader = rrr.http.request.headers?.authorization ?? "None";
|
||||
const authType = authHeader.split(" ");
|
||||
rrr.http.request.headers.authorization = authType[0];
|
||||
rrr.http.request.headers["x-forwarded-for"] = "";
|
||||
_.set(rrr.http.request, "headers.authorization", authType[0]);
|
||||
_.set(rrr.http.request, "headers['x-forwarded-for']", "");
|
||||
},
|
||||
})
|
||||
);
|
||||
|
|
@ -94,9 +95,8 @@ function addApiRoutes(app: Application): void {
|
|||
]);
|
||||
});
|
||||
|
||||
Object.keys(API_ROUTE_MAP).forEach((route) => {
|
||||
_.each(API_ROUTE_MAP, (router: Router, route) => {
|
||||
const apiRoute = `${BASE_ROUTE}${route}`;
|
||||
const router = API_ROUTE_MAP[route];
|
||||
app.use(apiRoute, router);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const quotesRouter = Router();
|
|||
|
||||
const checkIfUserIsQuoteMod = checkUserPermissions({
|
||||
criteria: (user) => {
|
||||
return user.quoteMod;
|
||||
return !!user.quoteMod;
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -51,16 +51,16 @@ class ApeKeysDAO {
|
|||
const user = (await UsersDAO.getUser(uid)) as MonkeyTypes.User;
|
||||
checkIfKeyExists(user.apeKeys, keyId);
|
||||
|
||||
const apeKey = user.apeKeys[keyId];
|
||||
const apeKey = user.apeKeys![keyId];
|
||||
|
||||
const updatedApeKey = {
|
||||
const updatedApeKey: MonkeyTypes.ApeKey = {
|
||||
...apeKey,
|
||||
modifiedOn: Date.now(),
|
||||
name: name ?? apeKey.name,
|
||||
enabled: _.isNil(enabled) ? apeKey.enabled : enabled,
|
||||
};
|
||||
|
||||
user.apeKeys[keyId] = updatedApeKey;
|
||||
user.apeKeys![keyId] = updatedApeKey;
|
||||
|
||||
await UsersDAO.setApeKeys(uid, user.apeKeys);
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ class ApeKeysDAO {
|
|||
): Promise<void> {
|
||||
checkIfKeyExists(user.apeKeys, keyId);
|
||||
|
||||
user.apeKeys[keyId].lastUsedOn = Date.now();
|
||||
user.apeKeys![keyId].lastUsedOn = Date.now();
|
||||
await UsersDAO.setApeKeys(user.uid, user.apeKeys);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class QuoteRatingsDAO {
|
|||
const quoteRating = await this.get(quoteId, language);
|
||||
const average = parseFloat(
|
||||
(
|
||||
Math.round((quoteRating.totalRating / quoteRating.ratings) * 10) / 10
|
||||
Math.round((quoteRating!.totalRating / quoteRating!.ratings) * 10) / 10
|
||||
).toFixed(1)
|
||||
);
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ class QuoteRatingsDAO {
|
|||
static async get(
|
||||
quoteId: number,
|
||||
language: string
|
||||
): Promise<MonkeyTypes.QuoteRating> {
|
||||
): Promise<MonkeyTypes.QuoteRating | null> {
|
||||
return await db
|
||||
.collection<MonkeyTypes.QuoteRating>("quote-rating")
|
||||
.findOne({ quoteId, language });
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import {
|
|||
} from "mongodb";
|
||||
|
||||
class DatabaseClient {
|
||||
static mongoClient: MongoClient = null;
|
||||
static db: Db = null;
|
||||
static mongoClient: MongoClient;
|
||||
static db: Db;
|
||||
static collections: Record<string, Collection<any>> = {};
|
||||
static connected = false;
|
||||
|
||||
|
|
@ -22,6 +22,10 @@ class DatabaseClient {
|
|||
DB_NAME,
|
||||
} = process.env;
|
||||
|
||||
if (!DB_URI || !DB_NAME) {
|
||||
throw new Error("No database configuration provided");
|
||||
}
|
||||
|
||||
const connectionOptions: MongoClientOptions = {
|
||||
connectTimeoutMS: 2000,
|
||||
serverSelectionTimeoutMS: 2000,
|
||||
|
|
@ -64,10 +68,6 @@ class DatabaseClient {
|
|||
}
|
||||
|
||||
static collection<T>(collectionName: string): Collection<T> {
|
||||
if (!this.connected) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(collectionName in this.collections)) {
|
||||
this.collections[collectionName] = this.db.collection<T>(collectionName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ function validateRequest(validationSchema: ValidationSchema): RequestHandler {
|
|||
throw new MonkeyError(
|
||||
400,
|
||||
validationErrorMessage ??
|
||||
`${errorMessage} (${error.details[0].context.value})`
|
||||
`${errorMessage} (${error.details[0]?.context?.value})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ function authenticateRequest(authOptions = DEFAULT_OPTIONS): Handler {
|
|||
): Promise<void> => {
|
||||
try {
|
||||
const { authorization: authHeader } = req.headers;
|
||||
let token: MonkeyTypes.DecodedToken = {};
|
||||
let token: MonkeyTypes.DecodedToken;
|
||||
|
||||
if (authHeader) {
|
||||
token = await authenticateWithAuthHeader(
|
||||
|
|
@ -77,6 +77,7 @@ function authenticateWithBody(
|
|||
return {
|
||||
type: "Bearer",
|
||||
uid,
|
||||
email: "",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -113,7 +114,7 @@ async function authenticateWithBearerToken(
|
|||
return {
|
||||
type: "Bearer",
|
||||
uid: decodedToken.uid,
|
||||
email: decodedToken.email,
|
||||
email: decodedToken.email ?? "",
|
||||
};
|
||||
} catch (error) {
|
||||
console.log("-----------");
|
||||
|
|
@ -158,11 +159,11 @@ async function authenticateWithApeKey(
|
|||
const keyOwner = (await UsersDAO.getUser(uid)) as MonkeyTypes.User;
|
||||
const targetApeKey = _.get(keyOwner.apeKeys, keyId);
|
||||
|
||||
if (!targetApeKey.enabled) {
|
||||
if (!targetApeKey?.enabled) {
|
||||
throw new MonkeyError(400, "ApeKey is disabled");
|
||||
}
|
||||
|
||||
const isKeyValid = await compare(apeKey, targetApeKey?.hash);
|
||||
const isKeyValid = await compare(apeKey, targetApeKey?.hash ?? "");
|
||||
|
||||
if (!isKeyValid) {
|
||||
throw new MonkeyError(400, "Invalid ApeKey");
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ async function contextMiddleware(
|
|||
req.ctx = {
|
||||
configuration,
|
||||
decodedToken: {
|
||||
uid: null,
|
||||
email: null,
|
||||
type: "None",
|
||||
uid: "",
|
||||
email: "",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,6 @@ async function bootServer(port: number): Promise<Server> {
|
|||
});
|
||||
}
|
||||
|
||||
const PORT = parseInt(process.env.PORT) || 5005;
|
||||
const PORT = parseInt(process.env.PORT ?? "5005", 10);
|
||||
|
||||
bootServer(PORT);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@
|
|||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true
|
||||
"esModuleInterop": true,
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"exclude": ["node_modules", "build"]
|
||||
}
|
||||
|
|
|
|||
6
backend/types/types.d.ts
vendored
6
backend/types/types.d.ts
vendored
|
|
@ -27,9 +27,9 @@ declare namespace MonkeyTypes {
|
|||
}
|
||||
|
||||
interface DecodedToken {
|
||||
type?: "Bearer" | "ApeKey";
|
||||
uid?: string;
|
||||
email?: string;
|
||||
type: "Bearer" | "ApeKey" | "None";
|
||||
uid: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface Context {
|
||||
|
|
|
|||
|
|
@ -5,12 +5,7 @@ class MonkeyError extends Error {
|
|||
errorId: string;
|
||||
uid?: string;
|
||||
|
||||
constructor(
|
||||
status: number,
|
||||
message: string,
|
||||
stack: string = null,
|
||||
uid: string = null
|
||||
) {
|
||||
constructor(status: number, message: string, stack?: string, uid?: string) {
|
||||
super();
|
||||
this.status = status ?? 500;
|
||||
this.errorId = uuidv4();
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ export default {
|
|||
const logsCollection = db.collection<Log>("logs");
|
||||
|
||||
console.log(new Date(), "\t", event, "\t", uid, "\t", message);
|
||||
await logsCollection.insertOne({
|
||||
logsCollection.insertOne({
|
||||
timestamp: Date.now(),
|
||||
uid,
|
||||
uid: uid ?? "",
|
||||
event,
|
||||
message,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ import _ from "lodash";
|
|||
interface CheckAndUpdatePbResult {
|
||||
isPb: boolean;
|
||||
obj: object;
|
||||
lbObj: object;
|
||||
lbObj?: object;
|
||||
}
|
||||
|
||||
type Result = MonkeyTypes.Result<MonkeyTypes.Mode>;
|
||||
|
||||
export function checkAndUpdatePb(
|
||||
userPersonalBests: MonkeyTypes.User["personalBests"],
|
||||
lbPersonalBests: MonkeyTypes.User["lbPersonalBests"],
|
||||
lbPersonalBests: MonkeyTypes.User["lbPersonalBests"] | undefined,
|
||||
result: Result
|
||||
): CheckAndUpdatePbResult {
|
||||
const { mode, mode2 } = result;
|
||||
|
|
@ -34,7 +34,9 @@ export function checkAndUpdatePb(
|
|||
userPb[mode][mode2].push(buildPersonalBest(result));
|
||||
}
|
||||
|
||||
updateLeaderboardPersonalBests(userPb, lbPersonalBests, result);
|
||||
if (!_.isNil(lbPersonalBests)) {
|
||||
updateLeaderboardPersonalBests(userPb, lbPersonalBests, result);
|
||||
}
|
||||
|
||||
return {
|
||||
isPb,
|
||||
|
|
@ -69,8 +71,8 @@ function updatePersonalBest(
|
|||
personalBest.consistency = result.consistency;
|
||||
personalBest.difficulty = result.difficulty;
|
||||
personalBest.language = result.language;
|
||||
personalBest.punctuation = result.punctuation;
|
||||
personalBest.lazyMode = result.lazyMode;
|
||||
personalBest.punctuation = result.punctuation ?? false;
|
||||
personalBest.lazyMode = result.lazyMode ?? false;
|
||||
personalBest.raw = result.rawWpm;
|
||||
personalBest.wpm = result.wpm;
|
||||
personalBest.timestamp = Date.now();
|
||||
|
|
@ -83,9 +85,9 @@ function buildPersonalBest(result: Result): MonkeyTypes.PersonalBest {
|
|||
acc: result.acc,
|
||||
consistency: result.consistency,
|
||||
difficulty: result.difficulty,
|
||||
lazyMode: result.lazyMode,
|
||||
lazyMode: result.lazyMode ?? false,
|
||||
language: result.language,
|
||||
punctuation: result.punctuation,
|
||||
punctuation: result.punctuation ?? false,
|
||||
raw: result.rawWpm,
|
||||
wpm: result.wpm,
|
||||
timestamp: Date.now(),
|
||||
|
|
@ -97,7 +99,7 @@ function updateLeaderboardPersonalBests(
|
|||
lbPersonalBests: MonkeyTypes.User["lbPersonalBests"],
|
||||
result: Result
|
||||
): void {
|
||||
if (!shouldUpdateLeaderboardPersonalBests(lbPersonalBests, result)) {
|
||||
if (!shouldUpdateLeaderboardPersonalBests(result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -136,11 +138,8 @@ function updateLeaderboardPersonalBests(
|
|||
);
|
||||
}
|
||||
|
||||
function shouldUpdateLeaderboardPersonalBests(
|
||||
lbPersonalBests: MonkeyTypes.User["lbPersonalBests"],
|
||||
result: Result
|
||||
): boolean {
|
||||
function shouldUpdateLeaderboardPersonalBests(result: Result): boolean {
|
||||
const isValidTimeMode =
|
||||
result.mode === "time" && (result.mode2 === "15" || result.mode2 === "60");
|
||||
return lbPersonalBests && isValidTimeMode && !result.lazyMode;
|
||||
return isValidTimeMode && !result.lazyMode;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ export function isTestTooShort(result: MonkeyTypes.CompletedEvent): boolean {
|
|||
if (mode === "custom") {
|
||||
if (!customText) return true;
|
||||
const { isWordRandom, isTimeRandom, textLen, word, time } = customText;
|
||||
const setTextTooShort = !isWordRandom && !isTimeRandom && textLen < 10;
|
||||
const setTextTooShort =
|
||||
!isWordRandom && !isTimeRandom && _.isNumber(textLen) && textLen < 10;
|
||||
const randomWordsTooShort = isWordRandom && !isTimeRandom && word < 10;
|
||||
const randomTimeTooShort = !isWordRandom && isTimeRandom && time < 15;
|
||||
return setTextTooShort || randomWordsTooShort || randomTimeTooShort;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue