mirror of
https://github.com/zkldi/Tachi.git
synced 2026-10-03 20:38:17 +03:00
Support webhooks (Fixes #29)
This commit is contained in:
+1
-1
@@ -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"
|
||||
},
|
||||
|
||||
Generated
+4
-4
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user