diff --git a/prisma/models/client.prisma b/prisma/models/client.prisma index e01787a..414fcbb 100644 --- a/prisma/models/client.prisma +++ b/prisma/models/client.prisma @@ -1,7 +1,8 @@ enum ClientCapabilities { - PeerAPI @map("peerAPI") // other clients can use the HTTP API to P2P with this client - UserStatus @map("userStatus") // this client can report this user's status (playing, online, etc etc) - CloudSaves @map("cloudSaves") // ability to save to save slots + PeerAPI @map("peerAPI") // other clients can use the HTTP API to P2P with this client + UserStatus @map("userStatus") // this client can report this user's status (playing, online, etc etc) + CloudSaves @map("cloudSaves") // ability to save to save slots + TrackPlaytime @map("trackPlaytime") // ability to track user playtime } // References a device @@ -18,4 +19,4 @@ model Client { lastAccessedSaves SaveSlot[] tokens APIToken[] -} \ No newline at end of file +} diff --git a/prisma/models/content.prisma b/prisma/models/content.prisma index 4781ee3..ab9fff3 100644 --- a/prisma/models/content.prisma +++ b/prisma/models/content.prisma @@ -36,6 +36,7 @@ model Game { saves SaveSlot[] screenshots Screenshot[] tags Tag[] + playtime Playtime[] developers Company[] @relation(name: "developers") publishers Company[] @relation(name: "publishers") @@ -124,6 +125,21 @@ model Screenshot { @@index([userId]) } +model Playtime { + gameId String + game Game @relation(fields: [gameId], references: [id], onDelete: Cascade) + userId String + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + seconds Int // seconds user has spent playing the game + + updatedAt DateTime @updatedAt @db.Timestamptz(6) + createdAt DateTime @default(now()) @db.Timestamptz(6) + + @@id([gameId, userId]) + @@index([userId]) +} + model Company { id String @id @default(uuid()) diff --git a/prisma/models/user.prisma b/prisma/models/user.prisma index d880109..30cecb8 100644 --- a/prisma/models/user.prisma +++ b/prisma/models/user.prisma @@ -19,6 +19,7 @@ model User { saves SaveSlot[] screenshots Screenshot[] + playtime Playtime[] } model Notification { diff --git a/server/internal/clients/capabilities.ts b/server/internal/clients/capabilities.ts index 5e30e00..639e835 100644 --- a/server/internal/clients/capabilities.ts +++ b/server/internal/clients/capabilities.ts @@ -10,6 +10,7 @@ export enum InternalClientCapability { PeerAPI = "peerAPI", UserStatus = "userStatus", CloudSaves = "cloudSaves", + TrackPlaytime = "trackPlaytime", } export const validCapabilities = Object.values(InternalClientCapability); @@ -79,6 +80,7 @@ class CapabilityManager { [InternalClientCapability.PeerAPI]: async () => true, [InternalClientCapability.UserStatus]: async () => true, // No requirements for user status [InternalClientCapability.CloudSaves]: async () => true, // No requirements for cloud saves + [InternalClientCapability.TrackPlaytime]: async () => true, }; async validateCapabilityConfiguration( @@ -160,6 +162,28 @@ class CapabilityManager { }, }); }, + [InternalClientCapability.TrackPlaytime]: async function () { + const currentClient = await prisma.client.findUnique({ + where: { id: clientId }, + select: { + capabilities: true, + }, + }); + if (!currentClient) throw new Error("Invalid client ID"); + if ( + currentClient.capabilities.includes(ClientCapabilities.TrackPlaytime) + ) + return; + + await prisma.client.update({ + where: { id: clientId }, + data: { + capabilities: { + push: ClientCapabilities.TrackPlaytime, + }, + }, + }); + }, }; await upsertFunctions[capability](); } diff --git a/server/internal/playtime/index.ts b/server/internal/playtime/index.ts new file mode 100644 index 0000000..3ae1497 --- /dev/null +++ b/server/internal/playtime/index.ts @@ -0,0 +1,50 @@ +import prisma from "../db/database"; + +class PlaytimeManager { + /** + * Get a user's playtime on a game + * @param gameId + * @param userId + * @returns + */ + async get(gameId: string, userId: string) { + return await prisma.playtime.findUnique({ + where: { + gameId_userId: { + gameId, + userId, + }, + }, + }); + } + + /** + * Add time to a user's playtime + * @param gameId + * @param userId + * @param seconds seconds played + */ + async add(gameId: string, userId: string, seconds: number) { + await prisma.playtime.upsert({ + where: { + gameId_userId: { + gameId, + userId, + }, + }, + create: { + gameId, + userId, + seconds, + }, + update: { + seconds: { + increment: seconds, + }, + }, + }); + } +} + +export const playtimeManager = new PlaytimeManager(); +export default playtimeManager;