Remove class syntax from controllers (#2791)

* Remove class syntax from controllers

* Specify base and rename
This commit is contained in:
Bruce Berrios 2022-03-30 18:20:13 -04:00 committed by GitHub
parent 433b2fc130
commit beb7e35dcb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 889 additions and 860 deletions

View file

@ -0,0 +1,82 @@
import _ from "lodash";
import { randomBytes } from "crypto";
import { hash } from "bcrypt";
import ApeKeysDAO from "../../dao/ape-keys";
import MonkeyError from "../../utils/error";
import { MonkeyResponse } from "../../utils/monkey-response";
import { base64UrlEncode } from "../../utils/misc";
function cleanApeKey(apeKey: MonkeyTypes.ApeKey): Partial<MonkeyTypes.ApeKey> {
return _.omit(apeKey, "hash", "_id", "uid", "useCount");
}
export async function getApeKeys(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const apeKeys = await ApeKeysDAO.getApeKeys(uid);
const cleanedKeys = _(apeKeys).keyBy("_id").mapValues(cleanApeKey).value();
return new MonkeyResponse("ApeKeys retrieved", cleanedKeys);
}
export async function generateApeKey(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { name, enabled } = req.body;
const { uid } = req.ctx.decodedToken;
const { maxKeysPerUser, apeKeyBytes, apeKeySaltRounds } =
req.ctx.configuration.apeKeys;
const currentNumberOfApeKeys = await ApeKeysDAO.countApeKeysForUser(uid);
if (currentNumberOfApeKeys >= maxKeysPerUser) {
throw new MonkeyError(409, "Maximum number of ApeKeys have been generated");
}
const apiKey = randomBytes(apeKeyBytes).toString("base64url");
const saltyHash = await hash(apiKey, apeKeySaltRounds);
const apeKey: MonkeyTypes.ApeKey = {
name,
enabled,
uid,
hash: saltyHash,
createdOn: Date.now(),
modifiedOn: Date.now(),
lastUsedOn: -1,
useCount: 0,
};
const apeKeyId = await ApeKeysDAO.addApeKey(apeKey);
return new MonkeyResponse("ApeKey generated", {
apeKey: base64UrlEncode(`${apeKeyId}.${apiKey}`),
apeKeyId,
apeKeyDetails: cleanApeKey(apeKey),
});
}
export async function editApeKey(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { apeKeyId } = req.params;
const { name, enabled } = req.body;
const { uid } = req.ctx.decodedToken;
await ApeKeysDAO.editApeKey(uid, apeKeyId, name, enabled);
return new MonkeyResponse("ApeKey updated");
}
export async function deleteApeKey(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { apeKeyId } = req.params;
const { uid } = req.ctx.decodedToken;
await ApeKeysDAO.deleteApeKey(uid, apeKeyId);
return new MonkeyResponse("ApeKey deleted");
}

View file

@ -1,83 +0,0 @@
import _ from "lodash";
import { randomBytes } from "crypto";
import { hash } from "bcrypt";
import ApeKeysDAO from "../../dao/ape-keys";
import MonkeyError from "../../utils/error";
import { MonkeyResponse } from "../../utils/monkey-response";
import { base64UrlEncode } from "../../utils/misc";
function cleanApeKey(apeKey: MonkeyTypes.ApeKey): Partial<MonkeyTypes.ApeKey> {
return _.omit(apeKey, "hash", "_id", "uid", "useCount");
}
class ApeKeysController {
static async getApeKeys(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const apeKeys = await ApeKeysDAO.getApeKeys(uid);
const hashlessKeys = _(apeKeys).keyBy("_id").mapValues(cleanApeKey).value();
return new MonkeyResponse("ApeKeys retrieved", hashlessKeys);
}
static async generateApeKey(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { name, enabled } = req.body;
const { uid } = req.ctx.decodedToken;
const { maxKeysPerUser, apeKeyBytes, apeKeySaltRounds } =
req.ctx.configuration.apeKeys;
const currentNumberOfApeKeys = await ApeKeysDAO.countApeKeysForUser(uid);
if (currentNumberOfApeKeys >= maxKeysPerUser) {
throw new MonkeyError(
409,
"Maximum number of ApeKeys have been generated"
);
}
const apiKey = randomBytes(apeKeyBytes).toString("base64url");
const saltyHash = await hash(apiKey, apeKeySaltRounds);
const apeKey: MonkeyTypes.ApeKey = {
name,
enabled,
uid,
hash: saltyHash,
createdOn: Date.now(),
modifiedOn: Date.now(),
lastUsedOn: -1,
useCount: 0,
};
const apeKeyId = await ApeKeysDAO.addApeKey(apeKey);
return new MonkeyResponse("ApeKey generated", {
apeKey: base64UrlEncode(`${apeKeyId}.${apiKey}`),
apeKeyId,
apeKeyDetails: cleanApeKey(apeKey),
});
}
static async editApeKey(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { apeKeyId } = req.params;
const { name, enabled } = req.body;
const { uid } = req.ctx.decodedToken;
await ApeKeysDAO.editApeKey(uid, apeKeyId, name, enabled);
return new MonkeyResponse("ApeKey updated");
}
static async deleteApeKey(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { apeKeyId } = req.params;
const { uid } = req.ctx.decodedToken;
await ApeKeysDAO.deleteApeKey(uid, apeKeyId);
return new MonkeyResponse("ApeKey deleted");
}
}
export default ApeKeysController;

View file

@ -1,22 +1,22 @@
import ConfigDAO from "../../dao/config";
import { MonkeyResponse } from "../../utils/monkey-response";
class ConfigController {
static async getConfig(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
export async function getConfig(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const data = await ConfigDAO.getConfig(uid);
return new MonkeyResponse("Configuration retrieved", data);
}
static async saveConfig(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { config } = req.body;
const { uid } = req.ctx.decodedToken;
await ConfigDAO.saveConfig(uid, config);
return new MonkeyResponse("Config updated");
}
const data = await ConfigDAO.getConfig(uid);
return new MonkeyResponse("Configuration retrieved", data);
}
export default ConfigController;
export async function saveConfig(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { config } = req.body;
const { uid } = req.ctx.decodedToken;
await ConfigDAO.saveConfig(uid, config);
return new MonkeyResponse("Config updated");
}

View file

@ -0,0 +1,54 @@
import _ from "lodash";
import { MonkeyResponse } from "../../utils/monkey-response";
import LeaderboardsDAO from "../../dao/leaderboards";
export async function getLeaderboard(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { language, mode, mode2, skip, limit = 50 } = req.query;
const { uid } = req.ctx.decodedToken;
const queryLimit = Math.min(parseInt(limit as string, 10), 50);
const leaderboard = await LeaderboardsDAO.get(
mode,
mode2,
language,
parseInt(skip as string, 10),
queryLimit
);
if (leaderboard === false) {
return new MonkeyResponse(
"Leaderboard is currently updating. Please try again in a few seconds.",
null,
503
);
}
const normalizedLeaderboard = _.map(leaderboard as any[], (entry) => {
return uid && entry.uid === uid
? entry
: _.omit(entry, ["discordId", "uid", "difficulty", "language"]);
});
return new MonkeyResponse("Leaderboard retrieved", normalizedLeaderboard);
}
export async function getRankFromLeaderboard(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { language, mode, mode2 } = req.query;
const { uid } = req.ctx.decodedToken;
const data = await LeaderboardsDAO.getRank(mode, mode2, language, uid);
if (data === false) {
return new MonkeyResponse(
"Leaderboard is currently updating. Please try again in a few seconds.",
null,
503
);
}
return new MonkeyResponse("Rank retrieved", data);
}

View file

@ -1,54 +0,0 @@
import _ from "lodash";
import { MonkeyResponse } from "../../utils/monkey-response";
import LeaderboardsDAO from "../../dao/leaderboards";
class LeaderboardsController {
static async get(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { language, mode, mode2, skip, limit = 50 } = req.query;
const { uid } = req.ctx.decodedToken;
const queryLimit = Math.min(parseInt(limit as string, 10), 50);
const leaderboard = await LeaderboardsDAO.get(
mode,
mode2,
language,
parseInt(skip as string, 10),
queryLimit
);
if (leaderboard === false) {
return new MonkeyResponse(
"Leaderboard is currently updating. Please try again in a few seconds.",
null,
503
);
}
const normalizedLeaderboard = _.map(leaderboard as any[], (entry) => {
return uid && entry.uid === uid
? entry
: _.omit(entry, ["discordId", "uid", "difficulty", "language"]);
});
return new MonkeyResponse("Leaderboard retrieved", normalizedLeaderboard);
}
static async getRank(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { language, mode, mode2 } = req.query;
const { uid } = req.ctx.decodedToken;
const data = await LeaderboardsDAO.getRank(mode, mode2, language, uid);
if (data === false) {
return new MonkeyResponse(
"Leaderboard is currently updating. Please try again in a few seconds.",
null,
503
);
}
return new MonkeyResponse("Rank retrieved", data);
}
}
export default LeaderboardsController;

View file

@ -1,40 +1,44 @@
import PresetDAO from "../../dao/preset";
import { MonkeyResponse } from "../../utils/monkey-response";
class PresetController {
static async getPresets(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
export async function getPresets(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const data = await PresetDAO.getPresets(uid);
return new MonkeyResponse("Preset retrieved", data);
}
static async addPreset(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { name, config } = req.body;
const { uid } = req.ctx.decodedToken;
const data = await PresetDAO.addPreset(uid, name, config);
return new MonkeyResponse("Preset created", data);
}
static async editPreset(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { _id, name, config } = req.body;
const { uid } = req.ctx.decodedToken;
await PresetDAO.editPreset(uid, _id, name, config);
return new MonkeyResponse("Preset updated");
}
static async removePreset(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { presetId } = req.params;
const { uid } = req.ctx.decodedToken;
await PresetDAO.removePreset(uid, presetId);
return new MonkeyResponse("Preset deleted");
}
const data = await PresetDAO.getPresets(uid);
return new MonkeyResponse("Preset retrieved", data);
}
export default PresetController;
export async function addPreset(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { name, config } = req.body;
const { uid } = req.ctx.decodedToken;
const data = await PresetDAO.addPreset(uid, name, config);
return new MonkeyResponse("Preset created", data);
}
export async function editPreset(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { _id, name, config } = req.body;
const { uid } = req.ctx.decodedToken;
await PresetDAO.editPreset(uid, _id, name, config);
return new MonkeyResponse("Preset updated");
}
export async function removePreset(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { presetId } = req.params;
const { uid } = req.ctx.decodedToken;
await PresetDAO.removePreset(uid, presetId);
return new MonkeyResponse("Preset deleted");
}

View file

@ -1,11 +1,9 @@
import PsaDAO from "../../dao/psa";
import { MonkeyResponse } from "../../utils/monkey-response";
class PsaController {
static async get(_req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const data = await PsaDAO.get();
return new MonkeyResponse("PSAs retrieved", data);
}
export async function getPsas(
_req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const data = await PsaDAO.get();
return new MonkeyResponse("PSAs retrieved", data);
}
export default PsaController;

View file

@ -0,0 +1,138 @@
import _ from "lodash";
import { v4 as uuidv4 } from "uuid";
import UserDAO from "../../dao/user";
import ReportDAO from "../../dao/report";
import NewQuotesDao from "../../dao/new-quotes";
import QuoteRatingsDAO from "../../dao/quote-ratings";
import MonkeyError from "../../utils/error";
import { verify } from "../../utils/captcha";
import Logger from "../../utils/logger";
import { MonkeyResponse } from "../../utils/monkey-response";
async function verifyCaptcha(captcha: string): Promise<void> {
if (!(await verify(captcha))) {
throw new MonkeyError(422, "Captcha check failed");
}
}
export async function getQuotes(
_req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const data = await NewQuotesDao.get();
return new MonkeyResponse("Quote submissions retrieved", data);
}
export async function addQuote(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { text, source, language, captcha } = req.body;
await verifyCaptcha(captcha);
await NewQuotesDao.add(text, source, language, uid);
return new MonkeyResponse("Quote submission added");
}
export async function approveQuote(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { quoteId, editText, editSource } = req.body;
const data = await NewQuotesDao.approve(quoteId, editText, editSource);
Logger.logToDb("system_quote_approved", data, uid);
return new MonkeyResponse(data.message, data.quote);
}
export async function refuseQuote(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { quoteId } = req.body;
await NewQuotesDao.refuse(quoteId);
return new MonkeyResponse("Quote refused");
}
export async function getRating(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { quoteId, language } = req.query;
const data = await QuoteRatingsDAO.get(
parseInt(quoteId as string, 10),
language as string
);
return new MonkeyResponse("Rating retrieved", data);
}
export async function submitRating(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { quoteId, rating, language } = req.body;
const user = await UserDAO.getUser(uid);
if (!user) {
throw new MonkeyError(401, "User not found.");
}
const normalizedQuoteId = parseInt(quoteId as string, 10);
const normalizedRating = Math.round(parseInt(rating as string, 10));
const userQuoteRatings = user.quoteRatings ?? {};
const currentRating = userQuoteRatings[language]?.[normalizedQuoteId] ?? 0;
const newRating = normalizedRating - currentRating;
const shouldUpdateRating = currentRating !== 0;
await QuoteRatingsDAO.submit(
quoteId,
language,
newRating,
shouldUpdateRating
);
_.setWith(
userQuoteRatings,
`[${language}][${normalizedQuoteId}]`,
normalizedRating,
Object
);
await UserDAO.updateQuoteRatings(uid, userQuoteRatings);
const responseMessage = `Rating ${
shouldUpdateRating ? "updated" : "submitted"
}`;
return new MonkeyResponse(responseMessage);
}
export async function reportQuote(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const {
quoteReport: { maxReports, contentReportLimit },
} = req.ctx.configuration;
const { quoteId, quoteLanguage, reason, comment, captcha } = req.body;
await verifyCaptcha(captcha);
const newReport: MonkeyTypes.Report = {
id: uuidv4(),
type: "quote",
timestamp: new Date().getTime(),
uid,
contentId: `${quoteLanguage}-${quoteId}`,
reason,
comment,
};
await ReportDAO.createReport(newReport, maxReports, contentReportLimit);
return new MonkeyResponse("Quote reported");
}

View file

@ -1,128 +0,0 @@
import _ from "lodash";
import { v4 as uuidv4 } from "uuid";
import UserDAO from "../../dao/user";
import ReportDAO from "../../dao/report";
import NewQuotesDao from "../../dao/new-quotes";
import QuoteRatingsDAO from "../../dao/quote-ratings";
import MonkeyError from "../../utils/error";
import { verify } from "../../utils/captcha";
import Logger from "../../utils/logger";
import { MonkeyResponse } from "../../utils/monkey-response";
async function verifyCaptcha(captcha: string): Promise<void> {
if (!(await verify(captcha))) {
throw new MonkeyError(422, "Captcha check failed");
}
}
class QuotesController {
static async getQuotes(_req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const data = await NewQuotesDao.get();
return new MonkeyResponse("Quote submissions retrieved", data);
}
static async addQuote(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { text, source, language, captcha } = req.body;
await verifyCaptcha(captcha);
await NewQuotesDao.add(text, source, language, uid);
return new MonkeyResponse("Quote submission added");
}
static async approveQuote(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { quoteId, editText, editSource } = req.body;
const data = await NewQuotesDao.approve(quoteId, editText, editSource);
Logger.logToDb("system_quote_approved", data, uid);
return new MonkeyResponse(data.message, data.quote);
}
static async refuseQuote(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { quoteId } = req.body;
await NewQuotesDao.refuse(quoteId);
return new MonkeyResponse("Quote refused");
}
static async getRating(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { quoteId, language } = req.query;
const data = await QuoteRatingsDAO.get(
parseInt(quoteId as string),
language as string
);
return new MonkeyResponse("Rating retrieved", data);
}
static async submitRating(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { quoteId, rating, language } = req.body;
const user = await UserDAO.getUser(uid);
if (!user) {
throw new MonkeyError(401, "User not found.");
}
const normalizedQuoteId = parseInt(quoteId as string);
const normalizedRating = Math.round(parseInt(rating as string));
const userQuoteRatings = user.quoteRatings ?? {};
const currentRating = userQuoteRatings[language]?.[normalizedQuoteId] ?? 0;
const newRating = normalizedRating - currentRating;
const shouldUpdateRating = currentRating !== 0;
await QuoteRatingsDAO.submit(
quoteId,
language,
newRating,
shouldUpdateRating
);
_.setWith(
userQuoteRatings,
`[${language}][${normalizedQuoteId}]`,
normalizedRating,
Object
);
await UserDAO.updateQuoteRatings(uid, userQuoteRatings);
const responseMessage = `Rating ${
shouldUpdateRating ? "updated" : "submitted"
}`;
return new MonkeyResponse(responseMessage);
}
static async reportQuote(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const {
quoteReport: { maxReports, contentReportLimit },
} = req.ctx.configuration;
const { quoteId, quoteLanguage, reason, comment, captcha } = req.body;
await verifyCaptcha(captcha);
const newReport: MonkeyTypes.Report = {
id: uuidv4(),
type: "quote",
timestamp: new Date().getTime(),
uid,
contentId: `${quoteLanguage}-${quoteId}`,
reason,
comment,
};
await ReportDAO.createReport(newReport, maxReports, contentReportLimit);
return new MonkeyResponse("Quote reported");
}
}
export default QuotesController;

View file

@ -34,74 +34,181 @@ try {
}
}
class ResultController {
static async getResults(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const results = await ResultDAO.getResults(uid);
return new MonkeyResponse("Result retrieved", results);
export async function getResults(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const results = await ResultDAO.getResults(uid);
return new MonkeyResponse("Result retrieved", results);
}
export async function deleteAll(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
await ResultDAO.deleteAll(uid);
Logger.logToDb("user_results_deleted", "", uid);
return new MonkeyResponse("All results deleted");
}
export async function updateTags(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { tagIds, resultId } = req.body;
await ResultDAO.updateTags(uid, resultId, tagIds);
return new MonkeyResponse("Result tags updated");
}
export async function addResult(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const useRedisForBotTasks = req.ctx.configuration.useRedisForBotTasks.enabled;
const result = Object.assign({}, req.body.result);
result.uid = uid;
if (result.wpm === result.raw && result.acc !== 100) {
const status = MonkeyStatusCodes.RESULT_DATA_INVALID;
throw new MonkeyError(status.code, "Bad input"); // todo move this
}
if (isTestTooShort(result)) {
const status = MonkeyStatusCodes.TEST_TOO_SHORT;
throw new MonkeyError(status.code, status.message);
}
static async deleteAll(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
await ResultDAO.deleteAll(uid);
Logger.logToDb("user_results_deleted", "", uid);
return new MonkeyResponse("All results deleted");
const resulthash = result.hash;
delete result.hash;
delete result.stringified;
if (
req.ctx.configuration.resultObjectHashCheck.enabled &&
resulthash.length === 40
) {
//if its not 64 that means client is still using old hashing package
const serverhash = objectHash(result);
if (serverhash !== resulthash) {
Logger.logToDb(
"incorrect_result_hash",
{
serverhash,
resulthash,
result,
},
uid
);
const status = MonkeyStatusCodes.RESULT_HASH_INVALID;
throw new MonkeyError(status.code, "Incorrect result hash");
}
}
static async updateTags(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { tagIds, resultId } = req.body;
await ResultDAO.updateTags(uid, resultId, tagIds);
return new MonkeyResponse("Result tags updated");
}
static async addResult(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const useRedisForBotTasks =
req.ctx.configuration.useRedisForBotTasks.enabled;
const result = Object.assign({}, req.body.result);
result.uid = uid;
if (result.wpm === result.raw && result.acc !== 100) {
if (anticheatImplemented()) {
if (!validateResult(result)) {
const status = MonkeyStatusCodes.RESULT_DATA_INVALID;
throw new MonkeyError(status.code, "Bad input"); // todo move this
throw new MonkeyError(status.code, "Result data doesn't make sense");
}
if (isTestTooShort(result)) {
const status = MonkeyStatusCodes.TEST_TOO_SHORT;
throw new MonkeyError(status.code, status.message);
} else {
if (process.env.MODE !== "dev") {
throw new Error("No anticheat module found");
}
Logger.warning(
"No anticheat module found. Continuing in dev mode, results will not be validated."
);
}
const resulthash = result.hash;
delete result.hash;
delete result.stringified;
if (
req.ctx.configuration.resultObjectHashCheck.enabled &&
resulthash.length === 40
) {
//if its not 64 that means client is still using old hashing package
const serverhash = objectHash(result);
if (serverhash !== resulthash) {
Logger.logToDb(
"incorrect_result_hash",
{
serverhash,
resulthash,
result,
},
uid
);
const status = MonkeyStatusCodes.RESULT_HASH_INVALID;
throw new MonkeyError(status.code, "Incorrect result hash");
}
}
//dont use - result timestamp is unreliable, can be changed by system time and stuff
// if (result.timestamp > Math.round(Date.now() / 1000) * 1000 + 10) {
// log(
// "time_traveler",
// {
// resultTimestamp: result.timestamp,
// serverTimestamp: Math.round(Date.now() / 1000) * 1000 + 10,
// },
// uid
// );
// return res.status(400).json({ message: "Time traveler detected" });
// this probably wont work if we replace the timestamp with the server time later
// let timestampres = await ResultDAO.getResultByTimestamp(
// uid,
// result.timestamp
// );
// if (timestampres) {
// return res.status(400).json({ message: "Duplicate result" });
// }
//convert result test duration to miliseconds
const testDurationMilis = result.testDuration * 1000;
//get latest result ordered by timestamp
let lastResultTimestamp;
try {
lastResultTimestamp = (await ResultDAO.getLastResult(uid)).timestamp;
} catch (e) {
lastResultTimestamp = null;
}
result.timestamp = Math.floor(Date.now() / 1000) * 1000;
//check if now is earlier than last result plus duration (-1 second as a buffer)
const earliestPossible = lastResultTimestamp + testDurationMilis;
const nowNoMilis = Math.floor(Date.now() / 1000) * 1000;
if (lastResultTimestamp && nowNoMilis < earliestPossible - 1000) {
Logger.logToDb(
"invalid_result_spacing",
{
lastTimestamp: lastResultTimestamp,
earliestPossible,
now: nowNoMilis,
testDuration: testDurationMilis,
difference: nowNoMilis - earliestPossible,
},
uid
);
const status = MonkeyStatusCodes.RESULT_SPACING_INVALID;
throw new MonkeyError(status.code, "Invalid result spacing");
}
try {
result.keySpacingStats = {
average:
result.keySpacing.reduce((previous, current) => (current += previous)) /
result.keySpacing.length,
sd: stdDev(result.keySpacing),
};
} catch (e) {
//
}
try {
result.keyDurationStats = {
average:
result.keyDuration.reduce(
(previous, current) => (current += previous)
) / result.keyDuration.length,
sd: stdDev(result.keyDuration),
};
} catch (e) {
//
}
const user = await UserDAO.getUser(uid);
//check keyspacing and duration here for bots
if (
result.mode === "time" &&
result.wpm > 130 &&
result.testDuration < 122 &&
(user.verified === false || user.verified === undefined)
) {
if (!result.keySpacingStats || !result.keyDurationStats) {
const status = MonkeyStatusCodes.MISSING_KEY_DATA;
throw new MonkeyError(status.code, "Missing key data");
}
if (anticheatImplemented()) {
if (!validateResult(result)) {
const status = MonkeyStatusCodes.RESULT_DATA_INVALID;
throw new MonkeyError(status.code, "Result data doesn't make sense");
if (!validateKeys(result, uid)) {
const status = MonkeyStatusCodes.BOT_DETECTED;
throw new MonkeyError(status.code, "Possible bot detected");
}
} else {
if (process.env.MODE !== "dev") {
@ -111,202 +218,95 @@ class ResultController {
"No anticheat module found. Continuing in dev mode, results will not be validated."
);
}
//dont use - result timestamp is unreliable, can be changed by system time and stuff
// if (result.timestamp > Math.round(Date.now() / 1000) * 1000 + 10) {
// log(
// "time_traveler",
// {
// resultTimestamp: result.timestamp,
// serverTimestamp: Math.round(Date.now() / 1000) * 1000 + 10,
// },
// uid
// );
// return res.status(400).json({ message: "Time traveler detected" });
// this probably wont work if we replace the timestamp with the server time later
// let timestampres = await ResultDAO.getResultByTimestamp(
// uid,
// result.timestamp
// );
// if (timestampres) {
// return res.status(400).json({ message: "Duplicate result" });
// }
//convert result test duration to miliseconds
const testDurationMilis = result.testDuration * 1000;
//get latest result ordered by timestamp
let lastResultTimestamp;
try {
lastResultTimestamp = (await ResultDAO.getLastResult(uid)).timestamp;
} catch (e) {
lastResultTimestamp = null;
}
result.timestamp = Math.floor(Date.now() / 1000) * 1000;
//check if now is earlier than last result plus duration (-1 second as a buffer)
const earliestPossible = lastResultTimestamp + testDurationMilis;
const nowNoMilis = Math.floor(Date.now() / 1000) * 1000;
if (lastResultTimestamp && nowNoMilis < earliestPossible - 1000) {
Logger.logToDb(
"invalid_result_spacing",
{
lastTimestamp: lastResultTimestamp,
earliestPossible,
now: nowNoMilis,
testDuration: testDurationMilis,
difference: nowNoMilis - earliestPossible,
},
uid
);
const status = MonkeyStatusCodes.RESULT_SPACING_INVALID;
throw new MonkeyError(status.code, "Invalid result spacing");
}
try {
result.keySpacingStats = {
average:
result.keySpacing.reduce(
(previous, current) => (current += previous)
) / result.keySpacing.length,
sd: stdDev(result.keySpacing),
};
} catch (e) {
//
}
try {
result.keyDurationStats = {
average:
result.keyDuration.reduce(
(previous, current) => (current += previous)
) / result.keyDuration.length,
sd: stdDev(result.keyDuration),
};
} catch (e) {
//
}
const user = await UserDAO.getUser(uid);
//check keyspacing and duration here for bots
if (
result.mode === "time" &&
result.wpm > 130 &&
result.testDuration < 122 &&
(user.verified === false || user.verified === undefined)
) {
if (!result.keySpacingStats || !result.keyDurationStats) {
const status = MonkeyStatusCodes.MISSING_KEY_DATA;
throw new MonkeyError(status.code, "Missing key data");
}
if (anticheatImplemented()) {
if (!validateKeys(result, uid)) {
const status = MonkeyStatusCodes.BOT_DETECTED;
throw new MonkeyError(status.code, "Possible bot detected");
}
} else {
if (process.env.MODE !== "dev") {
throw new Error("No anticheat module found");
}
Logger.warning(
"No anticheat module found. Continuing in dev mode, results will not be validated."
);
}
}
delete result.keySpacing;
delete result.keyDuration;
delete result.smoothConsistency;
delete result.wpmConsistency;
try {
result.keyDurationStats.average = roundTo2(
result.keyDurationStats.average
);
result.keyDurationStats.sd = roundTo2(result.keyDurationStats.sd);
result.keySpacingStats.average = roundTo2(result.keySpacingStats.average);
result.keySpacingStats.sd = roundTo2(result.keySpacingStats.sd);
} catch (e) {
//
}
let isPb = false;
let tagPbs: any[] = [];
if (!result.bailedOut) {
[isPb, tagPbs] = await Promise.all([
UserDAO.checkIfPb(uid, user, result),
UserDAO.checkIfTagPb(uid, user, result),
]);
}
if (isPb) {
result.isPb = true;
}
if (result.mode === "time" && String(result.mode2) === "60") {
UserDAO.incrementBananas(uid, result.wpm);
if (isPb && user.discordId) {
if (useRedisForBotTasks) {
George.updateDiscordRole(user.discordId, result.wpm);
}
BotDAO.updateDiscordRole(user.discordId, result.wpm);
}
}
if (result.challenge && user.discordId) {
if (useRedisForBotTasks) {
George.awardChallenge(user.discordId, result.challenge);
}
BotDAO.awardChallenge(user.discordId, result.challenge);
} else {
delete result.challenge;
}
let tt = 0;
let afk = result.afkDuration;
if (afk == undefined) {
afk = 0;
}
tt = result.testDuration + result.incompleteTestSeconds - afk;
UserDAO.updateTypingStats(uid, result.restartCount, tt);
PublicStatsDAO.updateStats(result.restartCount, tt);
if (result.bailedOut === false) delete result.bailedOut;
if (result.blindMode === false) delete result.blindMode;
if (result.lazyMode === false) delete result.lazyMode;
if (result.difficulty === "normal") delete result.difficulty;
if (result.funbox === "none") delete result.funbox;
if (result.language === "english") delete result.language;
if (result.numbers === false) delete result.numbers;
if (result.punctuation === false) delete result.punctuation;
if (result.mode !== "custom") delete result.customText;
const addedResult = await ResultDAO.addResult(uid, result);
if (isPb) {
Logger.logToDb(
"user_new_pb",
`${result.mode + " " + result.mode2} ${result.wpm} ${result.acc}% ${
result.rawWpm
} ${result.consistency}% (${addedResult.insertedId})`,
uid
);
}
const data = {
isPb,
name: result.name,
tagPbs,
insertedId: addedResult.insertedId,
};
incrementResult(result);
return new MonkeyResponse("Result saved", data);
}
}
export default ResultController;
delete result.keySpacing;
delete result.keyDuration;
delete result.smoothConsistency;
delete result.wpmConsistency;
try {
result.keyDurationStats.average = roundTo2(result.keyDurationStats.average);
result.keyDurationStats.sd = roundTo2(result.keyDurationStats.sd);
result.keySpacingStats.average = roundTo2(result.keySpacingStats.average);
result.keySpacingStats.sd = roundTo2(result.keySpacingStats.sd);
} catch (e) {
//
}
let isPb = false;
let tagPbs: any[] = [];
if (!result.bailedOut) {
[isPb, tagPbs] = await Promise.all([
UserDAO.checkIfPb(uid, user, result),
UserDAO.checkIfTagPb(uid, user, result),
]);
}
if (isPb) {
result.isPb = true;
}
if (result.mode === "time" && String(result.mode2) === "60") {
UserDAO.incrementBananas(uid, result.wpm);
if (isPb && user.discordId) {
if (useRedisForBotTasks) {
George.updateDiscordRole(user.discordId, result.wpm);
}
BotDAO.updateDiscordRole(user.discordId, result.wpm);
}
}
if (result.challenge && user.discordId) {
if (useRedisForBotTasks) {
George.awardChallenge(user.discordId, result.challenge);
}
BotDAO.awardChallenge(user.discordId, result.challenge);
} else {
delete result.challenge;
}
let tt = 0;
let afk = result.afkDuration;
if (afk == undefined) {
afk = 0;
}
tt = result.testDuration + result.incompleteTestSeconds - afk;
UserDAO.updateTypingStats(uid, result.restartCount, tt);
PublicStatsDAO.updateStats(result.restartCount, tt);
if (result.bailedOut === false) delete result.bailedOut;
if (result.blindMode === false) delete result.blindMode;
if (result.lazyMode === false) delete result.lazyMode;
if (result.difficulty === "normal") delete result.difficulty;
if (result.funbox === "none") delete result.funbox;
if (result.language === "english") delete result.language;
if (result.numbers === false) delete result.numbers;
if (result.punctuation === false) delete result.punctuation;
if (result.mode !== "custom") delete result.customText;
const addedResult = await ResultDAO.addResult(uid, result);
if (isPb) {
Logger.logToDb(
"user_new_pb",
`${result.mode + " " + result.mode2} ${result.wpm} ${result.acc}% ${
result.rawWpm
} ${result.consistency}% (${addedResult.insertedId})`,
uid
);
}
const data = {
isPb,
name: result.name,
tagPbs,
insertedId: addedResult.insertedId,
};
incrementResult(result);
return new MonkeyResponse("Result saved", data);
}

View file

@ -7,268 +7,286 @@ import { linkAccount } from "../../utils/discord";
import { buildAgentLog } from "../../utils/misc";
import George from "../../tasks/george";
class UserController {
static async createNewUser(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { name } = req.body;
const { email, uid } = req.ctx.decodedToken;
export async function createNewUser(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { name } = req.body;
const { email, uid } = req.ctx.decodedToken;
await UsersDAO.addUser(name, email, uid);
Logger.logToDb("user_created", `${name} ${email}`, uid);
await UsersDAO.addUser(name, email, uid);
Logger.logToDb("user_created", `${name} ${email}`, uid);
return new MonkeyResponse("User created");
}
static async deleteUser(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const userInfo = await UsersDAO.getUser(uid);
await UsersDAO.deleteUser(uid);
Logger.logToDb("user_deleted", `${userInfo.email} ${userInfo.name}`, uid);
return new MonkeyResponse("User deleted");
}
static async updateName(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { name } = req.body;
const oldUser = await UsersDAO.getUser(uid);
await UsersDAO.updateName(uid, name);
Logger.logToDb(
"user_name_updated",
`changed name from ${oldUser.name} to ${name}`,
uid
);
return new MonkeyResponse("User's name updated");
}
static async clearPb(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
await UsersDAO.clearPb(uid);
Logger.logToDb("user_cleared_pbs", "", uid);
return new MonkeyResponse("User's PB cleared");
}
static async checkName(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { name } = req.params;
const available = await UsersDAO.isNameAvailable(name);
if (!available) {
throw new MonkeyError(409, "Username unavailable");
}
return new MonkeyResponse("Username available");
}
static async updateEmail(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { newEmail } = req.body;
try {
await UsersDAO.updateEmail(uid, newEmail);
} catch (e) {
throw new MonkeyError(404, e.message, "update email", uid);
}
Logger.logToDb("user_email_updated", `changed email to ${newEmail}`, uid);
return new MonkeyResponse("Email updated");
}
static async getUser(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { email, uid } = req.ctx.decodedToken;
let userInfo;
try {
userInfo = await UsersDAO.getUser(uid);
} catch (e) {
if (email && uid) {
userInfo = await UsersDAO.addUser(undefined, email, uid);
} else {
throw new MonkeyError(
404,
"User not found. Could not recreate user document.",
"Tried to recreate user document but either email or uid is nullish",
uid
);
}
}
const agentLog = buildAgentLog(req);
Logger.logToDb("user_data_requested", agentLog, uid);
return new MonkeyResponse("User data retrieved", userInfo);
}
static async linkDiscord(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const {
data: { tokenType, accessToken },
} = req.body;
const useRedisForBotTasks =
req.ctx.configuration.useRedisForBotTasks.enabled;
const userInfo = await UsersDAO.getUser(uid);
if (userInfo.banned) {
throw new MonkeyError(403, "Banned accounts cannot link with Discord");
}
const { id: discordId } = await linkAccount(tokenType, accessToken);
if (!discordId) {
throw new MonkeyError(
500,
"Could not get Discord account info",
"discord id is undefined"
);
}
const discordIdAvailable = await UsersDAO.isDiscordIdAvailable(discordId);
if (!discordIdAvailable) {
throw new MonkeyError(
409,
"This Discord account is already linked to a different account"
);
}
await UsersDAO.linkDiscord(uid, discordId);
if (useRedisForBotTasks) {
George.linkDiscord(discordId, uid);
}
await BotDAO.linkDiscord(uid, discordId);
Logger.logToDb("user_discord_link", `linked to ${discordId}`, uid);
return new MonkeyResponse("Discord account linked", discordId);
}
static async unlinkDiscord(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const useRedisForBotTasks =
req.ctx.configuration.useRedisForBotTasks.enabled;
const userInfo = await UsersDAO.getUser(uid);
if (!userInfo.discordId) {
throw new MonkeyError(404, "User does not have a linked Discord account");
}
if (useRedisForBotTasks) {
George.unlinkDiscord(userInfo.discordId, uid);
}
await BotDAO.unlinkDiscord(uid, userInfo.discordId);
await UsersDAO.unlinkDiscord(uid);
Logger.logToDb("user_discord_unlinked", userInfo.discordId, uid);
return new MonkeyResponse("Discord account unlinked");
}
static async addTag(req: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { tagName } = req.body;
const tag = await UsersDAO.addTag(uid, tagName);
return new MonkeyResponse("Tag updated", tag);
}
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: 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: 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: MonkeyTypes.Request): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const tags = await UsersDAO.getTags(uid);
return new MonkeyResponse("Tags retrieved", tags ?? []);
}
static async updateLbMemory(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { mode, mode2, language, rank } = req.body;
await UsersDAO.updateLbMemory(uid, mode, mode2, language, rank);
return new MonkeyResponse("Leaderboard memory updated");
}
static async getCustomThemes(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const customThemes = await UsersDAO.getThemes(uid);
return new MonkeyResponse("Custom themes retrieved", customThemes);
}
static async addCustomTheme(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { name, colors } = req.body;
const addedTheme = await UsersDAO.addTheme(uid, { name, colors });
return new MonkeyResponse("Custom theme added", {
theme: addedTheme,
});
}
static async removeCustomTheme(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { themeId } = req.body;
await UsersDAO.removeTheme(uid, themeId);
return new MonkeyResponse("Custom theme removed");
}
static async editCustomTheme(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { themeId, theme } = req.body;
await UsersDAO.editTheme(uid, themeId, theme);
return new MonkeyResponse("Custom theme updated");
}
static async getPersonalBests(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { mode, mode2 } = req.query;
const data = (await UsersDAO.getPersonalBests(uid, mode, mode2)) ?? null;
return new MonkeyResponse("Personal bests retrieved", data);
}
return new MonkeyResponse("User created");
}
export default UserController;
export async function deleteUser(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const userInfo = await UsersDAO.getUser(uid);
await UsersDAO.deleteUser(uid);
Logger.logToDb("user_deleted", `${userInfo.email} ${userInfo.name}`, uid);
return new MonkeyResponse("User deleted");
}
export async function updateName(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { name } = req.body;
const oldUser = await UsersDAO.getUser(uid);
await UsersDAO.updateName(uid, name);
Logger.logToDb(
"user_name_updated",
`changed name from ${oldUser.name} to ${name}`,
uid
);
return new MonkeyResponse("User's name updated");
}
export async function clearPb(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
await UsersDAO.clearPb(uid);
Logger.logToDb("user_cleared_pbs", "", uid);
return new MonkeyResponse("User's PB cleared");
}
export async function checkName(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { name } = req.params;
const available = await UsersDAO.isNameAvailable(name);
if (!available) {
throw new MonkeyError(409, "Username unavailable");
}
return new MonkeyResponse("Username available");
}
export async function updateEmail(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { newEmail } = req.body;
try {
await UsersDAO.updateEmail(uid, newEmail);
} catch (e) {
throw new MonkeyError(404, e.message, "update email", uid);
}
Logger.logToDb("user_email_updated", `changed email to ${newEmail}`, uid);
return new MonkeyResponse("Email updated");
}
export async function getUser(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { email, uid } = req.ctx.decodedToken;
let userInfo;
try {
userInfo = await UsersDAO.getUser(uid);
} catch (e) {
if (email && uid) {
userInfo = await UsersDAO.addUser(undefined, email, uid);
} else {
throw new MonkeyError(
404,
"User not found. Could not recreate user document.",
"Tried to recreate user document but either email or uid is nullish",
uid
);
}
}
const agentLog = buildAgentLog(req);
Logger.logToDb("user_data_requested", agentLog, uid);
return new MonkeyResponse("User data retrieved", userInfo);
}
export async function linkDiscord(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const {
data: { tokenType, accessToken },
} = req.body;
const useRedisForBotTasks = req.ctx.configuration.useRedisForBotTasks.enabled;
const userInfo = await UsersDAO.getUser(uid);
if (userInfo.banned) {
throw new MonkeyError(403, "Banned accounts cannot link with Discord");
}
const { id: discordId } = await linkAccount(tokenType, accessToken);
if (!discordId) {
throw new MonkeyError(
500,
"Could not get Discord account info",
"discord id is undefined"
);
}
const discordIdAvailable = await UsersDAO.isDiscordIdAvailable(discordId);
if (!discordIdAvailable) {
throw new MonkeyError(
409,
"This Discord account is already linked to a different account"
);
}
await UsersDAO.linkDiscord(uid, discordId);
if (useRedisForBotTasks) {
George.linkDiscord(discordId, uid);
}
await BotDAO.linkDiscord(uid, discordId);
Logger.logToDb("user_discord_link", `linked to ${discordId}`, uid);
return new MonkeyResponse("Discord account linked", discordId);
}
export async function unlinkDiscord(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const useRedisForBotTasks = req.ctx.configuration.useRedisForBotTasks.enabled;
const userInfo = await UsersDAO.getUser(uid);
if (!userInfo.discordId) {
throw new MonkeyError(404, "User does not have a linked Discord account");
}
if (useRedisForBotTasks) {
George.unlinkDiscord(userInfo.discordId, uid);
}
await BotDAO.unlinkDiscord(uid, userInfo.discordId);
await UsersDAO.unlinkDiscord(uid);
Logger.logToDb("user_discord_unlinked", userInfo.discordId, uid);
return new MonkeyResponse("Discord account unlinked");
}
export async function addTag(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { tagName } = req.body;
const tag = await UsersDAO.addTag(uid, tagName);
return new MonkeyResponse("Tag updated", tag);
}
export async function 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");
}
export async function 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");
}
export async function 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");
}
export async function getTags(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const tags = await UsersDAO.getTags(uid);
return new MonkeyResponse("Tags retrieved", tags ?? []);
}
export async function updateLbMemory(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { mode, mode2, language, rank } = req.body;
await UsersDAO.updateLbMemory(uid, mode, mode2, language, rank);
return new MonkeyResponse("Leaderboard memory updated");
}
export async function getCustomThemes(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const customThemes = await UsersDAO.getThemes(uid);
return new MonkeyResponse("Custom themes retrieved", customThemes);
}
export async function addCustomTheme(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { name, colors } = req.body;
const addedTheme = await UsersDAO.addTheme(uid, { name, colors });
return new MonkeyResponse("Custom theme added", {
theme: addedTheme,
});
}
export async function removeCustomTheme(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { themeId } = req.body;
await UsersDAO.removeTheme(uid, themeId);
return new MonkeyResponse("Custom theme removed");
}
export async function editCustomTheme(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { themeId, theme } = req.body;
await UsersDAO.editTheme(uid, themeId, theme);
return new MonkeyResponse("Custom theme updated");
}
export async function getPersonalBests(
req: MonkeyTypes.Request
): Promise<MonkeyResponse> {
const { uid } = req.ctx.decodedToken;
const { mode, mode2 } = req.query;
const data = (await UsersDAO.getPersonalBests(uid, mode, mode2)) ?? null;
return new MonkeyResponse("Personal bests retrieved", data);
}

View file

@ -7,7 +7,7 @@ import {
validateRequest,
} from "../../middlewares/api-utils";
import { authenticateRequest } from "../../middlewares/auth";
import ApeKeysController from "../controllers/ape-keys";
import * as ApeKeyController from "../controllers/ape-key";
import * as RateLimit from "../../middlewares/rate-limit";
const apeKeyNameSchema = joi
@ -43,7 +43,7 @@ router.get(
RateLimit.apeKeysGet,
authenticateRequest(),
checkIfUserCanManageApeKeys,
asyncHandler(ApeKeysController.getApeKeys)
asyncHandler(ApeKeyController.getApeKeys)
);
router.post(
@ -57,7 +57,7 @@ router.post(
enabled: joi.boolean().required(),
},
}),
asyncHandler(ApeKeysController.generateApeKey)
asyncHandler(ApeKeyController.generateApeKey)
);
router.patch(
@ -74,7 +74,7 @@ router.patch(
enabled: joi.boolean(),
},
}),
asyncHandler(ApeKeysController.editApeKey)
asyncHandler(ApeKeyController.editApeKey)
);
router.delete(
@ -87,7 +87,7 @@ router.delete(
apeKeyId: joi.string().required(),
},
}),
asyncHandler(ApeKeysController.deleteApeKey)
asyncHandler(ApeKeyController.deleteApeKey)
);
export default router;

View file

@ -2,7 +2,7 @@ import { Router } from "express";
import { authenticateRequest } from "../../middlewares/auth";
import { asyncHandler, validateRequest } from "../../middlewares/api-utils";
import configSchema from "../schemas/config-schema";
import ConfigController from "../controllers/config";
import * as ConfigController from "../controllers/config";
import * as RateLimit from "../../middlewares/rate-limit";
const router = Router();

View file

@ -3,7 +3,7 @@ import { Router } from "express";
import * as RateLimit from "../../middlewares/rate-limit";
import apeRateLimit from "../../middlewares/ape-rate-limit";
import { authenticateRequest } from "../../middlewares/auth";
import LeaderboardsController from "../controllers/leaderboards";
import * as LeaderboardController from "../controllers/leaderboard";
import { asyncHandler, validateRequest } from "../../middlewares/api-utils";
const router = Router();
@ -21,7 +21,7 @@ router.get(
limit: joi.number().min(0).max(50),
},
}),
asyncHandler(LeaderboardsController.get)
asyncHandler(LeaderboardController.getLeaderboard)
);
router.get(
@ -36,7 +36,7 @@ router.get(
mode2: joi.string().required(),
},
}),
asyncHandler(LeaderboardsController.getRank)
asyncHandler(LeaderboardController.getRankFromLeaderboard)
);
export default router;

View file

@ -1,6 +1,6 @@
import joi from "joi";
import { authenticateRequest } from "../../middlewares/auth";
import PresetController from "../controllers/preset";
import * as PresetController from "../controllers/preset";
import * as RateLimit from "../../middlewares/rate-limit";
import configSchema from "../schemas/config-schema";
import { asyncHandler, validateRequest } from "../../middlewares/api-utils";

View file

@ -1,10 +1,10 @@
import PsaController from "../controllers/psa";
import * as PsaController from "../controllers/psa";
import * as RateLimit from "../../middlewares/rate-limit";
import { asyncHandler } from "../../middlewares/api-utils";
import { Router } from "express";
const router = Router();
router.get("/", RateLimit.psaGet, asyncHandler(PsaController.get));
router.get("/", RateLimit.psaGet, asyncHandler(PsaController.getPsas));
export default router;

View file

@ -1,7 +1,7 @@
import joi from "joi";
import { authenticateRequest } from "../../middlewares/auth";
import { Router } from "express";
import QuotesController from "../controllers/quotes";
import * as QuoteController from "../controllers/quote";
import * as RateLimit from "../../middlewares/rate-limit";
import {
asyncHandler,
@ -23,7 +23,7 @@ quotesRouter.get(
RateLimit.newQuotesGet,
authenticateRequest(),
checkIfUserIsQuoteMod,
asyncHandler(QuotesController.getQuotes)
asyncHandler(QuoteController.getQuotes)
);
quotesRouter.post(
@ -46,7 +46,7 @@ quotesRouter.post(
},
validationErrorMessage: "Please fill all the fields",
}),
asyncHandler(QuotesController.addQuote)
asyncHandler(QuoteController.addQuote)
);
quotesRouter.post(
@ -62,7 +62,7 @@ quotesRouter.post(
validationErrorMessage: "Please fill all the fields",
}),
checkIfUserIsQuoteMod,
asyncHandler(QuotesController.approveQuote)
asyncHandler(QuoteController.approveQuote)
);
quotesRouter.post(
@ -75,7 +75,7 @@ quotesRouter.post(
},
}),
checkIfUserIsQuoteMod,
asyncHandler(QuotesController.refuseQuote)
asyncHandler(QuoteController.refuseQuote)
);
quotesRouter.get(
@ -88,7 +88,7 @@ quotesRouter.get(
language: joi.string().required(),
},
}),
asyncHandler(QuotesController.getRating)
asyncHandler(QuoteController.getRating)
);
quotesRouter.post(
@ -102,7 +102,7 @@ quotesRouter.post(
language: joi.string().required(),
},
}),
asyncHandler(QuotesController.submitRating)
asyncHandler(QuoteController.submitRating)
);
quotesRouter.post(
@ -137,7 +137,7 @@ quotesRouter.post(
return !user.cannotReport;
},
}),
asyncHandler(QuotesController.reportQuote)
asyncHandler(QuoteController.reportQuote)
);
export default quotesRouter;

View file

@ -1,4 +1,4 @@
import ResultController from "../controllers/result";
import * as ResultController from "../controllers/result";
import resultSchema from "../schemas/result-schema";
import {
asyncHandler,

View file

@ -1,7 +1,7 @@
import joi from "joi";
import { authenticateRequest } from "../../middlewares/auth";
import { Router } from "express";
import UserController from "../controllers/user";
import * as UserController from "../controllers/user";
import { asyncHandler, validateRequest } from "../../middlewares/api-utils";
import * as RateLimit from "../../middlewares/rate-limit";
import apeRateLimit from "../../middlewares/ape-rate-limit";