Migrate some utils to TS (#2625)

* Migrate some utils to TS

* Add argument type

* Fix logic

* Refactor math functions

* Rename function
This commit is contained in:
Bruce Berrios 2022-03-04 10:25:24 -05:00 committed by GitHub
parent b473606309
commit 7ef0c424d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 181 additions and 259 deletions

View file

@ -2,7 +2,7 @@ import _ from "lodash";
import UsersDAO from "../../dao/user";
import BotDAO from "../../dao/bot";
import MonkeyError from "../../utils/error";
import Logger from "../../utils/logger.js";
import Logger from "../../utils/logger";
import { MonkeyResponse } from "../../utils/monkey-response";
import { linkAccount } from "../../utils/discord";
import { buildAgentLog } from "../../utils/misc";

View file

@ -1,3 +1,5 @@
import _ from "lodash";
type Status = {
code: number;
message: string;
@ -13,10 +15,6 @@ type Statuses = {
GIT_GUD: Status;
};
export function getCodesRangeStart(): number {
return 460;
}
const statuses: Statuses = {
TEST_TOO_SHORT: {
code: 460,
@ -48,4 +46,12 @@ const statuses: Statuses = {
},
};
const CUSTOM_STATUS_CODES = new Set(
_.map(statuses, (status: Status) => status.code)
);
export function isCustomCode(code: number): boolean {
return CUSTOM_STATUS_CODES.has(code);
}
export default statuses;

View file

@ -1,6 +1,6 @@
import _ from "lodash";
import { isUsernameValid } from "../utils/validation";
import { updateAuthEmail } from "../utils/auth";
import { updateUserEmail } from "../utils/auth";
import { checkAndUpdatePb } from "../utils/pb";
import db from "../init/db";
import MonkeyError from "../utils/error";
@ -66,7 +66,7 @@ class UsersDAO {
static async updateEmail(uid, email) {
const user = await db.collection("users").findOne({ uid });
if (!user) throw new MonkeyError(404, "User not found", "update email");
await updateAuthEmail(uid, email);
await updateUserEmail(uid, email);
await db.collection("users").updateOne({ uid }, { $set: { email } });
return true;
}

View file

@ -1,6 +1,6 @@
import db from "./db";
import _ from "lodash";
import Logger from "../utils/logger.js";
import Logger from "../utils/logger";
import { identity } from "../utils/misc";
import BASE_CONFIGURATION from "../constants/base-configuration";

View file

@ -1,12 +0,0 @@
import admin from "firebase-admin";
export async function verifyIdToken(idToken) {
return await admin.auth().verifyIdToken(idToken, true);
}
export async function updateAuthEmail(uid, email) {
return await admin.auth().updateUser(uid, {
email,
emailVerified: false,
});
}

17
backend/utils/auth.ts Normal file
View file

@ -0,0 +1,17 @@
import admin from "firebase-admin";
import { UserRecord } from "firebase-admin/lib/auth/user-record";
import { DecodedIdToken } from "firebase-admin/lib/auth/token-verifier";
export async function verifyIdToken(idToken: string): Promise<DecodedIdToken> {
return await admin.auth().verifyIdToken(idToken, true);
}
export async function updateUserEmail(
uid: string,
email: string
): Promise<UserRecord> {
return await admin.auth().updateUser(uid, {
email,
emailVerified: false,
});
}

View file

@ -1,16 +0,0 @@
import fetch from "node-fetch";
import "dotenv/config";
export async function verify(captcha) {
if (process.env.MODE === "dev") return true;
const response = await fetch(
`https://www.google.com/recaptcha/api/siteverify`,
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `secret=${process.env.RECAPTCHA_SECRET}&response=${captcha}`,
}
);
const responseJSON = await response.json();
return responseJSON?.success;
}

24
backend/utils/captcha.ts Normal file
View file

@ -0,0 +1,24 @@
import fetch from "node-fetch";
interface CaptchaData {
success: boolean;
challenge_ts?: number;
hostname: string;
"error-codes"?: string[];
}
export async function verify(captcha: string): Promise<boolean> {
if (process.env.MODE === "dev") {
return true;
}
const response = await fetch(
`https://www.google.com/recaptcha/api/siteverify`,
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `secret=${process.env.RECAPTCHA_SECRET}&response=${captcha}`,
}
);
const captchaData = (await response.json()) as CaptchaData;
return captchaData.success;
}

View file

@ -1,13 +1,19 @@
import * as uuid from "uuid";
import { v4 as uuidv4 } from "uuid";
class MonkeyError extends Error {
status: number;
errorId: string;
uid: string;
constructor(status: number, message: string, stack = null, uid = null) {
uid?: string;
constructor(
status: number,
message: string,
stack: string = null,
uid: string = null
) {
super();
this.status = status ?? 500;
this.errorId = uuid.v4();
this.errorId = uuidv4();
this.stack = stack;
this.uid = uid;

View file

@ -1,8 +1,15 @@
import db from "../init/db";
interface Log {
timestamp: number;
uid: string;
event: string;
message: string;
}
export default {
async log(event, message, uid) {
const logsCollection = db.collection("logs");
async log(event: string, message: any, uid?: string): Promise<void> {
const logsCollection = db.collection<Log>("logs");
console.log(new Date(), "\t", event, "\t", uid, "\t", message);
await logsCollection.insertOne({

View file

@ -1,70 +0,0 @@
import uaparser from "ua-parser-js";
export function roundTo2(num) {
return Math.round((num + Number.EPSILON) * 100) / 100;
}
export function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
export function stdDev(array) {
const n = array.length;
const mean = array.reduce((a, b) => a + b) / n;
return Math.sqrt(
array.map((x) => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n
);
}
export function mean(array) {
try {
return (
array.reduce((previous, current) => (current += previous)) / array.length
);
} catch (e) {
return 0;
}
}
export function kogasa(cov) {
return (
100 * (1 - Math.tanh(cov + Math.pow(cov, 3) / 3 + Math.pow(cov, 5) / 5))
);
}
export function identity(value) {
return Object.prototype.toString
.call(value)
.replace(/^\[object\s+([a-z]+)\]$/i, "$1")
.toLowerCase();
}
export function base64UrlEncode(string) {
return Buffer.from(string).toString("base64url");
}
export function base64UrlDecode(string) {
return Buffer.from(string, "base64url").toString();
}
export function buildAgentLog(req) {
const agent = uaparser(req.headers["user-agent"]);
const agentLog = {
ip:
req.headers["cf-connecting-ip"] ||
req.headers["x-forwarded-for"] ||
req.ip ||
"255.255.255.255",
agent: `${agent.os.name} ${agent.os.version} ${agent.browser.name} ${agent.browser.version}`,
};
const {
device: { vendor, model, type },
} = agent;
if (vendor) {
agentLog.device = `${vendor} ${model} ${type}`;
}
return agentLog;
}

72
backend/utils/misc.ts Normal file
View file

@ -0,0 +1,72 @@
import _ from "lodash";
import uaparser from "ua-parser-js";
export function roundTo2(num: number): number {
return _.round(num, 2);
}
export function stdDev(population: number[]): number {
const n = population.length;
if (n === 0) {
return 0;
}
const populationMean = mean(population);
const variance = _.sumBy(population, (x) => (x - populationMean) ** 2) / n;
return Math.sqrt(variance);
}
export function mean(population: number[]): number {
const n = population.length;
return n > 0 ? _.sum(population) / n : 0;
}
export function kogasa(cov: number): number {
return (
100 * (1 - Math.tanh(cov + Math.pow(cov, 3) / 3 + Math.pow(cov, 5) / 5))
);
}
export function identity(value: string): string {
return Object.prototype.toString
.call(value)
.replace(/^\[object\s+([a-z]+)\]$/i, "$1")
.toLowerCase();
}
export function base64UrlEncode(data: string): string {
return Buffer.from(data).toString("base64url");
}
export function base64UrlDecode(data: string): string {
return Buffer.from(data, "base64url").toString();
}
interface AgentLog {
ip: string | string[];
agent: string;
device?: string;
}
export function buildAgentLog(req: MonkeyTypes.Request): AgentLog {
const agent = uaparser(req.headers["user-agent"]);
const agentLog: AgentLog = {
ip:
req.headers["cf-connecting-ip"] ||
req.headers["x-forwarded-for"] ||
req.ip ||
"255.255.255.255",
agent: `${agent.os.name} ${agent.os.version} ${agent.browser.name} ${agent.browser.version}`,
};
const {
device: { vendor, model, type },
} = agent;
if (vendor) {
agentLog.device = `${vendor} ${model} ${type}`;
}
return agentLog;
}

View file

@ -1,27 +0,0 @@
import { getCodesRangeStart } from "../constants/monkey-status-codes";
export class MonkeyResponse {
constructor(message, data, status = 200) {
this.message = message;
this.data = data ?? null;
this.status = status;
}
}
export function handleMonkeyResponse(handlerData, res) {
const isMonkeyResponse = handlerData instanceof MonkeyResponse;
const monkeyResponse = !isMonkeyResponse
? new MonkeyResponse("ok", handlerData)
: handlerData;
const { message, data, status } = monkeyResponse;
res.status(status);
if (status >= getCodesRangeStart()) res.statusMessage = message;
res.monkeyMessage = message; // so that we can see message in swagger stats
if ([301, 302].includes(status)) {
return res.redirect(data);
}
res.json({ message, data });
}

View file

@ -0,0 +1,34 @@
import { Response } from "express";
import { isCustomCode } from "../constants/monkey-status-codes";
export class MonkeyResponse {
message: string;
data: any;
status: number;
constructor(message?: string, data?: any, status = 200) {
this.message = message ?? "ok";
this.data = data ?? null;
this.status = status;
}
}
export function handleMonkeyResponse(
monkeyResponse: MonkeyResponse,
res: Response
): void {
const { message, data, status } = monkeyResponse;
res.status(status);
if (isCustomCode(status)) {
res.statusMessage = message;
}
//@ts-ignore ignored so that we can see message in swagger stats
res.monkeyMessage = message;
if ([301, 302].includes(status)) {
return res.redirect(data);
}
res.json({ message, data });
}

View file

@ -1,119 +0,0 @@
// module.exports = {
// check(result, userdata) {
// let pbs = null;
// if (result.mode == "quote") {
// return false;
// }
// if (result.funbox !== "none") {
// return false;
// }
// pbs = userdata?.personalBests;
// if(pbs === undefined){
// //userdao set personal best
// return true;
// }
// // try {
// // pbs = userdata.personalBests;
// // if (pbs === undefined) {
// // throw new Error("pb is undefined");
// // }
// // } catch (e) {
// // User.findOne({ uid: userdata.uid }, (err, user) => {
// // user.personalBests = {
// // [result.mode]: {
// // [result.mode2]: [
// // {
// // language: result.language,
// // difficulty: result.difficulty,
// // punctuation: result.punctuation,
// // wpm: result.wpm,
// // acc: result.acc,
// // raw: result.rawWpm,
// // timestamp: Date.now(),
// // consistency: result.consistency,
// // },
// // ],
// // },
// // };
// // }).then(() => {
// // return true;
// // });
// // }
// let toUpdate = false;
// let found = false;
// try {
// if (pbs[result.mode][result.mode2] === undefined) {
// pbs[result.mode][result.mode2] = [];
// }
// pbs[result.mode][result.mode2].forEach((pb) => {
// if (
// pb.punctuation === result.punctuation &&
// pb.difficulty === result.difficulty &&
// pb.language === result.language
// ) {
// //entry like this already exists, compare wpm
// found = true;
// if (pb.wpm < result.wpm) {
// //new pb
// pb.wpm = result.wpm;
// pb.acc = result.acc;
// pb.raw = result.rawWpm;
// pb.timestamp = Date.now();
// pb.consistency = result.consistency;
// toUpdate = true;
// } else {
// //no pb
// return false;
// }
// }
// });
// //checked all pbs, nothing found - meaning this is a new pb
// if (!found) {
// pbs[result.mode][result.mode2] = [
// {
// language: result.language,
// difficulty: result.difficulty,
// punctuation: result.punctuation,
// wpm: result.wpm,
// acc: result.acc,
// raw: result.rawWpm,
// timestamp: Date.now(),
// consistency: result.consistency,
// },
// ];
// toUpdate = true;
// }
// } catch (e) {
// // console.log(e);
// pbs[result.mode] = {};
// pbs[result.mode][result.mode2] = [
// {
// language: result.language,
// difficulty: result.difficulty,
// punctuation: result.punctuation,
// wpm: result.wpm,
// acc: result.acc,
// raw: result.rawWpm,
// timestamp: Date.now(),
// consistency: result.consistency,
// },
// ];
// toUpdate = true;
// }
// if (toUpdate) {
// // User.findOne({ uid: userdata.uid }, (err, user) => {
// // user.personalBests = pbs;
// // user.save();
// // });
// //userdao update the whole personalBests parameter with pbs object
// return true;
// } else {
// return false;
// }
// }
// }

View file

@ -46,7 +46,7 @@ export function isTestTooShort(result: MonkeyTypes.CompletedEvent): boolean {
if (mode === "words") {
const setWordTooShort = mode2 > 0 && mode2 < 10;
const infiniteWordTooShort = mode2 == 0 && testDuration < 15;
const infiniteWordTooShort = mode2 === 0 && testDuration < 15;
return setWordTooShort || infiniteWordTooShort;
}