diff --git a/.vscode/settings.json b/.vscode/settings.json index 56b68a9b2..034a26912 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,9 @@ "typescript.tsdk": "node_modules/typescript/lib", "[typescriptreact]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint" + }, + "[typescript]": { + "editor.defaultFormatter": "dbaeumer.vscode-eslint" } +} } \ No newline at end of file diff --git a/Dockerfile.bootstrap b/Dockerfile.bootstrap index fda324acc..4cd332d7d 100644 --- a/Dockerfile.bootstrap +++ b/Dockerfile.bootstrap @@ -6,7 +6,7 @@ # I really do not care. # Want it better? Do it yourself. -FROM node:20 as base +FROM node:20 AS base RUN npm install --silent -g pnpm@8.15.6 diff --git a/Dockerfile.bot b/Dockerfile.bot index 4008d8cb4..23ba39609 100644 --- a/Dockerfile.bot +++ b/Dockerfile.bot @@ -1,4 +1,4 @@ -FROM node:20-alpine as base +FROM node:20-alpine AS base RUN npm install --silent -g pnpm@8.15.6 FROM base AS build diff --git a/Dockerfile.client b/Dockerfile.client index 63f844c1e..61110822a 100644 --- a/Dockerfile.client +++ b/Dockerfile.client @@ -1,4 +1,4 @@ -FROM node:20-alpine as base +FROM node:20-alpine AS base RUN npm install --silent -g pnpm@8.15.6 diff --git a/Dockerfile.seeds b/Dockerfile.seeds index 37c9ae77d..92906c6ee 100644 --- a/Dockerfile.seeds +++ b/Dockerfile.seeds @@ -1,5 +1,5 @@ # no alpine here as we want bash + general nice-to-haves -FROM node:20 as base +FROM node:20 AS base RUN npm install --silent -g pnpm@8.15.6 diff --git a/Dockerfile.server b/Dockerfile.server index b526c50be..dc3537c3f 100644 --- a/Dockerfile.server +++ b/Dockerfile.server @@ -3,7 +3,7 @@ # does need to boot. You should consider using docker-compose for this. # base image -FROM node:20-alpine as base +FROM node:20-alpine AS base ARG COMMIT_HASH ENV COMMIT_HASH=${COMMIT_HASH} diff --git a/client/src/components/user/UGPTHeader.tsx b/client/src/components/user/UGPTHeader.tsx index 5bec4ae30..c25e4ed0d 100644 --- a/client/src/components/user/UGPTHeader.tsx +++ b/client/src/components/user/UGPTHeader.tsx @@ -1,4 +1,4 @@ -import { MillisToSince } from "util/time"; +import { FormatDurationHours, MillisToSince } from "util/time"; import { APIFetchV1 } from "util/api"; import { HumaniseError } from "util/humanise-error"; import { SendErrorToast, SendSuccessToast } from "util/toaster"; @@ -16,6 +16,8 @@ import { GetGPTUtilsName } from "components/gpt-utils/GPTUtils"; import ApiError from "components/util/ApiError"; import Loading from "components/util/Loading"; import useApiQuery from "components/util/query/useApiQuery"; +import QuickTooltip from "components/layout/misc/QuickTooltip"; +import { TachiConfig } from "lib/config"; import ProfileBadges from "./ProfileBadges"; import ProfilePicture from "./ProfilePicture"; import RankingData from "./UGPTRankingData"; @@ -82,6 +84,23 @@ export function UGPTHeaderBody({ : MillisToSince(stats.firstScore.timeAchieved)} + + + +
+ Session Playtime +
+
+ + {FormatDurationHours(stats.playtime)} +
diff --git a/client/src/types/api-returns.ts b/client/src/types/api-returns.ts index 0f6a7eee8..482c211c6 100644 --- a/client/src/types/api-returns.ts +++ b/client/src/types/api-returns.ts @@ -41,6 +41,7 @@ export interface UGPTStatsReturn { outOf: integer; } >; + playtime: number; } export interface UGPTLeaderboardAdjacent { diff --git a/client/src/util/time.ts b/client/src/util/time.ts index 5f31b74f9..5634ac410 100644 --- a/client/src/util/time.ts +++ b/client/src/util/time.ts @@ -20,6 +20,13 @@ export function FormatDuration(ms: number) { }); } +export function FormatDurationHours(ms: number) { + return humaniseDuration(ms, { + units: ["h"], + maxDecimalPoints: 0, + }); +} + export function FormatTimeSmall(ms: number) { return DateTime.fromMillis(ms).toLocaleString(DateTime.DATE_SHORT); } diff --git a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts index 6fe653b82..6ad933a8f 100644 --- a/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts +++ b/server/src/server/router/api/v1/users/_userID/games/_game/_playtype/router.ts @@ -53,7 +53,7 @@ router.get("/", async (req, res) => { const stats = GetTachiData(req, "requestedUserGameStats"); - const [totalScores, firstScore, mostRecentScore, rankingData] = await Promise.all([ + const [totalScores, firstScore, mostRecentScore, rankingData, playtime] = await Promise.all([ db.scores.count({ userID: user.id, game, @@ -86,6 +86,26 @@ router.get("/", async (req, res) => { } ), GetAllRankings(stats), + db.sessions.aggregate<[{ playtime: number }]>([ + { + $match: { + userID: user.id, + game, + playtype, + }, + }, + { + $project: { + duration: { $subtract: ["$timeEnded", "$timeStarted"] }, + }, + }, + { + $group: { + _id: null, + playtime: { $sum: "$duration" }, + }, + }, + ]), ]); return res.status(200).json({ @@ -97,6 +117,7 @@ router.get("/", async (req, res) => { mostRecentScore, totalScores, rankingData, + playtime: playtime[0].playtime, }, }); });