mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2025-11-08 05:03:39 +08:00
Move quote moderation logic to quotes (#2590)
* Move quote moderation logic to quotes * Add note * Fix issue
This commit is contained in:
parent
39b13804da
commit
bbc8c837df
5 changed files with 127 additions and 71 deletions
|
|
@ -1,50 +0,0 @@
|
|||
import NewQuotesDao from "../../dao/new-quotes";
|
||||
import MonkeyError from "../../handlers/error";
|
||||
import UsersDAO from "../../dao/user";
|
||||
import Logger from "../../handlers/logger.js";
|
||||
import { verify } from "../../handlers/captcha";
|
||||
import { MonkeyResponse } from "../../handlers/monkey-response";
|
||||
|
||||
class NewQuotesController {
|
||||
static async getQuotes(req, _res) {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
const userInfo = await UsersDAO.getUser(uid);
|
||||
if (!userInfo.quoteMod) {
|
||||
throw new MonkeyError(403, "You don't have permission to do this");
|
||||
}
|
||||
const data = await NewQuotesDao.get();
|
||||
return new MonkeyResponse("Quote submissions retrieved", data);
|
||||
}
|
||||
|
||||
static async addQuote(req, _res) {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
const { text, source, language, captcha } = req.body;
|
||||
if (!(await verify(captcha))) {
|
||||
throw new MonkeyError(400, "Captcha check failed");
|
||||
}
|
||||
await NewQuotesDao.add(text, source, language, uid);
|
||||
return new MonkeyResponse("Quote submission added");
|
||||
}
|
||||
|
||||
static async approve(req, _res) {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
const { quoteId, editText, editSource } = req.body;
|
||||
const userInfo = await UsersDAO.getUser(uid);
|
||||
if (!userInfo.quoteMod) {
|
||||
throw new MonkeyError(403, "You don't have permission to do this");
|
||||
}
|
||||
const data = await NewQuotesDao.approve(quoteId, editText, editSource);
|
||||
Logger.log("system_quote_approved", data, uid);
|
||||
|
||||
return new MonkeyResponse(data.message, data.quote);
|
||||
}
|
||||
|
||||
static async refuse(req, _res) {
|
||||
const { quoteId } = req.body;
|
||||
|
||||
await NewQuotesDao.refuse(quoteId);
|
||||
return new MonkeyResponse("Quote refused");
|
||||
}
|
||||
}
|
||||
|
||||
export default NewQuotesController;
|
||||
|
|
@ -2,14 +2,48 @@ 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 UsersDAO from "../../dao/user";
|
||||
import MonkeyError from "../../handlers/error";
|
||||
import { verify } from "../../handlers/captcha";
|
||||
import Logger from "../../handlers/logger";
|
||||
import { MonkeyResponse } from "../../handlers/monkey-response";
|
||||
|
||||
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;
|
||||
|
||||
if (!(await verify(captcha))) {
|
||||
throw new MonkeyError(400, "Captcha check failed");
|
||||
}
|
||||
|
||||
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.log("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;
|
||||
|
||||
|
|
@ -67,11 +101,6 @@ class QuotesController {
|
|||
quoteReport: { maxReports, contentReportLimit },
|
||||
} = req.ctx.configuration;
|
||||
|
||||
const user = await UsersDAO.getUser(uid);
|
||||
if (user.cannotReport) {
|
||||
throw new MonkeyError(403, "You don't have permission to do this.");
|
||||
}
|
||||
|
||||
const { quoteId, quoteLanguage, reason, comment, captcha } = req.body;
|
||||
|
||||
if (!(await verify(captcha))) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import joi from "joi";
|
||||
import { authenticateRequest } from "../../middlewares/auth";
|
||||
import { Router } from "express";
|
||||
import NewQuotesController from "../controllers/new-quotes";
|
||||
import QuotesController from "../controllers/quotes";
|
||||
import * as RateLimit from "../../middlewares/rate-limit";
|
||||
import {
|
||||
asyncHandler,
|
||||
checkUserPermissions,
|
||||
validateConfiguration,
|
||||
validateRequest,
|
||||
} from "../../middlewares/api-utils";
|
||||
|
|
@ -13,11 +13,18 @@ import SUPPORTED_QUOTE_LANGUAGES from "../../constants/quote-languages";
|
|||
|
||||
const quotesRouter = Router();
|
||||
|
||||
const checkIfUserIsQuoteMod = checkUserPermissions({
|
||||
criteria: (user) => {
|
||||
return user.quoteMod;
|
||||
},
|
||||
});
|
||||
|
||||
quotesRouter.get(
|
||||
"/",
|
||||
RateLimit.newQuotesGet,
|
||||
authenticateRequest(),
|
||||
asyncHandler(NewQuotesController.getQuotes)
|
||||
checkIfUserIsQuoteMod,
|
||||
asyncHandler(QuotesController.getQuotes)
|
||||
);
|
||||
|
||||
quotesRouter.post(
|
||||
|
|
@ -40,7 +47,7 @@ quotesRouter.post(
|
|||
},
|
||||
validationErrorMessage: "Please fill all the fields",
|
||||
}),
|
||||
asyncHandler(NewQuotesController.addQuote)
|
||||
asyncHandler(QuotesController.addQuote)
|
||||
);
|
||||
|
||||
quotesRouter.post(
|
||||
|
|
@ -55,7 +62,8 @@ quotesRouter.post(
|
|||
},
|
||||
validationErrorMessage: "Please fill all the fields",
|
||||
}),
|
||||
asyncHandler(NewQuotesController.approve)
|
||||
checkIfUserIsQuoteMod,
|
||||
asyncHandler(QuotesController.approveQuote)
|
||||
);
|
||||
|
||||
quotesRouter.post(
|
||||
|
|
@ -67,7 +75,8 @@ quotesRouter.post(
|
|||
quoteId: joi.string().required(),
|
||||
},
|
||||
}),
|
||||
asyncHandler(NewQuotesController.refuse)
|
||||
checkIfUserIsQuoteMod,
|
||||
asyncHandler(QuotesController.refuseQuote)
|
||||
);
|
||||
|
||||
quotesRouter.get(
|
||||
|
|
@ -127,6 +136,11 @@ quotesRouter.post(
|
|||
captcha: joi.string().required(),
|
||||
},
|
||||
}),
|
||||
checkUserPermissions({
|
||||
criteria: (user) => {
|
||||
return !user.cannotReport;
|
||||
},
|
||||
}),
|
||||
asyncHandler(QuotesController.reportQuote)
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@ import {
|
|||
handleMonkeyResponse,
|
||||
MonkeyResponse,
|
||||
} from "../handlers/monkey-response";
|
||||
import UsersDAO from "../dao/user";
|
||||
|
||||
interface ConfigurationValidationOptions {
|
||||
criteria: (configuration: MonkeyTypes.Configuration) => boolean;
|
||||
interface ValidationOptions<T> {
|
||||
criteria: (data: T) => boolean;
|
||||
invalidMessage?: string;
|
||||
}
|
||||
|
||||
|
|
@ -17,19 +18,53 @@ interface ConfigurationValidationOptions {
|
|||
* the criteria.
|
||||
*/
|
||||
function validateConfiguration(
|
||||
options: ConfigurationValidationOptions
|
||||
options: ValidationOptions<MonkeyTypes.Configuration>
|
||||
): RequestHandler {
|
||||
const { criteria, invalidMessage } = options;
|
||||
const {
|
||||
criteria,
|
||||
invalidMessage = "This service is currently unavailable.",
|
||||
} = options;
|
||||
|
||||
return (req: MonkeyTypes.Request, _res: Response, next: NextFunction) => {
|
||||
const configuration = req.ctx.configuration;
|
||||
|
||||
const validated = criteria(configuration);
|
||||
if (!validated) {
|
||||
throw new MonkeyError(
|
||||
503,
|
||||
invalidMessage ?? "This service is currently unavailable."
|
||||
);
|
||||
throw new MonkeyError(503, invalidMessage);
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check user permissions before handling request.
|
||||
* Note that this middleware must be used after authentication in the middleware stack.
|
||||
*/
|
||||
function checkUserPermissions(
|
||||
options: ValidationOptions<MonkeyTypes.User>
|
||||
): RequestHandler {
|
||||
const { criteria, invalidMessage = "You don't have permission to do this." } =
|
||||
options;
|
||||
|
||||
return async (
|
||||
req: MonkeyTypes.Request,
|
||||
_res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
try {
|
||||
const { uid } = req.ctx.decodedToken;
|
||||
|
||||
const userData = (await UsersDAO.getUser(
|
||||
uid
|
||||
)) as unknown as MonkeyTypes.User;
|
||||
const hasPermission = criteria(userData);
|
||||
|
||||
if (!hasPermission) {
|
||||
throw new MonkeyError(403, invalidMessage);
|
||||
}
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
|
||||
next();
|
||||
|
|
@ -110,4 +145,9 @@ function validateRequest(validationSchema: ValidationSchema): RequestHandler {
|
|||
};
|
||||
}
|
||||
|
||||
export { validateConfiguration, asyncHandler, validateRequest };
|
||||
export {
|
||||
validateConfiguration,
|
||||
checkUserPermissions,
|
||||
asyncHandler,
|
||||
validateRequest,
|
||||
};
|
||||
|
|
|
|||
25
backend/types/types.d.ts
vendored
25
backend/types/types.d.ts
vendored
|
|
@ -21,7 +21,6 @@ declare namespace MonkeyTypes {
|
|||
enabled: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface DecodedToken {
|
||||
uid?: string;
|
||||
email?: string;
|
||||
|
|
@ -35,4 +34,28 @@ declare namespace MonkeyTypes {
|
|||
interface Request extends ExpressRequest {
|
||||
ctx: Readonly<Context>;
|
||||
}
|
||||
|
||||
// Data Model
|
||||
|
||||
interface User {
|
||||
// TODO, Complete the typings for the user model
|
||||
_id: string;
|
||||
addedAt: number;
|
||||
bananas: number;
|
||||
completedTests: number;
|
||||
discordId?: string;
|
||||
email: string;
|
||||
lastNameChange: number;
|
||||
lbMemory: object;
|
||||
lbPersonalBests: object;
|
||||
name: string;
|
||||
personalBests: object;
|
||||
quoteRatings: Record<string, Record<string, number>>;
|
||||
startedTests: number;
|
||||
tags: object[];
|
||||
timeTyping: number;
|
||||
uid: string;
|
||||
quoteMod: boolean;
|
||||
cannotReport: boolean;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue