Support webhooks (Fixes #29)

This commit is contained in:
zkldi
2021-09-05 07:16:22 +01:00
parent 7b4d7ba877
commit ab2db4ad85
9 changed files with 94 additions and 25 deletions
+1 -1
View File
@@ -73,7 +73,7 @@
"redis": "3.1.2",
"rimraf": "3.0.2",
"safe-json-stringify": "1.2.0",
"tachi-common": "0.1.54",
"tachi-common": "0.2.1",
"typescript": "4.3.4",
"winston": "3.3.3"
},
+4 -4
View File
@@ -54,7 +54,7 @@ specifiers:
rimraf: 3.0.2
safe-json-stringify: 1.2.0
supertest: 6.1.3
tachi-common: 0.1.54
tachi-common: 0.2.1
tap: 15.0.9
ts-node: 10.0.0
tsconfig-paths: 3.10.1
@@ -88,7 +88,7 @@ dependencies:
redis: 3.1.2
rimraf: 3.0.2
safe-json-stringify: 1.2.0
tachi-common: 0.1.54_ts-node@10.0.0+typescript@4.3.4
tachi-common: 0.2.1_ts-node@10.0.0+typescript@4.3.4
typescript: 4.3.4
winston: 3.3.3
@@ -3681,8 +3681,8 @@ packages:
strip-ansi: 6.0.0
dev: true
/tachi-common/0.1.54_ts-node@10.0.0+typescript@4.3.4:
resolution: {integrity: sha512-m2l1zLk6YiLckvaXcu4n0cdrFo54XA0eeq+IrfbIp5N9qHf4ACkTPCVu206KPLiaRq2Dr4LMugv+XxZyKIo98w==}
/tachi-common/0.2.1_ts-node@10.0.0+typescript@4.3.4:
resolution: {integrity: sha512-4Dc9K2o9s9ZDKiETLoegKM+rNC/D7BCTxdKeZnHJX8hcWo4lcTcKZbH51NHLb+hfDTGFYTgfHvm3nXSEcrQm9A==}
dependencies:
monk: 7.3.4
tap: 15.0.9_ts-node@10.0.0+typescript@4.3.4
+6 -1
View File
@@ -2,7 +2,12 @@
import t from "tap";
import { RedisPub, RedisSub } from "./redis-IPC";
t.test("Basic PUB/SUB testing", (t) => {
/**
* Note: The functions these things use have been stubbed out. However, the tests still run and
* they also still work. We'll skip them though, anyway.
*/
t.skip("Basic PUB/SUB testing", (t) => {
t.setTimeout(2000);
RedisSub("test" as any, (d) => {
t.equal(d.userID, 1);
+15 -1
View File
@@ -1,10 +1,24 @@
import redis from "redis";
import { RedisIPCChannels, RedisIPCData } from "tachi-common";
import CreateLogCtx from "lib/logger/logger";
import { ServerConfig } from "lib/setup/config";
/**
* This code has been stubbed out! It doesn't really have a use at the moment.
* At some point, though, we might actually need this. (Maybe for a logging framework)
*
* This was used to communicate with other processes running on the same host as this server.
* The idea was the use it for the discord bot, however, we have webhooks now that do this (better).
* Thanks!
*/
const logger = CreateLogCtx(__filename);
// Stub types. In the future, we may actually use this.
export type RedisIPCChannels = "";
export type RedisIPCData = {
"": unknown;
};
type RedisSubCallback<T extends RedisIPCChannels> = (data: RedisIPCData[T]) => void;
type SubCallbacks = {
@@ -2,7 +2,7 @@ import { integer, Game, GoalDocument, UserGoalDocument } from "tachi-common";
import { EvaluateGoalForUser } from "lib/achievables/goals";
import db from "external/mongo/db";
import { KtLogger } from "lib/logger/logger";
import { RedisPub } from "external/redis/redis-IPC";
import { EmitWebhookEvent } from "lib/webhooks/webhooks";
/**
* Update a user's progress on all of their set goals.
@@ -111,11 +111,14 @@ export async function ProcessGoal(
// if this is a newly-achieved goal
if (res.achieved && !userGoal.achieved) {
RedisPub("goal-achieved", {
userID,
goalID: goal.goalID,
old: oldData,
new: newData,
EmitWebhookEvent({
type: "goal-achieved/v1",
content: {
userID,
goalID: goal.goalID,
old: oldData,
new: newData,
},
});
}
@@ -11,7 +11,7 @@ import {
import db from "external/mongo/db";
import { BulkWriteUpdateOneOperation } from "mongodb";
import { CalculateMilestoneOutOf, GetGoalIDsFromMilestone } from "utils/milestone";
import { RedisPub } from "external/redis/redis-IPC";
import { EmitWebhookEvent } from "lib/webhooks/webhooks";
/**
* Processes and updates a user's milestones from their Goal Import Info (i.e. what is returned
@@ -131,9 +131,12 @@ export async function UpdateUsersMilestones(
}
if (achieved && !userMilestone.achieved) {
RedisPub("milestone-achieved", {
userID,
...milestoneInfo,
EmitWebhookEvent({
type: "milestone-achieved/v1",
content: {
userID,
...milestoneInfo,
},
});
}
}
@@ -10,6 +10,7 @@ import {
import { ReturnClassIfGreater } from "utils/class";
import { RedisPub } from "external/redis/redis-IPC";
import { ClassHandler, ScoreClasses } from "./types";
import { EmitWebhookEvent } from "lib/webhooks/webhooks";
type ClassHandlerMap = {
[G in Game]:
@@ -152,7 +153,7 @@ export function CalculateClassDeltas(
};
}
RedisPub("class-update", { userID, ...delta });
EmitWebhookEvent({ type: "class-update/v1", content: { userID, ...delta } });
deltas.push(delta);
}
+37
View File
@@ -0,0 +1,37 @@
import db from "external/mongo/db";
import CreateLogCtx from "lib/logger/logger";
import { WebhookEvents } from "tachi-common";
const logger = CreateLogCtx(__filename);
// @todo make use of aggressive caching here?
export async function GetWebhookUrlInfo() {
const urls = await db["oauth2-clients"].find(
{ webhookUri: { $ne: null } },
{ projection: { webhookUri: 1, clientSecret: 1 } }
);
return urls;
}
/**
* Emits a webhook event to all registered client webhooks on this tachi-server install.
*/
export async function EmitWebhookEvent(content: WebhookEvents) {
const webhookUrls = await GetWebhookUrlInfo();
logger.verbose(`Emitting webhook event ${content.type} to ${webhookUrls.length} clients.`);
// We don't actually care about the response of these. Just fire them and forget.
for (const client of webhookUrls) {
// we know this to be non-null because of GetWebhookUrlInfo.
fetch(client.webhookUri!, {
method: "POST",
body: JSON.stringify(content),
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${client.clientSecret}`,
},
});
}
}
+13 -7
View File
@@ -2,8 +2,8 @@ import { Game, integer, IDStrings, Playtypes, UserGameStats } from "tachi-common
import db from "external/mongo/db";
import CreateLogCtx from "lib/logger/logger";
import { GameClassSets } from "tachi-common/js/game-classes";
import { RedisPub } from "external/redis/redis-IPC";
import { CreateGameSettings } from "lib/game-settings/create-game-settings";
import { EmitWebhookEvent } from "lib/webhooks/webhooks";
const logger = CreateLogCtx(__filename);
@@ -70,15 +70,21 @@ export async function UpdateClassIfGreater(
}
if (isGreater === null) {
RedisPub("class-update", { userID, new: classVal, old: null, set: classSet });
EmitWebhookEvent({
type: "class-update/v1",
content: { userID, new: classVal, old: null, set: classSet },
});
return null;
} else {
RedisPub("class-update", {
userID,
new: classVal,
old: userGameStats!.classes[classSet]!,
set: classSet,
EmitWebhookEvent({
type: "class-update/v1",
content: {
userID,
new: classVal,
old: userGameStats!.classes[classSet]!,
set: classSet,
},
});
return true;