feat: database redist support

This commit is contained in:
DecDuck
2025-08-20 11:50:59 +10:00
parent 6d89b7e510
commit 6853383e86
8 changed files with 286 additions and 212 deletions

View File

@ -0,0 +1,35 @@
-- DropIndex
DROP INDEX "GameTag_name_idx";
-- CreateTable
CREATE TABLE "RedistVersion" (
"redistId" TEXT NOT NULL,
"versionName" TEXT NOT NULL,
"created" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"platform" "Platform" NOT NULL,
"dropletManifest" JSONB NOT NULL,
CONSTRAINT "RedistVersion_pkey" PRIMARY KEY ("redistId","versionName")
);
-- CreateTable
CREATE TABLE "Redist" (
"id" TEXT NOT NULL,
"created" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"name" TEXT NOT NULL,
"description" TEXT NOT NULL,
"iconObjectId" TEXT NOT NULL,
"libraryId" TEXT NOT NULL,
"libraryPath" TEXT NOT NULL,
CONSTRAINT "Redist_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "GameTag_name_idx" ON "GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
-- AddForeignKey
ALTER TABLE "RedistVersion" ADD CONSTRAINT "RedistVersion_redistId_fkey" FOREIGN KEY ("redistId") REFERENCES "Redist"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Redist" ADD CONSTRAINT "Redist_libraryId_fkey" FOREIGN KEY ("libraryId") REFERENCES "Library"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -0,0 +1,14 @@
/*
Warnings:
- A unique constraint covering the columns `[libraryId,libraryPath]` on the table `Redist` will be added. If there are existing duplicate values, this will fail.
*/
-- DropIndex
DROP INDEX "GameTag_name_idx";
-- CreateIndex
CREATE INDEX "GameTag_name_idx" ON "GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
-- CreateIndex
CREATE UNIQUE INDEX "Redist_libraryId_libraryPath_key" ON "Redist"("libraryId", "libraryPath");

View File

@ -28,5 +28,6 @@ model Library {
backend LibraryBackend
options Json
games Game[]
games Game[]
redists Redist[]
}

View File

@ -1,184 +1,96 @@
enum MetadataSource {
Manual
GiantBomb
PCGamingWiki
IGDB
Metacritic
OpenCritic
}
model Game {
id String @id @default(uuid())
metadataSource MetadataSource
metadataId String
created DateTime @default(now())
// Any field prefixed with m is filled in from metadata
// Acts as a cache so we can search and filter it
mName String // Name of game
mShortDescription String // Short description
mDescription String // Supports markdown
mReleased DateTime // When the game was released
ratings GameRating[]
featured Boolean @default(false)
mIconObjectId String // linked to objects in s3
mBannerObjectId String // linked to objects in s3
mCoverObjectId String
mImageCarouselObjectIds String[] // linked to below array
mImageLibraryObjectIds String[] // linked to objects in s3
versions GameVersion[]
// These fields will not be optional in the next version
// Any game without a library ID will be assigned one at startup, based on the defaults
libraryId String?
library Library? @relation(fields: [libraryId], references: [id], onDelete: Cascade, onUpdate: Cascade)
libraryPath String
collections CollectionEntry[]
saves SaveSlot[]
screenshots Screenshot[]
tags GameTag[]
playtime Playtime[]
developers Company[] @relation(name: "developers")
publishers Company[] @relation(name: "publishers")
@@unique([metadataSource, metadataId], name: "metadataKey")
@@unique([libraryId, libraryPath], name: "libraryKey")
}
model GameTag {
id String @id @default(uuid())
name String @unique
games Game[]
@@index([name(ops: raw("gist_trgm_ops(siglen=32)"))], type: Gist)
}
model GameRating {
id String @id @default(uuid())
metadataSource MetadataSource
metadataId String
created DateTime @default(now())
mReviewCount Int
mReviewRating Float // 0 to 1
mReviewHref String?
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
gameId String
@@unique([metadataSource, metadataId], name: "metadataKey")
}
// A particular set of files that relate to the version
model GameVersion {
gameId String
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
versionName String // Sub directory for the game files
gameId String
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
versionName String // Sub directory for the game files
created DateTime @default(now())
created DateTime @default(now())
platform Platform
platform Platform
launchCommand String @default("") // Command to run to start. Platform-specific. Windows games on Linux will wrap this command in Proton/Wine
launchArgs String[]
setupCommand String @default("") // Command to setup game (dependencies and such)
setupArgs String[]
onlySetup Boolean @default(false)
launchCommand String @default("") // Command to run to start. Platform-specific. Windows games on Linux will wrap this command in Proton/Wine
launchArgs String[]
setupCommand String @default("") // Command to setup game (dependencies and such)
setupArgs String[]
onlySetup Boolean @default(false)
umuIdOverride String?
umuIdOverride String?
dropletManifest Json // Results from droplet
dropletManifest Json // Results from droplet
versionIndex Int
delta Boolean @default(false)
versionIndex Int
delta Boolean @default(false)
@@id([gameId, versionName])
@@id([gameId, versionName])
}
model RedistVersion {
redistId String
redist Redist @relation(fields: [redistId], references: [id], onDelete: Cascade)
versionName String
created DateTime @default(now())
platform Platform
dropletManifest Json // Results from droplet
@@id([redistId, versionName])
}
// A save slot for a game
model SaveSlot {
gameId String
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
index Int
gameId String
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
index Int
createdAt DateTime @default(now())
playtime Float @default(0) // hours
createdAt DateTime @default(now())
playtime Float @default(0) // hours
lastUsedClientId String?
lastUsedClient Client? @relation(fields: [lastUsedClientId], references: [id])
lastUsedClientId String?
lastUsedClient Client? @relation(fields: [lastUsedClientId], references: [id])
historyObjectIds String[] // list of objects
historyChecksums String[] // list of hashes
historyObjectIds String[] // list of objects
historyChecksums String[] // list of hashes
@@id([gameId, userId, index], name: "id")
@@id([gameId, userId, index], name: "id")
}
model Screenshot {
id String @id @default(uuid())
id String @id @default(uuid())
gameId String
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
gameId String
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
objectId String
private Boolean // if other users can see
objectId String
private Boolean // if other users can see
createdAt DateTime @default(now()) @db.Timestamptz(0)
createdAt DateTime @default(now()) @db.Timestamptz(0)
@@index([gameId, userId])
@@index([userId])
@@index([gameId, userId])
@@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)
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
seconds Int // seconds user has spent playing the game
updatedAt DateTime @updatedAt @db.Timestamptz(6)
createdAt DateTime @default(now()) @db.Timestamptz(6)
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())
metadataSource MetadataSource
metadataId String
metadataOriginalQuery String
mName String
mShortDescription String
mDescription String
mLogoObjectId String
mBannerObjectId String
mWebsite String
developed Game[] @relation(name: "developers")
published Game[] @relation(name: "publishers")
@@unique([metadataSource, metadataId], name: "metadataKey")
@@id([gameId, userId])
@@index([userId])
}
model ObjectHash {
id String @id
hash String
id String @id
hash String
}

View File

@ -0,0 +1,117 @@
enum MetadataSource {
Manual
GiantBomb
PCGamingWiki
IGDB
Metacritic
OpenCritic
}
model Game {
id String @id @default(uuid())
metadataSource MetadataSource
metadataId String
created DateTime @default(now())
// Any field prefixed with m is filled in from metadata
// Acts as a cache so we can search and filter it
mName String // Name of game
mShortDescription String // Short description
mDescription String // Supports markdown
mReleased DateTime // When the game was released
ratings GameRating[]
featured Boolean @default(false)
mIconObjectId String // linked to objects in s3
mBannerObjectId String // linked to objects in s3
mCoverObjectId String
mImageCarouselObjectIds String[] // linked to below array
mImageLibraryObjectIds String[] // linked to objects in s3
versions GameVersion[]
// These fields will not be optional in the next version
// Any game without a library ID will be assigned one at startup, based on the defaults
libraryId String?
library Library? @relation(fields: [libraryId], references: [id], onDelete: Cascade, onUpdate: Cascade)
libraryPath String
collections CollectionEntry[]
saves SaveSlot[]
screenshots Screenshot[]
tags GameTag[]
playtime Playtime[]
developers Company[] @relation(name: "developers")
publishers Company[] @relation(name: "publishers")
@@unique([metadataSource, metadataId], name: "metadataKey")
@@unique([libraryId, libraryPath], name: "libraryKey")
}
model Redist {
id String @id @default(uuid())
created DateTime @default(now())
name String
description String
iconObjectId String
libraryId String
library Library @relation(fields: [libraryId], references: [id], onDelete: Cascade, onUpdate: Cascade)
libraryPath String
versions RedistVersion[]
@@unique([libraryId, libraryPath], name: "libraryKey")
}
model GameTag {
id String @id @default(uuid())
name String @unique
games Game[]
@@index([name(ops: raw("gist_trgm_ops(siglen=32)"))], type: Gist)
}
model GameRating {
id String @id @default(uuid())
metadataSource MetadataSource
metadataId String
created DateTime @default(now())
mReviewCount Int
mReviewRating Float // 0 to 1
mReviewHref String?
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
gameId String
@@unique([metadataSource, metadataId], name: "metadataKey")
}
model Company {
id String @id @default(uuid())
metadataSource MetadataSource
metadataId String
metadataOriginalQuery String
mName String
mShortDescription String
mDescription String
mLogoObjectId String
mBannerObjectId String
mWebsite String
developed Game[] @relation(name: "developers")
published Game[] @relation(name: "publishers")
@@unique([metadataSource, metadataId], name: "metadataKey")
}