feat: annotated client routes

This commit is contained in:
DecDuck
2025-08-10 15:51:10 +10:00
parent 824b4e708b
commit 7c234067a5
31 changed files with 479 additions and 389 deletions

View File

@ -1,3 +1,4 @@
import { ArkErrors, type } from "arktype";
import cacheHandler from "~/server/internal/cache";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import prisma from "~/server/internal/db/database";
@ -10,74 +11,93 @@ const gameLookupCache = cacheHandler.createCache<{
libraryPath: string;
}>("downloadGameLookupCache");
export default defineClientEventHandler(async (h3) => {
const query = getQuery(h3);
const gameId = query.id?.toString();
const versionName = query.version?.toString();
const filename = query.name?.toString();
const chunkIndex = parseInt(query.chunk?.toString() ?? "?");
if (!gameId || !versionName || !filename || Number.isNaN(chunkIndex))
throw createError({
statusCode: 400,
statusMessage: "Invalid chunk arguments",
});
let game = await gameLookupCache.getItem(gameId);
if (!game) {
game = await prisma.game.findUnique({
where: {
id: gameId,
},
select: {
libraryId: true,
libraryPath: true,
},
});
if (!game || !game.libraryId)
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
await gameLookupCache.setItem(gameId, game);
}
if (!game.libraryId)
throw createError({
statusCode: 500,
statusMessage: "Somehow, we got here.",
});
const peek = await libraryManager.peekFile(
game.libraryId,
game.libraryPath,
versionName,
filename,
);
if (!peek)
throw createError({ status: 400, statusMessage: "Failed to peek file" });
const start = chunkIndex * chunkSize;
const end = Math.min((chunkIndex + 1) * chunkSize, peek.size);
const currentChunkSize = end - start;
setHeader(h3, "Content-Length", currentChunkSize);
if (start >= end)
throw createError({
statusCode: 400,
statusMessage: "Invalid chunk index",
});
const gameReadStream = await libraryManager.readFile(
game.libraryId,
game.libraryPath,
versionName,
filename,
{ start, end },
);
if (!gameReadStream)
throw createError({
statusCode: 400,
statusMessage: "Failed to create stream",
});
return sendStream(h3, gameReadStream);
const Query = type({
id: "string",
version: "string",
name: "string",
chunk: "string.numeric.parse",
});
/**
* v1 download API
* @deprecated
* @response Raw binary data (`application/octet-stream`)
*/
export default defineClientEventHandler<unknown, { query: typeof Query.infer }>(
async (h3) => {
const query = Query(getQuery(h3));
if (query instanceof ArkErrors)
throw createError({ statusCode: 400, statusMessage: query.summary });
const gameId = query.id;
const versionName = query.version;
const filename = query.name;
const chunkIndex = query.chunk;
if (!gameId || !versionName || !filename || Number.isNaN(chunkIndex))
throw createError({
statusCode: 400,
statusMessage: "Invalid chunk arguments",
});
let game = await gameLookupCache.getItem(gameId);
if (!game) {
game = await prisma.game.findUnique({
where: {
id: gameId,
},
select: {
libraryId: true,
libraryPath: true,
},
});
if (!game || !game.libraryId)
throw createError({
statusCode: 400,
statusMessage: "Invalid game ID",
});
await gameLookupCache.setItem(gameId, game);
}
if (!game.libraryId)
throw createError({
statusCode: 500,
statusMessage: "Somehow, we got here.",
});
const peek = await libraryManager.peekFile(
game.libraryId,
game.libraryPath,
versionName,
filename,
);
if (!peek)
throw createError({ status: 400, statusMessage: "Failed to peek file" });
const start = chunkIndex * chunkSize;
const end = Math.min((chunkIndex + 1) * chunkSize, peek.size);
const currentChunkSize = end - start;
setHeader(h3, "Content-Length", currentChunkSize);
if (start >= end)
throw createError({
statusCode: 400,
statusMessage: "Invalid chunk index",
});
const gameReadStream = await libraryManager.readFile(
game.libraryId,
game.libraryPath,
versionName,
filename,
{ start, end },
);
if (!gameReadStream)
throw createError({
statusCode: 400,
statusMessage: "Failed to create stream",
});
return sendStream(h3, gameReadStream);
},
);