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