mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2024-09-20 23:36:37 +08:00
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:
parent
b473606309
commit
7ef0c424d6
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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
17
backend/utils/auth.ts
Normal 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,
|
||||
});
|
||||
}
|
|
@ -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
24
backend/utils/captcha.ts
Normal 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;
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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({
|
|
@ -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
72
backend/utils/misc.ts
Normal 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;
|
||||
}
|
|
@ -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 });
|
||||
}
|
34
backend/utils/monkey-response.ts
Normal file
34
backend/utils/monkey-response.ts
Normal 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 });
|
||||
}
|
|
@ -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;
|
||||
// }
|
||||
// }
|
||||
// }
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue