mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-09 20:12:10 +10:00
Compare commits
12 Commits
2087531ace
...
ef244edd1a
| Author | SHA1 | Date | |
|---|---|---|---|
| ef244edd1a | |||
| b083c1e00a | |||
| 5f2cb75666 | |||
| 2a23f4d14c | |||
| b20d355527 | |||
| fa9620eac1 | |||
| a201b62c04 | |||
| 9bf164ab77 | |||
| 97c6f3490c | |||
| f5cb856d3d | |||
| f331661c14 | |||
| 67de1f6c02 |
@ -4,9 +4,10 @@
|
|||||||
v-for="(_, i) in amount"
|
v-for="(_, i) in amount"
|
||||||
:key="i"
|
:key="i"
|
||||||
:class="[
|
:class="[
|
||||||
carousel.currentSlide == i ? 'bg-blue-600 w-6' : 'bg-zinc-700 w-3',
|
carousel.currentSlide === i ? 'bg-blue-600 w-6' : 'bg-zinc-700 w-3',
|
||||||
'transition-all cursor-pointer h-2 rounded-full',
|
'transition-all cursor-pointer h-2 rounded-full',
|
||||||
]"
|
]"
|
||||||
|
@click="slideTo(i)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -18,8 +19,8 @@ const carousel = inject(injectCarousel)!;
|
|||||||
|
|
||||||
const amount = carousel.maxSlide - carousel.minSlide + 1;
|
const amount = carousel.maxSlide - carousel.minSlide + 1;
|
||||||
|
|
||||||
// function slideTo(index: number) {
|
function slideTo(index: number) {
|
||||||
// const offsetIndex = index + carousel.minSlide;
|
const offsetIndex = index + carousel.minSlide;
|
||||||
// carousel.nav.slideTo(offsetIndex);
|
carousel.nav.slideTo(offsetIndex);
|
||||||
// }
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,3 +1,12 @@
|
|||||||
|
<i18n>
|
||||||
|
{
|
||||||
|
"en": {
|
||||||
|
"↓": "↓",
|
||||||
|
"↑": "↑"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</i18n>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
@ -176,9 +185,12 @@
|
|||||||
active ? 'bg-zinc-900 outline-hidden' : '',
|
active ? 'bg-zinc-900 outline-hidden' : '',
|
||||||
'w-full text-left block px-4 py-2 text-sm',
|
'w-full text-left block px-4 py-2 text-sm',
|
||||||
]"
|
]"
|
||||||
@click="() => (currentSort = option.param)"
|
@click.prevent="handleSortClick(option, $event)"
|
||||||
>
|
>
|
||||||
{{ option.name }}
|
{{ option.name }}
|
||||||
|
<span v-if="currentSort === option.param">
|
||||||
|
{{ sortOrder === "asc" ? $t("↑") : $t("↓") }}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</div>
|
</div>
|
||||||
@ -298,7 +310,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="games?.length ?? 0 > 0"
|
v-if="games?.length ?? 0 > 0"
|
||||||
ref="product-grid"
|
ref="product-grid"
|
||||||
class="relative lg:col-span-4 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-7 gap-4"
|
class="relative lg:col-span-4 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4"
|
||||||
>
|
>
|
||||||
<!-- Your content -->
|
<!-- Your content -->
|
||||||
<GamePanel
|
<GamePanel
|
||||||
@ -397,8 +409,13 @@ const sorts: Array<StoreSortOption> = [
|
|||||||
name: "Recently Added",
|
name: "Recently Added",
|
||||||
param: "recent",
|
param: "recent",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Name",
|
||||||
|
param: "name",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
const currentSort = ref(sorts[0].param);
|
const currentSort = ref(sorts[0].param);
|
||||||
|
const sortOrder = ref<"asc" | "desc">("desc");
|
||||||
|
|
||||||
const options: Array<StoreFilterOption> = [
|
const options: Array<StoreFilterOption> = [
|
||||||
...(tags.length > 0
|
...(tags.length > 0
|
||||||
@ -474,7 +491,7 @@ async function updateGames(query: string, resetGames: boolean) {
|
|||||||
results: Array<SerializeObject<GameModel>>;
|
results: Array<SerializeObject<GameModel>>;
|
||||||
count: number;
|
count: number;
|
||||||
}>(
|
}>(
|
||||||
`/api/v1/store?take=50&skip=${resetGames ? 0 : games.value?.length || 0}&sort=${currentSort.value}${query ? "&" + query : ""}`,
|
`/api/v1/store?take=50&skip=${resetGames ? 0 : games.value?.length || 0}&sort=${currentSort.value}&order=${sortOrder.value}${query ? "&" + query : ""}`,
|
||||||
);
|
);
|
||||||
if (resetGames) {
|
if (resetGames) {
|
||||||
games.value = newValues.results;
|
games.value = newValues.results;
|
||||||
@ -491,6 +508,19 @@ watch(filterQuery, (newUrl) => {
|
|||||||
watch(currentSort, (_) => {
|
watch(currentSort, (_) => {
|
||||||
updateGames(filterQuery.value, true);
|
updateGames(filterQuery.value, true);
|
||||||
});
|
});
|
||||||
|
watch(sortOrder, (_) => {
|
||||||
|
updateGames(filterQuery.value, true);
|
||||||
|
});
|
||||||
|
|
||||||
await updateGames(filterQuery.value, true);
|
await updateGames(filterQuery.value, true);
|
||||||
|
|
||||||
|
function handleSortClick(option: StoreSortOption, event: MouseEvent) {
|
||||||
|
event.stopPropagation();
|
||||||
|
if (currentSort.value === option.param) {
|
||||||
|
sortOrder.value = sortOrder.value === "asc" ? "desc" : "asc";
|
||||||
|
} else {
|
||||||
|
currentSort.value = option.param;
|
||||||
|
sortOrder.value = option.param === "name" ? "asc" : "desc";
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -134,34 +134,41 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- setup mode -->
|
<!-- setup mode -->
|
||||||
<SwitchGroup as="div" class="max-w-lg flex items-center justify-between">
|
<fieldset class="max-w-lg">
|
||||||
<span class="flex flex-grow flex-col">
|
<legend class="text-sm/6 font-semibold text-white">
|
||||||
<SwitchLabel
|
Select an import mode
|
||||||
as="span"
|
</legend>
|
||||||
class="text-sm font-medium leading-6 text-zinc-100"
|
<div class="mt-2 grid grid-cols-1 gap-y-6 sm:grid-cols-2 sm:gap-x-4">
|
||||||
passive
|
<label
|
||||||
>{{ $t("library.admin.import.version.setupMode") }}</SwitchLabel
|
v-for="mode in setupModes"
|
||||||
|
:key="mode.id"
|
||||||
|
:aria-label="mode.title"
|
||||||
|
:aria-description="mode.description"
|
||||||
|
class="cursor-pointer group relative flex rounded-lg border border-white/10 bg-zinc-800/50 p-4 has-checked:bg-blue-500/10 has-checked:outline-2 has-checked:-outline-offset-2 has-checked:outline-blue-500 has-focus-visible:outline-3 has-focus-visible:-outline-offset-1 has-disabled:bg-gray-800 has-disabled:opacity-25"
|
||||||
>
|
>
|
||||||
<SwitchDescription as="span" class="text-sm text-zinc-400">{{
|
<input
|
||||||
$t("library.admin.import.version.setupModeDesc")
|
type="radio"
|
||||||
}}</SwitchDescription>
|
name="mode"
|
||||||
</span>
|
:value="mode.id"
|
||||||
<Switch
|
:checked="versionSettings.onlySetup === mode.value"
|
||||||
v-model="versionSettings.onlySetup"
|
class="absolute inset-0 appearance-none opacity-0 focus:outline-none"
|
||||||
:class="[
|
@click="versionSettings.onlySetup = mode.value"
|
||||||
versionSettings.onlySetup ? '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',
|
<div class="flex-1">
|
||||||
]"
|
<span class="block text-sm font-medium text-white">{{
|
||||||
>
|
mode.title
|
||||||
<span
|
}}</span>
|
||||||
aria-hidden="true"
|
<span class="mt-1 block text-xs text-zinc-400">{{
|
||||||
:class="[
|
mode.description
|
||||||
versionSettings.onlySetup ? 'translate-x-5' : 'translate-x-0',
|
}}</span>
|
||||||
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
</div>
|
||||||
]"
|
<CheckCircleIcon
|
||||||
/>
|
class="invisible size-5 text-blue-500 group-has-checked:visible"
|
||||||
</Switch>
|
aria-hidden="true"
|
||||||
</SwitchGroup>
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
<!-- launch commands -->
|
<!-- launch commands -->
|
||||||
<div class="relative max-w-3xl">
|
<div class="relative max-w-3xl">
|
||||||
<label
|
<label
|
||||||
@ -462,10 +469,14 @@ import {
|
|||||||
} from "@headlessui/vue";
|
} from "@headlessui/vue";
|
||||||
import { XCircleIcon } from "@heroicons/vue/16/solid";
|
import { XCircleIcon } from "@heroicons/vue/16/solid";
|
||||||
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
|
||||||
import { PlusIcon, TrashIcon } from "@heroicons/vue/24/outline";
|
import {
|
||||||
|
CheckCircleIcon,
|
||||||
|
PlusIcon,
|
||||||
|
TrashIcon,
|
||||||
|
} from "@heroicons/vue/24/outline";
|
||||||
import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/vue/24/solid";
|
import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/vue/24/solid";
|
||||||
import type { SerializeObject } from "nitropack";
|
import type { SerializeObject } from "nitropack";
|
||||||
import type { ImportVersion } from "~~/server/api/v1/admin/import/version/index.post";
|
import type { ImportGameVersion } from "~~/server/api/v1/admin/import/version/index.post";
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: "admin",
|
layout: "admin",
|
||||||
@ -481,9 +492,10 @@ const versions = await $dropFetch(
|
|||||||
const userPlatforms = await useAdminPlatforms();
|
const userPlatforms = await useAdminPlatforms();
|
||||||
const allPlatforms = renderPlatforms(userPlatforms);
|
const allPlatforms = renderPlatforms(userPlatforms);
|
||||||
const currentlySelectedVersion = ref(-1);
|
const currentlySelectedVersion = ref(-1);
|
||||||
const versionSettings = ref<Partial<typeof ImportVersion.infer>>({
|
|
||||||
id: gameId,
|
const versionSettings = ref<Partial<ImportGameVersion>>({
|
||||||
launches: [],
|
launches: [],
|
||||||
|
onlySetup: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const versionGuesses =
|
const versionGuesses =
|
||||||
@ -540,7 +552,7 @@ async function updateCurrentlySelectedVersion(value: number) {
|
|||||||
const options = await $dropFetch(
|
const options = await $dropFetch(
|
||||||
`/api/v1/admin/import/version/preload?id=${encodeURIComponent(
|
`/api/v1/admin/import/version/preload?id=${encodeURIComponent(
|
||||||
gameId,
|
gameId,
|
||||||
)}&version=${encodeURIComponent(version)}`,
|
)}&version=${encodeURIComponent(version)}&mode=game`,
|
||||||
);
|
);
|
||||||
versionGuesses.value = options.map((e) => ({
|
versionGuesses.value = options.map((e) => ({
|
||||||
...e,
|
...e,
|
||||||
@ -556,6 +568,7 @@ async function startImport() {
|
|||||||
body: {
|
body: {
|
||||||
id: gameId,
|
id: gameId,
|
||||||
version: versions[currentlySelectedVersion.value],
|
version: versions[currentlySelectedVersion.value],
|
||||||
|
mode: "game",
|
||||||
...versionSettings.value,
|
...versionSettings.value,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -572,4 +585,26 @@ function startImport_wrapper() {
|
|||||||
importLoading.value = false;
|
importLoading.value = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setupModes: Array<{
|
||||||
|
id: string;
|
||||||
|
value: boolean;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
}> = [
|
||||||
|
{
|
||||||
|
id: "portable",
|
||||||
|
value: false,
|
||||||
|
title: "Portable",
|
||||||
|
description:
|
||||||
|
"This mode is for games that are designed to be launched directly from the install directory. Drop works best with these.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "setup",
|
||||||
|
value: true,
|
||||||
|
title: "Installer",
|
||||||
|
description:
|
||||||
|
"Also known as 'setup-only', this mode is for installers that modify the system directly, and install to directories like Program Files.",
|
||||||
|
},
|
||||||
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
<template></template>
|
<template><div /></template>
|
||||||
|
|||||||
@ -72,7 +72,7 @@
|
|||||||
{{ $t("store.images") }}
|
{{ $t("store.images") }}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<VueCarousel :items-to-show="1">
|
<VueCarousel :items-to-show="1" :wrap-around="true">
|
||||||
<VueSlide
|
<VueSlide
|
||||||
v-for="image in game.mImageCarouselObjectIds"
|
v-for="image in game.mImageCarouselObjectIds"
|
||||||
:key="image"
|
:key="image"
|
||||||
|
|||||||
@ -190,7 +190,7 @@
|
|||||||
{{ game.mShortDescription }}
|
{{ game.mShortDescription }}
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-6 py-4 rounded">
|
<div class="mt-6 py-4 rounded">
|
||||||
<VueCarousel :items-to-show="1">
|
<VueCarousel :items-to-show="1" :wrap-around="true">
|
||||||
<VueSlide
|
<VueSlide
|
||||||
v-for="image in game.mImageCarouselObjectIds"
|
v-for="image in game.mImageCarouselObjectIds"
|
||||||
:key="image"
|
:key="image"
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export default withNuxt([
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
"@intlify/vue-i18n/no-missing-keys": "error",
|
"@intlify/vue-i18n/no-missing-keys": "error",
|
||||||
"vue/multi-word-component-names": "ignore",
|
"vue/multi-word-component-names": "off",
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
"vue-i18n": {
|
"vue-i18n": {
|
||||||
|
|||||||
@ -263,6 +263,7 @@ export default defineNuxtConfig({
|
|||||||
"https://www.giantbomb.com",
|
"https://www.giantbomb.com",
|
||||||
"https://images.pcgamingwiki.com",
|
"https://images.pcgamingwiki.com",
|
||||||
"https://images.igdb.com",
|
"https://images.igdb.com",
|
||||||
|
"https://*.steamstatic.com",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
strictTransportSecurity: false,
|
strictTransportSecurity: false,
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordapp/twemoji": "^16.0.1",
|
"@discordapp/twemoji": "^16.0.1",
|
||||||
"@drop-oss/droplet": "3.0.1",
|
"@drop-oss/droplet": "3.2.0",
|
||||||
"@headlessui/vue": "^1.7.23",
|
"@headlessui/vue": "^1.7.23",
|
||||||
"@heroicons/vue": "^2.1.5",
|
"@heroicons/vue": "^2.1.5",
|
||||||
"@nuxt/fonts": "^0.11.0",
|
"@nuxt/fonts": "^0.11.0",
|
||||||
@ -32,7 +32,7 @@
|
|||||||
"@vueuse/nuxt": "13.6.0",
|
"@vueuse/nuxt": "13.6.0",
|
||||||
"argon2": "^0.43.0",
|
"argon2": "^0.43.0",
|
||||||
"arktype": "^2.1.10",
|
"arktype": "^2.1.10",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.12.0",
|
||||||
"bcryptjs": "^3.0.2",
|
"bcryptjs": "^3.0.2",
|
||||||
"cheerio": "^1.0.0",
|
"cheerio": "^1.0.0",
|
||||||
"cookie-es": "^2.0.0",
|
"cookie-es": "^2.0.0",
|
||||||
|
|||||||
159
pnpm-lock.yaml
generated
159
pnpm-lock.yaml
generated
@ -12,8 +12,8 @@ importers:
|
|||||||
specifier: ^16.0.1
|
specifier: ^16.0.1
|
||||||
version: 16.0.1
|
version: 16.0.1
|
||||||
'@drop-oss/droplet':
|
'@drop-oss/droplet':
|
||||||
specifier: 3.0.1
|
specifier: 3.2.0
|
||||||
version: 3.0.1
|
version: 3.2.0
|
||||||
'@headlessui/vue':
|
'@headlessui/vue':
|
||||||
specifier: ^1.7.23
|
specifier: ^1.7.23
|
||||||
version: 1.7.23(vue@3.5.21(typescript@5.9.2))
|
version: 1.7.23(vue@3.5.21(typescript@5.9.2))
|
||||||
@ -45,7 +45,7 @@ importers:
|
|||||||
specifier: ^2.1.10
|
specifier: ^2.1.10
|
||||||
version: 2.1.22
|
version: 2.1.22
|
||||||
axios:
|
axios:
|
||||||
specifier: ^1.7.7
|
specifier: ^1.12.0
|
||||||
version: 1.12.2
|
version: 1.12.2
|
||||||
bcryptjs:
|
bcryptjs:
|
||||||
specifier: ^3.0.2
|
specifier: ^3.0.2
|
||||||
@ -387,74 +387,83 @@ packages:
|
|||||||
'@discordapp/twemoji@16.0.1':
|
'@discordapp/twemoji@16.0.1':
|
||||||
resolution: {integrity: sha512-figLiBWzjS5cyrAjLaGjM8AAaowO3qvK8rg5bA2dElB4qsaPMvBVlFDMO2d3x+nC1igt7kgWH4dvNmvvUHUF8w==}
|
resolution: {integrity: sha512-figLiBWzjS5cyrAjLaGjM8AAaowO3qvK8rg5bA2dElB4qsaPMvBVlFDMO2d3x+nC1igt7kgWH4dvNmvvUHUF8w==}
|
||||||
|
|
||||||
'@drop-oss/droplet-darwin-arm64@3.0.1':
|
'@drop-oss/droplet-darwin-arm64@3.2.0':
|
||||||
resolution: {integrity: sha512-LXe8vsXUBL96boI78H6oXpSaPVwF4cCwJ5l/QVtsOWMebNo6gk9wICDZ+5IoR/Ol32t1a1lk+DjbD1zeGenPxg==}
|
resolution: {integrity: sha512-dH/vRFxuLjOzYBBvDG140wKcx4LmFxBJ5iTjZrWzV641wiRjx8B38niWXuqZ2ZADkCL4muOvgRGFJ4W1N/j6jQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@drop-oss/droplet-darwin-universal@3.0.1':
|
'@drop-oss/droplet-darwin-universal@3.2.0':
|
||||||
resolution: {integrity: sha512-Mf2gjC24u6s8djV/3slZvwdr4+h0qBu2OYXBUSDfR4H/VJwV5TstnWVKF+U8d1hjmHE9eLO8elbGNnpQmSoTOQ==}
|
resolution: {integrity: sha512-k7Xhzs2mXrQcm3SLhLNDBkUaCWqtbQ6dyme1ubsG9PZEcvv25T//8CNVFEsHVZTKqj5nF41iSh4Wz1Qn6VxkVw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@drop-oss/droplet-darwin-x64@3.0.1':
|
'@drop-oss/droplet-darwin-x64@3.2.0':
|
||||||
resolution: {integrity: sha512-4IIDl/E+hzZ2Vt9m4FMPlZEXwj1EwE6qXyUidACK6TTFqpjLpsEHKuhv1FOxGyJ8qkvagtyPCc+cs1TxoZD6FA==}
|
resolution: {integrity: sha512-GvRwQrtcC1Dq6YyXxBGSFj+WasnIa1dk9t2lCaR9OQdh3qp2did21o2poo1Sgdjg+mI2lUdgZ6w0yXJlL1vl+A==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@drop-oss/droplet-linux-arm64-gnu@3.0.1':
|
'@drop-oss/droplet-linux-arm64-gnu@3.2.0':
|
||||||
resolution: {integrity: sha512-klGvlLf1xSMT3iYsIAaBbmbir1ZJWtcVyOMUlsfc1lkJ8mgyB+PrW4BsnYj7Pp4G34n7WsOChjC8TdJDBBuBWg==}
|
resolution: {integrity: sha512-ZqH0xTEeSeJF77vy8rZDxHEV8JMaN0khdg6ptpnbBfc56J5jt6wS3NlHK8M0ZVlDqqZnXMS1vUO0b6rfmQodKw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@drop-oss/droplet-linux-arm64-musl@3.0.1':
|
'@drop-oss/droplet-linux-arm64-musl@3.2.0':
|
||||||
resolution: {integrity: sha512-oOjvGETlrJGC1RlNhUoVS9N89Rn/0DqBauVz3BBFjJTKSd5jU3/gLzwgmfkKDGVEU5lyGPAn2WQroiESEG9wdA==}
|
resolution: {integrity: sha512-TTw44PggYfp3RJkvNhXH89duuuvONEA8c8oRBCzCczRf3hDnbzCQLaB1UlnIlESsJZXXiFSDIBV2/0kkpB+Ukg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@drop-oss/droplet-linux-riscv64-gnu@3.0.1':
|
'@drop-oss/droplet-linux-riscv64-gnu@3.2.0':
|
||||||
resolution: {integrity: sha512-Zf3gUsWq9Hqb275MOi7PJDhmJz7Qa/Y1XMen880bxPaOeDFqFOoKUxUr2/qv1MYp6tT3zO27NprGsHirYWqsyA==}
|
resolution: {integrity: sha512-Ee/PfkoG8pm/9C3LFXJleIi5N8V5cK+44p+iDaneAo6gj5k67zYzuga3mJVswTgd3fncG1cw+xPqBl4PUWc1pg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@drop-oss/droplet-linux-x64-gnu@3.0.1':
|
'@drop-oss/droplet-linux-x64-gnu@3.2.0':
|
||||||
resolution: {integrity: sha512-sskblycJdtNJVnRHjPHhwHkQUfQNaDIWDzXOzEaBPOcDKqYA7od7VMDAseqBkrKDn7l8bBUtRXFAipdsO8hffw==}
|
resolution: {integrity: sha512-L2M/MEoe5Y74MTtzpEWHIvdyRSPLgM1WLzpb/xRNCWe8d6FcUFDgdMlbd6rDj5t4Q6JEzyMIHUciVRaYIv+ShA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@drop-oss/droplet-linux-x64-musl@3.0.1':
|
'@drop-oss/droplet-linux-x64-musl@3.2.0':
|
||||||
resolution: {integrity: sha512-lh+1M6UAf5+ET1/ZEFRsB3shFHjkT/9Ql9akr/vyUue91TWPmP71meqVkCugWDhP6lxBt56jg2VVrJfmPAsK6w==}
|
resolution: {integrity: sha512-F/uQUAHWbhiiAtoyKHQHPgjG7jJd8pQX6sCgdf5ufCdwFLvHEdu9pO0qN+xpzaACceIKX4Vip0vUwQwEzYhAKA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@drop-oss/droplet-win32-arm64-msvc@3.0.1':
|
'@drop-oss/droplet-win32-arm64-msvc@3.2.0':
|
||||||
resolution: {integrity: sha512-caQDPoDNJyyJXUEijw+hGTy0wmCrW5efTqBwnvMcQ282EOilg1d5WeJ31pfEcuLYF4MK1t9uaLcG6jZ9YLtzEQ==}
|
resolution: {integrity: sha512-x7i1KKL8vQGcXbKIyH56LCEdQxLKNEk/KFjuD/YGrbBJ/+Q+fh46hLK+Sx4I/HzPHecd5g3xc2kVgO7+DgjhYA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@drop-oss/droplet-win32-x64-msvc@3.0.1':
|
'@drop-oss/droplet-win32-x64-msvc@3.2.0':
|
||||||
resolution: {integrity: sha512-bp8KwewF/T3JkVeJWkg86U3b0cGQD9i8k92x6HYPtnF5nLPAb2UIUEJgmYYFNPFe36RECBV7PIIG0ujdT1ELQw==}
|
resolution: {integrity: sha512-lC8a456IQ0ArzX40IlStolV4GIdl26xF9PikcuQ9r+n4VDqWSHb8A0Wwj87leU3QdoMu+Y2nlA1QHKgpVSEuoQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@drop-oss/droplet@3.0.1':
|
'@drop-oss/droplet@3.2.0':
|
||||||
resolution: {integrity: sha512-YhtgpwNqEHO8R03yf9Xb5LXuaLWkQvY+2lxOD1PwzpGI5V9PKlDE+x1IJBmdBF5bDPDGk9MxQidGtnYQuAEBEA==}
|
resolution: {integrity: sha512-+3zw3MPriMrj8HlKAq2VTlXEPOXN0homusjmQcBRzVx7GjtGvb5Y9YIHs16qfn8zdTEDi5twrtsUBQYkVjU2bQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
|
||||||
'@emnapi/core@1.5.0':
|
'@emnapi/core@1.4.5':
|
||||||
resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==}
|
resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==}
|
||||||
|
|
||||||
'@emnapi/runtime@1.5.0':
|
'@emnapi/core@1.6.0':
|
||||||
resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==}
|
resolution: {integrity: sha512-zq/ay+9fNIJJtJiZxdTnXS20PllcYMX3OE23ESc4HK/bdYu3cOWYVhsOhVnXALfU/uqJIxn5NBPd9z4v+SfoSg==}
|
||||||
|
|
||||||
|
'@emnapi/runtime@1.4.5':
|
||||||
|
resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==}
|
||||||
|
|
||||||
|
'@emnapi/runtime@1.6.0':
|
||||||
|
resolution: {integrity: sha512-obtUmAHTMjll499P+D9A3axeJFlhdjOWdKUNs/U6QIGT7V5RjcUW1xToAzjvmgTSQhDbYn/NwfTRoJcQ2rNBxA==}
|
||||||
|
|
||||||
|
'@emnapi/wasi-threads@1.0.4':
|
||||||
|
resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==}
|
||||||
|
|
||||||
'@emnapi/wasi-threads@1.1.0':
|
'@emnapi/wasi-threads@1.1.0':
|
||||||
resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
|
resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
|
||||||
@ -851,8 +860,8 @@ packages:
|
|||||||
'@napi-rs/wasm-runtime@0.2.12':
|
'@napi-rs/wasm-runtime@0.2.12':
|
||||||
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
|
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@1.0.5':
|
'@napi-rs/wasm-runtime@1.0.7':
|
||||||
resolution: {integrity: sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg==}
|
resolution: {integrity: sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw==}
|
||||||
|
|
||||||
'@nodelib/fs.scandir@2.1.5':
|
'@nodelib/fs.scandir@2.1.5':
|
||||||
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
|
||||||
@ -1804,6 +1813,9 @@ packages:
|
|||||||
'@twemoji/parser@16.0.0':
|
'@twemoji/parser@16.0.0':
|
||||||
resolution: {integrity: sha512-jmuIjkp3OIaEemwMy3sArBwZSuZkRqmueGwRe2Zk4cFzbUJISFBJSZLDUUBNIgq3c+nY49ideYN2OiII6JUqwA==}
|
resolution: {integrity: sha512-jmuIjkp3OIaEemwMy3sArBwZSuZkRqmueGwRe2Zk4cFzbUJISFBJSZLDUUBNIgq3c+nY49ideYN2OiII6JUqwA==}
|
||||||
|
|
||||||
|
'@tybys/wasm-util@0.10.0':
|
||||||
|
resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==}
|
||||||
|
|
||||||
'@tybys/wasm-util@0.10.1':
|
'@tybys/wasm-util@0.10.1':
|
||||||
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||||
|
|
||||||
@ -5802,56 +5814,72 @@ snapshots:
|
|||||||
jsonfile: 5.0.0
|
jsonfile: 5.0.0
|
||||||
universalify: 0.1.2
|
universalify: 0.1.2
|
||||||
|
|
||||||
'@drop-oss/droplet-darwin-arm64@3.0.1':
|
'@drop-oss/droplet-darwin-arm64@3.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@drop-oss/droplet-darwin-universal@3.0.1':
|
'@drop-oss/droplet-darwin-universal@3.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@drop-oss/droplet-darwin-x64@3.0.1':
|
'@drop-oss/droplet-darwin-x64@3.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@drop-oss/droplet-linux-arm64-gnu@3.0.1':
|
'@drop-oss/droplet-linux-arm64-gnu@3.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@drop-oss/droplet-linux-arm64-musl@3.0.1':
|
'@drop-oss/droplet-linux-arm64-musl@3.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@drop-oss/droplet-linux-riscv64-gnu@3.0.1':
|
'@drop-oss/droplet-linux-riscv64-gnu@3.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@drop-oss/droplet-linux-x64-gnu@3.0.1':
|
'@drop-oss/droplet-linux-x64-gnu@3.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@drop-oss/droplet-linux-x64-musl@3.0.1':
|
'@drop-oss/droplet-linux-x64-musl@3.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@drop-oss/droplet-win32-arm64-msvc@3.0.1':
|
'@drop-oss/droplet-win32-arm64-msvc@3.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@drop-oss/droplet-win32-x64-msvc@3.0.1':
|
'@drop-oss/droplet-win32-x64-msvc@3.2.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@drop-oss/droplet@3.0.1':
|
'@drop-oss/droplet@3.2.0':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@drop-oss/droplet-darwin-arm64': 3.0.1
|
'@drop-oss/droplet-darwin-arm64': 3.2.0
|
||||||
'@drop-oss/droplet-darwin-universal': 3.0.1
|
'@drop-oss/droplet-darwin-universal': 3.2.0
|
||||||
'@drop-oss/droplet-darwin-x64': 3.0.1
|
'@drop-oss/droplet-darwin-x64': 3.2.0
|
||||||
'@drop-oss/droplet-linux-arm64-gnu': 3.0.1
|
'@drop-oss/droplet-linux-arm64-gnu': 3.2.0
|
||||||
'@drop-oss/droplet-linux-arm64-musl': 3.0.1
|
'@drop-oss/droplet-linux-arm64-musl': 3.2.0
|
||||||
'@drop-oss/droplet-linux-riscv64-gnu': 3.0.1
|
'@drop-oss/droplet-linux-riscv64-gnu': 3.2.0
|
||||||
'@drop-oss/droplet-linux-x64-gnu': 3.0.1
|
'@drop-oss/droplet-linux-x64-gnu': 3.2.0
|
||||||
'@drop-oss/droplet-linux-x64-musl': 3.0.1
|
'@drop-oss/droplet-linux-x64-musl': 3.2.0
|
||||||
'@drop-oss/droplet-win32-arm64-msvc': 3.0.1
|
'@drop-oss/droplet-win32-arm64-msvc': 3.2.0
|
||||||
'@drop-oss/droplet-win32-x64-msvc': 3.0.1
|
'@drop-oss/droplet-win32-x64-msvc': 3.2.0
|
||||||
|
|
||||||
'@emnapi/core@1.5.0':
|
'@emnapi/core@1.4.5':
|
||||||
|
dependencies:
|
||||||
|
'@emnapi/wasi-threads': 1.0.4
|
||||||
|
tslib: 2.8.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@emnapi/core@1.6.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/wasi-threads': 1.1.0
|
'@emnapi/wasi-threads': 1.1.0
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@emnapi/runtime@1.5.0':
|
'@emnapi/runtime@1.4.5':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@emnapi/runtime@1.6.0':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@emnapi/wasi-threads@1.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
optional: true
|
optional: true
|
||||||
@ -6228,15 +6256,15 @@ snapshots:
|
|||||||
|
|
||||||
'@napi-rs/wasm-runtime@0.2.12':
|
'@napi-rs/wasm-runtime@0.2.12':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/core': 1.5.0
|
'@emnapi/core': 1.4.5
|
||||||
'@emnapi/runtime': 1.5.0
|
'@emnapi/runtime': 1.4.5
|
||||||
'@tybys/wasm-util': 0.10.1
|
'@tybys/wasm-util': 0.10.0
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@napi-rs/wasm-runtime@1.0.5':
|
'@napi-rs/wasm-runtime@1.0.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@emnapi/core': 1.5.0
|
'@emnapi/core': 1.6.0
|
||||||
'@emnapi/runtime': 1.5.0
|
'@emnapi/runtime': 1.6.0
|
||||||
'@tybys/wasm-util': 0.10.1
|
'@tybys/wasm-util': 0.10.1
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -6722,7 +6750,7 @@ snapshots:
|
|||||||
|
|
||||||
'@oxc-minify/binding-wasm32-wasi@0.87.0':
|
'@oxc-minify/binding-wasm32-wasi@0.87.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@napi-rs/wasm-runtime': 1.0.5
|
'@napi-rs/wasm-runtime': 1.0.7
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@oxc-minify/binding-win32-arm64-msvc@0.87.0':
|
'@oxc-minify/binding-win32-arm64-msvc@0.87.0':
|
||||||
@ -6807,7 +6835,7 @@ snapshots:
|
|||||||
|
|
||||||
'@oxc-parser/binding-wasm32-wasi@0.87.0':
|
'@oxc-parser/binding-wasm32-wasi@0.87.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@napi-rs/wasm-runtime': 1.0.5
|
'@napi-rs/wasm-runtime': 1.0.7
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@oxc-parser/binding-win32-arm64-msvc@0.70.0':
|
'@oxc-parser/binding-win32-arm64-msvc@0.70.0':
|
||||||
@ -6870,7 +6898,7 @@ snapshots:
|
|||||||
|
|
||||||
'@oxc-transform/binding-wasm32-wasi@0.87.0':
|
'@oxc-transform/binding-wasm32-wasi@0.87.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@napi-rs/wasm-runtime': 1.0.5
|
'@napi-rs/wasm-runtime': 1.0.7
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@oxc-transform/binding-win32-arm64-msvc@0.87.0':
|
'@oxc-transform/binding-win32-arm64-msvc@0.87.0':
|
||||||
@ -7258,6 +7286,11 @@ snapshots:
|
|||||||
|
|
||||||
'@twemoji/parser@16.0.0': {}
|
'@twemoji/parser@16.0.0': {}
|
||||||
|
|
||||||
|
'@tybys/wasm-util@0.10.0':
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@tybys/wasm-util@0.10.1':
|
'@tybys/wasm-util@0.10.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
onlyBuiltDependencies:
|
onlyBuiltDependencies:
|
||||||
|
- '@prisma/client'
|
||||||
|
- '@prisma/engines'
|
||||||
|
- '@tailwindcss/oxide'
|
||||||
- esbuild
|
- esbuild
|
||||||
|
- prisma
|
||||||
|
|
||||||
shamefullyHoist: true
|
shamefullyHoist: true
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
-- AlterEnum
|
||||||
|
ALTER TYPE "MetadataSource" ADD VALUE 'Steam';
|
||||||
|
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "GameTag_name_idx";
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "GameTag_name_idx" ON "GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
|
||||||
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- A unique constraint covering the columns `[installRId]` on the table `LaunchOption` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- A unique constraint covering the columns `[uninstallRId]` on the table `LaunchOption` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- A unique constraint covering the columns `[installId]` on the table `RedistVersion` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- A unique constraint covering the columns `[uninstallId]` on the table `RedistVersion` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "public"."GameTag_name_idx";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "public"."LaunchOption" ADD COLUMN "installRId" TEXT,
|
||||||
|
ADD COLUMN "uninstallRId" TEXT;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "public"."RedistVersion" ADD COLUMN "installId" TEXT,
|
||||||
|
ADD COLUMN "onlySetup" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
ADD COLUMN "uninstallId" TEXT;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "GameTag_name_idx" ON "public"."GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "LaunchOption_installRId_key" ON "public"."LaunchOption"("installRId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "LaunchOption_uninstallRId_key" ON "public"."LaunchOption"("uninstallRId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "RedistVersion_installId_key" ON "public"."RedistVersion"("installId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "RedistVersion_uninstallId_key" ON "public"."RedistVersion"("uninstallId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "public"."RedistVersion" ADD CONSTRAINT "RedistVersion_installId_fkey" FOREIGN KEY ("installId") REFERENCES "public"."LaunchOption"("launchId") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "public"."RedistVersion" ADD CONSTRAINT "RedistVersion_uninstallId_fkey" FOREIGN KEY ("uninstallId") REFERENCES "public"."LaunchOption"("launchId") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Added the required column `versionIndex` to the `RedistVersion` 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"."RedistVersion" ADD COLUMN "delta" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
ADD COLUMN "versionIndex" INTEGER NOT NULL;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "GameTag_name_idx" ON "public"."GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
|
||||||
@ -49,6 +49,11 @@ model LaunchOption {
|
|||||||
uninstallGId String? @unique
|
uninstallGId String? @unique
|
||||||
uninstallGVersion GameVersion? @relation(name: "uninstall")
|
uninstallGVersion GameVersion? @relation(name: "uninstall")
|
||||||
|
|
||||||
|
installRId String? @unique
|
||||||
|
installRVersion RedistVersion? @relation(name: "install_redist")
|
||||||
|
uninstallRId String? @unique
|
||||||
|
uninstallRVersion RedistVersion? @relation(name: "uninstall_redist")
|
||||||
|
|
||||||
name String
|
name String
|
||||||
description String
|
description String
|
||||||
|
|
||||||
@ -125,8 +130,17 @@ model RedistVersion {
|
|||||||
versionId String @id
|
versionId String @id
|
||||||
version Version @relation(fields: [versionId], references: [versionId], onDelete: Cascade, onUpdate: Cascade)
|
version Version @relation(fields: [versionId], references: [versionId], onDelete: Cascade, onUpdate: Cascade)
|
||||||
|
|
||||||
|
installId String? @unique
|
||||||
|
install LaunchOption? @relation(name: "install_redist", fields: [installId], references: [launchId])
|
||||||
|
uninstallId String? @unique
|
||||||
|
uninstall LaunchOption? @relation(name: "uninstall_redist", fields: [uninstallId], references: [launchId])
|
||||||
|
onlySetup Boolean @default(false)
|
||||||
|
|
||||||
launches LaunchOption[]
|
launches LaunchOption[]
|
||||||
|
|
||||||
|
versionIndex Int
|
||||||
|
delta Boolean @default(false)
|
||||||
|
|
||||||
gameDependees GameVersion[]
|
gameDependees GameVersion[]
|
||||||
dlcDependees DLCVersion[]
|
dlcDependees DLCVersion[]
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@ enum MetadataSource {
|
|||||||
IGDB
|
IGDB
|
||||||
Metacritic
|
Metacritic
|
||||||
OpenCritic
|
OpenCritic
|
||||||
|
Steam
|
||||||
}
|
}
|
||||||
|
|
||||||
model Game {
|
model Game {
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
|
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
|
||||||
import aclManager from "~~/server/internal/acls";
|
import aclManager from "~~/server/internal/acls";
|
||||||
import prisma from "~~/server/internal/db/database";
|
|
||||||
import libraryManager from "~~/server/internal/library";
|
import libraryManager from "~~/server/internal/library";
|
||||||
import { convertIDToLink } from "~~/server/internal/platform/link";
|
|
||||||
|
|
||||||
export const LaunchCommands = type({
|
export const LaunchCommands = type({
|
||||||
name: "string > 0",
|
name: "string > 0",
|
||||||
@ -12,14 +10,18 @@ export const LaunchCommands = type({
|
|||||||
launchArgs: "string = ''",
|
launchArgs: "string = ''",
|
||||||
}).array();
|
}).array();
|
||||||
|
|
||||||
export const ImportVersion = type({
|
const ImportVersionBase = type({
|
||||||
id: "string",
|
id: "string",
|
||||||
version: "string",
|
version: "string",
|
||||||
name: "string?",
|
name: "string?",
|
||||||
|
|
||||||
platform: "string",
|
platform: "string",
|
||||||
onlySetup: "boolean = false",
|
|
||||||
delta: "boolean = false",
|
delta: "boolean = false",
|
||||||
|
});
|
||||||
|
|
||||||
|
const ImportGameVersion = type({
|
||||||
|
mode: "'game'",
|
||||||
|
onlySetup: "boolean = false",
|
||||||
umuId: "string = ''",
|
umuId: "string = ''",
|
||||||
|
|
||||||
install: "string?",
|
install: "string?",
|
||||||
@ -27,7 +29,26 @@ export const ImportVersion = type({
|
|||||||
launches: LaunchCommands,
|
launches: LaunchCommands,
|
||||||
uninstall: "string?",
|
uninstall: "string?",
|
||||||
uninstallArgs: "string?",
|
uninstallArgs: "string?",
|
||||||
}).configure(throwingArktype);
|
});
|
||||||
|
|
||||||
|
const ImportRedistVersion = type({
|
||||||
|
mode: "'redist'",
|
||||||
|
install: "string?",
|
||||||
|
installArgs: "string?",
|
||||||
|
launches: LaunchCommands,
|
||||||
|
uninstall: "string?",
|
||||||
|
uninstallArgs: "string?",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ImportVersion = ImportVersionBase.and(
|
||||||
|
ImportGameVersion.or(ImportRedistVersion),
|
||||||
|
).configure(throwingArktype);
|
||||||
|
|
||||||
|
export type ImportGameVersion = typeof ImportVersionBase.infer &
|
||||||
|
typeof ImportGameVersion.infer;
|
||||||
|
|
||||||
|
export type ImportRedistVersion = typeof ImportVersionBase.infer &
|
||||||
|
typeof ImportRedistVersion.infer;
|
||||||
|
|
||||||
export default defineEventHandler(async (h3) => {
|
export default defineEventHandler(async (h3) => {
|
||||||
const allowed = await aclManager.allowSystemACL(h3, ["import:version:new"]);
|
const allowed = await aclManager.allowSystemACL(h3, ["import:version:new"]);
|
||||||
@ -35,48 +56,10 @@ export default defineEventHandler(async (h3) => {
|
|||||||
|
|
||||||
const body = await readDropValidatedBody(h3, ImportVersion);
|
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,
|
|
||||||
},
|
|
||||||
delta: false,
|
|
||||||
platform,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (validOverlayVersions == 0)
|
|
||||||
throw createError({
|
|
||||||
statusCode: 400,
|
|
||||||
message:
|
|
||||||
"Update mode requires a pre-existing version for this platform.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (body.onlySetup) {
|
|
||||||
if (!body.install)
|
|
||||||
throw createError({
|
|
||||||
statusCode: 400,
|
|
||||||
message: 'Install required in "setup mode".',
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (!body.delta && body.launches.length == 0)
|
|
||||||
throw createError({
|
|
||||||
statusCode: 400,
|
|
||||||
message:
|
|
||||||
"At least one launch command is required for non-delta versions",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// startup & delta require more complex checking logic
|
// startup & delta require more complex checking logic
|
||||||
const taskId = await libraryManager.importVersion(
|
const taskId = await libraryManager.importVersion(
|
||||||
body.id,
|
body.id,
|
||||||
body.version,
|
body.version,
|
||||||
"game",
|
|
||||||
body,
|
body,
|
||||||
);
|
);
|
||||||
if (!taskId)
|
if (!taskId)
|
||||||
|
|||||||
@ -1,22 +1,26 @@
|
|||||||
|
import { ArkErrors, type } from "arktype";
|
||||||
import aclManager from "~~/server/internal/acls";
|
import aclManager from "~~/server/internal/acls";
|
||||||
import libraryManager from "~~/server/internal/library";
|
import libraryManager, { VersionImportModes } from "~~/server/internal/library";
|
||||||
|
|
||||||
|
export const PreloadQuery = type({
|
||||||
|
id: "string",
|
||||||
|
version: "string",
|
||||||
|
mode: type.enumerated(...VersionImportModes),
|
||||||
|
});
|
||||||
|
|
||||||
export default defineEventHandler(async (h3) => {
|
export default defineEventHandler(async (h3) => {
|
||||||
const allowed = await aclManager.allowSystemACL(h3, ["import:version:read"]);
|
const allowed = await aclManager.allowSystemACL(h3, ["import:version:read"]);
|
||||||
if (!allowed) throw createError({ statusCode: 403 });
|
if (!allowed) throw createError({ statusCode: 403 });
|
||||||
|
|
||||||
const query = await getQuery(h3);
|
const rawQuery = await getQuery(h3);
|
||||||
const gameId = query.id?.toString();
|
const query = PreloadQuery(rawQuery);
|
||||||
const versionName = query.version?.toString();
|
if (query instanceof ArkErrors)
|
||||||
if (!gameId || !versionName)
|
throw createError({ statusCode: 400, message: query.summary });
|
||||||
throw createError({
|
|
||||||
statusCode: 400,
|
|
||||||
message: "Missing id or version in request params",
|
|
||||||
});
|
|
||||||
|
|
||||||
const preload = await libraryManager.fetchUnimportedVersionInformation(
|
const preload = await libraryManager.fetchUnimportedVersionInformation(
|
||||||
gameId,
|
query.id,
|
||||||
versionName,
|
query.mode,
|
||||||
|
query.version,
|
||||||
);
|
);
|
||||||
if (!preload)
|
if (!preload)
|
||||||
throw createError({
|
throw createError({
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { type } from "arktype";
|
|||||||
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
|
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
|
||||||
import aclManager from "~~/server/internal/acls";
|
import aclManager from "~~/server/internal/acls";
|
||||||
import taskHandler from "~~/server/internal/tasks";
|
import taskHandler from "~~/server/internal/tasks";
|
||||||
import { TASK_GROUPS, type TaskGroup } from "~~/server/internal/tasks/group";
|
import { TASK_GROUPS } from "~~/server/internal/tasks/group";
|
||||||
|
|
||||||
const StartTask = type({
|
const StartTask = type({
|
||||||
taskGroup: type.enumerated(...TASK_GROUPS),
|
taskGroup: type.enumerated(...TASK_GROUPS),
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
import { ClientCapabilities } from "~~/prisma/client/enums";
|
import type { ClientCapabilities } from "~~/prisma/client/enums";
|
||||||
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
|
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
|
||||||
import type {
|
import type {
|
||||||
CapabilityConfiguration,
|
CapabilityConfiguration,
|
||||||
@ -7,7 +7,7 @@ import type {
|
|||||||
import capabilityManager, {
|
import capabilityManager, {
|
||||||
validCapabilities,
|
validCapabilities,
|
||||||
} from "~~/server/internal/clients/capabilities";
|
} from "~~/server/internal/clients/capabilities";
|
||||||
import clientHandler, { AuthMode, AuthModes } from "~~/server/internal/clients/handler";
|
import clientHandler, { AuthModes } from "~~/server/internal/clients/handler";
|
||||||
import { parsePlatform } from "~~/server/internal/utils/parseplatform";
|
import { parsePlatform } from "~~/server/internal/utils/parseplatform";
|
||||||
|
|
||||||
const ClientAuthInitiate = type({
|
const ClientAuthInitiate = type({
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import { type } from "arktype";
|
import { type } from "arktype";
|
||||||
import { ClientCapabilities } from "~~/prisma/client/enums";
|
import { ClientCapabilities } from "~~/prisma/client/enums";
|
||||||
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
|
import { readDropValidatedBody, throwingArktype } from "~~/server/arktype";
|
||||||
import capabilityManager, {
|
import capabilityManager from "~~/server/internal/clients/capabilities";
|
||||||
validCapabilities,
|
|
||||||
} from "~~/server/internal/clients/capabilities";
|
|
||||||
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
|
import { defineClientEventHandler } from "~~/server/internal/clients/event-handler";
|
||||||
import notificationSystem from "~~/server/internal/notifications";
|
import notificationSystem from "~~/server/internal/notifications";
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,8 @@ const StoreRead = type({
|
|||||||
company: "string?",
|
company: "string?",
|
||||||
companyActions: "string = 'published,developed'",
|
companyActions: "string = 'published,developed'",
|
||||||
|
|
||||||
sort: "'default' | 'newest' | 'recent' = 'default'",
|
sort: "'default' | 'newest' | 'recent' | 'name' = 'default'",
|
||||||
|
order: "'asc' | 'desc' = 'desc'",
|
||||||
});
|
});
|
||||||
|
|
||||||
export default defineEventHandler(async (h3) => {
|
export default defineEventHandler(async (h3) => {
|
||||||
@ -101,10 +102,13 @@ export default defineEventHandler(async (h3) => {
|
|||||||
switch (options.sort) {
|
switch (options.sort) {
|
||||||
case "default":
|
case "default":
|
||||||
case "newest":
|
case "newest":
|
||||||
sort.mReleased = "desc";
|
sort.mReleased = options.order;
|
||||||
break;
|
break;
|
||||||
case "recent":
|
case "recent":
|
||||||
sort.created = "desc";
|
sort.created = options.order;
|
||||||
|
break;
|
||||||
|
case "name":
|
||||||
|
sort.mName = options.order;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,10 @@ import type {
|
|||||||
GameVersionCreateInput,
|
GameVersionCreateInput,
|
||||||
LaunchOptionCreateManyInput,
|
LaunchOptionCreateManyInput,
|
||||||
VersionCreateArgs,
|
VersionCreateArgs,
|
||||||
|
VersionWhereInput,
|
||||||
} from "~~/prisma/client/models";
|
} from "~~/prisma/client/models";
|
||||||
|
import type { PlatformLink } from "~~/prisma/client/client";
|
||||||
|
import { convertIDToLink } from "../platform/link";
|
||||||
|
|
||||||
export const VersionImportModes = ["game", "redist"] as const;
|
export const VersionImportModes = ["game", "redist"] as const;
|
||||||
export type VersionImportMode = (typeof VersionImportModes)[number];
|
export type VersionImportMode = (typeof VersionImportModes)[number];
|
||||||
@ -216,7 +219,17 @@ class LibraryManager {
|
|||||||
return await this.fetchLibraryObjectWithStatus(redists);
|
return await this.fetchLibraryObjectWithStatus(redists);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchLibraryPath(id: string, mode: VersionImportMode) {
|
private async fetchLibraryPath(
|
||||||
|
id: string,
|
||||||
|
mode: VersionImportMode,
|
||||||
|
platform?: PlatformLink,
|
||||||
|
): Promise<
|
||||||
|
| [
|
||||||
|
{ mName: string; libraryId: string; libraryPath: string } | null,
|
||||||
|
VersionWhereInput,
|
||||||
|
]
|
||||||
|
| undefined
|
||||||
|
> {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case "game":
|
case "game":
|
||||||
return [
|
return [
|
||||||
@ -224,8 +237,8 @@ class LibraryManager {
|
|||||||
where: { id },
|
where: { id },
|
||||||
select: { mName: true, libraryId: true, libraryPath: true },
|
select: { mName: true, libraryId: true, libraryPath: true },
|
||||||
}),
|
}),
|
||||||
{ gameId: id },
|
{ gameId: id, gameVersions: { some: { platform } } },
|
||||||
] as const;
|
];
|
||||||
case "redist":
|
case "redist":
|
||||||
return [
|
return [
|
||||||
await prisma.redist.findUnique({
|
await prisma.redist.findUnique({
|
||||||
@ -233,7 +246,7 @@ class LibraryManager {
|
|||||||
select: { mName: true, libraryId: true, libraryPath: true },
|
select: { mName: true, libraryId: true, libraryPath: true },
|
||||||
}),
|
}),
|
||||||
{ redistId: id },
|
{ redistId: id },
|
||||||
] as const;
|
];
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -241,11 +254,10 @@ class LibraryManager {
|
|||||||
private createVersionOptions(
|
private createVersionOptions(
|
||||||
id: string,
|
id: string,
|
||||||
currentIndex: number,
|
currentIndex: number,
|
||||||
mode: VersionImportMode,
|
|
||||||
metadata: typeof ImportVersion.infer,
|
metadata: typeof ImportVersion.infer,
|
||||||
): Partial<VersionCreateArgs["data"]> {
|
): Partial<VersionCreateArgs["data"]> {
|
||||||
switch (mode) {
|
switch (metadata.mode) {
|
||||||
case "game":
|
case "game": {
|
||||||
const installCreator = {
|
const installCreator = {
|
||||||
install: {
|
install: {
|
||||||
create: {
|
create: {
|
||||||
@ -303,6 +315,7 @@ class LibraryManager {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
case "redist":
|
case "redist":
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@ -310,18 +323,20 @@ class LibraryManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches recommendations and extra data about the version. Doesn't actually check if it's been imported.
|
* Fetches recommendations and extra data about the version. Doesn't actually check if it's been imported.
|
||||||
* @param gameId
|
* @param id
|
||||||
* @param versionName
|
* @param version
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async fetchUnimportedVersionInformation(gameId: string, versionName: string) {
|
async fetchUnimportedVersionInformation(
|
||||||
const game = await prisma.game.findUnique({
|
id: string,
|
||||||
where: { id: gameId },
|
mode: VersionImportMode,
|
||||||
select: { libraryPath: true, libraryId: true, mName: true },
|
version: string,
|
||||||
});
|
) {
|
||||||
if (!game || !game.libraryId) return undefined;
|
const value = await this.fetchLibraryPath(id, mode);
|
||||||
|
if (!value?.[0] || !value[0].libraryId) return undefined;
|
||||||
|
const [libraryDetails] = value;
|
||||||
|
|
||||||
const library = this.libraries.get(game.libraryId);
|
const library = this.libraries.get(libraryDetails.libraryId);
|
||||||
if (!library) return undefined;
|
if (!library) return undefined;
|
||||||
|
|
||||||
const userPlatforms = await prisma.userPlatform.findMany({});
|
const userPlatforms = await prisma.userPlatform.findMany({});
|
||||||
@ -354,7 +369,10 @@ class LibraryManager {
|
|||||||
match: number;
|
match: number;
|
||||||
}> = [];
|
}> = [];
|
||||||
|
|
||||||
const files = await library.versionReaddir(game.libraryPath, versionName);
|
const files = await library.versionReaddir(
|
||||||
|
libraryDetails.libraryPath,
|
||||||
|
version,
|
||||||
|
);
|
||||||
for (const filename of files) {
|
for (const filename of files) {
|
||||||
const basename = path.basename(filename);
|
const basename = path.basename(filename);
|
||||||
const dotLocation = filename.lastIndexOf(".");
|
const dotLocation = filename.lastIndexOf(".");
|
||||||
@ -363,7 +381,7 @@ class LibraryManager {
|
|||||||
for (const [platform, checkExts] of Object.entries(fileExts)) {
|
for (const [platform, checkExts] of Object.entries(fileExts)) {
|
||||||
for (const checkExt of checkExts) {
|
for (const checkExt of checkExts) {
|
||||||
if (checkExt != ext) continue;
|
if (checkExt != ext) continue;
|
||||||
const fuzzyValue = fuzzy(basename, game.mName);
|
const fuzzyValue = fuzzy(basename, libraryDetails.mName);
|
||||||
options.push({
|
options.push({
|
||||||
filename,
|
filename,
|
||||||
platform,
|
platform,
|
||||||
@ -404,17 +422,56 @@ class LibraryManager {
|
|||||||
async importVersion(
|
async importVersion(
|
||||||
id: string,
|
id: string,
|
||||||
version: string,
|
version: string,
|
||||||
mode: VersionImportMode,
|
|
||||||
metadata: typeof ImportVersion.infer,
|
metadata: typeof ImportVersion.infer,
|
||||||
) {
|
) {
|
||||||
const taskId = createVersionImportTaskId(id, version);
|
const taskId = createVersionImportTaskId(id, version);
|
||||||
|
|
||||||
const value = await this.fetchLibraryPath(id, mode);
|
if (metadata.mode === "game") {
|
||||||
if (!value || !value[0]) return undefined;
|
if (metadata.onlySetup) {
|
||||||
|
if (!metadata.install)
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
message: "An install command is required in only-setup mode.",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (!metadata.delta && metadata.launches.length == 0)
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
message:
|
||||||
|
"At least one launch command is required in non-delta, non-setup mode.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const platform = await convertIDToLink(metadata.platform);
|
||||||
|
if (!platform)
|
||||||
|
throw createError({ statusCode: 400, message: "Invalid platform." });
|
||||||
|
|
||||||
|
const value = await this.fetchLibraryPath(id, metadata.mode, platform);
|
||||||
|
if (!value || !value[0])
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
message: `${metadata.mode} not found.`,
|
||||||
|
});
|
||||||
const [libraryDetails, idFilter] = value;
|
const [libraryDetails, idFilter] = value;
|
||||||
|
|
||||||
const library = this.libraries.get(libraryDetails.libraryId);
|
const library = this.libraries.get(libraryDetails.libraryId);
|
||||||
if (!library) return undefined;
|
if (!library)
|
||||||
|
throw createError({
|
||||||
|
statusCode: 500,
|
||||||
|
message: "Library not found but exists in database?",
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentIndex = await prisma.version.count({
|
||||||
|
where: { ...idFilter },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (metadata.delta && currentIndex == 0)
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
message:
|
||||||
|
"At least one pre-existing version of the same platform is required for delta mode.",
|
||||||
|
});
|
||||||
|
|
||||||
taskHandler.create({
|
taskHandler.create({
|
||||||
id: taskId,
|
id: taskId,
|
||||||
@ -439,10 +496,6 @@ class LibraryManager {
|
|||||||
|
|
||||||
logger.info("Created manifest successfully!");
|
logger.info("Created manifest successfully!");
|
||||||
|
|
||||||
const currentIndex = await prisma.version.count({
|
|
||||||
where: { ...idFilter },
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then, create the database object
|
// Then, create the database object
|
||||||
await prisma.version.create({
|
await prisma.version.create({
|
||||||
data: {
|
data: {
|
||||||
@ -450,7 +503,7 @@ class LibraryManager {
|
|||||||
versionName: metadata.name ?? version,
|
versionName: metadata.name ?? version,
|
||||||
dropletManifest: manifest,
|
dropletManifest: manifest,
|
||||||
|
|
||||||
...libraryManager.createVersionOptions(id, currentIndex, mode, metadata)
|
...libraryManager.createVersionOptions(id, currentIndex, metadata),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -460,7 +513,7 @@ class LibraryManager {
|
|||||||
nonce: `version-create-${id}-${version}`,
|
nonce: `version-create-${id}-${version}`,
|
||||||
title: `'${libraryDetails.mName}' ('${version}') finished importing.`,
|
title: `'${libraryDetails.mName}' ('${version}') finished importing.`,
|
||||||
description: `Drop finished importing version ${version} for ${libraryDetails.mName}.`,
|
description: `Drop finished importing version ${version} for ${libraryDetails.mName}.`,
|
||||||
actions: [`View|/admin/library/${modeToLink[mode]}/${id}`],
|
actions: [`View|/admin/library/${modeToLink[metadata.mode]}/${id}`],
|
||||||
acls: ["system:import:version:read"],
|
acls: ["system:import:version:read"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -72,7 +72,7 @@ interface IGDBCompanyWebsite extends IGDBItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IGDBCover extends IGDBItem {
|
interface IGDBCover extends IGDBItem {
|
||||||
url: string;
|
image_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IGDBSearchStub extends IGDBItem {
|
interface IGDBSearchStub extends IGDBItem {
|
||||||
@ -179,7 +179,7 @@ export class IGDBProvider implements MetadataProvider {
|
|||||||
|
|
||||||
if (response.status !== 200)
|
if (response.status !== 200)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Error in IDGB \nStatus Code: ${response.status}\n${response.data}`,
|
`Error in IGDB \nStatus Code: ${response.status}\n${response.data}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.accessToken = response.data.access_token;
|
this.accessToken = response.data.access_token;
|
||||||
@ -187,7 +187,7 @@ export class IGDBProvider implements MetadataProvider {
|
|||||||
seconds: response.data.expires_in,
|
seconds: response.data.expires_in,
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info("IDGB done authorizing with twitch");
|
logger.info("IGDB done authorizing with twitch");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async refreshCredentials() {
|
private async refreshCredentials() {
|
||||||
@ -246,39 +246,47 @@ export class IGDBProvider implements MetadataProvider {
|
|||||||
return <T[]>response.data;
|
return <T[]>response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _getMediaInternal(mediaID: IGDBID, type: string) {
|
private async _getMediaInternal(
|
||||||
|
mediaID: IGDBID,
|
||||||
|
type: string,
|
||||||
|
size: string = "t_thumb",
|
||||||
|
) {
|
||||||
if (mediaID === undefined)
|
if (mediaID === undefined)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`IGDB mediaID when getting item of type ${type} was undefined`,
|
`IGDB mediaID when getting item of type ${type} was undefined`,
|
||||||
);
|
);
|
||||||
|
|
||||||
const body = `where id = ${mediaID}; fields url;`;
|
const body = `where id = ${mediaID}; fields image_id;`;
|
||||||
const response = await this.request<IGDBCover>(type, body);
|
const response = await this.request<IGDBCover>(type, body);
|
||||||
|
|
||||||
let result = "";
|
if (!response.length || !response[0].image_id) {
|
||||||
|
throw new Error(`No image_id found for ${type} with id ${mediaID}`);
|
||||||
|
}
|
||||||
|
|
||||||
response.forEach((cover) => {
|
const imageId = response[0].image_id;
|
||||||
if (cover.url.startsWith("https:")) {
|
const result = `https://images.igdb.com/igdb/image/upload/${size}/${imageId}.jpg`;
|
||||||
result = cover.url;
|
|
||||||
} else {
|
|
||||||
// twitch *sometimes* provides it in the format "//images.igdb.com"
|
|
||||||
result = `https:${cover.url}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getCoverURL(id: IGDBID) {
|
private async getCoverURL(id: IGDBID) {
|
||||||
return await this._getMediaInternal(id, "covers");
|
return await this._getMediaInternal(id, "covers", "t_cover_big");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getArtworkURL(id: IGDBID) {
|
private async getArtworkURL(id: IGDBID) {
|
||||||
return await this._getMediaInternal(id, "artworks");
|
return await this._getMediaInternal(id, "artworks", "t_1080p");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getScreenshotURL(id: IGDBID) {
|
||||||
|
return await this._getMediaInternal(id, "screenshots", "t_1080p");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getIconURL(id: IGDBID) {
|
||||||
|
return await this._getMediaInternal(id, "covers", "t_thumb");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getCompanyLogoURl(id: IGDBID) {
|
private async getCompanyLogoURl(id: IGDBID) {
|
||||||
return await this._getMediaInternal(id, "company_logos");
|
return await this._getMediaInternal(id, "company_logos", "t_original");
|
||||||
}
|
}
|
||||||
|
|
||||||
private trimMessage(msg: string, len: number) {
|
private trimMessage(msg: string, len: number) {
|
||||||
@ -327,7 +335,7 @@ export class IGDBProvider implements MetadataProvider {
|
|||||||
let icon = "";
|
let icon = "";
|
||||||
const cover = response[i].cover;
|
const cover = response[i].cover;
|
||||||
if (cover !== undefined) {
|
if (cover !== undefined) {
|
||||||
icon = await this.getCoverURL(cover);
|
icon = await this.getIconURL(cover);
|
||||||
} else {
|
} else {
|
||||||
icon = "";
|
icon = "";
|
||||||
}
|
}
|
||||||
@ -355,23 +363,26 @@ export class IGDBProvider implements MetadataProvider {
|
|||||||
const currentGame = (await this.request<IGDBGameFull>("games", body)).at(0);
|
const currentGame = (await this.request<IGDBGameFull>("games", body)).at(0);
|
||||||
if (!currentGame) throw new Error("No game found on IGDB with that id");
|
if (!currentGame) throw new Error("No game found on IGDB with that id");
|
||||||
|
|
||||||
context?.logger.info("Using IDGB provider.");
|
context?.logger.info("Using IGDB provider.");
|
||||||
|
|
||||||
let iconRaw;
|
let iconRaw, coverRaw;
|
||||||
const cover = currentGame.cover;
|
const cover = currentGame.cover;
|
||||||
|
|
||||||
if (cover !== undefined) {
|
if (cover !== undefined) {
|
||||||
context?.logger.info("Found cover URL, using...");
|
context?.logger.info("Found cover URL, using...");
|
||||||
iconRaw = await this.getCoverURL(cover);
|
iconRaw = await this.getIconURL(cover);
|
||||||
|
coverRaw = await this.getCoverURL(cover);
|
||||||
} else {
|
} else {
|
||||||
context?.logger.info("Missing cover URL, using fallback...");
|
context?.logger.info("Missing cover URL, using fallback...");
|
||||||
iconRaw = jdenticon.toPng(id, 512);
|
iconRaw = jdenticon.toPng(id, 512);
|
||||||
|
coverRaw = iconRaw;
|
||||||
}
|
}
|
||||||
|
|
||||||
const icon = createObject(iconRaw);
|
const icon = createObject(iconRaw);
|
||||||
|
const coverID = createObject(coverRaw);
|
||||||
let banner;
|
let banner;
|
||||||
|
|
||||||
const images = [icon];
|
const images = [coverID];
|
||||||
for (const art of currentGame.artworks ?? []) {
|
for (const art of currentGame.artworks ?? []) {
|
||||||
const objectId = createObject(await this.getArtworkURL(art));
|
const objectId = createObject(await this.getArtworkURL(art));
|
||||||
if (!banner) {
|
if (!banner) {
|
||||||
@ -384,6 +395,11 @@ export class IGDBProvider implements MetadataProvider {
|
|||||||
banner = createObject(jdenticon.toPng(id, 512));
|
banner = createObject(jdenticon.toPng(id, 512));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const screenshot of currentGame.screenshots ?? []) {
|
||||||
|
const objectId = createObject(await this.getScreenshotURL(screenshot));
|
||||||
|
images.push(objectId);
|
||||||
|
}
|
||||||
|
|
||||||
context?.progress(20);
|
context?.progress(20);
|
||||||
|
|
||||||
const publishers: CompanyModel[] = [];
|
const publishers: CompanyModel[] = [];
|
||||||
@ -452,13 +468,25 @@ export class IGDBProvider implements MetadataProvider {
|
|||||||
|
|
||||||
const genres = await this.getGenres(currentGame.genres);
|
const genres = await this.getGenres(currentGame.genres);
|
||||||
|
|
||||||
const deck = this.trimMessage(currentGame.summary, 280);
|
let description = "";
|
||||||
|
let shortDescription = "";
|
||||||
|
|
||||||
|
if (currentGame.summary.length > (currentGame.storyline?.length ?? 0)) {
|
||||||
|
description = currentGame.summary;
|
||||||
|
shortDescription = this.trimMessage(
|
||||||
|
currentGame.storyline ?? currentGame.summary,
|
||||||
|
280,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
description = currentGame.storyline ?? currentGame.summary;
|
||||||
|
shortDescription = this.trimMessage(currentGame.summary, 280);
|
||||||
|
}
|
||||||
|
|
||||||
const metadata = {
|
const metadata = {
|
||||||
id: currentGame.id.toString(),
|
id: currentGame.id.toString(),
|
||||||
name: currentGame.name,
|
name: currentGame.name,
|
||||||
shortDescription: deck,
|
shortDescription,
|
||||||
description: currentGame.summary,
|
description,
|
||||||
released,
|
released,
|
||||||
|
|
||||||
genres,
|
genres,
|
||||||
@ -471,7 +499,7 @@ export class IGDBProvider implements MetadataProvider {
|
|||||||
|
|
||||||
icon,
|
icon,
|
||||||
bannerId: banner,
|
bannerId: banner,
|
||||||
coverId: icon,
|
coverId: coverID,
|
||||||
images,
|
images,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
1022
server/internal/metadata/steam.ts
Normal file
1022
server/internal/metadata/steam.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
import { defineDropTask } from "..";
|
// import { defineDropTask } from "..";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
export default defineDropTask({
|
export default defineDropTask({
|
||||||
|
|||||||
@ -5,11 +5,13 @@ import { GiantBombProvider } from "../internal/metadata/giantbomb";
|
|||||||
import { IGDBProvider } from "../internal/metadata/igdb";
|
import { IGDBProvider } from "../internal/metadata/igdb";
|
||||||
import { ManualMetadataProvider } from "../internal/metadata/manual";
|
import { ManualMetadataProvider } from "../internal/metadata/manual";
|
||||||
import { PCGamingWikiProvider } from "../internal/metadata/pcgamingwiki";
|
import { PCGamingWikiProvider } from "../internal/metadata/pcgamingwiki";
|
||||||
import { logger } from "~~/server/internal/logging";
|
import { logger } from "../internal/logging";
|
||||||
|
import { SteamProvider } from "../internal/metadata/steam";
|
||||||
|
|
||||||
export default defineNitroPlugin(async (_nitro) => {
|
export default defineNitroPlugin(async (_nitro) => {
|
||||||
const metadataProviders = [
|
const metadataProviders = [
|
||||||
GiantBombProvider,
|
GiantBombProvider,
|
||||||
|
SteamProvider,
|
||||||
PCGamingWikiProvider,
|
PCGamingWikiProvider,
|
||||||
IGDBProvider,
|
IGDBProvider,
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user