From 53b6b29179b9e0060fbf6104b03a59591fe21256 Mon Sep 17 00:00:00 2001 From: zkldi Date: Thu, 29 Jul 2021 01:09:29 +0100 Subject: [PATCH] Session View Count Fixes #249 --- server/pnpm-lock.yaml | 8 ++-- server/src/external/mongo/db.ts | 2 + server/src/external/mongo/indexes.ts | 5 +++ .../framework/sessions/sessions.ts | 1 + .../api/v1/sessions/_sessionID/middleware.ts | 39 +++++++++++++++++++ .../api/v1/sessions/_sessionID/router.ts | 8 +++- 6 files changed, 57 insertions(+), 6 deletions(-) diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index 97bfffff6..554968c97 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -84,7 +84,7 @@ dependencies: redis: 3.1.2 rimraf: 3.0.2 safe-json-stringify: 1.2.0 - tachi-common: github.com/zkldi/tachi-common/606ef49825d0e7b649a0b50899d162d603c2c9e2_ts-node@10.0.0+typescript@4.3.4 + tachi-common: github.com/zkldi/tachi-common/42a4da4ced2934d73b862f31a38814a0cc0a16f0_ts-node@10.0.0+typescript@4.3.4 typescript: 4.3.4 winston: 3.3.3 @@ -4226,9 +4226,9 @@ packages: engines: {node: '>=6'} dev: true - github.com/zkldi/tachi-common/606ef49825d0e7b649a0b50899d162d603c2c9e2_ts-node@10.0.0+typescript@4.3.4: - resolution: {tarball: https://codeload.github.com/zkldi/tachi-common/tar.gz/606ef49825d0e7b649a0b50899d162d603c2c9e2} - id: github.com/zkldi/tachi-common/606ef49825d0e7b649a0b50899d162d603c2c9e2 + github.com/zkldi/tachi-common/42a4da4ced2934d73b862f31a38814a0cc0a16f0_ts-node@10.0.0+typescript@4.3.4: + resolution: {tarball: https://codeload.github.com/zkldi/tachi-common/tar.gz/42a4da4ced2934d73b862f31a38814a0cc0a16f0} + id: github.com/zkldi/tachi-common/42a4da4ced2934d73b862f31a38814a0cc0a16f0 name: tachi-common version: 0.1.0 dependencies: diff --git a/server/src/external/mongo/db.ts b/server/src/external/mongo/db.ts index 22a6cd539..d678227ab 100644 --- a/server/src/external/mongo/db.ts +++ b/server/src/external/mongo/db.ts @@ -26,6 +26,7 @@ import { BMSCourseDocument, ImportLockDocument, UGPTSettings, + SessionViewDocument, } from "tachi-common"; import monk, { TMiddleware } from "monk"; import { MONGO_CONNECTION_URL, MONGO_DATABASE_NAME } from "lib/setup/config"; @@ -144,6 +145,7 @@ const db = { tables: monkDB.get("tables"), "game-settings": monkDB.get("game-settings"), "game-stats-snapshots": monkDB.get("game-stats-snapshots"), + "session-view-cache": monkDB.get("session-view-cache"), }; export default db; diff --git a/server/src/external/mongo/indexes.ts b/server/src/external/mongo/indexes.ts index 2dcebe184..5dad5ea6f 100644 --- a/server/src/external/mongo/indexes.ts +++ b/server/src/external/mongo/indexes.ts @@ -4,6 +4,7 @@ import { IndexOptions } from "mongodb"; import { ValidDatabases } from "tachi-common"; import CreateLogCtx from "lib/logger/logger"; import { CONF_INFO } from "lib/setup/config"; +import { ONE_DAY } from "lib/constants/time"; const logger = CreateLogCtx(__filename); @@ -80,6 +81,10 @@ const staticIndexes: Partial> = { "api-tokens": [index({ token: 1 }, UNIQUE), index({ userID: 1 })], tables: [index({ tableID: 1 }, UNIQUE), index({ game: 1, playtype: 1 })], "game-stats-snapshots": [index({ timestamp: 1, userID: 1, game: 1, playtype: 1 }, UNIQUE)], + "session-view-cache": [ + index({ sessionID: 1, ip: 1 }, UNIQUE), + index({ timestamp: 1 }, { expireAfterSeconds: ONE_DAY / 1000 }), + ], }; const indexes: Partial> = staticIndexes; diff --git a/server/src/lib/score-import/framework/sessions/sessions.ts b/server/src/lib/score-import/framework/sessions/sessions.ts index e16405b26..82287ab04 100644 --- a/server/src/lib/score-import/framework/sessions/sessions.ts +++ b/server/src/lib/score-import/framework/sessions/sessions.ts @@ -133,6 +133,7 @@ function CreateSession( timeStarted: groupScores[0].timeAchieved!, timeEnded: groupScores[groupScores.length - 1].timeAchieved!, calculatedData, + views: 0, }; } diff --git a/server/src/server/router/api/v1/sessions/_sessionID/middleware.ts b/server/src/server/router/api/v1/sessions/_sessionID/middleware.ts index e40daf3d2..9e4be647f 100644 --- a/server/src/server/router/api/v1/sessions/_sessionID/middleware.ts +++ b/server/src/server/router/api/v1/sessions/_sessionID/middleware.ts @@ -1,6 +1,7 @@ import { RequestHandler } from "express"; import db from "external/mongo/db"; import { SYMBOL_TachiAPIAuth, SYMBOL_TachiData } from "lib/constants/tachi"; +import { ONE_DAY } from "lib/constants/time"; import { AssignToReqTachiData } from "utils/req-tachi-data"; export const GetSessionFromParam: RequestHandler = async (req, res, next) => { @@ -33,3 +34,41 @@ export const RequireOwnershipOfSession: RequestHandler = (req, res, next) => { return next(); }; + +export const UpdateSessionViewcount: RequestHandler = async (req, res, next) => { + const session = req[SYMBOL_TachiData]!.sessionDoc!; + + // we don't need to actually check timestamp - this collection expires documents every 24hours. + const hasViewedRecently = await db["session-view-cache"].findOne({ + sessionID: session.sessionID, + ip: req.ip, + }); + + if (hasViewedRecently) { + return next(); + } + + await db["session-view-cache"].insert([ + { + sessionID: session.sessionID, + ip: req.ip, + timestamp: Date.now(), + }, + ]); + + await db.sessions.update( + { + sessionID: session.sessionID, + }, + { + $inc: { + views: 1, + }, + } + ); + + // increment locally so that the right state is shown to the end user. + session.views++; + + return next(); +}; diff --git a/server/src/server/router/api/v1/sessions/_sessionID/router.ts b/server/src/server/router/api/v1/sessions/_sessionID/router.ts index 84e935210..728b73e77 100644 --- a/server/src/server/router/api/v1/sessions/_sessionID/router.ts +++ b/server/src/server/router/api/v1/sessions/_sessionID/router.ts @@ -4,7 +4,11 @@ import { SYMBOL_TachiData } from "lib/constants/tachi"; import { RequirePermissions } from "server/middleware/auth"; import prValidate from "server/middleware/prudence-validate"; import p from "prudence"; -import { GetSessionFromParam, RequireOwnershipOfSession } from "./middleware"; +import { + GetSessionFromParam, + RequireOwnershipOfSession, + UpdateSessionViewcount, +} from "./middleware"; import { GetUserWithID } from "utils/user"; const router: Router = Router({ mergeParams: true }); @@ -16,7 +20,7 @@ router.use(GetSessionFromParam); * * @name GET /api/v1/sessions/:sessionID */ -router.get("/", async (req, res) => { +router.get("/", UpdateSessionViewcount, async (req, res) => { const session = req[SYMBOL_TachiData]!.sessionDoc!; const scores = await db.scores.find({