This commit is contained in:
parent
e0363de4ad
commit
4c6a8c80fb
152
package-lock.json
generated
152
package-lock.json
generated
@ -10,10 +10,12 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@influxdata/influxdb-client": "^1.33.2",
|
"@influxdata/influxdb-client": "^1.33.2",
|
||||||
|
"@sentry/node": "^7.77.0",
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"mongoose": "^7.6.3",
|
"mongoose": "^7.6.3",
|
||||||
|
"node-cron": "^3.0.2",
|
||||||
"nodemon": "^3.0.1",
|
"nodemon": "^3.0.1",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"websocket": "^1.0.34"
|
"websocket": "^1.0.34"
|
||||||
@ -21,6 +23,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^4.17.20",
|
"@types/express": "^4.17.20",
|
||||||
"@types/node": "^20.8.9",
|
"@types/node": "^20.8.9",
|
||||||
|
"@types/node-cron": "^3.0.10",
|
||||||
"@types/websocket": "^1.0.8"
|
"@types/websocket": "^1.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -38,6 +41,65 @@
|
|||||||
"sparse-bitfield": "^3.0.3"
|
"sparse-bitfield": "^3.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@sentry-internal/tracing": {
|
||||||
|
"version": "7.77.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.77.0.tgz",
|
||||||
|
"integrity": "sha512-8HRF1rdqWwtINqGEdx8Iqs9UOP/n8E0vXUu3Nmbqj4p5sQPA7vvCfq+4Y4rTqZFc7sNdFpDsRION5iQEh8zfZw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/core": "7.77.0",
|
||||||
|
"@sentry/types": "7.77.0",
|
||||||
|
"@sentry/utils": "7.77.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/core": {
|
||||||
|
"version": "7.77.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.77.0.tgz",
|
||||||
|
"integrity": "sha512-Tj8oTYFZ/ZD+xW8IGIsU6gcFXD/gfE+FUxUaeSosd9KHwBQNOLhZSsYo/tTVf/rnQI/dQnsd4onPZLiL+27aTg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/types": "7.77.0",
|
||||||
|
"@sentry/utils": "7.77.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/node": {
|
||||||
|
"version": "7.77.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.77.0.tgz",
|
||||||
|
"integrity": "sha512-Ob5tgaJOj0OYMwnocc6G/CDLWC7hXfVvKX/ofkF98+BbN/tQa5poL+OwgFn9BA8ud8xKzyGPxGU6LdZ8Oh3z/g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry-internal/tracing": "7.77.0",
|
||||||
|
"@sentry/core": "7.77.0",
|
||||||
|
"@sentry/types": "7.77.0",
|
||||||
|
"@sentry/utils": "7.77.0",
|
||||||
|
"https-proxy-agent": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/types": {
|
||||||
|
"version": "7.77.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.77.0.tgz",
|
||||||
|
"integrity": "sha512-nfb00XRJVi0QpDHg+JkqrmEBHsqBnxJu191Ded+Cs1OJ5oPXEW6F59LVcBScGvMqe+WEk1a73eH8XezwfgrTsA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@sentry/utils": {
|
||||||
|
"version": "7.77.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.77.0.tgz",
|
||||||
|
"integrity": "sha512-NmM2kDOqVchrey3N5WSzdQoCsyDkQkiRxExPaNI2oKQ/jMWHs9yt0tSy7otPBcXs0AP59ihl75Bvm1tDRcsp5g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@sentry/types": "7.77.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/body-parser": {
|
"node_modules/@types/body-parser": {
|
||||||
"version": "1.19.4",
|
"version": "1.19.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz",
|
||||||
@ -101,6 +163,12 @@
|
|||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/node-cron": {
|
||||||
|
"version": "3.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node-cron/-/node-cron-3.0.10.tgz",
|
||||||
|
"integrity": "sha512-8thdLSpV7na8+bsmiyMH/KKQWOBg4WmoLInlGxWz7JJn8Mie+53QWaygSlc7f/AsMsCB5bBTVjoueAU6tRutTw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/qs": {
|
"node_modules/@types/qs": {
|
||||||
"version": "6.9.9",
|
"version": "6.9.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz",
|
||||||
@ -174,6 +242,38 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/agent-base": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/agent-base/node_modules/debug": {
|
||||||
|
"version": "4.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "2.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/agent-base/node_modules/ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
|
},
|
||||||
"node_modules/anymatch": {
|
"node_modules/anymatch": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
@ -795,6 +895,39 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/https-proxy-agent": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "6",
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/https-proxy-agent/node_modules/debug": {
|
||||||
|
"version": "4.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "2.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/https-proxy-agent/node_modules/ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
@ -1087,6 +1220,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
|
||||||
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
|
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/node-cron": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"uuid": "8.3.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/node-gyp-build": {
|
"node_modules/node-gyp-build": {
|
||||||
"version": "4.6.1",
|
"version": "4.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz",
|
||||||
@ -1568,6 +1712,14 @@
|
|||||||
"node": ">= 0.4.0"
|
"node": ">= 0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vary": {
|
"node_modules/vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
@ -12,10 +12,12 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@influxdata/influxdb-client": "^1.33.2",
|
"@influxdata/influxdb-client": "^1.33.2",
|
||||||
|
"@sentry/node": "^7.77.0",
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"mongoose": "^7.6.3",
|
"mongoose": "^7.6.3",
|
||||||
|
"node-cron": "^3.0.2",
|
||||||
"nodemon": "^3.0.1",
|
"nodemon": "^3.0.1",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"websocket": "^1.0.34"
|
"websocket": "^1.0.34"
|
||||||
@ -23,6 +25,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/express": "^4.17.20",
|
"@types/express": "^4.17.20",
|
||||||
"@types/node": "^20.8.9",
|
"@types/node": "^20.8.9",
|
||||||
|
"@types/node-cron": "^3.0.10",
|
||||||
"@types/websocket": "^1.0.8"
|
"@types/websocket": "^1.0.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
47
src/api/api.ts
Normal file
47
src/api/api.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import * as Sentry from "@sentry/node";
|
||||||
|
import express from "express";
|
||||||
|
import analyticsRoute from "./routes/analytics";
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
Sentry.init({
|
||||||
|
dsn: "https://19a8f6e661f41ed26ec9c221954594cf@sentry.fascinated.cc/3",
|
||||||
|
integrations: [
|
||||||
|
// enable HTTP calls tracing
|
||||||
|
new Sentry.Integrations.Http({ tracing: true }),
|
||||||
|
// enable Express.js middleware tracing
|
||||||
|
new Sentry.Integrations.Express({ app }),
|
||||||
|
new Sentry.Integrations.OnUncaughtException(),
|
||||||
|
new Sentry.Integrations.OnUnhandledRejection(),
|
||||||
|
new Sentry.Integrations.Mongo({
|
||||||
|
useMongoose: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
// Performance Monitoring
|
||||||
|
tracesSampleRate: 1.0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Routes
|
||||||
|
analyticsRoute(app);
|
||||||
|
|
||||||
|
// The request handler must be the first middleware on the app
|
||||||
|
app.use(Sentry.Handlers.requestHandler());
|
||||||
|
|
||||||
|
// TracingHandler creates a trace for every incoming request
|
||||||
|
app.use(Sentry.Handlers.tracingHandler());
|
||||||
|
|
||||||
|
// The error handler must be registered before any other error middleware and after all controllers
|
||||||
|
app.use(Sentry.Handlers.errorHandler());
|
||||||
|
|
||||||
|
// Optional fallthrough error handler
|
||||||
|
app.use(function onError(err: any, req: any, res: any, next: any) {
|
||||||
|
// The error id is attached to `res.sentry` to be returned
|
||||||
|
// and optionally displayed to the user for support.
|
||||||
|
res.statusCode = 500;
|
||||||
|
res.end(res.sentry + "\n");
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`API Server is running on http://localhost:${port}`);
|
||||||
|
});
|
85
src/api/routes/analytics.ts
Normal file
85
src/api/routes/analytics.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { INFLUXDB_BUCKET, InfluxQueryAPI } from "../..";
|
||||||
|
import { formatString } from "../../utils/stringUtils";
|
||||||
|
import { parseTimeToMilliseconds } from "../../utils/timeUtils";
|
||||||
|
|
||||||
|
// Query to get the player count history for tge last 24 hours in 1 hour intervals
|
||||||
|
const getPlayerHistoryQuery = `from(bucket: "${INFLUXDB_BUCKET}")
|
||||||
|
|> range(start: -{})
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "scoresaber")
|
||||||
|
|> filter(fn: (r) => r["_field"] == "value")
|
||||||
|
|> filter(fn: (r) => r["type"] == "player_count")
|
||||||
|
|> aggregateWindow(every: {}, fn: mean)
|
||||||
|
|> yield()
|
||||||
|
`;
|
||||||
|
|
||||||
|
const getScoreCountHistoryQuery = `from(bucket: "${INFLUXDB_BUCKET}")
|
||||||
|
|> range(start: -{})
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "scoresaber")
|
||||||
|
|> filter(fn: (r) => r["_field"] == "value")
|
||||||
|
|> filter(fn: (r) => r["type"] == "score_count")
|
||||||
|
|> aggregateWindow(every: {}, fn: spread, createEmpty: true)
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default function analyticsRoute(app: any) {
|
||||||
|
app.get("/analytics", async (req: any, res: any) => {
|
||||||
|
const before = new Date().getTime();
|
||||||
|
|
||||||
|
const timeQuery = req.query.time || "30d";
|
||||||
|
const timeInMs = parseTimeToMilliseconds(timeQuery.toString());
|
||||||
|
if (timeInMs > 30 * 24 * 60 * 60 * 1000) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: "Time range too large. Max time range is 30 days.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const shouldUseLongerIntervals = timeInMs > 24 * 60 * 60 * 1000 * 7; // 7 days
|
||||||
|
|
||||||
|
const getActivePlayersHistory = async () => {
|
||||||
|
const rows = await InfluxQueryAPI.collectRows(
|
||||||
|
formatString(
|
||||||
|
getPlayerHistoryQuery,
|
||||||
|
false,
|
||||||
|
timeQuery,
|
||||||
|
shouldUseLongerIntervals ? "1d" : "1h"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let history = rows.map((row: any) => ({
|
||||||
|
time: row._time,
|
||||||
|
value: row._value !== null ? row._value.toFixed(0) : null,
|
||||||
|
}));
|
||||||
|
return history.sort(
|
||||||
|
(a: any, b: any) =>
|
||||||
|
new Date(a.time).getTime() - new Date(b.time).getTime()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getScoreCountHistory = async () => {
|
||||||
|
const rows = await InfluxQueryAPI.collectRows(
|
||||||
|
formatString(
|
||||||
|
getScoreCountHistoryQuery,
|
||||||
|
false,
|
||||||
|
timeQuery,
|
||||||
|
shouldUseLongerIntervals ? "1d" : "1h"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let history = rows.map((row: any) => ({
|
||||||
|
time: row._time,
|
||||||
|
value: row._value !== null ? row._value.toFixed(0) : null,
|
||||||
|
}));
|
||||||
|
return history.sort(
|
||||||
|
(a: any, b: any) =>
|
||||||
|
new Date(a.time).getTime() - new Date(b.time).getTime()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const [activePlayersHistory, scoreCountHistory] = await Promise.all([
|
||||||
|
getActivePlayersHistory(),
|
||||||
|
getScoreCountHistory(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
serverTimeTaken: new Date().getTime() - before + "ms",
|
||||||
|
activePlayersHistory,
|
||||||
|
scoreCountHistory,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -23,4 +23,4 @@ export const InfluxWriteAPI = influxClient.getWriteApi(
|
|||||||
export const InfluxQueryAPI = influxClient.getQueryApi(INFLUXDB_ORG);
|
export const InfluxQueryAPI = influxClient.getQueryApi(INFLUXDB_ORG);
|
||||||
|
|
||||||
require("./services/updateData");
|
require("./services/updateData");
|
||||||
require("./services/api");
|
require("./api/api");
|
||||||
|
@ -1,95 +0,0 @@
|
|||||||
import express from "express";
|
|
||||||
import { INFLUXDB_BUCKET, InfluxQueryAPI } from "../index";
|
|
||||||
import { formatString } from "../utils/stringUtils";
|
|
||||||
import { parseTimeToMilliseconds } from "../utils/timeUtils";
|
|
||||||
|
|
||||||
const app = express();
|
|
||||||
const port = process.env.PORT || 3000;
|
|
||||||
|
|
||||||
// Query to get the player count history for tge last 24 hours in 1 hour intervals
|
|
||||||
const getPlayerHistoryQuery = `from(bucket: "${INFLUXDB_BUCKET}")
|
|
||||||
|> range(start: -{})
|
|
||||||
|> filter(fn: (r) => r["_measurement"] == "scoresaber")
|
|
||||||
|> filter(fn: (r) => r["_field"] == "value")
|
|
||||||
|> filter(fn: (r) => r["type"] == "player_count")
|
|
||||||
|> aggregateWindow(every: {}, fn: mean)
|
|
||||||
|> yield()
|
|
||||||
`;
|
|
||||||
|
|
||||||
const getScoreCountHistoryQuery = `from(bucket: "${INFLUXDB_BUCKET}")
|
|
||||||
|> range(start: -{})
|
|
||||||
|> filter(fn: (r) => r["_measurement"] == "scoresaber")
|
|
||||||
|> filter(fn: (r) => r["_field"] == "value")
|
|
||||||
|> filter(fn: (r) => r["type"] == "score_count")
|
|
||||||
|> aggregateWindow(every: {}, fn: spread, createEmpty: true)
|
|
||||||
`;
|
|
||||||
|
|
||||||
app.get("/", (req, res) => {
|
|
||||||
res.send("Hello!");
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get("/analytics", async (req, res) => {
|
|
||||||
const before = new Date().getTime();
|
|
||||||
|
|
||||||
const timeQuery = req.query.time || "30d";
|
|
||||||
const timeInMs = parseTimeToMilliseconds(timeQuery.toString());
|
|
||||||
if (timeInMs > 30 * 24 * 60 * 60 * 1000) {
|
|
||||||
return res.status(400).json({
|
|
||||||
error: "Time range too large. Max time range is 30 days.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const shouldUseLongerIntervals = timeInMs > 24 * 60 * 60 * 1000 * 7; // 7 days
|
|
||||||
|
|
||||||
const getActivePlayersHistory = async () => {
|
|
||||||
const rows = await InfluxQueryAPI.collectRows(
|
|
||||||
formatString(
|
|
||||||
getPlayerHistoryQuery,
|
|
||||||
false,
|
|
||||||
timeQuery,
|
|
||||||
shouldUseLongerIntervals ? "1d" : "1h"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let history = rows.map((row: any) => ({
|
|
||||||
time: row._time,
|
|
||||||
value: row._value !== null ? row._value.toFixed(0) : null,
|
|
||||||
}));
|
|
||||||
return history.sort(
|
|
||||||
(a: any, b: any) =>
|
|
||||||
new Date(a.time).getTime() - new Date(b.time).getTime()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getScoreCountHistory = async () => {
|
|
||||||
const rows = await InfluxQueryAPI.collectRows(
|
|
||||||
formatString(
|
|
||||||
getScoreCountHistoryQuery,
|
|
||||||
false,
|
|
||||||
timeQuery,
|
|
||||||
shouldUseLongerIntervals ? "1d" : "1h"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
let history = rows.map((row: any) => ({
|
|
||||||
time: row._time,
|
|
||||||
value: row._value !== null ? row._value.toFixed(0) : null,
|
|
||||||
}));
|
|
||||||
return history.sort(
|
|
||||||
(a: any, b: any) =>
|
|
||||||
new Date(a.time).getTime() - new Date(b.time).getTime()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [activePlayersHistory, scoreCountHistory] = await Promise.all([
|
|
||||||
getActivePlayersHistory(),
|
|
||||||
getScoreCountHistory(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return res.json({
|
|
||||||
serverTimeTaken: new Date().getTime() - before + "ms",
|
|
||||||
activePlayersHistory,
|
|
||||||
scoreCountHistory,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(port, () => {
|
|
||||||
console.log(`API Server is running on http://localhost:${port}`);
|
|
||||||
});
|
|
@ -1,5 +1,6 @@
|
|||||||
import { Point } from "@influxdata/influxdb-client";
|
import { Point } from "@influxdata/influxdb-client";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import cron from "node-cron";
|
||||||
import { w3cwebsocket as WebsocketClient } from "websocket";
|
import { w3cwebsocket as WebsocketClient } from "websocket";
|
||||||
import { InfluxWriteAPI } from "..";
|
import { InfluxWriteAPI } from "..";
|
||||||
import { connectMongo } from "../db/mongo";
|
import { connectMongo } from "../db/mongo";
|
||||||
@ -18,6 +19,7 @@ async function update() {
|
|||||||
.intField("value", parseInt(count))
|
.intField("value", parseInt(count))
|
||||||
.timestamp(new Date());
|
.timestamp(new Date());
|
||||||
InfluxWriteAPI.writePoint(point);
|
InfluxWriteAPI.writePoint(point);
|
||||||
|
console.log(`Updated player count to ${count}`);
|
||||||
|
|
||||||
if (totalScores) {
|
if (totalScores) {
|
||||||
InfluxWriteAPI.writePoint(
|
InfluxWriteAPI.writePoint(
|
||||||
@ -26,9 +28,8 @@ async function update() {
|
|||||||
.intField("value", totalScores)
|
.intField("value", totalScores)
|
||||||
.timestamp(new Date())
|
.timestamp(new Date())
|
||||||
);
|
);
|
||||||
|
console.log(`Updated score count to ${totalScores}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Updated player count to ${count}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function connectWebsocket() {
|
async function connectWebsocket() {
|
||||||
@ -83,7 +84,7 @@ async function connectWebsocket() {
|
|||||||
leaderboardPlayerInfo: player,
|
leaderboardPlayerInfo: player,
|
||||||
pp,
|
pp,
|
||||||
} = score;
|
} = score;
|
||||||
const { maxScore, stars, id: leaderboardId } = leaderboard;
|
const { maxScore, stars } = leaderboard;
|
||||||
|
|
||||||
const hmdName = Headsets[hmd] || "Unknown";
|
const hmdName = Headsets[hmd] || "Unknown";
|
||||||
const countryId = normalizedRegionName(player.country) || "Unknown";
|
const countryId = normalizedRegionName(player.country) || "Unknown";
|
||||||
@ -144,4 +145,4 @@ async function connectWebsocket() {
|
|||||||
|
|
||||||
update();
|
update();
|
||||||
connectWebsocket();
|
connectWebsocket();
|
||||||
setInterval(update, 60_000); // 1 minute
|
cron.schedule("*/5 * * * *", update);
|
||||||
|
Loading…
Reference in New Issue
Block a user