feat: partial user platform support + statusMessage -> message

This commit is contained in:
DecDuck
2025-08-27 11:25:23 +10:00
parent 3af00e085e
commit 8efddc07bc
143 changed files with 831 additions and 593 deletions

View File

@ -106,7 +106,7 @@ function signin_wrapper() {
router.push(route.query.redirect?.toString() ?? "/");
})
.catch((response) => {
const message = response.statusMessage || t("errors.unknown");
const message = response.message || t("errors.unknown");
error.value = message;
})
.finally(() => {

View File

@ -466,7 +466,7 @@ const game = defineModel<ModelType>() as Ref<ModelType>;
if (!game.value)
throw createError({
statusCode: 500,
statusMessage: "Game not provided to editor component",
message: "Game not provided to editor component",
});
const currentTags = ref<{ [key: string]: boolean }>(
@ -553,7 +553,7 @@ function coreMetadataUpdate_wrapper() {
{
title: t("errors.game.metadata.title"),
description: t("errors.game.metadata.description", [
(e as H3Error)?.statusMessage ?? t("errors.unknown"),
(e as H3Error)?.message ?? t("errors.unknown"),
]),
buttonText: t("common.close"),
},
@ -614,7 +614,7 @@ watch(descriptionHTML, (_v) => {
{
title: t("errors.game.description.title"),
description: t("errors.game.description.description", [
(e as H3Error)?.statusMessage ?? t("errors.unknown"),
(e as H3Error)?.message ?? t("errors.unknown"),
]),
buttonText: t("common.close"),
},
@ -660,7 +660,7 @@ async function updateBannerImage(id: string) {
{
title: t("errors.game.banner.title"),
description: t("errors.game.banner.description", [
(e as H3Error)?.statusMessage ?? t("errors.unknown"),
(e as H3Error)?.message ?? t("errors.unknown"),
]),
buttonText: t("common.close"),
},
@ -688,7 +688,7 @@ async function updateCoverImage(id: string) {
{
title: t("errors.game.cover.title"),
description: t("errors.game.cover.description", [
(e as H3Error)?.statusMessage ?? t("errors.unknown"),
(e as H3Error)?.message ?? t("errors.unknown"),
]),
buttonText: t("common.close"),
},
@ -717,7 +717,7 @@ async function deleteImage(id: string) {
{
title: t("errors.game.deleteImage.title"),
description: t("errors.game.deleteImage.description", [
(e as H3Error)?.statusMessage ?? t("errors.unknown"),
(e as H3Error)?.message ?? t("errors.unknown"),
]),
buttonText: t("common.close"),
},
@ -761,7 +761,7 @@ async function updateImageCarousel() {
{
title: t("errors.game.carousel.title"),
description: t("errors.game.carousel.description", [
(e as H3Error)?.statusMessage ?? t("errors.unknown"),
(e as H3Error)?.message ?? t("errors.unknown"),
]),
buttonText: t("common.close"),
},

View File

@ -71,10 +71,6 @@
}}
</div>
<div class="inline-flex items-center gap-x-2">
<component
:is="PLATFORM_ICONS[item.platform]"
class="size-6 text-blue-600"
/>
<Bars3Icon
class="cursor-move w-6 h-6 text-zinc-400 handle"
/>
@ -151,7 +147,7 @@ const game = defineModel<SerializeObject<GameAndVersions>>() as Ref<
if (!game.value)
throw createError({
statusCode: 500,
statusMessage: "Game not provided to editor component",
message: "Game not provided to editor component",
});
async function updateVersionOrder() {
@ -170,7 +166,7 @@ async function updateVersionOrder() {
{
title: t("errors.version.order.title"),
description: t("errors.version.order.desc", {
error: (e as H3Error)?.statusMessage ?? t("errors.unknown"),
error: (e as H3Error)?.message ?? t("errors.unknown"),
}),
buttonText: t("common.close"),
},

View File

@ -1,3 +1,4 @@
<!-- eslint-disable vue/no-v-html -->
<template>
<div class="flex flex-row gap-x-4">
<div class="relative size-24 bg-zinc-800 rounded-md overflow-hidden">
@ -47,6 +48,83 @@
</div>
</div>
</div>
<SwitchGroup
as="div"
class="max-w-lg flex items-center justify-between gap-x-4"
>
<span class="flex flex-grow flex-col">
<SwitchLabel
as="span"
class="text-sm font-medium leading-6 text-zinc-100"
passive
>Create as platform</SwitchLabel
>
<SwitchDescription as="span" class="text-sm text-zinc-400"
>Versions for this redistributable will be able to take a series of
launch commands. Intended to be used with emulators and similar
programs.</SwitchDescription
>
</span>
<Switch
v-model="isPlatform"
:class="[
isPlatform ? 'bg-blue-600' : 'bg-zinc-800',
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2',
]"
>
<span
aria-hidden="true"
:class="[
isPlatform ? 'translate-x-5' : 'translate-x-0',
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
]"
/>
</Switch>
</SwitchGroup>
<div class="relative">
<div class="flex flex-row gap-x-4">
<div class="relative size-24 bg-zinc-800 rounded-md overflow-hidden">
<input
id="platform-icon-upload"
type="file"
class="hidden"
accept="image/svg+xml"
@change="addSvg"
/>
<div
v-if="platform.icon"
class="absolute inset-0 object-cover w-full h-full text-blue-600"
v-html="platform.icon"
/>
<label
for="platform-icon-upload"
class="absolute inset-0 cursor-pointer flex flex-col gap-y-1 items-center justify-center text-zinc-300 bg-zinc-900/50"
>
<ArrowUpTrayIcon class="size-6" />
<span class="text-xs font-bold font-display uppercase"
>Upload SVG</span
>
</label>
</div>
<div class="grow flex flex-col gap-y-4">
<div>
<label
for="platform-name"
class="block text-sm font-medium text-zinc-100"
>Platform Name</label
>
<input
id="platform-name"
v-model="platform.name"
type="text"
class="mt-1 block w-full rounded-md border-0 bg-zinc-950 py-1.5 text-white shadow-sm ring-1 ring-inset ring-zinc-700 placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
/>
</div>
</div>
</div>
<div v-if="!isPlatform" class="absolute inset-0 bg-zinc-950/20" />
</div>
<div>
<LoadingButton
class="w-fit"
@ -73,6 +151,12 @@
</template>
<script setup lang="ts">
import {
Switch,
SwitchDescription,
SwitchGroup,
SwitchLabel,
} from "@headlessui/vue";
import { ArrowUpTrayIcon } from "@heroicons/vue/24/outline";
const currentFile = ref<File | undefined>(undefined);
@ -81,11 +165,18 @@ const currentFileObjectUrl = ref<string | undefined>(undefined);
const emit = defineEmits<{
import: [
metadata: { name: string; description: string; icon: File } | undefined,
platform: typeof platform.value | undefined,
];
}>();
const name = ref("");
const description = ref("");
const isPlatform = ref(false);
const platform = ref<{ name: string; icon: string; fileExts: string[] }>({
name: "",
icon: "",
fileExts: [],
});
function addFile(event: Event) {
const file = (event.target as HTMLInputElement)?.files?.[0];
@ -99,6 +190,32 @@ function addFile(event: Event) {
currentFileObjectUrl.value = URL.createObjectURL(file);
}
async function addSvg(event: Event) {
const file = (event.target as HTMLInputElement)?.files?.[0];
if (!file) return;
const svgContent = await file.text();
const parser = new DOMParser();
try {
const document = parser.parseFromString(svgContent, "image/svg+xml");
const svg = document.getElementsByTagName("svg").item(0);
if (!svg) throw "No SVG in uploaded image.";
svg.removeAttribute("width");
svg.removeAttribute("height");
platform.value.icon = svg.outerHTML;
} catch (e) {
createModal(
ModalType.Notification,
{
title: "Failed to upload SVG",
description: (e as string)?.toString() ?? e,
},
(_, c) => c(),
);
return;
}
}
const props = defineProps<{
gameName: string;
loading: boolean;
@ -107,10 +224,14 @@ const props = defineProps<{
function importRedist() {
if (!currentFile.value) return;
emit("import", {
name: name.value,
description: description.value,
icon: currentFile.value,
});
emit(
"import",
{
name: name.value,
description: description.value,
icon: currentFile.value,
},
isPlatform.value ? platform.value : undefined,
);
}
</script>

View File

@ -231,7 +231,7 @@ async function addGame() {
emit("created", game, published.value, developed.value);
} catch (e) {
if (e instanceof FetchError) {
addError.value = e.statusMessage ?? e.message ?? t("errors.unknown");
addError.value = e.message ?? t("errors.unknown");
} else {
throw e;
}

View File

@ -97,13 +97,13 @@ async function createCollection() {
} catch (error) {
console.error("Failed to create collection:", error);
const err = error as { statusMessage?: string };
const err = error as { message?: string };
createModal(
ModalType.Notification,
{
title: t("errors.library.collection.create.title"),
description: t("errors.library.collection.create.desc", [
err?.statusMessage ?? t("errors.unknown"),
err?.message ?? t("errors.unknown"),
]),
},
(_, c) => c(),

View File

@ -67,8 +67,8 @@ async function deleteCollection() {
{
title: t("errors.library.add.title"),
description: t("errors.library.add.desc", [
// @ts-expect-error attempt to display statusMessage on error
e?.statusMessage ?? t("errors.unknown"),
// @ts-expect-error attempt to display message on error
e?.message ?? t("errors.unknown"),
]),
},
(_, c) => c(),

View File

@ -71,8 +71,8 @@ async function deleteArticle() {
{
title: t("errors.news.article.delete.title"),
description: t("errors.news.article.delete.desc", [
// @ts-expect-error attempt to display statusMessage on error
e?.statusMessage ?? t("errors.unknown"),
// @ts-expect-error attempt to display message on error
e?.message ?? t("errors.unknown"),
]),
},
(_, c) => c(),

View File

@ -62,8 +62,8 @@ async function deleteUser() {
{
title: t("errors.admin.user.delete.title"),
description: t("errors.admin.user.delete.desc", [
// @ts-expect-error attempt to display statusMessage on error
e?.statusMessage ?? t("errors.unknown"),
// @ts-expect-error attempt to display message on error
e?.message ?? t("errors.unknown"),
]),
},
(_, c) => c(),

View File

@ -177,7 +177,7 @@ function uploadFile_wrapper() {
uploadLoading.value = true;
uploadFile()
.catch((error) => {
uploadError.value = error.statusMessage ?? t("errors.unknown");
uploadError.value = error.message ?? t("errors.unknown");
})
.finally(() => {
uploadLoading.value = false;

View File

@ -414,8 +414,8 @@ async function createArticle() {
modalOpen.value = false;
} catch (e) {
// @ts-expect-error attempt to get statusMessage on error
error.value = e?.statusMessage ?? t("errors.unknown");
// @ts-expect-error attempt to get message on error
error.value = e?.message ?? t("errors.unknown");
} finally {
loading.value = false;
}

View File

@ -377,6 +377,8 @@ const props = defineProps<{
const tags =
await $dropFetch<Array<SerializeObject<GameTagModel>>>("/api/v1/store/tags");
const userPlatforms = await $dropFetch("/api/v1/store/platforms");
const sorts: Array<StoreSortOption> = [
{
name: "Default",
@ -408,7 +410,10 @@ const options: Array<StoreFilterOption> = [
name: "Platform",
param: "platform",
multiple: true,
options: Object.values(Platform).map((e) => ({ name: e, param: e })),
options: [
...Object.values(Platform).map((e) => ({ name: e, param: e })),
...userPlatforms.map((e) => ({ name: e.platformName, param: e.id })),
],
},
...(props.extraOptions ?? []),
];

View File

@ -60,7 +60,7 @@ export const $dropFetch: DropFetch = async (rawRequest, opts) => {
{
title: opts.failTitle,
description:
(e as FetchError)?.statusMessage ?? (e as string).toString(),
(e as FetchError)?.message ?? (e as string).toString(),
//buttonText: $t("common.close"),
},
(_, c) => c(),

View File

@ -33,7 +33,7 @@ export class WebSocketHandler {
case "unauthenticated": {
const error = createError({
statusCode: 403,
statusMessage: "Unable to connect to websocket - unauthenticated",
message: "Unable to connect to websocket - unauthenticated",
});
if (this.errorHandler) {
return this.errorHandler(error);

View File

@ -40,6 +40,7 @@
"fast-fuzzy": "^1.12.0",
"file-type-mime": "^0.4.3",
"jdenticon": "^3.3.0",
"jsdom": "^26.1.0",
"luxon": "^3.6.1",
"micromark": "^4.0.1",
"normalize-url": "^8.0.2",
@ -66,6 +67,7 @@
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@types/bcryptjs": "^3.0.0",
"@types/jsdom": "^21.1.7",
"@types/luxon": "^3.6.2",
"@types/node": "^22.13.16",
"@types/semver": "^7.7.0",

View File

@ -180,7 +180,7 @@ if (route.query.payload) {
} catch {
throw createError({
statusCode: 400,
statusMessage: "Failed to parse the token create payload.",
message: "Failed to parse the token create payload.",
fatal: true,
});
}

View File

@ -727,7 +727,7 @@ function startImport_wrapper() {
importLoading.value = true;
startImport()
.catch((error) => {
importError.value = error.statusMessage ?? t("errors.unknown");
importError.value = error.message ?? t("errors.unknown");
})
.finally(() => {
importLoading.value = false;

View File

@ -202,7 +202,7 @@
:game-name="games.unimportedGames[currentlySelectedGame].game"
:loading="importLoading"
:error="importError"
@import="(v: unknown) => importModes[currentImportMode].import(v)"
@import="(...v: unknown[]) => importModes[currentImportMode].import(...v)"
/>
</div>
</template>
@ -233,7 +233,7 @@ const importModes = shallowRef<{
description: string;
icon: Component;
component: Component;
import: (v: unknown) => void;
import: (...v: unknown[]) => void;
};
}>({
Game: {
@ -249,7 +249,7 @@ const importModes = shallowRef<{
"Redistributables are packaged dependencies for games, that are installed alongside and required to play certain games.",
icon: ArchiveBoxIcon,
component: ImportRedist,
import: importRedist as (v: unknown) => void,
import: importRedist as (v: unknown, k: unknown) => void,
},
});
@ -303,12 +303,12 @@ async function importGame_wrapper(
} catch (error) {
console.warn(error);
importError.value =
(error as FetchError)?.statusMessage || t("errors.unknown");
(error as FetchError)?.message || t("errors.unknown");
}
importLoading.value = false;
}
async function importRedist(data: object) {
async function importRedist(data: object, platform: object | undefined) {
importLoading.value = true;
importError.value = undefined;
try {
@ -316,7 +316,19 @@ async function importRedist(data: object) {
const formData = new FormData();
for (const [key, value] of Object.entries(data)) {
formData.append(key, value);
formData.append(
key,
typeof value === "object" ? JSON.stringify(value) : value,
);
}
if (platform) {
for (const [key, value] of Object.entries(platform)) {
formData.append(
`platform.${key}`,
typeof value === "object" ? JSON.stringify(value) : value,
);
}
}
formData.append("library", option.library.id);
@ -335,7 +347,7 @@ async function importRedist(data: object) {
} catch (error) {
console.warn(error);
importError.value =
(error as FetchError)?.statusMessage || t("errors.unknown");
(error as FetchError)?.message || t("errors.unknown");
}
importLoading.value = false;
}

View File

@ -412,7 +412,7 @@ function performActionSource_wrapper() {
})
.catch((e) => {
if (e instanceof FetchError) {
modalError.value = e.statusMessage ?? e.message;
modalError.value = e.message ?? e.message;
} else {
modalError.value = e as string;
}
@ -449,8 +449,8 @@ async function deleteSource(index: number) {
{
title: t("errors.library.source.delete.title"),
description: t("errors.library.source.delete.desc", [
// @ts-expect-error attempt to display statusMessage on error
e?.statusMessage ?? t("errors.unknown"),
// @ts-expect-error attempt to display message on error
e?.message ?? t("errors.unknown"),
]),
},
(_, c) => c(),

View File

@ -96,7 +96,7 @@ async function saveSettings() {
title: `Failed to save settings.`,
description:
e instanceof FetchError
? (e.statusMessage ?? e.message)
? (e.message)
: (e as string).toString(),
},
(_, c) => c(),

View File

@ -184,7 +184,7 @@ if (route.query.payload) {
} catch {
throw createError({
statusCode: 400,
statusMessage: "Failed to parse the token create payload.",
message: "Failed to parse the token create payload.",
fatal: true,
});
}

View File

@ -502,7 +502,7 @@ function invite_wrapper() {
invitations.value.push(invitation);
})
.catch((response) => {
const message = response.statusMessage || t("errors.unknown");
const message = response.message || t("errors.unknown");
error.value = message;
})
.finally(() => {

View File

@ -203,7 +203,7 @@ const invitationId = route.query.id?.toString();
if (!invitationId)
throw createError({
statusCode: 400,
statusMessage: t("errors.inviteRequired"),
message: t("errors.inviteRequired"),
});
const invitation = await $dropFetch(
@ -271,7 +271,7 @@ function register_wrapper() {
router.push("/auth/signin");
})
.catch((response) => {
const message = response.statusMessage || t("errors.unknown");
const message = response.message || t("errors.unknown");
error.value = message;
})
.finally(() => {

View File

@ -192,7 +192,7 @@ async function authorize() {
}
throw createError({
statusCode: 500,
statusMessage: "Unknown client auth mode: " + clientData.mode,
message: "Unknown client auth mode: " + clientData.mode,
fatal: true,
});
}
@ -202,7 +202,7 @@ async function authorize_wrapper() {
await authorize();
} catch (e) {
const errorMessage =
(e as FetchError)?.statusMessage || "An unknown error occurred.";
(e as FetchError)?.message || "An unknown error occurred.";
error.value = errorMessage;
} finally {
completed.value = true;

View File

@ -125,7 +125,7 @@ async function complete(code: string) {
} catch (e) {
if (e instanceof FetchError) {
error.value =
e.statusMessage ?? e.message ?? "An unknown error occurred.";
e.message ?? "An unknown error occurred.";
} else {
error.value = (e as string)?.toString();
}

View File

@ -44,7 +44,8 @@ const collection = computed(() =>
if (collection.value === undefined) {
throw createError({
statusCode: 404,
statusMessage: t("library.collection.notFound"),
message: t("library.collection.notFound"),
fatal: true,
});
}

View File

@ -99,7 +99,7 @@ const article = computed(() =>
if (!article.value)
throw createError({
statusCode: 404,
statusMessage: t("news.notFound"),
message: t("news.notFound"),
fatal: true,
});

View File

@ -159,7 +159,7 @@ const token = route.query.token;
if (!token)
throw createError({
statusCode: 400,
statusMessage: "No token.",
message: "No token.",
fatal: true,
});
const bearerToken = `Bearer ${token}`;
@ -170,7 +170,7 @@ const allowed = await $dropFetch("/api/v1/admin", {
if (!allowed)
throw createError({
statusCode: 400,
statusMessage: "Invalid setup token. Please check the URL you opened.",
message: "Invalid setup token. Please check the URL you opened.",
fatal: true,
});

View File

@ -73,15 +73,21 @@
<td
class="whitespace-nowrap inline-flex gap-x-4 px-3 py-4 text-sm text-zinc-400"
>
<component
:is="PLATFORM_ICONS[platform]"
<div
v-for="platform in platforms"
:key="platform"
class="text-blue-600 w-6 h-6"
/>
:key="typeof platform === 'string' ? platform : platform.id"
>
<component
:is="PLATFORM_ICONS[platform]"
v-if="typeof platform === 'string'"
class="text-blue-600 w-6 h-6"
/>
<div v-else class="text-blue-600 w-6 h-6" v-html="platform.iconSvg" />
</div>
<span
v-if="platforms.length == 0"
class="font-semibold text-blue-600"
class="font-display uppercase font-bold text-zinc-700"
>{{ $t("store.commingSoon") }}</span
>
</td>
@ -278,8 +284,9 @@ const descriptionHTML = micromark(game.mDescription);
const showReadMore = previewHTML != descriptionHTML;
const platforms = game.versions
.map((e) => e.platform)
.map((e) => e.platform ?? e.userPlatform)
.flat()
.filter((e) => e !== null)
.filter((e, i, u) => u.indexOf(e) === i);
// const rating = Math.round(game.mReviewRating * 5);

View File

@ -1,35 +0,0 @@
-- DropForeignKey
ALTER TABLE "public"."Version" DROP CONSTRAINT "addon_link";
-- DropForeignKey
ALTER TABLE "public"."Version" DROP CONSTRAINT "game_link";
-- DropForeignKey
ALTER TABLE "public"."Version" DROP CONSTRAINT "mod_link";
-- DropForeignKey
ALTER TABLE "public"."Version" DROP CONSTRAINT "redist_link";
-- DropIndex
DROP INDEX "public"."GameTag_name_idx";
-- AlterTable
ALTER TABLE "public"."GameVersion" ADD COLUMN "setupArgs" TEXT NOT NULL DEFAULT '';
-- AlterTable
ALTER TABLE "public"."GameVersionLaunch" ADD COLUMN "launchArgs" TEXT NOT NULL DEFAULT '';
-- CreateIndex
CREATE INDEX "GameTag_name_idx" ON "public"."GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
-- AddForeignKey
ALTER TABLE "public"."Version" ADD CONSTRAINT "game_link" FOREIGN KEY ("rootId") REFERENCES "public"."Game"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Version" ADD CONSTRAINT "redist_link" FOREIGN KEY ("rootId") REFERENCES "public"."Redist"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Version" ADD CONSTRAINT "addon_link" FOREIGN KEY ("rootId") REFERENCES "public"."Addon"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Version" ADD CONSTRAINT "mod_link" FOREIGN KEY ("rootId") REFERENCES "public"."Mod"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -1,42 +0,0 @@
/*
Warnings:
- You are about to drop the column `rootId` on the `Version` table. All the data in the column will be lost.
*/
-- DropForeignKey
ALTER TABLE "public"."Version" DROP CONSTRAINT "addon_link";
-- DropForeignKey
ALTER TABLE "public"."Version" DROP CONSTRAINT "game_link";
-- DropForeignKey
ALTER TABLE "public"."Version" DROP CONSTRAINT "mod_link";
-- DropForeignKey
ALTER TABLE "public"."Version" DROP CONSTRAINT "redist_link";
-- DropIndex
DROP INDEX "public"."GameTag_name_idx";
-- AlterTable
ALTER TABLE "public"."Version" DROP COLUMN "rootId",
ADD COLUMN "addonId" TEXT,
ADD COLUMN "gameId" TEXT,
ADD COLUMN "modId" TEXT,
ADD COLUMN "redistId" TEXT;
-- CreateIndex
CREATE INDEX "GameTag_name_idx" ON "public"."GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
-- AddForeignKey
ALTER TABLE "public"."Version" ADD CONSTRAINT "game_link" FOREIGN KEY ("gameId") REFERENCES "public"."Game"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Version" ADD CONSTRAINT "redist_link" FOREIGN KEY ("redistId") REFERENCES "public"."Redist"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Version" ADD CONSTRAINT "addon_link" FOREIGN KEY ("addonId") REFERENCES "public"."Addon"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Version" ADD CONSTRAINT "mod_link" FOREIGN KEY ("modId") REFERENCES "public"."Mod"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -1,35 +0,0 @@
-- DropForeignKey
ALTER TABLE "public"."AddonVersion" DROP CONSTRAINT "AddonVersion_versionId_fkey";
-- DropForeignKey
ALTER TABLE "public"."GameVersion" DROP CONSTRAINT "GameVersion_versionId_fkey";
-- DropForeignKey
ALTER TABLE "public"."GameVersionLaunch" DROP CONSTRAINT "GameVersionLaunch_versionId_fkey";
-- DropForeignKey
ALTER TABLE "public"."ModVersion" DROP CONSTRAINT "ModVersion_versionId_fkey";
-- DropForeignKey
ALTER TABLE "public"."RedistVersion" DROP CONSTRAINT "RedistVersion_versionId_fkey";
-- DropIndex
DROP INDEX "public"."GameTag_name_idx";
-- CreateIndex
CREATE INDEX "GameTag_name_idx" ON "public"."GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
-- AddForeignKey
ALTER TABLE "public"."GameVersion" ADD CONSTRAINT "GameVersion_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."Version"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."GameVersionLaunch" ADD CONSTRAINT "GameVersionLaunch_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."GameVersion"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."AddonVersion" ADD CONSTRAINT "AddonVersion_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."Version"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."RedistVersion" ADD CONSTRAINT "RedistVersion_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."Version"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."ModVersion" ADD CONSTRAINT "ModVersion_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."Version"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -1,8 +0,0 @@
-- DropIndex
DROP INDEX "public"."GameTag_name_idx";
-- AlterTable
ALTER TABLE "public"."Library" ADD COLUMN "mode" "public"."LibraryMode" NOT NULL DEFAULT 'Game';
-- CreateIndex
CREATE INDEX "GameTag_name_idx" ON "public"."GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));

View File

@ -1,99 +0,0 @@
/*
Warnings:
- The values [Addon] on the enum `LibraryMode` will be removed. If these variants are still used in the database, this will fail.
- You are about to drop the column `addonId` on the `Version` table. All the data in the column will be lost.
- You are about to drop the `Addon` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `AddonVersion` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `_AddonVersionToRedistVersion` table. If the table is not empty, all the data it contains will be lost.
*/
-- AlterEnum
BEGIN;
CREATE TYPE "public"."LibraryMode_new" AS ENUM ('Game', 'Redist', 'DLC', 'Mod');
ALTER TABLE "public"."Library" ALTER COLUMN "mode" DROP DEFAULT;
ALTER TABLE "public"."Library" ALTER COLUMN "mode" TYPE "public"."LibraryMode_new" USING ("mode"::text::"public"."LibraryMode_new");
ALTER TYPE "public"."LibraryMode" RENAME TO "LibraryMode_old";
ALTER TYPE "public"."LibraryMode_new" RENAME TO "LibraryMode";
DROP TYPE "public"."LibraryMode_old";
ALTER TABLE "public"."Library" ALTER COLUMN "mode" SET DEFAULT 'Game';
COMMIT;
-- DropForeignKey
ALTER TABLE "public"."Addon" DROP CONSTRAINT "Addon_libraryId_fkey";
-- DropForeignKey
ALTER TABLE "public"."AddonVersion" DROP CONSTRAINT "AddonVersion_versionId_fkey";
-- DropForeignKey
ALTER TABLE "public"."Version" DROP CONSTRAINT "addon_link";
-- DropForeignKey
ALTER TABLE "public"."_AddonVersionToRedistVersion" DROP CONSTRAINT "_AddonVersionToRedistVersion_A_fkey";
-- DropForeignKey
ALTER TABLE "public"."_AddonVersionToRedistVersion" DROP CONSTRAINT "_AddonVersionToRedistVersion_B_fkey";
-- DropIndex
DROP INDEX "public"."GameTag_name_idx";
-- AlterTable
ALTER TABLE "public"."Version" DROP COLUMN "addonId",
ADD COLUMN "dlcId" TEXT;
-- DropTable
DROP TABLE "public"."Addon";
-- DropTable
DROP TABLE "public"."AddonVersion";
-- DropTable
DROP TABLE "public"."_AddonVersionToRedistVersion";
-- CreateTable
CREATE TABLE "public"."DLCVersion" (
"versionId" TEXT NOT NULL,
CONSTRAINT "DLCVersion_pkey" PRIMARY KEY ("versionId")
);
-- CreateTable
CREATE TABLE "public"."DLC" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT NOT NULL,
"iconObjectId" TEXT NOT NULL,
"libraryId" TEXT NOT NULL,
"libraryPath" TEXT NOT NULL,
CONSTRAINT "DLC_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "public"."_DLCVersionToRedistVersion" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_DLCVersionToRedistVersion_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateIndex
CREATE INDEX "_DLCVersionToRedistVersion_B_index" ON "public"."_DLCVersionToRedistVersion"("B");
-- CreateIndex
CREATE INDEX "GameTag_name_idx" ON "public"."GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
-- AddForeignKey
ALTER TABLE "public"."Version" ADD CONSTRAINT "dlc_link" FOREIGN KEY ("dlcId") REFERENCES "public"."DLC"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."DLCVersion" ADD CONSTRAINT "DLCVersion_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."Version"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."DLC" ADD CONSTRAINT "DLC_libraryId_fkey" FOREIGN KEY ("libraryId") REFERENCES "public"."Library"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."_DLCVersionToRedistVersion" ADD CONSTRAINT "_DLCVersionToRedistVersion_A_fkey" FOREIGN KEY ("A") REFERENCES "public"."DLCVersion"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."_DLCVersionToRedistVersion" ADD CONSTRAINT "_DLCVersionToRedistVersion_B_fkey" FOREIGN KEY ("B") REFERENCES "public"."RedistVersion"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -1,8 +0,0 @@
-- DropIndex
DROP INDEX "GameTag_name_idx";
-- AlterTable
ALTER TABLE "APIToken" ADD COLUMN "expiresAt" TIMESTAMP(3);
-- CreateIndex
CREATE INDEX "GameTag_name_idx" ON "GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));

View File

@ -1,5 +0,0 @@
-- DropIndex
DROP INDEX "GameTag_name_idx";
-- CreateIndex
CREATE INDEX "GameTag_name_idx" ON "GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));

View File

@ -1,4 +1,4 @@
-- Add pg_trgm
-- enable pg_trgm
CREATE EXTENSION pg_trgm;
-- CreateEnum
@ -8,7 +8,7 @@ CREATE TYPE "public"."Platform" AS ENUM ('windows', 'linux', 'macos');
CREATE TYPE "public"."LibraryBackend" AS ENUM ('Filesystem', 'FlatFilesystem');
-- CreateEnum
CREATE TYPE "public"."LibraryMode" AS ENUM ('Game', 'Redist', 'Addon', 'Mod');
CREATE TYPE "public"."LibraryMode" AS ENUM ('Game', 'Redist', 'DLC', 'Mod');
-- CreateEnum
CREATE TYPE "public"."AuthMec" AS ENUM ('Simple', 'OpenID');
@ -40,6 +40,7 @@ CREATE TABLE "public"."Library" (
"name" TEXT NOT NULL,
"backend" "public"."LibraryBackend" NOT NULL,
"options" JSONB NOT NULL,
"mode" "public"."LibraryMode" NOT NULL DEFAULT 'Game',
CONSTRAINT "Library_pkey" PRIMARY KEY ("id")
);
@ -75,6 +76,7 @@ CREATE TABLE "public"."APIToken" (
"userId" TEXT,
"clientId" TEXT,
"acls" TEXT[],
"expiresAt" TIMESTAMP(3),
CONSTRAINT "APIToken_pkey" PRIMARY KEY ("id")
);
@ -129,14 +131,28 @@ CREATE TABLE "public"."CollectionEntry" (
CONSTRAINT "CollectionEntry_pkey" PRIMARY KEY ("collectionId","gameId")
);
-- CreateTable
CREATE TABLE "public"."UserPlatform" (
"id" TEXT NOT NULL,
"redistId" TEXT,
"platformName" TEXT NOT NULL,
"iconSvg" TEXT NOT NULL,
CONSTRAINT "UserPlatform_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "public"."Version" (
"versionId" TEXT NOT NULL,
"rootId" TEXT NOT NULL,
"versionPath" TEXT NOT NULL,
"versionName" TEXT NOT NULL,
"created" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"platform" "public"."Platform" NOT NULL,
"gameId" TEXT,
"redistId" TEXT,
"dlcId" TEXT,
"modId" TEXT,
"platform" "public"."Platform",
"userPlatformRedistId" TEXT,
"dropletManifest" JSONB NOT NULL,
CONSTRAINT "Version_pkey" PRIMARY KEY ("versionId")
@ -145,31 +161,34 @@ CREATE TABLE "public"."Version" (
-- CreateTable
CREATE TABLE "public"."GameVersion" (
"versionId" TEXT NOT NULL,
"setup" TEXT NOT NULL DEFAULT '',
"setupCommand" TEXT NOT NULL DEFAULT '',
"setupArgs" TEXT NOT NULL DEFAULT '',
"onlySetup" BOOLEAN NOT NULL DEFAULT false,
"umuIdOverride" TEXT,
"versionIndex" INTEGER NOT NULL,
"delta" BOOLEAN NOT NULL DEFAULT false,
"hidden" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "GameVersion_pkey" PRIMARY KEY ("versionId")
);
-- CreateTable
CREATE TABLE "public"."GameVersionLaunch" (
CREATE TABLE "public"."LaunchOption" (
"launchId" TEXT NOT NULL,
"versionId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT NOT NULL,
"launchCommand" TEXT NOT NULL,
"launchArgs" TEXT NOT NULL DEFAULT '',
CONSTRAINT "GameVersionLaunch_pkey" PRIMARY KEY ("launchId")
CONSTRAINT "LaunchOption_pkey" PRIMARY KEY ("launchId")
);
-- CreateTable
CREATE TABLE "public"."AddonVersion" (
CREATE TABLE "public"."DLCVersion" (
"versionId" TEXT NOT NULL,
CONSTRAINT "AddonVersion_pkey" PRIMARY KEY ("versionId")
CONSTRAINT "DLCVersion_pkey" PRIMARY KEY ("versionId")
);
-- CreateTable
@ -255,7 +274,7 @@ CREATE TABLE "public"."Game" (
);
-- CreateTable
CREATE TABLE "public"."Addon" (
CREATE TABLE "public"."DLC" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT NOT NULL,
@ -263,7 +282,7 @@ CREATE TABLE "public"."Addon" (
"libraryId" TEXT NOT NULL,
"libraryPath" TEXT NOT NULL,
CONSTRAINT "Addon_pkey" PRIMARY KEY ("id")
CONSTRAINT "DLC_pkey" PRIMARY KEY ("id")
);
-- CreateTable
@ -411,11 +430,11 @@ CREATE TABLE "public"."_GameVersionToRedistVersion" (
);
-- CreateTable
CREATE TABLE "public"."_AddonVersionToRedistVersion" (
CREATE TABLE "public"."_DLCVersionToRedistVersion" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL,
CONSTRAINT "_AddonVersionToRedistVersion_AB_pkey" PRIMARY KEY ("A","B")
CONSTRAINT "_DLCVersionToRedistVersion_AB_pkey" PRIMARY KEY ("A","B")
);
-- CreateTable
@ -464,6 +483,9 @@ CREATE UNIQUE INDEX "APIToken_token_key" ON "public"."APIToken"("token");
-- CreateIndex
CREATE INDEX "APIToken_token_idx" ON "public"."APIToken"("token");
-- CreateIndex
CREATE UNIQUE INDEX "UserPlatform_redistId_key" ON "public"."UserPlatform"("redistId");
-- CreateIndex
CREATE UNIQUE INDEX "Version_versionId_key" ON "public"."Version"("versionId");
@ -510,7 +532,7 @@ CREATE UNIQUE INDEX "Notification_userId_nonce_key" ON "public"."Notification"("
CREATE INDEX "_GameVersionToRedistVersion_B_index" ON "public"."_GameVersionToRedistVersion"("B");
-- CreateIndex
CREATE INDEX "_AddonVersionToRedistVersion_B_index" ON "public"."_AddonVersionToRedistVersion"("B");
CREATE INDEX "_DLCVersionToRedistVersion_B_index" ON "public"."_DLCVersionToRedistVersion"("B");
-- CreateIndex
CREATE INDEX "_GameToGameTag_B_index" ON "public"."_GameToGameTag"("B");
@ -552,31 +574,40 @@ ALTER TABLE "public"."CollectionEntry" ADD CONSTRAINT "CollectionEntry_collectio
ALTER TABLE "public"."CollectionEntry" ADD CONSTRAINT "CollectionEntry_gameId_fkey" FOREIGN KEY ("gameId") REFERENCES "public"."Game"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Version" ADD CONSTRAINT "game_link" FOREIGN KEY ("rootId") REFERENCES "public"."Game"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "public"."UserPlatform" ADD CONSTRAINT "UserPlatform_redistId_fkey" FOREIGN KEY ("redistId") REFERENCES "public"."Redist"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Version" ADD CONSTRAINT "redist_link" FOREIGN KEY ("rootId") REFERENCES "public"."Redist"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "public"."Version" ADD CONSTRAINT "game_link" FOREIGN KEY ("gameId") REFERENCES "public"."Game"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Version" ADD CONSTRAINT "addon_link" FOREIGN KEY ("rootId") REFERENCES "public"."Addon"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "public"."Version" ADD CONSTRAINT "redist_link" FOREIGN KEY ("redistId") REFERENCES "public"."Redist"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Version" ADD CONSTRAINT "mod_link" FOREIGN KEY ("rootId") REFERENCES "public"."Mod"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "public"."Version" ADD CONSTRAINT "dlc_link" FOREIGN KEY ("dlcId") REFERENCES "public"."DLC"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."GameVersion" ADD CONSTRAINT "GameVersion_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."Version"("versionId") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "public"."Version" ADD CONSTRAINT "mod_link" FOREIGN KEY ("modId") REFERENCES "public"."Mod"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."GameVersionLaunch" ADD CONSTRAINT "GameVersionLaunch_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."GameVersion"("versionId") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "public"."Version" ADD CONSTRAINT "Version_userPlatformRedistId_fkey" FOREIGN KEY ("userPlatformRedistId") REFERENCES "public"."UserPlatform"("redistId") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."AddonVersion" ADD CONSTRAINT "AddonVersion_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."Version"("versionId") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "public"."GameVersion" ADD CONSTRAINT "GameVersion_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."Version"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."RedistVersion" ADD CONSTRAINT "RedistVersion_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."Version"("versionId") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "public"."LaunchOption" ADD CONSTRAINT "gameVersion_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."GameVersion"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."ModVersion" ADD CONSTRAINT "ModVersion_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."Version"("versionId") ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE "public"."LaunchOption" ADD CONSTRAINT "redistVersion_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."RedistVersion"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."DLCVersion" ADD CONSTRAINT "DLCVersion_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."Version"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."RedistVersion" ADD CONSTRAINT "RedistVersion_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."Version"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."ModVersion" ADD CONSTRAINT "ModVersion_versionId_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."Version"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."SaveSlot" ADD CONSTRAINT "SaveSlot_gameId_fkey" FOREIGN KEY ("gameId") REFERENCES "public"."Game"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@ -603,7 +634,7 @@ ALTER TABLE "public"."Playtime" ADD CONSTRAINT "Playtime_userId_fkey" FOREIGN KE
ALTER TABLE "public"."Game" ADD CONSTRAINT "Game_libraryId_fkey" FOREIGN KEY ("libraryId") REFERENCES "public"."Library"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Addon" ADD CONSTRAINT "Addon_libraryId_fkey" FOREIGN KEY ("libraryId") REFERENCES "public"."Library"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "public"."DLC" ADD CONSTRAINT "DLC_libraryId_fkey" FOREIGN KEY ("libraryId") REFERENCES "public"."Library"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."Redist" ADD CONSTRAINT "Redist_libraryId_fkey" FOREIGN KEY ("libraryId") REFERENCES "public"."Library"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@ -630,10 +661,10 @@ ALTER TABLE "public"."_GameVersionToRedistVersion" ADD CONSTRAINT "_GameVersionT
ALTER TABLE "public"."_GameVersionToRedistVersion" ADD CONSTRAINT "_GameVersionToRedistVersion_B_fkey" FOREIGN KEY ("B") REFERENCES "public"."RedistVersion"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."_AddonVersionToRedistVersion" ADD CONSTRAINT "_AddonVersionToRedistVersion_A_fkey" FOREIGN KEY ("A") REFERENCES "public"."AddonVersion"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "public"."_DLCVersionToRedistVersion" ADD CONSTRAINT "_DLCVersionToRedistVersion_A_fkey" FOREIGN KEY ("A") REFERENCES "public"."DLCVersion"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."_AddonVersionToRedistVersion" ADD CONSTRAINT "_AddonVersionToRedistVersion_B_fkey" FOREIGN KEY ("B") REFERENCES "public"."RedistVersion"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "public"."_DLCVersionToRedistVersion" ADD CONSTRAINT "_DLCVersionToRedistVersion_B_fkey" FOREIGN KEY ("B") REFERENCES "public"."RedistVersion"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."_GameToGameTag" ADD CONSTRAINT "_GameToGameTag_A_fkey" FOREIGN KEY ("A") REFERENCES "public"."Game"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@ -2,7 +2,7 @@
DROP INDEX "public"."GameTag_name_idx";
-- AlterTable
ALTER TABLE "public"."GameVersion" ADD COLUMN "hidden" BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE "public"."UserPlatform" ADD COLUMN "fileExtensions" TEXT[] DEFAULT ARRAY[]::TEXT[];
-- CreateIndex
CREATE INDEX "GameTag_name_idx" ON "public"."GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));

View File

@ -1,3 +1,16 @@
model UserPlatform {
id String @id @default(uuid())
redistId String? @unique
redist Redist? @relation(fields: [redistId], references: [id], onDelete: Cascade, onUpdate: Cascade)
platformName String
iconSvg String
fileExtensions String[] @default([])
versions Version[]
}
model Version {
versionId String @id @unique @default(uuid())
@ -22,7 +35,11 @@ model Version {
mod Mod? @relation(fields: [modId], references: [id], map: "mod_link", onDelete: Cascade, onUpdate: Cascade)
modVersion ModVersion?
platform Platform
platform Platform?
userPlatformRedistId String?
userPlatform UserPlatform? @relation(fields: [userPlatformRedistId], references: [redistId])
dropletManifest Json // Results from droplet
}
@ -32,11 +49,11 @@ model GameVersion {
redistDeps RedistVersion[]
launches GameVersionLaunch[]
launches LaunchOption[]
setup String @default("") // Command to setup game (dependencies and such)
setupArgs String @default("")
onlySetup Boolean @default(false)
setupCommand String @default("")
setupArgs String @default("")
onlySetup Boolean @default(false)
umuIdOverride String?
@ -45,11 +62,12 @@ model GameVersion {
hidden Boolean @default(false)
}
model GameVersionLaunch {
model LaunchOption {
launchId String @id @default(uuid())
versionId String
gameVersion GameVersion @relation(fields: [versionId], references: [versionId], onDelete: Cascade, onUpdate: Cascade)
gameVersion GameVersion? @relation(fields: [versionId], references: [versionId], onDelete: Cascade, onUpdate: Cascade, map: "gameVersion_fkey")
redistVersion RedistVersion? @relation(fields: [versionId], references: [versionId], onDelete: Cascade, onUpdate: Cascade, map: "redistVersion_fkey")
name String
description String
@ -69,8 +87,10 @@ model RedistVersion {
versionId String @id
version Version @relation(fields: [versionId], references: [versionId], onDelete: Cascade, onUpdate: Cascade)
gameDependees GameVersion[]
dlcDependees DLCVersion[]
launches LaunchOption[]
gameDependees GameVersion[]
dlcDependees DLCVersion[]
}
model ModVersion {

View File

@ -79,7 +79,8 @@ model Redist {
library Library @relation(fields: [libraryId], references: [id], onDelete: Cascade, onUpdate: Cascade)
libraryPath String
versions Version[]
versions Version[]
platform UserPlatform?
@@unique([libraryId, libraryPath], name: "libraryKey")
}

View File

@ -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

@ -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

@ -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

@ -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

@ -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

@ -33,7 +33,7 @@ export default defineEventHandler(async (h3) => {
});
if (!game || !game.libraryId)
throw createError({ statusCode: 404, statusMessage: "Game ID not found" });
throw createError({ statusCode: 404, message: "Game ID not found" });
const unimportedVersions = await libraryManager.fetchUnimportedGameVersions(
game.libraryId,

View File

@ -1,8 +1,14 @@
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"]);
if (!allowed) throw createError({ statusCode: 403 });
@ -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

@ -32,11 +32,11 @@ 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);

View File

@ -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

@ -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

@ -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

@ -4,6 +4,7 @@ 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",
@ -11,6 +12,12 @@ export const ImportRedist = type({
name: "string",
description: "string",
"platform?": type({
name: "string",
icon: "string",
fileExts: type("string").pipe.try((s) => JSON.parse(s), type("string[]")),
}),
});
export default defineEventHandler(async (h3) => {
@ -18,14 +25,13 @@ export default defineEventHandler(async (h3) => {
if (!allowed) throw createError({ statusCode: 403 });
const body = await handleFileUpload(h3, {}, ["internal:read"], 1);
if (!body)
throw createError({ statusCode: 400, statusMessage: "Body required." });
if (!body) throw createError({ statusCode: 400, message: "Body required." });
const [[id], rawOptions, pull,, add] = body;
const [[id], rawOptions, pull, , add] = body;
const options = ImportRedist(rawOptions);
if (options instanceof ArkErrors)
throw createError({ statusCode: 400, statusMessage: options.summary });
throw createError({ statusCode: 400, message: options.summary });
const valid = await libraryManager.checkUnimportedGamePath(
options.library,
@ -34,11 +40,25 @@ export default defineEventHandler(async (h3) => {
if (!valid)
throw createError({
statusCode: 400,
statusMessage: "Invalid library or game.",
message: "Invalid library or game.",
});
const icon = id ?? add(jdenticon.toPng(options.name, 512));
let svgContent = "";
if (options.platform) {
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,
@ -47,6 +67,18 @@ export default defineEventHandler(async (h3) => {
mName: options.name,
mShortDescription: options.description,
mIconObjectId: icon,
platform: {
...(options.platform
? {
create: {
platformName: options.platform.name,
iconSvg: svgContent,
fileExtensions: options.platform.fileExts,
},
}
: undefined),
},
},
});

View File

@ -11,7 +11,7 @@ export default defineEventHandler(async (h3) => {
if (!gameId)
throw createError({
statusCode: 400,
statusMessage: "Missing id in request params",
message: "Missing id in request params",
});
const game = await prisma.game.findUnique({
@ -19,14 +19,14 @@ export default defineEventHandler(async (h3) => {
select: { libraryId: true, libraryPath: true },
});
if (!game || !game.libraryId)
throw createError({ statusCode: 404, statusMessage: "Game not found" });
throw createError({ statusCode: 404, message: "Game not found" });
const unimportedVersions = await libraryManager.fetchUnimportedGameVersions(
game.libraryId,
game.libraryPath,
);
if (!unimportedVersions)
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
throw createError({ statusCode: 400, message: "Invalid game ID" });
return unimportedVersions;
});

View File

@ -5,6 +5,13 @@ import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database";
import libraryManager from "~/server/internal/library";
export const LaunchCommands = type({
name: "string > 0",
description: "string = ''",
launchCommand: "string > 0",
launchArgs: "string = ''",
}).array();
export const ImportVersion = type({
id: "string",
version: "string",
@ -17,12 +24,7 @@ export const ImportVersion = type({
delta: "boolean = false",
umuId: "string = ''",
launches: type({
name: "string > 0",
description: "string = ''",
launchCommand: "string > 0",
launchArgs: "string = ''",
}).array(),
launches: LaunchCommands,
}).configure(throwingArktype);
export default defineEventHandler(async (h3) => {
@ -41,7 +43,7 @@ export default defineEventHandler(async (h3) => {
if (validOverlayVersions == 0)
throw createError({
statusCode: 400,
statusMessage:
message:
"Update mode requires a pre-existing version for this platform.",
});
}
@ -50,13 +52,13 @@ export default defineEventHandler(async (h3) => {
if (!body.setup)
throw createError({
statusCode: 400,
statusMessage: 'Setup required in "setup mode".',
message: 'Setup required in "setup mode".',
});
} else {
if (!body.delta && body.launches.length == 0)
throw createError({
statusCode: 400,
statusMessage:
message:
"At least one launch command is required for non-delta versions",
});
}
@ -70,7 +72,7 @@ export default defineEventHandler(async (h3) => {
if (!taskId)
throw createError({
statusCode: 400,
statusMessage: "Invalid options for import",
message: "Invalid options for import",
});
return { taskId: taskId };

View File

@ -11,7 +11,7 @@ export default defineEventHandler(async (h3) => {
if (!gameId || !versionName)
throw createError({
statusCode: 400,
statusMessage: "Missing id or version in request params",
message: "Missing id or version in request params",
});
const preload = await libraryManager.fetchUnimportedVersionInformation(
@ -21,7 +21,7 @@ export default defineEventHandler(async (h3) => {
if (!preload)
throw createError({
statusCode: 400,
statusMessage: "Invalid game or version id/name",
message: "Invalid game or version id/name",
});
return preload;

View File

@ -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

@ -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];
@ -63,7 +63,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

@ -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

@ -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

@ -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

@ -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

@ -18,14 +18,14 @@ export default defineEventHandler(async (h3) => {
if (!taskGroups[taskGroup])
throw createError({
statusCode: 400,
statusMessage: "Invalid task group.",
message: "Invalid task group.",
});
const task = await taskHandler.runTaskGroupByName(taskGroup);
if (!task)
throw createError({
statusCode: 500,
statusMessage: "Could not start task.",
message: "Could not start task.",
});
return { id: task };
});

View File

@ -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

@ -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

@ -19,12 +19,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 } });
return { success: true };

View File

@ -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 fetch 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

@ -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

@ -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

@ -26,7 +26,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);
@ -37,7 +37,7 @@ export default defineEventHandler<{
if (!invitation)
throw createError({
statusCode: 401,
statusMessage: t("errors.auth.invalidInvite"),
message: t("errors.auth.invalidInvite"),
});
// reuse items from invite
@ -50,7 +50,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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -28,7 +28,7 @@ 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<
@ -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

@ -14,13 +14,13 @@ export default defineClientEventHandler(
if (!rawCapability || typeof rawCapability !== "string")
throw createError({
statusCode: 400,
statusMessage: "capability must be a string",
message: "capability must be a string",
});
if (!configuration || typeof configuration !== "object")
throw createError({
statusCode: 400,
statusMessage: "configuration must be an object",
message: "configuration must be an object",
});
const capability = rawCapability as InternalClientCapability;
@ -28,7 +28,7 @@ export default defineClientEventHandler(
if (!validCapabilities.includes(capability))
throw createError({
statusCode: 400,
statusMessage: "Invalid capability.",
message: "Invalid capability.",
});
const isValid = await capabilityManager.validateCapabilityConfiguration(
@ -38,7 +38,7 @@ export default defineClientEventHandler(
if (!isValid)
throw createError({
statusCode: 400,
statusMessage: "Invalid capability configuration.",
message: "Invalid capability configuration.",
});
await capabilityManager.upsertClientCapability(

View File

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -4,7 +4,7 @@ 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

@ -7,14 +7,14 @@ export default defineClientEventHandler(async (h3) => {
if (!id)
throw createError({
statusCode: 400,
statusMessage: "Missing version id in query",
message: "Missing version id in query",
});
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

@ -8,7 +8,7 @@ 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({
@ -20,7 +20,7 @@ export default defineClientEventHandler(async (h3) => {
if (!gameVersion)
throw createError({
statusCode: 404,
statusMessage: "Game version not found",
message: "Game version not found",
});
return gameVersion;

View File

@ -7,7 +7,7 @@ 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({

View File

@ -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

@ -3,13 +3,13 @@ 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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -7,7 +7,7 @@ export default defineClientEventHandler(async (_h3, { 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");

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