mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-09 20:12:10 +10:00
Compare commits
11 Commits
5c1b0e6c1e
...
fix-types
| Author | SHA1 | Date | |
|---|---|---|---|
| d0475d3ebd | |||
| 7c234067a5 | |||
| 824b4e708b | |||
| 94e795787e | |||
| a0b4381f0b | |||
| 80f7757558 | |||
| 90b02b7f8e | |||
| 29fdfcbdd4 | |||
| e3feaeb970 | |||
| ef7a62bf0b | |||
| 7af29ef0eb |
Submodule drop-base updated: 04125e89be...4c42edf5ad
@ -2,14 +2,10 @@
|
||||
<div class="min-h-screen flex flex-col">
|
||||
<div class="grow grid grid-cols-1 lg:grid-cols-2">
|
||||
<div class="border-b lg:border-b-0 lg:border-r border-zinc-700">
|
||||
<header
|
||||
class="mx-auto w-full max-w-7xl px-6 pt-6 sm:pt-10 lg:col-span-2 lg:col-start-1 lg:row-start-1 lg:px-8"
|
||||
>
|
||||
<header class="mx-auto w-full max-w-7xl px-6 pt-6 sm:pt-10 lg:col-span-2 lg:col-start-1 lg:row-start-1 lg:px-8">
|
||||
<DropWordmark />
|
||||
</header>
|
||||
<main
|
||||
class="mx-auto w-full max-w-7xl px-6 py-24 sm:py-32 lg:col-span-2 lg:col-start-1 lg:row-start-2 lg:px-8"
|
||||
>
|
||||
<main class="mx-auto w-full max-w-7xl px-6 py-24 sm:py-32 lg:col-span-2 lg:col-start-1 lg:row-start-2 lg:px-8">
|
||||
<div>
|
||||
<h1 class="text-4xl font-display font-bold text-zinc-100">
|
||||
{{ $t("setup.welcome") }}
|
||||
@ -20,32 +16,19 @@
|
||||
</p>
|
||||
</div>
|
||||
<ul role="list" class="mt-10 divide-y divide-zinc-700/5">
|
||||
<li
|
||||
v-for="(action, actionIdx) in actions"
|
||||
:key="action.name"
|
||||
class="relative flex gap-x-6 py-6"
|
||||
>
|
||||
<li v-for="(action, actionIdx) in actions" :key="action.name" class="relative flex gap-x-6 py-6">
|
||||
<div
|
||||
class="flex size-10 flex-none items-center justify-center rounded-lg shadow-xs outline-1 outline-zinc-100/10"
|
||||
>
|
||||
<component
|
||||
:is="action.icon"
|
||||
v-if="!actionsComplete[actionIdx]"
|
||||
class="size-6 text-blue-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
class="flex size-10 flex-none items-center justify-center rounded-lg shadow-xs outline-1 outline-zinc-100/10">
|
||||
<component :is="action.icon" v-if="!actionsComplete[actionIdx]" class="size-6 text-blue-500"
|
||||
aria-hidden="true" />
|
||||
<CheckIcon v-else class="size-6 text-blue-500" />
|
||||
</div>
|
||||
<div class="flex-auto">
|
||||
<h3 class="text-sm/6 font-semibold text-zinc-100">
|
||||
<button
|
||||
:class="
|
||||
actionsComplete[actionIdx]
|
||||
? 'line-through text-zinc-300'
|
||||
: ''
|
||||
"
|
||||
@click="() => (currentAction = actionIdx)"
|
||||
>
|
||||
<button :class="actionsComplete[actionIdx]
|
||||
? 'line-through text-zinc-300'
|
||||
: ''
|
||||
" @click="() => (currentAction = actionIdx)">
|
||||
<span class="absolute inset-0" aria-hidden="true" />
|
||||
{{ action.name }}
|
||||
</button>
|
||||
@ -55,18 +38,11 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex-none self-center">
|
||||
<ChevronRightIcon
|
||||
class="size-5 text-gray-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<ChevronRightIcon class="size-5 text-gray-400" aria-hidden="true" />
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<LoadingButton
|
||||
:disabled="!finished"
|
||||
:loading="finishLoading"
|
||||
@click="() => finish()"
|
||||
>
|
||||
<LoadingButton :disabled="!finished" :loading="finishLoading" @click="() => finish()">
|
||||
<i18n-t keypath="setup.finish" tag="span" scope="global">
|
||||
<template #arrow>
|
||||
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
||||
@ -75,25 +51,13 @@
|
||||
</LoadingButton>
|
||||
</main>
|
||||
</div>
|
||||
<component
|
||||
:is="actions[currentAction].page"
|
||||
v-if="actions[currentAction] && !useModal"
|
||||
v-model="actionsComplete[currentAction]"
|
||||
:token="bearerToken"
|
||||
/>
|
||||
<div
|
||||
v-else-if="!useModal"
|
||||
class="bg-zinc-950/30 flex items-center justify-center"
|
||||
>
|
||||
<component :is="actions[currentAction].page" v-if="actions[currentAction] && !useModal"
|
||||
v-model="actionsComplete[currentAction]" :token="bearerToken" />
|
||||
<div v-else-if="!useModal" class="bg-zinc-950/30 flex items-center justify-center">
|
||||
<!-- <p class="uppercase text-sm font-display text-zinc-700 font-bold">
|
||||
{{ $t("setup.noPage") }}
|
||||
</p> -->
|
||||
<img
|
||||
src="/wallpapers/signin.jpg"
|
||||
class="inset-0 h-full w-full object-cover"
|
||||
alt=""
|
||||
preload
|
||||
/>
|
||||
<img src="/wallpapers/signin.jpg" class="inset-0 h-full w-full object-cover" alt="" preload />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@ -102,25 +66,17 @@
|
||||
<div class="fixed inset-0 bg-zinc-900/75 transition-opacity" />
|
||||
|
||||
<div class="fixed inset-0 z-10 w-screen overflow-y-auto">
|
||||
<div
|
||||
class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"
|
||||
>
|
||||
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<div
|
||||
class="relative transform overflow-hidden rounded-lg bg-zinc-900 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm"
|
||||
>
|
||||
class="relative transform overflow-hidden rounded-lg bg-zinc-900 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm">
|
||||
<div>
|
||||
<component
|
||||
:is="actions[currentAction].page"
|
||||
v-model="actionsComplete[currentAction]"
|
||||
:token="bearerToken"
|
||||
/>
|
||||
<component :is="actions[currentAction].page" v-model="actionsComplete[currentAction]"
|
||||
:token="bearerToken" />
|
||||
</div>
|
||||
<div class="mt-5 sm:mt-6 p-4">
|
||||
<button
|
||||
type="button"
|
||||
<button type="button"
|
||||
class="inline-flex w-full justify-center rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-xs hover:bg-blue-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
|
||||
@click="currentAction = -1"
|
||||
>
|
||||
@click="currentAction = -1">
|
||||
{{ $t("common.close") }}
|
||||
</button>
|
||||
</div>
|
||||
@ -164,7 +120,7 @@ if (!token)
|
||||
});
|
||||
const bearerToken = `Bearer ${token}`;
|
||||
|
||||
const allowed = await $dropFetch("/api/v1/admin", {
|
||||
const allowed = await $dropFetch("/api/v1/admin/setup", {
|
||||
headers: { Authorization: bearerToken },
|
||||
});
|
||||
if (!allowed)
|
||||
|
||||
@ -2,6 +2,9 @@ import { AuthMec } from "~/prisma/client/enums";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import authManager from "~/server/internal/auth";
|
||||
|
||||
/**
|
||||
* Fetches all the enabled authentication mechanisms on this instance, and their configuration, if enabled.
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["auth:read", "setup"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -7,6 +7,10 @@ const DeleteInvite = type({
|
||||
id: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
/**
|
||||
* Deletes a "Simple" invitation
|
||||
* @returns nothing
|
||||
*/
|
||||
export default defineEventHandler<{
|
||||
body: typeof DeleteInvite.infer;
|
||||
}>(async (h3) => {
|
||||
|
||||
@ -3,6 +3,9 @@ import { systemConfig } from "~/server/internal/config/sys-conf";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import taskHandler from "~/server/internal/tasks";
|
||||
|
||||
/**
|
||||
* Fetches a "Simple" invitation
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, [
|
||||
"auth:simple:invitation:read",
|
||||
|
||||
@ -11,6 +11,9 @@ const CreateInvite = SharedRegisterValidator.partial()
|
||||
})
|
||||
.configure(throwingArktype);
|
||||
|
||||
/**
|
||||
* Creates a "Simple" invitation
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, [
|
||||
"auth:simple:invitation:new",
|
||||
|
||||
@ -3,6 +3,11 @@ import prisma from "~/server/internal/db/database";
|
||||
import objectHandler from "~/server/internal/objects";
|
||||
import { handleFileUpload } from "~/server/internal/utils/handlefileupload";
|
||||
|
||||
/**
|
||||
* Multi-part form upload for the banner.
|
||||
* @request `multipart/form-data` data. Only one file, can be named anything.
|
||||
* @param id Company ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["company:update"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -7,31 +7,37 @@ const GameDelete = type({
|
||||
id: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["company:update"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
/**
|
||||
* Delete a game's association with a company
|
||||
* @param id Company ID
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof GameDelete.infer }>(
|
||||
async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["company:update"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const companyId = getRouterParam(h3, "id")!;
|
||||
const companyId = getRouterParam(h3, "id")!;
|
||||
|
||||
const body = await readDropValidatedBody(h3, GameDelete);
|
||||
const body = await readDropValidatedBody(h3, GameDelete);
|
||||
|
||||
await prisma.game.update({
|
||||
where: {
|
||||
id: body.id,
|
||||
},
|
||||
data: {
|
||||
publishers: {
|
||||
disconnect: {
|
||||
id: companyId,
|
||||
await prisma.game.update({
|
||||
where: {
|
||||
id: body.id,
|
||||
},
|
||||
data: {
|
||||
publishers: {
|
||||
disconnect: {
|
||||
id: companyId,
|
||||
},
|
||||
},
|
||||
developers: {
|
||||
disconnect: {
|
||||
id: companyId,
|
||||
},
|
||||
},
|
||||
},
|
||||
developers: {
|
||||
disconnect: {
|
||||
id: companyId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
});
|
||||
return;
|
||||
},
|
||||
);
|
||||
|
||||
@ -9,29 +9,35 @@ const GamePatch = type({
|
||||
id: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["company:update"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
/**
|
||||
* Update a company's association with a game.
|
||||
* @param id Company ID
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof GamePatch.infer }>(
|
||||
async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["company:update"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const companyId = getRouterParam(h3, "id")!;
|
||||
const companyId = getRouterParam(h3, "id")!;
|
||||
|
||||
const body = await readDropValidatedBody(h3, GamePatch);
|
||||
const body = await readDropValidatedBody(h3, GamePatch);
|
||||
|
||||
const action = body.action === "developed" ? "developers" : "publishers";
|
||||
const actionType = body.enabled ? "connect" : "disconnect";
|
||||
const action = body.action === "developed" ? "developers" : "publishers";
|
||||
const actionType = body.enabled ? "connect" : "disconnect";
|
||||
|
||||
await prisma.game.update({
|
||||
where: {
|
||||
id: body.id,
|
||||
},
|
||||
data: {
|
||||
[action]: {
|
||||
[actionType]: {
|
||||
id: companyId,
|
||||
await prisma.game.update({
|
||||
where: {
|
||||
id: body.id,
|
||||
},
|
||||
data: {
|
||||
[action]: {
|
||||
[actionType]: {
|
||||
id: companyId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
});
|
||||
return;
|
||||
},
|
||||
);
|
||||
|
||||
@ -9,61 +9,67 @@ const GamePost = type({
|
||||
id: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["company:update"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
/**
|
||||
* Add a new game association to this company
|
||||
* @param id Company ID
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof GamePost.infer }>(
|
||||
async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["company:update"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const companyId = getRouterParam(h3, "id")!;
|
||||
const companyId = getRouterParam(h3, "id")!;
|
||||
|
||||
const body = await readDropValidatedBody(h3, GamePost);
|
||||
const body = await readDropValidatedBody(h3, GamePost);
|
||||
|
||||
if (!body.published && !body.developed)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Must be related (either developed or published).",
|
||||
if (!body.published && !body.developed)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Must be related (either developed or published).",
|
||||
});
|
||||
|
||||
const publisherConnect = body.published
|
||||
? {
|
||||
publishers: {
|
||||
connect: {
|
||||
id: companyId,
|
||||
},
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const developerConnect = body.developed
|
||||
? {
|
||||
developers: {
|
||||
connect: {
|
||||
id: companyId,
|
||||
},
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const game = await prisma.game.update({
|
||||
where: {
|
||||
id: body.id,
|
||||
},
|
||||
data: {
|
||||
...publisherConnect,
|
||||
...developerConnect,
|
||||
},
|
||||
include: {
|
||||
publishers: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
developers: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const publisherConnect = body.published
|
||||
? {
|
||||
publishers: {
|
||||
connect: {
|
||||
id: companyId,
|
||||
},
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const developerConnect = body.developed
|
||||
? {
|
||||
developers: {
|
||||
connect: {
|
||||
id: companyId,
|
||||
},
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const game = await prisma.game.update({
|
||||
where: {
|
||||
id: body.id,
|
||||
},
|
||||
data: {
|
||||
...publisherConnect,
|
||||
...developerConnect,
|
||||
},
|
||||
include: {
|
||||
publishers: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
developers: {
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return game;
|
||||
});
|
||||
return game;
|
||||
},
|
||||
);
|
||||
|
||||
@ -3,6 +3,11 @@ import prisma from "~/server/internal/db/database";
|
||||
import objectHandler from "~/server/internal/objects";
|
||||
import { handleFileUpload } from "~/server/internal/utils/handlefileupload";
|
||||
|
||||
/**
|
||||
* Multi-part form upload for the icon of this company
|
||||
* @request `multipart/form-data` data. Only one file, can be named anything.
|
||||
* @param id Company ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["company:update"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Delete this company
|
||||
* @param id Company ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["company:delete"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Fetch a company and its associations
|
||||
* @param id Company ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["company:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Update a company. Pass any fields into the body to be updated on the model
|
||||
* @request Partial of the data returned by GET, minus the `developed` and `published` fields.
|
||||
* @param id Company ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["company:update"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Fetch all companies on this instance
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["company:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -12,36 +12,41 @@ const CompanyCreate = type({
|
||||
website: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["company:create"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
/**
|
||||
* Create a new company on this instance
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof CompanyCreate.infer }>(
|
||||
async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["company:create"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const body = await readDropValidatedBody(h3, CompanyCreate);
|
||||
const obj = new ObjectTransactionalHandler();
|
||||
const [register, pull, _] = obj.new({}, ["internal:read"]);
|
||||
const body = await readDropValidatedBody(h3, CompanyCreate);
|
||||
const obj = new ObjectTransactionalHandler();
|
||||
const [register, pull, _] = obj.new({}, ["internal:read"]);
|
||||
|
||||
const icon = jdenticon.toPng(body.name, 512);
|
||||
const logoId = register(icon);
|
||||
const icon = jdenticon.toPng(body.name, 512);
|
||||
const logoId = register(icon);
|
||||
|
||||
const banner = jdenticon.toPng(body.description, 1024);
|
||||
const bannerId = register(banner);
|
||||
const banner = jdenticon.toPng(body.description, 1024);
|
||||
const bannerId = register(banner);
|
||||
|
||||
const company = await prisma.company.create({
|
||||
data: {
|
||||
metadataSource: MetadataSource.Manual,
|
||||
metadataId: crypto.randomUUID(),
|
||||
metadataOriginalQuery: "",
|
||||
const company = await prisma.company.create({
|
||||
data: {
|
||||
metadataSource: MetadataSource.Manual,
|
||||
metadataId: crypto.randomUUID(),
|
||||
metadataOriginalQuery: "",
|
||||
|
||||
mName: body.name,
|
||||
mShortDescription: body.description,
|
||||
mDescription: "",
|
||||
mLogoObjectId: logoId,
|
||||
mBannerObjectId: bannerId,
|
||||
mWebsite: body.website,
|
||||
},
|
||||
});
|
||||
mName: body.name,
|
||||
mShortDescription: body.description,
|
||||
mDescription: "",
|
||||
mLogoObjectId: logoId,
|
||||
mBannerObjectId: bannerId,
|
||||
mWebsite: body.website,
|
||||
},
|
||||
});
|
||||
|
||||
await pull();
|
||||
await pull();
|
||||
|
||||
return company;
|
||||
});
|
||||
return company;
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Delete a game from this instance
|
||||
* @param id Game ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["game:delete"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -2,6 +2,10 @@ import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
|
||||
/**
|
||||
* Fetch a game by ID
|
||||
* @param id Game ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["game:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Update a game's metadata
|
||||
* @request Partial of data returned by GET
|
||||
* @param id Game ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["game:update"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -3,6 +3,11 @@ import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import { handleFileUpload } from "~/server/internal/utils/handlefileupload";
|
||||
|
||||
/**
|
||||
* Update icon, name, and/or description
|
||||
* @request `multipart/form-data`, any file will become the icon, and `name` and `description` will become their respective fields.
|
||||
* @param id Game ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["game:update"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -7,23 +7,29 @@ const PatchTags = type({
|
||||
tags: "string[]",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["game:update"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
/**
|
||||
* Update the tags associated with this game.
|
||||
* @param id Game ID
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof PatchTags.infer }>(
|
||||
async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["game:update"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const body = await readDropValidatedBody(h3, PatchTags);
|
||||
const id = getRouterParam(h3, "id")!;
|
||||
const body = await readDropValidatedBody(h3, PatchTags);
|
||||
const id = getRouterParam(h3, "id")!;
|
||||
|
||||
await prisma.game.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data: {
|
||||
tags: {
|
||||
connect: body.tags.map((e) => ({ id: e })),
|
||||
await prisma.game.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
},
|
||||
});
|
||||
data: {
|
||||
tags: {
|
||||
connect: body.tags.map((e) => ({ id: e })),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
});
|
||||
return;
|
||||
},
|
||||
);
|
||||
|
||||
@ -9,6 +9,9 @@ const DeleteGameImage = type({
|
||||
imageId: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
/**
|
||||
* Delete a game's image
|
||||
*/
|
||||
export default defineEventHandler<{
|
||||
body: typeof DeleteGameImage.infer;
|
||||
}>(async (h3) => {
|
||||
|
||||
@ -2,6 +2,10 @@ import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import { handleFileUpload } from "~/server/internal/utils/handlefileupload";
|
||||
|
||||
/**
|
||||
* Upload a game to a game's image library
|
||||
* @request `multipart/form-data`. All files will be uploaded as images. Set the game ID in the `id` field.
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["game:image:new"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Fetch brief metadata on all games connected to this instance
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["game:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -8,7 +8,10 @@ const DeleteVersion = type({
|
||||
versionName: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
export default defineEventHandler<{ body: typeof DeleteVersion }>(
|
||||
/**
|
||||
* Delete a game's version
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof DeleteVersion.infer }>(
|
||||
async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, [
|
||||
"game:version:delete",
|
||||
|
||||
@ -8,7 +8,10 @@ const UpdateVersionOrder = type({
|
||||
versions: "string[]",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
export default defineEventHandler<{ body: typeof UpdateVersionOrder }>(
|
||||
/**
|
||||
* Update the version order of a game.
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof UpdateVersionOrder.infer }>(
|
||||
async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, [
|
||||
"game:version:update",
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
|
||||
/**
|
||||
* Fetch all games that are available for import.
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["import:game:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -14,6 +14,10 @@ const ImportGameBody = type({
|
||||
},
|
||||
}).configure(throwingArktype);
|
||||
|
||||
/**
|
||||
* Import a game as a background task.
|
||||
* @response Task IDs can be used with the websocket endpoint /api/v1/task
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof ImportGameBody.infer }>(
|
||||
async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["import:game:new"]);
|
||||
|
||||
@ -1,16 +1,30 @@
|
||||
import { ArkErrors, type } from "arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import metadataHandler from "~/server/internal/metadata";
|
||||
|
||||
const SearchGame = type({
|
||||
q: "string",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type APIQuery = typeof SearchGame.infer;
|
||||
|
||||
/**
|
||||
* Search metadata providers for a query. Results can be used to import a game with metadata.
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["import:game:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const query = getQuery(h3);
|
||||
const search = query.q?.toString();
|
||||
if (!search)
|
||||
throw createError({ statusCode: 400, statusMessage: "Invalid search" });
|
||||
const search = SearchGame(query);
|
||||
if (search instanceof ArkErrors)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid search: " + search.summary,
|
||||
});
|
||||
|
||||
const results = await metadataHandler.search(search);
|
||||
const results = await metadataHandler.search(search.q);
|
||||
|
||||
if (results.length == 0)
|
||||
throw createError({
|
||||
|
||||
@ -1,19 +1,31 @@
|
||||
import { ArkErrors, type } from "arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
|
||||
const Query = type({
|
||||
id: "string",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type APIQuery = typeof Query.inferIn;
|
||||
|
||||
/**
|
||||
* Fetch all versions available for import for a game (`id` in query params).
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["import:version:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const query = await getQuery(h3);
|
||||
const gameId = query.id?.toString();
|
||||
if (!gameId)
|
||||
const query = Query(await getQuery(h3));
|
||||
if (query instanceof ArkErrors)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Missing id in request params",
|
||||
statusMessage: "Invalid query params: " + query.summary,
|
||||
});
|
||||
|
||||
const gameId = query.id;
|
||||
|
||||
const game = await prisma.game.findUnique({
|
||||
where: { id: gameId },
|
||||
select: { libraryId: true, libraryPath: true },
|
||||
|
||||
@ -19,71 +19,80 @@ const ImportVersion = type({
|
||||
umuId: "string = ''",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["import:version:new"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
/**
|
||||
* Import a version for a game.
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof ImportVersion.infer }>(
|
||||
async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["import:version:new"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const {
|
||||
id,
|
||||
version,
|
||||
platform,
|
||||
launch,
|
||||
launchArgs,
|
||||
setup,
|
||||
setupArgs,
|
||||
onlySetup,
|
||||
delta,
|
||||
umuId,
|
||||
} = await readDropValidatedBody(h3, ImportVersion);
|
||||
const {
|
||||
id,
|
||||
version,
|
||||
platform,
|
||||
launch,
|
||||
launchArgs,
|
||||
setup,
|
||||
setupArgs,
|
||||
onlySetup,
|
||||
delta,
|
||||
umuId,
|
||||
} = await readDropValidatedBody(h3, ImportVersion);
|
||||
|
||||
const platformParsed = parsePlatform(platform);
|
||||
if (!platformParsed)
|
||||
throw createError({ statusCode: 400, statusMessage: "Invalid platform." });
|
||||
const platformParsed = parsePlatform(platform);
|
||||
if (!platformParsed)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid platform.",
|
||||
});
|
||||
|
||||
if (delta) {
|
||||
const validOverlayVersions = await prisma.gameVersion.count({
|
||||
where: { gameId: id, platform: platformParsed, delta: false },
|
||||
if (delta) {
|
||||
const validOverlayVersions = await prisma.gameVersion.count({
|
||||
where: { gameId: id, platform: platformParsed, delta: false },
|
||||
});
|
||||
if (validOverlayVersions == 0)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage:
|
||||
"Update mode requires a pre-existing version for this platform.",
|
||||
});
|
||||
}
|
||||
|
||||
if (onlySetup) {
|
||||
if (!setup)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Setup required in "setup mode".',
|
||||
});
|
||||
} else {
|
||||
if (!delta && !launch)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage:
|
||||
"Launch executable is required for non-update versions",
|
||||
});
|
||||
}
|
||||
|
||||
// startup & delta require more complex checking logic
|
||||
const taskId = await libraryManager.importVersion(id, version, {
|
||||
platform,
|
||||
onlySetup,
|
||||
|
||||
launch,
|
||||
launchArgs,
|
||||
setup,
|
||||
setupArgs,
|
||||
|
||||
umuId,
|
||||
delta,
|
||||
});
|
||||
if (validOverlayVersions == 0)
|
||||
if (!taskId)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage:
|
||||
"Update mode requires a pre-existing version for this platform.",
|
||||
statusMessage: "Invalid options for import",
|
||||
});
|
||||
}
|
||||
|
||||
if (onlySetup) {
|
||||
if (!setup)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'Setup required in "setup mode".',
|
||||
});
|
||||
} else {
|
||||
if (!delta && !launch)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Launch executable is required for non-update versions",
|
||||
});
|
||||
}
|
||||
|
||||
// startup & delta require more complex checking logic
|
||||
const taskId = await libraryManager.importVersion(id, version, {
|
||||
platform,
|
||||
onlySetup,
|
||||
|
||||
launch,
|
||||
launchArgs,
|
||||
setup,
|
||||
setupArgs,
|
||||
|
||||
umuId,
|
||||
delta,
|
||||
});
|
||||
if (!taskId)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid options for import",
|
||||
});
|
||||
|
||||
return { taskId: taskId };
|
||||
});
|
||||
return { taskId: taskId };
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,18 +1,30 @@
|
||||
import { ArkErrors, type } from "arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
|
||||
const Query = type({
|
||||
id: "string",
|
||||
version: "string",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type APIQuery = typeof Query.inferIn;
|
||||
|
||||
/**
|
||||
* Fetch recommendations for version import.
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["import:version:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const query = await getQuery(h3);
|
||||
const gameId = query.id?.toString();
|
||||
const versionName = query.version?.toString();
|
||||
if (!gameId || !versionName)
|
||||
const query = Query(await getQuery(h3));
|
||||
if (query instanceof ArkErrors)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Missing id or version in request params",
|
||||
statusMessage: "Invalid query: " + query.summary,
|
||||
});
|
||||
const gameId = query.id;
|
||||
const versionName = query.version;
|
||||
|
||||
const preload = await libraryManager.fetchUnimportedVersionInformation(
|
||||
gameId,
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
|
||||
/**
|
||||
* Fetch library data for admin UI
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["library:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -8,6 +8,9 @@ const DeleteLibrarySource = type({
|
||||
id: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
/**
|
||||
* Delete a given library source
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof DeleteLibrarySource.infer }>(
|
||||
async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, [
|
||||
|
||||
@ -4,6 +4,9 @@ import libraryManager from "~/server/internal/library";
|
||||
|
||||
export type WorkingLibrarySource = LibraryModel & { working: boolean };
|
||||
|
||||
/**
|
||||
* Fetch all library sources on this instance
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, [
|
||||
"library:sources:read",
|
||||
|
||||
@ -12,6 +12,9 @@ const UpdateLibrarySource = type({
|
||||
options: "object",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
/**
|
||||
* Update a library source's options. Validates options and live-updates the source.
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof UpdateLibrarySource.infer }>(
|
||||
async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, [
|
||||
|
||||
@ -14,6 +14,9 @@ const CreateLibrarySource = type({
|
||||
options: "object",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
/**
|
||||
* Create a new library source with options
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof CreateLibrarySource.infer }>(
|
||||
async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, [
|
||||
|
||||
@ -2,6 +2,10 @@ import { defineEventHandler, createError } from "h3";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import newsManager from "~/server/internal/news";
|
||||
|
||||
/**
|
||||
* Delete a news article
|
||||
* @param id Article ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["news:delete"]);
|
||||
if (!allowed)
|
||||
|
||||
@ -2,6 +2,10 @@ import { defineEventHandler, createError } from "h3";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import newsManager from "~/server/internal/news";
|
||||
|
||||
/**
|
||||
* Fetch a single news article
|
||||
* @param id Article ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["news:read"]);
|
||||
if (!allowed)
|
||||
|
||||
@ -2,6 +2,9 @@ import { defineEventHandler, getQuery } from "h3";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import newsManager from "~/server/internal/news";
|
||||
|
||||
/**
|
||||
* Fetch all news articles.
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["news:read"]);
|
||||
if (!allowed)
|
||||
|
||||
@ -11,51 +11,56 @@ const CreateNews = type({
|
||||
tags: "string = '[]'",
|
||||
});
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["news:create"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
/**
|
||||
* Create a new news article
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof CreateNews.infer }>(
|
||||
async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["news:create"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const form = await readMultipartFormData(h3);
|
||||
if (!form)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "This endpoint requires multipart form data.",
|
||||
const form = await readMultipartFormData(h3);
|
||||
if (!form)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "This endpoint requires multipart form data.",
|
||||
});
|
||||
|
||||
const uploadResult = await handleFileUpload(h3, {}, ["internal:read"], 1);
|
||||
if (!uploadResult)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Failed to upload file",
|
||||
});
|
||||
|
||||
const [imageIds, options, pull, _dump] = uploadResult;
|
||||
|
||||
const body = await CreateNews(options);
|
||||
if (body instanceof ArkErrors)
|
||||
throw createError({ statusCode: 400, statusMessage: body.summary });
|
||||
|
||||
const parsedTags = JSON.parse(body.tags);
|
||||
if (typeof parsedTags !== "object" || !Array.isArray(parsedTags))
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Tags must be an array",
|
||||
});
|
||||
|
||||
const imageId = imageIds.at(0);
|
||||
|
||||
const article = await newsManager.create({
|
||||
title: body.title,
|
||||
description: body.description,
|
||||
content: body.content,
|
||||
|
||||
tags: parsedTags,
|
||||
|
||||
...(imageId && { imageObjectId: imageId }),
|
||||
authorId: "system",
|
||||
});
|
||||
|
||||
const uploadResult = await handleFileUpload(h3, {}, ["internal:read"], 1);
|
||||
if (!uploadResult)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Failed to upload file",
|
||||
});
|
||||
await pull();
|
||||
|
||||
const [imageIds, options, pull, _dump] = uploadResult;
|
||||
|
||||
const body = await CreateNews(options);
|
||||
if (body instanceof ArkErrors)
|
||||
throw createError({ statusCode: 400, statusMessage: body.summary });
|
||||
|
||||
const parsedTags = JSON.parse(body.tags);
|
||||
if (typeof parsedTags !== "object" || !Array.isArray(parsedTags))
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Tags must be an array",
|
||||
});
|
||||
|
||||
const imageId = imageIds.at(0);
|
||||
|
||||
const article = await newsManager.create({
|
||||
title: body.title,
|
||||
description: body.description,
|
||||
content: body.content,
|
||||
|
||||
tags: parsedTags,
|
||||
|
||||
...(imageId && { imageObjectId: imageId }),
|
||||
authorId: "system",
|
||||
});
|
||||
|
||||
await pull();
|
||||
|
||||
return article;
|
||||
});
|
||||
return article;
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Fetch dummy data for rendering the settings page.
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.getUserACL(h3, ["settings:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -8,6 +8,9 @@ const UpdateSettings = type({
|
||||
showGamePanelTextDecoration: "boolean",
|
||||
});
|
||||
|
||||
/**
|
||||
* Update global Drop settings.
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof UpdateSettings.infer }>(
|
||||
async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["settings:update"]);
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
|
||||
/**
|
||||
* Check if we are a setup token
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, []);
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["setup"]);
|
||||
if (!allowed) return false;
|
||||
return true;
|
||||
});
|
||||
@ -1,6 +1,10 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Delete game tags.
|
||||
* @param id Tag ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["tags:delete"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Fetch all game tags
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["tags:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -7,16 +7,21 @@ const CreateTag = type({
|
||||
name: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["tags:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
/**
|
||||
* Create a game tag
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof CreateTag.infer }>(
|
||||
async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["tags:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const body = await readDropValidatedBody(h3, CreateTag);
|
||||
const body = await readDropValidatedBody(h3, CreateTag);
|
||||
|
||||
const tag = await prisma.gameTag.create({
|
||||
data: {
|
||||
...body,
|
||||
},
|
||||
});
|
||||
return tag;
|
||||
});
|
||||
const tag = await prisma.gameTag.create({
|
||||
data: {
|
||||
...body,
|
||||
},
|
||||
});
|
||||
return tag;
|
||||
},
|
||||
);
|
||||
|
||||
@ -2,6 +2,9 @@ import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import taskHandler from "~/server/internal/tasks";
|
||||
|
||||
/**
|
||||
* Fetches all tasks that the current token has access to (ACL-based)
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["task:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -2,6 +2,10 @@ import { defineEventHandler, createError } from "h3";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Delete a user
|
||||
* @param id User ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["user:delete"]);
|
||||
if (!allowed)
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Fetch a user by ID
|
||||
* @param id User ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["user:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Fetch all users and their enabled authentication mechanisms
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["user:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import authManager from "~/server/internal/auth";
|
||||
|
||||
/**
|
||||
* Fetch public authentication provider mechanisms
|
||||
*/
|
||||
export default defineEventHandler(() => {
|
||||
return authManager.getEnabledAuthProviders();
|
||||
});
|
||||
|
||||
@ -15,6 +15,9 @@ const signinValidator = type({
|
||||
"rememberMe?": "boolean | undefined",
|
||||
});
|
||||
|
||||
/**
|
||||
* Sign in as a session using the "Simple" authentication mechanism. Not recommended for third-party applications.
|
||||
*/
|
||||
export default defineEventHandler<{
|
||||
body: typeof signinValidator.infer;
|
||||
}>(async (h3) => {
|
||||
|
||||
@ -1,7 +1,18 @@
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import taskHandler from "~/server/internal/tasks";
|
||||
import authManager from "~/server/internal/auth";
|
||||
import { ArkErrors, type } from "arktype";
|
||||
|
||||
const Query = type({
|
||||
id: "string",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type APIQuery = typeof Query.inferIn;
|
||||
|
||||
/**
|
||||
* Fetch invitation details for pre-filling
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const t = await useTranslation(h3);
|
||||
|
||||
@ -11,13 +22,13 @@ export default defineEventHandler(async (h3) => {
|
||||
statusMessage: t("errors.auth.method.signinDisabled"),
|
||||
});
|
||||
|
||||
const query = getQuery(h3);
|
||||
const id = query.id?.toString();
|
||||
if (!id)
|
||||
const query = Query(getQuery(h3));
|
||||
if (query instanceof ArkErrors)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: t("errors.auth.inviteIdRequired"),
|
||||
statusMessage: "Invalid query: " + query.summary,
|
||||
});
|
||||
const id = query.id;
|
||||
taskHandler.runTaskGroupByName("cleanup:invitations");
|
||||
|
||||
const invitation = await prisma.invitation.findUnique({ where: { id: id } });
|
||||
|
||||
@ -18,6 +18,9 @@ const CreateUserValidator = SharedRegisterValidator.and({
|
||||
"displayName?": "string | undefined",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
/**
|
||||
* Create user from invitation
|
||||
*/
|
||||
export default defineEventHandler<{
|
||||
body: typeof CreateUserValidator.infer;
|
||||
}>(async (h3) => {
|
||||
|
||||
@ -1,30 +1,41 @@
|
||||
import { type } from "arktype";
|
||||
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import clientHandler from "~/server/internal/clients/handler";
|
||||
import sessionHandler from "~/server/internal/session";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const user = await sessionHandler.getSession(h3);
|
||||
if (!user) throw createError({ statusCode: 403 });
|
||||
const AuthorizeBody = type({
|
||||
id: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
const body = await readBody(h3);
|
||||
const clientId = await body.id;
|
||||
/**
|
||||
* Finalize the authorization for a client
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof AuthorizeBody.infer }>(
|
||||
async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, []);
|
||||
if (!userId) throw createError({ statusCode: 403 });
|
||||
|
||||
const client = await clientHandler.fetchClient(clientId);
|
||||
if (!client)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid or expired client ID.",
|
||||
});
|
||||
const body = await readDropValidatedBody(h3, AuthorizeBody);
|
||||
const clientId = body.id;
|
||||
|
||||
if (client.userId != user.userId)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Not allowed to authorize this client.",
|
||||
});
|
||||
const client = await clientHandler.fetchClient(clientId);
|
||||
if (!client)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid or expired client ID.",
|
||||
});
|
||||
|
||||
const token = await clientHandler.generateAuthToken(clientId);
|
||||
if (client.userId != userId)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Not allowed to authorize this client.",
|
||||
});
|
||||
|
||||
return {
|
||||
redirect: `drop://handshake/${clientId}/${token}`,
|
||||
token: `${clientId}/${token}`,
|
||||
};
|
||||
});
|
||||
const token = await clientHandler.generateAuthToken(clientId);
|
||||
|
||||
return {
|
||||
redirect: `drop://handshake/${clientId}/${token}`,
|
||||
token: `${clientId}/${token}`,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,17 +1,25 @@
|
||||
import { ArkErrors, type } from "arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import clientHandler from "~/server/internal/clients/handler";
|
||||
import sessionHandler from "~/server/internal/session";
|
||||
|
||||
const Query = type({
|
||||
code: "string.upper",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type APIQuery = typeof Query.inferIn;
|
||||
|
||||
/**
|
||||
* Fetch client ID by authorize code
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const user = await sessionHandler.getSession(h3);
|
||||
if (!user) throw createError({ statusCode: 403 });
|
||||
const userId = await aclManager.getUserIdACL(h3, []);
|
||||
if (!userId) throw createError({ statusCode: 403 });
|
||||
|
||||
const query = getQuery(h3);
|
||||
const code = query.code?.toString()?.toUpperCase();
|
||||
if (!code)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Code required in query params.",
|
||||
});
|
||||
const query = Query(getQuery(h3));
|
||||
if (query instanceof ArkErrors)
|
||||
throw createError({ statusCode: 400, statusMessage: query.summary });
|
||||
const code = query.code;
|
||||
|
||||
const clientId = await clientHandler.fetchClientIdByCode(code);
|
||||
if (!clientId)
|
||||
|
||||
@ -1,35 +1,46 @@
|
||||
import { type } from "arktype";
|
||||
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import clientHandler from "~/server/internal/clients/handler";
|
||||
import sessionHandler from "~/server/internal/session";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const user = await sessionHandler.getSession(h3);
|
||||
if (!user) throw createError({ statusCode: 403 });
|
||||
const CodeAuthorize = type({
|
||||
id: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
const body = await readBody(h3);
|
||||
const clientId = await body.id;
|
||||
/**
|
||||
* Authorize code by client ID, and send token via WS to client
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof CodeAuthorize.infer }>(
|
||||
async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, []);
|
||||
if (!userId) throw createError({ statusCode: 403 });
|
||||
|
||||
const client = await clientHandler.fetchClient(clientId);
|
||||
if (!client)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid or expired client ID.",
|
||||
});
|
||||
const body = await readDropValidatedBody(h3, CodeAuthorize);
|
||||
const clientId = body.id;
|
||||
|
||||
if (client.userId != user.userId)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Not allowed to authorize this client.",
|
||||
});
|
||||
const client = await clientHandler.fetchClient(clientId);
|
||||
if (!client)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid or expired client ID.",
|
||||
});
|
||||
|
||||
if (!client.peer)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: "No client listening for authorization.",
|
||||
});
|
||||
if (client.userId != userId)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Not allowed to authorize this client.",
|
||||
});
|
||||
|
||||
const token = await clientHandler.generateAuthToken(clientId);
|
||||
if (!client.peer)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: "No client listening for authorization.",
|
||||
});
|
||||
|
||||
await clientHandler.sendAuthToken(clientId, token);
|
||||
const token = await clientHandler.generateAuthToken(clientId);
|
||||
|
||||
return;
|
||||
});
|
||||
await clientHandler.sendAuthToken(clientId, token);
|
||||
|
||||
return;
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import type { FetchError } from "ofetch";
|
||||
import clientHandler from "~/server/internal/clients/handler";
|
||||
|
||||
/**
|
||||
* Client route to listen for code authorization.
|
||||
* @request Pass the code in the `Authorization` header
|
||||
*/
|
||||
export default defineWebSocketHandler({
|
||||
async open(peer) {
|
||||
try {
|
||||
|
||||
@ -1,45 +1,52 @@
|
||||
import { type } from "arktype";
|
||||
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
|
||||
import clientHandler from "~/server/internal/clients/handler";
|
||||
import { useCertificateAuthority } from "~/server/plugins/ca";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const body = await readBody(h3);
|
||||
const clientId = body.clientId;
|
||||
const token = body.token;
|
||||
if (!clientId || !token)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Missing token or client ID from body",
|
||||
});
|
||||
const HandshakeBody = type({
|
||||
clientId: "string",
|
||||
token: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
const metadata = await clientHandler.fetchClient(clientId);
|
||||
if (!metadata)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Invalid client ID",
|
||||
});
|
||||
if (!metadata.authToken || !metadata.userId)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Un-authorized client ID",
|
||||
});
|
||||
if (metadata.authToken !== token)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Invalid token",
|
||||
});
|
||||
/**
|
||||
* Client route to complete handshake, after the user has authorize it.
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof HandshakeBody.infer }>(
|
||||
async (h3) => {
|
||||
const body = await readDropValidatedBody(h3, HandshakeBody);
|
||||
const clientId = body.clientId;
|
||||
const token = body.token;
|
||||
|
||||
const certificateAuthority = useCertificateAuthority();
|
||||
const bundle = await certificateAuthority.generateClientCertificate(
|
||||
clientId,
|
||||
metadata.data.name,
|
||||
);
|
||||
const metadata = await clientHandler.fetchClient(clientId);
|
||||
if (!metadata)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Invalid client ID",
|
||||
});
|
||||
if (!metadata.authToken || !metadata.userId)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Un-authorized client ID",
|
||||
});
|
||||
if (metadata.authToken !== token)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Invalid token",
|
||||
});
|
||||
|
||||
const client = await clientHandler.finialiseClient(clientId);
|
||||
await certificateAuthority.storeClientCertificate(clientId, bundle);
|
||||
const certificateAuthority = useCertificateAuthority();
|
||||
const bundle = await certificateAuthority.generateClientCertificate(
|
||||
clientId,
|
||||
metadata.data.name,
|
||||
);
|
||||
|
||||
return {
|
||||
private: bundle.priv,
|
||||
certificate: bundle.cert,
|
||||
id: client.id,
|
||||
};
|
||||
});
|
||||
const client = await clientHandler.finialiseClient(clientId);
|
||||
await certificateAuthority.storeClientCertificate(clientId, bundle);
|
||||
|
||||
return {
|
||||
private: bundle.priv,
|
||||
certificate: bundle.cert,
|
||||
id: client.id,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,17 +1,25 @@
|
||||
import { ArkErrors, type } from "arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import clientHandler from "~/server/internal/clients/handler";
|
||||
import sessionHandler from "~/server/internal/session";
|
||||
|
||||
const Query = type({
|
||||
id: "string",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type APIQuery = typeof Query.inferIn;
|
||||
|
||||
/**
|
||||
* Fetch details about an authorization request, and claim it for the current user
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const user = await sessionHandler.getSession(h3);
|
||||
if (!user) throw createError({ statusCode: 403 });
|
||||
const userId = await aclManager.getUserIdACL(h3, []);
|
||||
if (!userId) throw createError({ statusCode: 403 });
|
||||
|
||||
const query = getQuery(h3);
|
||||
const providedClientId = query.id?.toString();
|
||||
if (!providedClientId)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Provide client ID in request params as 'id'",
|
||||
});
|
||||
const query = Query(getQuery(h3));
|
||||
if (query instanceof ArkErrors)
|
||||
throw createError({ statusCode: 400, statusMessage: query.summary });
|
||||
const providedClientId = query.id;
|
||||
|
||||
const client = await clientHandler.fetchClient(providedClientId);
|
||||
if (!client)
|
||||
@ -20,13 +28,13 @@ export default defineEventHandler(async (h3) => {
|
||||
statusMessage: "Request not found.",
|
||||
});
|
||||
|
||||
if (client.userId && user.userId !== client.userId)
|
||||
if (client.userId && userId !== client.userId)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Client already claimed.",
|
||||
});
|
||||
|
||||
await clientHandler.attachUserId(providedClientId, user.userId);
|
||||
await clientHandler.attachUserId(providedClientId, userId);
|
||||
|
||||
return client.data;
|
||||
});
|
||||
|
||||
@ -17,55 +17,61 @@ const ClientAuthInitiate = type({
|
||||
mode: type.valueOf(AuthMode).default(AuthMode.Callback),
|
||||
}).configure(throwingArktype);
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const body = await readDropValidatedBody(h3, ClientAuthInitiate);
|
||||
/**
|
||||
* Client route to initiate authorization flow.
|
||||
* @response The requested callback or code.
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof ClientAuthInitiate.infer }>(
|
||||
async (h3) => {
|
||||
const body = await readDropValidatedBody(h3, ClientAuthInitiate);
|
||||
|
||||
const platformRaw = body.platform;
|
||||
const capabilities: Partial<CapabilityConfiguration> =
|
||||
body.capabilities ?? {};
|
||||
const platformRaw = body.platform;
|
||||
const capabilities: Partial<CapabilityConfiguration> =
|
||||
body.capabilities ?? {};
|
||||
|
||||
const platform = parsePlatform(platformRaw);
|
||||
if (!platform)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid or unsupported platform",
|
||||
const platform = parsePlatform(platformRaw);
|
||||
if (!platform)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid or unsupported platform",
|
||||
});
|
||||
|
||||
const capabilityIterable = Object.entries(capabilities) as Array<
|
||||
[InternalClientCapability, object]
|
||||
>;
|
||||
if (
|
||||
capabilityIterable.length > 0 &&
|
||||
capabilityIterable
|
||||
.map(([capability]) => validCapabilities.find((v) => capability == v))
|
||||
.filter((e) => e).length == 0
|
||||
)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid capabilities.",
|
||||
});
|
||||
|
||||
if (
|
||||
capabilityIterable.length > 0 &&
|
||||
capabilityIterable.filter(
|
||||
([capability, configuration]) =>
|
||||
!capabilityManager.validateCapabilityConfiguration(
|
||||
capability,
|
||||
configuration,
|
||||
),
|
||||
).length > 0
|
||||
)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid capability configuration.",
|
||||
});
|
||||
|
||||
const result = await clientHandler.initiate({
|
||||
name: body.name,
|
||||
platform,
|
||||
capabilities,
|
||||
mode: body.mode,
|
||||
});
|
||||
|
||||
const capabilityIterable = Object.entries(capabilities) as Array<
|
||||
[InternalClientCapability, object]
|
||||
>;
|
||||
if (
|
||||
capabilityIterable.length > 0 &&
|
||||
capabilityIterable
|
||||
.map(([capability]) => validCapabilities.find((v) => capability == v))
|
||||
.filter((e) => e).length == 0
|
||||
)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid capabilities.",
|
||||
});
|
||||
|
||||
if (
|
||||
capabilityIterable.length > 0 &&
|
||||
capabilityIterable.filter(
|
||||
([capability, configuration]) =>
|
||||
!capabilityManager.validateCapabilityConfiguration(
|
||||
capability,
|
||||
configuration,
|
||||
),
|
||||
).length > 0
|
||||
)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid capability configuration.",
|
||||
});
|
||||
|
||||
const result = await clientHandler.initiate({
|
||||
name: body.name,
|
||||
platform,
|
||||
capabilities,
|
||||
mode: body.mode,
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
return result;
|
||||
},
|
||||
);
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export default defineEventHandler((_h3) => {});
|
||||
@ -1,63 +0,0 @@
|
||||
import type { InternalClientCapability } from "~/server/internal/clients/capabilities";
|
||||
import capabilityManager, {
|
||||
validCapabilities,
|
||||
} from "~/server/internal/clients/capabilities";
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import notificationSystem from "~/server/internal/notifications";
|
||||
|
||||
export default defineClientEventHandler(
|
||||
async (h3, { clientId, fetchClient, fetchUser }) => {
|
||||
const body = await readBody(h3);
|
||||
const rawCapability = body.capability;
|
||||
const configuration = body.configuration;
|
||||
|
||||
if (!rawCapability || typeof rawCapability !== "string")
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "capability must be a string",
|
||||
});
|
||||
|
||||
if (!configuration || typeof configuration !== "object")
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "configuration must be an object",
|
||||
});
|
||||
|
||||
const capability = rawCapability as InternalClientCapability;
|
||||
|
||||
if (!validCapabilities.includes(capability))
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid capability.",
|
||||
});
|
||||
|
||||
const isValid = await capabilityManager.validateCapabilityConfiguration(
|
||||
capability,
|
||||
configuration,
|
||||
);
|
||||
if (!isValid)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid capability configuration.",
|
||||
});
|
||||
|
||||
await capabilityManager.upsertClientCapability(
|
||||
capability,
|
||||
configuration,
|
||||
clientId,
|
||||
);
|
||||
|
||||
const client = await fetchClient();
|
||||
const user = await fetchUser();
|
||||
|
||||
await notificationSystem.push(user.id, {
|
||||
nonce: `capability-${clientId}-${capability}`,
|
||||
title: `"${client.name}" can now access ${capability}`,
|
||||
description: `A device called "${client.name}" now has access to your ${capability}.`,
|
||||
actions: ["Review|/account/devices"],
|
||||
acls: ["user:clients:read"],
|
||||
});
|
||||
|
||||
return {};
|
||||
},
|
||||
);
|
||||
@ -1,3 +1,4 @@
|
||||
import { ArkErrors, type } from "arktype";
|
||||
import cacheHandler from "~/server/internal/cache";
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
@ -10,12 +11,29 @@ const gameLookupCache = cacheHandler.createCache<{
|
||||
libraryPath: string;
|
||||
}>("downloadGameLookupCache");
|
||||
|
||||
const Query = type({
|
||||
id: "string",
|
||||
version: "string",
|
||||
name: "string",
|
||||
chunk: "string.numeric.parse",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type APIQuery = typeof Query.inferIn;
|
||||
|
||||
/**
|
||||
* v1 download API
|
||||
* @deprecated
|
||||
* @response Raw binary data (`application/octet-stream`)
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3) => {
|
||||
const query = getQuery(h3);
|
||||
const gameId = query.id?.toString();
|
||||
const versionName = query.version?.toString();
|
||||
const filename = query.name?.toString();
|
||||
const chunkIndex = parseInt(query.chunk?.toString() ?? "?");
|
||||
const query = Query(getQuery(h3));
|
||||
if (query instanceof ArkErrors)
|
||||
throw createError({ statusCode: 400, statusMessage: query.summary });
|
||||
const gameId = query.id;
|
||||
const versionName = query.version;
|
||||
const filename = query.name;
|
||||
const chunkIndex = query.chunk;
|
||||
|
||||
if (!gameId || !versionName || !filename || Number.isNaN(chunkIndex))
|
||||
throw createError({
|
||||
@ -35,7 +53,10 @@ export default defineClientEventHandler(async (h3) => {
|
||||
},
|
||||
});
|
||||
if (!game || !game.libraryId)
|
||||
throw createError({ statusCode: 400, statusMessage: "Invalid game ID" });
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Invalid game ID",
|
||||
});
|
||||
|
||||
await gameLookupCache.setItem(gameId, game);
|
||||
}
|
||||
@ -79,5 +100,7 @@ export default defineClientEventHandler(async (h3) => {
|
||||
statusMessage: "Failed to create stream",
|
||||
});
|
||||
|
||||
return sendStream(h3, gameReadStream);
|
||||
await sendStream(h3, gameReadStream);
|
||||
|
||||
return;
|
||||
});
|
||||
|
||||
@ -1,7 +1,17 @@
|
||||
import { type } from "arktype";
|
||||
import { throwingArktype } from "~/server/arktype";
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
export default defineClientEventHandler(async (h3, { fetchUser }) => {
|
||||
const EntryRemove = type({
|
||||
id: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
/**
|
||||
* Remove entry from user's collection
|
||||
* @param id Collection ID
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3, { fetchUser, body }) => {
|
||||
const user = await fetchUser();
|
||||
|
||||
const id = getRouterParam(h3, "id");
|
||||
@ -11,10 +21,7 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
|
||||
statusMessage: "ID required in route params",
|
||||
});
|
||||
|
||||
const body = await readBody(h3);
|
||||
const gameId = body.id;
|
||||
if (!gameId)
|
||||
throw createError({ statusCode: 400, statusMessage: "Game ID required" });
|
||||
|
||||
const successful = await userLibraryManager.collectionRemove(
|
||||
gameId,
|
||||
@ -27,4 +34,4 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
|
||||
statusMessage: "Collection not found",
|
||||
});
|
||||
return {};
|
||||
});
|
||||
}, EntryRemove);
|
||||
|
||||
@ -1,7 +1,17 @@
|
||||
import { type } from "arktype";
|
||||
import { throwingArktype } from "~/server/arktype";
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
export default defineClientEventHandler(async (h3, { fetchUser }) => {
|
||||
const AddEntry = type({
|
||||
id: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
/**
|
||||
* Add entry to collection by game ID
|
||||
* @param id Collection ID
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3, { fetchUser, body }) => {
|
||||
const user = await fetchUser();
|
||||
|
||||
const id = getRouterParam(h3, "id");
|
||||
@ -11,10 +21,7 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
|
||||
statusMessage: "ID required in route params",
|
||||
});
|
||||
|
||||
const body = await readBody(h3);
|
||||
const gameId = body.id;
|
||||
if (!gameId)
|
||||
throw createError({ statusCode: 400, statusMessage: "Game ID required" });
|
||||
|
||||
return await userLibraryManager.collectionAdd(gameId, id, user.id);
|
||||
});
|
||||
}, AddEntry);
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
/**
|
||||
* Delete collection by ID
|
||||
* @param id Collection ID
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3, { fetchUser }) => {
|
||||
const user = await fetchUser();
|
||||
|
||||
const id = getRouterParam(h3, "id");
|
||||
if (!id)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "ID required in route params",
|
||||
});
|
||||
const id = getRouterParam(h3, "id")!;
|
||||
|
||||
// Verify collection exists and user owns it
|
||||
// Will not return the default collection
|
||||
@ -27,5 +26,5 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
|
||||
});
|
||||
|
||||
await userLibraryManager.deleteCollection(id);
|
||||
return { success: true };
|
||||
return;
|
||||
});
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
/**
|
||||
* Fetch collection by ID
|
||||
* @param id Collection ID
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3, { fetchUser }) => {
|
||||
const user = await fetchUser();
|
||||
|
||||
const id = getRouterParam(h3, "id");
|
||||
if (!id)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "ID required in route params",
|
||||
});
|
||||
const id = getRouterParam(h3, "id")!;
|
||||
|
||||
// Fetch specific collection
|
||||
// Will not return the default collection
|
||||
|
||||
@ -1,15 +1,20 @@
|
||||
import { type } from "arktype";
|
||||
import { throwingArktype } from "~/server/arktype";
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
export default defineClientEventHandler(async (h3, { fetchUser }) => {
|
||||
const EntryDelete = type({
|
||||
id: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
/**
|
||||
* Remove game from user's library
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3, { fetchUser, body }) => {
|
||||
const user = await fetchUser();
|
||||
|
||||
const body = await readBody(h3);
|
||||
|
||||
const gameId = body.id;
|
||||
if (!gameId)
|
||||
throw createError({ statusCode: 400, statusMessage: "Game ID required" });
|
||||
|
||||
await userLibraryManager.libraryRemove(gameId, user.id);
|
||||
return {};
|
||||
});
|
||||
}, EntryDelete);
|
||||
|
||||
@ -1,15 +1,21 @@
|
||||
import { type } from "arktype";
|
||||
import { throwingArktype } from "~/server/arktype";
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
export default defineClientEventHandler(async (h3, { fetchUser }) => {
|
||||
const AddEntry = type({
|
||||
id: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
/**
|
||||
* Add game to user's library
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3, { fetchUser, body }) => {
|
||||
const user = await fetchUser();
|
||||
|
||||
const body = await readBody(h3);
|
||||
const gameId = body.id;
|
||||
if (!gameId)
|
||||
throw createError({ statusCode: 400, statusMessage: "Game ID required" });
|
||||
|
||||
// Add the game to the default collection
|
||||
await userLibraryManager.libraryAdd(gameId, user.id);
|
||||
return {};
|
||||
});
|
||||
}, AddEntry);
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
/**
|
||||
* Fetch user's library
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3, { fetchUser }) => {
|
||||
const user = await fetchUser();
|
||||
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
/**
|
||||
* Fetch all of user's collections
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3, { fetchUser }) => {
|
||||
const user = await fetchUser();
|
||||
|
||||
|
||||
@ -1,14 +1,19 @@
|
||||
import { type } from "arktype";
|
||||
import { throwingArktype } from "~/server/arktype";
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
export default defineClientEventHandler(async (h3, { fetchUser }) => {
|
||||
const CreateCollection = type({
|
||||
name: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
/**
|
||||
* Create new collection
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3, { fetchUser, body }) => {
|
||||
const user = await fetchUser();
|
||||
|
||||
const body = await readBody(h3);
|
||||
|
||||
const name = body.name;
|
||||
if (!name)
|
||||
throw createError({ statusCode: 400, statusMessage: "Requires name" });
|
||||
|
||||
// Create the collection using the manager
|
||||
const newCollection = await userLibraryManager.collectionCreate(
|
||||
@ -16,4 +21,4 @@ export default defineClientEventHandler(async (h3, { fetchUser }) => {
|
||||
user.id,
|
||||
);
|
||||
return newCollection;
|
||||
});
|
||||
}, CreateCollection);
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Fetch game by ID
|
||||
* @param id Game ID
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3) => {
|
||||
const id = getRouterParam(h3, "id");
|
||||
if (!id)
|
||||
throw createError({ statusCode: 400, statusMessage: "No ID in route" });
|
||||
const id = getRouterParam(h3, "id")!;
|
||||
|
||||
const game = await prisma.game.findUnique({
|
||||
where: {
|
||||
|
||||
@ -1,15 +1,23 @@
|
||||
import { ArkErrors, type } from "arktype";
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import manifestGenerator from "~/server/internal/downloads/manifest";
|
||||
|
||||
const Query = type({
|
||||
id: "string",
|
||||
version: "string",
|
||||
});
|
||||
|
||||
/**
|
||||
* Fetch Droplet manifest from game ID and version
|
||||
* @request `id` and `version` query params are required.
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3) => {
|
||||
const query = getQuery(h3);
|
||||
const id = query.id?.toString();
|
||||
const version = query.version?.toString();
|
||||
if (!id || !version)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Missing id or version in query",
|
||||
});
|
||||
const query = Query(getQuery(h3));
|
||||
if (query instanceof ArkErrors)
|
||||
throw createError({ statusCode: 400, statusMessage: query.summary });
|
||||
|
||||
const id = query.id;
|
||||
const version = query.version;
|
||||
|
||||
const manifest = await manifestGenerator.generateManifest(id, version);
|
||||
if (!manifest)
|
||||
|
||||
@ -1,15 +1,22 @@
|
||||
import { ArkErrors, type } from "arktype";
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
const Query = type({
|
||||
id: "string",
|
||||
version: "string",
|
||||
});
|
||||
|
||||
/**
|
||||
* Fetch version metadata from game ID and version name
|
||||
* @request `id` and `version` query params are required.
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3) => {
|
||||
const query = getQuery(h3);
|
||||
const id = query.id?.toString();
|
||||
const version = query.version?.toString();
|
||||
if (!id || !version)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Missing id or version in query",
|
||||
});
|
||||
const query = Query(getQuery(h3));
|
||||
if (query instanceof ArkErrors)
|
||||
throw createError({ statusCode: 400, statusMessage: query.summary });
|
||||
const id = query.id;
|
||||
const version = query.version;
|
||||
|
||||
const gameVersion = await prisma.gameVersion.findUnique({
|
||||
where: {
|
||||
@ -18,6 +25,9 @@ export default defineClientEventHandler(async (h3) => {
|
||||
versionName: version,
|
||||
},
|
||||
},
|
||||
omit: {
|
||||
dropletManifest: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!gameVersion)
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Fetch all versions for game ID
|
||||
* @request `id` required in query params
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3) => {
|
||||
const query = getQuery(h3);
|
||||
const id = query.id?.toString();
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import newsManager from "~/server/internal/news";
|
||||
|
||||
/**
|
||||
* Fetch new article by ID
|
||||
* @param id Article ID
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3) => {
|
||||
const id = h3.context.params?.id;
|
||||
if (!id)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: "Missing news ID",
|
||||
});
|
||||
const id = getRouterParam(h3, "id")!;
|
||||
|
||||
const news = await newsManager.fetchById(id);
|
||||
if (!news)
|
||||
|
||||
@ -1,27 +1,44 @@
|
||||
import { ArkErrors, type } from "arktype";
|
||||
import { getQuery } from "h3";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import newsManager from "~/server/internal/news";
|
||||
|
||||
const NewsFetch = type({
|
||||
"order?": "'asc' | 'desc'",
|
||||
"tags?": "string[]",
|
||||
"limit?": "string.numeric.parse",
|
||||
"skip?": "string.numeric.parse",
|
||||
"search?": "string",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type APIQuery = typeof NewsFetch.infer;
|
||||
|
||||
/**
|
||||
* Fetch instance news articles
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3) => {
|
||||
const query = getQuery(h3);
|
||||
const userId = await aclManager.getUserIdACL(h3, ["news:read"]);
|
||||
if (!userId)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Requires authentication",
|
||||
});
|
||||
|
||||
const orderBy = query.order as "asc" | "desc";
|
||||
if (orderBy) {
|
||||
if (typeof orderBy !== "string" || !["asc", "desc"].includes(orderBy))
|
||||
throw createError({ statusCode: 400, statusMessage: "Invalid order" });
|
||||
}
|
||||
const query = NewsFetch(getQuery(h3));
|
||||
if (query instanceof ArkErrors)
|
||||
throw createError({ statusCode: 400, statusMessage: query.summary });
|
||||
|
||||
const tags = query.tags as string[] | undefined;
|
||||
if (tags) {
|
||||
if (typeof tags !== "object" || !Array.isArray(tags))
|
||||
throw createError({ statusCode: 400, statusMessage: "Invalid tags" });
|
||||
}
|
||||
const orderBy = query.order;
|
||||
const tags = query.tags;
|
||||
|
||||
const options = {
|
||||
take: parseInt(query.limit as string),
|
||||
skip: parseInt(query.skip as string),
|
||||
take: Math.min(query.limit ?? 10, 10),
|
||||
skip: query.skip ?? 0,
|
||||
orderBy: orderBy,
|
||||
...(tags && { tags: tags.map((e) => e.toString()) }),
|
||||
search: query.search as string,
|
||||
search: query.search,
|
||||
};
|
||||
|
||||
const news = await newsManager.fetch(options);
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import objectHandler from "~/server/internal/objects";
|
||||
|
||||
/**
|
||||
* Fetch object by ID
|
||||
* @param id Object ID
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3, utils) => {
|
||||
const id = getRouterParam(h3, "id");
|
||||
if (!id) throw createError({ statusCode: 400, statusMessage: "Invalid ID" });
|
||||
const id = getRouterParam(h3, "id")!;
|
||||
|
||||
const user = await utils.fetchUser();
|
||||
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
|
||||
/**
|
||||
* Fetch user data for this client
|
||||
*/
|
||||
export default defineClientEventHandler(async (h3, { fetchUser }) => {
|
||||
const user = await fetchUser();
|
||||
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
/**
|
||||
* Fetch library for user
|
||||
*/
|
||||
export default defineClientEventHandler(async (_h3, { fetchUser }) => {
|
||||
const user = await fetchUser();
|
||||
const library = await userLibraryManager.fetchLibrary(user.id);
|
||||
|
||||
@ -4,6 +4,9 @@ import type { UserACL } from "~/server/internal/acls";
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Generate API token with limited API scopes to render store in client
|
||||
*/
|
||||
export default defineClientEventHandler(
|
||||
async (h3, { fetchUser, fetchClient, clientId }) => {
|
||||
const user = await fetchUser();
|
||||
|
||||
@ -1,34 +1,44 @@
|
||||
import { type } from "arktype";
|
||||
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["collections:remove"]);
|
||||
if (!userId)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
});
|
||||
const RemoveEntry = type({
|
||||
id: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
const id = getRouterParam(h3, "id");
|
||||
if (!id)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "ID required in route params",
|
||||
});
|
||||
/**
|
||||
* Remove entry from collection
|
||||
* @param id Collection ID
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof RemoveEntry.infer }>(
|
||||
async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["collections:remove"]);
|
||||
if (!userId)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
});
|
||||
|
||||
const body = await readBody(h3);
|
||||
const gameId = body.id;
|
||||
if (!gameId)
|
||||
throw createError({ statusCode: 400, statusMessage: "Game ID required" });
|
||||
const id = getRouterParam(h3, "id");
|
||||
if (!id)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "ID required in route params",
|
||||
});
|
||||
|
||||
const successful = await userLibraryManager.collectionRemove(
|
||||
gameId,
|
||||
id,
|
||||
userId,
|
||||
);
|
||||
if (!successful)
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: "Collection not found",
|
||||
});
|
||||
return {};
|
||||
});
|
||||
const body = await readDropValidatedBody(h3, RemoveEntry);
|
||||
const gameId = body.id;
|
||||
|
||||
const successful = await userLibraryManager.collectionRemove(
|
||||
gameId,
|
||||
id,
|
||||
userId,
|
||||
);
|
||||
if (!successful)
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: "Collection not found",
|
||||
});
|
||||
return {};
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,24 +1,34 @@
|
||||
import { type } from "arktype";
|
||||
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["collections:add"]);
|
||||
if (!userId)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
});
|
||||
const AddEntry = type({
|
||||
id: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
const id = getRouterParam(h3, "id");
|
||||
if (!id)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "ID required in route params",
|
||||
});
|
||||
/**
|
||||
* Add game to collection
|
||||
* @param id Collection ID
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof AddEntry.infer }>(
|
||||
async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["collections:add"]);
|
||||
if (!userId)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
});
|
||||
|
||||
const body = await readBody(h3);
|
||||
const gameId = body.id;
|
||||
if (!gameId)
|
||||
throw createError({ statusCode: 400, statusMessage: "Game ID required" });
|
||||
const id = getRouterParam(h3, "id");
|
||||
if (!id)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "ID required in route params",
|
||||
});
|
||||
|
||||
return await userLibraryManager.collectionAdd(gameId, id, userId);
|
||||
});
|
||||
const body = await readDropValidatedBody(h3, AddEntry);
|
||||
const gameId = body.id;
|
||||
|
||||
return await userLibraryManager.collectionAdd(gameId, id, userId);
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
/**
|
||||
* Delete a collection
|
||||
* @param id Collection ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["collections:delete"]);
|
||||
if (!userId)
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
/**
|
||||
* Fetch collection by ID
|
||||
* @param id Collection ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["collections:read"]);
|
||||
if (!userId)
|
||||
|
||||
@ -1,20 +1,29 @@
|
||||
import { type } from "arktype";
|
||||
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["library:remove"]);
|
||||
if (!userId)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Requires authentication",
|
||||
});
|
||||
const DeleteEntry = type({
|
||||
id: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
const body = await readBody(h3);
|
||||
/**
|
||||
* Remove game from user library
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof DeleteEntry.infer }>(
|
||||
async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["library:remove"]);
|
||||
if (!userId)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Requires authentication",
|
||||
});
|
||||
|
||||
const gameId = body.id;
|
||||
if (!gameId)
|
||||
throw createError({ statusCode: 400, statusMessage: "Game ID required" });
|
||||
const body = await readDropValidatedBody(h3, DeleteEntry);
|
||||
|
||||
await userLibraryManager.libraryRemove(gameId, userId);
|
||||
return {};
|
||||
});
|
||||
const gameId = body.id;
|
||||
|
||||
await userLibraryManager.libraryRemove(gameId, userId);
|
||||
return {};
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,20 +1,29 @@
|
||||
import { type } from "arktype";
|
||||
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["library:add"]);
|
||||
if (!userId)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Requires authentication",
|
||||
});
|
||||
const AddGame = type({
|
||||
id: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
const body = await readBody(h3);
|
||||
const gameId = body.id;
|
||||
if (!gameId)
|
||||
throw createError({ statusCode: 400, statusMessage: "Game ID required" });
|
||||
/**
|
||||
* Add game to user library
|
||||
*/
|
||||
export default defineEventHandler<{ body: typeof AddGame.infer }>(
|
||||
async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["library:add"]);
|
||||
if (!userId)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Requires authentication",
|
||||
});
|
||||
|
||||
// Add the game to the default collection
|
||||
await userLibraryManager.libraryAdd(gameId, userId);
|
||||
return {};
|
||||
});
|
||||
const body = await readDropValidatedBody(h3, AddGame);
|
||||
const gameId = body.id;
|
||||
|
||||
// Add the game to the default collection
|
||||
await userLibraryManager.libraryAdd(gameId, userId);
|
||||
return {};
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
/**
|
||||
* Fetch user library
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["collections:read"]);
|
||||
if (!userId)
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
/**
|
||||
* Fetch all collections
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["collections:read"]);
|
||||
if (!userId)
|
||||
|
||||
@ -1,20 +1,28 @@
|
||||
import { type } from "arktype";
|
||||
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import userLibraryManager from "~/server/internal/userlibrary";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["collections:read"]);
|
||||
if (!userId)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
});
|
||||
const CreateCollection = type({
|
||||
name: "string",
|
||||
}).configure(throwingArktype);
|
||||
|
||||
const body = await readBody(h3);
|
||||
export default defineEventHandler<{ body: typeof CreateCollection.infer }>(
|
||||
async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["collections:read"]);
|
||||
if (!userId)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
});
|
||||
|
||||
const name = body.name;
|
||||
if (!name)
|
||||
throw createError({ statusCode: 400, statusMessage: "Requires name" });
|
||||
const body = await readDropValidatedBody(h3, CreateCollection);
|
||||
const name = body.name;
|
||||
|
||||
// Create the collection using the manager
|
||||
const newCollection = await userLibraryManager.collectionCreate(name, userId);
|
||||
return newCollection;
|
||||
});
|
||||
// Create the collection using the manager
|
||||
const newCollection = await userLibraryManager.collectionCreate(
|
||||
name,
|
||||
userId,
|
||||
);
|
||||
return newCollection;
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Fetch company by ID
|
||||
* @param id Company ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["store:read"]);
|
||||
if (!userId) throw createError({ statusCode: 403 });
|
||||
|
||||
const companyId = getRouterParam(h3, "id");
|
||||
if (!companyId)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Missing gameId in route params (somehow...?)",
|
||||
});
|
||||
const companyId = getRouterParam(h3, "id")!;
|
||||
|
||||
const company = await prisma.company.findUnique({
|
||||
where: { id: companyId },
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Fetch game by ID
|
||||
* @param id Game ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["store:read"]);
|
||||
if (!userId) throw createError({ statusCode: 403 });
|
||||
|
||||
const gameId = getRouterParam(h3, "id");
|
||||
if (!gameId)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Missing gameId in route params (somehow...?)",
|
||||
});
|
||||
const gameId = getRouterParam(h3, "id")!;
|
||||
|
||||
const game = await prisma.game.findUnique({
|
||||
where: { id: gameId },
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { systemConfig } from "~/server/internal/config/sys-conf";
|
||||
|
||||
export default defineEventHandler((_h3) => {
|
||||
/**
|
||||
* Fetch instance information
|
||||
*/
|
||||
export default defineEventHandler(async (_h3) => {
|
||||
return {
|
||||
appName: "Drop",
|
||||
version: systemConfig.getDropVersion(),
|
||||
|
||||
@ -2,6 +2,10 @@ import { defineEventHandler, createError } from "h3";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import newsManager from "~/server/internal/news";
|
||||
|
||||
/**
|
||||
* Fetch news article by ID
|
||||
* @param id Article ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["news:read"]);
|
||||
if (!userId)
|
||||
@ -10,12 +14,7 @@ export default defineEventHandler(async (h3) => {
|
||||
statusMessage: "Requires authentication",
|
||||
});
|
||||
|
||||
const id = h3.context.params?.id;
|
||||
if (!id)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
message: "Missing news ID",
|
||||
});
|
||||
const id = getRouterParam(h3, "id")!;
|
||||
|
||||
const news = await newsManager.fetchById(id);
|
||||
if (!news)
|
||||
|
||||
@ -1,7 +1,22 @@
|
||||
import { ArkErrors, type } from "arktype";
|
||||
import { defineEventHandler, getQuery } from "h3";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import newsManager from "~/server/internal/news";
|
||||
|
||||
const NewsFetch = type({
|
||||
"order?": "'asc' | 'desc'",
|
||||
"tags?": "string[]",
|
||||
"limit?": "string.numeric.parse",
|
||||
"skip?": "string.numeric.parse",
|
||||
"search?": "string",
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
type APIQuery = typeof NewsFetch.infer;
|
||||
|
||||
/**
|
||||
* Fetch instance news articles
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["news:read"]);
|
||||
if (!userId)
|
||||
@ -10,26 +25,19 @@ export default defineEventHandler(async (h3) => {
|
||||
statusMessage: "Requires authentication",
|
||||
});
|
||||
|
||||
const query = getQuery(h3);
|
||||
const query = NewsFetch(getQuery(h3));
|
||||
if (query instanceof ArkErrors)
|
||||
throw createError({ statusCode: 400, statusMessage: query.summary });
|
||||
|
||||
const orderBy = query.order as "asc" | "desc";
|
||||
if (orderBy) {
|
||||
if (typeof orderBy !== "string" || !["asc", "desc"].includes(orderBy))
|
||||
throw createError({ statusCode: 400, statusMessage: "Invalid order" });
|
||||
}
|
||||
|
||||
const tags = query.tags as string[] | undefined;
|
||||
if (tags) {
|
||||
if (typeof tags !== "object" || !Array.isArray(tags))
|
||||
throw createError({ statusCode: 400, statusMessage: "Invalid tags" });
|
||||
}
|
||||
const orderBy = query.order;
|
||||
const tags = query.tags;
|
||||
|
||||
const options = {
|
||||
take: parseInt(query.limit as string),
|
||||
skip: parseInt(query.skip as string),
|
||||
take: Math.min(query.limit ?? 10, 10),
|
||||
skip: query.skip ?? 0,
|
||||
orderBy: orderBy,
|
||||
...(tags && { tags: tags.map((e) => e.toString()) }),
|
||||
search: query.search as string,
|
||||
search: query.search,
|
||||
};
|
||||
|
||||
const news = await newsManager.fetch(options);
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Delete notification.
|
||||
* @param id Notification ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["notifications:delete"]);
|
||||
if (!userId) throw createError({ statusCode: 403 });
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
|
||||
/**
|
||||
* Fetch notification by ID
|
||||
* @param id Notification ID
|
||||
*/
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["notifications:read"]);
|
||||
if (!userId) throw createError({ statusCode: 403 });
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user