From 90b02b7f8e86d494d10e398158aa611136a0d8b5 Mon Sep 17 00:00:00 2001 From: DecDuck Date: Sat, 9 Aug 2025 23:14:09 +1000 Subject: [PATCH] partial: admin import annotations --- server/api/v1/admin/import/game/index.get.ts | 3 + server/api/v1/admin/import/game/index.post.ts | 4 + server/api/v1/admin/import/game/search.get.ts | 49 ++++--- .../api/v1/admin/import/version/index.get.ts | 19 ++- .../api/v1/admin/import/version/index.post.ts | 129 ++++++++++-------- .../v1/admin/import/version/preload.get.ts | 21 ++- 6 files changed, 136 insertions(+), 89 deletions(-) diff --git a/server/api/v1/admin/import/game/index.get.ts b/server/api/v1/admin/import/game/index.get.ts index 8ef65fb..d10b01e 100644 --- a/server/api/v1/admin/import/game/index.get.ts +++ b/server/api/v1/admin/import/game/index.get.ts @@ -1,6 +1,9 @@ import aclManager from "~/server/internal/acls"; import libraryManager from "~/server/internal/library"; +/** + * Fetch all games that are available for import. + */ export default defineEventHandler(async (h3) => { const allowed = await aclManager.allowSystemACL(h3, ["import:game:read"]); if (!allowed) throw createError({ statusCode: 403 }); diff --git a/server/api/v1/admin/import/game/index.post.ts b/server/api/v1/admin/import/game/index.post.ts index e3e3247..2b96c88 100644 --- a/server/api/v1/admin/import/game/index.post.ts +++ b/server/api/v1/admin/import/game/index.post.ts @@ -14,6 +14,10 @@ const ImportGameBody = type({ }, }).configure(throwingArktype); +/** + * Import a game as a background task. + * @response Task IDs can be used with the websocket endpoint /api/v1/task + */ export default defineEventHandler<{ body: typeof ImportGameBody.infer }>( async (h3) => { const allowed = await aclManager.allowSystemACL(h3, ["import:game:new"]); diff --git a/server/api/v1/admin/import/game/search.get.ts b/server/api/v1/admin/import/game/search.get.ts index a68f45d..49fa458 100644 --- a/server/api/v1/admin/import/game/search.get.ts +++ b/server/api/v1/admin/import/game/search.get.ts @@ -1,22 +1,35 @@ +import { ArkErrors, type } from "arktype"; import aclManager from "~/server/internal/acls"; import metadataHandler from "~/server/internal/metadata"; -export default defineEventHandler(async (h3) => { - const allowed = await aclManager.allowSystemACL(h3, ["import:game:read"]); - if (!allowed) throw createError({ statusCode: 403 }); - - const query = getQuery(h3); - const search = query.q?.toString(); - if (!search) - throw createError({ statusCode: 400, statusMessage: "Invalid search" }); - - const results = await metadataHandler.search(search); - - if (results.length == 0) - throw createError({ - statusCode: 404, - statusMessage: "No metadata provider returned search results.", - }); - - return results; +const SearchGame = type({ + q: "string", }); + +/** + * Search metadata providers for a query. Results can be used to import a game with metadata. + */ +export default defineEventHandler<{ query: typeof SearchGame.infer }>( + async (h3) => { + const allowed = await aclManager.allowSystemACL(h3, ["import:game:read"]); + if (!allowed) throw createError({ statusCode: 403 }); + + const query = getQuery(h3); + const search = SearchGame(query); + if (search instanceof ArkErrors) + throw createError({ + statusCode: 400, + statusMessage: "Invalid search: " + search.summary, + }); + + const results = await metadataHandler.search(search.q); + + if (results.length == 0) + throw createError({ + statusCode: 404, + statusMessage: "No metadata provider returned search results.", + }); + + return results; + }, +); diff --git a/server/api/v1/admin/import/version/index.get.ts b/server/api/v1/admin/import/version/index.get.ts index 893d295..79c2ff9 100644 --- a/server/api/v1/admin/import/version/index.get.ts +++ b/server/api/v1/admin/import/version/index.get.ts @@ -1,19 +1,28 @@ +import { ArkErrors, type } from "arktype"; 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 Query = type({ + id: "string", +}); + +/** + * Fetch all versions available for import for a game (`id` in query params). + */ +export default defineEventHandler<{ query: typeof Query.infer }>(async (h3) => { const allowed = await aclManager.allowSystemACL(h3, ["import:version:read"]); if (!allowed) throw createError({ statusCode: 403 }); - const query = await getQuery(h3); - const gameId = query.id?.toString(); - if (!gameId) + const query = Query(await getQuery(h3)); + if (query instanceof ArkErrors) throw createError({ statusCode: 400, - statusMessage: "Missing id in request params", + statusMessage: "Invalid query params: " + query.summary, }); + const gameId = query.id; + const game = await prisma.game.findUnique({ where: { id: gameId }, select: { libraryId: true, libraryPath: true }, diff --git a/server/api/v1/admin/import/version/index.post.ts b/server/api/v1/admin/import/version/index.post.ts index ab9ad1c..1c31748 100644 --- a/server/api/v1/admin/import/version/index.post.ts +++ b/server/api/v1/admin/import/version/index.post.ts @@ -19,71 +19,80 @@ const ImportVersion = type({ umuId: "string = ''", }).configure(throwingArktype); -export default defineEventHandler(async (h3) => { - const allowed = await aclManager.allowSystemACL(h3, ["import:version:new"]); - if (!allowed) throw createError({ statusCode: 403 }); +/** + * Import a version for a game. + */ +export default defineEventHandler<{ body: typeof ImportVersion.infer }>( + async (h3) => { + const allowed = await aclManager.allowSystemACL(h3, ["import:version:new"]); + if (!allowed) throw createError({ statusCode: 403 }); - const { - id, - version, - platform, - launch, - launchArgs, - setup, - setupArgs, - onlySetup, - delta, - umuId, - } = await readDropValidatedBody(h3, ImportVersion); + const { + id, + version, + platform, + launch, + launchArgs, + setup, + setupArgs, + onlySetup, + delta, + umuId, + } = await readDropValidatedBody(h3, ImportVersion); - const platformParsed = parsePlatform(platform); - if (!platformParsed) - throw createError({ statusCode: 400, statusMessage: "Invalid platform." }); + const platformParsed = parsePlatform(platform); + if (!platformParsed) + throw createError({ + statusCode: 400, + statusMessage: "Invalid platform.", + }); - if (delta) { - const validOverlayVersions = await prisma.gameVersion.count({ - where: { gameId: id, platform: platformParsed, delta: false }, + if (delta) { + const validOverlayVersions = await prisma.gameVersion.count({ + where: { gameId: id, platform: platformParsed, delta: false }, + }); + if (validOverlayVersions == 0) + throw createError({ + statusCode: 400, + statusMessage: + "Update mode requires a pre-existing version for this platform.", + }); + } + + if (onlySetup) { + if (!setup) + throw createError({ + statusCode: 400, + statusMessage: 'Setup required in "setup mode".', + }); + } else { + if (!delta && !launch) + throw createError({ + statusCode: 400, + statusMessage: + "Launch executable is required for non-update versions", + }); + } + + // startup & delta require more complex checking logic + const taskId = await libraryManager.importVersion(id, version, { + platform, + onlySetup, + + launch, + launchArgs, + setup, + setupArgs, + + umuId, + delta, }); - if (validOverlayVersions == 0) + if (!taskId) throw createError({ statusCode: 400, - statusMessage: - "Update mode requires a pre-existing version for this platform.", + statusMessage: "Invalid options for import", }); - } - if (onlySetup) { - if (!setup) - throw createError({ - statusCode: 400, - statusMessage: 'Setup required in "setup mode".', - }); - } else { - if (!delta && !launch) - throw createError({ - statusCode: 400, - statusMessage: "Launch executable is required for non-update versions", - }); - } - - // startup & delta require more complex checking logic - const taskId = await libraryManager.importVersion(id, version, { - platform, - onlySetup, - - launch, - launchArgs, - setup, - setupArgs, - - umuId, - delta, - }); - if (!taskId) - throw createError({ - statusCode: 400, - statusMessage: "Invalid options for import", - }); - - return { taskId: taskId }; -}); + return { taskId: taskId }; + }, +); diff --git a/server/api/v1/admin/import/version/preload.get.ts b/server/api/v1/admin/import/version/preload.get.ts index d83b936..5020563 100644 --- a/server/api/v1/admin/import/version/preload.get.ts +++ b/server/api/v1/admin/import/version/preload.get.ts @@ -1,18 +1,27 @@ +import { ArkErrors, type } from "arktype"; import aclManager from "~/server/internal/acls"; import libraryManager from "~/server/internal/library"; -export default defineEventHandler(async (h3) => { +const Query = type({ + id: "string", + version: "string", +}); + +/** + * Fetch recommendations for version import. + */ +export default defineEventHandler<{ query: typeof Query.infer }>(async (h3) => { const allowed = await aclManager.allowSystemACL(h3, ["import:version:read"]); if (!allowed) throw createError({ statusCode: 403 }); - const query = await getQuery(h3); - const gameId = query.id?.toString(); - const versionName = query.version?.toString(); - if (!gameId || !versionName) + const query = Query(await getQuery(h3)); + if (query instanceof ArkErrors) throw createError({ statusCode: 400, - statusMessage: "Missing id or version in request params", + statusMessage: "Invalid query: " + query.summary, }); + const gameId = query.id; + const versionName = query.version; const preload = await libraryManager.fetchUnimportedVersionInformation( gameId,