mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-10-10 07:36:09 +08:00
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:
parent
124e842115
commit
1ba7a76b86
15 changed files with 172 additions and 2290 deletions
15
.github/workflows/pr-check.yml
vendored
15
.github/workflows/pr-check.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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 }));
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import db from "./db";
|
||||
|
||||
export function mongoDB() {
|
||||
return db;
|
||||
}
|
|
@ -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] ?? {});
|
||||
|
|
|
@ -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.");
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
|
@ -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)) {
|
|
@ -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);
|
||||
|
|
11
backend/types/types.d.ts
vendored
11
backend/types/types.d.ts
vendored
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
2169
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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",
|
||||
|
|
Loading…
Add table
Reference in a new issue