fix: more eslint stuff

This commit is contained in:
Huskydog9988
2025-04-15 21:10:45 -04:00
parent 8f429e1e56
commit 8e109dd562
58 changed files with 1066 additions and 1016 deletions

View File

@@ -195,7 +195,7 @@
name="startup"
class="border-l border-zinc-700 block flex-1 border-0 py-1.5 pl-2 bg-transparent text-zinc-100 placeholder:text-zinc-400 focus:ring-0 sm:text-sm sm:leading-6"
placeholder="--setup"
>
/>
</div>
</div>
</div>
@@ -351,7 +351,7 @@
name="startup"
class="border-l border-zinc-700 block flex-1 border-0 py-1.5 pl-2 bg-transparent text-zinc-100 placeholder:text-zinc-400 focus:ring-0 sm:text-sm sm:leading-6"
placeholder="--launch"
>
/>
</div>
</div>
<div
@@ -461,7 +461,7 @@
:disabled="!umuIdEnabled"
placeholder="umu-starcitizen"
class="block w-full rounded-md border-0 py-1.5 px-3 bg-zinc-950 disabled:bg-zinc-900/80 text-zinc-100 disabled:text-zinc-400 shadow-sm ring-1 ring-inset ring-zinc-700 disabled:ring-zinc-800 placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
>
/>
</div>
</div>
</div>
@@ -536,7 +536,6 @@ import {
Combobox,
ComboboxButton,
ComboboxInput,
ComboboxLabel,
ComboboxOption,
ComboboxOptions,
} from "@headlessui/vue";
@@ -553,7 +552,7 @@ const router = useRouter();
const route = useRoute();
const gameId = route.params.id.toString();
const versions = await $dropFetch(
`/api/v1/admin/import/version?id=${encodeURIComponent(gameId)}`
`/api/v1/admin/import/version?id=${encodeURIComponent(gameId)}`,
);
const currentlySelectedVersion = ref(-1);
const versionSettings = ref<{
@@ -585,13 +584,13 @@ const setupProcessQuery = ref("");
const launchFilteredVersionGuesses = computed(() =>
versionGuesses.value?.filter((e) =>
e.filename.toLowerCase().includes(launchProcessQuery.value.toLowerCase())
)
e.filename.toLowerCase().includes(launchProcessQuery.value.toLowerCase()),
),
);
const setupFilteredVersionGuesses = computed(() =>
versionGuesses.value?.filter((e) =>
e.filename.toLowerCase().includes(setupProcessQuery.value.toLowerCase())
)
e.filename.toLowerCase().includes(setupProcessQuery.value.toLowerCase()),
),
);
function updateLaunchCommand(value: string) {
@@ -608,7 +607,7 @@ function autosetPlatform(value: string) {
if (!versionGuesses.value) return;
if (versionSettings.value.platform) return;
const guessIndex = versionGuesses.value.findIndex(
(e) => e.filename === value
(e) => e.filename === value,
);
if (guessIndex == -1) return;
versionSettings.value.platform = versionGuesses.value[guessIndex].platform;
@@ -636,8 +635,8 @@ async function updateCurrentlySelectedVersion(value: number) {
const version = versions[currentlySelectedVersion.value];
const results = await $dropFetch(
`/api/v1/admin/import/version/preload?id=${encodeURIComponent(
gameId
)}&version=${encodeURIComponent(version)}`
gameId,
)}&version=${encodeURIComponent(version)}`,
);
versionGuesses.value = results.map((e) => ({
...e,

View File

@@ -1,4 +1,6 @@
<!-- eslint-disable vue/no-v-html -->
<template>
<div>
<div
v-if="game && unimportedVersions !== undefined"
class="grow flex flex-col gap-y-8"
@@ -8,7 +10,7 @@
class="flex flex-col lg:flex-row lg:justify-between items-start lg:items-center gap-2"
>
<div class="inline-flex items-center gap-4">
<img :src="useObject(game.mIconId)" class="size-20" >
<img :src="useObject(game.mIconId)" class="size-20" />
<div>
<h1 class="text-5xl font-bold font-display text-zinc-100">
{{ game.mName }}
@@ -68,7 +70,7 @@
>
<template #item="{ element }: { element: string }">
<div class="relative group min-w-fit">
<img :src="useObject(element)" class="h-48 w-auto" >
<img :src="useObject(element)" class="h-48 w-auto" />
<div
class="transition-all lg:opacity-0 lg:group-hover:opacity-100 absolute inset-0 flex flex-col items-center justify-center gap-y-2 bg-zinc-950/50"
>
@@ -241,10 +243,10 @@
<div class="mt-3 grid grid-cols-2 grid-flow-dense gap-8">
<div
v-for="(image, imageIdx) in game.mImageLibrary"
:key="image"
:key="imageIdx"
class="group relative flex items-center bg-zinc-950/30"
>
<img :src="useObject(image)" class="w-full h-auto" >
<img :src="useObject(image)" class="w-full h-auto" />
<div
class="transition-all lg:opacity-0 lg:group-hover:opacity-100 absolute inset-0 flex flex-col items-center justify-center gap-y-2 bg-zinc-950/50"
>
@@ -303,7 +305,9 @@
</div>
</div>
<div class="mt-4 text-center w-full text-sm text-zinc-600">lowest</div>
<div class="mt-4 text-center w-full text-sm text-zinc-600">
lowest
</div>
<draggable
:list="game.versions"
handle=".handle"
@@ -339,7 +343,9 @@
>
no versions added
</div>
<div class="mt-2 text-center w-full text-sm text-zinc-600">highest</div>
<div class="mt-2 text-center w-full text-sm text-zinc-600">
highest
</div>
</div>
</div>
</div>
@@ -355,10 +361,10 @@
<div class="grid grid-cols-2 grid-flow-dense gap-4">
<div
v-for="(image, imageIdx) in validAddCarouselImages"
:key="image"
:key="imageIdx"
class="group relative flex items-center bg-zinc-950/30"
>
<img :src="useObject(image)" class="w-full h-auto" >
<img :src="useObject(image)" class="w-full h-auto" />
<div
class="transition-all lg:opacity-0 lg:group-hover:opacity-100 absolute inset-0 flex flex-col items-center justify-center gap-y-2 bg-zinc-950/50"
>
@@ -395,10 +401,10 @@
<div class="grid grid-cols-2 grid-flow-dense gap-4">
<div
v-for="(image, imageIdx) in game.mImageLibrary"
:key="image"
:key="imageIdx"
class="group relative flex items-center bg-zinc-950/30"
>
<img :src="useObject(image)" class="w-full h-auto" >
<img :src="useObject(image)" class="w-full h-auto" />
<div
class="transition-all lg:opacity-0 lg:group-hover:opacity-100 absolute inset-0 flex flex-col items-center justify-center gap-y-2 bg-zinc-950/50"
>
@@ -435,7 +441,7 @@
<div class="flex flex-col lg:flex-row gap-6">
<!-- icon upload div -->
<div class="flex flex-col items-center gap-4">
<img :src="coreMetadataIconUrl" class="size-24 aspect-square" >
<img :src="coreMetadataIconUrl" class="size-24 aspect-square" />
<label for="file-upload">
<span
type="button"
@@ -449,13 +455,15 @@
class="hidden"
type="file"
@change="(e) => coreMetadataUploadFiles(e as any)"
>
/>
</label>
</div>
<!-- edit title -->
<div class="flex flex-col gap-y-4 grow">
<div>
<label for="name" class="block text-sm/6 font-medium text-zinc-100"
<label
for="name"
class="block text-sm/6 font-medium text-zinc-100"
>Game Name</label
>
<div class="mt-2">
@@ -465,7 +473,7 @@
type="text"
name="name"
class="block w-full rounded-md bg-zinc-800 px-3 py-1.5 text-base text-zinc-100 outline outline-1 -outline-offset-1 outline-zinc-700 placeholder:text-zinc-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-blue-600 sm:text-sm/6"
>
/>
</div>
</div>
<div>
@@ -481,7 +489,7 @@
type="text"
name="description"
class="block w-full rounded-md bg-zinc-800 px-3 py-1.5 text-base text-zinc-100 outline outline-1 -outline-offset-1 outline-zinc-700 placeholder:text-zinc-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-blue-600 sm:text-sm/6"
>
/>
</div>
</div>
</div>
@@ -506,6 +514,7 @@
</button>
</template>
</ModalTemplate>
</div>
</template>
<script setup lang="ts">
@@ -533,7 +542,7 @@ const mobileShowFinalDescription = ref(true);
const route = useRoute();
const gameId = route.params.id.toString();
const { game: rawGame, unimportedVersions } = await $dropFetch(
`/api/v1/admin/game?id=${encodeURIComponent(gameId)}`
`/api/v1/admin/game?id=${encodeURIComponent(gameId)}`,
);
const game = ref(rawGame);
@@ -549,6 +558,7 @@ function coreMetadataUploadFiles(e: InputEvent) {
URL.revokeObjectURL(coreMetadataIconUrl.value);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
coreMetadataIconFileUpload.value = (e.target as any)?.files;
const file = coreMetadataIconFileUpload.value?.item(0);
if (!file) {
@@ -559,7 +569,7 @@ function coreMetadataUploadFiles(e: InputEvent) {
description: "Drop couldn't upload this file.",
buttonText: "Close",
},
(e, c) => c()
(e, c) => c(),
);
return;
}
@@ -598,7 +608,7 @@ function coreMetadataUpdate_wrapper() {
}`,
buttonText: "Close",
},
(e, c) => c()
(e, c) => c(),
);
})
.then((newGame) => {
@@ -612,7 +622,7 @@ function coreMetadataUpdate_wrapper() {
}
const descriptionHTML = computed(() =>
micromark(game.value?.mDescription ?? "")
micromark(game.value?.mDescription ?? ""),
);
const descriptionEditor = ref<HTMLTextAreaElement | undefined>();
// 0 is not loading
@@ -622,7 +632,7 @@ const descriptionSaving = ref<number>(0);
let savingTimeout: undefined | NodeJS.Timeout;
watch(descriptionHTML, (v) => {
watch(descriptionHTML, (_v) => {
console.log(game.value.mDescription);
descriptionSaving.value = 1;
if (savingTimeout) clearTimeout(savingTimeout);
@@ -637,24 +647,27 @@ watch(descriptionHTML, (v) => {
},
});
descriptionSaving.value = 0;
} catch (e: any) {
} catch (e) {
createModal(
ModalType.Notification,
{
title: "Failed to update game description",
description: `Drop failed to update the game description: ${
e?.statusMessage || "An unknown error occurred."
// @ts-expect-error attempt to get statusMessage on error
e?.statusMessage ?? "An unknown error occurred."
}`,
buttonText: "Close",
},
(e, c) => c()
(e, c) => c(),
);
}
}, 1500);
});
const validAddCarouselImages = computed(() =>
game.value.mImageLibrary.filter((e) => !game.value.mImageCarousel.includes(e))
game.value.mImageLibrary.filter(
(e) => !game.value.mImageCarousel.includes(e),
),
);
function insertImageAtCursor(id: string) {
@@ -664,7 +677,7 @@ function insertImageAtCursor(id: string) {
const text = `![](/api/v1/object/${id})`;
game.value.mDescription = `${game.value.mDescription.slice(
0,
insertPosition
insertPosition,
)}${text}${game.value.mDescription.slice(insertPosition)}`;
}
@@ -679,17 +692,18 @@ async function updateBannerImage(id: string) {
},
});
game.value.mBannerId = mBannerId;
} catch (e: any) {
} catch (e) {
createModal(
ModalType.Notification,
{
title: "There an error while updating the banner image",
description: `Drop encountered an error while updating the banner image: ${
e?.statusMessage || "An unknown error occurred"
// @ts-expect-error attempt to get statusMessage on error
e?.statusMessage ?? "An unknown error occurred"
}`,
buttonText: "Close",
},
(e, c) => c()
(e, c) => c(),
);
}
}
@@ -705,17 +719,18 @@ async function updateCoverImage(id: string) {
},
});
game.value.mCoverId = mCoverId;
} catch (e: any) {
} catch (e) {
createModal(
ModalType.Notification,
{
title: "There an error while updating the cover image",
description: `Drop encountered an error while updating the cover image: ${
e?.statusMessage || "An unknown error occurred"
// @ts-expect-error attempt to get statusMessage on error
e?.statusMessage ?? "An unknown error occurred"
}`,
buttonText: "Close",
},
(e, c) => c()
(e, c) => c(),
);
}
}
@@ -730,21 +745,22 @@ async function deleteImage(id: string) {
gameId: game.value.id,
imageId: id,
},
}
},
);
game.value.mImageLibrary = mImageLibrary;
game.value.mBannerId = mBannerId;
} catch (e: any) {
} catch (e) {
createModal(
ModalType.Notification,
{
title: "There an error while deleting the image",
description: `Drop encountered an error while deleting the image: ${
e?.statusMessage || "An unknown error occurred"
// @ts-expect-error attempt to get statusMessage on error
e?.statusMessage ?? "An unknown error occurred"
}`,
buttonText: "Close",
},
(e, c) => c()
(e, c) => c(),
);
}
}
@@ -765,19 +781,20 @@ async function deleteVersion(versionName: string) {
});
game.value.versions.splice(
game.value.versions.findIndex((e) => e.versionName === versionName),
1
1,
);
} catch (e: any) {
} catch (e) {
createModal(
ModalType.Notification,
{
title: "There an error while deleting the version",
description: `Drop encountered an error while deleting the version: ${
e?.statusMessage || "An unknown error occurred"
// @ts-expect-error attempt to get statusMessage on error
e?.statusMessage ?? "An unknown error occurred"
}`,
buttonText: "Close",
},
(e, c) => c()
(e, c) => c(),
);
}
}
@@ -792,17 +809,18 @@ async function updateVersionOrder() {
},
});
game.value.versions = newVersions;
} catch (e: any) {
} catch (e) {
createModal(
ModalType.Notification,
{
title: "There an error while updating the version order",
description: `Drop encountered an error while updating the version: ${
e?.statusMessage || "An unknown error occurred"
// @ts-expect-error attempt to get statusMessage on error
e?.statusMessage ?? "An unknown error occurred"
}`,
buttonText: "Close",
},
(e, c) => c()
(e, c) => c(),
);
}
}
@@ -828,17 +846,18 @@ async function updateImageCarousel() {
mImageCarousel: game.value.mImageCarousel,
},
});
} catch (e: any) {
} catch (e) {
createModal(
ModalType.Notification,
{
title: "There an error while updating the image carousel",
description: `Drop encountered an error while updating image carousel: ${
e?.statusMessage || "An unknown error occurred"
// @ts-expect-error attempt to get statusMessage on error
e?.statusMessage ?? "An unknown error occurred"
}`,
buttonText: "Close",
},
(e, c) => c()
(e, c) => c(),
);
}
}

View File

@@ -1,45 +1,69 @@
<template>
<div class="flex flex-col gap-y-6 w-full max-w-md">
<Listbox
as="div" :model="currentlySelectedGame"
@update:model-value="(value) => updateSelectedGame_wrapper(value)">
<ListboxLabel class="block text-sm font-medium leading-6 text-zinc-100">Select game to import</ListboxLabel>
as="div"
:model="currentlySelectedGame"
@update:model-value="(value) => updateSelectedGame_wrapper(value)"
>
<ListboxLabel class="block text-sm font-medium leading-6 text-zinc-100"
>Select game to import</ListboxLabel
>
<div class="relative mt-2">
<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-600 sm:text-sm sm:leading-6">
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-600 sm:text-sm sm:leading-6"
>
<span v-if="currentlySelectedGame != -1" class="block truncate">{{
games.unimportedGames[currentlySelectedGame]
}}</span>
<span v-else class="block truncate text-zinc-400">Please select a directory...</span>
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon class="h-5 w-5 text-gray-400" aria-hidden="true" />
<span v-else class="block truncate text-zinc-400"
>Please select a directory...</span
>
<span
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
>
<ChevronUpDownIcon
class="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</span>
</ListboxButton>
<transition
leave-active-class="transition ease-in duration-100" leave-from-class="opacity-100"
leave-to-class="opacity-0">
leave-active-class="transition ease-in duration-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<ListboxOptions
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-zinc-800 focus:outline-none sm:text-sm">
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-zinc-800 focus:outline-none sm:text-sm"
>
<ListboxOption
v-for="(game, gameIdx) in games.unimportedGames" :key="game" v-slot="{ active, selected }" as="template"
:value="gameIdx">
v-for="(game, gameIdx) in games.unimportedGames"
:key="game"
v-slot="{ active, selected }"
as="template"
:value="gameIdx"
>
<li
:class="[
:class="[
active ? 'bg-blue-600 text-white' : 'text-zinc-100',
'relative cursor-default select-none py-2 pl-3 pr-9',
]">
]"
>
<span
:class="[
:class="[
selected ? 'font-semibold' : 'font-normal',
'block truncate',
]">{{ game }}</span>
]"
>{{ game }}</span
>
<span
v-if="selected" :class="[
v-if="selected"
:class="[
active ? 'text-white' : 'text-blue-600',
'absolute inset-y-0 right-0 flex items-center pr-4',
]">
]"
>
<CheckIcon class="h-5 w-5" aria-hidden="true" />
</span>
</li>
@@ -52,47 +76,76 @@ v-if="selected" :class="[
<div v-if="currentlySelectedGame !== -1" class="flex flex-col gap-y-4">
<!-- without metadata option -->
<div>
<LoadingButton class="w-fit" :loading="importLoading" @click="() => importGame_wrapper(false)">Import without
metadata
<LoadingButton
class="w-fit"
:loading="importLoading"
@click="() => importGame_wrapper(false)"
>Import without metadata
</LoadingButton>
</div>
<!-- divider -->
<div class="inline-flex items-center gap-x-4 text-zinc-600 font-display font-bold">
<div class="h-[1px] grow bg-zinc-800" />OR
<div
class="inline-flex items-center gap-x-4 text-zinc-600 font-display font-bold"
>
<div class="h-[1px] grow bg-zinc-800" />
OR
<div class="h-[1px] grow bg-zinc-800" />
</div>
<!-- with metadata option -->
<div class="flex flex-col gap-y-4">
<Listbox v-if="metadataResults && metadataResults.length > 0" v-model="currentlySelectedMetadata" as="div">
<ListboxLabel class="block text-sm font-medium leading-6 text-zinc-100">Select game</ListboxLabel>
<Listbox
v-if="metadataResults && metadataResults.length > 0"
v-model="currentlySelectedMetadata"
as="div"
>
<ListboxLabel
class="block text-sm font-medium leading-6 text-zinc-100"
>Select game</ListboxLabel
>
<div class="relative mt-2">
<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-600 sm:text-sm sm:leading-6">
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-600 sm:text-sm sm:leading-6"
>
<GameSearchResultWidget
v-if="currentlySelectedMetadata != -1"
:game="metadataResults[currentlySelectedMetadata]" />
<span v-else class="block truncate text-zinc-600">Please select a game...</span>
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<ChevronUpDownIcon class="h-5 w-5 text-gray-400" aria-hidden="true" />
v-if="currentlySelectedMetadata != -1"
:game="metadataResults[currentlySelectedMetadata]"
/>
<span v-else class="block truncate text-zinc-600"
>Please select a game...</span
>
<span
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
>
<ChevronUpDownIcon
class="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</span>
</ListboxButton>
<transition
leave-active-class="transition ease-in duration-100" leave-from-class="opacity-100"
leave-to-class="opacity-0">
leave-active-class="transition ease-in duration-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<ListboxOptions
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-black ring-opacity-5 focus:outline-none sm:text-sm">
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-black ring-opacity-5 focus:outline-none sm:text-sm"
>
<ListboxOption
v-for="(result, resultIdx) in metadataResults" :key="result.id" v-slot="{ active, selected }"
as="template" :value="resultIdx">
v-for="(result, resultIdx) in metadataResults"
:key="result.id"
v-slot="{ active }"
as="template"
:value="resultIdx"
>
<li
:class="[
:class="[
active ? 'bg-blue-600 text-white' : 'text-zinc-100',
'relative cursor-default select-none py-2 pl-3 pr-9',
]">
]"
>
<GameSearchResultWidget :game="result" />
</li>
</ListboxOption>
@@ -101,23 +154,34 @@ v-for="(result, resultIdx) in metadataResults" :key="result.id" v-slot="{ active
</div>
</Listbox>
<div
v-else-if="gameSearchResultsLoading" role="status"
class="inline-flex text-zinc-100 font-display font-semibold items-center gap-x-4">
v-else-if="gameSearchResultsLoading"
role="status"
class="inline-flex text-zinc-100 font-display font-semibold items-center gap-x-4"
>
Loading game results...
<svg
aria-hidden="true" class="w-6 h-6 text-transparent animate-spin fill-white" viewBox="0 0 100 101"
fill="none" xmlns="http://www.w3.org/2000/svg">
aria-hidden="true"
class="w-6 h-6 text-transparent animate-spin fill-white"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor" />
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill" />
fill="currentFill"
/>
</svg>
<span class="sr-only">Loading...</span>
</div>
<div v-if="gameSearchResultsError" class="w-fit rounded-md bg-red-600/10 p-4">
<div
v-if="gameSearchResultsError"
class="w-fit rounded-md bg-red-600/10 p-4"
>
<div class="flex">
<div class="flex-shrink-0">
<XCircleIcon class="h-5 w-5 text-red-600" aria-hidden="true" />
@@ -132,11 +196,17 @@ aria-hidden="true" class="w-6 h-6 text-transparent animate-spin fill-white" view
<div>
<LoadingButton
class="w-fit" :loading="importLoading" :disabled="currentlySelectedMetadata === -1"
@click="() => importGame_wrapper()">Import
class="w-fit"
:loading="importLoading"
:disabled="currentlySelectedMetadata === -1"
@click="() => importGame_wrapper()"
>Import
</LoadingButton>
<div v-if="importError" class="mt-4 w-fit rounded-md bg-red-600/10 p-4">
<div
v-if="importError"
class="mt-4 w-fit rounded-md bg-red-600/10 p-4"
>
<div class="flex">
<div class="flex-shrink-0">
<XCircleIcon class="h-5 w-5 text-red-600" aria-hidden="true" />
@@ -187,18 +257,21 @@ async function updateSelectedGame(value: number) {
currentlySelectedMetadata.value = -1;
const results = await $dropFetch(
`/api/v1/admin/import/game/search?q=${encodeURIComponent(game)}`
`/api/v1/admin/import/game/search?q=${encodeURIComponent(game)}`,
);
metadataResults.value = results;
}
function updateSelectedGame_wrapper(value: number) {
gameSearchResultsLoading.value = true;
updateSelectedGame(value).catch((error) => {
gameSearchResultsError.value = error.statusMessage || "An unknown error occurred";
}).finally(() => {
gameSearchResultsLoading.value = false;
updateSelectedGame(value)
.catch((error) => {
gameSearchResultsError.value =
error.statusMessage || "An unknown error occurred";
})
.finally(() => {
gameSearchResultsLoading.value = false;
});
}
const metadataResults = ref<Array<GameMetadataSearchResult> | undefined>();
@@ -215,7 +288,10 @@ async function importGame(metadata: boolean) {
method: "POST",
body: {
path: games.unimportedGames[currentlySelectedGame.value],
metadata: metadata && metadataResults.value ? metadataResults.value[currentlySelectedMetadata.value] : undefined,
metadata:
metadata && metadataResults.value
? metadataResults.value[currentlySelectedMetadata.value]
: undefined,
},
});

View File

@@ -49,7 +49,7 @@
name="search"
class="col-start-1 row-start-1 block w-full rounded-md bg-zinc-900 py-1.5 pl-10 pr-3 text-base text-zinc-100 outline outline-1 -outline-offset-1 outline-zinc-700 placeholder:text-gray-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-blue-600 sm:pl-9 sm:text-sm/6"
placeholder="Search library..."
>
/>
<MagnifyingGlassIcon
class="pointer-events-none col-start-1 row-start-1 ml-3 size-5 self-center text-zinc-400 sm:size-4"
aria-hidden="true"
@@ -69,7 +69,7 @@
class="h-16 w-16 flex-shrink-0 rounded-md"
:src="useObject(game.mIconId)"
alt=""
>
/>
<div class="flex flex-col">
<h3 class="text-sm font-medium text-zinc-100 font-display">
{{ game.mName }}
@@ -193,11 +193,12 @@ const libraryGames = ref(
},
hasNotifications: noVersions || toImport,
};
})
}),
);
const filteredLibraryGames = computed(() =>
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore excessively deep ts
libraryGames.value.filter((e) => {
if (!searchQuery.value) return true;
const searchQueryLower = searchQuery.value.toLowerCase();
@@ -205,7 +206,7 @@ const filteredLibraryGames = computed(() =>
if (e.mShortDescription.toLowerCase().includes(searchQueryLower))
return true;
return false;
})
}),
);
async function deleteGame(id: string) {

View File

@@ -1,4 +1,6 @@
<template></template>
<template>
<div></div>
</template>
<script setup lang="ts">
definePageMeta({

View File

@@ -80,7 +80,7 @@
<script setup lang="ts">
import { CheckCircleIcon } from "@heroicons/vue/16/solid";
import { ExclamationCircleIcon, XMarkIcon } from "@heroicons/vue/24/solid";
import { ExclamationCircleIcon } from "@heroicons/vue/24/solid";
const route = useRoute();
const taskId = route.params.id.toString();

View File

@@ -164,7 +164,7 @@
autocomplete="username"
placeholder="myUsername"
class="block w-full rounded-md border-0 py-1.5 px-3 bg-zinc-800 disabled:bg-zinc-900/80 text-zinc-100 disabled:text-zinc-400 shadow-sm ring-1 ring-inset ring-zinc-700 disabled:ring-zinc-800 placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
>
/>
</div>
</div>
@@ -191,7 +191,7 @@
autocomplete="email"
placeholder="me@example.com"
class="block w-full rounded-md border-0 py-1.5 px-3 bg-zinc-800 disabled:bg-zinc-900/80 text-zinc-100 disabled:text-zinc-400 shadow-sm ring-1 ring-inset ring-zinc-700 disabled:ring-zinc-800 placeholder:text-zinc-400 focus:ring-2 focus:ring-inset focus:ring-blue-600 sm:text-sm sm:leading-6"
>
/>
</div>
</div>
@@ -262,7 +262,7 @@
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-black ring-opacity-5 focus:outline-none sm:text-sm"
>
<ListboxOption
v-for="[label, _] in Object.entries(expiry)"
v-for="[label] in Object.entries(expiry)"
:key="label"
v-slot="{ active, selected }"
as="template"
@@ -354,7 +354,6 @@
<script setup lang="ts">
import {
Dialog,
DialogPanel,
DialogTitle,
TransitionChild,
TransitionRoot,
@@ -368,16 +367,8 @@ import {
ListboxOption,
ListboxOptions,
} from "@headlessui/vue";
import {
ChevronRightIcon,
CheckIcon,
ChevronUpDownIcon,
} from "@heroicons/vue/20/solid";
import {
CalendarDateRangeIcon,
TrashIcon,
XCircleIcon,
} from "@heroicons/vue/24/solid";
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
import { TrashIcon, XCircleIcon } from "@heroicons/vue/24/solid";
import type { Invitation } from "@prisma/client";
import type { SerializeObject } from "nitropack";
import type { DurationLike } from "luxon";
@@ -392,7 +383,7 @@ useHead({
});
const data = await $dropFetch<Array<SerializeObject<Invitation>>>(
"/api/v1/admin/auth/invitation"
"/api/v1/admin/auth/invitation",
);
const invitations = ref(data ?? []);
@@ -401,7 +392,7 @@ const generateInvitationUrl = (id: string) =>
const invitationUrls = ref<undefined | Array<string>>();
onMounted(() => {
invitationUrls.value = invitations.value.map((invitation) =>
generateInvitationUrl(invitation.id)
generateInvitationUrl(invitation.id),
);
});
@@ -417,7 +408,7 @@ const username = computed({
},
});
const validUsername = computed(() =>
_username.value === undefined ? true : _username.value.length >= 5
_username.value === undefined ? true : _username.value.length >= 5,
);
// Same as above
@@ -433,7 +424,7 @@ const email = computed({
});
const mailRegex = /^\S+@\S+\.\S+$/;
const validEmail = computed(() =>
_email.value === undefined ? true : mailRegex.test(email.value as string)
_email.value === undefined ? true : mailRegex.test(email.value as string),
);
const isAdmin = ref(false);
@@ -459,7 +450,7 @@ const expiry: Record<string, DurationLike> = {
year: 3000,
}, // Never is relative, right?
};
const expiryKey = ref<keyof typeof expiry>(Object.keys(expiry)[0] as any); // Cast to any because we just know it's okay
const expiryKey = ref<keyof typeof expiry>(Object.keys(expiry)[0]); // Cast to any because we just know it's okay
const loading = ref(false);
const error = ref<undefined | string>();
@@ -481,7 +472,7 @@ async function invite() {
email.value = "";
username.value = "";
isAdmin.value = false;
expiryKey.value = Object.keys(expiry)[0] as any; // Same reason as above
expiryKey.value = Object.keys(expiry)[0]; // Same reason as above
return newInvitation;
}

View File

@@ -1,5 +1,5 @@
<template>
<div></div>
</template>
<script setup lang="ts">
@@ -8,5 +8,5 @@ useHead({
});
const router = useRouter();
router.replace('/store')
router.replace("/store");
</script>

View File

@@ -97,16 +97,7 @@ import {
TransitionChild,
TransitionRoot,
} from "@headlessui/vue";
import {
Bars3Icon,
CalendarIcon,
ChartPieIcon,
DocumentDuplicateIcon,
FolderIcon,
HomeIcon,
UsersIcon,
XMarkIcon,
} from "@heroicons/vue/24/outline";
import { Bars3Icon, XMarkIcon } from "@heroicons/vue/24/outline";
const router = useRouter();
const sidebarOpen = ref(false);

View File

@@ -33,12 +33,12 @@
</template>
<script setup lang="ts">
import { ArrowLeftIcon, TrashIcon } from "@heroicons/vue/20/solid";
import { ArrowLeftIcon } from "@heroicons/vue/20/solid";
const route = useRoute();
const collections = await useCollections();
const collection = computed(() =>
collections.value.find((e) => e.id == route.params.id)
collections.value.find((e) => e.id == route.params.id),
);
if (collection.value === undefined) {
throw createError({ statusCode: 404, statusMessage: "Collection not found" });

View File

@@ -1,3 +1,4 @@
<!-- eslint-disable vue/no-v-html -->
<template>
<div
class="mx-auto w-full relative flex flex-col justify-center pt-72 overflow-hidden"
@@ -7,7 +8,7 @@
<img
:src="useObject(game.mBannerId)"
class="w-full h-[24rem] object-cover blur-sm scale-105"
>
/>
<div
class="absolute inset-0 bg-gradient-to-t from-zinc-900 to-transparent opacity-90"
/>
@@ -76,7 +77,7 @@
<img
class="w-fit h-48 lg:h-96 rounded"
:src="useObject(image)"
>
/>
</VueSlide>
<VueSlide v-if="game.mImageCarousel.length == 0">
<div
@@ -112,13 +113,9 @@
<script setup lang="ts">
import {
ArrowLeftIcon,
ChevronLeftIcon,
ChevronRightIcon,
PhotoIcon,
ArrowTopRightOnSquareIcon,
ArrowUpRightIcon,
} from "@heroicons/vue/20/solid";
import { BuildingStorefrontIcon } from "@heroicons/vue/24/outline";
import { micromark } from "micromark";
import type { Game } from "@prisma/client";
@@ -135,23 +132,23 @@ const game = computed(() => {
// Convert markdown to HTML
const descriptionHTML = computed(() =>
micromark(game.value.mDescription ?? "")
micromark(game.value.mDescription ?? ""),
);
const currentImageIndex = ref(0);
// const currentImageIndex = ref(0);
function nextImage() {
if (!game.value?.mImageCarousel) return;
currentImageIndex.value =
(currentImageIndex.value + 1) % game.value.mImageCarousel.length;
}
// function nextImage() {
// if (!game.value?.mImageCarousel) return;
// currentImageIndex.value =
// (currentImageIndex.value + 1) % game.value.mImageCarousel.length;
// }
function previousImage() {
if (!game.value?.mImageCarousel) return;
currentImageIndex.value =
(currentImageIndex.value - 1 + game.value.mImageCarousel.length) %
game.value.mImageCarousel.length;
}
// function previousImage() {
// if (!game.value?.mImageCarousel) return;
// currentImageIndex.value =
// (currentImageIndex.value - 1 + game.value.mImageCarousel.length) %
// game.value.mImageCarousel.length;
// }
</script>
<style scoped>

View File

@@ -1,4 +1,5 @@
<template>
<div>
<div class="flex flex-col gap-y-8">
<div class="max-w-2xl">
<h2 class="text-2xl font-bold font-display text-zinc-100">Library</h2>
@@ -86,16 +87,12 @@
<CreateCollectionModal v-model="collectionCreateOpen" />
<DeleteCollectionModal v-model="currentlyDeleting" />
</div>
</template>
<script setup lang="ts">
import {
ArrowTopRightOnSquareIcon,
ArrowUpRightIcon,
TrashIcon,
ArrowLeftIcon, PlusIcon
} from "@heroicons/vue/20/solid";
import type { Collection, Game, GameVersion } from "@prisma/client";
import { TrashIcon, PlusIcon } from "@heroicons/vue/20/solid";
import type { Collection } from "@prisma/client";
const collections = await useCollections();
const collectionCreateOpen = ref(false);

View File

@@ -97,22 +97,13 @@ import {
TransitionChild,
TransitionRoot,
} from "@headlessui/vue";
import {
Bars3Icon,
CalendarIcon,
ChartPieIcon,
DocumentDuplicateIcon,
FolderIcon,
HomeIcon,
UsersIcon,
XMarkIcon,
} from "@heroicons/vue/24/outline";
import { Bars3Icon, XMarkIcon } from "@heroicons/vue/24/outline";
const news = useNews();
if (!news.value) {
await fetchNews();
console.log('fetched news')
console.log("fetched news");
}
const router = useRouter();

View File

@@ -1,4 +1,6 @@
<!-- eslint-disable vue/no-v-html -->
<template>
<div>
<div v-if="article" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- Banner header with blurred background -->
<div class="relative w-full h-[300px] mb-8 rounded-lg overflow-hidden">
@@ -7,7 +9,7 @@
:src="useObject(article.image)"
alt=""
class="w-full h-full object-cover blur-sm scale-110"
>
/>
<div
class="absolute inset-0 bg-gradient-to-b from-transparent to-zinc-950"
/>
@@ -70,10 +72,14 @@
</div>
<!-- Article content - markdown -->
<div class="mx-auto prose prose-invert prose-lg" v-html="renderedContent" />
<div
class="mx-auto prose prose-invert prose-lg"
v-html="renderedContent"
/>
</div>
<DeleteNewsModal v-model="currentlyDeleting" />
</div>
</template>
<script setup lang="ts">
@@ -88,7 +94,9 @@ const news = useNews();
if (!news.value) {
news.value = await fetchNews();
}
const article = computed(() => news.value?.find((e) => e.id == route.params.id));
const article = computed(() =>
news.value?.find((e) => e.id == route.params.id),
);
if (!article.value)
throw createError({
statusCode: 404,

View File

@@ -30,7 +30,7 @@
:src="useObject(article.image)"
alt=""
class="h-full w-full object-cover object-center transition-all duration-500 group-hover:scale-110 scale-105"
>
/>
<div class="absolute top-4 left-4 flex gap-2">
<span
v-for="tag in article.tags"
@@ -84,7 +84,7 @@ import { DocumentIcon } from "@heroicons/vue/24/outline";
import type { Article } from "@prisma/client";
import type { SerializeObject } from "nitropack/types";
const props = defineProps<{
const { articles } = defineProps<{
articles: SerializeObject<
Article & {
tags: Array<{ name: string; id: string }>;

View File

@@ -1,3 +1,4 @@
<!-- eslint-disable vue/no-v-html -->
<template>
<div
class="mx-auto bg-zinc-950 w-full relative flex flex-col justify-center pt-32 xl:pt-24 z-10 overflow-hidden"
@@ -197,7 +198,7 @@ const descriptionSplitIndex = gameDescriptionCharacters.findIndex(
if (i < 500) return false;
if (v != "\n") return false;
return true;
}
},
);
const previewDescription = gameDescriptionCharacters

View File

@@ -17,7 +17,7 @@
:src="useObject(game.mBannerId)"
alt=""
class="size-full object-cover object-center"
>
/>
</div>
<div
class="relative flex items-center justify-center w-full h-full bg-zinc-900/75 px-6 py-32 sm:px-12 sm:py-40 lg:px-16"
@@ -99,8 +99,8 @@ const recent = await $dropFetch("/api/v1/store/recent");
const updated = await $dropFetch("/api/v1/store/updated");
const released = await $dropFetch("/api/v1/store/released");
const developers = await $dropFetch("/api/v1/store/developers");
const publishers = await $dropFetch("/api/v1/store/publishers");
// const developers = await $dropFetch("/api/v1/store/developers");
// const publishers = await $dropFetch("/api/v1/store/publishers");
useHead({
title: "Store",

View File

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

View File

@@ -1,4 +1,4 @@
import { defineEventHandler, createError, readBody } from "h3";
import { defineEventHandler, createError } from "h3";
import aclManager from "~/server/internal/acls";
import newsManager from "~/server/internal/news";
import { handleFileUpload } from "~/server/internal/utils/handlefileupload";
@@ -21,7 +21,7 @@ export default defineEventHandler(async (h3) => {
statusMessage: "Failed to upload file",
});
const [imageId, options, pull, dump] = uploadResult;
const [imageId, options, pull, _dump] = uploadResult;
const title = options.title;
const description = options.description;

View File

@@ -1,4 +1,4 @@
import { AuthMec, Invitation } from "@prisma/client";
import { AuthMec } from "@prisma/client";
import prisma from "~/server/internal/db/database";
import { createHashArgon2 } from "~/server/internal/security/simple";
import * as jdenticon from "jdenticon";
@@ -63,7 +63,7 @@ export default defineEventHandler(async (h3) => {
profilePictureId,
async () => jdenticon.toPng(user.username, 256),
{},
[`internal:read`, `${userId}:read`]
[`internal:read`, `${userId}:read`],
);
const [linkMec] = await prisma.$transaction([
prisma.linkedAuthMec.create({

View File

@@ -1,3 +1 @@
export default defineEventHandler((h3) => {
})
export default defineEventHandler((_h3) => {});

View File

@@ -1,4 +1,3 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import prisma from "~/server/internal/db/database";
import fs from "fs";
import path from "path";
@@ -33,7 +32,7 @@ export default defineEventHandler(async (h3) => {
const versionDir = path.join(
libraryManager.fetchLibraryPath(),
game.libraryBasePath,
versionName
versionName,
);
if (!fs.existsSync(versionDir))
throw createError({

View File

@@ -1,4 +1,3 @@
import aclManager from "~/server/internal/acls";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";
@@ -20,7 +19,7 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
const successful = await userLibraryManager.collectionRemove(
gameId,
id,
user.id
user.id,
);
if (!successful)
throw createError({

View File

@@ -1,4 +1,3 @@
import aclManager from "~/server/internal/acls";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";

View File

@@ -1,4 +1,3 @@
import aclManager from "~/server/internal/acls";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";

View File

@@ -1,4 +1,3 @@
import aclManager from "~/server/internal/acls";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";

View File

@@ -1,4 +1,3 @@
import aclManager from "~/server/internal/acls";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";

View File

@@ -1,4 +1,3 @@
import aclManager from "~/server/internal/acls";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";

View File

@@ -1,4 +1,3 @@
import aclManager from "~/server/internal/acls";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";

View File

@@ -1,4 +1,3 @@
import aclManager from "~/server/internal/acls";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";

View File

@@ -1,4 +1,3 @@
import aclManager from "~/server/internal/acls";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import userLibraryManager from "~/server/internal/userlibrary";
@@ -12,6 +11,9 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
throw createError({ statusCode: 400, statusMessage: "Requires name" });
// Create the collection using the manager
const newCollection = await userLibraryManager.collectionCreate(name, user.id);
const newCollection = await userLibraryManager.collectionCreate(
name,
user.id,
);
return newCollection;
});

View File

@@ -1,8 +1,7 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import prisma from "~/server/internal/db/database";
import { DropManifest } from "~/server/internal/downloads/manifest";
export default defineClientEventHandler(async (h3, {}) => {
export default defineClientEventHandler(async (h3) => {
const query = getQuery(h3);
const id = query.id?.toString();
if (!id)
@@ -25,7 +24,7 @@ export default defineClientEventHandler(async (h3, {}) => {
if (!version.dropletManifest) return undefined;
const newVersion = { ...version, dropletManifest: undefined };
// @ts-expect-error
// @ts-expect-error idk why we delete an undefined object
delete newVersion.dropletManifest;
return {
...newVersion,

View File

@@ -1,10 +1,8 @@
import { ClientCapabilities } from "@prisma/client";
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import { applicationSettings } from "~/server/internal/config/application-configuration";
import prisma from "~/server/internal/db/database";
export default defineClientEventHandler(
async (h3, { fetchClient, fetchUser }) => {
export default defineClientEventHandler(async (_h3, { fetchClient }) => {
const client = await fetchClient();
if (!client.capabilities.includes(ClientCapabilities.CloudSaves))
throw createError({
@@ -16,5 +14,4 @@ export default defineClientEventHandler(
const sizeLimit = await applicationSettings.get("saveSlotSizeLimit");
const history = await applicationSettings.get("saveSlotHistoryLimit");
return { slotLimit, sizeLimit, history };
}
);
});

View File

@@ -1,8 +1,7 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import prisma from "~/server/internal/db/database";
import userLibraryManager from "~/server/internal/userlibrary";
export default defineClientEventHandler(async (h3, { fetchUser }) => {
export default defineClientEventHandler(async (_h3, { fetchUser }) => {
const user = await fetchUser();
const library = await userLibraryManager.fetchLibrary(user.id);
return library.entries.map((e) => e.game);

View File

@@ -1,4 +1,4 @@
export default defineEventHandler((h3) => {
export default defineEventHandler((_h3) => {
return {
appName: "Drop",
};

View File

@@ -1,11 +1,9 @@
import notificationSystem from "~/server/internal/notifications";
import session from "~/server/internal/session";
import { parse as parseCookies } from "cookie-es";
import aclManager from "~/server/internal/acls";
// TODO add web socket sessions for horizontal scaling
// Peer ID to user ID
const socketSessions: { [key: string]: string } = {};
const socketSessions = new Map<string, string>();
export default defineWebSocketHandler({
async open(peer) {
@@ -25,7 +23,7 @@ export default defineWebSocketHandler({
userIds.push("system");
}
socketSessions[peer.id] = userId;
socketSessions.set(peer.id, userId);
for (const listenUserId of userIds) {
notificationSystem.listen(listenUserId, peer.id, (notification) => {
@@ -33,8 +31,8 @@ export default defineWebSocketHandler({
});
}
},
async close(peer, details) {
const userId = socketSessions[peer.id];
async close(peer, _details) {
const userId = socketSessions.get(peer.id);
if (!userId) {
console.log(`skipping websocket close for ${peer.id}`);
return;
@@ -42,6 +40,6 @@ export default defineWebSocketHandler({
notificationSystem.unlisten(userId, peer.id);
notificationSystem.unlisten("system", peer.id); // In case we were listening as 'system'
delete socketSessions[peer.id];
socketSessions.delete(peer.id);
},
});

View File

@@ -1,11 +1,9 @@
import session from "~/server/internal/session";
import taskHandler, { TaskMessage } from "~/server/internal/tasks";
import { parse as parseCookies } from "cookie-es";
import taskHandler from "~/server/internal/tasks";
import type { MinimumRequestObject } from "~/server/h3";
// TODO add web socket sessions for horizontal scaling
// ID to admin
const socketHeaders: { [key: string]: MinimumRequestObject } = {};
const socketHeaders = new Map<string, MinimumRequestObject>();
export default defineWebSocketHandler({
async open(peer) {
@@ -15,25 +13,26 @@ export default defineWebSocketHandler({
return;
}
socketHeaders[peer.id] = {
socketHeaders.set(peer.id, {
headers: request.headers ?? new Headers(),
};
});
peer.send(`connect`);
},
message(peer, message) {
if (!peer.id) return;
if (socketHeaders[peer.id] === undefined) return;
const headers = socketHeaders.get(peer.id);
if (headers === undefined) return;
const text = message.text();
if (text.startsWith("connect/")) {
const id = text.substring("connect/".length);
taskHandler.connect(peer.id, id, peer, socketHeaders[peer.id]);
taskHandler.connect(peer.id, id, peer, headers);
return;
}
},
close(peer, details) {
close(peer, _details) {
if (!peer.id) return;
if (socketHeaders[peer.id] === undefined) return;
delete socketHeaders[peer.id];
if (!socketHeaders.has(peer.id)) return;
socketHeaders.delete(peer.id);
taskHandler.disconnectAll(peer.id);
},

View File

@@ -1,6 +1,5 @@
import aclManager from "~/server/internal/acls";
import clientHandler from "~/server/internal/clients/handler";
import prisma from "~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["clients:revoke"]);

View File

@@ -1,6 +1,5 @@
import { APITokenMode } from "@prisma/client";
import aclManager, { userACLs } from "~/server/internal/acls";
import { userACLDescriptions } from "~/server/internal/acls/descriptions";
import prisma from "~/server/internal/db/database";
export default defineEventHandler(async (h3) => {
@@ -25,8 +24,14 @@ export default defineEventHandler(async (h3) => {
statusMessage: "Token requires more than zero ACLs",
});
const invalidACLs = acls.filter((e) => userACLs.findIndex((v) => e == v) == -1);
if(invalidACLs.length > 0) throw createError({statusCode: 400, statusMessage: `Invalid ACLs: ${invalidACLs.join(", ")}`});
const invalidACLs = acls.filter(
(e) => userACLs.findIndex((v) => e == v) == -1,
);
if (invalidACLs.length > 0)
throw createError({
statusCode: 400,
statusMessage: `Invalid ACLs: ${invalidACLs.join(", ")}`,
});
const token = await prisma.aPIToken.create({
data: {
@@ -34,8 +39,8 @@ export default defineEventHandler(async (h3) => {
name: name,
userId: userId,
acls: acls,
}
})
},
});
return token;
});

View File

@@ -1,5 +1,4 @@
import { APITokenMode, User } from "@prisma/client";
import { H3Context, H3Event } from "h3";
import { APITokenMode } from "@prisma/client";
import prisma from "../db/database";
import sessionHandler from "../session";
import type { MinimumRequestObject } from "~/server/h3";
@@ -98,7 +97,7 @@ class ACLManager {
if (!token) return undefined;
if (!token.userId)
throw new Error(
"No userId on user or client token - is something broken?"
"No userId on user or client token - is something broken?",
);
for (const acl of acls) {
@@ -124,7 +123,7 @@ class ACLManager {
async allowSystemACL(
request: MinimumRequestObject | undefined,
acls: SystemACL
acls: SystemACL,
) {
if (!request)
throw new Error("Native web requests not available - weird deployment?");
@@ -157,13 +156,17 @@ class ACLManager {
for (const acl of acls) {
if (acl.startsWith(userACLPrefix)) {
const rawACL = acl.substring(userACLPrefix.length);
const userId = await this.getUserIdACL(request, [rawACL as any]);
const userId = await this.getUserIdACL(request, [
rawACL as UserACL[number],
]);
if (!userId) return false;
}
if (acl.startsWith(systemACLPrefix)) {
const rawACL = acl.substring(systemACLPrefix.length);
const allowed = await this.allowSystemACL(request, [rawACL as any]);
const allowed = await this.allowSystemACL(request, [
rawACL as SystemACL[number],
]);
if (!allowed) return false;
}
}

View File

@@ -1,8 +1,5 @@
import path from "path";
import fs from "fs";
import droplet from "@drop-oss/droplet";
import type { CertificateStore} from "./ca-store";
import { fsCertificateStore } from "./ca-store";
import type { CertificateStore } from "./ca-store";
export type CertificateBundle = {
priv: string;
@@ -50,7 +47,7 @@ export class CertificateAuthority {
clientId,
clientName,
caCertificate.cert,
caCertificate.priv
caCertificate.priv,
);
const certBundle: CertificateBundle = {
priv,
@@ -65,7 +62,7 @@ export class CertificateAuthority {
async fetchClientCertificate(clientId: string) {
const isBlacklist = await this.certificateStore.checkBlacklistCertificate(
`client:${clientId}`
`client:${clientId}`,
);
if (isBlacklist) return undefined;
return await this.certificateStore.fetch(`client:${clientId}`);

View File

@@ -18,8 +18,8 @@ export const validCapabilities = Object.values(InternalClientCapability);
export type CapabilityConfiguration = {
[InternalClientCapability.PeerAPI]: { endpoints: string[] };
[InternalClientCapability.UserStatus]: {};
[InternalClientCapability.CloudSaves]: {};
[InternalClientCapability.UserStatus]: object;
[InternalClientCapability.CloudSaves]: object;
};
class CapabilityManager {
@@ -53,7 +53,7 @@ class CapabilityManager {
const serverCertificate = await ca.fetchClientCertificate("server");
if (!serverCertificate)
throw new Error(
"CA not initialised properly - server mTLS certificate not present"
"CA not initialised properly - server mTLS certificate not present",
);
const httpsAgent = new https.Agent({
key: serverCertificate.priv,
@@ -70,7 +70,9 @@ class CapabilityManager {
});
valid = true;
break;
} catch {}
} catch {
/* empty */
}
}
return valid;
@@ -81,7 +83,7 @@ class CapabilityManager {
async validateCapabilityConfiguration(
capability: InternalClientCapability,
configuration: object
configuration: object,
) {
const validationFunction = this.validationFunctions[capability];
if (!validationFunction) return false;
@@ -91,7 +93,7 @@ class CapabilityManager {
async upsertClientCapability(
capability: InternalClientCapability,
rawCapability: object,
clientId: string
clientId: string,
) {
const upsertFunctions: EnumDictionary<
InternalClientCapability,

View File

@@ -6,7 +6,7 @@ import { useCertificateAuthority } from "~/server/plugins/ca";
export type EventHandlerFunction<T> = (
h3: H3Event<EventHandlerRequest>,
utils: ClientUtils
utils: ClientUtils,
) => Promise<T> | T;
type ClientUtils = {
@@ -25,7 +25,7 @@ export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
let clientId: string;
switch (method) {
case "Debug":
case "Debug": {
if (!import.meta.dev) throw createError({ statusCode: 403 });
const client = await prisma.client.findFirst({ select: { id: true } });
if (!client)
@@ -35,7 +35,8 @@ export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
});
clientId = client.id;
break;
case "Nonce":
}
case "Nonce": {
clientId = parts[0];
const nonce = parts[1];
const signature = parts[2];
@@ -59,9 +60,8 @@ export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
}
const certificateAuthority = useCertificateAuthority();
const certBundle = await certificateAuthority.fetchClientCertificate(
clientId
);
const certBundle =
await certificateAuthority.fetchClientCertificate(clientId);
// This does the blacklist check already
if (!certBundle)
throw createError({
@@ -76,12 +76,14 @@ export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
statusMessage: "Invalid nonce signature.",
});
break;
default:
}
default: {
throw createError({
statusCode: 403,
statusMessage: "No authentication",
});
}
}
if (clientId === undefined)
throw createError({
@@ -95,7 +97,7 @@ export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
});
if (!client)
throw new Error(
"client util fetch client broke - this should NOT happen"
"client util fetch client broke - this should NOT happen",
);
return client;
}
@@ -110,7 +112,7 @@ export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
if (!client)
throw new Error(
"client util fetch client broke - this should NOT happen"
"client util fetch client broke - this should NOT happen",
);
return client.user;

View File

@@ -1,5 +1,4 @@
import { randomUUID } from "node:crypto";
import { CertificateBundle } from "./ca";
import prisma from "../db/database";
import type { Platform } from "@prisma/client";
import { useCertificateAuthority } from "~/server/plugins/ca";
@@ -10,25 +9,29 @@ export interface ClientMetadata {
}
export class ClientHandler {
private temporaryClientTable: {
[key: string]: {
private temporaryClientTable = new Map<
string,
{
timeout: NodeJS.Timeout;
data: ClientMetadata;
userId?: string;
authToken?: string;
};
} = {};
}
>();
async initiate(metadata: ClientMetadata) {
const clientId = randomUUID();
this.temporaryClientTable[clientId] = {
this.temporaryClientTable.set(clientId, {
data: metadata,
timeout: setTimeout(() => {
if (this.temporaryClientTable[clientId])
delete this.temporaryClientTable[clientId];
}, 1000 * 60 * 10), // 10 minutes
};
timeout: setTimeout(
() => {
if (this.temporaryClientTable.has(clientId))
this.temporaryClientTable.delete(clientId);
},
1000 * 60 * 10,
), // 10 minutes
});
return clientId;
}
@@ -38,23 +41,23 @@ export class ClientHandler {
}
async fetchClient(clientId: string) {
const entry = this.temporaryClientTable[clientId];
const entry = this.temporaryClientTable.get(clientId);
if (!entry) return undefined;
return entry;
}
async attachUserId(clientId: string, userId: string) {
if (!this.temporaryClientTable[clientId])
throw new Error("Invalid clientId for attaching userId");
this.temporaryClientTable[clientId].userId = userId;
const clientTable = this.temporaryClientTable.get(clientId);
if (!clientTable) throw new Error("Invalid clientId for attaching userId");
clientTable.userId = userId;
}
async generateAuthToken(clientId: string) {
const entry = this.temporaryClientTable[clientId];
const entry = this.temporaryClientTable.get(clientId);
if (!entry) throw new Error("Invalid clientId to generate token");
const token = randomUUID();
this.temporaryClientTable[clientId].authToken = token;
entry.authToken = token;
return token;
}
@@ -66,7 +69,7 @@ export class ClientHandler {
}
async finialiseClient(id: string) {
const metadata = this.temporaryClientTable[id];
const metadata = this.temporaryClientTable.get(id);
if (!metadata) throw new Error("Invalid client ID");
if (!metadata.userId) throw new Error("Un-authorized client ID");

View File

@@ -5,6 +5,5 @@ When a client signs on and registers itself as a peer
*/
class DownloadCoordinator {
}
// eslint-disable-next-line @typescript-eslint/no-extraneous-class, @typescript-eslint/no-unused-vars
class DownloadCoordinator {}

View File

@@ -33,7 +33,7 @@ class ManifestGenerator {
key,
Object.assign({}, value, { versionName: rootManifest.versionName }),
];
})
}),
);
}
@@ -71,6 +71,7 @@ class ManifestGenerator {
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.versionIndex - 1; true; i--) {
const currentVersion = await prisma.gameVersion.findFirst({
where: {
@@ -88,7 +89,7 @@ class ManifestGenerator {
const metadata: DropManifestMetadata[] = leastToMost.map((e) => {
return {
manifest: JSON.parse(
e.dropletManifest?.toString() ?? "{}"
e.dropletManifest?.toString() ?? "{}",
) as DropManifest,
versionName: e.versionName,
};
@@ -96,7 +97,7 @@ class ManifestGenerator {
const manifest = ManifestGenerator.generateManifestFromMetadata(
metadata[0],
...metadata.slice(1)
...metadata.slice(1),
);
return manifest;

View File

@@ -8,8 +8,7 @@
import fs from "fs";
import path from "path";
import prisma from "../db/database";
import type { GameVersion} from "@prisma/client";
import { Platform } from "@prisma/client";
import type { GameVersion } from "@prisma/client";
import { fuzzy } from "fast-fuzzy";
import { recursivelyReaddir } from "../utils/recursivedirs";
import taskHandler from "../tasks";
@@ -52,13 +51,13 @@ class LibraryManager {
async fetchUnimportedGameVersions(
libraryBasePath: string,
versions: Array<GameVersion>
versions: Array<GameVersion>,
) {
const gameDir = path.join(this.basePath, libraryBasePath);
const versionsDirs = fs.readdirSync(gameDir);
const importedVersionDirs = versions.map((e) => e.versionName);
const unimportedVersions = versionsDirs.filter(
(e) => !importedVersionDirs.includes(e)
(e) => !importedVersionDirs.includes(e),
);
return unimportedVersions;
@@ -89,10 +88,10 @@ class LibraryManager {
noVersions: e.versions.length == 0,
unimportedVersions: await this.fetchUnimportedGameVersions(
e.libraryBasePath,
e.versions
e.versions,
),
},
}))
})),
);
}
@@ -113,7 +112,7 @@ class LibraryManager {
const targetDir = path.join(this.basePath, game.libraryBasePath);
if (!fs.existsSync(targetDir))
throw new Error(
"Game in database, but no physical directory? Something is very very wrong..."
"Game in database, but no physical directory? Something is very very wrong...",
);
const versions = fs.readdirSync(targetDir);
const validVersions = versions.filter((versionDir) => {
@@ -124,7 +123,7 @@ class LibraryManager {
const currentVersions = game.versions.map((e) => e.versionName);
const unimportedVersions = validVersions.filter(
(e) => !currentVersions.includes(e)
(e) => !currentVersions.includes(e),
);
return unimportedVersions;
}
@@ -138,7 +137,7 @@ class LibraryManager {
const targetDir = path.join(
this.basePath,
game.libraryBasePath,
versionName
versionName,
);
if (!fs.existsSync(targetDir)) return undefined;
@@ -217,7 +216,7 @@ class LibraryManager {
delta: boolean;
umuId: string;
}
},
) {
const taskId = `import:${gameId}:${versionName}`;
@@ -254,7 +253,7 @@ class LibraryManager {
(err, manifest) => {
if (err) return reject(err);
resolve(manifest);
}
},
);
});

View File

@@ -1,10 +1,5 @@
import type {
Developer,
Publisher} from "@prisma/client";
import {
MetadataSource,
PrismaClient
} from "@prisma/client";
import type { Developer, Publisher } from "@prisma/client";
import { MetadataSource } from "@prisma/client";
import prisma from "../db/database";
import type {
_FetchDeveloperMetadataParams,
@@ -17,11 +12,7 @@ import type {
PublisherMetadata,
} from "./types";
import { ObjectTransactionalHandler } from "../objects/transactional";
import { PriorityList, PriorityListIndexed } from "../utils/prioritylist";
import { GiantBombProvider } from "./giantbomb";
import { ManualMetadataProvider } from "./manual";
import { PCGamingWikiProvider } from "./pcgamingwiki";
import { IGDBProvider } from "./igdb";
import { PriorityListIndexed } from "../utils/prioritylist";
export class MissingMetadataProviderConfig extends Error {
private providerName: string;
@@ -47,10 +38,10 @@ export abstract class MetadataProvider {
abstract search(query: string): Promise<GameMetadataSearchResult[]>;
abstract fetchGame(params: _FetchGameMetadataParams): Promise<GameMetadata>;
abstract fetchPublisher(
params: _FetchPublisherMetadataParams
params: _FetchPublisherMetadataParams,
): Promise<PublisherMetadata>;
abstract fetchDeveloper(
params: _FetchDeveloperMetadataParams
params: _FetchDeveloperMetadataParams,
): Promise<DeveloperMetadata>;
}
@@ -81,6 +72,8 @@ export class MetadataHandler {
for (const provider of this.providers.values()) {
const queryTransformationPromise = new Promise<
InternalGameMetadataResult[]
// TODO: fix eslint error
// eslint-disable-next-line no-async-promise-executor
>(async (resolve, reject) => {
try {
const results = await provider.search(query);
@@ -89,7 +82,7 @@ export class MetadataHandler {
Object.assign({}, result, {
sourceId: provider.id(),
sourceName: provider.name(),
})
}),
);
resolve(mappedResults);
} catch (e) {
@@ -119,13 +112,13 @@ export class MetadataHandler {
sourceId: "manual",
sourceName: "Manual",
},
libraryBasePath
libraryBasePath,
);
}
async createGame(
result: InternalGameMetadataResult,
libraryBasePath: string
libraryBasePath: string,
) {
const provider = this.providers.get(result.sourceId);
if (!provider)
@@ -143,7 +136,7 @@ export class MetadataHandler {
const [createObject, pullObjects, dumpObjects] = this.objectHandler.new(
{},
["internal:read"]
["internal:read"],
);
let metadata;
@@ -197,7 +190,7 @@ export class MetadataHandler {
return (await this.fetchDeveloperPublisher(
query,
"fetchDeveloper",
"developer"
"developer",
)) as Developer;
}
@@ -205,7 +198,7 @@ export class MetadataHandler {
return (await this.fetchDeveloperPublisher(
query,
"fetchPublisher",
"publisher"
"publisher",
)) as Publisher;
}
@@ -214,8 +207,9 @@ export class MetadataHandler {
private async fetchDeveloperPublisher(
query: string,
functionName: "fetchDeveloper" | "fetchPublisher",
databaseName: "developer" | "publisher"
databaseName: "developer" | "publisher",
) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const existing = await (prisma as any)[databaseName].findFirst({
where: {
metadataOriginalQuery: query,
@@ -229,7 +223,7 @@ export class MetadataHandler {
const [createObject, pullObjects, dumpObjects] = this.objectHandler.new(
{},
["internal:read"]
["internal:read"],
);
let result: PublisherMetadata;
try {
@@ -243,6 +237,7 @@ export class MetadataHandler {
// If we're successful
await pullObjects();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const object = await (prisma as any)[databaseName].create({
data: {
metadataSource: provider.source(),
@@ -262,7 +257,7 @@ export class MetadataHandler {
}
throw new Error(
`No metadata provider found a ${databaseName} for "${query}"`
`No metadata provider found a ${databaseName} for "${query}"`,
);
}
}

View File

@@ -6,9 +6,7 @@ import type {
_FetchPublisherMetadataParams,
PublisherMetadata,
_FetchDeveloperMetadataParams,
DeveloperMetadata} from "./types";
import {
GameMetadataSearchResult
DeveloperMetadata,
} from "./types";
import * as jdenticon from "jdenticon";
@@ -22,13 +20,11 @@ export class ManualMetadataProvider implements MetadataProvider {
source() {
return MetadataSource.Manual;
}
async search(query: string) {
async search(_query: string) {
return [];
}
async fetchGame({
name,
publisher,
developer,
createObject,
}: _FetchGameMetadataParams): Promise<GameMetadata> {
const icon = jdenticon.toPng(name, 512);
@@ -52,12 +48,12 @@ export class ManualMetadataProvider implements MetadataProvider {
};
}
async fetchPublisher(
params: _FetchPublisherMetadataParams
_params: _FetchPublisherMetadataParams,
): Promise<PublisherMetadata> {
throw new Error("Method not implemented.");
}
async fetchDeveloper(
params: _FetchDeveloperMetadataParams
_params: _FetchDeveloperMetadataParams,
): Promise<DeveloperMetadata> {
throw new Error("Method not implemented.");
}

View File

@@ -1,7 +1,6 @@
import type { Developer, Publisher } from "@prisma/client";
import { MetadataSource } from "@prisma/client";
import type { MetadataProvider} from ".";
import { MissingMetadataProviderConfig } from ".";
import type { MetadataProvider } from ".";
import type {
GameMetadataSearchResult,
_FetchGameMetadataParams,
@@ -48,7 +47,7 @@ interface PCGamingWikiCargoResult<T> {
cargoquery: [
{
title: T;
}
},
];
error?: {
code?: string;
@@ -61,8 +60,6 @@ interface PCGamingWikiCargoResult<T> {
// Api Docs: https://www.pcgamingwiki.com/wiki/PCGamingWiki:API
// Good tool for helping build cargo queries: https://www.pcgamingwiki.com/wiki/Special:CargoQuery
export class PCGamingWikiProvider implements MetadataProvider {
constructor() {}
id() {
return "pcgamingwiki";
}
@@ -75,7 +72,7 @@ export class PCGamingWikiProvider implements MetadataProvider {
private async request<T>(
query: URLSearchParams,
options?: AxiosRequestConfig
options?: AxiosRequestConfig,
) {
const finalURL = `https://www.pcgamingwiki.com/w/api.php?${query.toString()}`;
@@ -84,12 +81,12 @@ export class PCGamingWikiProvider implements MetadataProvider {
baseURL: "",
};
const response = await axios.request<PCGamingWikiCargoResult<T>>(
Object.assign({}, options, overlay)
Object.assign({}, options, overlay),
);
if (response.status !== 200)
throw new Error(
`Error in pcgamingwiki \nStatus Code: ${response.status}`
`Error in pcgamingwiki \nStatus Code: ${response.status}`,
);
else if (response.data.error !== undefined)
throw new Error(`Error in pcgamingwiki, malformed query`);
@@ -256,7 +253,7 @@ export class PCGamingWikiProvider implements MetadataProvider {
}
async fetchDeveloper(
params: _FetchDeveloperMetadataParams
params: _FetchDeveloperMetadataParams,
): Promise<DeveloperMetadata> {
return await this.fetchPublisher(params);
}

View File

@@ -1,6 +1,5 @@
import type { Developer, Publisher } from "@prisma/client";
import type { TransactionDataType } from "../objects/transactional";
import { ObjectTransactionalHandler } from "../objects/transactional";
import type { ObjectReference } from "../objects/objectHandler";
export interface GameMetadataSearchResult {

View File

@@ -1,4 +1,3 @@
import { triggerAsyncId } from "async_hooks";
import prisma from "../db/database";
import objectHandler from "../objects";
@@ -50,7 +49,7 @@ class NewsManager {
orderBy?: "asc" | "desc";
tags?: string[];
search?: string;
} = {}
} = {},
) {
return await prisma.article.findMany({
where: {
@@ -116,7 +115,7 @@ class NewsManager {
content?: string;
excerpt?: string;
image?: string;
}
},
) {
return await prisma.article.update({
where: { id },

View File

@@ -15,27 +15,28 @@ export type NotificationCreateArgs = Pick<
>;
class NotificationSystem {
private listeners: {
[key: string]: Map<string, (notification: Notification) => any>;
} = {};
private listeners = new Map<
string,
Map<string, (notification: Notification) => void>
>();
listen(
userId: string,
id: string,
callback: (notification: Notification) => any
callback: (notification: Notification) => void,
) {
this.listeners[userId] ??= new Map();
this.listeners[userId].set(id, callback);
this.listeners.set(userId, new Map());
this.listeners.get(userId)?.set(id, callback);
this.catchupListener(userId, id);
}
unlisten(userId: string, id: string) {
this.listeners[userId].delete(id);
this.listeners.get(userId)?.delete(id);
}
private async catchupListener(userId: string, id: string) {
const callback = this.listeners[userId].get(id);
const callback = this.listeners.get(userId)?.get(id);
if (!callback)
throw new Error("Failed to catch-up listener: callback does not exist");
const notifications = await prisma.notification.findMany({
@@ -50,7 +51,7 @@ class NotificationSystem {
}
private async pushNotification(userId: string, notification: Notification) {
for (const listener of this.listeners[userId] ?? []) {
for (const listener of this.listeners.get(userId) ?? []) {
await listener[1](notification);
}
}

View File

@@ -4,7 +4,7 @@ import { ObjectBackend } from "./objectHandler";
import { LRUCache } from "lru-cache";
import fs from "fs";
import path from "path";
import { Readable, Stream } from "stream";
import { Readable } from "stream";
import { createHash } from "crypto";
import prisma from "../db/database";
@@ -40,7 +40,7 @@ export class FsObjectBackend extends ObjectBackend {
if (source instanceof Readable) {
const outputStream = fs.createWriteStream(objectPath);
source.pipe(outputStream, { end: true });
await new Promise((r, j) => source.on("end", r));
await new Promise((r, _j) => source.on("end", r));
return true;
}
@@ -61,7 +61,7 @@ export class FsObjectBackend extends ObjectBackend {
async create(
id: string,
source: Source,
metadata: ObjectMetadata
metadata: ObjectMetadata,
): Promise<ObjectReference | undefined> {
const objectPath = path.join(this.baseObjectPath, id);
const metadataPath = path.join(this.baseMetadataPath, `${id}.json`);
@@ -104,7 +104,7 @@ export class FsObjectBackend extends ObjectBackend {
return true;
}
async fetchMetadata(
id: ObjectReference
id: ObjectReference,
): Promise<ObjectMetadata | undefined> {
const metadataPath = path.join(this.baseMetadataPath, `${id}.json`);
if (!fs.existsSync(metadataPath)) return undefined;
@@ -113,7 +113,7 @@ export class FsObjectBackend extends ObjectBackend {
}
async writeMetadata(
id: ObjectReference,
metadata: ObjectMetadata
metadata: ObjectMetadata,
): Promise<boolean> {
const metadataPath = path.join(this.baseMetadataPath, `${id}.json`);
if (!fs.existsSync(metadataPath)) return false;
@@ -153,8 +153,6 @@ class FsHashStore {
max: 1000, // number of items
});
constructor() {}
/**
* Gets hash of object
* @param id
@@ -211,6 +209,8 @@ class FsHashStore {
id,
},
});
} catch {}
} catch {
/* empty */
}
}
}

View File

@@ -16,7 +16,7 @@
import { parse as getMimeTypeBuffer } from "file-type-mime";
import type { Writable } from "stream";
import Stream, { Readable } from "stream";
import { Readable } from "stream";
import { getMimeType as getMimeTypeStream } from "stream-mime-type";
export type ObjectReference = string;
@@ -50,19 +50,19 @@ export abstract class ObjectBackend {
abstract create(
id: string,
source: Source,
metadata: ObjectMetadata
metadata: ObjectMetadata,
): Promise<ObjectReference | undefined>;
abstract createWithWriteStream(
id: string,
metadata: ObjectMetadata
metadata: ObjectMetadata,
): Promise<Writable | undefined>;
abstract delete(id: ObjectReference): Promise<boolean>;
abstract fetchMetadata(
id: ObjectReference
id: ObjectReference,
): Promise<ObjectMetadata | undefined>;
abstract writeMetadata(
id: ObjectReference,
metadata: ObjectMetadata
metadata: ObjectMetadata,
): Promise<boolean>;
abstract fetchHash(id: ObjectReference): Promise<string | undefined>;
}
@@ -96,7 +96,7 @@ export class ObjectHandler {
id: string,
sourceFetcher: () => Promise<Source>,
metadata: { [key: string]: string },
permissions: Array<string>
permissions: Array<string>,
) {
const { source, mime } = await this.fetchMimeType(await sourceFetcher());
if (!mime)
@@ -112,7 +112,7 @@ export class ObjectHandler {
async createWithStream(
id: string,
metadata: { [key: string]: string },
permissions: Array<string>
permissions: Array<string>,
) {
return this.backend.createWithWriteStream(id, {
permissions,
@@ -194,7 +194,7 @@ export class ObjectHandler {
async writeWithPermissions(
id: ObjectReference,
sourceFetcher: () => Promise<Source>,
userId?: string
userId?: string,
) {
const metadata = await this.backend.fetchMetadata(id);
if (!metadata) return false;

View File

@@ -1,6 +1,6 @@
import prisma from "../internal/db/database";
export default defineNitroPlugin(async () => {
export default defineNitroPlugin(async (_nitro) => {
// Ensure system user exists
// The system user owns any user-based code
// that we want to re-use for the app

View File

@@ -1,6 +1,6 @@
import prisma from "../internal/db/database";
export default defineNitroPlugin(async () => {
export default defineNitroPlugin(async (_nitro) => {
const userCount = await prisma.user.count({
where: { id: { not: "system" } },
});

View File

@@ -6,7 +6,7 @@ import { IGDBProvider } from "../internal/metadata/igdb";
import { ManualMetadataProvider } from "../internal/metadata/manual";
import { PCGamingWikiProvider } from "../internal/metadata/pcgamingwiki";
export default defineNitroPlugin(async () => {
export default defineNitroPlugin(async (_nitro) => {
const metadataProviders = [
GiantBombProvider,
PCGamingWikiProvider,