forked from pinks/eris
update rest api
This commit is contained in:
parent
c0354ef679
commit
a8d36db20b
|
@ -1,13 +1,15 @@
|
|||
import { Endpoint, Route } from "t_rest/server";
|
||||
import { createEndpoint, createMethodFilter } from "t_rest/server";
|
||||
import { generationQueue } from "../app/generationQueue.ts";
|
||||
|
||||
export const jobsRoute = {
|
||||
GET: new Endpoint(
|
||||
export const jobsRoute = createMethodFilter({
|
||||
GET: createEndpoint(
|
||||
{ query: null, body: null },
|
||||
async () => ({
|
||||
status: 200,
|
||||
type: "application/json",
|
||||
body: await generationQueue.getAllJobs(),
|
||||
body: {
|
||||
type: "application/json",
|
||||
data: await generationQueue.getAllJobs(),
|
||||
},
|
||||
}),
|
||||
),
|
||||
} satisfies Route;
|
||||
});
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { route } from "reroute";
|
||||
import { serveSpa } from "serve_spa";
|
||||
import { api } from "./api.ts";
|
||||
import { serveApi } from "./serveApi.ts";
|
||||
|
||||
export async function serveUi() {
|
||||
const server = Deno.serve({ port: 5999 }, (request) =>
|
||||
route(request, {
|
||||
"/api/*": (request) => api.serve(request),
|
||||
"/api/*": (request) => serveApi(request),
|
||||
"/*": (request) =>
|
||||
serveSpa(request, {
|
||||
fsRoot: new URL("../ui/", import.meta.url).pathname,
|
||||
|
|
|
@ -1,26 +1,22 @@
|
|||
import { deepMerge } from "std/collections/deep_merge.ts";
|
||||
import { getLogger } from "std/log/mod.ts";
|
||||
import { Endpoint, Route } from "t_rest/server";
|
||||
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";
|
||||
|
||||
export const logger = () => getLogger();
|
||||
|
||||
export const paramsRoute = {
|
||||
GET: new Endpoint(
|
||||
export const paramsRoute = createMethodFilter({
|
||||
GET: createEndpoint(
|
||||
{ query: null, body: null },
|
||||
async () => {
|
||||
const config = await getConfig();
|
||||
return {
|
||||
status: 200,
|
||||
type: "application/json",
|
||||
body: config?.defaultParams,
|
||||
};
|
||||
return { status: 200, body: { type: "application/json", data: config.defaultParams } };
|
||||
},
|
||||
),
|
||||
|
||||
PATCH: new Endpoint(
|
||||
PATCH: createEndpoint(
|
||||
{
|
||||
query: { sessionId: { type: "string" } },
|
||||
body: {
|
||||
|
@ -31,7 +27,7 @@ export const paramsRoute = {
|
|||
async ({ query, body }) => {
|
||||
const session = sessions.get(query.sessionId);
|
||||
if (!session?.userId) {
|
||||
return { status: 401, type: "text/plain", body: "Must be logged in" };
|
||||
return { status: 401, body: { type: "text/plain", data: "Must be logged in" } };
|
||||
}
|
||||
const chat = await bot.api.getChat(session.userId);
|
||||
if (chat.type !== "private") {
|
||||
|
@ -39,16 +35,16 @@ export const paramsRoute = {
|
|||
}
|
||||
const userName = chat.username;
|
||||
if (!userName) {
|
||||
return { status: 403, type: "text/plain", body: "Must have a username" };
|
||||
return { status: 403, body: { type: "text/plain", data: "Must have a username" } };
|
||||
}
|
||||
const config = await getConfig();
|
||||
if (!config?.adminUsernames?.includes(userName)) {
|
||||
return { status: 403, type: "text/plain", body: "Must be an admin" };
|
||||
return { status: 403, body: { type: "text/plain", data: "Must be an admin" } };
|
||||
}
|
||||
logger().info(`User ${userName} updated default params: ${JSON.stringify(body)}`);
|
||||
const defaultParams = deepMerge(config.defaultParams ?? {}, body);
|
||||
logger().info(`User ${userName} updated default params: ${JSON.stringify(body.data)}`);
|
||||
const defaultParams = deepMerge(config.defaultParams ?? {}, body.data);
|
||||
await setConfig({ defaultParams });
|
||||
return { status: 200, type: "application/json", body: config.defaultParams };
|
||||
return { status: 200, body: { type: "application/json", data: config.defaultParams } };
|
||||
},
|
||||
),
|
||||
} satisfies Route;
|
||||
});
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { Api } from "t_rest/server";
|
||||
import { createPathFilter } from "t_rest/server";
|
||||
import { jobsRoute } from "./jobsRoute.ts";
|
||||
import { sessionsRoute } from "./sessionsRoute.ts";
|
||||
import { usersRoute } from "./usersRoute.ts";
|
||||
import { paramsRoute } from "./paramsRoute.ts";
|
||||
|
||||
export const api = new Api({
|
||||
export const serveApi = createPathFilter({
|
||||
"jobs": jobsRoute,
|
||||
"sessions": sessionsRoute,
|
||||
"users": usersRoute,
|
||||
"settings/params": paramsRoute,
|
||||
});
|
||||
|
||||
export type ErisApi = typeof api;
|
||||
export type ApiHandler = typeof serveApi;
|
|
@ -1,5 +1,5 @@
|
|||
// deno-lint-ignore-file require-await
|
||||
import { Endpoint, Route } from "t_rest/server";
|
||||
import { createEndpoint, createMethodFilter, createPathFilter } from "t_rest/server";
|
||||
import { ulid } from "ulid";
|
||||
|
||||
export const sessions = new Map<string, Session>();
|
||||
|
@ -8,25 +8,30 @@ export interface Session {
|
|||
userId?: number;
|
||||
}
|
||||
|
||||
export const sessionsRoute = {
|
||||
POST: new Endpoint(
|
||||
{ query: null, body: null },
|
||||
async () => {
|
||||
const id = ulid();
|
||||
const session: Session = {};
|
||||
sessions.set(id, session);
|
||||
return { status: 200, type: "application/json", body: { id, ...session } };
|
||||
},
|
||||
),
|
||||
GET: new Endpoint(
|
||||
{ query: { sessionId: { type: "string" } }, body: null },
|
||||
async ({ query }) => {
|
||||
const id = query.sessionId;
|
||||
const session = sessions.get(id);
|
||||
if (!session) {
|
||||
return { status: 401, type: "text/plain", body: "Session not found" };
|
||||
}
|
||||
return { status: 200, type: "application/json", body: { id, ...session } };
|
||||
},
|
||||
),
|
||||
} satisfies Route;
|
||||
export const sessionsRoute = createPathFilter({
|
||||
"": createMethodFilter({
|
||||
POST: createEndpoint(
|
||||
{ query: null, body: null },
|
||||
async () => {
|
||||
const id = ulid();
|
||||
const session: Session = {};
|
||||
sessions.set(id, session);
|
||||
return { status: 200, body: { type: "application/json", data: { id, ...session } } };
|
||||
},
|
||||
),
|
||||
}),
|
||||
|
||||
"{sessionId}": createMethodFilter({
|
||||
GET: createEndpoint(
|
||||
{ query: null, body: null },
|
||||
async ({ params }) => {
|
||||
const id = params.sessionId;
|
||||
const session = sessions.get(id);
|
||||
if (!session) {
|
||||
return { status: 401, body: { type: "text/plain", data: "Session not found" } };
|
||||
}
|
||||
return { status: 200, body: { type: "application/json", data: { id, ...session } } };
|
||||
},
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
import { encode } from "std/encoding/base64.ts";
|
||||
import { Endpoint, Route } from "t_rest/server";
|
||||
import { createEndpoint, createMethodFilter, createPathFilter } from "t_rest/server";
|
||||
import { getConfig } from "../app/config.ts";
|
||||
import { bot } from "../bot/mod.ts";
|
||||
|
||||
export const usersRoute = {
|
||||
GET: new Endpoint(
|
||||
{ query: { userId: { type: "number" } }, body: null },
|
||||
async ({ query }) => {
|
||||
const chat = await bot.api.getChat(query.userId);
|
||||
if (chat.type !== "private") {
|
||||
throw new Error("Chat is not private");
|
||||
}
|
||||
const photoData = chat.photo?.small_file_id
|
||||
? encode(
|
||||
await fetch(
|
||||
`https://api.telegram.org/file/bot${bot.token}/${await bot.api.getFile(
|
||||
chat.photo.small_file_id,
|
||||
).then((file) => file.file_path)}`,
|
||||
).then((resp) => resp.arrayBuffer()),
|
||||
)
|
||||
: undefined;
|
||||
const config = await getConfig();
|
||||
const isAdmin = config?.adminUsernames?.includes(chat.username);
|
||||
return {
|
||||
status: 200,
|
||||
type: "application/json",
|
||||
body: {
|
||||
...chat,
|
||||
photoData,
|
||||
isAdmin,
|
||||
},
|
||||
};
|
||||
},
|
||||
),
|
||||
} satisfies Route;
|
||||
export const usersRoute = createPathFilter({
|
||||
"{userId}": createMethodFilter({
|
||||
GET: createEndpoint(
|
||||
{ query: null, body: null },
|
||||
async ({ params }) => {
|
||||
const chat = await bot.api.getChat(params.userId);
|
||||
if (chat.type !== "private") {
|
||||
throw new Error("Chat is not private");
|
||||
}
|
||||
const photoData = chat.photo?.small_file_id
|
||||
? encode(
|
||||
await fetch(
|
||||
`https://api.telegram.org/file/bot${bot.token}/${await bot.api.getFile(
|
||||
chat.photo.small_file_id,
|
||||
).then((file) => file.file_path)}`,
|
||||
).then((resp) => resp.arrayBuffer()),
|
||||
)
|
||||
: undefined;
|
||||
const config = await getConfig();
|
||||
const isAdmin = config?.adminUsernames?.includes(chat.username);
|
||||
return {
|
||||
status: 200,
|
||||
body: {
|
||||
type: "application/json",
|
||||
data: { ...chat, photoData, isAdmin },
|
||||
},
|
||||
};
|
||||
},
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"tasks": {
|
||||
"start": "deno run --unstable --allow-env --allow-read --allow-write --allow-net main.ts"
|
||||
"start": "deno run --unstable --allow-env --allow-read --allow-write --allow-net main.ts",
|
||||
"check": "deno check --unstable main.ts && deno check --unstable ui/main.tsx"
|
||||
},
|
||||
"compilerOptions": {
|
||||
"jsx": "react"
|
||||
|
@ -33,9 +34,9 @@
|
|||
"png_chunks_extract": "https://esm.sh/png-chunks-extract@1.0.0",
|
||||
"png_chunk_text": "https://esm.sh/png-chunk-text@1.0.0",
|
||||
"openapi_fetch": "https://esm.sh/openapi-fetch@0.7.6",
|
||||
"t_rest/server": "https://esm.sh/ty-rest@0.2.1/server?dev",
|
||||
"t_rest/server": "https://esm.sh/ty-rest@0.3.1/server?dev",
|
||||
|
||||
"t_rest/client": "https://esm.sh/ty-rest@0.2.1/client?dev",
|
||||
"t_rest/client": "https://esm.sh/ty-rest@0.3.1/client?dev",
|
||||
"react": "https://esm.sh/react@18.2.0?dev",
|
||||
"react-dom/client": "https://esm.sh/react-dom@18.2.0/client?dev",
|
||||
"react-router-dom": "https://esm.sh/react-router-dom@6.16.0?dev",
|
||||
|
|
|
@ -4,7 +4,7 @@ import useLocalStorage from "use-local-storage";
|
|||
import { AppHeader } from "./AppHeader.tsx";
|
||||
import { QueuePage } from "./QueuePage.tsx";
|
||||
import { SettingsPage } from "./SettingsPage.tsx";
|
||||
import { apiClient, handleResponse } from "./apiClient.tsx";
|
||||
import { fetchApi, handleResponse } from "./apiClient.tsx";
|
||||
|
||||
export function App() {
|
||||
// store session ID in the local storage
|
||||
|
@ -13,7 +13,7 @@ export function App() {
|
|||
// initialize a new session when there is no session ID
|
||||
useEffect(() => {
|
||||
if (!sessionId) {
|
||||
apiClient.fetch("sessions", "POST", {}).then(handleResponse).then((session) => {
|
||||
fetchApi("sessions", "POST", {}).then(handleResponse).then((session) => {
|
||||
console.log("Initialized session", session.id);
|
||||
setSessionId(session.id);
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@ import { cx } from "@twind/core";
|
|||
import React, { ReactNode } from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import useSWR from "swr";
|
||||
import { apiClient, handleResponse } from "./apiClient.tsx";
|
||||
import { fetchApi, handleResponse } from "./apiClient.tsx";
|
||||
|
||||
function NavTab(props: { to: string; children: ReactNode }) {
|
||||
return (
|
||||
|
@ -21,16 +21,16 @@ export function AppHeader(
|
|||
const { className, sessionId, onLogOut } = props;
|
||||
|
||||
const session = useSWR(
|
||||
sessionId ? ["sessions", "GET", { query: { sessionId } }] as const : null,
|
||||
(args) => apiClient.fetch(...args).then(handleResponse),
|
||||
sessionId ? ["sessions/{sessionId}", "GET", { params: { sessionId } }] as const : null,
|
||||
(args) => fetchApi(...args).then(handleResponse),
|
||||
{ onError: () => onLogOut() },
|
||||
);
|
||||
|
||||
const user = useSWR(
|
||||
session.data?.userId
|
||||
? ["users", "GET", { query: { userId: session.data.userId } }] as const
|
||||
? ["users/{userId}", "GET", { params: { userId: String(session.data.userId) } }] as const
|
||||
: null,
|
||||
(args) => apiClient.fetch(...args).then(handleResponse),
|
||||
(args) => fetchApi(...args).then(handleResponse),
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -3,12 +3,12 @@ import FlipMove from "react-flip-move";
|
|||
import useSWR from "swr";
|
||||
import { getFlagEmoji } from "../utils/getFlagEmoji.ts";
|
||||
import { Progress } from "./Progress.tsx";
|
||||
import { apiClient, handleResponse } from "./apiClient.tsx";
|
||||
import { fetchApi, handleResponse } from "./apiClient.tsx";
|
||||
|
||||
export function QueuePage() {
|
||||
const jobs = useSWR(
|
||||
["jobs", "GET", {}] as const,
|
||||
(args) => apiClient.fetch(...args).then(handleResponse),
|
||||
(args) => fetchApi(...args).then(handleResponse),
|
||||
{ refreshInterval: 2000 },
|
||||
);
|
||||
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import { cx } from "@twind/core";
|
||||
import React, { ReactNode, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
import { apiClient, handleResponse } from "./apiClient.tsx";
|
||||
import { fetchApi, handleResponse } from "./apiClient.tsx";
|
||||
|
||||
export function SettingsPage(props: { sessionId: string }) {
|
||||
const { sessionId } = props;
|
||||
const session = useSWR(
|
||||
["sessions", "GET", { query: { sessionId } }] as const,
|
||||
(args) => apiClient.fetch(...args).then(handleResponse),
|
||||
sessionId ? ["sessions/{sessionId}", "GET", { params: { sessionId } }] as const : null,
|
||||
(args) => fetchApi(...args).then(handleResponse),
|
||||
);
|
||||
const user = useSWR(
|
||||
session.data?.userId
|
||||
? ["users", "GET", { query: { userId: session.data.userId } }] as const
|
||||
? ["users/{userId}", "GET", { params: { userId: String(session.data.userId) } }] as const
|
||||
: null,
|
||||
(args) => apiClient.fetch(...args).then(handleResponse),
|
||||
(args) => fetchApi(...args).then(handleResponse),
|
||||
);
|
||||
const params = useSWR(
|
||||
["settings/params", "GET", {}] as const,
|
||||
(args) => apiClient.fetch(...args).then(handleResponse),
|
||||
(args) => fetchApi(...args).then(handleResponse),
|
||||
);
|
||||
const [changedParams, setChangedParams] = useState<Partial<typeof params.data>>({});
|
||||
const [error, setError] = useState<string>();
|
||||
|
@ -28,10 +28,9 @@ export function SettingsPage(props: { sessionId: string }) {
|
|||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
params.mutate(() =>
|
||||
apiClient.fetch("settings/params", "PATCH", {
|
||||
fetchApi("settings/params", "PATCH", {
|
||||
query: { sessionId },
|
||||
type: "application/json",
|
||||
body: changedParams ?? {},
|
||||
body: { type: "application/json", data: changedParams ?? {} },
|
||||
}).then(handleResponse)
|
||||
)
|
||||
.then(() => setChangedParams({}))
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import { Client } from "t_rest/client";
|
||||
import { ApiResponse } from "t_rest/server";
|
||||
import { ErisApi } from "../api/api.ts";
|
||||
import { createFetcher, Output } from "t_rest/client";
|
||||
import { ApiHandler } from "../api/serveApi.ts";
|
||||
|
||||
export const apiClient = new Client<ErisApi>(`${location.origin}/api/`);
|
||||
export const fetchApi = createFetcher<ApiHandler>({
|
||||
baseUrl: `${location.origin}/api/`,
|
||||
});
|
||||
|
||||
export function handleResponse<T extends ApiResponse>(response: T): (T & { status: 200 })["body"] {
|
||||
export function handleResponse<T extends Output>(
|
||||
response: T,
|
||||
): (T & { status: 200 })["body"]["data"] {
|
||||
if (response.status !== 200) {
|
||||
throw new Error(String(response.body));
|
||||
throw new Error(String(response.body.data));
|
||||
}
|
||||
return response.body;
|
||||
return response.body.data;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue