From 6db41b28923499f610a179a6bcb711fdb81762ad Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Mon, 24 Jan 2022 03:43:15 +0100 Subject: [PATCH 1/2] Rename report rule to category The report rules function more as a categorization of reports than rules and the name "rule" is somewhat confusing as they are not the rules of any particular community or server. --- CHANGELOG.md | 4 + src/app.ts | 6 +- src/database/{rule.ts => category.ts} | 12 +- src/database/guildconfig.ts | 8 +- src/database/ids.ts | 2 +- src/database/reportinfo.ts | 2 +- ...controller.ts => categories.controller.ts} | 122 +++++++-------- src/routes/communities.controller.ts | 28 ++-- src/routes/reports.controller.ts | 64 ++++---- src/routes/revocations.controller.ts | 82 +++++----- src/utils/Prometheus.ts | 56 +++---- src/utils/info.ts | 146 +++++++++--------- test/categories.spec.ts | 22 +++ test/rules.spec.ts | 22 --- test/utils.ts | 8 +- 15 files changed, 294 insertions(+), 290 deletions(-) rename src/database/{rule.ts => category.ts} (76%) rename src/routes/{rules.controller.ts => categories.controller.ts} (67%) create mode 100644 test/categories.spec.ts delete mode 100644 test/rules.spec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index edc5169..716ee1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## UNRELEASED +### Changes + +- Renamed report rules to categories + ### Additions - Added in Master Rules API to add and remove rules with a master API key diff --git a/src/app.ts b/src/app.ts index 73028a7..a8400fc 100644 --- a/src/app.ts +++ b/src/app.ts @@ -76,7 +76,7 @@ fastify.register(fastifySwagger, { // produces: ["application/json"], tags: [ { name: "community", description: "Community related end-points" }, - { name: "rules", description: "Rule related end-points" }, + { name: "categories", description: "Category related end-points" }, { name: "reports", description: "Report related end-points" }, { name: "profiles", description: "Profile related end-points" }, { @@ -130,8 +130,8 @@ fastify.addSchema({ ...generateSchema(Types.Community) }) fastify.addSchema({ - $id: "RuleClass", - ...generateSchema(Types.Rule) + $id: "CategoryClass", + ...generateSchema(Types.Category) }) fastify.addSchema({ $id: "ReportClass", diff --git a/src/database/rule.ts b/src/database/category.ts similarity index 76% rename from src/database/rule.ts rename to src/database/category.ts index daad2ec..b2ffaaf 100644 --- a/src/database/rule.ts +++ b/src/database/category.ts @@ -4,10 +4,10 @@ import IdModel, { IdType } from "./ids" @modelOptions({ schemaOptions: { - collection: "rules", + collection: "categories", }, }) -@pre("save", async function (next) { +@pre("save", async function (next) { if (!this.id || !this._id) { const id = await getUserStringFromID(IdType.COMMUNITY) this.id = id.id @@ -15,7 +15,7 @@ import IdModel, { IdType } from "./ids" } next() }) -export class RuleClass { +export class CategoryClass { @prop({ unique: true }) id!: string @@ -26,9 +26,9 @@ export class RuleClass { longdesc!: string } -const RuleModel = getModelForClass(RuleClass) +const CategoryModel = getModelForClass(CategoryClass) -const watcher = RuleModel.watch() +const watcher = CategoryModel.watch() watcher.on("change", async (change) => { if (change.operationType === "delete") { // delete the ID from the db too @@ -38,4 +38,4 @@ watcher.on("change", async (change) => { } }) -export default RuleModel +export default CategoryModel diff --git a/src/database/guildconfig.ts b/src/database/guildconfig.ts index ff82048..e220877 100644 --- a/src/database/guildconfig.ts +++ b/src/database/guildconfig.ts @@ -11,7 +11,7 @@ class Roles { reports!: string webhooks!: string setConfig!: string - setRules!: string + setCategories!: string setCommunities!: string } @@ -36,14 +36,14 @@ export class GuildConfigClass { reports: String, webhooks: String, setConfig: String, - setRules: String, + setCategories: String, setCommunities: String, }), default: { reports: "", webhooks: "", setConfig: "", - setRules: "", + setCategories: "", setCommunities: "", } }) @@ -53,7 +53,7 @@ export class GuildConfigClass { trustedCommunities!: string[] @prop({ type: [ String ], default: [] }) - ruleFilters!: string[] + categoryFilters!: string[] } const GuildConfigModel = getModelForClass(GuildConfigClass) diff --git a/src/database/ids.ts b/src/database/ids.ts index 2b4d21b..ecf5fcd 100644 --- a/src/database/ids.ts +++ b/src/database/ids.ts @@ -2,7 +2,7 @@ import { getModelForClass, modelOptions, prop } from "@typegoose/typegoose" export enum IdType { COMMUNITY = "community", - RULE = "rule", + CATEGORY = "category", REPORT = "report", REVOCATION = "revocation", LOG = "log", diff --git a/src/database/reportinfo.ts b/src/database/reportinfo.ts index 204abf3..5624f7e 100644 --- a/src/database/reportinfo.ts +++ b/src/database/reportinfo.ts @@ -27,7 +27,7 @@ export class ReportInfoClass { communityId!: string @prop() - brokenRule!: string + categoryId!: string @prop() proof!: string diff --git a/src/routes/rules.controller.ts b/src/routes/categories.controller.ts similarity index 67% rename from src/routes/rules.controller.ts rename to src/routes/categories.controller.ts index 9711171..f232155 100644 --- a/src/routes/rules.controller.ts +++ b/src/routes/categories.controller.ts @@ -1,37 +1,37 @@ import { FastifyReply, FastifyRequest } from "fastify" import { Controller, DELETE, GET, PATCH, POST } from "fastify-decorators" -import RuleModel from "../database/rule" +import CategoryModel from "../database/category" import GuildConfigModel from "../database/guildconfig" import { MasterAuthenticate } from "../utils/authentication" -import { guildConfigChanged, ruleCreatedMessage, ruleRemovedMessage, rulesMergedMessage, ruleUpdatedMessage } from "../utils/info" +import { guildConfigChanged, categoryCreatedMessage, categoryRemovedMessage, categoriesMergedMessage, categoryUpdatedMessage } from "../utils/info" import { z } from "zod" import ReportInfoModel from "../database/reportinfo" -@Controller({ route: "/rules" }) -export default class RuleController { +@Controller({ route: "/categories" }) +export default class CategoryController { @GET({ url: "/", options: { schema: { - description: "Fetch all rules", - tags: [ "rules" ], + description: "Fetch all categories", + tags: [ "categories" ], response: { "200": { type: "array", items: { - $ref: "RuleClass#", + $ref: "CategoryClass#", }, }, }, }, }, }) - async getAllRules( + async getAllCategories( _req: FastifyRequest, res: FastifyReply ): Promise { - const rules = await RuleModel.find({}) - return res.send(rules) + const categories = await CategoryModel.find({}) + return res.send(categories) } @GET({ @@ -42,17 +42,17 @@ export default class RuleController { id: z.string(), }).required(), - description: "Fetch a rule by ID", - tags: [ "rules" ], + description: "Fetch a category by ID", + tags: [ "categories" ], response: { "200": { - allOf: [ { nullable: true }, { $ref: "RuleClass#" } ], + allOf: [ { nullable: true }, { $ref: "CategoryClass#" } ], }, }, }, }, }) - async getRule( + async getCategory( req: FastifyRequest<{ Params: { id: string @@ -61,8 +61,8 @@ export default class RuleController { res: FastifyReply ): Promise { const { id } = req.params - const rule = await RuleModel.findOne({ id: id }) - return res.send(rule) + const category = await CategoryModel.findOne({ id: id }) + return res.send(category) } @POST({ @@ -74,8 +74,8 @@ export default class RuleController { longdesc: z.string() }).required(), - description: "Create a rule", - tags: [ "rules" ], + description: "Create a category", + tags: [ "categories" ], security: [ { masterAuthorization: [], @@ -83,7 +83,7 @@ export default class RuleController { ], response: { "200": { - $ref: "RuleClass#", + $ref: "CategoryClass#", }, }, }, @@ -100,12 +100,12 @@ export default class RuleController { res: FastifyReply ): Promise { const { shortdesc, longdesc } = req.body - const rule = await RuleModel.create({ + const category = await CategoryModel.create({ shortdesc: shortdesc, longdesc: longdesc, }) - ruleCreatedMessage(rule) - return res.send(rule) + categoryCreatedMessage(category) + return res.send(category) } @PATCH({ @@ -120,8 +120,8 @@ export default class RuleController { longdesc: z.string().optional(), }).optional(), - description: "Update a rule", - tags: [ "rules" ], + description: "Update a category", + tags: [ "categories" ], security: [ { masterAuthorization: [], @@ -129,7 +129,7 @@ export default class RuleController { ], response: { "200": { - $ref: "RuleClass#", + $ref: "CategoryClass#", }, }, }, @@ -152,22 +152,22 @@ export default class RuleController { const { id } = req.params if (!shortdesc && !longdesc) { - return res.send(await RuleModel.findOne({ id: id })) + return res.send(await CategoryModel.findOne({ id: id })) } - const oldRule = await RuleModel.findOne({ id: id }) + const oldCategory = await CategoryModel.findOne({ id: id }) - if (!oldRule) return res.send(null) - const newRule = await RuleModel.findOneAndUpdate({ + if (!oldCategory) return res.send(null) + const newCategory = await CategoryModel.findOneAndUpdate({ id: id }, { ...Boolean(shortdesc) && { shortdesc: shortdesc }, ...Boolean(longdesc) && { longdesc: longdesc } }, { new: true }) - if (!newRule) return res.send(null) + if (!newCategory) return res.send(null) - ruleUpdatedMessage(oldRule, newRule) + categoryUpdatedMessage(oldCategory, newCategory) - return res.send(newRule) + return res.send(newCategory) } @DELETE({ @@ -178,8 +178,8 @@ export default class RuleController { id: z.string(), }), - description: "Remove a rule", - tags: [ "rules" ], + description: "Remove a category", + tags: [ "categories" ], security: [ { masterAuthorization: [], @@ -187,7 +187,7 @@ export default class RuleController { ], response: { "200": { - $ref: "RuleClass#", + $ref: "CategoryClass#", }, }, }, @@ -203,22 +203,22 @@ export default class RuleController { res: FastifyReply ): Promise { const { id } = req.params - const rule = await RuleModel.findOneAndRemove({ + const category = await CategoryModel.findOneAndRemove({ id: id, }) - if (rule) { - ruleRemovedMessage(rule) - // store the IDs of the affected guilds - ones which have the rule filtered + if (category) { + categoryRemovedMessage(category) + // store the IDs of the affected guilds - ones which have the category filtered const affectedGuildConfigs = await GuildConfigModel.find({ - ruleFilters: [ rule.id ] + categoryFilters: [ category.id ] }) - // remove the rule ID from any guild configs which may have it + // remove the category ID from any guild configs which may have it await GuildConfigModel.updateMany({ _id: { $in: affectedGuildConfigs.map(config => config._id) } }, { - $pull: { ruleFilters: rule.id } + $pull: { categoryFilters: category.id } }) const newGuildConfigs = await GuildConfigModel.find({ @@ -226,7 +226,7 @@ export default class RuleController { }) await ReportInfoModel.deleteMany({ - brokenRule: rule.id + categoryId: category.id }) // tell guilds about it after the revocations + reports have been removed @@ -245,7 +245,7 @@ export default class RuleController { } sendGuildConfigInfo() // this will make it execute whilst letting other code still run } - return res.send(rule) + return res.send(category) } @PATCH({ @@ -257,8 +257,8 @@ export default class RuleController { idDissolving: z.string(), }), - description: "Merge rule idTwo into rule idReceiving", - tags: [ "rules" ], + description: "Merge category idTwo into category idReceiving", + tags: [ "categories" ], security: [ { masterAuthorization: [], @@ -266,14 +266,14 @@ export default class RuleController { ], response: { "200": { - $ref: "RuleClass#", + $ref: "CategoryClass#", }, }, }, }, }) @MasterAuthenticate - async mergeRules( + async mergeCategories( req: FastifyRequest<{ Params: { idReceiving: string @@ -283,48 +283,48 @@ export default class RuleController { res: FastifyReply ): Promise { const { idReceiving, idDissolving } = req.params - const receiving = await RuleModel.findOne({ + const receiving = await CategoryModel.findOne({ id: idReceiving }) if (!receiving) return res.status(400).send({ errorCode: 400, error: "Bad Request", - message: "idOne must be a valid rule ID", + message: "idOne must be a valid category ID", }) - const dissolving = await RuleModel.findOne({ + const dissolving = await CategoryModel.findOne({ id: idDissolving }) if (!dissolving) return res.status(400).send({ errorCode: 400, error: "Bad Request", - message: "idTwo must be a valid rule ID", + message: "idTwo must be a valid category ID", }) - await RuleModel.findOneAndDelete({ + await CategoryModel.findOneAndDelete({ id: idDissolving }) await ReportInfoModel.updateMany({ - brokenRule: idDissolving + categoryId: idDissolving }, { - brokenRule: idReceiving + categoryId: idReceiving }) await GuildConfigModel.updateMany({ - ruleFilters: idDissolving + categoryFilters: idDissolving }, { - $addToSet: { ruleFilters: idReceiving } + $addToSet: { categoryFilters: idReceiving } }) await GuildConfigModel.updateMany({ - ruleFilters: idDissolving, + categoryFilters: idDissolving, }, { - $pull: { ruleFilters: idDissolving }, + $pull: { categoryFilters: idDissolving }, }) const affectedConfigs = await GuildConfigModel.find({ - ruleFilters: idReceiving + categoryFilters: idReceiving }) const sendGuildConfigInfo = async () => { @@ -342,7 +342,7 @@ export default class RuleController { } sendGuildConfigInfo() // this will make it execute whilst letting other code still run - rulesMergedMessage(receiving, dissolving) + categoriesMergedMessage(receiving, dissolving) return res.send(receiving) } diff --git a/src/routes/communities.controller.ts b/src/routes/communities.controller.ts index 4fbc1a5..39ae10f 100644 --- a/src/routes/communities.controller.ts +++ b/src/routes/communities.controller.ts @@ -1,6 +1,6 @@ import { FastifyReply, FastifyRequest } from "fastify" import { Controller, DELETE, GET, PATCH, POST } from "fastify-decorators" -import RuleModel from "../database/rule" +import CategoryModel from "../database/category" import { Authenticate, createApikey, MasterAuthenticate, OptionalAuthenticate } from "../utils/authentication" import CommunityModel from "../database/community" import GuildConfigModel from "../database/guildconfig" @@ -242,13 +242,13 @@ export default class CommunityController { guildId: z.string() }), body: z.object({ - ruleFilters: z.array(z.string()).optional(), + categoryFilters: z.array(z.string()).optional(), trustedCommunities: z.array(z.string()).optional(), roles: z.object({ reports: z.string().optional(), webhooks: z.string().optional(), setConfig: z.string().optional(), - setRules: z.string().optional(), + setCategories: z.string().optional(), setCommunities: z.string().optional(), apiKey: z.string().optional(), }).optional() @@ -278,13 +278,13 @@ export default class CommunityController { guildId: string } Body: { - ruleFilters?: string[] + categoryFilters?: string[] trustedCommunities?: string[] roles?: { reports?: string webhooks?: string setConfig?: string - setRules?: string + setCategories?: string setCommunities?: string } apiKey?: string @@ -292,7 +292,7 @@ export default class CommunityController { }>, res: FastifyReply ): Promise { - const { ruleFilters, trustedCommunities, roles, apiKey } = req.body + const { categoryFilters, trustedCommunities, roles, apiKey } = req.body const { guildId } = req.params // check if the community exists @@ -331,16 +331,16 @@ export default class CommunityController { message: "You are not allowed to edit this guild's config", }) - // query database if rules and communities actually exist - if (ruleFilters) { - const rulesExist = await RuleModel.find({ - id: { $in: ruleFilters }, + // query database if categories and communities actually exist + if (categoryFilters) { + const categoriesExist = await CategoryModel.find({ + id: { $in: categoryFilters }, }) - if (rulesExist.length !== ruleFilters.length) + if (categoriesExist.length !== categoryFilters.length) return res.status(400).send({ errorCode: 400, error: "Bad Request", - message: `ruleFilters must be array of IDs of rules, got ${ruleFilters.toString()}, some of which are not real rule IDs`, + message: `categoryFilters must be array of IDs of categories, got ${categoryFilters.toString()}, some of which are not real category IDs`, }) } if (trustedCommunities) { @@ -357,7 +357,7 @@ export default class CommunityController { // check other stuff if (apiKey) guildConfig.apikey = apiKey - if (ruleFilters) guildConfig.ruleFilters = ruleFilters + if (categoryFilters) guildConfig.categoryFilters = categoryFilters // explicitly add ID of community to trusted communities, as you need to trust yourself const communityIds = new Set(trustedCommunities) if (guildConfig.communityId) communityIds.add(guildConfig.communityId) @@ -376,7 +376,7 @@ export default class CommunityController { reports: "", webhooks: "", setConfig: "", - setRules: "", + setCategories: "", setCommunities: "", } if (roles) { diff --git a/src/routes/reports.controller.ts b/src/routes/reports.controller.ts index b121472..beae6ad 100644 --- a/src/routes/reports.controller.ts +++ b/src/routes/reports.controller.ts @@ -1,13 +1,13 @@ import { FastifyReply, FastifyRequest } from "fastify" import { Controller, GET, POST } from "fastify-decorators" -import RuleModel from "../database/rule" +import CategoryModel from "../database/category" import { Authenticate } from "../utils/authentication" import { reportCreatedMessage } from "../utils/info" import ReportInfoModel from "../database/reportinfo" import { validateDiscordUser, client } from "../utils/discord" import { Community, - Rule, + Category, ReportMessageExtraOpts, } from "fagc-api-types" import GuildConfigModel from "../database/guildconfig" @@ -46,7 +46,7 @@ export default class ReportController { body: z.object({ adminId: z.string(), playername: z.string(), - brokenRule: z.string(), + categoryId: z.string(), automated: z.boolean().nullish().default(false), reportedTime: z.string().default(new Date().toISOString()).refine((input) => validator.isISO8601(input), "reportedTime must be a valid ISO8601 date"), description: z.string().default("No description"), @@ -80,7 +80,7 @@ export default class ReportController { Body: { adminId: string playername: string - brokenRule: string + categoryId: string automated: boolean reportedTime: string description: string @@ -92,7 +92,7 @@ export default class ReportController { const { adminId, playername, - brokenRule, + categoryId, automated, reportedTime, description, @@ -107,12 +107,12 @@ export default class ReportController { message: "Your community could not be found", }) - const rule = await RuleModel.findOne({ id: brokenRule }) - if (!rule) + const category = await CategoryModel.findOne({ id: categoryId }) + if (!category) return res.status(400).send({ errorCode: 400, error: "Bad Request", - message: "brokenRule must be a valid ID", + message: "categoryId must be a valid ID", }) const isDiscordUser = await validateDiscordUser(adminId) @@ -124,7 +124,7 @@ export default class ReportController { }) const admin = await client.users.fetch(adminId) - // check whether any one of the community configs allows for this rule, if not, then don't accept the report + // check whether any one of the community configs allows for this category, if not, then don't accept the report const communityConfigs = await GuildConfigModel.find({ communityId: community.id, }) @@ -134,25 +134,25 @@ export default class ReportController { error: "Bad Request", message: "Your community does not have a community config", }) - const foundRuleFilter = communityConfigs.find((config) => { + const foundCategoryFilter = communityConfigs.find((config) => { return ( - config.ruleFilters?.length && - config.ruleFilters.indexOf(rule.id) !== -1 + config.categoryFilters?.length && + config.categoryFilters.indexOf(category.id) !== -1 ) }) - if (!foundRuleFilter) + if (!foundCategoryFilter) return res.status(400).send({ errorCode: 400, error: "Bad Request", message: - "Your community does not filter for the specified rule", + "Your community does not filter for the specified category", }) const report = await ReportInfoModel.create({ playername: playername, adminId: adminId, - brokenRule: brokenRule, + categoryId: categoryId, automated: automated, reportedTime: reportedTime, description: description, @@ -170,7 +170,7 @@ export default class ReportController { reportCreatedMessage(report, { community: (community.toObject()), - rule: (rule.toObject()), + category: (category.toObject()), createdBy: (admin), totalReports: allReports.length, totalCommunities: differentCommunities.size, @@ -216,10 +216,10 @@ export default class ReportController { querystring: z.object({ playername: z.string(), communityId: z.string(), - ruleId: z.string(), - }).partial().refine((x) => x.playername || x.communityId || x.ruleId, "At least one query param must be specified"), + categoryId: z.string(), + }).partial().refine((x) => x.playername || x.communityId || x.categoryId, "At least one query param must be specified"), - description: "Search for reports using their playername, communityId or ruleId", + description: "Search for reports using their playername, communityId or categoryId", tags: [ "reports" ], response: { "200": { @@ -237,31 +237,31 @@ export default class ReportController { Querystring: { playername?: string communityId?: string - ruleId?: string + categoryId?: string } }>, res: FastifyReply ): Promise { // the querystring validator already makes sure that there is at least one prop, so we can just use it now // TODO: finish + test this - const { playername, communityId, ruleId } = req.query + const { playername, communityId, categoryId } = req.query const reports = await ReportInfoModel.find({ playername: playername, communityId: communityId, - brokenRule: ruleId, + categoryId: categoryId, }) return res.send(reports) } @GET({ - url: "/rule/:id", + url: "/category/:id", options: { schema: { params: z.object({ id: z.string() }), - description: "Fetch a report by it's broken rule ID", + description: "Fetch a report by it's broken category ID", tags: [ "reports" ], response: { "200": { @@ -274,7 +274,7 @@ export default class ReportController { }, }, }) - async getByRule( + async getByCategory( req: FastifyRequest<{ Params: { id: string @@ -282,7 +282,7 @@ export default class ReportController { }>, res: FastifyReply ): Promise { - const reports = await ReportInfoModel.find({ brokenRule: req.params.id }) + const reports = await ReportInfoModel.find({ categoryId: req.params.id }) return res.status(200).send(reports) } @@ -366,7 +366,7 @@ export default class ReportController { schema: { body: z.object({ playername: z.string().nullish(), - ruleIds: z.array(z.string()) + categoryIds: z.array(z.string()) .max(100, "Exceeded maximum length of 100"), communityIds: z.array(z.string()) .max(100, "Exceeded maximum length of 100"), @@ -374,7 +374,7 @@ export default class ReportController { }), description: - "Fetch reports by their community IDs and rule IDs", + "Fetch reports by their community IDs and category IDs", tags: [ "reports" ], response: { "200": { @@ -391,17 +391,17 @@ export default class ReportController { req: FastifyRequest<{ Body: { playername?: string | null - ruleIds: string[] + categoryIds: string[] communityIds: string[] } }>, res: FastifyReply ): Promise { - const { playername, ruleIds, communityIds } = req.body + const { playername, categoryIds, communityIds } = req.body const reports = await ReportInfoModel.find({ playername: playername ?? undefined, - brokenRule: { - $in: ruleIds + categoryId: { + $in: categoryIds }, communityId: { $in: communityIds diff --git a/src/routes/revocations.controller.ts b/src/routes/revocations.controller.ts index 9b5fbd4..4a84aef 100644 --- a/src/routes/revocations.controller.ts +++ b/src/routes/revocations.controller.ts @@ -4,11 +4,11 @@ import { z } from "zod" import { Authenticate } from "../utils/authentication" import { client, validateDiscordUser } from "../utils/discord" import ReportInfoModel from "../database/reportinfo" -import RuleModel, { RuleClass } from "../database/rule" +import CategoryModel, { CategoryClass } from "../database/category" import { reportRevokedMessage } from "../utils/info" import { Community, - Rule, + Category, ReportMessageExtraOpts, RevocationMessageExtraOpts, Revocation, @@ -182,7 +182,7 @@ export default class RevocationController { }) const revoker = await client.users.fetch(req.body.adminId) const admin = await client.users.fetch(report.adminId) - const rule = await RuleModel.findOne({ id: report.brokenRule }) + const category = await CategoryModel.findOne({ id: report.categoryId }) const revocation = await ReportInfoModel.findOneAndUpdate({ id: report.id @@ -207,9 +207,9 @@ export default class RevocationController { reportRevokedMessage(Revocation.parse(revocation), { community: (community), - // this is allowed since the rule is GUARANTEED to exist if the report exists + // this is allowed since the category is GUARANTEED to exist if the report exists // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - rule: (rule!), + category: (category!), revokedBy: ( (revoker) ), @@ -220,14 +220,14 @@ export default class RevocationController { } @GET({ - url: "/rule/:id", + url: "/category/:id", options: { schema: { params: z.object({ id: z.string(), }), - description: "Fetch all revocations of a rule in your community", + description: "Fetch all revocations of a category in your community", tags: [ "revocations" ], security: [ { @@ -246,7 +246,7 @@ export default class RevocationController { }, }) @Authenticate - async fetchRule( + async fetchCategory( req: FastifyRequest<{ Params: { id: string @@ -263,7 +263,7 @@ export default class RevocationController { }) const revocations = await ReportInfoModel.find({ - brokenRuleId: id, + categoryIdId: id, communityId: community.id, revokedAt: { $ne: null }, }) @@ -272,7 +272,7 @@ export default class RevocationController { } @POST({ - url: "/rule/:id", + url: "/category/:id", options: { schema: { params: z.object({ @@ -282,7 +282,7 @@ export default class RevocationController { adminId: z.string(), }), - description: "Revoke all reports of a rule in your community", + description: "Revoke all reports of a category in your community", tags: [ "revocations" ], security: [ { @@ -301,7 +301,7 @@ export default class RevocationController { }, }) @Authenticate - async revokeRule( + async revokeCategory( req: FastifyRequest<{ Params: { id: string @@ -313,7 +313,7 @@ export default class RevocationController { res: FastifyReply ): Promise { const { adminId } = req.body - const { id: ruleId } = req.params + const { id: categoryId } = req.params const community = req.requestContext.get("community") if (!community) @@ -331,8 +331,8 @@ export default class RevocationController { message: "adminId must be a valid Discord user", }) - const rule = await RuleModel.findOne({ id: ruleId }) - if (!rule) + const category = await CategoryModel.findOne({ id: categoryId }) + if (!category) return res.status(400).send({ errorCode: 400, error: "Bad Request", @@ -341,7 +341,7 @@ export default class RevocationController { await ReportInfoModel.updateMany({ communityId: community.id, - brokenRule: ruleId, + categoryId: categoryId, revokedAt: { $eq: null }, }, { revokedAt: new Date(), @@ -350,22 +350,22 @@ export default class RevocationController { const rawRevocations = await ReportInfoModel.find({ communityId: community.id, - brokenRule: ruleId, + categoryId: categoryId, revokedAt: { $ne: null }, }) const revocations = z.array(Revocation).parse(rawRevocations) - const RuleMap: Map | null> = + const CategoryMap: Map | null> = new Map() await Promise.all( revocations.map(async (revocation) => { - if (RuleMap.get(revocation.brokenRule)) return + if (CategoryMap.get(revocation.categoryId)) return - RuleMap.set( - revocation.brokenRule, - await RuleModel.findOne({ - id: revocation.brokenRule, + CategoryMap.set( + revocation.categoryId, + await CategoryModel.findOne({ + id: revocation.categoryId, }).exec() ) }) @@ -382,9 +382,9 @@ export default class RevocationController { community: ( (community.toObject()) ), - // this is allowed since the rule is GUARANTEED to exist if the report exists + // this is allowed since the category is GUARANTEED to exist if the report exists // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - rule: RuleMap.get(revocation.brokenRule)!, + category: CategoryMap.get(revocation.categoryId)!, revokedBy: revoker, totalReports: totalReports.length, totalCommunities: differentCommunities, @@ -527,17 +527,17 @@ export default class RevocationController { }) const revocations = z.array(Revocation).parse(rawRevocations) - const RuleMap: Map | null> = + const CategoryMap: Map | null> = new Map() await Promise.all( revocations.map(async (revocation) => { - if (RuleMap.get(revocation.brokenRule)) return + if (CategoryMap.get(revocation.categoryId)) return - RuleMap.set( - revocation.brokenRule, - await RuleModel.findOne({ - id: revocation.brokenRule, + CategoryMap.set( + revocation.categoryId, + await CategoryModel.findOne({ + id: revocation.categoryId, }).exec() ) }) @@ -557,9 +557,9 @@ export default class RevocationController { community: ( (community.toObject()) ), - // this is allowed since the rule is GUARANTEED to exist if the report exists + // this is allowed since the category is GUARANTEED to exist if the report exists // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - rule: RuleMap.get(revocation.brokenRule)!, + category: CategoryMap.get(revocation.categoryId)!, revokedBy: revoker, totalReports: allReports.length, totalCommunities: differentCommunities.size, @@ -700,17 +700,17 @@ export default class RevocationController { }) const revocations = z.array(Revocation).parse(rawRevocations) - const RuleMap: Map | null> = + const CategoryMap: Map | null> = new Map() await Promise.all( revocations.map(async (revocation) => { - if (RuleMap.get(revocation.brokenRule)) return + if (CategoryMap.get(revocation.categoryId)) return - RuleMap.set( - revocation.brokenRule, - await RuleModel.findOne({ - id: revocation.brokenRule, + CategoryMap.set( + revocation.categoryId, + await CategoryModel.findOne({ + id: revocation.categoryId, }).exec() ) }) @@ -727,9 +727,9 @@ export default class RevocationController { community: ( (community.toObject()) ), - // this is allowed since the rule is GUARANTEED to exist if the report exists + // this is allowed since the category is GUARANTEED to exist if the report exists // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - rule: RuleMap.get(revocation.brokenRule)!, + category: CategoryMap.get(revocation.categoryId)!, revokedBy: revoker, totalReports: totalReports.length, totalCommunities: differentCommunities, diff --git a/src/utils/Prometheus.ts b/src/utils/Prometheus.ts index 82a2ca0..7f0d8d9 100644 --- a/src/utils/Prometheus.ts +++ b/src/utils/Prometheus.ts @@ -4,7 +4,7 @@ import GuildConfigModel, { GuildConfigClass, } from "../database/guildconfig" import CommunityModel, { CommunityClass } from "../database/community" -import RuleModel from "../database/rule" +import CategoryModel from "../database/category" import { DocumentType } from "@typegoose/typegoose" import ENV from "./env" @@ -18,14 +18,14 @@ const communityGauge = new promClient.Gauge({ help: "Amount of communities that trust this community", labelNames: [ "id", "name", "contact" ], }) -const ruleGauge = new promClient.Gauge({ - name: "rule_trust_count", - help: "Amount of communities that trust this rule", +const categoryGauge = new promClient.Gauge({ + name: "category_trust_count", + help: "Amount of communities that trust this category", labelNames: [ "id", "shortdesc" ], }) register.registerMetric(communityGauge) -register.registerMetric(ruleGauge) +register.registerMetric(categoryGauge) // Format community trust from config const trustedCommunities = async ( @@ -66,39 +66,39 @@ const trustedCommunities = async ( }) return await Promise.all(results) } -// Format rule trust from config -const trustedRules = async ( +// Format category trust from config +const trustedCategories = async ( communities: Omit, "apikey">[] ) => { const rawResults: { id: string; count: number }[] = [] - const CachedRules = new Map() - const getOrFetchRule = async (ruleid) => { - const cachedRule = CachedRules.get(ruleid) - if (cachedRule) return cachedRule - const rule = CachedRules.set( - ruleid, - RuleModel.findOne({ id: ruleid }) - ).get(ruleid) - return rule + const CachedCategories = new Map() + const getOrFetchCategory = async (categoryId) => { + const cachedCategory = CachedCategories.get(categoryId) + if (cachedCategory) return cachedCategory + const category = CachedCategories.set( + categoryId, + CategoryModel.findOne({ id: categoryId }) + ).get(categoryId) + return category } communities.forEach((community) => { - community.ruleFilters?.forEach((ruleID) => { + community.categoryFilters?.forEach((categoryId) => { let found = false rawResults.forEach((trusted) => { - if (trusted.id === ruleID) { + if (trusted.id === categoryId) { trusted.count++ found = true } }) if (!found) { - rawResults.push({ id: ruleID, count: 1 }) + rawResults.push({ id: categoryId, count: 1 }) } }) }) - const results = rawResults.map(async (rule) => { + const results = rawResults.map(async (category) => { return { - rule: await getOrFetchRule(rule.id), - count: rule.count, + category: await getOrFetchCategory(category.id), + count: category.count, } }) return await Promise.all(results) @@ -114,14 +114,14 @@ const collectStatistics = async () => { return CommunityConfig }) ) - const rules = await trustedRules(communitySettings) + const categories = await trustedCategories(communitySettings) const communities = await trustedCommunities(communitySettings) - rules.forEach((rule) => { - if (rule.rule) - ruleGauge.set( - { id: rule.rule.id, shortdesc: rule.rule.shortdesc }, - rule.count + categories.forEach((category) => { + if (category.category) + categoryGauge.set( + { id: category.category.id, shortdesc: category.category.shortdesc }, + category.count ) }) communities.forEach((community) => { diff --git a/src/utils/info.ts b/src/utils/info.ts index 9b54ea4..fcbe8c2 100644 --- a/src/utils/info.ts +++ b/src/utils/info.ts @@ -6,7 +6,7 @@ import GuildConfigModel, { } from "../database/guildconfig" import { DocumentType } from "@typegoose/typegoose" import { BeAnObject } from "@typegoose/typegoose/lib/types" -import { RuleClass } from "../database/rule" +import { CategoryClass } from "../database/category" import { CommunityClass } from "../database/community" import { ReportInfoClass } from "../database/reportinfo" import { @@ -156,8 +156,8 @@ export async function reportCreatedMessage( .addFields( { name: "Playername", value: report.playername, inline: true }, { - name: "Broken Rule", - value: `${opts.rule.shortdesc} (\`${opts.rule.id}\`)`, + name: "Category", + value: `${opts.category.shortdesc} (\`${opts.category.id}\`)`, inline: true, }, { name: "Description", value: report.description, inline: false }, @@ -210,8 +210,8 @@ export async function reportRevokedMessage( .addFields([ { name: "Playername", value: revocation.playername, inline: true }, { - name: "Broken Rule", - value: `${opts.rule.shortdesc} (\`${opts.rule.id}\`)`, + name: "Category", + value: `${opts.category.shortdesc} (\`${opts.category.id}\`)`, inline: true, }, { @@ -250,162 +250,162 @@ export async function reportRevokedMessage( ) } -export async function ruleCreatedMessage( - rule: DocumentType +export async function categoryCreatedMessage( + category: DocumentType ): Promise { if ( - rule === null || - rule.shortdesc === undefined || - rule.longdesc === undefined + category === null || + category.shortdesc === undefined || + category.longdesc === undefined ) return - // set the sent object's messageType to ruleCreated - // WebsocketMessage(JSON.stringify(Object.assign({}, rule.toObject(), { messageType: "ruleCreated" }))) + // set the sent object's messageType to categoryCreated + // WebsocketMessage(JSON.stringify(Object.assign({}, category.toObject(), { messageType: "categoryCreated" }))) - const ruleEmbed = new MessageEmbed() - .setTitle("FAGC - Rule Created") + const categoryEmbed = new MessageEmbed() + .setTitle("FAGC - Category Created") .setColor("#6f4fe3") .addFields( - { name: "Rule ID", value: `\`${rule.id}\``, inline: true }, + { name: "Category ID", value: `\`${category.id}\``, inline: true }, { - name: "Rule short description", - value: rule.shortdesc, + name: "Category short description", + value: category.shortdesc, inline: true, }, { - name: "Rule long description", - value: rule.longdesc, + name: "Category long description", + value: category.longdesc, inline: true, } ) - WebhookMessage(ruleEmbed) + WebhookMessage(categoryEmbed) WebsocketMessage( JSON.stringify({ - messageType: "ruleCreated", - embed: ruleEmbed, - rule: rule, + messageType: "categoryCreated", + embed: categoryEmbed, + category: category, }) ) } -export async function ruleRemovedMessage( - rule: DocumentType +export async function categoryRemovedMessage( + category: DocumentType ): Promise { if ( - rule === null || - rule.shortdesc === undefined || - rule.longdesc === undefined + category === null || + category.shortdesc === undefined || + category.longdesc === undefined ) return - // set the sent object's messageType to ruleRemoved - // WebsocketMessage(JSON.stringify(Object.assign({}, rule.toObject(), { messageType: "ruleRemoved" }))) + // set the sent object's messageType to categoryRemoved + // WebsocketMessage(JSON.stringify(Object.assign({}, category.toObject(), { messageType: "categoryRemoved" }))) - const ruleEmbed = new MessageEmbed() - .setTitle("FAGC - Rule Removed") + const categoryEmbed = new MessageEmbed() + .setTitle("FAGC - Category Removed") .setColor("#6f4fe3") .addFields( - { name: "Rule ID", value: `\`${rule.id}\``, inline: true }, + { name: "Category ID", value: `\`${category.id}\``, inline: true }, { - name: "Rule short description", - value: rule.shortdesc, + name: "Category short description", + value: category.shortdesc, inline: true, }, { - name: "Rule long description", - value: rule.longdesc, + name: "Category long description", + value: category.longdesc, inline: true, } ) - WebhookMessage(ruleEmbed) + WebhookMessage(categoryEmbed) WebsocketMessage( JSON.stringify({ - messageType: "ruleRemoved", - embed: ruleEmbed, - rule: rule, + messageType: "categoryRemoved", + embed: categoryEmbed, + category: category, }) ) } -export async function ruleUpdatedMessage( - oldRule: DocumentType, - newRule: DocumentType +export async function categoryUpdatedMessage( + oldCategory: DocumentType, + newCategory: DocumentType ): Promise { - const ruleEmbed = new MessageEmbed() - .setTitle("FAGC - Rule Updated") + const categoryEmbed = new MessageEmbed() + .setTitle("FAGC - Category Updated") .setColor("#6f4fe3") .addFields( - { name: "Rule ID", value: `\`${newRule.id}\``, inline: true }, + { name: "Category ID", value: `\`${newCategory.id}\``, inline: true }, { - name: "Old Rule short description", - value: oldRule.shortdesc, + name: "Old Category short description", + value: oldCategory.shortdesc, inline: true, }, { - name: "New Rule short description", - value: newRule.shortdesc, + name: "New Category short description", + value: newCategory.shortdesc, inline: true, }, { - name: "Old Rule long description", - value: oldRule.longdesc, + name: "Old Category long description", + value: oldCategory.longdesc, inline: true, }, { - name: "New Rule long description", - value: newRule.longdesc, + name: "New Category long description", + value: newCategory.longdesc, inline: true, } ) - WebhookMessage(ruleEmbed) + WebhookMessage(categoryEmbed) WebsocketMessage( JSON.stringify({ - messageType: "ruleUpdated", - embed: ruleEmbed, - oldRule: oldRule, - newRule: newRule + messageType: "categoryUpdated", + embed: categoryEmbed, + oldCategory: oldCategory, + newCategory: newCategory }) ) } -export async function rulesMergedMessage( - receiving: DocumentType, - dissolving: DocumentType +export async function categoriesMergedMessage( + receiving: DocumentType, + dissolving: DocumentType ): Promise { - const ruleEmbed = new MessageEmbed() - .setTitle("FAGC - Rules merged") + const categoryEmbed = new MessageEmbed() + .setTitle("FAGC - Categories merged") .setColor("#6f4fe3") .addFields( - { name: "Receiving Rule ID", value: `\`${dissolving.id}\``, inline: true }, + { name: "Receiving Category ID", value: `\`${dissolving.id}\``, inline: true }, { - name: "Dissolving Rule short description", + name: "Dissolving Category short description", value: receiving.shortdesc, inline: true, }, { - name: "Receiving Rule short description", + name: "Receiving Category short description", value: dissolving.shortdesc, inline: true, }, { - name: "Dissolving Rule long description", + name: "Dissolving Category long description", value: receiving.longdesc, inline: true, }, { - name: "Receiving Rule long description", + name: "Receiving Category long description", value: dissolving.longdesc, inline: true, } ) - WebhookMessage(ruleEmbed) + WebhookMessage(categoryEmbed) WebsocketMessage( JSON.stringify({ - messageType: "rulesMerged", - embed: ruleEmbed, + messageType: "categoriesMerged", + embed: categoryEmbed, receiving: receiving, dissolving: dissolving }) diff --git a/test/categories.spec.ts b/test/categories.spec.ts new file mode 100644 index 0000000..2ca0c49 --- /dev/null +++ b/test/categories.spec.ts @@ -0,0 +1,22 @@ +import CategoryModel from "../src/database/category" +import backend from "./prepareTest" +import * as mockingoose from "mockingoose" +import { createCategories } from "./utils" + +describe("GET /categories/", () => { + it("Should fetch all categories and return them correctly", async () => { + const fetchedData = createCategories(10) + mockingoose(CategoryModel).toReturn(fetchedData, "find") + const response = await backend.inject({ + path: "/categories", + method: "GET" + }) + expect(response.statusCode).toBe(200) + const backendData = await response.json() + expect(backendData.length).toBe(fetchedData.length) + fetchedData.map((fetchedCategory, i) => { + const backendCategory = backendData[i] + expect(backendCategory).toEqual(fetchedCategory) + }) + }) +}) \ No newline at end of file diff --git a/test/rules.spec.ts b/test/rules.spec.ts deleted file mode 100644 index c6ad469..0000000 --- a/test/rules.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import RuleModel from "../src/database/rule" -import backend from "./prepareTest" -import * as mockingoose from "mockingoose" -import { createRules } from "./utils" - -describe("GET /rules/", () => { - it("Should fetch all rules and return them correctly", async () => { - const fetchedData = createRules(10) - mockingoose(RuleModel).toReturn(fetchedData, "find") - const response = await backend.inject({ - path: "/rules", - method: "GET" - }) - expect(response.statusCode).toBe(200) - const backendData = await response.json() - expect(backendData.length).toBe(fetchedData.length) - fetchedData.map((fetchedRule, i) => { - const backendRule = backendData[i] - expect(backendRule).toEqual(fetchedRule) - }) - }) -}) \ No newline at end of file diff --git a/test/utils.ts b/test/utils.ts index 8b4848e..835e2dd 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,4 +1,4 @@ -import { Community, Rule } from "fagc-api-types" +import { Community, Category } from "fagc-api-types" import * as faker from "faker" export const createCommunity = (): Community => { @@ -14,13 +14,13 @@ export const createCommunities = (count: number): Community[] => { return Array(count).fill(0).map(() => createCommunity()) } -export const createRule = (): Rule => { +export const createCategory = (): Category => { return { id: faker.datatype.uuid(), shortdesc: faker.lorem.word(), longdesc: faker.lorem.sentence(), } } -export const createRules = (count: number): Rule[] => { - return Array(count).fill(0).map(() => createRule()) +export const createCategories = (count: number): Category[] => { + return Array(count).fill(0).map(() => createCategory()) } \ No newline at end of file From c600372eb85b3b2d0934194f40a2987bd2ab230e Mon Sep 17 00:00:00 2001 From: oof2win2 Date: Mon, 24 Jan 2022 09:21:40 +0100 Subject: [PATCH 2/2] fix(deps): bump fagc-api-types to 1.9.0 --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 018aaeb..2b5f8fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "discord.js": "^13.6.0", "dotenv": "^14.2.0", "envalid": "^7.2.2", - "fagc-api-types": "^1.7.0", + "fagc-api-types": "^1.9.0", "fastify": "^3.26.0", "fastify-autoload": "^3.10.0", "fastify-cors": "^6.0.2", @@ -3627,9 +3627,9 @@ } }, "node_modules/fagc-api-types": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/fagc-api-types/-/fagc-api-types-1.7.0.tgz", - "integrity": "sha512-uCDZBY0w9mVGpKBtM3XF4tJkOeLuB1Q9BAT00j9NxllPaT+eNgb4R51TUQEiCsFh2nGIexX9QJcVeW65Q6jfrQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/fagc-api-types/-/fagc-api-types-1.9.0.tgz", + "integrity": "sha512-7BzTEIG9k7ahNvujlCB5zEtosmTXecDpv9UjAnrI+atDFwDVxr9dzDFXHqHoWBO8v3KAeTSpAUgHy+1V4Cx5hw==", "dependencies": { "discord-api-types": "^0.23.1", "zod": "^3.11.6" @@ -11196,9 +11196,9 @@ } }, "fagc-api-types": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/fagc-api-types/-/fagc-api-types-1.7.0.tgz", - "integrity": "sha512-uCDZBY0w9mVGpKBtM3XF4tJkOeLuB1Q9BAT00j9NxllPaT+eNgb4R51TUQEiCsFh2nGIexX9QJcVeW65Q6jfrQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/fagc-api-types/-/fagc-api-types-1.9.0.tgz", + "integrity": "sha512-7BzTEIG9k7ahNvujlCB5zEtosmTXecDpv9UjAnrI+atDFwDVxr9dzDFXHqHoWBO8v3KAeTSpAUgHy+1V4Cx5hw==", "requires": { "discord-api-types": "^0.23.1", "zod": "^3.11.6" diff --git a/package.json b/package.json index 770e512..41cf289 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "discord.js": "^13.6.0", "dotenv": "^14.2.0", "envalid": "^7.2.2", - "fagc-api-types": "^1.7.0", + "fagc-api-types": "^1.9.0", "fastify": "^3.26.0", "fastify-autoload": "^3.10.0", "fastify-cors": "^6.0.2",