simplify api routes

This commit is contained in:
pinks 2023-10-17 15:03:14 +02:00
parent 2f059ceaff
commit 083f6bc01c
8 changed files with 90 additions and 147 deletions

9
api/botRoute.ts Normal file
View File

@ -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 } } };
}),
});

View File

@ -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 });
},
),
});

View File

@ -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 },
);

View File

@ -1,4 +1,3 @@
// deno-lint-ignore-file require-await
import { createEndpoint, createMethodFilter, createPathFilter } from "t_rest/server";
import { ulid } from "ulid";

28
api/withUser.ts Normal file
View File

@ -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<O extends Output>(
query: { sessionId: string },
cb: (user: Chat.PrivateGetChat) => Promise<O>,
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);
}

View File

@ -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<WorkerInstance, "sdUrl" | "sdAuth"> & {
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 });
},
),
}),

View File

@ -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",

View File

@ -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,
},
},
});