mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-10 04:22:09 +10:00
feat: import of custom platforms & file extensions
This commit is contained in:
@ -1,11 +1,14 @@
|
||||
<!-- 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">
|
||||
<label
|
||||
for="icon-upload"
|
||||
class="relative size-24 bg-zinc-800 rounded-md overflow-hidden has-[:focus]:ring-2 has-[:focus]:ring-blue-600"
|
||||
>
|
||||
<input
|
||||
id="icon-upload"
|
||||
type="file"
|
||||
class="hidden"
|
||||
class="sr-only"
|
||||
accept="image/*"
|
||||
@change="addFile"
|
||||
/>
|
||||
@ -14,14 +17,13 @@
|
||||
:src="currentFileObjectUrl"
|
||||
class="absolute inset-0 object-cover w-full h-full"
|
||||
/>
|
||||
<label
|
||||
for="icon-upload"
|
||||
<div
|
||||
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</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<div class="grow flex flex-col gap-y-4">
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-zinc-100"
|
||||
@ -83,12 +85,16 @@
|
||||
</SwitchGroup>
|
||||
<div class="relative">
|
||||
<div class="flex flex-row gap-x-4">
|
||||
<div class="relative size-24 bg-zinc-800 rounded-md overflow-hidden">
|
||||
<label
|
||||
for="platform-icon-upload"
|
||||
class="relative size-24 bg-zinc-800 rounded-md overflow-hidden has-[:focus]:ring-2 has-[:focus]:ring-blue-600"
|
||||
>
|
||||
<input
|
||||
id="platform-icon-upload"
|
||||
type="file"
|
||||
class="hidden"
|
||||
class="sr-only"
|
||||
accept="image/svg+xml"
|
||||
:disabled="!isPlatform"
|
||||
@change="addSvg"
|
||||
/>
|
||||
<div
|
||||
@ -96,16 +102,15 @@
|
||||
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"
|
||||
<div
|
||||
class="absolute inset-0 cursor-pointer flex flex-col gap-y-1 items-center justify-center text-zinc-300 bg-zinc-900/50 focus:text-zinc-100"
|
||||
>
|
||||
<ArrowUpTrayIcon class="size-6" />
|
||||
<span class="text-xs font-bold font-display uppercase"
|
||||
>Upload SVG</span
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<div class="grow flex flex-col gap-y-4">
|
||||
<div>
|
||||
<label
|
||||
@ -117,12 +122,74 @@
|
||||
id="platform-name"
|
||||
v-model="platform.name"
|
||||
type="text"
|
||||
:disabled="!isPlatform"
|
||||
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 class="mt-2 w-full">
|
||||
<label for="platform-name" class="block text-sm font-medium text-zinc-100"
|
||||
>File Extensions {{ currentExtDotted }}
|
||||
</label
|
||||
>
|
||||
<Combobox
|
||||
as="div"
|
||||
:model-value="currentExtDotted"
|
||||
nullable
|
||||
class="mt-1 w-full"
|
||||
:disabled="!isPlatform"
|
||||
@update:model-value="(v) => addExt(v)"
|
||||
>
|
||||
<div class="relative">
|
||||
<ComboboxInput
|
||||
class="w-full block flex-1 rounded-lg border-1 border-zinc-800 py-2 px-2 bg-zinc-950 text-zinc-100 placeholder:text-zinc-400 focus:ring-0 sm:text-sm sm:leading-6"
|
||||
placeholder=".exe"
|
||||
@change="currentExt = $event.target.value"
|
||||
@blur="currentExt = ''"
|
||||
/>
|
||||
<ComboboxOptions
|
||||
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-zinc-900 py-1 text-base shadow-lg ring-1 ring-white/5 focus:outline-none sm:text-sm"
|
||||
>
|
||||
<ComboboxOption
|
||||
v-if="currentExt"
|
||||
v-slot="{ active }"
|
||||
:value="currentExtDotted"
|
||||
>
|
||||
<li
|
||||
:class="[
|
||||
'relative cursor-default select-none py-2 pl-3 pr-9',
|
||||
active
|
||||
? 'bg-blue-600 text-white outline-none'
|
||||
: 'text-zinc-100',
|
||||
]"
|
||||
>
|
||||
<span class="block">
|
||||
<span class="text-blue-300">filename</span
|
||||
><span class="font-semibold">{{ currentExtDotted }}</span>
|
||||
</span>
|
||||
</li>
|
||||
</ComboboxOption>
|
||||
</ComboboxOptions>
|
||||
</div>
|
||||
</Combobox>
|
||||
<div class="mt-2 flex gap-1 flex-wrap">
|
||||
<div
|
||||
v-for="ext in platform.fileExts"
|
||||
:key="ext"
|
||||
class="bg-blue-600/10 border-1 border-blue-700 rounded-full px-2 py-1 text-xs text-blue-400"
|
||||
>
|
||||
{{ ext }}
|
||||
</div>
|
||||
<span
|
||||
v-if="platform.fileExts.length == 0"
|
||||
class="uppercase font-display text-zinc-700 font-bold text-xs"
|
||||
>No suggested file extensions.</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isPlatform" class="absolute inset-0 bg-zinc-950/20" />
|
||||
</div>
|
||||
<div>
|
||||
@ -152,6 +219,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxInput,
|
||||
ComboboxOption,
|
||||
ComboboxOptions,
|
||||
Switch,
|
||||
SwitchDescription,
|
||||
SwitchGroup,
|
||||
@ -172,13 +243,27 @@ const emit = defineEmits<{
|
||||
const name = ref("");
|
||||
const description = ref("");
|
||||
const isPlatform = ref(false);
|
||||
const currentExt = ref("");
|
||||
const currentExtDotted = computed(() => {
|
||||
if(!currentExt.value) return "";
|
||||
const cleaned = currentExt.value.replace(/\W/g, "").toLowerCase();
|
||||
return `.${cleaned}`;
|
||||
});
|
||||
const platform = ref<{ name: string; icon: string; fileExts: string[] }>({
|
||||
name: "",
|
||||
icon: "",
|
||||
fileExts: [],
|
||||
});
|
||||
|
||||
const buttonDisabled = computed<boolean>(() => !(name.value && description.value && currentFileObjectUrl.value && (!isPlatform.value || (platform.value.name && platform.value.icon))))
|
||||
const buttonDisabled = computed<boolean>(
|
||||
() =>
|
||||
!(
|
||||
name.value &&
|
||||
description.value &&
|
||||
currentFileObjectUrl.value &&
|
||||
(!isPlatform.value || (platform.value.name && platform.value.icon))
|
||||
),
|
||||
);
|
||||
|
||||
function addFile(event: Event) {
|
||||
const file = (event.target as HTMLInputElement)?.files?.[0];
|
||||
@ -218,6 +303,13 @@ async function addSvg(event: Event) {
|
||||
}
|
||||
}
|
||||
|
||||
function addExt(ext: string | null) {
|
||||
if (!ext) return;
|
||||
if (platform.value.fileExts.includes(ext)) return;
|
||||
platform.value.fileExts.push(ext);
|
||||
currentExt.value = "";
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
gameName: string;
|
||||
loading: boolean;
|
||||
@ -233,7 +325,12 @@ function importRedist() {
|
||||
description: description.value,
|
||||
icon: currentFile.value,
|
||||
},
|
||||
isPlatform.value ? platform.value : undefined,
|
||||
isPlatform.value
|
||||
? {
|
||||
...platform.value,
|
||||
fileExts: platform.value.fileExts.map((e) => e.slice(1)),
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Listbox v-model="typedModel" as="div">
|
||||
<Listbox v-model="model" as="div">
|
||||
<ListboxLabel class="block text-sm font-medium leading-6 text-zinc-100"
|
||||
><slot
|
||||
/></ListboxLabel>
|
||||
@ -7,13 +7,13 @@
|
||||
<ListboxButton
|
||||
class="relative w-full cursor-default rounded-md bg-zinc-950 py-1.5 pl-3 pr-10 text-left text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-800 focus:outline-none focus:ring-2 focus:ring-blue-500 sm:text-sm sm:leading-6"
|
||||
>
|
||||
<span v-if="model" class="flex items-center">
|
||||
<component
|
||||
:is="PLATFORM_ICONS[model]"
|
||||
alt=""
|
||||
<span v-if="currentEntry" class="flex items-center">
|
||||
<IconsPlatform
|
||||
:platform="currentEntry.platformIcon.key"
|
||||
:fallback="currentEntry.platformIcon.fallback"
|
||||
class="h-5 w-5 flex-shrink-0 text-blue-600"
|
||||
/>
|
||||
<span class="ml-3 block truncate">{{ model }}</span>
|
||||
<span class="ml-3 block truncate">{{ currentEntry.name }}</span>
|
||||
</span>
|
||||
<span v-else>{{ $t("library.admin.import.selectPlatform") }}</span>
|
||||
<span
|
||||
@ -32,11 +32,11 @@
|
||||
class="absolute z-10 mt-1 max-h-56 w-full overflow-auto rounded-md bg-zinc-900 py-1 text-base shadow-lg ring-1 ring-zinc-950 ring-opacity-5 focus:outline-none sm:text-sm"
|
||||
>
|
||||
<ListboxOption
|
||||
v-for="[name, value] in values"
|
||||
:key="value"
|
||||
v-for="entry in values"
|
||||
:key="entry.param"
|
||||
v-slot="{ active, selected }"
|
||||
as="template"
|
||||
:value="value"
|
||||
:value="entry.param"
|
||||
>
|
||||
<li
|
||||
:class="[
|
||||
@ -45,15 +45,13 @@
|
||||
]"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<component
|
||||
:is="PLATFORM_ICONS[value]"
|
||||
alt=""
|
||||
:class="[
|
||||
active ? 'text-zinc-100' : 'text-blue-600',
|
||||
'h-5 w-5 flex-shrink-0',
|
||||
]"
|
||||
<IconsPlatform
|
||||
v-if="entry.platformIcon"
|
||||
:platform="entry.platformIcon.key"
|
||||
:fallback="entry.platformIcon.fallback"
|
||||
class="size-5 text-blue-500"
|
||||
/>
|
||||
<span class="ml-3 block truncate">{{ name }}</span>
|
||||
<span class="ml-3 block truncate">{{ entry.name }}</span>
|
||||
</div>
|
||||
|
||||
<span
|
||||
@ -82,19 +80,15 @@ import {
|
||||
ListboxOptions,
|
||||
} from "@headlessui/vue";
|
||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||
import { Platform } from "~/prisma/client/enums";
|
||||
|
||||
const model = defineModel<Platform | undefined>();
|
||||
const model = defineModel<string | undefined>();
|
||||
|
||||
const typedModel = computed<Platform | null>({
|
||||
get() {
|
||||
return model.value || null;
|
||||
},
|
||||
set(v) {
|
||||
if (v === null) return (model.value = undefined);
|
||||
model.value = v;
|
||||
},
|
||||
});
|
||||
const props = defineProps<{ platforms: PlatformRenderable[] }>();
|
||||
const currentEntry = computed(() =>
|
||||
model.value
|
||||
? props.platforms.find((v) => v.param === model.value)
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const values = Object.entries(Platform);
|
||||
const values = props.platforms;
|
||||
</script>
|
||||
|
||||
@ -367,7 +367,6 @@ import {
|
||||
import type { SerializeObject } from "nitropack";
|
||||
import type { GameModel, GameTagModel } from "~/prisma/client/models";
|
||||
import MultiItemSelector from "./MultiItemSelector.vue";
|
||||
import { Platform } from "~/prisma/client/enums";
|
||||
const { showGamePanelTextDecoration } = await $dropFetch(`/api/v1/settings`);
|
||||
|
||||
const mobileFiltersOpen = ref(false);
|
||||
@ -416,18 +415,7 @@ const options: Array<StoreFilterOption> = [
|
||||
name: "Platform",
|
||||
param: "platform",
|
||||
multiple: true,
|
||||
options: [
|
||||
...Object.values(Platform).map((e) => ({
|
||||
name: e,
|
||||
param: e,
|
||||
platformIcon: { key: e },
|
||||
})),
|
||||
...userPlatforms.map((e) => ({
|
||||
name: e.platformName,
|
||||
param: e.id,
|
||||
platformIcon: { key: e.id, fallback: e.iconSvg },
|
||||
})),
|
||||
],
|
||||
options: renderPlatforms(userPlatforms),
|
||||
},
|
||||
...(props.extraOptions ?? []),
|
||||
];
|
||||
|
||||
24
composables/platform.ts
Normal file
24
composables/platform.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Platform } from "~/prisma/client/enums";
|
||||
|
||||
export type PlatformRenderable = {
|
||||
name: string;
|
||||
param: string;
|
||||
platformIcon: { key: string; fallback?: string };
|
||||
};
|
||||
|
||||
export function renderPlatforms(
|
||||
userPlatforms: { platformName: string; id: string; iconSvg: string }[],
|
||||
): PlatformRenderable[] {
|
||||
return [
|
||||
...Object.values(Platform).map((e) => ({
|
||||
name: e,
|
||||
param: e,
|
||||
platformIcon: { key: e },
|
||||
})),
|
||||
...userPlatforms.map((e) => ({
|
||||
name: e.platformName,
|
||||
param: e.id,
|
||||
platformIcon: { key: e.id, fallback: e.iconSvg },
|
||||
})),
|
||||
];
|
||||
}
|
||||
@ -4,7 +4,7 @@ import type {
|
||||
NitroFetchRequest,
|
||||
TypedInternalResponse,
|
||||
} from "nitropack/types";
|
||||
import type { FetchError } from "ofetch";
|
||||
import { FetchError } from "ofetch";
|
||||
|
||||
interface DropFetch<
|
||||
DefaultT = unknown,
|
||||
@ -66,6 +66,9 @@ export const $dropFetch: DropFetch = async (rawRequest, opts) => {
|
||||
(_, c) => c(),
|
||||
);
|
||||
}
|
||||
if(e instanceof FetchError) {
|
||||
e.message = e.data.message ?? e.message;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@
|
||||
"chars": {
|
||||
"arrow": "→",
|
||||
"arrowBack": "←",
|
||||
"quoted": "\"\"",
|
||||
"quoted": "\"{text}\"",
|
||||
"srComma": ", {0}"
|
||||
},
|
||||
"common": {
|
||||
|
||||
@ -71,7 +71,7 @@
|
||||
"chars": {
|
||||
"arrow": "→",
|
||||
"arrowBack": "←",
|
||||
"quoted": "\"\"",
|
||||
"quoted": "\"{text}\"",
|
||||
"srComma": ", {0}"
|
||||
},
|
||||
"common": {
|
||||
|
||||
@ -93,7 +93,7 @@
|
||||
"chars": {
|
||||
"arrow": "→",
|
||||
"arrowBack": "←",
|
||||
"quoted": "\"\"",
|
||||
"quoted": "\"{text}\"",
|
||||
"srComma": ", {0}"
|
||||
},
|
||||
"common": {
|
||||
|
||||
@ -70,7 +70,7 @@
|
||||
"chars": {
|
||||
"arrow": "→",
|
||||
"arrowBack": "←",
|
||||
"quoted": "\"\"",
|
||||
"quoted": "\"{text}\"",
|
||||
"srComma": ", {0}"
|
||||
},
|
||||
"common": {
|
||||
|
||||
@ -241,6 +241,9 @@ export default defineNuxtConfig({
|
||||
file: "zh_tw.json",
|
||||
},
|
||||
],
|
||||
bundle: {
|
||||
optimizeTranslationDirective: false,
|
||||
},
|
||||
},
|
||||
|
||||
security: {
|
||||
|
||||
@ -66,7 +66,6 @@
|
||||
"@nuxt/eslint": "^1.3.0",
|
||||
"@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",
|
||||
|
||||
@ -438,7 +438,11 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PlatformSelector v-model="versionSettings.platform" class="max-w-lg">
|
||||
<PlatformSelector
|
||||
v-model="versionSettings.platform"
|
||||
class="max-w-lg"
|
||||
:platforms="allPlatforms"
|
||||
>
|
||||
{{ $t("library.admin.import.version.platform") }}
|
||||
</PlatformSelector>
|
||||
<SwitchGroup as="div" class="flex items-center justify-between max-w-lg">
|
||||
@ -636,6 +640,10 @@ const gameId = route.params.id.toString();
|
||||
const versions = await $dropFetch(
|
||||
`/api/v1/admin/import/version?id=${encodeURIComponent(gameId)}`,
|
||||
);
|
||||
const userPlatforms = await $dropFetch(
|
||||
"/api/v1/admin/import/version/platforms",
|
||||
);
|
||||
const allPlatforms = renderPlatforms(userPlatforms);
|
||||
const currentlySelectedVersion = ref(-1);
|
||||
const versionSettings = ref<Partial<typeof ImportVersion.infer>>({
|
||||
id: gameId,
|
||||
@ -643,7 +651,8 @@ const versionSettings = ref<Partial<typeof ImportVersion.infer>>({
|
||||
});
|
||||
|
||||
const versionGuesses =
|
||||
ref<Array<SerializeObject<{ platform: Platform; filename: string }>>>();
|
||||
ref<Array<SerializeObject<{ platform: string; filename: string }>>>();
|
||||
|
||||
const launchProcessQuery = ref("");
|
||||
const setupProcessQuery = ref("");
|
||||
|
||||
@ -698,15 +707,12 @@ async function updateCurrentlySelectedVersion(value: number) {
|
||||
if (currentlySelectedVersion.value == value) return;
|
||||
currentlySelectedVersion.value = value;
|
||||
const version = versions[currentlySelectedVersion.value];
|
||||
const results = await $dropFetch(
|
||||
const options = await $dropFetch(
|
||||
`/api/v1/admin/import/version/preload?id=${encodeURIComponent(
|
||||
gameId,
|
||||
)}&version=${encodeURIComponent(version)}`,
|
||||
);
|
||||
versionGuesses.value = results.map((e) => ({
|
||||
...e,
|
||||
platform: e.platform as Platform,
|
||||
}));
|
||||
versionGuesses.value = options;
|
||||
versionSettings.value.name = version;
|
||||
}
|
||||
|
||||
|
||||
@ -198,8 +198,8 @@
|
||||
>{{ metadata.title }}
|
||||
<span class="ml-2 font-mono text-zinc-500 text-xs">{{
|
||||
source
|
||||
}}</span></RadioGroupLabel
|
||||
>
|
||||
}}</span>
|
||||
</RadioGroupLabel>
|
||||
<RadioGroupDescription
|
||||
as="span"
|
||||
class="text-zinc-400 text-xs"
|
||||
@ -405,18 +405,21 @@ function performActionSource_wrapper() {
|
||||
modalError.value = undefined;
|
||||
modalLoading.value = true;
|
||||
performActionSource()
|
||||
.then(() => {
|
||||
actionSourceOpen.value = false;
|
||||
sourceConfig.value = {};
|
||||
sourceName.value = "";
|
||||
})
|
||||
.catch((e) => {
|
||||
if (e instanceof FetchError) {
|
||||
modalError.value = e.message ?? e.message;
|
||||
} else {
|
||||
modalError.value = e as string;
|
||||
}
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
actionSourceOpen.value = false;
|
||||
sourceConfig.value = {};
|
||||
sourceName.value = "";
|
||||
},
|
||||
(e) => {
|
||||
if (e instanceof FetchError) {
|
||||
console.log(e.data.message);
|
||||
modalError.value = e.message;
|
||||
} else {
|
||||
modalError.value = e as string;
|
||||
}
|
||||
},
|
||||
)
|
||||
.finally(() => {
|
||||
modalLoading.value = false;
|
||||
});
|
||||
|
||||
@ -84,6 +84,7 @@
|
||||
? undefined
|
||||
: platform.iconSvg
|
||||
"
|
||||
class="size-8 text-blue-600"
|
||||
/>
|
||||
<span
|
||||
v-if="platforms.length == 0"
|
||||
@ -257,7 +258,7 @@ const gameId = route.params.id.toString();
|
||||
|
||||
const user = useUser();
|
||||
|
||||
const { game, rating } = await $dropFetch(`/api/v1/games/${gameId}`);
|
||||
const { game, rating, platforms } = await $dropFetch(`/api/v1/games/${gameId}`);
|
||||
|
||||
// Preview description (first 30 lines)
|
||||
const showPreview = ref(true);
|
||||
@ -283,11 +284,6 @@ const previewHTML = micromark(previewDescription);
|
||||
const descriptionHTML = micromark(game.mDescription);
|
||||
|
||||
const showReadMore = previewHTML != descriptionHTML;
|
||||
const platforms = game.versions
|
||||
.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);
|
||||
const averageRating = Math.round((rating._avg.mReviewRating ?? 0) * 5);
|
||||
|
||||
12557
pnpm-lock.yaml
generated
Normal file
12557
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,8 +0,0 @@
|
||||
-- DropIndex
|
||||
DROP INDEX "public"."GameTag_name_idx";
|
||||
|
||||
-- AlterTable
|
||||
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));
|
||||
@ -1,14 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Made the column `libraryId` on table `Game` required. This step will fail if there are existing NULL values in that column.
|
||||
|
||||
*/
|
||||
-- DropIndex
|
||||
DROP INDEX "public"."GameTag_name_idx";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."Game" ALTER COLUMN "libraryId" SET NOT NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "GameTag_name_idx" ON "public"."GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
|
||||
@ -1,32 +0,0 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Added the required column `mReleased` to the `Mod` table without a default value. This is not possible if the table is not empty.
|
||||
|
||||
*/
|
||||
-- DropIndex
|
||||
DROP INDEX "public"."GameTag_name_idx";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."Mod" ADD COLUMN "mReleased" TIMESTAMP(3) NOT NULL;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."GameDLCMetadata" (
|
||||
"id" TEXT NOT NULL,
|
||||
"mName" TEXT NOT NULL,
|
||||
"mShortDescription" TEXT NOT NULL,
|
||||
"mDescription" TEXT NOT NULL,
|
||||
"mIconObjectId" TEXT NOT NULL,
|
||||
"mBannerObjectId" TEXT NOT NULL,
|
||||
"mCoverObjectId" TEXT NOT NULL,
|
||||
"mImageCarouselObjectIds" TEXT[],
|
||||
"mImageLibraryObjectIds" TEXT[],
|
||||
|
||||
CONSTRAINT "GameDLCMetadata_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "GameTag_name_idx" ON "public"."GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."GameDLCMetadata" ADD CONSTRAINT "GameDLCMetadata_id_fkey" FOREIGN KEY ("id") REFERENCES "public"."DLC"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@ -1,3 +1,4 @@
|
||||
|
||||
-- enable pg_trgm
|
||||
CREATE EXTENSION pg_trgm;
|
||||
|
||||
@ -19,6 +20,9 @@ CREATE TYPE "public"."APITokenMode" AS ENUM ('User', 'System', 'Client');
|
||||
-- CreateEnum
|
||||
CREATE TYPE "public"."ClientCapabilities" AS ENUM ('peerAPI', 'userStatus', 'cloudSaves', 'trackPlaytime');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "public"."HardwarePlatform" AS ENUM ('windows', 'linux', 'macos');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "public"."MetadataSource" AS ENUM ('Manual', 'GiantBomb', 'PCGamingWiki', 'IGDB', 'Metacritic', 'OpenCritic');
|
||||
|
||||
@ -107,7 +111,7 @@ CREATE TABLE "public"."Client" (
|
||||
"userId" TEXT NOT NULL,
|
||||
"capabilities" "public"."ClientCapabilities"[],
|
||||
"name" TEXT NOT NULL,
|
||||
"platform" "public"."Platform" NOT NULL,
|
||||
"platform" "public"."HardwarePlatform" NOT NULL,
|
||||
"lastConnected" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Client_pkey" PRIMARY KEY ("id")
|
||||
@ -134,13 +138,38 @@ CREATE TABLE "public"."CollectionEntry" (
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."UserPlatform" (
|
||||
"id" TEXT NOT NULL,
|
||||
"redistId" TEXT,
|
||||
"platformName" TEXT NOT NULL,
|
||||
"iconSvg" TEXT NOT NULL,
|
||||
"fileExtensions" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"redistId" TEXT,
|
||||
|
||||
CONSTRAINT "UserPlatform_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."PlatformLink" (
|
||||
"id" TEXT NOT NULL,
|
||||
"platform" "public"."HardwarePlatform",
|
||||
"userPlatformId" TEXT,
|
||||
|
||||
CONSTRAINT "PlatformLink_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."LaunchOption" (
|
||||
"launchId" TEXT NOT NULL,
|
||||
"versionId" TEXT NOT NULL,
|
||||
"launchGId" TEXT,
|
||||
"installGId" TEXT,
|
||||
"uninstallGId" TEXT,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"command" TEXT NOT NULL,
|
||||
"args" TEXT NOT NULL DEFAULT '',
|
||||
|
||||
CONSTRAINT "LaunchOption_pkey" PRIMARY KEY ("launchId")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."Version" (
|
||||
"versionId" TEXT NOT NULL,
|
||||
@ -151,8 +180,6 @@ CREATE TABLE "public"."Version" (
|
||||
"redistId" TEXT,
|
||||
"dlcId" TEXT,
|
||||
"modId" TEXT,
|
||||
"platform" "public"."Platform",
|
||||
"userPlatformRedistId" TEXT,
|
||||
"dropletManifest" JSONB NOT NULL,
|
||||
|
||||
CONSTRAINT "Version_pkey" PRIMARY KEY ("versionId")
|
||||
@ -161,32 +188,22 @@ CREATE TABLE "public"."Version" (
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."GameVersion" (
|
||||
"versionId" TEXT NOT NULL,
|
||||
"setupCommand" TEXT NOT NULL DEFAULT '',
|
||||
"setupArgs" TEXT NOT NULL DEFAULT '',
|
||||
"installId" TEXT,
|
||||
"uninstallId" TEXT,
|
||||
"onlySetup" BOOLEAN NOT NULL DEFAULT false,
|
||||
"umuIdOverride" TEXT,
|
||||
"versionIndex" INTEGER NOT NULL,
|
||||
"delta" BOOLEAN NOT NULL DEFAULT false,
|
||||
"hidden" BOOLEAN NOT NULL DEFAULT false,
|
||||
"platformId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "GameVersion_pkey" PRIMARY KEY ("versionId")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
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 "LaunchOption_pkey" PRIMARY KEY ("launchId")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."DLCVersion" (
|
||||
"versionId" TEXT NOT NULL,
|
||||
"platformId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "DLCVersion_pkey" PRIMARY KEY ("versionId")
|
||||
);
|
||||
@ -194,6 +211,7 @@ CREATE TABLE "public"."DLCVersion" (
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."RedistVersion" (
|
||||
"versionId" TEXT NOT NULL,
|
||||
"platformId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "RedistVersion_pkey" PRIMARY KEY ("versionId")
|
||||
);
|
||||
@ -202,6 +220,7 @@ CREATE TABLE "public"."RedistVersion" (
|
||||
CREATE TABLE "public"."ModVersion" (
|
||||
"versionId" TEXT NOT NULL,
|
||||
"dependencies" TEXT[],
|
||||
"platformId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "ModVersion_pkey" PRIMARY KEY ("versionId")
|
||||
);
|
||||
@ -267,7 +286,7 @@ CREATE TABLE "public"."Game" (
|
||||
"mCoverObjectId" TEXT NOT NULL,
|
||||
"mImageCarouselObjectIds" TEXT[],
|
||||
"mImageLibraryObjectIds" TEXT[],
|
||||
"libraryId" TEXT,
|
||||
"libraryId" TEXT NOT NULL,
|
||||
"libraryPath" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "Game_pkey" PRIMARY KEY ("id")
|
||||
@ -285,6 +304,21 @@ CREATE TABLE "public"."DLC" (
|
||||
CONSTRAINT "DLC_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."GameDLCMetadata" (
|
||||
"id" TEXT NOT NULL,
|
||||
"mName" TEXT NOT NULL,
|
||||
"mShortDescription" TEXT NOT NULL,
|
||||
"mDescription" TEXT NOT NULL,
|
||||
"mIconObjectId" TEXT NOT NULL,
|
||||
"mBannerObjectId" TEXT NOT NULL,
|
||||
"mCoverObjectId" TEXT NOT NULL,
|
||||
"mImageCarouselObjectIds" TEXT[],
|
||||
"mImageLibraryObjectIds" TEXT[],
|
||||
|
||||
CONSTRAINT "GameDLCMetadata_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."Redist" (
|
||||
"id" TEXT NOT NULL,
|
||||
@ -307,6 +341,7 @@ CREATE TABLE "public"."Mod" (
|
||||
"mName" TEXT NOT NULL,
|
||||
"mShortDescription" TEXT NOT NULL,
|
||||
"mDescription" TEXT NOT NULL,
|
||||
"mReleased" TIMESTAMP(3) NOT NULL,
|
||||
"mIconObjectId" TEXT NOT NULL,
|
||||
"mBannerObjectId" TEXT NOT NULL,
|
||||
"mCoverObjectId" TEXT NOT NULL,
|
||||
@ -486,9 +521,21 @@ CREATE INDEX "APIToken_token_idx" ON "public"."APIToken"("token");
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "UserPlatform_redistId_key" ON "public"."UserPlatform"("redistId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "LaunchOption_installGId_key" ON "public"."LaunchOption"("installGId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "LaunchOption_uninstallGId_key" ON "public"."LaunchOption"("uninstallGId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Version_versionId_key" ON "public"."Version"("versionId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "GameVersion_installId_key" ON "public"."GameVersion"("installId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "GameVersion_uninstallId_key" ON "public"."GameVersion"("uninstallId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Screenshot_gameId_userId_idx" ON "public"."Screenshot"("gameId", "userId");
|
||||
|
||||
@ -574,7 +621,16 @@ 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"."UserPlatform" ADD CONSTRAINT "UserPlatform_redistId_fkey" FOREIGN KEY ("redistId") REFERENCES "public"."Redist"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "public"."UserPlatform" ADD CONSTRAINT "UserPlatform_redistId_fkey" FOREIGN KEY ("redistId") REFERENCES "public"."Redist"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."PlatformLink" ADD CONSTRAINT "PlatformLink_userPlatformId_fkey" FOREIGN KEY ("userPlatformId") REFERENCES "public"."UserPlatform"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
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"."LaunchOption" ADD CONSTRAINT "LaunchOption_launchGId_fkey" FOREIGN KEY ("launchGId") REFERENCES "public"."GameVersion"("versionId") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."Version" ADD CONSTRAINT "game_link" FOREIGN KEY ("gameId") REFERENCES "public"."Game"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@ -588,27 +644,36 @@ ALTER TABLE "public"."Version" ADD CONSTRAINT "dlc_link" FOREIGN KEY ("dlcId") R
|
||||
-- AddForeignKey
|
||||
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"."Version" ADD CONSTRAINT "Version_userPlatformRedistId_fkey" FOREIGN KEY ("userPlatformRedistId") REFERENCES "public"."UserPlatform"("redistId") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- 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"."LaunchOption" ADD CONSTRAINT "gameVersion_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."GameVersion"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "public"."GameVersion" ADD CONSTRAINT "GameVersion_installId_fkey" FOREIGN KEY ("installId") REFERENCES "public"."LaunchOption"("launchId") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."LaunchOption" ADD CONSTRAINT "redistVersion_fkey" FOREIGN KEY ("versionId") REFERENCES "public"."RedistVersion"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ALTER TABLE "public"."GameVersion" ADD CONSTRAINT "GameVersion_uninstallId_fkey" FOREIGN KEY ("uninstallId") REFERENCES "public"."LaunchOption"("launchId") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."GameVersion" ADD CONSTRAINT "GameVersion_platformId_fkey" FOREIGN KEY ("platformId") REFERENCES "public"."PlatformLink"("id") ON DELETE RESTRICT 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"."DLCVersion" ADD CONSTRAINT "DLCVersion_platformId_fkey" FOREIGN KEY ("platformId") REFERENCES "public"."PlatformLink"("id") ON DELETE RESTRICT 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"."RedistVersion" ADD CONSTRAINT "RedistVersion_platformId_fkey" FOREIGN KEY ("platformId") REFERENCES "public"."PlatformLink"("id") ON DELETE RESTRICT 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"."ModVersion" ADD CONSTRAINT "ModVersion_platformId_fkey" FOREIGN KEY ("platformId") REFERENCES "public"."PlatformLink"("id") ON DELETE RESTRICT 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;
|
||||
|
||||
@ -636,6 +701,9 @@ ALTER TABLE "public"."Game" ADD CONSTRAINT "Game_libraryId_fkey" FOREIGN KEY ("l
|
||||
-- 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"."GameDLCMetadata" ADD CONSTRAINT "GameDLCMetadata_id_fkey" FOREIGN KEY ("id") REFERENCES "public"."DLC"("id") ON DELETE RESTRICT 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;
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `platform` on the `PlatformLink` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- DropIndex
|
||||
DROP INDEX "public"."GameTag_name_idx";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."PlatformLink" DROP COLUMN "platform",
|
||||
ADD COLUMN "hardwarePlatform" "public"."HardwarePlatform";
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "GameTag_name_idx" ON "public"."GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
|
||||
@ -0,0 +1,21 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `versionId` on the `LaunchOption` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "public"."LaunchOption" DROP CONSTRAINT "redistVersion_fkey";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "public"."GameTag_name_idx";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."LaunchOption" DROP COLUMN "versionId",
|
||||
ADD COLUMN "redistVersionId" TEXT;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "GameTag_name_idx" ON "public"."GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."LaunchOption" ADD CONSTRAINT "redistVersion_fkey" FOREIGN KEY ("redistVersionId") REFERENCES "public"."RedistVersion"("versionId") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@ -0,0 +1,20 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `userPlatformId` on the `PlatformLink` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "public"."PlatformLink" DROP CONSTRAINT "PlatformLink_userPlatformId_fkey";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "public"."GameTag_name_idx";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."PlatformLink" DROP COLUMN "userPlatformId";
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "GameTag_name_idx" ON "public"."GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."PlatformLink" ADD CONSTRAINT "PlatformLink_id_fkey" FOREIGN KEY ("id") REFERENCES "public"."UserPlatform"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@ -1,3 +1,6 @@
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "public"."PlatformLink" DROP CONSTRAINT "PlatformLink_id_fkey";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "public"."GameTag_name_idx";
|
||||
|
||||
@ -0,0 +1,20 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Made the column `redistId` on table `UserPlatform` required. This step will fail if there are existing NULL values in that column.
|
||||
|
||||
*/
|
||||
-- DropForeignKey
|
||||
ALTER TABLE "public"."UserPlatform" DROP CONSTRAINT "UserPlatform_redistId_fkey";
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "public"."GameTag_name_idx";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."UserPlatform" ALTER COLUMN "redistId" SET NOT NULL;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "GameTag_name_idx" ON "public"."GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."UserPlatform" ADD CONSTRAINT "UserPlatform_redistId_fkey" FOREIGN KEY ("redistId") REFERENCES "public"."Redist"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@ -14,7 +14,7 @@ model Client {
|
||||
capabilities ClientCapabilities[]
|
||||
|
||||
name String
|
||||
platform Platform
|
||||
platform HardwarePlatform
|
||||
lastConnected DateTime
|
||||
|
||||
lastAccessedSaves SaveSlot[]
|
||||
|
||||
@ -1,16 +1,62 @@
|
||||
enum HardwarePlatform {
|
||||
Windows @map("windows")
|
||||
Linux @map("linux")
|
||||
macOS @map("macos")
|
||||
// Switch @map("switch")
|
||||
// etc
|
||||
|
||||
// @@map("Platform")
|
||||
}
|
||||
|
||||
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[]
|
||||
redistId String @unique
|
||||
redist Redist @relation(fields: [redistId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
|
||||
//platform PlatformLink[]
|
||||
}
|
||||
|
||||
model PlatformLink {
|
||||
id String @id // This is either the ID of the user platform, or a repeat of the HardwarePlatform enum. It's cursed.
|
||||
|
||||
hardwarePlatform HardwarePlatform?
|
||||
// Waiting on weak reference
|
||||
// userPlatform UserPlatform? @relation(fields: [id], references: [id])
|
||||
|
||||
gameVersions GameVersion[]
|
||||
dlcVersions DLCVersion[]
|
||||
redistVerisons RedistVersion[]
|
||||
modVersions ModVersion[]
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
model LaunchOption {
|
||||
launchId String @id @default(uuid())
|
||||
|
||||
redistVersionId String?
|
||||
redistVersion RedistVersion? @relation(fields: [redistVersionId], references: [versionId], onDelete: Cascade, onUpdate: Cascade, map: "redistVersion_fkey")
|
||||
|
||||
launchGId String?
|
||||
launchGVersion GameVersion? @relation(name: "launches", fields: [launchGId], references: [versionId])
|
||||
installGId String? @unique
|
||||
installGVersion GameVersion? @relation(name: "install")
|
||||
uninstallGId String? @unique
|
||||
uninstallGVersion GameVersion? @relation(name: "uninstall")
|
||||
|
||||
name String
|
||||
description String
|
||||
|
||||
command String
|
||||
args String @default("")
|
||||
}
|
||||
|
||||
// Platform agnostic object
|
||||
model Version {
|
||||
versionId String @id @unique @default(uuid())
|
||||
|
||||
@ -19,72 +65,62 @@ model Version {
|
||||
versionName String
|
||||
created DateTime @default(now())
|
||||
|
||||
gameId String?
|
||||
game Game? @relation(fields: [gameId], references: [id], map: "game_link", onDelete: Cascade, onUpdate: Cascade)
|
||||
gameVersion GameVersion?
|
||||
gameId String?
|
||||
game Game? @relation(fields: [gameId], references: [id], map: "game_link", onDelete: Cascade, onUpdate: Cascade)
|
||||
gameVersions GameVersion[]
|
||||
|
||||
redistId String?
|
||||
redist Redist? @relation(fields: [redistId], references: [id], map: "redist_link", onDelete: Cascade, onUpdate: Cascade)
|
||||
redistVersion RedistVersion?
|
||||
redistId String?
|
||||
redist Redist? @relation(fields: [redistId], references: [id], map: "redist_link", onDelete: Cascade, onUpdate: Cascade)
|
||||
redistVersions RedistVersion[]
|
||||
|
||||
dlcId String?
|
||||
dlc DLC? @relation(fields: [dlcId], references: [id], map: "dlc_link", onDelete: Cascade, onUpdate: Cascade)
|
||||
dlcVersion DLCVersion?
|
||||
dlcId String?
|
||||
dlc DLC? @relation(fields: [dlcId], references: [id], map: "dlc_link", onDelete: Cascade, onUpdate: Cascade)
|
||||
dlcVersions DLCVersion[]
|
||||
|
||||
modId String?
|
||||
mod Mod? @relation(fields: [modId], references: [id], map: "mod_link", onDelete: Cascade, onUpdate: Cascade)
|
||||
modVersion ModVersion?
|
||||
|
||||
platform Platform?
|
||||
|
||||
userPlatformRedistId String?
|
||||
userPlatform UserPlatform? @relation(fields: [userPlatformRedistId], references: [redistId])
|
||||
modId String?
|
||||
mod Mod? @relation(fields: [modId], references: [id], map: "mod_link", onDelete: Cascade, onUpdate: Cascade)
|
||||
modVersions ModVersion[]
|
||||
|
||||
dropletManifest Json // Results from droplet
|
||||
}
|
||||
|
||||
// Platform specific object
|
||||
model GameVersion {
|
||||
versionId String @id
|
||||
version Version @relation(fields: [versionId], references: [versionId], onDelete: Cascade, onUpdate: Cascade)
|
||||
|
||||
redistDeps RedistVersion[]
|
||||
|
||||
launches LaunchOption[]
|
||||
launches LaunchOption[] @relation(name: "launches")
|
||||
|
||||
setupCommand String @default("")
|
||||
setupArgs String @default("")
|
||||
onlySetup Boolean @default(false)
|
||||
installId String? @unique
|
||||
install LaunchOption? @relation(name: "install", fields: [installId], references: [launchId])
|
||||
uninstallId String? @unique
|
||||
uninstall LaunchOption? @relation(name: "uninstall", fields: [uninstallId], references: [launchId])
|
||||
onlySetup Boolean @default(false)
|
||||
|
||||
umuIdOverride String?
|
||||
|
||||
versionIndex Int
|
||||
delta Boolean @default(false)
|
||||
hidden Boolean @default(false)
|
||||
|
||||
platformId String
|
||||
platform PlatformLink @relation(fields: [platformId], references: [id])
|
||||
}
|
||||
|
||||
model LaunchOption {
|
||||
launchId String @id @default(uuid())
|
||||
versionId String
|
||||
|
||||
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
|
||||
|
||||
launchCommand String
|
||||
launchArgs String @default("")
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Platform specific object
|
||||
model DLCVersion {
|
||||
versionId String @id
|
||||
version Version @relation(fields: [versionId], references: [versionId], onDelete: Cascade, onUpdate: Cascade)
|
||||
|
||||
redistDeps RedistVersion[]
|
||||
|
||||
platformId String
|
||||
platform PlatformLink @relation(fields: [platformId], references: [id])
|
||||
}
|
||||
|
||||
// Platform specific object
|
||||
model RedistVersion {
|
||||
versionId String @id
|
||||
version Version @relation(fields: [versionId], references: [versionId], onDelete: Cascade, onUpdate: Cascade)
|
||||
@ -93,13 +129,20 @@ model RedistVersion {
|
||||
|
||||
gameDependees GameVersion[]
|
||||
dlcDependees DLCVersion[]
|
||||
|
||||
platformId String
|
||||
platform PlatformLink @relation(fields: [platformId], references: [id])
|
||||
}
|
||||
|
||||
// Platform specific object
|
||||
model ModVersion {
|
||||
versionId String @id
|
||||
version Version @relation(fields: [versionId], references: [versionId], onDelete: Cascade, onUpdate: Cascade)
|
||||
|
||||
dependencies String[]
|
||||
|
||||
platformId String
|
||||
platform PlatformLink @relation(fields: [platformId], references: [id])
|
||||
}
|
||||
|
||||
// A save slot for a game
|
||||
|
||||
@ -14,16 +14,6 @@ export default defineEventHandler(async (h3) => {
|
||||
},
|
||||
include: {
|
||||
versions: {
|
||||
where: {
|
||||
gameVersion: {
|
||||
isNot: null,
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
gameVersion: {
|
||||
versionIndex: "asc",
|
||||
},
|
||||
},
|
||||
omit: {
|
||||
dropletManifest: true,
|
||||
},
|
||||
|
||||
@ -16,7 +16,7 @@ export const ImportRedist = type({
|
||||
"platform?": type({
|
||||
name: "string",
|
||||
icon: "string",
|
||||
fileExts: type("string").pipe.try((s) => JSON.parse(s), type("string[]")),
|
||||
fileExts: type("string").pipe.try((s) => JSON.parse(s), type("string.alphanumeric").array()),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -78,7 +78,7 @@ export default defineEventHandler(async (h3) => {
|
||||
create: {
|
||||
platformName: options.platform.name,
|
||||
iconSvg: svgContent,
|
||||
fileExtensions: options.platform.fileExts,
|
||||
fileExtensions: options.platform.fileExts.map((v) => `.${v}`),
|
||||
},
|
||||
}
|
||||
: undefined),
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
import { type } from "arktype";
|
||||
import { Platform } from "~/prisma/client/enums";
|
||||
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
import { convertIDToLink } from "~/server/internal/platform/link";
|
||||
|
||||
export const LaunchCommands = type({
|
||||
name: "string > 0",
|
||||
description: "string = ''",
|
||||
launchCommand: "string > 0",
|
||||
launchArgs: "string = ''",
|
||||
}).array();
|
||||
name: "string > 0",
|
||||
description: "string = ''",
|
||||
launchCommand: "string > 0",
|
||||
launchArgs: "string = ''",
|
||||
}).array();
|
||||
|
||||
export const ImportVersion = type({
|
||||
id: "string",
|
||||
version: "string",
|
||||
name: "string?",
|
||||
|
||||
platform: type.valueOf(Platform),
|
||||
platform: "string",
|
||||
setup: "string = ''",
|
||||
setupArgs: "string = ''",
|
||||
onlySetup: "boolean = false",
|
||||
@ -33,11 +33,18 @@ export default defineEventHandler(async (h3) => {
|
||||
|
||||
const body = await readDropValidatedBody(h3, ImportVersion);
|
||||
|
||||
const platform = await convertIDToLink(body.platform);
|
||||
if (!platform)
|
||||
throw createError({ statusCode: 400, message: "Invalid platform." });
|
||||
|
||||
if (body.delta) {
|
||||
const validOverlayVersions = await prisma.gameVersion.count({
|
||||
where: {
|
||||
version: { gameId: body.id, platform: body.platform },
|
||||
version: {
|
||||
gameId: body.id,
|
||||
},
|
||||
delta: false,
|
||||
platform,
|
||||
},
|
||||
});
|
||||
if (validOverlayVersions == 0)
|
||||
|
||||
11
server/api/v1/admin/import/version/platforms.get.ts
Normal file
11
server/api/v1/admin/import/version/platforms.get.ts
Normal 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;
|
||||
})
|
||||
@ -1,5 +1,6 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import { convertIDsToPlatforms } from "~/server/internal/platform/link";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["store:read"]);
|
||||
@ -17,7 +18,11 @@ export default defineEventHandler(async (h3) => {
|
||||
include: {
|
||||
versions: {
|
||||
include: {
|
||||
userPlatform: true,
|
||||
gameVersions: {
|
||||
include: {
|
||||
platform: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
publishers: {
|
||||
@ -40,8 +45,7 @@ export default defineEventHandler(async (h3) => {
|
||||
},
|
||||
});
|
||||
|
||||
if (!game)
|
||||
throw createError({ statusCode: 404, message: "Game not found" });
|
||||
if (!game) throw createError({ statusCode: 404, message: "Game not found" });
|
||||
|
||||
const rating = await prisma.gameRating.aggregate({
|
||||
where: {
|
||||
@ -55,5 +59,18 @@ export default defineEventHandler(async (h3) => {
|
||||
},
|
||||
});
|
||||
|
||||
return { game, rating };
|
||||
const platformIDs = game.versions
|
||||
.map((e) => e.gameVersions)
|
||||
.flat()
|
||||
.map((e) => e.platform)
|
||||
.flat()
|
||||
.map((e) => e.id)
|
||||
.filter((e) => e !== null)
|
||||
.filter((v, index, arr) => arr.findIndex((k) => k == v) == index);
|
||||
|
||||
const platforms = await convertIDsToPlatforms(platformIDs);
|
||||
|
||||
const noVersionsGame = { ...game, versions: undefined };
|
||||
|
||||
return { game: noVersionsGame, rating, platforms };
|
||||
});
|
||||
|
||||
@ -2,7 +2,6 @@ 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 { parsePlatform } from "~/server/internal/utils/parseplatform";
|
||||
|
||||
const StoreRead = type({
|
||||
skip: type("string")
|
||||
@ -45,18 +44,19 @@ export default defineEventHandler(async (h3) => {
|
||||
}
|
||||
: undefined;
|
||||
const platformFilter = options.platform
|
||||
? {
|
||||
? ({
|
||||
versions: {
|
||||
some: {
|
||||
platform: {
|
||||
in: options.platform
|
||||
.split(",")
|
||||
.map(parsePlatform)
|
||||
.filter((e) => e !== undefined),
|
||||
},
|
||||
gameVersions: {
|
||||
some: {
|
||||
platform: {
|
||||
id: options.platform
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
} satisfies Prisma.GameWhereInput)
|
||||
: undefined;
|
||||
|
||||
/**
|
||||
|
||||
@ -20,7 +20,7 @@ class DownloadContextManager {
|
||||
where: {
|
||||
gameId: game,
|
||||
versionPath,
|
||||
gameVersion: {
|
||||
game: {
|
||||
isNot: null,
|
||||
},
|
||||
},
|
||||
|
||||
@ -55,47 +55,64 @@ class ManifestGenerator {
|
||||
async generateManifest(versionId: string) {
|
||||
const versions = [];
|
||||
|
||||
const baseVersion = await prisma.version.findUnique({
|
||||
const baseVersion = await prisma.gameVersion.findUnique({
|
||||
where: {
|
||||
versionId,
|
||||
version: {
|
||||
gameId: {
|
||||
not: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
gameVersion: true,
|
||||
platform: true,
|
||||
version: {
|
||||
select: {
|
||||
gameId: true,
|
||||
dropletManifest: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!baseVersion) return undefined;
|
||||
versions.push(baseVersion);
|
||||
|
||||
// Collect other versions if this is a delta
|
||||
if (baseVersion.gameVersion?.delta) {
|
||||
if (baseVersion.delta) {
|
||||
// Start at the same index minus one, and keep grabbing them
|
||||
// until we run out or we hit something that isn't a delta
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
for (let i = baseVersion.gameVersion.versionIndex - 1; true; i--) {
|
||||
const currentVersion = await prisma.version.findFirst({
|
||||
for (let i = baseVersion.versionIndex - 1; true; i--) {
|
||||
const currentVersion = await prisma.gameVersion.findFirst({
|
||||
where: {
|
||||
gameId: baseVersion.gameId,
|
||||
platform: baseVersion.platform,
|
||||
gameVersion: {
|
||||
versionIndex: i,
|
||||
version: {
|
||||
gameId: baseVersion.version.gameId!,
|
||||
},
|
||||
platform: {
|
||||
id: baseVersion.platform.id,
|
||||
},
|
||||
versionIndex: i,
|
||||
},
|
||||
include: {
|
||||
gameVersion: true,
|
||||
version: {
|
||||
select: {
|
||||
dropletManifest: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!currentVersion) return undefined;
|
||||
versions.push(currentVersion);
|
||||
if (!currentVersion.gameVersion?.delta) break;
|
||||
if (!currentVersion?.delta) break;
|
||||
}
|
||||
}
|
||||
versions.reverse();
|
||||
const metadata: DropManifestMetadata[] = versions.map((version) => {
|
||||
const metadata: DropManifestMetadata[] = versions.map((gameVersion) => {
|
||||
return {
|
||||
manifest: JSON.parse(
|
||||
version.dropletManifest?.toString() ?? "{}",
|
||||
gameVersion.version.dropletManifest?.toString() ?? "{}",
|
||||
) as DropManifest,
|
||||
versionId: version.versionId,
|
||||
versionId: gameVersion.versionId,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@ -9,13 +9,12 @@ import path from "path";
|
||||
import prisma from "../db/database";
|
||||
import { fuzzy } from "fast-fuzzy";
|
||||
import taskHandler from "../tasks";
|
||||
import { parsePlatform } from "../utils/parseplatform";
|
||||
import notificationSystem from "../notifications";
|
||||
import { GameNotFoundError, type LibraryProvider } from "./provider";
|
||||
import { logger } from "../logging";
|
||||
import { createHash } from "node:crypto";
|
||||
import type { ImportVersion } from "~/server/api/v1/admin/import/version/index.post";
|
||||
import type { LaunchOptionCreateManyGameVersionInput } from "~/prisma/client/models";
|
||||
import type { LaunchOptionCreateManyInput } from "~/prisma/client/models";
|
||||
|
||||
export function createGameImportTaskId(libraryId: string, libraryPath: string) {
|
||||
return createHash("md5")
|
||||
@ -221,6 +220,8 @@ class LibraryManager {
|
||||
const library = this.libraries.get(game.libraryId);
|
||||
if (!library) return undefined;
|
||||
|
||||
const userPlatforms = await prisma.userPlatform.findMany({});
|
||||
|
||||
const fileExts: { [key: string]: string[] } = {
|
||||
Linux: [
|
||||
// Ext for Unity games
|
||||
@ -239,6 +240,12 @@ class LibraryManager {
|
||||
],
|
||||
};
|
||||
|
||||
for (const platform of userPlatforms) {
|
||||
fileExts[platform.id] = platform.fileExtensions;
|
||||
}
|
||||
|
||||
console.log(fileExts);
|
||||
|
||||
const options: Array<{
|
||||
filename: string;
|
||||
platform: string;
|
||||
@ -299,9 +306,6 @@ class LibraryManager {
|
||||
) {
|
||||
const taskId = createVersionImportTaskId(gameId, versionPath);
|
||||
|
||||
const platform = parsePlatform(metadata.platform);
|
||||
if (!platform) return undefined;
|
||||
|
||||
const game = await prisma.game.findUnique({
|
||||
where: { id: gameId },
|
||||
select: { mName: true, libraryId: true, libraryPath: true },
|
||||
@ -345,17 +349,14 @@ class LibraryManager {
|
||||
versionPath: versionPath,
|
||||
versionName: metadata.name ?? versionPath,
|
||||
dropletManifest: manifest,
|
||||
platform: platform,
|
||||
|
||||
gameVersion: {
|
||||
gameVersions: {
|
||||
create: {
|
||||
versionIndex: currentIndex,
|
||||
delta: metadata.delta,
|
||||
umuIdOverride: metadata.umuId,
|
||||
|
||||
onlySetup: metadata.onlySetup,
|
||||
setupCommand: metadata.setup,
|
||||
setupArgs: metadata.setupArgs,
|
||||
|
||||
launches: {
|
||||
createMany: {
|
||||
@ -364,12 +365,27 @@ class LibraryManager {
|
||||
({
|
||||
name: v.name,
|
||||
description: v.description,
|
||||
launchCommand: v.launchCommand,
|
||||
launchArgs: v.launchArgs,
|
||||
}) satisfies LaunchOptionCreateManyGameVersionInput,
|
||||
command: v.launchCommand,
|
||||
args: v.launchArgs,
|
||||
}) satisfies LaunchOptionCreateManyInput,
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
install: {
|
||||
create: {
|
||||
name: "",
|
||||
description: "",
|
||||
command: metadata.setup,
|
||||
args: metadata.setupArgs,
|
||||
},
|
||||
},
|
||||
|
||||
platform: {
|
||||
connect: {
|
||||
id: metadata.platform,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
47
server/internal/platform/link.ts
Normal file
47
server/internal/platform/link.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { Platform, type HardwarePlatform } from "~/prisma/client/enums";
|
||||
import prisma from "../db/database";
|
||||
import type { PlatformLink } from "~/prisma/client/client";
|
||||
|
||||
export async function convertIDsToPlatforms(platformIDs: string[]) {
|
||||
const userPlatforms = await prisma.userPlatform.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: platformIDs,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const platforms = platformIDs.map(
|
||||
(e) => userPlatforms.find((v) => v.id === e) ?? (e as HardwarePlatform),
|
||||
);
|
||||
|
||||
return platforms;
|
||||
}
|
||||
|
||||
export async function convertIDToLink(
|
||||
id: string,
|
||||
): Promise<PlatformLink | undefined> {
|
||||
const link = await prisma.platformLink.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
if (link) return link;
|
||||
|
||||
if (Platform[id as Platform]) {
|
||||
return await prisma.platformLink.create({
|
||||
data: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const userPlatform = await prisma.userPlatform.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!userPlatform) return undefined;
|
||||
return await prisma.platformLink.create({
|
||||
data: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user