feat: rearchitecture of database schemas, migration reset, and #180

This commit is contained in:
DecDuck
2025-08-20 20:35:50 +10:00
parent 6853383e86
commit 322af0b4ca
125 changed files with 1384 additions and 1837 deletions

View File

@ -15,12 +15,13 @@ class DownloadContextManager {
}
> = new Map();
async createContext(game: string, versionName: string) {
const version = await prisma.gameVersion.findUnique({
async createContext(game: string, versionPath: string) {
const version = await prisma.version.findFirst({
where: {
gameId_versionName: {
gameId: game,
versionName,
gameId: game,
versionPath,
gameVersion: {
isNot: null,
},
},
include: {
@ -38,9 +39,9 @@ class DownloadContextManager {
this.contexts.set(contextId, {
timeout: new Date(),
manifest: JSON.parse(version.dropletManifest as string) as DropManifest,
versionName,
libraryId: version.game.libraryId!,
libraryPath: version.game.libraryPath,
versionName: versionPath,
libraryId: version.game!.libraryId!,
libraryPath: version.game!.libraryPath,
});
return contextId;

View File

@ -1,4 +1,3 @@
import type { GameVersionModel } from "~/prisma/client/models";
import prisma from "../db/database";
export type DropChunk = {
@ -14,11 +13,11 @@ export type DropManifest = {
export type DropManifestMetadata = {
manifest: DropManifest;
versionName: string;
versionId: string;
};
export type DropGeneratedManifest = DropManifest & {
[key: string]: { versionName: string };
[key: string]: { versionId: string };
};
class ManifestGenerator {
@ -31,7 +30,7 @@ class ManifestGenerator {
Object.entries(rootManifest.manifest).map(([key, value]) => {
return [
key,
Object.assign({}, value, { versionName: rootManifest.versionName }),
Object.assign({}, value, { versionId: rootManifest.versionId }),
];
}),
);
@ -44,7 +43,7 @@ class ManifestGenerator {
for (const [filename, chunk] of Object.entries(version.manifest)) {
if (manifest[filename]) continue;
manifest[filename] = Object.assign({}, chunk, {
versionName: version.versionName,
versionId: version.versionId,
});
}
}
@ -53,45 +52,50 @@ class ManifestGenerator {
}
// Local function because eventual caching
async generateManifest(gameId: string, versionName: string) {
const versions: GameVersionModel[] = [];
async generateManifest(versionId: string) {
const versions = [];
const baseVersion = await prisma.gameVersion.findUnique({
const baseVersion = await prisma.version.findUnique({
where: {
gameId_versionName: {
gameId: gameId,
versionName: versionName,
},
versionId,
},
include: {
gameVersion: true,
},
});
if (!baseVersion) return undefined;
versions.push(baseVersion);
// Collect other versions if this is a delta
if (baseVersion.delta) {
if (baseVersion.gameVersion?.delta) {
// Start at the same index minus one, and keep grabbing them
// until we run out or we hit something that isn't a delta
// eslint-disable-next-line no-constant-condition
for (let i = baseVersion.versionIndex - 1; true; i--) {
const currentVersion = await prisma.gameVersion.findFirst({
for (let i = baseVersion.gameVersion.versionIndex - 1; true; i--) {
const currentVersion = await prisma.version.findFirst({
where: {
gameId: gameId,
versionIndex: i,
gameId: baseVersion.gameId,
platform: baseVersion.platform,
gameVersion: {
versionIndex: i,
},
},
include: {
gameVersion: true,
},
});
if (!currentVersion) return undefined;
versions.push(currentVersion);
if (!currentVersion.delta) break;
if (!currentVersion.gameVersion?.delta) break;
}
}
const leastToMost = versions.reverse();
const metadata: DropManifestMetadata[] = leastToMost.map((e) => {
versions.reverse();
const metadata: DropManifestMetadata[] = versions.map((version) => {
return {
manifest: JSON.parse(
e.dropletManifest?.toString() ?? "{}",
version.dropletManifest?.toString() ?? "{}",
) as DropManifest,
versionName: e.versionName,
versionId: version.versionId,
};
});

View File

@ -14,6 +14,8 @@ import notificationSystem from "../notifications";
import { GameNotFoundError, type LibraryProvider } from "./provider";
import { logger } from "../logging";
import { createHash } from "node:crypto";
import type { ImportVersion } from "~/server/api/v1/admin/import/version/index.post";
import type { GameVersionLaunchCreateManyGameVersionInputEnvelope } from "~/prisma/client/models";
export function createGameImportTaskId(libraryId: string, libraryPath: string) {
return createHash("md5")
@ -247,21 +249,10 @@ class LibraryManager {
async importVersion(
gameId: string,
versionName: string,
metadata: {
platform: string;
onlySetup: boolean;
setup: string;
setupArgs: string;
launch: string;
launchArgs: string;
delta: boolean;
umuId: string;
},
versionPath: string,
metadata: typeof ImportVersion.infer,
) {
const taskId = createVersionImportTaskId(gameId, versionName);
const taskId = createVersionImportTaskId(gameId, versionPath);
const platform = parsePlatform(metadata.platform);
if (!platform) return undefined;
@ -278,14 +269,14 @@ class LibraryManager {
taskHandler.create({
id: taskId,
taskGroup: "import:game",
name: `Importing version ${versionName} for ${game.mName}`,
name: `Importing version "${metadata.name}" (${versionPath}) for ${game.mName}`,
acls: ["system:import:version:read"],
async run({ progress, logger }) {
// First, create the manifest via droplet.
// This takes up 90% of our progress, so we wrap it in a *0.9
const manifest = await library.generateDropletManifest(
game.libraryPath,
versionName,
versionPath,
(err, value) => {
if (err) throw err;
progress(value * 0.9);
@ -299,34 +290,52 @@ class LibraryManager {
logger.info("Created manifest successfully!");
const currentIndex = await prisma.gameVersion.count({
where: { gameId: gameId },
where: { version: { gameId: gameId } },
});
// Then, create the database object
await prisma.gameVersion.create({
await prisma.version.create({
data: {
gameId: gameId,
versionName: versionName,
gameId,
versionPath: versionPath,
versionName: metadata.name ?? versionPath,
dropletManifest: manifest,
versionIndex: currentIndex,
delta: metadata.delta,
umuIdOverride: metadata.umuId,
platform: platform,
onlySetup: metadata.onlySetup,
setupCommand: metadata.setup,
setupArgs: metadata.setupArgs.split(" "),
launchCommand: metadata.launch,
launchArgs: metadata.launchArgs.split(" "),
gameVersion: {
create: {
versionIndex: currentIndex,
delta: metadata.delta,
umuIdOverride: metadata.umuId,
onlySetup: metadata.onlySetup,
setup: metadata.setup,
setupArgs: metadata.setupArgs,
launches: {
createMany: {
data: metadata.launches.map(
(v) =>
({
name: v.name,
description: v.description,
launchCommand: v.launchCommand,
launchArgs: v.launchArgs,
}) satisfies GameVersionLaunchCreateManyGameVersionInputEnvelope["data"],
),
},
},
},
},
},
});
logger.info("Successfully created version!");
notificationSystem.systemPush({
nonce: `version-create-${gameId}-${versionName}`,
title: `'${game.mName}' ('${versionName}') finished importing.`,
description: `Drop finished importing version ${versionName} for ${game.mName}.`,
nonce: `version-create-${gameId}-${versionPath}`,
title: `'${game.mName}' ('${versionPath}') finished importing.`,
description: `Drop finished importing version ${versionPath} for ${game.mName}.`,
actions: [`View|/admin/library/${gameId}`],
acls: ["system:import:version:read"],
});