From 083f6bc01c9979373927f50f7c3333cee6794b2c Mon Sep 17 00:00:00 2001 From: pinks Date: Tue, 17 Oct 2023 15:03:14 +0200 Subject: [PATCH] simplify api routes --- api/botRoute.ts | 9 +++ api/paramsRoute.ts | 27 +++----- api/serveApi.ts | 17 +---- api/sessionsRoute.ts | 1 - api/withUser.ts | 28 +++++++++ api/workersRoute.ts | 146 ++++++++++--------------------------------- deno.json | 7 +++ ui/WorkersPage.tsx | 2 +- 8 files changed, 90 insertions(+), 147 deletions(-) create mode 100644 api/botRoute.ts create mode 100644 api/withUser.ts diff --git a/api/botRoute.ts b/api/botRoute.ts new file mode 100644 index 0000000..f5466b1 --- /dev/null +++ b/api/botRoute.ts @@ -0,0 +1,9 @@ +import { createEndpoint, createMethodFilter } from "t_rest/server"; +import { bot } from "../bot/mod.ts"; + +export const botRoute = createMethodFilter({ + GET: createEndpoint({ query: null, body: null }, async () => { + const username = bot.botInfo.username; + return { status: 200, body: { type: "application/json", data: { username } } }; + }), +}); diff --git a/api/paramsRoute.ts b/api/paramsRoute.ts index 6532795..f0c4bad 100644 --- a/api/paramsRoute.ts +++ b/api/paramsRoute.ts @@ -2,8 +2,7 @@ import { deepMerge } from "std/collections/deep_merge.ts"; import { info } from "std/log/mod.ts"; import { createEndpoint, createMethodFilter } from "t_rest/server"; import { configSchema, getConfig, setConfig } from "../app/config.ts"; -import { bot } from "../bot/mod.ts"; -import { sessions } from "./sessionsRoute.ts"; +import { withUser } from "./withUser.ts"; export const paramsRoute = createMethodFilter({ GET: createEndpoint( @@ -23,23 +22,13 @@ export const paramsRoute = createMethodFilter({ }, }, async ({ query, body }) => { - const session = sessions.get(query.sessionId); - if (!session?.userId) { - return { status: 401, body: { type: "text/plain", data: "Must be logged in" } }; - } - const chat = await bot.api.getChat(session.userId); - if (chat.type !== "private") throw new Error("Chat is not private"); - if (!chat.username) { - return { status: 403, body: { type: "text/plain", data: "Must have a username" } }; - } - const config = await getConfig(); - if (!config?.adminUsernames?.includes(chat.username)) { - return { status: 403, body: { type: "text/plain", data: "Must be an admin" } }; - } - info(`User ${chat.username} updated default params: ${JSON.stringify(body.data)}`); - const defaultParams = deepMerge(config.defaultParams ?? {}, body.data); - await setConfig({ defaultParams }); - return { status: 200, body: { type: "application/json", data: config.defaultParams } }; + return withUser(query, async (chat) => { + const config = await getConfig(); + info(`User ${chat.username} updated default params: ${JSON.stringify(body.data)}`); + const defaultParams = deepMerge(config.defaultParams ?? {}, body.data); + await setConfig({ defaultParams }); + return { status: 200, body: { type: "application/json", data: config.defaultParams } }; + }, { admin: true }); }, ), }); diff --git a/api/serveApi.ts b/api/serveApi.ts index 6d72732..c907be6 100644 --- a/api/serveApi.ts +++ b/api/serveApi.ts @@ -1,16 +1,11 @@ -import { - createEndpoint, - createLoggerMiddleware, - createMethodFilter, - createPathFilter, -} from "t_rest/server"; +import { createLoggerMiddleware, createPathFilter } from "t_rest/server"; +import { botRoute } from "./botRoute.ts"; import { jobsRoute } from "./jobsRoute.ts"; import { paramsRoute } from "./paramsRoute.ts"; import { sessionsRoute } from "./sessionsRoute.ts"; import { statsRoute } from "./statsRoute.ts"; import { usersRoute } from "./usersRoute.ts"; import { workersRoute } from "./workersRoute.ts"; -import { bot } from "../bot/mod.ts"; export const serveApi = createLoggerMiddleware( createPathFilter({ @@ -20,13 +15,7 @@ export const serveApi = createLoggerMiddleware( "settings/params": paramsRoute, "stats": statsRoute, "workers": workersRoute, - "bot": createMethodFilter({ - // deno-lint-ignore require-await - GET: createEndpoint({ query: null, body: null }, async () => { - const username = bot.botInfo.username; - return { status: 200, body: { type: "application/json", data: { username } } }; - }), - }), + "bot": botRoute, }), { filterStatus: (status) => status >= 400 }, ); diff --git a/api/sessionsRoute.ts b/api/sessionsRoute.ts index 9861acd..67b1d0e 100644 --- a/api/sessionsRoute.ts +++ b/api/sessionsRoute.ts @@ -1,4 +1,3 @@ -// deno-lint-ignore-file require-await import { createEndpoint, createMethodFilter, createPathFilter } from "t_rest/server"; import { ulid } from "ulid"; diff --git a/api/withUser.ts b/api/withUser.ts new file mode 100644 index 0000000..4a2d19e --- /dev/null +++ b/api/withUser.ts @@ -0,0 +1,28 @@ +import { Chat } from "grammy_types"; +import { Output } from "t_rest/client"; +import { getConfig } from "../app/config.ts"; +import { bot } from "../bot/mod.ts"; +import { sessions } from "./sessionsRoute.ts"; + +export async function withUser( + query: { sessionId: string }, + cb: (user: Chat.PrivateGetChat) => Promise, + options?: { admin?: boolean }, +) { + const session = sessions.get(query.sessionId); + if (!session?.userId) { + return { status: 401, body: { type: "text/plain", data: "Must be logged in" } } as const; + } + const chat = await bot.api.getChat(session.userId); + if (chat.type !== "private") throw new Error("Chat is not private"); + if (options?.admin) { + if (!chat.username) { + return { status: 403, body: { type: "text/plain", data: "Must have a username" } } as const; + } + const config = await getConfig(); + if (!config?.adminUsernames?.includes(chat.username)) { + return { status: 403, body: { type: "text/plain", data: "Must be an admin" } } as const; + } + } + return cb(chat); +} diff --git a/api/workersRoute.ts b/api/workersRoute.ts index 96bd146..b140ffb 100644 --- a/api/workersRoute.ts +++ b/api/workersRoute.ts @@ -3,14 +3,16 @@ import { Model } from "indexed_kv"; import createOpenApiFetch from "openapi_fetch"; import { info } from "std/log/mod.ts"; import { createEndpoint, createMethodFilter, createPathFilter } from "t_rest/server"; -import { getConfig } from "../app/config.ts"; import { activeGenerationWorkers } from "../app/generationQueue.ts"; import { generationStore } from "../app/generationStore.ts"; import * as SdApi from "../app/sdApi.ts"; -import { WorkerInstance, workerInstanceStore } from "../app/workerInstanceStore.ts"; -import { bot } from "../bot/mod.ts"; +import { + WorkerInstance, + workerInstanceSchema, + workerInstanceStore, +} from "../app/workerInstanceStore.ts"; import { getAuthHeader } from "../utils/getAuthHeader.ts"; -import { sessions } from "./sessionsRoute.ts"; +import { withUser } from "./withUser.ts"; export type WorkerData = Omit & { id: string; @@ -69,7 +71,6 @@ export const workersRoute = createPathFilter({ async () => { const workerInstances = await workerInstanceStore.getAll(); const workers = await Promise.all(workerInstances.map(getWorkerData)); - return { status: 200, body: { type: "application/json", data: workers satisfies WorkerData[] }, @@ -83,51 +84,18 @@ export const workersRoute = createPathFilter({ }, body: { type: "application/json", - schema: { - type: "object", - properties: { - key: { type: "string" }, - name: { type: ["string", "null"] }, - sdUrl: { type: "string" }, - sdAuth: { - type: ["object", "null"], - properties: { - user: { type: "string" }, - password: { type: "string" }, - }, - required: ["user", "password"], - }, - }, - required: ["key", "name", "sdUrl", "sdAuth"], - }, + schema: workerInstanceSchema, }, }, async ({ query, body }) => { - const session = sessions.get(query.sessionId); - if (!session?.userId) { - return { status: 401, body: { type: "text/plain", data: "Must be logged in" } }; - } - const chat = await bot.api.getChat(session.userId); - if (chat.type !== "private") throw new Error("Chat is not private"); - if (!chat.username) { - return { status: 403, body: { type: "text/plain", data: "Must have a username" } }; - } - const config = await getConfig(); - if (!config?.adminUsernames?.includes(chat.username)) { - return { status: 403, body: { type: "text/plain", data: "Must be an admin" } }; - } - const workerInstance = await workerInstanceStore.create({ - key: body.data.key, - name: body.data.name, - sdUrl: body.data.sdUrl, - sdAuth: body.data.sdAuth, - }); - info(`User ${chat.username} created worker ${workerInstance.id}`); - const worker = await getWorkerData(workerInstance); - return { - status: 200, - body: { type: "application/json", data: worker satisfies WorkerData }, - }; + return withUser(query, async (chat) => { + const workerInstance = await workerInstanceStore.create(body.data); + info(`User ${chat.username} created worker ${workerInstance.id}`); + return { + status: 200, + body: { type: "application/json", data: await getWorkerData(workerInstance) }, + }; + }, { admin: true }); }, ), }), @@ -141,10 +109,9 @@ export const workersRoute = createPathFilter({ if (!workerInstance) { return { status: 404, body: { type: "text/plain", data: `Worker not found` } }; } - const worker: WorkerData = await getWorkerData(workerInstance); return { status: 200, - body: { type: "application/json", data: worker satisfies WorkerData }, + body: { type: "application/json", data: await getWorkerData(workerInstance) }, }; }, ), @@ -155,22 +122,7 @@ export const workersRoute = createPathFilter({ }, body: { type: "application/json", - schema: { - type: "object", - properties: { - key: { type: "string" }, - name: { type: ["string", "null"] }, - sdUrl: { type: "string" }, - auth: { - type: ["object", "null"], - properties: { - user: { type: "string" }, - password: { type: "string" }, - }, - required: ["user", "password"], - }, - }, - }, + schema: { ...workerInstanceSchema, required: [] }, }, }, async ({ params, query, body }) => { @@ -178,37 +130,18 @@ export const workersRoute = createPathFilter({ if (!workerInstance) { return { status: 404, body: { type: "text/plain", data: `Worker not found` } }; } - const session = sessions.get(query.sessionId); - if (!session?.userId) { - return { status: 401, body: { type: "text/plain", data: "Must be logged in" } }; - } - const chat = await bot.api.getChat(session.userId); - if (chat.type !== "private") throw new Error("Chat is not private"); - if (!chat.username) { - return { status: 403, body: { type: "text/plain", data: "Must have a username" } }; - } - const config = await getConfig(); - if (!config?.adminUsernames?.includes(chat.username)) { - return { status: 403, body: { type: "text/plain", data: "Must be an admin" } }; - } - if (body.data.name !== undefined) { - workerInstance.value.name = body.data.name; - } - if (body.data.sdUrl !== undefined) { - workerInstance.value.sdUrl = body.data.sdUrl; - } - if (body.data.auth !== undefined) { - workerInstance.value.sdAuth = body.data.auth; - } - info( - `User ${chat.username} updated worker ${params.workerId}: ${JSON.stringify(body.data)}`, - ); - await workerInstance.update(); - const worker = await getWorkerData(workerInstance); - return { - status: 200, - body: { type: "application/json", data: worker satisfies WorkerData }, - }; + return withUser(query, async (chat) => { + info( + `User ${chat.username} updated worker ${params.workerId}: ${ + JSON.stringify(body.data) + }`, + ); + await workerInstance.update(body.data); + return { + status: 200, + body: { type: "application/json", data: await getWorkerData(workerInstance) }, + }; + }, { admin: true }); }, ), DELETE: createEndpoint( @@ -223,22 +156,11 @@ export const workersRoute = createPathFilter({ if (!workerInstance) { return { status: 404, body: { type: "text/plain", data: `Worker not found` } }; } - const session = sessions.get(query.sessionId); - if (!session?.userId) { - return { status: 401, body: { type: "text/plain", data: "Must be logged in" } }; - } - const chat = await bot.api.getChat(session.userId); - if (chat.type !== "private") throw new Error("Chat is not private"); - if (!chat.username) { - return { status: 403, body: { type: "text/plain", data: "Must have a username" } }; - } - const config = await getConfig(); - if (!config?.adminUsernames?.includes(chat.username)) { - return { status: 403, body: { type: "text/plain", data: "Must be an admin" } }; - } - info(`User ${chat.username} deleted worker ${params.workerId}`); - await workerInstance.delete(); - return { status: 200, body: { type: "application/json", data: null } }; + return withUser(query, async (chat) => { + info(`User ${chat.username} deleted worker ${params.workerId}`); + await workerInstance.delete(); + return { status: 200, body: { type: "application/json", data: null } }; + }, { admin: true }); }, ), }), diff --git a/deno.json b/deno.json index 7d99f4d..84c6f2d 100644 --- a/deno.json +++ b/deno.json @@ -9,6 +9,13 @@ "fmt": { "lineWidth": 100 }, + "lint": { + "rules": { + "exclude": [ + "require-await" + ] + } + }, "imports": { "@date-fns/utc": "https://cdn.skypack.dev/@date-fns/utc@1.1.0?dts", "async": "https://deno.land/x/async@v2.0.2/mod.ts", diff --git a/ui/WorkersPage.tsx b/ui/WorkersPage.tsx index 3390720..32cff47 100644 --- a/ui/WorkersPage.tsx +++ b/ui/WorkersPage.tsx @@ -249,7 +249,7 @@ function WorkerListItem(props: { worker: WorkerData; sessionId?: string }) { body: { type: "application/json", data: { - auth: user && password ? { user, password } : null, + sdAuth: user && password ? { user, password } : null, }, }, });