Paginated admin library & upgrade manifests (#351)
* feat: new page layout + endpoint * feat: non-parallel mass import * feat: paginated admin library * feat: lint and performance improvement * feat: library filter util * feat: link frontend features to backend * fix: lint * fix: small fixes * feat: bump torrential * fix: lint
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import { libraryManager } from "~/server/internal/library";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["import:version:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const games = await prisma.game.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
mName: true,
|
||||
mIconObjectId: true,
|
||||
versions: {
|
||||
select: {
|
||||
versionPath: true,
|
||||
},
|
||||
},
|
||||
unimportedGameVersions: {
|
||||
select: {
|
||||
id: true,
|
||||
versionName: true,
|
||||
},
|
||||
},
|
||||
libraryId: true,
|
||||
libraryPath: true,
|
||||
},
|
||||
});
|
||||
|
||||
const unimportedVersions = await Promise.all(
|
||||
games.map(async (v) => ({
|
||||
id: v.id,
|
||||
name: v.mName,
|
||||
icon: v.mIconObjectId,
|
||||
versions: await libraryManager.fetchUnimportedGameVersions(
|
||||
v.libraryId,
|
||||
v.libraryPath,
|
||||
{
|
||||
gameId: v.id,
|
||||
versions: v.versions
|
||||
.map((v) => v.versionPath)
|
||||
.filter((v) => v !== null),
|
||||
depotVersions: v.unimportedGameVersions,
|
||||
},
|
||||
),
|
||||
})),
|
||||
);
|
||||
|
||||
const onlyUnimported = unimportedVersions.filter(
|
||||
(v) => v.versions && v.versions.length > 0,
|
||||
);
|
||||
|
||||
return onlyUnimported;
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
import { type } from "arktype";
|
||||
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
|
||||
import { aclManager } from "~/server/internal/acls";
|
||||
import { libraryManager } from "~/server/internal/library";
|
||||
import { taskHandler, wrapTaskContext } from "~/server/internal/tasks";
|
||||
import type { Platform } from "~/prisma/client/client";
|
||||
|
||||
const MassImport = type({
|
||||
versions: type({
|
||||
id: "string",
|
||||
version: type({
|
||||
type: "'depot' | 'local'",
|
||||
identifier: "string",
|
||||
name: "string",
|
||||
}),
|
||||
displayName: "string?",
|
||||
setupMode: "boolean = false",
|
||||
}).array(),
|
||||
}).configure(throwingArktype);
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["import:version:new"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const body = await readDropValidatedBody(h3, MassImport);
|
||||
|
||||
const taskId = await taskHandler.create({
|
||||
key: "mass-import",
|
||||
taskGroup: "import:version",
|
||||
acls: ["system:import:version:read"],
|
||||
name: `Mass-importing for ${body.versions.length} versions`,
|
||||
async run({ progress, logger, addAction }) {
|
||||
for (
|
||||
let versionIndex = 0;
|
||||
versionIndex < body.versions.length;
|
||||
versionIndex++
|
||||
) {
|
||||
const version = body.versions[versionIndex];
|
||||
const preload = await libraryManager.fetchUnimportedVersionInformation(
|
||||
version.id,
|
||||
version.version,
|
||||
);
|
||||
if (!preload) {
|
||||
logger.warn(
|
||||
`failed to fetch preload information for: ${version.version.name} (${version.version.type})`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const chosenPreload = preload.at(0);
|
||||
if (!chosenPreload) {
|
||||
logger.warn(
|
||||
`failed to find preload information for: ${version.version.name} (${version.version.type}), there were no auto-discovered executables`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const launches: Array<{
|
||||
platform: Platform;
|
||||
launch: string;
|
||||
name: string;
|
||||
}> = [];
|
||||
const setups: Array<{ platform: Platform; launch: string }> = [];
|
||||
|
||||
if (version.setupMode) {
|
||||
setups.push({
|
||||
platform: chosenPreload.platform,
|
||||
launch: chosenPreload.filename,
|
||||
});
|
||||
} else {
|
||||
launches.push({
|
||||
platform: chosenPreload.platform,
|
||||
launch: chosenPreload.filename,
|
||||
name: "Play",
|
||||
});
|
||||
}
|
||||
|
||||
logger.info(`importing ${version.version.name}`);
|
||||
const min = versionIndex / body.versions.length;
|
||||
const max = (versionIndex + 1) / body.versions.length;
|
||||
|
||||
await libraryManager.importVersion(
|
||||
version.id,
|
||||
version.version,
|
||||
{
|
||||
id: version.id,
|
||||
version: version.version,
|
||||
launches,
|
||||
setups,
|
||||
onlySetup: version.setupMode,
|
||||
delta: false,
|
||||
requiredContent: [],
|
||||
},
|
||||
wrapTaskContext(
|
||||
{
|
||||
logger,
|
||||
progress,
|
||||
addAction,
|
||||
},
|
||||
{
|
||||
min: min * 100,
|
||||
max: max * 100,
|
||||
prefix: `${version.version.name}`,
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
logger.info(`finished import for ${version.version.name}`);
|
||||
|
||||
progress(max * 100);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return { taskId };
|
||||
});
|
||||
@@ -2,7 +2,6 @@ import { type } from "arktype";
|
||||
import { Platform } from "~/prisma/client/enums";
|
||||
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
|
||||
export const ImportVersion = type({
|
||||
@@ -42,45 +41,6 @@ export default defineEventHandler(async (h3) => {
|
||||
|
||||
const body = await readDropValidatedBody(h3, ImportVersion);
|
||||
|
||||
if (body.delta) {
|
||||
for (const platformObject of [...body.launches, ...body.setups].filter(
|
||||
(v, i, a) => a.findIndex((k) => k.platform === v.platform) == i,
|
||||
)) {
|
||||
const validOverlayVersions = await prisma.gameVersion.count({
|
||||
where: {
|
||||
gameId: body.id,
|
||||
delta: false,
|
||||
OR: [
|
||||
{ launches: { some: { platform: platformObject.platform } } },
|
||||
{
|
||||
setups: { some: { platform: platformObject.platform } },
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
if (validOverlayVersions == 0)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: `Update mode requires a pre-existing version for platform: ${platformObject.platform}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (body.onlySetup) {
|
||||
if (body.setups.length == 0)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Setup required in "setup mode".',
|
||||
});
|
||||
} else {
|
||||
if (body.launches.length == 0)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Launch executable is required.",
|
||||
});
|
||||
}
|
||||
|
||||
// startup & delta require more complex checking logic
|
||||
const taskId = await libraryManager.importVersion(
|
||||
body.id,
|
||||
body.version,
|
||||
|
||||
Reference in New Issue
Block a user