2024-11-16 16:24:23 +11:00
|
|
|
enum MetadataSource {
|
2025-07-06 13:10:57 +10:00
|
|
|
Manual
|
|
|
|
|
GiantBomb
|
2025-09-21 02:43:35 +02:00
|
|
|
Steam
|
2025-07-06 13:10:57 +10:00
|
|
|
PCGamingWiki
|
|
|
|
|
IGDB
|
|
|
|
|
Metacritic
|
|
|
|
|
OpenCritic
|
2024-11-16 16:24:23 +11:00
|
|
|
}
|
|
|
|
|
|
2026-01-23 05:04:38 +00:00
|
|
|
enum GameType {
|
|
|
|
|
Game
|
2026-02-06 23:12:03 +11:00
|
|
|
Emulator
|
|
|
|
|
Dependency
|
2026-01-23 05:04:38 +00:00
|
|
|
}
|
|
|
|
|
|
2024-11-16 16:24:23 +11:00
|
|
|
model Game {
|
2025-07-06 13:10:57 +10:00
|
|
|
id String @id @default(uuid())
|
|
|
|
|
|
|
|
|
|
metadataSource MetadataSource
|
|
|
|
|
metadataId String
|
|
|
|
|
created DateTime @default(now())
|
|
|
|
|
|
2026-01-23 05:04:38 +00:00
|
|
|
type GameType @default(Game)
|
|
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
// Any field prefixed with m is filled in from metadata
|
|
|
|
|
// Acts as a cache so we can search and filter it
|
2026-01-13 15:32:39 +11:00
|
|
|
mName String // Name of game
|
|
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
mShortDescription String // Short description
|
|
|
|
|
mDescription String // Supports markdown
|
|
|
|
|
mReleased DateTime // When the game was released
|
|
|
|
|
|
2026-07-25 18:42:16 -07:00
|
|
|
ratings GameRating[]
|
|
|
|
|
ageRatings GameAgeRating[]
|
2025-07-06 13:10:57 +10:00
|
|
|
|
2025-07-30 13:40:49 +10:00
|
|
|
featured Boolean @default(false)
|
|
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
mIconObjectId String // linked to objects in s3
|
|
|
|
|
mBannerObjectId String // linked to objects in s3
|
|
|
|
|
mCoverObjectId String
|
2026-02-25 02:17:33 +11:00
|
|
|
mImageCarouselObjectIds String[] @default([]) // linked to below array
|
2025-07-06 13:10:57 +10:00
|
|
|
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
|
2026-01-13 15:32:39 +11:00
|
|
|
libraryId String
|
|
|
|
|
library Library @relation(fields: [libraryId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
2025-07-06 13:10:57 +10:00
|
|
|
libraryPath String
|
|
|
|
|
|
|
|
|
|
collections CollectionEntry[]
|
|
|
|
|
saves SaveSlot[]
|
|
|
|
|
screenshots Screenshot[]
|
2025-07-30 13:40:49 +10:00
|
|
|
tags GameTag[]
|
2025-07-06 13:10:57 +10:00
|
|
|
playtime Playtime[]
|
|
|
|
|
|
2026-01-23 05:04:38 +00:00
|
|
|
developers Company[] @relation(name: "developers")
|
|
|
|
|
publishers Company[] @relation(name: "publishers")
|
|
|
|
|
unimportedGameVersions UnimportedGameVersion[]
|
2025-07-06 13:10:57 +10:00
|
|
|
|
community: M5 -- mods catalog and achievement definitions/display
Adds the M5 slice of the community layer: Mod/ModVersion/ModDependency/
ModInstallation/ModInstalledFile/AchievementDefinition (migration
m5_mods_achievements). Gods/Users are referenced as plain soft columns
(gameId/uploaderId/identityId), not Prisma relations, following the
concurrent-edit-avoidance pattern devices.prisma established -- content.prisma
and user.prisma only grow a comment each.
Mods: syncs from script-library/scripts/archive-mod.py's _index/catalog.json
(read-only bind mount, homelab-compose/drop-stack/docker-compose.yml) into
Mod/ModVersion/ModDependency, resolves dependency refs into real Mod rows
where archived, and exposes a topologically-ordered install-plan resolver
with cycle/conflict/unresolved-dependency detection
(GET /mods/:id/versions/:v/plan). The install manifest
(ModInstallation/ModInstalledFile) records created/overwrote/skipped per
file so an uninstall plan is exact, computed server-side from what was
recorded, applied by the caller. Downloads stream directly off the mount
(GET /mods/:id/versions/:v/download) since cdn.quasarke.net has no
file_server wired to /mnt/local24/game-mods yet -- documented as a known gap
in server/internal/community/README.md, deliberately not fixed by editing
the live Caddyfile. A manual add form covers the DESIGN.md open-question #4
fallback (no upload write path exists).
Achievements: imports gbe_fork's archived generate_emu_config output
(/mnt/local24/game-tools/goldberg/schemas, also read-only mounted) via
goldbergAdapter.ts, copying icons into Drop's own object store
(objectHandler.createFromSource, same mechanism as Steam cover art) so
nothing is hotlinked. Display joins AchievementDefinition against M2's
already-landed AchievementUnlock by (steamAppId, apiName), exactly the join
key M2 designed it around -- no stub needed, M2's table was already in the
tree. Verified against real fixtures: appid 245170 (Skullgirls) importing
45/45 definitions with 90 icons, and 274190 (Broforce) importing 17/17.
Pages: /community/mods, /community/mods/:slug, /community/mods/new,
/community/achievements, /community/games/:gameId (achievement grid +
per-title mods, completion %). Small additive nav links on /community's
index rather than touching the actively-changing shared game-detail page.
2026-08-03 01:16:25 -04:00
|
|
|
// Note for M5 (mods/achievements) and other milestones: do NOT add a
|
|
|
|
|
// Prisma relation array here for a model owned by another milestone file.
|
|
|
|
|
// Prisma relations must be declared on both sides, which forces an edit to
|
|
|
|
|
// this shared file for every milestone in parallel -- exactly the
|
|
|
|
|
// concurrent-edit collision this fork's milestones were warned about (see
|
|
|
|
|
// devices.prisma's header). Mod/AchievementDefinition/etc reference Game.id
|
|
|
|
|
// as a plain, unenforced-by-FK column instead, resolved at the application
|
|
|
|
|
// layer -- same pattern devices.prisma already established for Device/
|
|
|
|
|
// PlaySession/AchievementUnlock.
|
|
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
@@unique([metadataSource, metadataId], name: "metadataKey")
|
|
|
|
|
@@unique([libraryId, libraryPath], name: "libraryKey")
|
2026-01-13 15:32:39 +11:00
|
|
|
@@index([mName(ops: raw("gist_trgm_ops(siglen=32)"))], type: Gist)
|
2024-11-16 16:24:23 +11:00
|
|
|
}
|
|
|
|
|
|
2025-07-30 13:40:49 +10:00
|
|
|
model GameTag {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
name String @unique
|
|
|
|
|
|
2026-01-13 15:32:39 +11:00
|
|
|
games Game[]
|
2025-07-30 13:40:49 +10:00
|
|
|
|
|
|
|
|
@@index([name(ops: raw("gist_trgm_ops(siglen=32)"))], type: Gist)
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-25 18:42:16 -07:00
|
|
|
enum AgeRatingOrganization {
|
|
|
|
|
ESRB
|
|
|
|
|
PEGI
|
|
|
|
|
CERO
|
|
|
|
|
USK
|
|
|
|
|
GRAC
|
|
|
|
|
ClassInd
|
|
|
|
|
ACB
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model GameAgeRating {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
|
|
|
|
|
organization AgeRatingOrganization
|
|
|
|
|
rating String // e.g. "E", "T", "M", "18", "CERO_B", comes from server enums
|
|
|
|
|
ratingCoverUrl String? // Badge image URL from IGDB, optional
|
|
|
|
|
|
|
|
|
|
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
|
|
|
|
|
gameId String
|
|
|
|
|
|
|
|
|
|
@@unique([gameId, organization], name: "gameOrganizationKey")
|
|
|
|
|
}
|
|
|
|
|
|
2025-05-14 21:40:25 -04:00
|
|
|
model GameRating {
|
2025-07-06 13:10:57 +10:00
|
|
|
id String @id @default(uuid())
|
2025-05-14 21:40:25 -04:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
metadataSource MetadataSource
|
|
|
|
|
metadataId String
|
|
|
|
|
created DateTime @default(now())
|
2025-05-14 21:40:25 -04:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
mReviewCount Int
|
|
|
|
|
mReviewRating Float // 0 to 1
|
2025-05-14 21:40:25 -04:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
mReviewHref String?
|
2025-05-14 21:40:25 -04:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
|
|
|
|
|
gameId String
|
2025-05-14 21:40:25 -04:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
@@unique([metadataSource, metadataId], name: "metadataKey")
|
2025-05-14 21:40:25 -04:00
|
|
|
}
|
|
|
|
|
|
2026-01-23 05:04:38 +00:00
|
|
|
model UnimportedGameVersion {
|
|
|
|
|
id String @id @default(uuid())
|
|
|
|
|
gameId String
|
|
|
|
|
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
|
|
|
|
|
versionName String
|
|
|
|
|
|
|
|
|
|
manifest Json
|
|
|
|
|
fileList String[]
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-16 16:24:23 +11:00
|
|
|
// A particular set of files that relate to the version
|
|
|
|
|
model GameVersion {
|
2026-01-13 15:32:39 +11:00
|
|
|
gameId String
|
|
|
|
|
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
|
2026-01-23 05:04:38 +00:00
|
|
|
versionId String @id @default(uuid())
|
2024-11-24 16:12:19 +11:00
|
|
|
|
2026-01-13 15:32:39 +11:00
|
|
|
displayName String?
|
2026-01-23 05:04:38 +00:00
|
|
|
versionPath String?
|
2024-11-16 16:24:23 +11:00
|
|
|
|
2026-01-13 15:32:39 +11:00
|
|
|
created DateTime @default(now())
|
2024-12-31 00:04:50 +11:00
|
|
|
|
2026-01-13 15:32:39 +11:00
|
|
|
launches LaunchConfiguration[]
|
|
|
|
|
setups SetupConfiguration[]
|
2024-12-31 00:04:50 +11:00
|
|
|
|
2026-01-13 15:32:39 +11:00
|
|
|
onlySetup Boolean @default(false)
|
2024-12-31 00:04:50 +11:00
|
|
|
|
2026-01-23 05:04:38 +00:00
|
|
|
dropletManifest Json // Results from droplet
|
|
|
|
|
fileList String[] // List of all files, for delta updates
|
|
|
|
|
negativeFileList String[] // List of files to remove, for delta updates
|
2024-11-16 16:24:23 +11:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
versionIndex Int
|
|
|
|
|
delta Boolean @default(false)
|
2024-11-16 16:24:23 +11:00
|
|
|
|
2026-01-23 05:04:38 +00:00
|
|
|
requiredContent GameVersion[] @relation(name: "requiredContent")
|
|
|
|
|
requiringContent GameVersion[] @relation(name: "requiredContent")
|
2026-01-13 15:32:39 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model SetupConfiguration {
|
|
|
|
|
setupId String @id @default(uuid())
|
|
|
|
|
|
|
|
|
|
command String
|
|
|
|
|
|
|
|
|
|
platform Platform
|
|
|
|
|
|
|
|
|
|
versionId String
|
2026-01-23 05:04:38 +00:00
|
|
|
gameVersion GameVersion @relation(fields: [versionId], references: [versionId], onDelete: Cascade, onUpdate: Cascade)
|
2026-01-13 15:32:39 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model LaunchConfiguration {
|
|
|
|
|
launchId String @id @default(uuid())
|
|
|
|
|
|
|
|
|
|
name String
|
|
|
|
|
|
|
|
|
|
command String
|
|
|
|
|
|
|
|
|
|
platform Platform
|
|
|
|
|
|
|
|
|
|
// For emulation targets
|
2026-02-06 23:12:03 +11:00
|
|
|
emulatorId String?
|
|
|
|
|
emulator LaunchConfiguration? @relation(fields: [emulatorId], references: [launchId], name: "emulator")
|
|
|
|
|
emulatorSuggestions String[]
|
2026-01-13 15:32:39 +11:00
|
|
|
|
2026-02-06 23:12:03 +11:00
|
|
|
umuIdOverride String?
|
|
|
|
|
umuStoreOverride String?
|
2026-01-13 15:32:39 +11:00
|
|
|
|
|
|
|
|
versionId String
|
2026-01-23 05:04:38 +00:00
|
|
|
gameVersion GameVersion @relation(fields: [versionId], references: [versionId], onDelete: Cascade, onUpdate: Cascade)
|
2026-01-13 15:32:39 +11:00
|
|
|
|
2026-02-06 23:12:03 +11:00
|
|
|
emulations LaunchConfiguration[] @relation("emulator")
|
2024-11-16 16:24:23 +11:00
|
|
|
}
|
|
|
|
|
|
2025-04-01 21:08:57 +11:00
|
|
|
// A save slot for a game
|
|
|
|
|
model SaveSlot {
|
2025-07-06 13:10:57 +10:00
|
|
|
gameId String
|
|
|
|
|
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
|
|
|
|
|
userId String
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
|
index Int
|
2025-04-01 21:08:57 +11:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
createdAt DateTime @default(now())
|
|
|
|
|
playtime Float @default(0) // hours
|
2025-04-01 21:08:57 +11:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
lastUsedClientId String?
|
|
|
|
|
lastUsedClient Client? @relation(fields: [lastUsedClientId], references: [id])
|
2025-04-01 21:08:57 +11:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
historyObjectIds String[] // list of objects
|
|
|
|
|
historyChecksums String[] // list of hashes
|
2025-04-01 21:08:57 +11:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
@@id([gameId, userId, index], name: "id")
|
2025-04-01 21:08:57 +11:00
|
|
|
}
|
|
|
|
|
|
2025-05-08 11:38:09 -04:00
|
|
|
model Screenshot {
|
2025-07-06 13:10:57 +10:00
|
|
|
id String @id @default(uuid())
|
2025-05-08 11:38:09 -04:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
gameId String
|
|
|
|
|
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
|
|
|
|
|
userId String
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
2025-05-08 11:38:09 -04:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
objectId String
|
|
|
|
|
private Boolean // if other users can see
|
2025-05-08 11:38:09 -04:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
createdAt DateTime @default(now()) @db.Timestamptz(0)
|
2025-05-08 11:38:09 -04:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
@@index([gameId, userId])
|
|
|
|
|
@@index([userId])
|
2025-05-08 11:38:09 -04:00
|
|
|
}
|
|
|
|
|
|
2025-05-27 12:30:20 -04:00
|
|
|
model Playtime {
|
2025-07-06 13:10:57 +10:00
|
|
|
gameId String
|
|
|
|
|
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
|
|
|
|
|
userId String
|
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
2025-05-27 12:30:20 -04:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
seconds Int // seconds user has spent playing the game
|
2025-05-27 12:30:20 -04:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
|
|
|
|
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
2025-05-27 12:30:20 -04:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
@@id([gameId, userId])
|
|
|
|
|
@@index([userId])
|
2025-05-27 12:30:20 -04:00
|
|
|
}
|
|
|
|
|
|
2025-05-08 20:35:15 -04:00
|
|
|
model Company {
|
2025-07-06 13:10:57 +10:00
|
|
|
id String @id @default(uuid())
|
2025-05-08 20:35:15 -04:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
metadataSource MetadataSource
|
|
|
|
|
metadataId String
|
|
|
|
|
metadataOriginalQuery String
|
2025-05-08 20:35:15 -04:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
mName String
|
|
|
|
|
mShortDescription String
|
|
|
|
|
mDescription String
|
|
|
|
|
mLogoObjectId String
|
|
|
|
|
mBannerObjectId String
|
|
|
|
|
mWebsite String
|
2025-05-08 20:35:15 -04:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
developed Game[] @relation(name: "developers")
|
|
|
|
|
published Game[] @relation(name: "publishers")
|
2025-05-08 20:35:15 -04:00
|
|
|
|
2025-07-06 13:10:57 +10:00
|
|
|
@@unique([metadataSource, metadataId], name: "metadataKey")
|
2025-05-08 20:35:15 -04:00
|
|
|
}
|
|
|
|
|
|
2025-04-12 15:54:26 -04:00
|
|
|
model ObjectHash {
|
2025-07-06 13:10:57 +10:00
|
|
|
id String @id
|
|
|
|
|
hash String
|
2025-04-12 15:54:26 -04:00
|
|
|
}
|