mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-11-08 05:03:39 +08:00
Migrate users controller (#2618)
* Migrate users controller + other things * Undo import rename * Fix return type * Change status code * Fix spacing
This commit is contained in:
parent
2d4df4edf3
commit
0429c560ee
12 changed files with 275 additions and 126 deletions
|
|
@ -1,78 +1,75 @@
|
|||
import _ from "lodash";
|
||||
import UsersDAO from "../../dao/user";
|
||||
import BotDAO from "../../dao/bot";
|
||||
import { isUsernameValid } from "../../handlers/validation";
|
||||
import MonkeyError from "../../handlers/error";
|
||||
import fetch from "node-fetch";
|
||||
import Logger from "./../../handlers/logger.js";
|
||||
import uaparser from "ua-parser-js";
|
||||
import Logger from "../../handlers/logger.js";
|
||||
import { MonkeyResponse } from "../../handlers/monkey-response";
|
||||
import { linkAccount } from "../../handlers/discord";
|
||||
import { buildAgentLog } from "../../handlers/misc";
|
||||
|
||||
function cleanUser(user) {
|
||||
function cleanUser(user: MonkeyTypes.User): Omit<MonkeyTypes.User, "apeKeys"> {
|
||||
return _.omit(user, "apeKeys");
|
||||
}
|
||||
|
||||
class UserController {
|
||||
static async createNewUser(req, _res) {
|
||||
static async createNewUser(
|
||||
req: MonkeyTypes.Request
|
||||
): Promise<MonkeyResponse> {
|
||||
const { name } = req.body;
|
||||
const { email, uid } = req.ctx.decodedToken;
|
||||
|
||||
await UsersDAO.addUser(name, email, uid);
|
||||
Logger.log("user_created", `${name} ${email}`, uid);
|
||||
|
||||
return new MonkeyResponse("User created");
|
||||
}
|
||||
|
||||
static async deleteUser(req, _res) {
|
||||
static async deleteUser(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
const userInfo = await UsersDAO.getUser(uid);
|
||||
|
||||
const userInfo = await UsersDAO.getUser(uid);
|
||||
await UsersDAO.deleteUser(uid);
|
||||
Logger.log("user_deleted", `${userInfo.email} ${userInfo.name}`, uid);
|
||||
|
||||
return new MonkeyResponse("User deleted");
|
||||
}
|
||||
|
||||
static async updateName(req, _res) {
|
||||
static async updateName(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
const { name } = req.body;
|
||||
if (!isUsernameValid(name))
|
||||
throw new MonkeyError(
|
||||
400,
|
||||
"Username invalid. Name cannot contain special characters or contain more than 14 characters. Can include _ . and -"
|
||||
);
|
||||
let olduser = await UsersDAO.getUser(uid);
|
||||
|
||||
const oldUser = await UsersDAO.getUser(uid);
|
||||
await UsersDAO.updateName(uid, name);
|
||||
Logger.log(
|
||||
"user_name_updated",
|
||||
`changed name from ${olduser.name} to ${name}`,
|
||||
`changed name from ${oldUser.name} to ${name}`,
|
||||
uid
|
||||
);
|
||||
|
||||
return new MonkeyResponse("User's name updated");
|
||||
}
|
||||
|
||||
static async clearPb(req, _res) {
|
||||
static async clearPb(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
|
||||
await UsersDAO.clearPb(uid);
|
||||
Logger.log("user_cleared_pbs", "", uid);
|
||||
|
||||
return new MonkeyResponse("User's PB cleared");
|
||||
}
|
||||
|
||||
static async checkName(req, _res) {
|
||||
static async checkName(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
|
||||
const { name } = req.params;
|
||||
|
||||
if (!isUsernameValid(name)) {
|
||||
throw new MonkeyError(
|
||||
400,
|
||||
"Username invalid. Name cannot contain special characters or contain more than 14 characters. Can include _ . and -"
|
||||
);
|
||||
const available = await UsersDAO.isNameAvailable(name);
|
||||
if (!available) {
|
||||
throw new MonkeyError(409, "Username unavailable");
|
||||
}
|
||||
|
||||
const available = await UsersDAO.isNameAvailable(name);
|
||||
if (!available) throw new MonkeyError(400, "Username unavailable");
|
||||
return new MonkeyResponse("Username available");
|
||||
}
|
||||
|
||||
static async updateEmail(req, _res) {
|
||||
static async updateEmail(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
const { newEmail } = req.body;
|
||||
|
||||
|
|
@ -81,11 +78,13 @@ class UserController {
|
|||
} catch (e) {
|
||||
throw new MonkeyError(400, e.message, "update email", uid);
|
||||
}
|
||||
|
||||
Logger.log("user_email_updated", `changed email to ${newEmail}`, uid);
|
||||
|
||||
return new MonkeyResponse("Email updated");
|
||||
}
|
||||
|
||||
static async getUser(req, _res) {
|
||||
static async getUser(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
|
||||
const { email, uid } = req.ctx.decodedToken;
|
||||
|
||||
let userInfo;
|
||||
|
|
@ -103,133 +102,109 @@ class UserController {
|
|||
);
|
||||
}
|
||||
}
|
||||
let agent = uaparser(req.headers["user-agent"]);
|
||||
let logobj = {
|
||||
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,
|
||||
};
|
||||
if (agent.device.vendor) {
|
||||
logobj.device =
|
||||
agent.device.vendor +
|
||||
" " +
|
||||
agent.device.model +
|
||||
" " +
|
||||
agent.device.type;
|
||||
}
|
||||
Logger.log("user_data_requested", logobj, uid);
|
||||
|
||||
const agentLog = buildAgentLog(req);
|
||||
Logger.log("user_data_requested", agentLog, uid);
|
||||
|
||||
return new MonkeyResponse("User data retrieved", cleanUser(userInfo));
|
||||
}
|
||||
|
||||
static async linkDiscord(req, _res) {
|
||||
static async linkDiscord(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
const {
|
||||
data: { tokenType, accessToken },
|
||||
} = req.body;
|
||||
|
||||
let requser;
|
||||
try {
|
||||
requser = await UsersDAO.getUser(uid);
|
||||
} catch (e) {
|
||||
requser = null;
|
||||
}
|
||||
if (requser?.banned === true) {
|
||||
const userInfo = await UsersDAO.getUser(uid);
|
||||
if (userInfo.banned) {
|
||||
throw new MonkeyError(403, "Banned accounts cannot link with Discord");
|
||||
}
|
||||
|
||||
const discordFetch = await fetch("https://discord.com/api/users/@me", {
|
||||
headers: {
|
||||
authorization: `${req.body.data.tokenType} ${req.body.data.accessToken}`,
|
||||
},
|
||||
});
|
||||
const discordFetchJSON = await discordFetch.json();
|
||||
const did = discordFetchJSON.id;
|
||||
if (!did) {
|
||||
const { id: discordId } = await linkAccount(tokenType, accessToken);
|
||||
|
||||
if (!discordId) {
|
||||
throw new MonkeyError(
|
||||
500,
|
||||
"Could not get Discord account info",
|
||||
"did is undefined"
|
||||
"discord id is undefined"
|
||||
);
|
||||
}
|
||||
let user;
|
||||
try {
|
||||
user = await UsersDAO.getUserByDiscordId(did);
|
||||
} catch (e) {
|
||||
user = null;
|
||||
}
|
||||
if (user !== null) {
|
||||
|
||||
const discordIdAvailable = await UsersDAO.isDiscordIdAvailable(discordId);
|
||||
if (!discordIdAvailable) {
|
||||
throw new MonkeyError(
|
||||
400,
|
||||
"This Discord account is already linked to a different account"
|
||||
);
|
||||
}
|
||||
await UsersDAO.linkDiscord(uid, did);
|
||||
await BotDAO.linkDiscord(uid, did);
|
||||
Logger.log("user_discord_link", `linked to ${did}`, uid);
|
||||
return new MonkeyResponse("Discord account linked ", did);
|
||||
|
||||
await UsersDAO.linkDiscord(uid, discordId);
|
||||
await BotDAO.linkDiscord(uid, discordId);
|
||||
Logger.log("user_discord_link", `linked to ${discordId}`, uid);
|
||||
|
||||
return new MonkeyResponse("Discord account linked", discordId);
|
||||
}
|
||||
|
||||
static async unlinkDiscord(req, _res) {
|
||||
static async unlinkDiscord(
|
||||
req: MonkeyTypes.Request
|
||||
): Promise<MonkeyResponse> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
|
||||
let userInfo;
|
||||
try {
|
||||
userInfo = await UsersDAO.getUser(uid);
|
||||
} catch (e) {
|
||||
throw new MonkeyError(400, "User not found.");
|
||||
}
|
||||
const userInfo = await UsersDAO.getUser(uid);
|
||||
if (!userInfo.discordId) {
|
||||
throw new MonkeyError(400, "User does not have a linked Discord account");
|
||||
}
|
||||
|
||||
await BotDAO.unlinkDiscord(uid, userInfo.discordId);
|
||||
await UsersDAO.unlinkDiscord(uid);
|
||||
Logger.log("user_discord_unlinked", userInfo.discordId, uid);
|
||||
return new MonkeyResponse("Discord account unlinked ");
|
||||
|
||||
return new MonkeyResponse("Discord account unlinked");
|
||||
}
|
||||
|
||||
static async addTag(req, _res) {
|
||||
static async addTag(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
const { tagName } = req.body;
|
||||
let tag = await UsersDAO.addTag(uid, tagName);
|
||||
|
||||
const tag = await UsersDAO.addTag(uid, tagName);
|
||||
return new MonkeyResponse("Tag updated", tag);
|
||||
}
|
||||
|
||||
static async clearTagPb(req, _res) {
|
||||
static async clearTagPb(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
const { tagId } = req.params;
|
||||
|
||||
await UsersDAO.removeTagPb(uid, tagId);
|
||||
[];
|
||||
return new MonkeyResponse("Tag PB cleared");
|
||||
}
|
||||
|
||||
static async editTag(req, _res) {
|
||||
static async editTag(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
const { tagId, newName } = req.body;
|
||||
|
||||
await UsersDAO.editTag(uid, tagId, newName);
|
||||
return new MonkeyResponse("Tag updated");
|
||||
}
|
||||
|
||||
static async removeTag(req, _res) {
|
||||
static async removeTag(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
const { tagId } = req.params;
|
||||
|
||||
await UsersDAO.removeTag(uid, tagId);
|
||||
return new MonkeyResponse("Tag deleted");
|
||||
}
|
||||
|
||||
static async getTags(req, _res) {
|
||||
static async getTags(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
let tags = await UsersDAO.getTags(uid);
|
||||
if (tags == undefined) tags = [];
|
||||
return new MonkeyResponse("Tags retrieved", tags);
|
||||
|
||||
const tags = await UsersDAO.getTags(uid);
|
||||
return new MonkeyResponse("Tags retrieved", tags ?? []);
|
||||
}
|
||||
|
||||
static async updateLbMemory(req, _res) {
|
||||
static async updateLbMemory(
|
||||
req: MonkeyTypes.Request
|
||||
): Promise<MonkeyResponse> {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
const { mode, mode2, language, rank } = req.body;
|
||||
|
||||
|
|
@ -4,6 +4,7 @@ import { Router } from "express";
|
|||
import UserController from "../controllers/user";
|
||||
import { asyncHandler, validateRequest } from "../../middlewares/api-utils";
|
||||
import * as RateLimit from "../../middlewares/rate-limit";
|
||||
import { isUsernameValid } from "../../handlers/validation";
|
||||
|
||||
const router = Router();
|
||||
|
||||
|
|
@ -18,6 +19,19 @@ const tagNameValidation = joi
|
|||
"string.max": "Tag name exceeds maximum of 16 characters",
|
||||
});
|
||||
|
||||
const usernameValidation = joi
|
||||
.string()
|
||||
.required()
|
||||
.custom((value, helpers) => {
|
||||
return isUsernameValid(value)
|
||||
? value
|
||||
: helpers.error("string.pattern.base");
|
||||
})
|
||||
.messages({
|
||||
"string.pattern.base":
|
||||
"Username invalid. Name cannot contain special characters or contain more than 14 characters. Can include _ . and -",
|
||||
});
|
||||
|
||||
router.get(
|
||||
"/",
|
||||
RateLimit.userGet,
|
||||
|
|
@ -32,7 +46,7 @@ router.post(
|
|||
validateRequest({
|
||||
body: {
|
||||
email: joi.string().email(),
|
||||
name: joi.string().required(),
|
||||
name: usernameValidation,
|
||||
uid: joi.string(),
|
||||
},
|
||||
}),
|
||||
|
|
@ -44,7 +58,7 @@ router.get(
|
|||
RateLimit.userCheckName,
|
||||
validateRequest({
|
||||
params: {
|
||||
name: joi.string().required(),
|
||||
name: usernameValidation,
|
||||
},
|
||||
}),
|
||||
asyncHandler(UserController.checkName)
|
||||
|
|
@ -63,7 +77,7 @@ router.patch(
|
|||
authenticateRequest(),
|
||||
validateRequest({
|
||||
body: {
|
||||
name: joi.string().required(),
|
||||
name: usernameValidation,
|
||||
},
|
||||
}),
|
||||
asyncHandler(UserController.updateName)
|
||||
|
|
|
|||
23
backend/constants/profanities.ts
Normal file
23
backend/constants/profanities.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Sorry for the bad words
|
||||
const profanities = [
|
||||
"miodec",
|
||||
"bitly",
|
||||
"fuck",
|
||||
"bitch",
|
||||
"shit",
|
||||
"pussy",
|
||||
"nigga",
|
||||
"niqqa",
|
||||
"niqqer",
|
||||
"nigger",
|
||||
"ni99a",
|
||||
"ni99er",
|
||||
"niggas",
|
||||
"niga",
|
||||
"niger",
|
||||
"cunt",
|
||||
"faggot",
|
||||
"retard",
|
||||
];
|
||||
|
||||
export default profanities;
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
// const MonkeyError = require("../handlers/error");
|
||||
import db from "../init/db";
|
||||
import { roundTo2 } from "../handlers/misc";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import _ from "lodash";
|
||||
import { isUsernameValid } from "../handlers/validation";
|
||||
import { updateAuthEmail } from "../handlers/auth";
|
||||
import { checkAndUpdatePb } from "../handlers/pb";
|
||||
import db from "../init/db";
|
||||
import MonkeyError from "../handlers/error";
|
||||
import { ObjectId } from "mongodb";
|
||||
|
||||
class UsersDAO {
|
||||
static async addUser(name, email, uid) {
|
||||
const user = await db.collection("users").findOne({ uid });
|
||||
|
|
@ -75,11 +77,9 @@ class UsersDAO {
|
|||
return user;
|
||||
}
|
||||
|
||||
static async getUserByDiscordId(discordId) {
|
||||
static async isDiscordIdAvailable(discordId) {
|
||||
const user = await db.collection("users").findOne({ discordId });
|
||||
if (!user)
|
||||
throw new MonkeyError(404, "User not found", "get user by discord id");
|
||||
return user;
|
||||
return _.isNil(user);
|
||||
}
|
||||
|
||||
static async addTag(uid, name) {
|
||||
|
|
|
|||
34
backend/handlers/discord.ts
Normal file
34
backend/handlers/discord.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import fetch from "node-fetch";
|
||||
|
||||
const BASE_URL = "https://discord.com/api";
|
||||
|
||||
interface DiscordUser {
|
||||
id: string;
|
||||
username: string;
|
||||
discriminator: string;
|
||||
avatar?: string;
|
||||
bot?: boolean;
|
||||
system?: boolean;
|
||||
mfa_enabled?: boolean;
|
||||
banner?: string;
|
||||
accent_color?: number;
|
||||
locale?: string;
|
||||
verified?: boolean;
|
||||
email?: string;
|
||||
flags?: number;
|
||||
premium_type?: number;
|
||||
public_flags?: number;
|
||||
}
|
||||
|
||||
export async function linkAccount(
|
||||
tokenType: string,
|
||||
accessToken: string
|
||||
): Promise<DiscordUser> {
|
||||
const response = await fetch(`${BASE_URL}/users/@me`, {
|
||||
headers: {
|
||||
authorization: `${tokenType} ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
return (await response.json()) as DiscordUser;
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import uaparser from "ua-parser-js";
|
||||
|
||||
export function roundTo2(num) {
|
||||
return Math.round((num + Number.EPSILON) * 100) / 100;
|
||||
}
|
||||
|
|
@ -44,3 +46,25 @@ export function base64UrlEncode(string) {
|
|||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
export function isUsernameValid(name) {
|
||||
if (name === null || name === undefined || name === "") return false;
|
||||
if (/.*miodec.*/.test(name.toLowerCase())) return false;
|
||||
//sorry for the bad words
|
||||
if (
|
||||
/.*(bitly|fuck|bitch|shit|pussy|nigga|niqqa|niqqer|nigger|ni99a|ni99er|niggas|niga|niger|cunt|faggot|retard).*/.test(
|
||||
name.toLowerCase()
|
||||
)
|
||||
)
|
||||
return false;
|
||||
if (name.length > 14) return false;
|
||||
if (/^\..*/.test(name.toLowerCase())) return false;
|
||||
return /^[0-9a-zA-Z_.-]+$/.test(name);
|
||||
}
|
||||
|
||||
export function isTagPresetNameValid(name) {
|
||||
if (name === null || name === undefined || name === "") return false;
|
||||
if (name.length > 16) return false;
|
||||
return /^[0-9a-zA-Z_.-]+$/.test(name);
|
||||
}
|
||||
36
backend/handlers/validation.ts
Normal file
36
backend/handlers/validation.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import _ from "lodash";
|
||||
import profanities from "../constants/profanities";
|
||||
|
||||
export function inRange(value: number, min: number, max: number): boolean {
|
||||
return value >= min && value <= max;
|
||||
}
|
||||
|
||||
export function isUsernameValid(name: string): boolean {
|
||||
if (_.isNil(name) || !inRange(name.length, 1, 14)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const normalizedName = name.toLowerCase();
|
||||
|
||||
const beginsWithPeriod = /^\..*/.test(normalizedName);
|
||||
if (beginsWithPeriod) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isProfanity = profanities.find((profanity) =>
|
||||
normalizedName.includes(profanity)
|
||||
);
|
||||
if (isProfanity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return /^[0-9a-zA-Z_.-]+$/.test(name);
|
||||
}
|
||||
|
||||
export function isTagPresetNameValid(name: string): boolean {
|
||||
if (_.isNil(name) || !inRange(name.length, 1, 16)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return /^[0-9a-zA-Z_.-]+$/.test(name);
|
||||
}
|
||||
61
backend/package-lock.json
generated
61
backend/package-lock.json
generated
|
|
@ -38,7 +38,9 @@
|
|||
"@types/lodash": "4.14.178",
|
||||
"@types/mongodb": "4.0.7",
|
||||
"@types/node": "17.0.18",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
"@types/swagger-stats": "0.95.4",
|
||||
"@types/ua-parser-js": "0.7.36",
|
||||
"@types/uuid": "8.3.4"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -749,6 +751,30 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.18.tgz",
|
||||
"integrity": "sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA=="
|
||||
},
|
||||
"node_modules/@types/node-fetch": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||
"integrity": "sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"form-data": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node-fetch/node_modules/form-data": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
|
||||
"integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.9.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
|
||||
|
|
@ -781,6 +807,12 @@
|
|||
"prom-client": ">=11.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ua-parser-js": {
|
||||
"version": "0.7.36",
|
||||
"resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz",
|
||||
"integrity": "sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
|
|
@ -5539,6 +5571,29 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.18.tgz",
|
||||
"integrity": "sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA=="
|
||||
},
|
||||
"@types/node-fetch": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||
"integrity": "sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"form-data": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
|
||||
"integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/qs": {
|
||||
"version": "6.9.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
|
||||
|
|
@ -5571,6 +5626,12 @@
|
|||
"prom-client": ">=11.5.3"
|
||||
}
|
||||
},
|
||||
"@types/ua-parser-js": {
|
||||
"version": "0.7.36",
|
||||
"resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz",
|
||||
"integrity": "sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/uuid": {
|
||||
"version": "8.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
|
||||
|
|
|
|||
|
|
@ -45,7 +45,9 @@
|
|||
"@types/lodash": "4.14.178",
|
||||
"@types/mongodb": "4.0.7",
|
||||
"@types/node": "17.0.18",
|
||||
"@types/node-fetch": "2.6.1",
|
||||
"@types/swagger-stats": "0.95.4",
|
||||
"@types/ua-parser-js": "0.7.36",
|
||||
"@types/uuid": "8.3.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
backend/types/types.d.ts
vendored
1
backend/types/types.d.ts
vendored
|
|
@ -62,6 +62,7 @@ declare namespace MonkeyTypes {
|
|||
quoteMod?: boolean;
|
||||
cannotReport?: boolean;
|
||||
apeKeys?: Record<string, ApeKey>;
|
||||
banned?: boolean;
|
||||
}
|
||||
|
||||
interface ApeKey {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue