Migrate middlewares to TypeScript (#2574)

* Migrate middlewares

* Update workflow

* Fix ignore

* Fix

* Fix

* Remove babel loader from root dependencies

* Remove mongoDb
This commit is contained in:
Bruce Berrios 2022-02-23 10:03:38 -05:00 committed by GitHub
parent 124e842115
commit 1ba7a76b86
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 172 additions and 2290 deletions

View file

@ -2,7 +2,7 @@ name: PR Check
on:
pull_request:
branches: [master, api-overhaul]
branches: [master]
jobs:
build:
@ -30,11 +30,14 @@ jobs:
- 'frontend/**/*.scss'
- 'frontend/**/*.js'
- 'frontend/**/*.ts'
- 'backend/**/*.{js,ts}'
scss:
- 'frontend/**/*.scss'
ts:
ts-fe:
- 'frontend/**/*.js'
- 'frontend/**/*.ts'
ts-be:
- 'backend/**/*.{js,ts}'
anti-cheat:
- 'backend/**/anticheat/**'
@ -88,12 +91,16 @@ jobs:
- name: Lint
if: steps.filter.outputs.any-tsscss == 'true'
run: npm run pr-check-lint-json
run: npm run lint
- name: Compile SCSS
if: steps.filter.outputs.scss == 'true'
run: npm run pr-check-scss
- name: Build backend
if: steps.filter.outputs.ts-be == 'true'
run: npm run pr-check-build-be
- name: Run webpack
if: steps.filter.outputs.ts == 'true'
if: steps.filter.outputs.ts-fe == 'true'
run: npm run pr-check-ts

View file

@ -10,7 +10,7 @@ const router = Router();
router.get(
"/",
RateLimit.leaderboardsGet,
authenticateRequest({ isPublic: true, acceptMonkeyTokens: false }),
authenticateRequest({ isPublic: true }),
validateRequest({
query: {
language: joi.string().required(),

View file

@ -5,7 +5,7 @@ import express, { urlencoded, json } from "express";
import contextMiddleware from "./middlewares/context";
import errorHandlingMiddleware from "./middlewares/error";
function buildApp() {
function buildApp(): express.Application {
const app = express();
app.use(urlencoded({ extended: true }));

View file

@ -1,4 +1,4 @@
import { mongoDB } from "../init/mongodb";
import db from "../init/db";
import Logger from "../handlers/logger";
import { performance } from "perf_hooks";
@ -6,7 +6,7 @@ class LeaderboardsDAO {
static async get(mode, mode2, language, skip, limit = 50) {
if (limit > 50 || limit <= 0) limit = 50;
if (skip < 0) skip = 0;
const preset = await mongoDB()
const preset = await db
.collection(`leaderboards.${language}.${mode}.${mode2}`)
.find()
.sort({ rank: 1 })
@ -17,11 +17,11 @@ class LeaderboardsDAO {
}
static async getRank(mode, mode2, language, uid) {
const res = await mongoDB()
const res = await db
.collection(`leaderboards.${language}.${mode}.${mode2}`)
.findOne({ uid });
if (res)
res.count = await mongoDB()
res.count = await db
.collection(`leaderboards.${language}.${mode}.${mode2}`)
.estimatedDocumentCount();
return res;
@ -30,7 +30,7 @@ class LeaderboardsDAO {
static async update(mode, mode2, language, uid = undefined) {
let str = `lbPersonalBests.${mode}.${mode2}.${language}`;
let start1 = performance.now();
let lb = await mongoDB()
let lb = await db
.collection("users")
.aggregate(
[
@ -84,23 +84,21 @@ class LeaderboardsDAO {
let end2 = performance.now();
let start3 = performance.now();
try {
await mongoDB()
.collection(`leaderboards.${language}.${mode}.${mode2}`)
.drop();
await db.collection(`leaderboards.${language}.${mode}.${mode2}`).drop();
} catch (e) {}
if (lb && lb.length !== 0)
await mongoDB()
await db
.collection(`leaderboards.${language}.${mode}.${mode2}`)
.insertMany(lb);
let end3 = performance.now();
let start4 = performance.now();
await mongoDB()
await db
.collection(`leaderboards.${language}.${mode}.${mode2}`)
.createIndex({
uid: -1,
});
await mongoDB()
await db
.collection(`leaderboards.${language}.${mode}.${mode2}`)
.createIndex({
rank: 1,

View file

@ -1,7 +1,8 @@
import * as uuid from "uuid";
class MonkeyError {
class MonkeyError extends Error {
constructor(status, message, stack = null, uid = null) {
super();
this.status = status ?? 500;
this.errorId = uuid.v4();
this.stack = stack;

View file

@ -1,5 +0,0 @@
import db from "./db";
export function mongoDB() {
return db;
}

View file

@ -63,9 +63,9 @@ function asyncHandler(handler: AsyncHandler): RequestHandler {
}
interface ValidationSchema {
body?: {};
query?: {};
params?: {};
body?: object;
query?: object;
params?: object;
validationErrorMessage?: string;
}
@ -91,7 +91,7 @@ function validateRequest(validationSchema: ValidationSchema): RequestHandler {
return (req: MonkeyTypes.Request, _res: Response, next: NextFunction) => {
_.each(
normalizedValidationSchema,
(schema: {}, key: keyof ValidationSchema) => {
(schema: object, key: keyof ValidationSchema) => {
const joiSchema = joi.object().keys(schema);
const { error } = joiSchema.validate(req[key] ?? {});

View file

@ -1,16 +1,31 @@
import MonkeyError from "../handlers/error";
import { verifyIdToken } from "../handlers/auth";
import { NextFunction, Response, Handler } from "express";
const DEFAULT_OPTIONS = {
interface RequestAuthenticationOptions {
isPublic?: boolean;
acceptMonkeyTokens?: boolean;
}
const DEFAULT_OPTIONS: RequestAuthenticationOptions = {
isPublic: false,
acceptMonkeyTokens: false,
};
function authenticateRequest(options = DEFAULT_OPTIONS) {
return async (req, _res, next) => {
function authenticateRequest(authOptions = DEFAULT_OPTIONS): Handler {
const options = {
...DEFAULT_OPTIONS,
...authOptions,
};
return async (
req: MonkeyTypes.Request,
_res: Response,
next: NextFunction
): Promise<void> => {
try {
const { authorization: authHeader } = req.headers;
let token = null;
let token: MonkeyTypes.DecodedToken = {};
if (authHeader) {
token = await authenticateWithAuthHeader(authHeader, options);
@ -26,7 +41,10 @@ function authenticateRequest(options = DEFAULT_OPTIONS) {
);
}
req.ctx.decodedToken = token;
req.ctx = {
...req.ctx,
decodedToken: token,
};
} catch (error) {
return next(error);
}
@ -35,7 +53,9 @@ function authenticateRequest(options = DEFAULT_OPTIONS) {
};
}
function authenticateWithBody(body) {
function authenticateWithBody(
body: MonkeyTypes.Request["body"]
): MonkeyTypes.DecodedToken {
const { uid } = body;
if (!uid) {
@ -50,7 +70,10 @@ function authenticateWithBody(body) {
};
}
async function authenticateWithAuthHeader(authHeader, options) {
async function authenticateWithAuthHeader(
authHeader: string,
options: RequestAuthenticationOptions
): Promise<MonkeyTypes.DecodedToken> {
const token = authHeader.split(" ");
const authScheme = token[0].trim();
@ -70,9 +93,16 @@ async function authenticateWithAuthHeader(authHeader, options) {
);
}
async function authenticateWithBearerToken(token) {
async function authenticateWithBearerToken(
token: string
): Promise<MonkeyTypes.DecodedToken> {
try {
return await verifyIdToken(token);
const decodedToken = await verifyIdToken(token);
return {
uid: decodedToken.uid,
email: decodedToken.email,
};
} catch (error) {
console.log("-----------");
console.log(error.errorInfo.code);
@ -96,7 +126,10 @@ async function authenticateWithBearerToken(token) {
}
}
async function authenticateWithMonkeyToken(token, options) {
async function authenticateWithMonkeyToken(
token: string,
options: RequestAuthenticationOptions
): Promise<MonkeyTypes.DecodedToken> {
if (!options.acceptMonkeyTokens) {
throw new MonkeyError(401, "This endpoint does not accept MonkeyTokens.");
}

View file

@ -1,12 +1,18 @@
import ConfigurationDAO from "../dao/configuration";
import { Response, NextFunction } from "express";
async function contextMiddleware(req, _res, next) {
async function contextMiddleware(
req: MonkeyTypes.Request,
_res: Response,
next: NextFunction
): Promise<void> {
const configuration = await ConfigurationDAO.getCachedConfiguration(true);
req.ctx = {
configuration,
decodedToken: {
uid: null,
email: null,
},
};

View file

@ -6,13 +6,21 @@ import {
MonkeyResponse,
handleMonkeyResponse,
} from "../handlers/monkey-response";
import { NextFunction, Response } from "express";
async function errorHandlingMiddleware(
error: Error,
req: MonkeyTypes.Request,
res: Response,
_next: NextFunction
): Promise<void> {
const monkeyError = error as MonkeyError;
async function errorHandlingMiddleware(error, req, res, _next) {
const monkeyResponse = new MonkeyResponse();
monkeyResponse.status = 500;
monkeyResponse.data = {
errorId: error.errorId ?? uuidv4(),
uid: error.uid ?? req.ctx?.decodedToken?.uid,
errorId: monkeyError.errorId ?? uuidv4(),
uid: monkeyError.uid ?? req.ctx?.decodedToken?.uid,
};
if (/ECONNREFUSED.*27017/i.test(error.message)) {

View file

@ -1,12 +1,14 @@
import "dotenv/config";
import admin, { ServiceAccount } from "firebase-admin";
import serviceAccount from "./credentials/serviceAccountKey.json";
// @ts-ignore
import serviceAccount from "./credentials/serviceAccountKey.json"; // eslint-disable-line require-path-exists/exists
import db from "./init/db.js";
import jobs from "./jobs";
import ConfigurationDAO from "./dao/configuration.js";
import app from "./app";
import { Server } from "http";
async function bootServer(port) {
async function bootServer(port: number): Promise<Server> {
try {
console.log("Connecting to database...");
await db.connect();
@ -38,6 +40,6 @@ async function bootServer(port) {
});
}
const PORT = process.env.PORT || 5005;
const PORT = parseInt(process.env.PORT) || 5005;
bootServer(PORT);

View file

@ -22,14 +22,17 @@ declare namespace MonkeyTypes {
};
}
interface DecodedToken {
uid?: string;
email?: string;
}
interface Context {
configuration: Configuration;
decodedToken: {
uid: string | null;
};
decodedToken: DecodedToken;
}
interface Request extends ExpressRequest {
ctx: Context;
ctx: Readonly<Context>;
}
}

View file

@ -1,76 +1,77 @@
// import path from "path";
// import serviceAccount from "./credentials/serviceAccountKey.json";
// import admin, { ServiceAccount } from "firebase-admin";
// import db from "./init/db";
// import { config } from "dotenv";
import path from "path";
// @ts-ignore
import serviceAccount from "./credentials/serviceAccountKey.json"; // eslint-disable-line require-path-exists/exists
import admin, { ServiceAccount } from "firebase-admin";
import db from "./init/db";
import { config } from "dotenv";
// config({ path: path.join(__dirname, ".env") });
config({ path: path.join(__dirname, ".env") });
// async function main(): Promise<void> {
// await db.connect();
// await admin.initializeApp({
// credential: admin.credential.cert(
// (serviceAccount as unknown) as ServiceAccount
// ),
// });
// console.log("Database Connected!!");
// refactor();
// }
async function main(): Promise<void> {
await db.connect();
admin.initializeApp({
credential: admin.credential.cert(
serviceAccount as unknown as ServiceAccount
),
});
console.log("Database Connected!!");
refactor();
}
// main();
main();
// async function refactor(): Promise<void> {
// console.log("getting all users");
async function refactor(): Promise<void> {
console.log("getting all users");
// const usersCollection = db.collection("users");
// let users = await usersCollection.find({}).toArray();
// console.log(users.length);
const usersCollection = db.collection("users");
const users = await usersCollection.find({}).toArray();
console.log(users.length);
// for (let user of users) {
// let obj = user.personalBests;
for (const user of users) {
const obj = user.personalBests;
// let lbPb = {
// time: {
// 15: {},
// 60: {},
// },
// };
// let bestForEveryLanguage = {};
// if (obj?.time?.[15]) {
// obj.time[15].forEach((pb) => {
// if (!bestForEveryLanguage[pb.language]) {
// bestForEveryLanguage[pb.language] = pb;
// } else {
// if (bestForEveryLanguage[pb.language].wpm < pb.wpm) {
// bestForEveryLanguage[pb.language] = pb;
// }
// }
// });
// Object.keys(bestForEveryLanguage).forEach((key) => {
// lbPb.time[15][key] = bestForEveryLanguage[key];
// });
// bestForEveryLanguage = {};
// }
// if (obj?.time?.[60]) {
// obj.time[60].forEach((pb) => {
// if (!bestForEveryLanguage[pb.language]) {
// bestForEveryLanguage[pb.language] = pb;
// } else {
// if (bestForEveryLanguage[pb.language].wpm < pb.wpm) {
// bestForEveryLanguage[pb.language] = pb;
// }
// }
// });
// Object.keys(bestForEveryLanguage).forEach((key) => {
// lbPb.time[60][key] = bestForEveryLanguage[key];
// });
// }
const lbPb = {
time: {
15: {},
60: {},
},
};
let bestForEveryLanguage = {};
if (obj?.time?.[15]) {
obj.time[15].forEach((pb) => {
if (!bestForEveryLanguage[pb.language]) {
bestForEveryLanguage[pb.language] = pb;
} else {
if (bestForEveryLanguage[pb.language].wpm < pb.wpm) {
bestForEveryLanguage[pb.language] = pb;
}
}
});
Object.keys(bestForEveryLanguage).forEach((key) => {
lbPb.time[15][key] = bestForEveryLanguage[key];
});
bestForEveryLanguage = {};
}
if (obj?.time?.[60]) {
obj.time[60].forEach((pb) => {
if (!bestForEveryLanguage[pb.language]) {
bestForEveryLanguage[pb.language] = pb;
} else {
if (bestForEveryLanguage[pb.language].wpm < pb.wpm) {
bestForEveryLanguage[pb.language] = pb;
}
}
});
Object.keys(bestForEveryLanguage).forEach((key) => {
lbPb.time[60][key] = bestForEveryLanguage[key];
});
}
// await usersCollection.updateOne(
// { _id: user._id },
// { $set: { lbPersonalBests: lbPb } }
// );
// console.log(`updated ${user.name}`);
// }
// console.log("done");
// }
await usersCollection.updateOne(
{ _id: user._id },
{ $set: { lbPersonalBests: lbPb } }
);
console.log(`updated ${user.name}`);
}
console.log("done");
}

2169
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -13,15 +13,13 @@
"lint": "eslint \"./backend/**/*.{ts,js}\" \"./frontend/src/scripts/**/*.{ts,js}\"",
"build:live": "cd ./frontend && npm run build:live",
"pretty": "prettier --check './backend/**/*.js' './frontend/src/**/*.{js,scss}' './frontend/static/**/*.{json,html}'",
"start:dev-ts": "concurrently --kill-others \"cd ./frontend && npm run start:dev-ts\" \"cd ./backend && npm run start:dev\"",
"start:dev:fe-ts": "cd frontend && npm run start:dev-ts",
"deploy:live-ts": "cd frontend && npm run deploy:live-ts",
"pr-check-lint-json": "cd frontend && npx gulp pr-check-lint-json",
"pr-check-quote-json": "cd frontend && npx gulp pr-check-quote-json",
"pr-check-language-json": "cd frontend && npx gulp pr-check-language-json",
"pr-check-other-json": "cd frontend && npx gulp pr-check-other-json",
"pr-check-scss": "cd frontend && npx gulp pr-check-scss",
"pr-check-ts": "cd frontend && npx gulp pr-check-ts"
"pr-check-ts": "cd frontend && npx gulp pr-check-ts",
"pr-check-build-be": "cd backend && npm run build"
},
"engines": {
"npm": "8.1.2"
@ -29,7 +27,6 @@
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.11.0",
"@typescript-eslint/parser": "^5.11.0",
"babel-loader": "^8.2.3",
"concurrently": "5.3.0",
"eslint": "8.8.0",
"eslint-config-standard": "16.0.3",