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:
Bruce Berrios 2022-03-07 11:10:07 -05:00 committed by GitHub
parent ff0ee93fe4
commit 3240abc22e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 52 additions and 54 deletions

View file

@ -231,7 +231,7 @@ class ResultController {
}
let isPb = false;
let tagPbs = [];
let tagPbs: any[] = [];
if (!result.bailedOut) {
[isPb, tagPbs] = await Promise.all([

View file

@ -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);
});

View file

@ -14,7 +14,7 @@ const quotesRouter = Router();
const checkIfUserIsQuoteMod = checkUserPermissions({
criteria: (user) => {
return user.quoteMod;
return !!user.quoteMod;
},
});

View file

@ -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);
}
}

View file

@ -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 });

View file

@ -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);
}

View file

@ -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})`
);
}
}

View file

@ -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");

View file

@ -11,8 +11,9 @@ async function contextMiddleware(
req.ctx = {
configuration,
decodedToken: {
uid: null,
email: null,
type: "None",
uid: "",
email: "",
},
};

View file

@ -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);

View file

@ -10,7 +10,8 @@
"moduleResolution": "node",
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
"esModuleInterop": true,
"strictNullChecks": true
},
"exclude": ["node_modules", "build"]
}

View file

@ -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 {

View file

@ -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();

View file

@ -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,
});

View file

@ -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;
}

View file

@ -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;