Rearchitecture for v0.4.0 (#197)

* feat: database redist support

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

* feat: import redists

* fix: giantbomb logging bug

* feat: partial user platform support + statusMessage -> message

* feat: add user platform filters to store view

* fix: sanitize svg uploads

... copilot suggested this

I feel dirty.

* feat: beginnings of platform & redist management

* feat: add server side redist patching

* fix: update drop-base commit

* feat: import of custom platforms & file extensions

* fix: redelete platform

* fix: remove platform

* feat: uninstall commands, new R UI

* checkpoint: before migrating to nuxt v4

* update to nuxt 4

* fix: fixes for Nuxt v4 update

* fix: remaining type issues

* feat: initial feedback to import other kinds of versions

* working commit

* fix: lint

* feat: redist import
This commit is contained in:
DecDuck
2025-11-10 10:36:13 +11:00
committed by GitHub
parent dfa30c8a65
commit 251ddb8ff8
465 changed files with 8029 additions and 7509 deletions

View File

@ -1,6 +1,6 @@
import { AuthMec } from "~/prisma/client/enums";
import aclManager from "~/server/internal/acls";
import authManager from "~/server/internal/auth";
import { AuthMec } from "~~/prisma/client/enums";
import aclManager from "~~/server/internal/acls";
import authManager from "~~/server/internal/auth";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["auth:read", "setup"]);

View File

@ -1,7 +1,7 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
const DeleteInvite = type({
id: "string",

View File

@ -1,7 +1,7 @@
import aclManager from "~/server/internal/acls";
import { systemConfig } from "~/server/internal/config/sys-conf";
import prisma from "~/server/internal/db/database";
import taskHandler from "~/server/internal/tasks";
import aclManager from "~~/server/internal/acls";
import { systemConfig } from "~~/server/internal/config/sys-conf";
import prisma from "~~/server/internal/db/database";
import taskHandler from "~~/server/internal/tasks";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, [

View File

@ -1,8 +1,8 @@
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
import { SharedRegisterValidator } from "../../../auth/signup/simple.post";
import { systemConfig } from "~/server/internal/config/sys-conf";
import { systemConfig } from "~~/server/internal/config/sys-conf";
const CreateInvite = SharedRegisterValidator.partial()
.and({

View File

@ -1,7 +1,7 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import objectHandler from "~/server/internal/objects";
import { handleFileUpload } from "~/server/internal/utils/handlefileupload";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
import objectHandler from "~~/server/internal/objects";
import { handleFileUpload } from "~~/server/internal/utils/handlefileupload";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["company:update"]);
@ -15,13 +15,13 @@ export default defineEventHandler(async (h3) => {
});
if (!company)
throw createError({ statusCode: 400, statusMessage: "Invalid company id" });
throw createError({ statusCode: 400, message: "Invalid company id" });
const result = await handleFileUpload(h3, {}, ["internal:read"], 1);
if (!result)
throw createError({
statusCode: 400,
statusMessage: "File upload required (multipart form)",
message: "File upload required (multipart form)",
});
const [ids, , pull, dump] = result;
@ -29,7 +29,7 @@ export default defineEventHandler(async (h3) => {
if (!id)
throw createError({
statusCode: 400,
statusMessage: "Upload at least one file.",
message: "Upload at least one file.",
});
try {

View File

@ -1,7 +1,7 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
const GameDelete = type({
id: "string",

View File

@ -1,7 +1,7 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
const GamePatch = type({
action: "'developed' | 'published'",

View File

@ -1,7 +1,7 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
const GamePost = type({
published: "boolean",
@ -20,7 +20,7 @@ export default defineEventHandler(async (h3) => {
if (!body.published && !body.developed)
throw createError({
statusCode: 400,
statusMessage: "Must be related (either developed or published).",
message: "Must be related (either developed or published).",
});
const publisherConnect = body.published

View File

@ -1,7 +1,7 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import objectHandler from "~/server/internal/objects";
import { handleFileUpload } from "~/server/internal/utils/handlefileupload";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
import objectHandler from "~~/server/internal/objects";
import { handleFileUpload } from "~~/server/internal/utils/handlefileupload";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["company:update"]);
@ -15,13 +15,13 @@ export default defineEventHandler(async (h3) => {
});
if (!company)
throw createError({ statusCode: 400, statusMessage: "Invalid company id" });
throw createError({ statusCode: 400, message: "Invalid company id" });
const result = await handleFileUpload(h3, {}, ["internal:read"], 1);
if (!result)
throw createError({
statusCode: 400,
statusMessage: "File upload required (multipart form)",
message: "File upload required (multipart form)",
});
const [ids, , pull, dump] = result;
@ -29,7 +29,7 @@ export default defineEventHandler(async (h3) => {
if (!id)
throw createError({
statusCode: 400,
statusMessage: "Upload at least one file.",
message: "Upload at least one file.",
});
try {

View File

@ -1,5 +1,5 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["company:delete"]);
@ -9,6 +9,6 @@ export default defineEventHandler(async (h3) => {
const company = await prisma.company.deleteMany({ where: { id } });
if (company.count == 0)
throw createError({ statusCode: 404, statusMessage: "Company not found" });
throw createError({ statusCode: 404, message: "Company not found" });
return;
});

View File

@ -1,5 +1,5 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["company:read"]);
@ -23,7 +23,7 @@ export default defineEventHandler(async (h3) => {
},
});
if (!company)
throw createError({ statusCode: 404, statusMessage: "Company not found" });
throw createError({ statusCode: 404, message: "Company not found" });
const games = await prisma.game.findMany({
where: {
OR: [

View File

@ -1,5 +1,5 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["company:update"]);

View File

@ -1,5 +1,5 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["company:read"]);

View File

@ -1,10 +1,10 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
import aclManager from "~~/server/internal/acls";
import * as jdenticon from "jdenticon";
import { ObjectTransactionalHandler } from "~/server/internal/objects/transactional";
import prisma from "~/server/internal/db/database";
import { MetadataSource } from "~/prisma/client/enums";
import { ObjectTransactionalHandler } from "~~/server/internal/objects/transactional";
import prisma from "~~/server/internal/db/database";
import { MetadataSource } from "~~/prisma/client/enums";
const CompanyCreate = type({
name: "string",

View File

@ -1,5 +1,5 @@
import aclManager from "~/server/internal/acls";
import libraryManager from "~/server/internal/library";
import aclManager from "~~/server/internal/acls";
import libraryManager from "~~/server/internal/library";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["game:delete"]);
@ -7,7 +7,7 @@ export default defineEventHandler(async (h3) => {
const gameId = getRouterParam(h3, "id")!;
libraryManager.deleteGame(gameId);
await libraryManager.deleteGame(gameId);
return {};
});

View File

@ -1,7 +1,6 @@
import type { GameVersion } from "~/prisma/client/client";
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import libraryManager from "~/server/internal/library";
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, ["game:read"]);
@ -15,24 +14,32 @@ export default defineEventHandler(async (h3) => {
},
include: {
versions: {
orderBy: {
versionIndex: "asc",
},
omit: {
dropletManifest: true,
},
include: {
gameVersions: {
include: {
install: true,
uninstall: true,
launches: true,
},
},
},
},
tags: true,
},
});
if (!game || !game.libraryId)
throw createError({ statusCode: 404, statusMessage: "Game ID not found" });
throw createError({ statusCode: 404, message: "Game ID not found" });
const getGameVersionSize = async (version: GameVersion) => {
const getGameVersionSize = async (
version: Omit<(typeof game)["versions"][number], "dropletManifest">,
) => {
const size = await libraryManager.getGameVersionSize(
gameId,
version.versionName,
version.versionId,
);
return { ...version, size };
};

View File

@ -1,5 +1,5 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["game:update"]);

View File

@ -1,7 +1,13 @@
import type { Prisma } from "~/prisma/client/client";
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import { handleFileUpload } from "~/server/internal/utils/handlefileupload";
import { ArkErrors, type } from "arktype";
import type { Prisma } from "~~/prisma/client/client";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
import { handleFileUpload } from "~~/server/internal/utils/handlefileupload";
const UpdateMetadata = type({
name: "string?",
description: "string?",
});
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["game:update"]);
@ -11,7 +17,7 @@ export default defineEventHandler(async (h3) => {
if (!form)
throw createError({
statusCode: 400,
statusMessage: "This endpoint requires multipart form data.",
message: "This endpoint requires multipart form data.",
});
const gameId = getRouterParam(h3, "id")!;
@ -20,20 +26,20 @@ export default defineEventHandler(async (h3) => {
if (!uploadResult)
throw createError({
statusCode: 400,
statusMessage: "Failed to upload file",
message: "Failed to upload file",
});
const [ids, options, pull, dump] = uploadResult;
const id = ids.at(0);
// handleFileUpload reads the rest of the options for us.
const name = options.name;
const description = options.description;
const body = UpdateMetadata(options);
if (body instanceof ArkErrors)
throw createError({ statusCode: 400, message: body.summary });
const updateModel: Prisma.GameUpdateInput = {
mName: name,
mShortDescription: description,
...(body.name ? { mName: body.name } : undefined),
...(body.description ? { mShortDescription: body.description } : undefined),
};
// handle if user uploaded new icon

View File

@ -1,7 +1,7 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
const PatchTags = type({
tags: "string[]",

View File

@ -1,8 +1,8 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import objectHandler from "~/server/internal/objects";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
import objectHandler from "~~/server/internal/objects";
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
const DeleteGameImage = type({
gameId: "string",
@ -32,20 +32,20 @@ export default defineEventHandler<{
});
if (!game)
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
throw createError({ statusCode: 400, message: "Invalid game ID" });
const imageIndex = game.mImageLibraryObjectIds.findIndex((e) => e == imageId);
if (imageIndex == -1)
throw createError({ statusCode: 400, statusMessage: "Image not found" });
throw createError({ statusCode: 400, message: "Image not found" });
game.mImageLibraryObjectIds.splice(imageIndex, 1);
await objectHandler.deleteAsSystem(imageId);
if (game.mBannerObjectId === imageId) {
game.mBannerObjectId = game.mImageLibraryObjectIds[0];
game.mBannerObjectId = game.mImageLibraryObjectIds[0] ?? "";
}
if (game.mCoverObjectId === imageId) {
game.mCoverObjectId = game.mImageLibraryObjectIds[0];
game.mCoverObjectId = game.mImageLibraryObjectIds[0] ?? "";
}
const result = await prisma.game.update({

View File

@ -1,6 +1,6 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import { handleFileUpload } from "~/server/internal/utils/handlefileupload";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
import { handleFileUpload } from "~~/server/internal/utils/handlefileupload";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["game:image:new"]);
@ -10,14 +10,14 @@ export default defineEventHandler(async (h3) => {
if (!form)
throw createError({
statusCode: 400,
statusMessage: "This endpoint requires multipart form data.",
message: "This endpoint requires multipart form data.",
});
const uploadResult = await handleFileUpload(h3, {}, ["internal:read"]);
if (!uploadResult)
throw createError({
statusCode: 400,
statusMessage: "Failed to upload file",
message: "Failed to upload file",
});
const [ids, options, pull, dump] = uploadResult;
@ -25,21 +25,21 @@ export default defineEventHandler(async (h3) => {
dump();
throw createError({
statusCode: 400,
statusMessage: "Did not upload a file",
message: "Did not upload a file",
});
}
const gameId = options.id;
const gameId = options.id as string;
if (!gameId)
throw createError({
statusCode: 400,
statusMessage: "No game ID attached",
message: "No game ID attached",
});
const hasGame = (await prisma.game.count({ where: { id: gameId } })) != 0;
if (!hasGame) {
dump();
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
throw createError({ statusCode: 400, message: "Invalid game ID" });
}
const result = await prisma.game.update({

View File

@ -1,5 +1,5 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["game:read"]);

View File

@ -1,11 +1,10 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import libraryManager from "~/server/internal/library";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
import aclManager from "~~/server/internal/acls";
import libraryManager from "~~/server/internal/library";
const DeleteVersion = type({
id: "string",
versionName: "string",
}).configure(throwingArktype);
export default defineEventHandler<{ body: typeof DeleteVersion }>(
@ -17,10 +16,8 @@ export default defineEventHandler<{ body: typeof DeleteVersion }>(
const body = await readDropValidatedBody(h3, DeleteVersion);
const gameId = body.id.toString();
const version = body.versionName.toString();
await libraryManager.deleteGameVersion(body.id);
await libraryManager.deleteGameVersion(gameId, version);
return {};
},
);

View File

@ -1,7 +1,7 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
const UpdateVersionOrder = type({
id: "string",
@ -16,57 +16,24 @@ export default defineEventHandler<{ body: typeof UpdateVersionOrder }>(
if (!allowed) throw createError({ statusCode: 403 });
const body = await readDropValidatedBody(h3, UpdateVersionOrder);
const gameId = body.id;
// We expect an array of the version names for this game
const unsortedVersions = await prisma.gameVersion.findMany({
where: {
versionName: { in: body.versions },
},
select: {
versionName: true,
versionIndex: true,
delta: true,
platform: true,
},
});
const versions = body.versions
.map((e) => unsortedVersions.find((v) => v.versionName === e))
.filter((e) => e !== undefined);
if (versions.length !== unsortedVersions.length)
throw createError({
statusCode: 500,
statusMessage: "Sorting versions yielded less results, somehow.",
});
// Validate the new order
const has: { [key: string]: boolean } = {};
for (const version of versions) {
if (version.delta && !has[version.platform])
throw createError({
statusCode: 400,
statusMessage: `"${version.versionName}" requires a base version to apply the delta to.`,
});
has[version.platform] = true;
}
const versions = body.versions;
await prisma.$transaction(
versions.map((version, versionIndex) =>
prisma.gameVersion.update({
versions.map((versionId, versionIndex) =>
prisma.version.update({
where: {
gameId_versionName: {
gameId: gameId,
versionName: version.versionName,
},
versionId,
},
data: {
versionIndex: versionIndex,
},
select: {},
}),
),
);
return versions;
setResponseStatus(h3, 201);
return;
},
);

View File

@ -1,8 +1,8 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import { systemConfig } from "~/server/internal/config/sys-conf";
import libraryManager from "~/server/internal/library";
import userStatsManager from "~/server/internal/userstats";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
import { systemConfig } from "~~/server/internal/config/sys-conf";
import libraryManager from "~~/server/internal/library";
import userStatsManager from "~~/server/internal/userstats";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["game:read"]);

View File

@ -1,8 +1,8 @@
import aclManager from "~/server/internal/acls";
import libraryManager from "~/server/internal/library";
import aclManager from "~~/server/internal/acls";
import libraryManager from "~~/server/internal/library";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["import:game:read"]);
const allowed = await aclManager.allowSystemACL(h3, ["import:game:read", "import:redist:read"]);
if (!allowed) throw createError({ statusCode: 403 });
const unimportedGames = await libraryManager.fetchUnimportedGames();

View File

@ -1,8 +1,8 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import libraryManager from "~/server/internal/library";
import metadataHandler from "~/server/internal/metadata";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
import aclManager from "~~/server/internal/acls";
import libraryManager from "~~/server/internal/library";
import metadataHandler from "~~/server/internal/metadata";
const ImportGameBody = type({
library: "string",
@ -27,14 +27,14 @@ export default defineEventHandler<{ body: typeof ImportGameBody.infer }>(
if (!path)
throw createError({
statusCode: 400,
statusMessage: "Path missing from body",
message: "Path missing from body",
});
const valid = await libraryManager.checkUnimportedGamePath(library, path);
if (!valid)
throw createError({
statusCode: 400,
statusMessage: "Invalid library or game.",
message: "Invalid library or game.",
});
const taskId = metadata
@ -44,7 +44,7 @@ export default defineEventHandler<{ body: typeof ImportGameBody.infer }>(
if (!taskId)
throw createError({
statusCode: 400,
statusMessage:
message:
"Duplicate metadata import. Please chose a different game or metadata provider.",
});

View File

@ -1,5 +1,5 @@
import aclManager from "~/server/internal/acls";
import metadataHandler from "~/server/internal/metadata";
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"]);
@ -8,14 +8,14 @@ export default defineEventHandler(async (h3) => {
const query = getQuery(h3);
const search = query.q?.toString();
if (!search)
throw createError({ statusCode: 400, statusMessage: "Invalid search" });
throw createError({ statusCode: 400, message: "Invalid search" });
const results = await metadataHandler.search(search);
if (results.length == 0)
throw createError({
statusCode: 404,
statusMessage: "No metadata provider returned search results.",
message: "No metadata provider returned search results.",
});
return results;

View File

@ -0,0 +1,3 @@
import handler from "../game/index.get";
export default handler;

View File

@ -0,0 +1,92 @@
import { ArkErrors, type } from "arktype";
import aclManager from "~~/server/internal/acls";
import { handleFileUpload } from "~~/server/internal/utils/handlefileupload";
import * as jdenticon from "jdenticon";
import prisma from "~~/server/internal/db/database";
import libraryManager from "~~/server/internal/library";
import jsdom from "jsdom";
export const ImportRedist = type({
library: "string",
path: "string",
name: "string",
description: "string",
"platform?": type({
name: "string",
icon: "string",
fileExts: type("string").pipe.try((s) => JSON.parse(s), type("string.alphanumeric").array()),
}),
});
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["import:redist:new"]);
if (!allowed) throw createError({ statusCode: 403 });
const body = await handleFileUpload(h3, {}, ["internal:read"], 1);
if (!body) throw createError({ statusCode: 400, message: "Body required." });
const [ids, rawOptions, pull, , add] = body;
const id = ids.at(0);
const options = ImportRedist(rawOptions);
if (options instanceof ArkErrors)
throw createError({ statusCode: 400, message: options.summary });
const valid = await libraryManager.checkUnimportedGamePath(
options.library,
options.path,
);
if (!valid)
throw createError({
statusCode: 400,
message: "Invalid library or game.",
});
const icon = id ?? add(jdenticon.toPng(options.name, 512));
let svgContent = "";
if (options.platform) {
// This logic is duplicated on the client to make viewing there possible.
// TODO?: refactor into a single function. Not totally sure if this is a good idea though,
// because they do different things
const dom = new jsdom.JSDOM(options.platform.icon);
const svg = dom.window.document.getElementsByTagName("svg").item(0);
if (!svg)
throw createError({
statusCode: 400,
statusMessage: "No SVG in uploaded image.",
});
svg.removeAttribute("width");
svg.removeAttribute("height");
svgContent = svg.outerHTML;
}
const redist = await prisma.redist.create({
data: {
libraryId: options.library,
libraryPath: options.path,
mName: options.name,
mShortDescription: options.description,
mIconObjectId: icon,
platform: {
...(options.platform
? {
create: {
platformName: options.platform.name,
iconSvg: svgContent,
fileExtensions: options.platform.fileExts.map((v) => `.${v}`),
},
}
: undefined),
},
},
});
await pull();
return redist;
});

View File

@ -1,32 +1,36 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import libraryManager from "~/server/internal/library";
import { ArkErrors, type } from "arktype";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
import libraryManager, { VersionImportModes } from "~~/server/internal/library";
export const PreloadQuery = type({
id: "string",
mode: type.enumerated(...VersionImportModes),
});
export default defineEventHandler(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)
throw createError({
statusCode: 400,
statusMessage: "Missing id in request params",
});
const rawQuery = await getQuery(h3);
const query = PreloadQuery(rawQuery);
if (query instanceof ArkErrors)
throw createError({ statusCode: 400, message: query.summary });
const game = await prisma.game.findUnique({
where: { id: gameId },
select: { libraryId: true, libraryPath: true },
});
if (!game || !game.libraryId)
throw createError({ statusCode: 404, statusMessage: "Game not found" });
const value: { libraryId: string; libraryPath: string } | undefined =
await // eslint-disable-next-line @typescript-eslint/no-explicit-any
(prisma[query.mode] as any).findUnique({
where: { id: query.id },
select: { libraryId: true, libraryPath: true },
});
if (!value) throw createError({ statusCode: 404, message: "Not found" });
const unimportedVersions = await libraryManager.fetchUnimportedGameVersions(
game.libraryId,
game.libraryPath,
value.libraryId,
value.libraryPath,
);
if (!unimportedVersions)
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
throw createError({ statusCode: 400, message: "Invalid game ID" });
return unimportedVersions;
});

View File

@ -1,88 +1,71 @@
import { type } from "arktype";
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";
import { parsePlatform } from "~/server/internal/utils/parseplatform";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
import aclManager from "~~/server/internal/acls";
import libraryManager from "~~/server/internal/library";
const ImportVersion = type({
export const LaunchCommands = type({
name: "string > 0",
description: "string = ''",
launchCommand: "string > 0",
launchArgs: "string = ''",
}).array();
const ImportVersionBase = type({
id: "string",
version: "string",
name: "string?",
platform: "string",
launch: "string = ''",
launchArgs: "string = ''",
setup: "string = ''",
setupArgs: "string = ''",
onlySetup: "boolean = false",
delta: "boolean = false",
});
const ImportGameVersion = type({
mode: "'game'",
onlySetup: "boolean = false",
umuId: "string = ''",
}).configure(throwingArktype);
install: "string?",
installArgs: "string?",
launches: LaunchCommands,
uninstall: "string?",
uninstallArgs: "string?",
});
const ImportRedistVersion = type({
mode: "'redist'",
install: "string?",
installArgs: "string?",
launches: LaunchCommands,
uninstall: "string?",
uninstallArgs: "string?",
});
export const ImportVersion = ImportVersionBase.and(
ImportGameVersion.or(ImportRedistVersion),
).configure(throwingArktype);
export type ImportGameVersion = typeof ImportVersionBase.infer &
typeof ImportGameVersion.infer;
export type ImportRedistVersion = typeof ImportVersionBase.infer &
typeof ImportRedistVersion.infer;
export default defineEventHandler(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 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 (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",
});
}
const body = await readDropValidatedBody(h3, ImportVersion);
// startup & delta require more complex checking logic
const taskId = await libraryManager.importVersion(id, version, {
platform,
onlySetup,
launch,
launchArgs,
setup,
setupArgs,
umuId,
delta,
});
const taskId = await libraryManager.importVersion(
body.id,
body.version,
body,
);
if (!taskId)
throw createError({
statusCode: 400,
statusMessage: "Invalid options for import",
message: "Invalid options for import",
});
return { taskId: taskId };

View File

@ -1,27 +1,31 @@
import aclManager from "~/server/internal/acls";
import libraryManager from "~/server/internal/library";
import { ArkErrors, type } from "arktype";
import aclManager from "~~/server/internal/acls";
import libraryManager, { VersionImportModes } from "~~/server/internal/library";
export const PreloadQuery = type({
id: "string",
version: "string",
mode: type.enumerated(...VersionImportModes),
});
export default defineEventHandler(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)
throw createError({
statusCode: 400,
statusMessage: "Missing id or version in request params",
});
const rawQuery = await getQuery(h3);
const query = PreloadQuery(rawQuery);
if (query instanceof ArkErrors)
throw createError({ statusCode: 400, message: query.summary });
const preload = await libraryManager.fetchUnimportedVersionInformation(
gameId,
versionName,
query.id,
query.mode,
query.version,
);
if (!preload)
throw createError({
statusCode: 400,
statusMessage: "Invalid game or version id/name",
message: "Invalid game or version id/name",
});
return preload;

View File

@ -1,4 +1,4 @@
import aclManager from "~/server/internal/acls";
import aclManager from "~~/server/internal/acls";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, []);

View File

@ -1,5 +1,5 @@
import aclManager from "~/server/internal/acls";
import libraryManager from "~/server/internal/library";
import aclManager from "~~/server/internal/acls";
import libraryManager from "~~/server/internal/library";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["library:read"]);
@ -7,9 +7,15 @@ export default defineEventHandler(async (h3) => {
const unimportedGames = await libraryManager.fetchUnimportedGames();
const games = await libraryManager.fetchGamesWithStatus();
const redists = await libraryManager.fetchRedistsWithStatus();
const libraries = await libraryManager.fetchLibraries();
// Fetch other library data here
return { unimportedGames, games, hasLibraries: libraries.length > 0 };
return {
unimportedGames,
games,
redists,
hasLibraries: libraries.length > 0,
};
});

View File

@ -1,8 +1,8 @@
import { type } from "arktype";
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";
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";
const DeleteLibrarySource = type({
id: "string",

View File

@ -1,6 +1,6 @@
import type { LibraryModel } from "~/prisma/client/models";
import aclManager from "~/server/internal/acls";
import libraryManager from "~/server/internal/library";
import type { LibraryModel } from "~~/prisma/client/models";
import aclManager from "~~/server/internal/acls";
import libraryManager from "~~/server/internal/library";
export type WorkingLibrarySource = LibraryModel & {
working: boolean;

View File

@ -1,10 +1,10 @@
import { type } from "arktype";
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";
import type { WorkingLibrarySource } from "~/server/api/v1/admin/library/sources/index.get";
import { libraryConstructors } from "~/server/plugins/05.library-init";
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";
import type { WorkingLibrarySource } from "~~/server/api/v1/admin/library/sources/index.get";
import { libraryConstructors } from "~~/server/plugins/05.library-init";
const UpdateLibrarySource = type({
id: "string",
@ -26,7 +26,7 @@ export default defineEventHandler<{ body: typeof UpdateLibrarySource.infer }>(
if (!source)
throw createError({
statusCode: 400,
statusMessage: "Library source not found",
message: "Library source not found",
});
const constructor = libraryConstructors[source.backend];
@ -61,7 +61,7 @@ export default defineEventHandler<{ body: typeof UpdateLibrarySource.infer }>(
} catch (e) {
throw createError({
statusCode: 400,
statusMessage: `Failed to create source: ${e}`,
message: `Failed to create source: ${e}`,
});
}
},

View File

@ -1,12 +1,12 @@
import { type } from "arktype";
import { randomUUID } from "crypto";
import { LibraryBackend } 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";
import { libraryConstructors } from "~/server/plugins/05.library-init";
import type { WorkingLibrarySource } from "~/server/api/v1/admin/library/sources/index.get";
import { LibraryBackend } 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";
import { libraryConstructors } from "~~/server/plugins/05.library-init";
import type { WorkingLibrarySource } from "~~/server/api/v1/admin/library/sources/index.get";
const CreateLibrarySource = type({
name: "string",
@ -29,7 +29,7 @@ export default defineEventHandler<{ body: typeof CreateLibrarySource.infer }>(
if (!backend)
throw createError({
statusCode: 400,
statusMessage: "Invalid source backend.",
message: "Invalid source backend.",
});
const constructor = libraryConstructors[backend];
@ -64,7 +64,7 @@ export default defineEventHandler<{ body: typeof CreateLibrarySource.infer }>(
} catch (e) {
throw createError({
statusCode: 400,
statusMessage: `Failed to create source: ${e}`,
message: `Failed to create source: ${e}`,
});
}
},

View File

@ -1,6 +1,6 @@
import { defineEventHandler, createError } from "h3";
import aclManager from "~/server/internal/acls";
import newsManager from "~/server/internal/news";
import aclManager from "~~/server/internal/acls";
import newsManager from "~~/server/internal/news";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["news:delete"]);

View File

@ -1,6 +1,6 @@
import { defineEventHandler, createError } from "h3";
import aclManager from "~/server/internal/acls";
import newsManager from "~/server/internal/news";
import aclManager from "~~/server/internal/acls";
import newsManager from "~~/server/internal/news";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["news:read"]);

View File

@ -1,6 +1,6 @@
import { defineEventHandler, getQuery } from "h3";
import aclManager from "~/server/internal/acls";
import newsManager from "~/server/internal/news";
import aclManager from "~~/server/internal/acls";
import newsManager from "~~/server/internal/news";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["news:read"]);
@ -14,13 +14,13 @@ export default defineEventHandler(async (h3) => {
const orderBy = query.order as "asc" | "desc";
if (orderBy) {
if (typeof orderBy !== "string" || !["asc", "desc"].includes(orderBy))
throw createError({ statusCode: 400, statusMessage: "Invalid order" });
throw createError({ statusCode: 400, message: "Invalid order" });
}
const tags = query.tags as string[] | undefined;
if (tags) {
if (typeof tags !== "object" || !Array.isArray(tags))
throw createError({ statusCode: 400, statusMessage: "Invalid tags" });
throw createError({ statusCode: 400, message: "Invalid tags" });
}
const options = {

View File

@ -1,8 +1,8 @@
import { ArkErrors, type } from "arktype";
import { defineEventHandler, createError } from "h3";
import aclManager from "~/server/internal/acls";
import newsManager from "~/server/internal/news";
import { handleFileUpload } from "~/server/internal/utils/handlefileupload";
import aclManager from "~~/server/internal/acls";
import newsManager from "~~/server/internal/news";
import { handleFileUpload } from "~~/server/internal/utils/handlefileupload";
const CreateNews = type({
title: "string",
@ -19,27 +19,27 @@ export default defineEventHandler(async (h3) => {
if (!form)
throw createError({
statusCode: 400,
statusMessage: "This endpoint requires multipart form data.",
message: "This endpoint requires multipart form data.",
});
const uploadResult = await handleFileUpload(h3, {}, ["internal:read"], 1);
if (!uploadResult)
throw createError({
statusCode: 400,
statusMessage: "Failed to upload file",
message: "Failed to upload file",
});
const [imageIds, options, pull, _dump] = uploadResult;
const body = await CreateNews(options);
if (body instanceof ArkErrors)
throw createError({ statusCode: 400, statusMessage: body.summary });
throw createError({ statusCode: 400, message: body.summary });
const parsedTags = JSON.parse(body.tags);
if (typeof parsedTags !== "object" || !Array.isArray(parsedTags))
throw createError({
statusCode: 400,
statusMessage: "Tags must be an array",
message: "Tags must be an array",
});
const imageId = imageIds.at(0);

View File

@ -0,0 +1,11 @@
import aclManager from "~~/server/internal/acls"
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["import:version:read"]);
if(!allowed) throw createError({statusCode: 403});
const userPlatforms = await prisma.userPlatform.findMany({});
return userPlatforms;
})

View File

@ -0,0 +1,19 @@
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["redist:delete"]);
if (!allowed) throw createError({ statusCode: 403 });
const id = getRouterParam(h3, "id")!;
const { count } = await prisma.redist.deleteMany({
where: {
id,
},
});
if (count == 0) throw createError({ statusCode: 404 });
return;
});

View File

@ -0,0 +1,38 @@
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, ["redist:read"]);
if (!allowed) throw createError({ statusCode: 403 });
const id = getRouterParam(h3, "id")!;
const redist = await prisma.redist.findUnique({
where: {
id,
},
include: {
platform: true,
versions: true,
},
});
if (!redist)
throw createError({
statusCode: 404,
message: "Redistributable not found.",
});
const unimportedVersions = await libraryManager.fetchUnimportedGameVersions(
redist.libraryId,
redist.libraryPath,
);
if (!unimportedVersions)
throw createError({
statusCode: 500,
message: "Failed to fetch unimported versions for redistributable.",
});
return { redist, unimportedVersions };
});

View File

@ -0,0 +1,27 @@
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["redist:update"]);
if (!allowed) throw createError({ statusCode: 403 });
const body = await readBody(h3);
const id = body.id;
if (!id || typeof id !== "string")
throw createError({ statusCode: 400, message: "ID required in body." });
const updateParams = body;
delete updateParams["id"];
try {
return await prisma.redist.update({
where: {
id,
},
data: updateParams,
});
} catch (e) {
throw createError({ statusCode: 400, message: (e as string)?.toString() });
}
});

View File

@ -0,0 +1,16 @@
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["redist:read"]);
if (!allowed) throw createError({ statusCode: 403 });
return await prisma.redist.findMany({
select: {
id: true,
mName: true,
mShortDescription: true,
mIconObjectId: true,
},
});
});

View File

@ -1,5 +1,5 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.getUserACL(h3, ["settings:read"]);

View File

@ -1,8 +1,8 @@
import { type } from "arktype";
import { applicationSettings } from "~/server/internal/config/application-configuration";
import { readDropValidatedBody } from "~/server/arktype";
import { applicationSettings } from "~~/server/internal/config/application-configuration";
import { readDropValidatedBody } from "~~/server/arktype";
import { defineEventHandler, createError } from "h3";
import aclManager from "~/server/internal/acls";
import aclManager from "~~/server/internal/acls";
const UpdateSettings = type({
showGamePanelTextDecoration: "boolean",

View File

@ -1,5 +1,5 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["tags:delete"]);
@ -9,6 +9,6 @@ export default defineEventHandler(async (h3) => {
const tag = await prisma.gameTag.deleteMany({ where: { id } });
if (tag.count == 0)
throw createError({ statusCode: 404, statusMessage: "Tag not found" });
throw createError({ statusCode: 404, message: "Tag not found" });
return;
});

View File

@ -1,5 +1,5 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["tags:read"]);

View File

@ -1,7 +1,7 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
const CreateTag = type({
name: "string",

View File

@ -1,7 +1,7 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import type { TaskMessage } from "~/server/internal/tasks";
import taskHandler from "~/server/internal/tasks";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
import type { TaskMessage } from "~~/server/internal/tasks";
import taskHandler from "~~/server/internal/tasks";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["task:read"]);
@ -10,7 +10,7 @@ export default defineEventHandler(async (h3) => {
if (!allAcls)
throw createError({
statusCode: 403,
statusMessage: "Somehow no ACLs on authenticated request.",
message: "Somehow no ACLs on authenticated request.",
});
const runningTasks = (await taskHandler.runningTasks()).map((e) => e.id);

View File

@ -1,12 +1,11 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls";
import taskHandler from "~/server/internal/tasks";
import type { TaskGroup } from "~/server/internal/tasks/group";
import { taskGroups } from "~/server/internal/tasks/group";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
import aclManager from "~~/server/internal/acls";
import taskHandler from "~~/server/internal/tasks";
import { TASK_GROUPS } from "~~/server/internal/tasks/group";
const StartTask = type({
taskGroup: type("string"),
taskGroup: type.enumerated(...TASK_GROUPS),
}).configure(throwingArktype);
export default defineEventHandler(async (h3) => {
@ -14,18 +13,12 @@ export default defineEventHandler(async (h3) => {
if (!allowed) throw createError({ statusCode: 403 });
const body = await readDropValidatedBody(h3, StartTask);
const taskGroup = body.taskGroup as TaskGroup;
if (!taskGroups[taskGroup])
throw createError({
statusCode: 400,
statusMessage: "Invalid task group.",
});
const task = await taskHandler.runTaskGroupByName(taskGroup);
const task = await taskHandler.runTaskGroupByName(body.taskGroup);
if (!task)
throw createError({
statusCode: 500,
statusMessage: "Could not start task.",
message: "Could not start task.",
});
return { id: task };
});

View File

@ -1,6 +1,6 @@
import { APITokenMode } from "~/prisma/client/enums";
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import { APITokenMode } from "~~/prisma/client/enums";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, []); // No ACLs only allows session authentication
@ -10,14 +10,14 @@ export default defineEventHandler(async (h3) => {
if (!id)
throw createError({
statusCode: 400,
statusMessage: "No id in router params",
message: "No id in router params",
});
const deleted = await prisma.aPIToken.delete({
where: { id: id, mode: APITokenMode.System },
})!;
if (!deleted)
throw createError({ statusCode: 404, statusMessage: "Token not found" });
throw createError({ statusCode: 404, message: "Token not found" });
return;
});

View File

@ -1,5 +1,5 @@
import aclManager from "~/server/internal/acls";
import { systemACLDescriptions } from "~/server/internal/acls/descriptions";
import aclManager from "~~/server/internal/acls";
import { systemACLDescriptions } from "~~/server/internal/acls/descriptions";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, []); // No ACLs only allows session authentication

View File

@ -1,6 +1,6 @@
import { APITokenMode } from "~/prisma/client/enums";
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import { APITokenMode } from "~~/prisma/client/enums";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, []); // No ACLs only allows session authentication

View File

@ -1,8 +1,8 @@
import { type } from "arktype";
import { APITokenMode } from "~/prisma/client/enums";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager, { systemACLs } from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import { APITokenMode } from "~~/prisma/client/enums";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
import aclManager, { systemACLs } from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
const CreateToken = type({
name: "string",
@ -22,7 +22,7 @@ export default defineEventHandler(async (h3) => {
if (invalidACLs.length > 0)
throw createError({
statusCode: 400,
statusMessage: `Invalid ACLs: ${invalidACLs.join(", ")}`,
message: `Invalid ACLs: ${invalidACLs.join(", ")}`,
});
const token = await prisma.aPIToken.create({

View File

@ -1,7 +1,7 @@
import { defineEventHandler, createError } from "h3";
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import userStatsManager from "~/server/internal/userstats";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
import userStatsManager from "~~/server/internal/userstats";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["user:delete"]);
@ -20,12 +20,12 @@ export default defineEventHandler(async (h3) => {
if (userId === "system")
throw createError({
statusCode: 400,
statusMessage: "Cannot interact with system user.",
message: "Cannot interact with system user.",
});
const user = await prisma.user.findUnique({ where: { id: userId } });
if (!user)
throw createError({ statusCode: 404, statusMessage: "User not found." });
throw createError({ statusCode: 404, message: "User not found." });
await prisma.user.delete({ where: { id: userId } });
await userStatsManager.deleteUser();

View File

@ -1,5 +1,5 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["user:read"]);
@ -9,18 +9,18 @@ export default defineEventHandler(async (h3) => {
if (!userId)
throw createError({
statusCode: 400,
statusMessage: "No userId in route.",
message: "No userId in route.",
});
if (userId == "system")
throw createError({
statusCode: 400,
statusMessage: "Cannot delete system user.",
message: "Cannot fetch system user.",
});
const user = await prisma.user.findUnique({ where: { id: userId } });
if (!user)
throw createError({ statusCode: 404, statusMessage: "User not found." });
throw createError({ statusCode: 404, message: "User not found." });
return user;
});

View File

@ -1,5 +1,5 @@
import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import aclManager from "~~/server/internal/acls";
import prisma from "~~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const allowed = await aclManager.allowSystemACL(h3, ["user:read"]);

View File

@ -1,4 +1,4 @@
import authManager from "~/server/internal/auth";
import authManager from "~~/server/internal/auth";
export default defineEventHandler(() => {
return authManager.getEnabledAuthProviders();

View File

@ -1,13 +1,13 @@
import { AuthMec } from "~/prisma/client/enums";
import { AuthMec } from "~~/prisma/client/enums";
import type { JsonArray } from "@prisma/client/runtime/library";
import { type } from "arktype";
import prisma from "~/server/internal/db/database";
import sessionHandler from "~/server/internal/session";
import prisma from "~~/server/internal/db/database";
import sessionHandler from "~~/server/internal/session";
import authManager, {
checkHashArgon2,
checkHashBcrypt,
} from "~/server/internal/auth";
import { logger } from "~/server/internal/logging";
} from "~~/server/internal/auth";
import { logger } from "~~/server/internal/logging";
const signinValidator = type({
username: "string",
@ -23,7 +23,7 @@ export default defineEventHandler<{
if (!authManager.getAuthProviders().Simple)
throw createError({
statusCode: 403,
statusMessage: t("errors.auth.method.signinDisabled"),
message: t("errors.auth.method.signinDisabled"),
});
const body = signinValidator(await readBody(h3));
@ -33,7 +33,7 @@ export default defineEventHandler<{
throw createError({
statusCode: 400,
statusMessage: body.summary,
message: body.summary,
});
}
@ -57,13 +57,13 @@ export default defineEventHandler<{
if (!authMek)
throw createError({
statusCode: 401,
statusMessage: t("errors.auth.invalidUserOrPass"),
message: t("errors.auth.invalidUserOrPass"),
});
if (!authMek.user.enabled)
throw createError({
statusCode: 403,
statusMessage: t("errors.auth.disabled"),
message: t("errors.auth.disabled"),
});
// LEGACY bcrypt
@ -74,13 +74,13 @@ export default defineEventHandler<{
if (!hash)
throw createError({
statusCode: 500,
statusMessage: t("errors.auth.invalidPassState"),
message: t("errors.auth.invalidPassState"),
});
if (!(await checkHashBcrypt(body.password, hash)))
throw createError({
statusCode: 401,
statusMessage: t("errors.auth.invalidUserOrPass"),
message: t("errors.auth.invalidUserOrPass"),
});
// TODO: send user to forgot password screen or something to force them to change their password to new system
@ -93,13 +93,13 @@ export default defineEventHandler<{
if (!hash || typeof hash !== "string")
throw createError({
statusCode: 500,
statusMessage: t("errors.auth.invalidPassState"),
message: t("errors.auth.invalidPassState"),
});
if (!(await checkHashArgon2(body.password, hash)))
throw createError({
statusCode: 401,
statusMessage: t("errors.auth.invalidUserOrPass"),
message: t("errors.auth.invalidUserOrPass"),
});
await sessionHandler.signin(h3, authMek.userId, body.rememberMe);

View File

@ -1,6 +1,6 @@
import prisma from "~/server/internal/db/database";
import taskHandler from "~/server/internal/tasks";
import authManager from "~/server/internal/auth";
import prisma from "~~/server/internal/db/database";
import taskHandler from "~~/server/internal/tasks";
import authManager from "~~/server/internal/auth";
export default defineEventHandler(async (h3) => {
const t = await useTranslation(h3);
@ -8,7 +8,7 @@ export default defineEventHandler(async (h3) => {
if (!authManager.getAuthProviders().Simple)
throw createError({
statusCode: 403,
statusMessage: t("errors.auth.method.signinDisabled"),
message: t("errors.auth.method.signinDisabled"),
});
const query = getQuery(h3);
@ -16,7 +16,7 @@ export default defineEventHandler(async (h3) => {
if (!id)
throw createError({
statusCode: 400,
statusMessage: t("errors.auth.inviteIdRequired"),
message: t("errors.auth.inviteIdRequired"),
});
taskHandler.runTaskGroupByName("cleanup:invitations");
@ -24,7 +24,7 @@ export default defineEventHandler(async (h3) => {
if (!invitation)
throw createError({
statusCode: 404,
statusMessage: t("errors.auth.invalidInvite"),
message: t("errors.auth.invalidInvite"),
});
return invitation;

View File

@ -1,12 +1,12 @@
import { AuthMec } from "~/prisma/client/enums";
import prisma from "~/server/internal/db/database";
import authManager, { createHashArgon2 } from "~/server/internal/auth";
import { AuthMec } from "~~/prisma/client/enums";
import prisma from "~~/server/internal/db/database";
import authManager, { createHashArgon2 } from "~~/server/internal/auth";
import * as jdenticon from "jdenticon";
import objectHandler from "~/server/internal/objects";
import objectHandler from "~~/server/internal/objects";
import { type } from "arktype";
import { randomUUID } from "node:crypto";
import { throwingArktype } from "~/server/arktype";
import userStatsManager from "~/server/internal/userstats";
import { throwingArktype } from "~~/server/arktype";
import userStatsManager from "~~/server/internal/userstats";
export const SharedRegisterValidator = type({
username: "string >= 5",
@ -27,7 +27,7 @@ export default defineEventHandler<{
if (!authManager.getAuthProviders().Simple)
throw createError({
statusCode: 403,
statusMessage: t("errors.auth.method.signinDisabled"),
message: t("errors.auth.method.signinDisabled"),
});
const user = await readValidatedBody(h3, CreateUserValidator);
@ -38,7 +38,7 @@ export default defineEventHandler<{
if (!invitation)
throw createError({
statusCode: 401,
statusMessage: t("errors.auth.invalidInvite"),
message: t("errors.auth.invalidInvite"),
});
// reuse items from invite
@ -51,7 +51,7 @@ export default defineEventHandler<{
if (existing > 0)
throw createError({
statusCode: 400,
statusMessage: t("errors.auth.usernameTaken"),
message: t("errors.auth.usernameTaken"),
});
const userId = randomUUID();

View File

@ -1,5 +1,5 @@
import clientHandler from "~/server/internal/clients/handler";
import sessionHandler from "~/server/internal/session";
import clientHandler from "~~/server/internal/clients/handler";
import sessionHandler from "~~/server/internal/session";
export default defineEventHandler(async (h3) => {
const user = await sessionHandler.getSession(h3);
@ -12,13 +12,13 @@ export default defineEventHandler(async (h3) => {
if (!client)
throw createError({
statusCode: 400,
statusMessage: "Invalid or expired client ID.",
message: "Invalid or expired client ID.",
});
if (client.userId != user.userId)
throw createError({
statusCode: 403,
statusMessage: "Not allowed to authorize this client.",
message: "Not allowed to authorize this client.",
});
const token = await clientHandler.generateAuthToken(clientId);

View File

@ -1,5 +1,5 @@
import clientHandler from "~/server/internal/clients/handler";
import sessionHandler from "~/server/internal/session";
import clientHandler from "~~/server/internal/clients/handler";
import sessionHandler from "~~/server/internal/session";
export default defineEventHandler(async (h3) => {
const user = await sessionHandler.getSession(h3);
@ -10,12 +10,12 @@ export default defineEventHandler(async (h3) => {
if (!code)
throw createError({
statusCode: 400,
statusMessage: "Code required in query params.",
message: "Code required in query params.",
});
const clientId = await clientHandler.fetchClientIdByCode(code);
if (!clientId)
throw createError({ statusCode: 400, statusMessage: "Invalid code." });
throw createError({ statusCode: 400, message: "Invalid code." });
return clientId;
});

View File

@ -1,5 +1,5 @@
import clientHandler from "~/server/internal/clients/handler";
import sessionHandler from "~/server/internal/session";
import clientHandler from "~~/server/internal/clients/handler";
import sessionHandler from "~~/server/internal/session";
export default defineEventHandler(async (h3) => {
const user = await sessionHandler.getSession(h3);
@ -12,19 +12,19 @@ export default defineEventHandler(async (h3) => {
if (!client)
throw createError({
statusCode: 400,
statusMessage: "Invalid or expired client ID.",
message: "Invalid or expired client ID.",
});
if (client.userId != user.userId)
throw createError({
statusCode: 403,
statusMessage: "Not allowed to authorize this client.",
message: "Not allowed to authorize this client.",
});
if (!client.peer)
throw createError({
statusCode: 500,
statusMessage: "No client listening for authorization.",
message: "No client listening for authorization.",
});
const token = await clientHandler.generateAuthToken(clientId);

View File

@ -1,5 +1,5 @@
import type { FetchError } from "ofetch";
import clientHandler from "~/server/internal/clients/handler";
import clientHandler from "~~/server/internal/clients/handler";
export default defineWebSocketHandler({
async open(peer) {
@ -9,14 +9,14 @@ export default defineWebSocketHandler({
if (!code)
throw createError({
statusCode: 400,
statusMessage: "Code required in Authorization header.",
message: "Code required in Authorization header.",
});
await clientHandler.connectCodeListener(code, peer);
} catch (e) {
peer.send(
JSON.stringify({
type: "error",
value: (e as FetchError)?.statusMessage,
value: (e as FetchError)?.message,
}),
);
peer.close();

View File

@ -1,5 +1,5 @@
import clientHandler from "~/server/internal/clients/handler";
import { useCertificateAuthority } from "~/server/plugins/ca";
import clientHandler from "~~/server/internal/clients/handler";
import { useCertificateAuthority } from "~~/server/plugins/ca";
export default defineEventHandler(async (h3) => {
const body = await readBody(h3);
@ -8,24 +8,24 @@ export default defineEventHandler(async (h3) => {
if (!clientId || !token)
throw createError({
statusCode: 400,
statusMessage: "Missing token or client ID from body",
message: "Missing token or client ID from body",
});
const metadata = await clientHandler.fetchClient(clientId);
if (!metadata)
throw createError({
statusCode: 403,
statusMessage: "Invalid client ID",
message: "Invalid client ID",
});
if (!metadata.authToken || !metadata.userId)
throw createError({
statusCode: 400,
statusMessage: "Un-authorized client ID",
message: "Un-authorized client ID",
});
if (metadata.authToken !== token)
throw createError({
statusCode: 403,
statusMessage: "Invalid token",
message: "Invalid token",
});
const certificateAuthority = useCertificateAuthority();

View File

@ -1,5 +1,5 @@
import clientHandler from "~/server/internal/clients/handler";
import sessionHandler from "~/server/internal/session";
import clientHandler from "~~/server/internal/clients/handler";
import sessionHandler from "~~/server/internal/session";
export default defineEventHandler(async (h3) => {
const user = await sessionHandler.getSession(h3);
@ -10,20 +10,20 @@ export default defineEventHandler(async (h3) => {
if (!providedClientId)
throw createError({
statusCode: 400,
statusMessage: "Provide client ID in request params as 'id'",
message: "Provide client ID in request params as 'id'",
});
const client = await clientHandler.fetchClient(providedClientId);
if (!client)
throw createError({
statusCode: 404,
statusMessage: "Request not found.",
message: "Request not found.",
});
if (client.userId && user.userId !== client.userId)
throw createError({
statusCode: 400,
statusMessage: "Client already claimed.",
message: "Client already claimed.",
});
await clientHandler.attachUserId(providedClientId, user.userId);

View File

@ -1,20 +1,20 @@
import { type } from "arktype";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import type { ClientCapabilities } from "~~/prisma/client/enums";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
import type {
CapabilityConfiguration,
InternalClientCapability,
} from "~/server/internal/clients/capabilities";
} from "~~/server/internal/clients/capabilities";
import capabilityManager, {
validCapabilities,
} from "~/server/internal/clients/capabilities";
import clientHandler, { AuthMode } from "~/server/internal/clients/handler";
import { parsePlatform } from "~/server/internal/utils/parseplatform";
} from "~~/server/internal/clients/capabilities";
import clientHandler, { AuthModes } from "~~/server/internal/clients/handler";
import { parsePlatform } from "~~/server/internal/utils/parseplatform";
const ClientAuthInitiate = type({
name: "string",
platform: "string",
capabilities: "object",
mode: type.valueOf(AuthMode).default(AuthMode.Callback),
mode: type.enumerated(...AuthModes).default("callback"),
}).configure(throwingArktype);
export default defineEventHandler(async (h3) => {
@ -28,11 +28,11 @@ export default defineEventHandler(async (h3) => {
if (!platform)
throw createError({
statusCode: 400,
statusMessage: "Invalid or unsupported platform",
message: "Invalid or unsupported platform",
});
const capabilityIterable = Object.entries(capabilities) as Array<
[InternalClientCapability, object]
[ClientCapabilities, object]
>;
if (
capabilityIterable.length > 0 &&
@ -42,7 +42,7 @@ export default defineEventHandler(async (h3) => {
)
throw createError({
statusCode: 400,
statusMessage: "Invalid capabilities.",
message: "Invalid capabilities.",
});
if (
@ -57,7 +57,7 @@ export default defineEventHandler(async (h3) => {
)
throw createError({
statusCode: 400,
statusMessage: "Invalid capability configuration.",
message: "Invalid capability configuration.",
});
const result = await clientHandler.initiate({

View File

@ -1,49 +1,32 @@
import type { InternalClientCapability } from "~/server/internal/clients/capabilities";
import capabilityManager, {
validCapabilities,
} from "~/server/internal/clients/capabilities";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import notificationSystem from "~/server/internal/notifications";
import { type } from "arktype";
import { ClientCapabilities } from "~~/prisma/client/enums";
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
import capabilityManager from "~~/server/internal/clients/capabilities";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import notificationSystem from "~~/server/internal/notifications";
const SetCapability = type({
capability: type.enumerated(...Object.values(ClientCapabilities)),
configuration: "object"
}).configure(throwingArktype);
export default defineClientEventHandler(
async (h3, { clientId, fetchClient, fetchUser }) => {
const body = await readBody(h3);
const rawCapability = body.capability;
const configuration = body.configuration;
if (!rawCapability || typeof rawCapability !== "string")
throw createError({
statusCode: 400,
statusMessage: "capability must be a string",
});
if (!configuration || typeof configuration !== "object")
throw createError({
statusCode: 400,
statusMessage: "configuration must be an object",
});
const capability = rawCapability as InternalClientCapability;
if (!validCapabilities.includes(capability))
throw createError({
statusCode: 400,
statusMessage: "Invalid capability.",
});
const body = await readDropValidatedBody(h3, SetCapability);
const isValid = await capabilityManager.validateCapabilityConfiguration(
capability,
configuration,
body.capability,
body.configuration,
);
if (!isValid)
throw createError({
statusCode: 400,
statusMessage: "Invalid capability configuration.",
message: "Invalid capability configuration.",
});
await capabilityManager.upsertClientCapability(
capability,
configuration,
body.capability,
body.configuration,
clientId,
);
@ -51,9 +34,9 @@ export default defineClientEventHandler(
const user = await fetchUser();
await notificationSystem.push(user.id, {
nonce: `capability-${clientId}-${capability}`,
title: `"${client.name}" can now access ${capability}`,
description: `A device called "${client.name}" now has access to your ${capability}.`,
nonce: `capability-${clientId}-${body.capability}`,
title: `"${client.name}" can now access ${body.capability}`,
description: `A device called "${client.name}" now has access to your ${body.capability}.`,
actions: ["Review|/account/devices"],
acls: ["user:clients:read"],
});

View File

@ -1,7 +1,7 @@
import cacheHandler from "~/server/internal/cache";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import prisma from "~/server/internal/db/database";
import libraryManager from "~/server/internal/library";
import cacheHandler from "~~/server/internal/cache";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import prisma from "~~/server/internal/db/database";
import libraryManager from "~~/server/internal/library";
const chunkSize = 1024 * 1024 * 64;
@ -20,7 +20,7 @@ export default defineClientEventHandler(async (h3) => {
if (!gameId || !versionName || !filename || Number.isNaN(chunkIndex))
throw createError({
statusCode: 400,
statusMessage: "Invalid chunk arguments",
message: "Invalid chunk arguments",
});
let game = await gameLookupCache.getItem(gameId);
@ -35,7 +35,7 @@ export default defineClientEventHandler(async (h3) => {
},
});
if (!game || !game.libraryId)
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
throw createError({ statusCode: 400, message: "Invalid game ID" });
await gameLookupCache.setItem(gameId, game);
}
@ -43,7 +43,7 @@ export default defineClientEventHandler(async (h3) => {
if (!game.libraryId)
throw createError({
statusCode: 500,
statusMessage: "Somehow, we got here.",
message: "Somehow, we got here.",
});
const peek = await libraryManager.peekFile(
@ -53,7 +53,7 @@ export default defineClientEventHandler(async (h3) => {
filename,
);
if (!peek)
throw createError({ status: 400, statusMessage: "Failed to peek file" });
throw createError({ status: 400, message: "Failed to peek file" });
const start = chunkIndex * chunkSize;
const end = Math.min((chunkIndex + 1) * chunkSize, peek.size);
@ -63,7 +63,7 @@ export default defineClientEventHandler(async (h3) => {
if (start >= end)
throw createError({
statusCode: 400,
statusMessage: "Invalid chunk index",
message: "Invalid chunk index",
});
const gameReadStream = await libraryManager.readFile(
@ -76,7 +76,7 @@ export default defineClientEventHandler(async (h3) => {
if (!gameReadStream)
throw createError({
statusCode: 400,
statusMessage: "Failed to create stream",
message: "Failed to create stream",
});
return sendStream(h3, gameReadStream);

View File

@ -1,5 +1,5 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import userLibraryManager from "~~/server/internal/userlibrary";
export default defineClientEventHandler(async (h3, { fetchUser }) => {
const user = await fetchUser();
@ -8,13 +8,13 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
if (!id)
throw createError({
statusCode: 400,
statusMessage: "ID required in route params",
message: "ID required in route params",
});
const body = await readBody(h3);
const gameId = body.id;
if (!gameId)
throw createError({ statusCode: 400, statusMessage: "Game ID required" });
throw createError({ statusCode: 400, message: "Game ID required" });
const successful = await userLibraryManager.collectionRemove(
gameId,
@ -24,7 +24,7 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
if (!successful)
throw createError({
statusCode: 404,
statusMessage: "Collection not found",
message: "Collection not found",
});
return {};
});

View File

@ -1,5 +1,5 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import userLibraryManager from "~~/server/internal/userlibrary";
export default defineClientEventHandler(async (h3, { fetchUser }) => {
const user = await fetchUser();
@ -8,13 +8,13 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
if (!id)
throw createError({
statusCode: 400,
statusMessage: "ID required in route params",
message: "ID required in route params",
});
const body = await readBody(h3);
const gameId = body.id;
if (!gameId)
throw createError({ statusCode: 400, statusMessage: "Game ID required" });
throw createError({ statusCode: 400, message: "Game ID required" });
return await userLibraryManager.collectionAdd(gameId, id, user.id);
});

View File

@ -1,5 +1,5 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import userLibraryManager from "~~/server/internal/userlibrary";
export default defineClientEventHandler(async (h3, { fetchUser }) => {
const user = await fetchUser();
@ -8,7 +8,7 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
if (!id)
throw createError({
statusCode: 400,
statusMessage: "ID required in route params",
message: "ID required in route params",
});
// Verify collection exists and user owns it
@ -17,13 +17,13 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
if (!collection)
throw createError({
statusCode: 404,
statusMessage: "Collection not found",
message: "Collection not found",
});
if (collection.userId !== user.id)
throw createError({
statusCode: 403,
statusMessage: "Not authorized to delete this collection",
message: "Not authorized to delete this collection",
});
await userLibraryManager.deleteCollection(id);

View File

@ -1,5 +1,5 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import userLibraryManager from "~~/server/internal/userlibrary";
export default defineClientEventHandler(async (h3, { fetchUser }) => {
const user = await fetchUser();
@ -8,7 +8,7 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
if (!id)
throw createError({
statusCode: 400,
statusMessage: "ID required in route params",
message: "ID required in route params",
});
// Fetch specific collection
@ -17,14 +17,14 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
if (!collection)
throw createError({
statusCode: 404,
statusMessage: "Collection not found",
message: "Collection not found",
});
// Verify user owns this collection
if (collection.userId !== user.id)
throw createError({
statusCode: 403,
statusMessage: "Not authorized to access this collection",
message: "Not authorized to access this collection",
});
return collection;

View File

@ -1,5 +1,5 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import userLibraryManager from "~~/server/internal/userlibrary";
export default defineClientEventHandler(async (h3, { fetchUser }) => {
const user = await fetchUser();
@ -8,7 +8,7 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
const gameId = body.id;
if (!gameId)
throw createError({ statusCode: 400, statusMessage: "Game ID required" });
throw createError({ statusCode: 400, message: "Game ID required" });
await userLibraryManager.libraryRemove(gameId, user.id);
return {};

View File

@ -1,5 +1,5 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import userLibraryManager from "~~/server/internal/userlibrary";
export default defineClientEventHandler(async (h3, { fetchUser }) => {
const user = await fetchUser();
@ -7,7 +7,7 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
const body = await readBody(h3);
const gameId = body.id;
if (!gameId)
throw createError({ statusCode: 400, statusMessage: "Game ID required" });
throw createError({ statusCode: 400, message: "Game ID required" });
// Add the game to the default collection
await userLibraryManager.libraryAdd(gameId, user.id);

View File

@ -1,5 +1,5 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import userLibraryManager from "~~/server/internal/userlibrary";
export default defineClientEventHandler(async (h3, { fetchUser }) => {
const user = await fetchUser();

View File

@ -1,5 +1,5 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import userLibraryManager from "~~/server/internal/userlibrary";
export default defineClientEventHandler(async (h3, { fetchUser }) => {
const user = await fetchUser();

View File

@ -1,5 +1,5 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import userLibraryManager from "~~/server/internal/userlibrary";
export default defineClientEventHandler(async (h3, { fetchUser }) => {
const user = await fetchUser();
@ -8,7 +8,7 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
const name = body.name;
if (!name)
throw createError({ statusCode: 400, statusMessage: "Requires name" });
throw createError({ statusCode: 400, message: "Requires name" });
// Create the collection using the manager
const newCollection = await userLibraryManager.collectionCreate(

View File

@ -1,10 +1,10 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import prisma from "~/server/internal/db/database";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import prisma from "~~/server/internal/db/database";
export default defineClientEventHandler(async (h3) => {
const id = getRouterParam(h3, "id");
if (!id)
throw createError({ statusCode: 400, statusMessage: "No ID in route" });
throw createError({ statusCode: 400, message: "No ID in route" });
const game = await prisma.game.findUnique({
where: {
@ -12,7 +12,7 @@ export default defineClientEventHandler(async (h3) => {
},
});
if (!game)
throw createError({ statusCode: 404, statusMessage: "Game not found" });
throw createError({ statusCode: 404, message: "Game not found" });
return game;
});

View File

@ -1,21 +1,20 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import manifestGenerator from "~/server/internal/downloads/manifest";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import manifestGenerator from "~~/server/internal/downloads/manifest";
export default defineClientEventHandler(async (h3) => {
const query = getQuery(h3);
const id = query.id?.toString();
const version = query.version?.toString();
if (!id || !version)
if (!id)
throw createError({
statusCode: 400,
statusMessage: "Missing id or version in query",
message: "Missing version id in query",
});
const manifest = await manifestGenerator.generateManifest(id, version);
const manifest = await manifestGenerator.generateManifest(id);
if (!manifest)
throw createError({
statusCode: 400,
statusMessage: "Invalid game or version, or no versions added.",
message: "Invalid game or version, or no versions added.",
});
return manifest;
});

View File

@ -1,6 +1,6 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import prisma from "~/server/internal/db/database";
import libraryManager from "~/server/internal/library";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import prisma from "~~/server/internal/db/database";
import libraryManager from "~~/server/internal/library";
export default defineClientEventHandler(async (h3) => {
const query = getQuery(h3);
@ -9,22 +9,19 @@ export default defineClientEventHandler(async (h3) => {
if (!id || !version)
throw createError({
statusCode: 400,
statusMessage: "Missing id or version in query",
message: "Missing id or version in query",
});
const gameVersion = await prisma.gameVersion.findUnique({
where: {
gameId_versionName: {
gameId: id,
versionName: version,
},
versionId: id,
},
});
if (!gameVersion)
throw createError({
statusCode: 404,
statusMessage: "Game version not found",
message: "Game version not found",
});
return {

View File

@ -1,5 +1,5 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import prisma from "~/server/internal/db/database";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import prisma from "~~/server/internal/db/database";
export default defineClientEventHandler(async (h3) => {
const query = getQuery(h3);
@ -7,31 +7,21 @@ export default defineClientEventHandler(async (h3) => {
if (!id)
throw createError({
statusCode: 400,
statusMessage: "No ID in request query",
message: "No ID in request query",
});
const versions = await prisma.gameVersion.findMany({
const versions = await prisma.version.findMany({
where: {
gameId: id,
hidden: false,
},
orderBy: {
versionIndex: "desc", // Latest one first
},
include: {
gameVersions: true,
},
});
const mappedVersions = versions
.map((version) => {
if (!version.dropletManifest) return undefined;
const newVersion = { ...version, dropletManifest: undefined };
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore idk why we delete an undefined object
delete newVersion.dropletManifest;
return {
...newVersion,
};
})
.filter((e) => e);
return mappedVersions;
return versions;
});

View File

@ -1,5 +1,5 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import newsManager from "~/server/internal/news";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import newsManager from "~~/server/internal/news";
export default defineClientEventHandler(async (h3) => {
const id = h3.context.params?.id;

View File

@ -1,5 +1,5 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import newsManager from "~/server/internal/news";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import newsManager from "~~/server/internal/news";
export default defineClientEventHandler(async (h3) => {
const query = getQuery(h3);
@ -7,13 +7,13 @@ export default defineClientEventHandler(async (h3) => {
const orderBy = query.order as "asc" | "desc";
if (orderBy) {
if (typeof orderBy !== "string" || !["asc", "desc"].includes(orderBy))
throw createError({ statusCode: 400, statusMessage: "Invalid order" });
throw createError({ statusCode: 400, message: "Invalid order" });
}
const tags = query.tags as string[] | undefined;
if (tags) {
if (typeof tags !== "object" || !Array.isArray(tags))
throw createError({ statusCode: 400, statusMessage: "Invalid tags" });
throw createError({ statusCode: 400, message: "Invalid tags" });
}
const options = {

View File

@ -1,15 +1,15 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import objectHandler from "~/server/internal/objects";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import objectHandler from "~~/server/internal/objects";
export default defineClientEventHandler(async (h3, utils) => {
const id = getRouterParam(h3, "id");
if (!id) throw createError({ statusCode: 400, statusMessage: "Invalid ID" });
if (!id) throw createError({ statusCode: 400, message: "Invalid ID" });
const user = await utils.fetchUser();
const object = await objectHandler.fetchWithPermissions(id, user.id);
if (!object)
throw createError({ statusCode: 404, statusMessage: "Object not found" });
throw createError({ statusCode: 404, message: "Object not found" });
setHeader(h3, "Content-Type", object.mime);
return object.data;

View File

@ -1,6 +1,6 @@
import { ClientCapabilities } from "~/prisma/client/enums";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import prisma from "~/server/internal/db/database";
import { ClientCapabilities } from "~~/prisma/client/enums";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import prisma from "~~/server/internal/db/database";
export default defineClientEventHandler(
async (h3, { fetchClient, fetchUser }) => {
@ -8,27 +8,27 @@ export default defineClientEventHandler(
if (!client.capabilities.includes(ClientCapabilities.CloudSaves))
throw createError({
statusCode: 403,
statusMessage: "Capability not allowed.",
message: "Capability not allowed.",
});
const user = await fetchUser();
const gameId = getRouterParam(h3, "gameid");
if (!gameId)
throw createError({
statusCode: 400,
statusMessage: "No gameID in route params",
message: "No gameID in route params",
});
const slotIndexString = getRouterParam(h3, "slotindex");
if (!slotIndexString)
throw createError({
statusCode: 400,
statusMessage: "No slotIndex in route params",
message: "No slotIndex in route params",
});
const slotIndex = parseInt(slotIndexString);
if (Number.isNaN(slotIndex))
throw createError({
statusCode: 400,
statusMessage: "Invalid slotIndex",
message: "Invalid slotIndex",
});
const game = await prisma.game.findUnique({
@ -36,7 +36,7 @@ export default defineClientEventHandler(
select: { id: true },
});
if (!game)
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
throw createError({ statusCode: 400, message: "Invalid game ID" });
const save = await prisma.saveSlot.delete({
where: {
@ -48,6 +48,6 @@ export default defineClientEventHandler(
},
});
if (!save)
throw createError({ statusCode: 404, statusMessage: "Save not found" });
throw createError({ statusCode: 404, message: "Save not found" });
},
);

View File

@ -1,6 +1,6 @@
import { ClientCapabilities } from "~/prisma/client/enums";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import prisma from "~/server/internal/db/database";
import { ClientCapabilities } from "~~/prisma/client/enums";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import prisma from "~~/server/internal/db/database";
export default defineClientEventHandler(
async (h3, { fetchClient, fetchUser }) => {
@ -8,27 +8,27 @@ export default defineClientEventHandler(
if (!client.capabilities.includes(ClientCapabilities.CloudSaves))
throw createError({
statusCode: 403,
statusMessage: "Capability not allowed.",
message: "Capability not allowed.",
});
const user = await fetchUser();
const gameId = getRouterParam(h3, "gameid");
if (!gameId)
throw createError({
statusCode: 400,
statusMessage: "No gameID in route params",
message: "No gameID in route params",
});
const slotIndexString = getRouterParam(h3, "slotindex");
if (!slotIndexString)
throw createError({
statusCode: 400,
statusMessage: "No slotIndex in route params",
message: "No slotIndex in route params",
});
const slotIndex = parseInt(slotIndexString);
if (Number.isNaN(slotIndex))
throw createError({
statusCode: 400,
statusMessage: "Invalid slotIndex",
message: "Invalid slotIndex",
});
const game = await prisma.game.findUnique({
@ -36,7 +36,7 @@ export default defineClientEventHandler(
select: { id: true },
});
if (!game)
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
throw createError({ statusCode: 400, message: "Invalid game ID" });
const save = await prisma.saveSlot.findUnique({
where: {
@ -48,7 +48,7 @@ export default defineClientEventHandler(
},
});
if (!save)
throw createError({ statusCode: 404, statusMessage: "Save not found" });
throw createError({ statusCode: 404, message: "Save not found" });
return save;
},

View File

@ -1,7 +1,7 @@
import { ClientCapabilities } from "~/prisma/client/enums";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import prisma from "~/server/internal/db/database";
import saveManager from "~/server/internal/saves";
import { ClientCapabilities } from "~~/prisma/client/enums";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import prisma from "~~/server/internal/db/database";
import saveManager from "~~/server/internal/saves";
export default defineClientEventHandler(
async (h3, { fetchClient, fetchUser }) => {
@ -9,27 +9,27 @@ export default defineClientEventHandler(
if (!client.capabilities.includes(ClientCapabilities.CloudSaves))
throw createError({
statusCode: 403,
statusMessage: "Capability not allowed.",
message: "Capability not allowed.",
});
const user = await fetchUser();
const gameId = getRouterParam(h3, "gameid");
if (!gameId)
throw createError({
statusCode: 400,
statusMessage: "No gameID in route params",
message: "No gameID in route params",
});
const slotIndexString = getRouterParam(h3, "slotindex");
if (!slotIndexString)
throw createError({
statusCode: 400,
statusMessage: "No slotIndex in route params",
message: "No slotIndex in route params",
});
const slotIndex = parseInt(slotIndexString);
if (Number.isNaN(slotIndex))
throw createError({
statusCode: 400,
statusMessage: "Invalid slotIndex",
message: "Invalid slotIndex",
});
const game = await prisma.game.findUnique({
@ -37,7 +37,7 @@ export default defineClientEventHandler(
select: { id: true },
});
if (!game)
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
throw createError({ statusCode: 400, message: "Invalid game ID" });
await saveManager.pushSave(
gameId,

View File

@ -1,6 +1,6 @@
import { ClientCapabilities } from "~/prisma/client/enums";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import prisma from "~/server/internal/db/database";
import { ClientCapabilities } from "~~/prisma/client/enums";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import prisma from "~~/server/internal/db/database";
export default defineClientEventHandler(
async (h3, { fetchClient, fetchUser }) => {
@ -8,14 +8,14 @@ export default defineClientEventHandler(
if (!client.capabilities.includes(ClientCapabilities.CloudSaves))
throw createError({
statusCode: 403,
statusMessage: "Capability not allowed.",
message: "Capability not allowed.",
});
const user = await fetchUser();
const gameId = getRouterParam(h3, "gameid");
if (!gameId)
throw createError({
statusCode: 400,
statusMessage: "No gameID in route params",
message: "No gameID in route params",
});
const game = await prisma.game.findUnique({
@ -23,7 +23,7 @@ export default defineClientEventHandler(
select: { id: true },
});
if (!game)
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
throw createError({ statusCode: 400, message: "Invalid game ID" });
const saves = await prisma.saveSlot.findMany({
where: {

View File

@ -1,7 +1,7 @@
import { ClientCapabilities } from "~/prisma/client/enums";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import { applicationSettings } from "~/server/internal/config/application-configuration";
import prisma from "~/server/internal/db/database";
import { ClientCapabilities } from "~~/prisma/client/enums";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import { applicationSettings } from "~~/server/internal/config/application-configuration";
import prisma from "~~/server/internal/db/database";
export default defineClientEventHandler(
async (h3, { fetchClient, fetchUser }) => {
@ -9,14 +9,14 @@ export default defineClientEventHandler(
if (!client.capabilities.includes(ClientCapabilities.CloudSaves))
throw createError({
statusCode: 403,
statusMessage: "Capability not allowed.",
message: "Capability not allowed.",
});
const user = await fetchUser();
const gameId = getRouterParam(h3, "gameid");
if (!gameId)
throw createError({
statusCode: 400,
statusMessage: "No gameID in route params",
message: "No gameID in route params",
});
const game = await prisma.game.findUnique({
@ -24,7 +24,7 @@ export default defineClientEventHandler(
select: { id: true },
});
if (!game)
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
throw createError({ statusCode: 400, message: "Invalid game ID" });
const saves = await prisma.saveSlot.findMany({
where: {
@ -40,7 +40,7 @@ export default defineClientEventHandler(
if (saves.length + 1 > limit)
throw createError({
statusCode: 400,
statusMessage: "Out of save slots",
message: "Out of save slots",
});
let firstIndex = 0;

View File

@ -1,6 +1,6 @@
import { ClientCapabilities } from "~/prisma/client/enums";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import prisma from "~/server/internal/db/database";
import { ClientCapabilities } from "~~/prisma/client/enums";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import prisma from "~~/server/internal/db/database";
export default defineClientEventHandler(
async (h3, { fetchClient, fetchUser }) => {
@ -8,7 +8,7 @@ export default defineClientEventHandler(
if (!client.capabilities.includes(ClientCapabilities.CloudSaves))
throw createError({
statusCode: 403,
statusMessage: "Capability not allowed.",
message: "Capability not allowed.",
});
const user = await fetchUser();

View File

@ -1,13 +1,13 @@
import { ClientCapabilities } from "~/prisma/client/enums";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import { applicationSettings } from "~/server/internal/config/application-configuration";
import { ClientCapabilities } from "~~/prisma/client/enums";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import { applicationSettings } from "~~/server/internal/config/application-configuration";
export default defineClientEventHandler(async (_h3, { fetchClient }) => {
const client = await fetchClient();
if (!client.capabilities.includes(ClientCapabilities.CloudSaves))
throw createError({
statusCode: 403,
statusMessage: "Capability not allowed.",
message: "Capability not allowed.",
});
const slotLimit = await applicationSettings.get("saveSlotCountLimit");

View File

@ -1,4 +1,4 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
export default defineClientEventHandler(async (h3, { fetchUser }) => {
const user = await fetchUser();

View File

@ -1,5 +1,5 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
import userLibraryManager from "~~/server/internal/userlibrary";
export default defineClientEventHandler(async (_h3, { fetchUser }) => {
const user = await fetchUser();

Some files were not shown because too many files have changed in this diff Show More