feat(api): add file upload api, get a upload url, then upload file, max 50mb for now
This commit is contained in:
parent
478859d9b5
commit
1ced8095dc
@ -1,43 +1,21 @@
|
|||||||
import { createRun } from "../../../../server/createRun";
|
import { app } from "../../../../routes/app";
|
||||||
import { db } from "@/db/db";
|
import { registerCreateRunRoute } from "@/routes/registerCreateRunRoute";
|
||||||
import { deploymentsTable, workflowRunsTable } from "@/db/schema";
|
import { registerGetOutputRoute } from "@/routes/registerGetOutputRoute";
|
||||||
import { createSelectSchema } from "@/lib/drizzle-zod-hono";
|
import { registerUploadRoute } from "@/routes/registerUploadRoute";
|
||||||
import { isKeyRevoked } from "@/server/curdApiKeys";
|
import { isKeyRevoked } from "@/server/curdApiKeys";
|
||||||
import { getRunsData } from "@/server/getRunsData";
|
|
||||||
import { parseJWT } from "@/server/parseJWT";
|
import { parseJWT } from "@/server/parseJWT";
|
||||||
import type { ResponseConfig } from "@asteasolutions/zod-to-openapi";
|
import type { Context, Next } from "hono";
|
||||||
import { z, createRoute } from "@hono/zod-openapi";
|
|
||||||
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { handle } from "hono/vercel";
|
import { handle } from "hono/vercel";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
const app = new OpenAPIHono().basePath("/api");
|
|
||||||
|
|
||||||
declare module "hono" {
|
declare module "hono" {
|
||||||
interface ContextVariableMap {
|
interface ContextVariableMap {
|
||||||
apiKeyTokenData: ReturnType<typeof parseJWT>;
|
apiKeyTokenData: ReturnType<typeof parseJWT>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const authError = {
|
async function checkAuth(c: Context, next: Next) {
|
||||||
401: {
|
|
||||||
content: {
|
|
||||||
"text/plain": {
|
|
||||||
schema: z.string().openapi({
|
|
||||||
type: "string",
|
|
||||||
example: "Invalid or expired token",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: "Invalid or expired token",
|
|
||||||
},
|
|
||||||
} satisfies {
|
|
||||||
[statusCode: string]: ResponseConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
app.use("/run", async (c, next) => {
|
|
||||||
const token = c.req.raw.headers.get("Authorization")?.split(" ")?.[1]; // Assuming token is sent as "Bearer your_token"
|
const token = c.req.raw.headers.get("Authorization")?.split(" ")?.[1]; // Assuming token is sent as "Bearer your_token"
|
||||||
const userData = token ? parseJWT(token) : undefined;
|
const userData = token ? parseJWT(token) : undefined;
|
||||||
if (!userData || token === undefined) {
|
if (!userData || token === undefined) {
|
||||||
@ -50,198 +28,19 @@ app.use("/run", async (c, next) => {
|
|||||||
c.set("apiKeyTokenData", userData);
|
c.set("apiKeyTokenData", userData);
|
||||||
|
|
||||||
await next();
|
await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use("/run", async (c, next) => {
|
||||||
|
return checkAuth(c, next);
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log(RunOutputZod.shape);
|
app.use("/upload", async (c, next) => {
|
||||||
|
return checkAuth(c, next);
|
||||||
const getOutputRoute = createRoute({
|
|
||||||
method: "get",
|
|
||||||
path: "/run",
|
|
||||||
tags: ["workflows"],
|
|
||||||
summary: "Get workflow run output",
|
|
||||||
description:
|
|
||||||
"Call this to get a run's output, usually in conjunction with polling method",
|
|
||||||
request: {
|
|
||||||
query: z.object({
|
|
||||||
run_id: z.string(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
// https://github.com/asteasolutions/zod-to-openapi/issues/194
|
|
||||||
schema: createSelectSchema(workflowRunsTable, {
|
|
||||||
workflow_inputs: (schema) =>
|
|
||||||
schema.workflow_inputs.openapi({
|
|
||||||
type: "object",
|
|
||||||
example: {
|
|
||||||
input_text: "some external text input",
|
|
||||||
input_image: "https://somestatic.png",
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: "Retrieve the output",
|
|
||||||
},
|
|
||||||
400: {
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: z.object({
|
|
||||||
code: z.number().openapi({
|
|
||||||
type: "string",
|
|
||||||
example: 400,
|
|
||||||
}),
|
|
||||||
message: z.string().openapi({
|
|
||||||
type: "string",
|
|
||||||
example: "Workflow not found",
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: "Workflow not found",
|
|
||||||
},
|
|
||||||
500: {
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: z.object({
|
|
||||||
error: z.string(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: "Error getting output",
|
|
||||||
},
|
|
||||||
...authError,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.openapi(getOutputRoute, async (c) => {
|
registerCreateRunRoute(app);
|
||||||
const data = c.req.valid("query");
|
registerGetOutputRoute(app);
|
||||||
const apiKeyTokenData = c.get("apiKeyTokenData")!;
|
registerUploadRoute(app);
|
||||||
|
|
||||||
try {
|
|
||||||
const run = await getRunsData(data.run_id, apiKeyTokenData);
|
|
||||||
|
|
||||||
if (!run)
|
|
||||||
return c.json(
|
|
||||||
{
|
|
||||||
code: 400,
|
|
||||||
message: "Workflow not found",
|
|
||||||
},
|
|
||||||
400
|
|
||||||
);
|
|
||||||
|
|
||||||
return c.json(run, 200);
|
|
||||||
} catch (error: any) {
|
|
||||||
return c.json(
|
|
||||||
{
|
|
||||||
error: error.message,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 500,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const createRunRoute = createRoute({
|
|
||||||
method: "post",
|
|
||||||
path: "/run",
|
|
||||||
tags: ["workflows"],
|
|
||||||
summary: "Run a workflow via deployment_id",
|
|
||||||
request: {
|
|
||||||
body: {
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: z.object({
|
|
||||||
deployment_id: z.string(),
|
|
||||||
inputs: z.record(z.string()).optional(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// headers: z.object({
|
|
||||||
// "Authorization": z.
|
|
||||||
// })
|
|
||||||
},
|
|
||||||
responses: {
|
|
||||||
200: {
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: z.object({
|
|
||||||
run_id: z.string(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: "Workflow queued",
|
|
||||||
},
|
|
||||||
500: {
|
|
||||||
content: {
|
|
||||||
"application/json": {
|
|
||||||
schema: z.object({
|
|
||||||
error: z.string(),
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
description: "Error creating run",
|
|
||||||
},
|
|
||||||
...authError,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
app.openapi(createRunRoute, async (c) => {
|
|
||||||
const data = c.req.valid("json");
|
|
||||||
const origin = new URL(c.req.url).origin;
|
|
||||||
const apiKeyTokenData = c.get("apiKeyTokenData")!;
|
|
||||||
|
|
||||||
const { deployment_id, inputs } = data;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const deploymentData = await db.query.deploymentsTable.findFirst({
|
|
||||||
where: eq(deploymentsTable.id, deployment_id),
|
|
||||||
with: {
|
|
||||||
machine: true,
|
|
||||||
version: {
|
|
||||||
with: {
|
|
||||||
workflow: {
|
|
||||||
columns: {
|
|
||||||
org_id: true,
|
|
||||||
user_id: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!deploymentData) throw new Error("Deployment not found");
|
|
||||||
|
|
||||||
const run_id = await createRun({
|
|
||||||
origin,
|
|
||||||
workflow_version_id: deploymentData.version,
|
|
||||||
machine_id: deploymentData.machine,
|
|
||||||
inputs,
|
|
||||||
isManualRun: false,
|
|
||||||
apiUser: apiKeyTokenData,
|
|
||||||
});
|
|
||||||
|
|
||||||
if ("error" in run_id) throw new Error(run_id.error);
|
|
||||||
|
|
||||||
return c.json({
|
|
||||||
run_id: "workflow_run_id" in run_id ? run_id.workflow_run_id : "",
|
|
||||||
});
|
|
||||||
} catch (error: any) {
|
|
||||||
return c.json(
|
|
||||||
{
|
|
||||||
error: error.message,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
status: 500,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// The OpenAPI documentation will be available at /doc
|
// The OpenAPI documentation will be available at /doc
|
||||||
app.doc("/doc", {
|
app.doc("/doc", {
|
||||||
|
4
web/src/routes/app.ts
Normal file
4
web/src/routes/app.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { OpenAPIHono } from "@hono/zod-openapi";
|
||||||
|
|
||||||
|
export const app = new OpenAPIHono().basePath("/api");
|
||||||
|
export type App = typeof app;
|
18
web/src/routes/authError.ts
Normal file
18
web/src/routes/authError.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { ResponseConfig } from "@asteasolutions/zod-to-openapi";
|
||||||
|
import { z } from "@hono/zod-openapi";
|
||||||
|
|
||||||
|
export const authError = {
|
||||||
|
401: {
|
||||||
|
content: {
|
||||||
|
"text/plain": {
|
||||||
|
schema: z.string().openapi({
|
||||||
|
type: "string",
|
||||||
|
example: "Invalid or expired token",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: "Invalid or expired token",
|
||||||
|
},
|
||||||
|
} satisfies {
|
||||||
|
[statusCode: string]: ResponseConfig;
|
||||||
|
};
|
106
web/src/routes/registerCreateRunRoute.ts
Normal file
106
web/src/routes/registerCreateRunRoute.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { createRun } from "../server/createRun";
|
||||||
|
import { db } from "@/db/db";
|
||||||
|
import { deploymentsTable } from "@/db/schema";
|
||||||
|
import type { App } from "@/routes/app";
|
||||||
|
import { authError } from "@/routes/authError";
|
||||||
|
import { z, createRoute } from "@hono/zod-openapi";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
const createRunRoute = createRoute({
|
||||||
|
method: "post",
|
||||||
|
path: "/run",
|
||||||
|
tags: ["workflows"],
|
||||||
|
summary: "Run a workflow via deployment_id",
|
||||||
|
request: {
|
||||||
|
body: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
deployment_id: z.string(),
|
||||||
|
inputs: z.record(z.string()).optional(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
run_id: z.string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: "Workflow queued",
|
||||||
|
},
|
||||||
|
500: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
error: z.string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: "Error creating run",
|
||||||
|
},
|
||||||
|
...authError,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const registerCreateRunRoute = (app: App) => {
|
||||||
|
app.openapi(createRunRoute, async (c) => {
|
||||||
|
const data = c.req.valid("json");
|
||||||
|
const origin = new URL(c.req.url).origin;
|
||||||
|
const apiKeyTokenData = c.get("apiKeyTokenData")!;
|
||||||
|
|
||||||
|
const { deployment_id, inputs } = data;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deploymentData = await db.query.deploymentsTable.findFirst({
|
||||||
|
where: eq(deploymentsTable.id, deployment_id),
|
||||||
|
with: {
|
||||||
|
machine: true,
|
||||||
|
version: {
|
||||||
|
with: {
|
||||||
|
workflow: {
|
||||||
|
columns: {
|
||||||
|
org_id: true,
|
||||||
|
user_id: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!deploymentData) throw new Error("Deployment not found");
|
||||||
|
|
||||||
|
const run_id = await createRun({
|
||||||
|
origin,
|
||||||
|
workflow_version_id: deploymentData.version,
|
||||||
|
machine_id: deploymentData.machine,
|
||||||
|
inputs,
|
||||||
|
isManualRun: false,
|
||||||
|
apiUser: apiKeyTokenData,
|
||||||
|
});
|
||||||
|
|
||||||
|
if ("error" in run_id) throw new Error(run_id.error);
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
run_id: "workflow_run_id" in run_id ? run_id.workflow_run_id : "",
|
||||||
|
});
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errorMessage =
|
||||||
|
error instanceof Error ? error.message : "Unknown error";
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
error: errorMessage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 500,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
101
web/src/routes/registerGetOutputRoute.ts
Normal file
101
web/src/routes/registerGetOutputRoute.ts
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import { workflowRunsTable } from "@/db/schema";
|
||||||
|
import { createSelectSchema } from "@/lib/drizzle-zod-hono";
|
||||||
|
import type { App } from "@/routes/app";
|
||||||
|
import { authError } from "@/routes/authError";
|
||||||
|
import { getRunsData } from "@/server/getRunsData";
|
||||||
|
import { z, createRoute } from "@hono/zod-openapi";
|
||||||
|
|
||||||
|
const getOutputRoute = createRoute({
|
||||||
|
method: "get",
|
||||||
|
path: "/run",
|
||||||
|
tags: ["workflows"],
|
||||||
|
summary: "Get workflow run output",
|
||||||
|
description:
|
||||||
|
"Call this to get a run's output, usually in conjunction with polling method",
|
||||||
|
request: {
|
||||||
|
query: z.object({
|
||||||
|
run_id: z.string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
// https://github.com/asteasolutions/zod-to-openapi/issues/194
|
||||||
|
schema: createSelectSchema(workflowRunsTable, {
|
||||||
|
workflow_inputs: (schema) =>
|
||||||
|
schema.workflow_inputs.openapi({
|
||||||
|
type: "object",
|
||||||
|
example: {
|
||||||
|
input_text: "some external text input",
|
||||||
|
input_image: "https://somestatic.png",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: "Retrieve the output",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
code: z.number().openapi({
|
||||||
|
type: "string",
|
||||||
|
example: 400,
|
||||||
|
}),
|
||||||
|
message: z.string().openapi({
|
||||||
|
type: "string",
|
||||||
|
example: "Workflow not found",
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: "Workflow not found",
|
||||||
|
},
|
||||||
|
500: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
error: z.string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: "Error getting output",
|
||||||
|
},
|
||||||
|
...authError,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const registerGetOutputRoute = (app: App) => {
|
||||||
|
app.openapi(getOutputRoute, async (c) => {
|
||||||
|
const data = c.req.valid("query");
|
||||||
|
const apiKeyTokenData = c.get("apiKeyTokenData")!;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const run = await getRunsData(data.run_id, apiKeyTokenData);
|
||||||
|
|
||||||
|
if (!run)
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
code: 400,
|
||||||
|
message: "Workflow not found",
|
||||||
|
},
|
||||||
|
400
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(run, 200);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errorMessage =
|
||||||
|
error instanceof Error ? error.message : "Unknown error";
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
error: errorMessage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 500,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
114
web/src/routes/registerUploadRoute.ts
Normal file
114
web/src/routes/registerUploadRoute.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import type { App } from "@/routes/app";
|
||||||
|
import { authError } from "@/routes/authError";
|
||||||
|
import { getFileDownloadUrl } from "@/server/getFileDownloadUrl";
|
||||||
|
import { handleResourceUpload } from "@/server/resource";
|
||||||
|
import { z, createRoute } from "@hono/zod-openapi";
|
||||||
|
import { customAlphabet } from "nanoid";
|
||||||
|
|
||||||
|
export const nanoid = customAlphabet(
|
||||||
|
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||||
|
);
|
||||||
|
|
||||||
|
const prefixes = {
|
||||||
|
img: "img",
|
||||||
|
vid: "vid",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export function newId(prefix: keyof typeof prefixes): string {
|
||||||
|
return [prefixes[prefix], nanoid(16)].join("_");
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadUrlRoute = createRoute({
|
||||||
|
method: "get",
|
||||||
|
path: "/upload-url",
|
||||||
|
tags: ["files"],
|
||||||
|
summary: "Upload any files to the storage",
|
||||||
|
description:
|
||||||
|
"Usually when you run something, you want to upload a file, image etc.",
|
||||||
|
request: {
|
||||||
|
query: z.object({
|
||||||
|
type: z.enum(["image/png", "image/jpg", "image/jpeg"]),
|
||||||
|
file_size: z
|
||||||
|
.string()
|
||||||
|
.refine((value) => !isNaN(Number(value)), {
|
||||||
|
message: "file_size must be a number",
|
||||||
|
path: ["file_size"],
|
||||||
|
})
|
||||||
|
.refine((value) => Number(value) > 0, {
|
||||||
|
message: "file_size cannot be less than or equal to 0",
|
||||||
|
path: ["file_size"],
|
||||||
|
})
|
||||||
|
.transform((v) => parseFloat(v)),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
upload_url: z.string(),
|
||||||
|
file_id: z.string(),
|
||||||
|
download_url: z.string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: "Retrieve the output",
|
||||||
|
},
|
||||||
|
500: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
error: z.string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: "Error when generating upload url",
|
||||||
|
},
|
||||||
|
...authError,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const registerUploadRoute = (app: App) => {
|
||||||
|
app.openapi(uploadUrlRoute, async (c) => {
|
||||||
|
const data = c.req.valid("query");
|
||||||
|
|
||||||
|
const sizeLimit = 50;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (data.file_size > sizeLimit * 1024 * 1024) {
|
||||||
|
// Check if the file size is greater than 50MB
|
||||||
|
throw new Error(`File size exceeds ${sizeLimit}MB limit`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = newId("img");
|
||||||
|
const filePath = `inputs/${id}.${data.type.split("/")[1]}`;
|
||||||
|
|
||||||
|
const uploadUrl = await handleResourceUpload({
|
||||||
|
resourceBucket: process.env.SPACES_BUCKET,
|
||||||
|
resourceId: filePath,
|
||||||
|
resourceType: data.type,
|
||||||
|
isPublic: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
upload_url: uploadUrl,
|
||||||
|
file_id: id,
|
||||||
|
download_url: await getFileDownloadUrl(filePath),
|
||||||
|
},
|
||||||
|
200
|
||||||
|
);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errorMessage =
|
||||||
|
error instanceof Error ? error.message : "Unknown error";
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
error: errorMessage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 500,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user