From 428764a1f39edc0e2cef4588681c91e2fd1dfd58 Mon Sep 17 00:00:00 2001 From: zkldi <20380519+zkldi@users.noreply.github.com> Date: Mon, 22 Nov 2021 11:44:27 +0000 Subject: [PATCH] Get stuff a user did in the last (16) hours --- .../api/arc-iidx/converter.test.ts | 2 +- .../import-types/api/arc-iidx/converter.ts | 2 +- .../router/api/v1/users/_userID/router.ts | 56 +++++++++++++++---- server/src/utils/misc.ts | 7 ++- server/src/utils/queries/summary.ts | 47 ++++++++++++++++ 5 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 server/src/utils/queries/summary.ts diff --git a/server/src/lib/score-import/import-types/api/arc-iidx/converter.test.ts b/server/src/lib/score-import/import-types/api/arc-iidx/converter.test.ts index 9bd19e86b..4ab0e89ab 100644 --- a/server/src/lib/score-import/import-types/api/arc-iidx/converter.test.ts +++ b/server/src/lib/score-import/import-types/api/arc-iidx/converter.test.ts @@ -24,7 +24,7 @@ t.test("#ConvertAPIArcIIDX", (t) => { game: "iidx", importType: "api/arc-iidx", timeAchieved: 1604784681894, - service: "ARC IIDX27", + service: "ARC IIDX28", scoreData: { grade: "A", score: 1200, diff --git a/server/src/lib/score-import/import-types/api/arc-iidx/converter.ts b/server/src/lib/score-import/import-types/api/arc-iidx/converter.ts index 01f7a0862..8d2eee949 100644 --- a/server/src/lib/score-import/import-types/api/arc-iidx/converter.ts +++ b/server/src/lib/score-import/import-types/api/arc-iidx/converter.ts @@ -80,7 +80,7 @@ export const ConvertAPIArcIIDX: ConverterFunction = async game: "iidx", importType, timeAchieved, - service: "ARC IIDX27", + service: "ARC IIDX28", scoreData: { grade, percent, diff --git a/server/src/server/router/api/v1/users/_userID/router.ts b/server/src/server/router/api/v1/users/_userID/router.ts index 7fec93ce7..02c4583a5 100644 --- a/server/src/server/router/api/v1/users/_userID/router.ts +++ b/server/src/server/router/api/v1/users/_userID/router.ts @@ -1,21 +1,26 @@ import { Router } from "express"; import db from "external/mongo/db"; import { SYMBOL_TachiData } from "lib/constants/tachi"; -import { GetUserFromParam, RequireSelfRequestFromUser } from "./middleware"; -import gamePTRouter from "./games/_game/_playtype/router"; -import bannerRouter from "./banner/router"; -import pfpRouter from "./pfp/router"; -import integrationsRouter from "./integrations/router"; -import settingsRouter from "./settings/router"; -import prValidate from "server/middleware/prudence-validate"; -import p from "prudence"; -import { optNull, optNullFluffStrField } from "utils/prudence"; -import { DeleteUndefinedProps, StripUrl } from "utils/misc"; -import { FormatUserDoc, GetAllRankings, GetUserWithID } from "utils/user"; -import { UserGameStats } from "tachi-common"; import CreateLogCtx from "lib/logger/logger"; +import p from "prudence"; +import prValidate from "server/middleware/prudence-validate"; +import { UserGameStats } from "tachi-common"; +import { DeleteUndefinedProps, StripUrl } from "utils/misc"; +import { optNull, optNullFluffStrField } from "utils/prudence"; +import { + GetRecentlyViewedFoldersAnyGPT, + GetRecentPlaycount, + GetRecentSessions, +} from "utils/queries/summary"; +import { FormatUserDoc, GetAllRankings, GetUserWithID } from "utils/user"; import apiTokensRouter from "./api-tokens/router"; +import bannerRouter from "./banner/router"; +import gamePTRouter from "./games/_game/_playtype/router"; +import integrationsRouter from "./integrations/router"; import invitesRouter from "./invites/router"; +import { GetUserFromParam, RequireSelfRequestFromUser } from "./middleware"; +import pfpRouter from "./pfp/router"; +import settingsRouter from "./settings/router"; const logger = CreateLogCtx(__filename); @@ -203,6 +208,33 @@ router.get("/game-stats", async (req, res) => { }); }); +/** + * Returns a summary of what the user has achieved in the past 24 hours. + * Used on the main dashboard page to give users quick links to sessions, + * alongside other information. + * + * @name GET /api/v1/users/:userID/recent-summary + */ +router.get("/recent-summary", async (req, res) => { + const user = req[SYMBOL_TachiData]!.requestedUser!; + + const [recentPlaycount, recentSessions, recentFolders] = await Promise.all([ + GetRecentPlaycount(user.id), + GetRecentSessions(user.id), + GetRecentlyViewedFoldersAnyGPT(user.id), + ]); + + return res.status(200).json({ + success: true, + description: `Retrieved information about ${FormatUserDoc(user)}.`, + body: { + recentPlaycount, + recentSessions, + recentFolders, + }, + }); +}); + /** * Returns whether the user has verified their email or not. * Requires self-key level permissions. diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts index b3c6cda53..67f00cfbb 100644 --- a/server/src/utils/misc.ts +++ b/server/src/utils/misc.ts @@ -1,6 +1,7 @@ import crypto from "crypto"; +import { ONE_HOUR } from "lib/constants/time"; import { TachiConfig } from "lib/setup/config"; -import { Game, GamePTConfig, GetGameConfig, Playtypes } from "tachi-common"; +import { Game, GamePTConfig, GetGameConfig, Playtypes, integer } from "tachi-common"; // https://github.com/sindresorhus/escape-string-regexp/blob/main/index.js // the developer of this has migrated everything to Force ES6 style modules, @@ -128,3 +129,7 @@ export function IsValidURL(string: string) { export function Sleep(ms: number) { return new Promise((resolve) => setTimeout(() => resolve(), ms)); } + +export function GetTimeXHoursAgo(hours: integer) { + return Date.now() - ONE_HOUR * hours; +} diff --git a/server/src/utils/queries/summary.ts b/server/src/utils/queries/summary.ts new file mode 100644 index 000000000..c40e993fb --- /dev/null +++ b/server/src/utils/queries/summary.ts @@ -0,0 +1,47 @@ +import db from "external/mongo/db"; +import { integer } from "tachi-common"; +import { GetTimeXHoursAgo } from "utils/misc"; + +// Various utils related to the player summary endpoint. +const REASONABLE_HOURS_AGO = 16; + +export function GetRecentPlaycount(userID: integer) { + const time = GetTimeXHoursAgo(REASONABLE_HOURS_AGO); + + return db.scores.count({ userID, timeAchieved: { $gte: time } }); +} + +export function GetRecentSessions(userID: integer) { + const time = GetTimeXHoursAgo(REASONABLE_HOURS_AGO); + + return db.sessions.find({ + userID, + timeEnded: { $gte: time }, + }); +} + +export async function GetRecentlyViewedFoldersAnyGPT(userID: integer) { + const time = GetTimeXHoursAgo(REASONABLE_HOURS_AGO); + + const views = await db["recent-folder-views"].find( + { + userID, + lastViewed: { $gte: time }, + }, + { + sort: { + lastViewed: -1, + }, + limit: 4, + } + ); + + const folders = await db.folders.find({ + folderID: { $in: views.map((e) => e.folderID) }, + }); + + // TODO: Sort recently viewed folders based on how recently viewed + // they were. + + return folders; +}