diff --git a/src/bots/slackbot.ts b/src/bots/slackbot.ts index 9c78e9f..313418b 100644 --- a/src/bots/slackbot.ts +++ b/src/bots/slackbot.ts @@ -218,12 +218,7 @@ export namespace SlackBot { export function notifyOfNewLock(projectName: string) { DB.allNotifiableEvaluators((user) => { - SlackBot.sendMessage( - user, - `A new Peer++ evaluation for the project \`${projectName}\` is ready.` + - `Use the command \`/book\` to book it.` + - `Use the command \`/notify-off\` to stop receiving these notifications.` - ); + SlackBot.sendMessage(user, `A new Peer++ evaluation for \`${projectName}\` is ready to be booked`); }); } diff --git a/src/bots/webhook.ts b/src/bots/webhook.ts index 4678a78..bddd883 100644 --- a/src/bots/webhook.ts +++ b/src/bots/webhook.ts @@ -13,7 +13,7 @@ import { getFullUser } from "../utils/user"; import { IntraWebhook } from "../utils/types"; import Logger, { LogType } from "../utils/logger"; import { Request, Response, NextFunction } from "express"; -import * as Checks from "../checks/index"; +import * as Checks from "../checks/evaluators"; import db from "../db"; /*============================================================================*/ @@ -44,28 +44,32 @@ async function filterAlreadyDeliveredWebhook(req: Request) { } /** - * Filters for requests and sends back corresponding error. - * @param req The incoming request. + * Filters for requests and, if required sends corresponding error. * @param secret The Webhook secret. - * @returns Status code and error message. */ -async function filterHook(req: Request, secret: string) { - if (!req.is("application/json")) { - return { code: 400, msg: "Content-Type is not application/json" }; - } - if (!req.headers["x-delivery"]) { - return { code: 400, msg: "X-Delivery header missing" }; - } - if (!req.headers["x-secret"]) { - return { code: 400, msg: "X-Secret header missing" }; - } - if (req.headers["x-secret"] !== secret) { - return { code: 412, msg: "X-Secret header incorrect" }; - } - if (await filterAlreadyDeliveredWebhook(req)) { - return { code: 200, msg: "Webhook already received" }; - } - return null; +function filterHook(secret: string): (req: Request, res: Response, next: NextFunction) => Promise { + return async (req: Request, res: Response, next: NextFunction) => { + let filter: { code: number; msg: string } | undefined = undefined; + + if (!req.is("application/json")) { + filter = { code: 400, msg: "Content-Type is not application/json" }; + } else if (!req.headers["x-delivery"]) { + filter = { code: 400, msg: "X-Delivery header missing" }; + } else if (!req.headers["x-secret"]) { + filter = { code: 400, msg: "X-Secret header missing" }; + } else if (req.headers["x-secret"] !== secret) { + filter = { code: 412, msg: "X-Secret header incorrect" }; + } else if (await filterAlreadyDeliveredWebhook(req)) { + filter = { code: 200, msg: "Webhook already delivered" }; + } + + if (filter) { + Logger.log(`Webhook filtered: ${JSON.stringify(filter)}`); + res.status(filter.code).send(filter.msg); + } else { + next(); + } + }; } /** @@ -127,7 +131,7 @@ export namespace Webhook { // NOTE (W2): Completely fucked up and weird endpoint btw. const teamUsers = await Intra.getTeamUsers(hook.team.id); - return (await Checks.Evaluators(hook, evaluations, teamUsers)) || (await Checks.Random()); + return (await Checks.evaluators(hook, evaluations, teamUsers)) || Checks.random(); } /** @@ -168,15 +172,8 @@ webhookApp.use((err: any, req: Request, res: Response, next: NextFunction) => { // TODO: Figure out how evaluation points should be handled. // Runs whenever a ScaleTeam / Evaluation is created. -webhookApp.post("/create", async (req: Request, res: Response) => { - Logger.log(JSON.stringify(req.headers)); +webhookApp.post("/create", filterHook(Env.WEBHOOK_CREATE_SECRET), async (req: Request, res: Response) => { const hook: IntraWebhook.Root = req.body; - const filter = await filterHook(req, Env.WEBHOOK_CREATE_SECRET); - - if (filter) { - res.status(filter.code).send(filter.msg); - return Logger.log(`Webhook: ${filter}`); - } Logger.log(`Evaluation created: ${hook.team.name} -> ${hook.project.name}`); if (await blockPotentialEvaluation(hook)) { @@ -222,14 +219,8 @@ webhookApp.post("/create", async (req: Request, res: Response) => { /*============================================================================*/ // Runs whenever a ScaleTeam / Evaluation is destroyed. -webhookApp.post("/delete", async (req: Request, res: Response) => { - Logger.log(JSON.stringify(req.headers)); +webhookApp.post("/delete", filterHook(Env.WEBHOOK_DELETE_SECRET), async (req: Request, res: Response) => { const hook: IntraWebhook.Root = req.body; - const filter = await filterHook(req, Env.WEBHOOK_DELETE_SECRET); - if (filter) { - res.status(filter.code).send(filter.msg); - return Logger.log(`Webhook: ${filter}`); - } Logger.log(`Evaluation destroyed: ${hook.team.name} -> ${hook.project.name} -> ID: ${req.headers["x-delivery"]}`); if (hook.user && hook.user.id != Config.botID) { @@ -262,14 +253,8 @@ webhookApp.post("/delete", async (req: Request, res: Response) => { /*============================================================================*/ // Runs whenever a ScaleTeam / Evaluation is changed in some way. -webhookApp.post("/update", async (req: Request, res: Response) => { - Logger.log(JSON.stringify(req.headers)); +webhookApp.post("/update", filterHook(Env.WEBHOOK_UPDATE_SECRET), async (req: Request, res: Response) => { const hook: IntraWebhook.Root = req.body; - const filter = await filterHook(req, Env.WEBHOOK_UPDATE_SECRET); - if (filter) { - res.status(filter.code).send(filter.msg); - return Logger.log(`Webhook: ${filter}`); - } Logger.log(`Evaluation update: ${hook.team.name} -> ${hook.project.name} -> ID: ${req.headers["x-delivery"]}`); diff --git a/src/checks/evaluators.ts b/src/checks/evaluators.ts index 2cc879a..b6ed85e 100644 --- a/src/checks/evaluators.ts +++ b/src/checks/evaluators.ts @@ -21,7 +21,7 @@ import { getFullUser, User } from "../utils/user"; * @param evaluations The evaluations of the project. * @return True if an evaluation is required, else false. */ -export async function Evaluators(hook: IntraWebhook.Root, evaluations: Intra.ScaleTeam[], teamUsers: IntraResponse.TeamUser[]) { +export async function evaluators(hook: IntraWebhook.Root, evaluations: Intra.ScaleTeam[], teamUsers: IntraResponse.TeamUser[]) { const leaderData = teamUsers.find((value) => value.leader == true)!; let levels: number[] = []; @@ -49,3 +49,7 @@ export async function Evaluators(hook: IntraWebhook.Root, evaluations: Intra.Sca } return true; } + +export function random() { + return Math.random() < Config.randomEvalChance / 100; +} diff --git a/src/checks/index.ts b/src/checks/index.ts deleted file mode 100644 index 1c98791..0000000 --- a/src/checks/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -// ----------------------------------------------------------------------------- -// Codam Coding College, Amsterdam @ 2022. -// See README in the root project for more information. -// ----------------------------------------------------------------------------- - -// NOTE (W2): This seems janky as hell but apparently this is how TS works ? - -export * from "./evaluators"; -export * from "./random"; diff --git a/src/checks/random.ts b/src/checks/random.ts deleted file mode 100644 index f875b05..0000000 --- a/src/checks/random.ts +++ /dev/null @@ -1,18 +0,0 @@ -// ----------------------------------------------------------------------------- -// Codam Coding College, Amsterdam @ 2022. -// See README in the root project for more information. -// ----------------------------------------------------------------------------- - -import { Config } from "../config"; - -/*============================================================================*/ - -/** - * Simply randomly decide if an evaluation is required. - * The weight / probability can be altered via the config. - * - * @return True if check passed, false otherwise. - */ -export async function Random() { - return Math.random() < Config.randomEvalChance / 100; -} diff --git a/src/db.ts b/src/db.ts index f4bcd45..5475492 100644 --- a/src/db.ts +++ b/src/db.ts @@ -11,55 +11,51 @@ import { User } from "./utils/user"; /*============================================================================*/ async function dbRun(query: string): Promise { - return new Promise((resolve, reject) => { - db.run(query, (err) => (err ? reject(err) : resolve())); + return await new Promise((resolve, reject) => { + db.run(query, (err) => { + if (err) { + err.message = `Query failed: "${query}"\n${err.message}`; + return reject(err); + } + resolve(); + }); }); } -async function dbGet(query: string): Promise> { +async function dbGet(query: string): Promise { return new Promise((resolve, reject) => { - db.get(query, (err, t) => (err ? reject(err) : resolve(t))); + db.get(query, (err, t) => { + if (err) { + err.message = `Query failed: "${query}"\n${err.message}`; + return reject(err); + } + resolve(t); + }); }); } /** SQLlite3 database wrapper functions */ namespace DB { /** Deletes all expiredTeam rows which are older than the lock days. */ - export function emptyOldLocks() { - return new Promise((resolve, reject) => { - db.run(`DELETE FROM expiredTeam WHERE datetime(created_at) < datetime('now', '-${Config.lockExpirationDays} days')`, (err) => { - if (err != null) return reject(`Failed to clear database: ${err}`); - return resolve(); - }); - }); + export async function emptyOldLocks() { + await dbRun(`DELETE FROM expiredTeam WHERE datetime(created_at) < datetime('now', '-${Config.lockExpirationDays} days')`); } /** * Insert the given team into the database and mark them as expired. * @param teamID The TeamID. */ - export function insert(teamID: number) { - return new Promise((resolve, reject) => { - db.run(`INSERT INTO expiredTeam(teamID) VALUES(${teamID})`, (err) => { - if (err != null) return reject(`Failed to insert value ${teamID}: ${err}`); - return resolve(); - }); - }); + export async function insert(teamID: number) { + await dbRun(`INSERT INTO expiredTeam(teamID) VALUES(${teamID})`); } /** * Checks wether the given teamID exists in the db. * @param teamID The TeamID. */ - export function exists(teamID: number) { - return new Promise((resolve, reject) => { - db.get(`SELECT COUNT(*) AS amount FROM expiredTeam WHERE teamID = ${teamID}`, (err, row) => { - if (err != null) { - return reject(`Failed to check if ${teamID} exists: ${err}`); - } - return resolve(row["amount"] > 0); - }); - }); + export async function exists(teamID: number) { + const team = await dbGet<{ amount: number }>(`SELECT COUNT(*) AS amount FROM expiredTeam WHERE teamID = ${teamID}`); + return team.amount > 0; } export async function hasWebhookDelivery(id: string): Promise {