feat: basic playtime backend

This commit is contained in:
Huskydog9988
2025-05-27 12:30:20 -04:00
parent 21eec081ee
commit 4b009f1aca
5 changed files with 96 additions and 4 deletions

View File

@ -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

View File

@ -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())

View File

@ -19,6 +19,7 @@ model User {
saves SaveSlot[]
screenshots Screenshot[]
playtime Playtime[]
}
model Notification {

View File

@ -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]();
}

View File

@ -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;