feat: add interactive api docs, rewrite run endpoint with hono and zod validator
This commit is contained in:
		
							parent
							
								
									e921d4c320
								
							
						
					
					
						commit
						7d36f8af88
					
				
							
								
								
									
										
											BIN
										
									
								
								web/bun.lockb
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/bun.lockb
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							@ -21,6 +21,10 @@
 | 
				
			|||||||
    "@clerk/nextjs": "^4.27.4",
 | 
					    "@clerk/nextjs": "^4.27.4",
 | 
				
			||||||
    "@headlessui/react": "^1.7.17",
 | 
					    "@headlessui/react": "^1.7.17",
 | 
				
			||||||
    "@headlessui/tailwindcss": "^0.2.0",
 | 
					    "@headlessui/tailwindcss": "^0.2.0",
 | 
				
			||||||
 | 
					    "@hono/node-server": "^1.4.0",
 | 
				
			||||||
 | 
					    "@hono/swagger-ui": "^0.2.1",
 | 
				
			||||||
 | 
					    "@hono/zod-openapi": "^0.9.5",
 | 
				
			||||||
 | 
					    "@hono/zod-validator": "^0.1.11",
 | 
				
			||||||
    "@hookform/resolvers": "^3.3.2",
 | 
					    "@hookform/resolvers": "^3.3.2",
 | 
				
			||||||
    "@mdx-js/loader": "^3.0.0",
 | 
					    "@mdx-js/loader": "^3.0.0",
 | 
				
			||||||
    "@mdx-js/react": "^3.0.0",
 | 
					    "@mdx-js/react": "^3.0.0",
 | 
				
			||||||
@ -46,6 +50,7 @@
 | 
				
			|||||||
    "@tanstack/react-table": "^8.10.7",
 | 
					    "@tanstack/react-table": "^8.10.7",
 | 
				
			||||||
    "@types/jsonwebtoken": "^9.0.5",
 | 
					    "@types/jsonwebtoken": "^9.0.5",
 | 
				
			||||||
    "@types/react-highlight-words": "^0.16.7",
 | 
					    "@types/react-highlight-words": "^0.16.7",
 | 
				
			||||||
 | 
					    "@types/swagger-ui-react": "^4.18.3",
 | 
				
			||||||
    "@types/uuid": "^9.0.7",
 | 
					    "@types/uuid": "^9.0.7",
 | 
				
			||||||
    "acorn": "^8.11.2",
 | 
					    "acorn": "^8.11.2",
 | 
				
			||||||
    "class-variance-authority": "^0.7.0",
 | 
					    "class-variance-authority": "^0.7.0",
 | 
				
			||||||
@ -58,6 +63,7 @@
 | 
				
			|||||||
    "fast-glob": "^3.3.2",
 | 
					    "fast-glob": "^3.3.2",
 | 
				
			||||||
    "flexsearch": "^0.7.31",
 | 
					    "flexsearch": "^0.7.31",
 | 
				
			||||||
    "framer-motion": "^10.16.16",
 | 
					    "framer-motion": "^10.16.16",
 | 
				
			||||||
 | 
					    "hono": "^3.12.0",
 | 
				
			||||||
    "jsonwebtoken": "^9.0.2",
 | 
					    "jsonwebtoken": "^9.0.2",
 | 
				
			||||||
    "lucide-react": "^0.294.0",
 | 
					    "lucide-react": "^0.294.0",
 | 
				
			||||||
    "mdast-util-to-string": "^4.0.0",
 | 
					    "mdast-util-to-string": "^4.0.0",
 | 
				
			||||||
@ -83,6 +89,7 @@
 | 
				
			|||||||
    "shikiji": "^0.9.3",
 | 
					    "shikiji": "^0.9.3",
 | 
				
			||||||
    "simple-functional-loader": "^1.2.1",
 | 
					    "simple-functional-loader": "^1.2.1",
 | 
				
			||||||
    "sonner": "^1.2.4",
 | 
					    "sonner": "^1.2.4",
 | 
				
			||||||
 | 
					    "swagger-ui-react": "^5.11.0",
 | 
				
			||||||
    "swr": "^2.2.4",
 | 
					    "swr": "^2.2.4",
 | 
				
			||||||
    "tailwind-merge": "^2.1.0",
 | 
					    "tailwind-merge": "^2.1.0",
 | 
				
			||||||
    "tailwindcss-animate": "^1.0.7",
 | 
					    "tailwindcss-animate": "^1.0.7",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										295
									
								
								web/src/app/(app)/api/[[...routes]]/route.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										295
									
								
								web/src/app/(app)/api/[[...routes]]/route.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,295 @@
 | 
				
			|||||||
 | 
					import { createRun } from "../../../../server/createRun";
 | 
				
			||||||
 | 
					import { db } from "@/db/db";
 | 
				
			||||||
 | 
					import { deploymentsTable, workflowRunsTable } from "@/db/schema";
 | 
				
			||||||
 | 
					import { createSelectSchema } from "@/lib/drizzle-zod-hono";
 | 
				
			||||||
 | 
					import { isKeyRevoked } from "@/server/curdApiKeys";
 | 
				
			||||||
 | 
					import { getRunsData } from "@/server/getRunsOutput";
 | 
				
			||||||
 | 
					import { parseJWT } from "@/server/parseJWT";
 | 
				
			||||||
 | 
					import { replaceCDNUrl } from "@/server/replaceCDNUrl";
 | 
				
			||||||
 | 
					import type { ResponseConfig } from "@asteasolutions/zod-to-openapi";
 | 
				
			||||||
 | 
					import { z, createRoute } from "@hono/zod-openapi";
 | 
				
			||||||
 | 
					import { OpenAPIHono } from "@hono/zod-openapi";
 | 
				
			||||||
 | 
					import { eq } from "drizzle-orm";
 | 
				
			||||||
 | 
					import { handle } from "hono/vercel";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const dynamic = "force-dynamic";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const app = new OpenAPIHono().basePath("/api");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					declare module "hono" {
 | 
				
			||||||
 | 
					  interface ContextVariableMap {
 | 
				
			||||||
 | 
					    apiKeyTokenData: ReturnType<typeof parseJWT>;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 userData = token ? parseJWT(token) : undefined;
 | 
				
			||||||
 | 
					  if (!userData || token === undefined) {
 | 
				
			||||||
 | 
					    return c.text("Invalid or expired token", 401);
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    const revokedKey = await isKeyRevoked(token);
 | 
				
			||||||
 | 
					    if (revokedKey) return c.text("Revoked token", 401);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  c.set("apiKeyTokenData", userData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await next();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// console.log(RunOutputZod.shape);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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) => {
 | 
				
			||||||
 | 
					  const data = c.req.valid("query");
 | 
				
			||||||
 | 
					  const apiKeyTokenData = c.get("apiKeyTokenData")!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const run = await getRunsData(apiKeyTokenData, data.run_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!run)
 | 
				
			||||||
 | 
					      return c.json(
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          code: 400,
 | 
				
			||||||
 | 
					          message: "Workflow not found",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        400
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Fill in the CDN url
 | 
				
			||||||
 | 
					    if (run?.status === "success" && run?.outputs?.length > 0) {
 | 
				
			||||||
 | 
					      for (let i = 0; i < run.outputs.length; i++) {
 | 
				
			||||||
 | 
					        const output = run.outputs[i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (output.data?.images !== undefined) {
 | 
				
			||||||
 | 
					          for (let j = 0; j < output.data?.images.length; j++) {
 | 
				
			||||||
 | 
					            const element = output.data?.images[j];
 | 
				
			||||||
 | 
					            element.url = replaceCDNUrl(
 | 
				
			||||||
 | 
					              `${process.env.SPACES_ENDPOINT}/${process.env.SPACES_BUCKET}/outputs/runs/${run.id}/${element.filename}`
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } else if (output.data?.files !== undefined) {
 | 
				
			||||||
 | 
					          for (let j = 0; j < output.data?.files.length; j++) {
 | 
				
			||||||
 | 
					            const element = output.data?.files[j];
 | 
				
			||||||
 | 
					            element.url = replaceCDNUrl(
 | 
				
			||||||
 | 
					              `${process.env.SPACES_ENDPOINT}/${process.env.SPACES_BUCKET}/outputs/runs/${run.id}/${element.filename}`
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
 | 
					app.doc("/doc", {
 | 
				
			||||||
 | 
					  openapi: "3.0.0",
 | 
				
			||||||
 | 
					  servers: [{ url: "/api" }],
 | 
				
			||||||
 | 
					  security: [{ bearerAuth: [] }],
 | 
				
			||||||
 | 
					  info: {
 | 
				
			||||||
 | 
					    version: "0.0.1",
 | 
				
			||||||
 | 
					    title: "Comfy Deploy API",
 | 
				
			||||||
 | 
					    description:
 | 
				
			||||||
 | 
					      "Interact with Comfy Deploy programmatically to trigger run and retrieve output",
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.openAPIRegistry.registerComponent("securitySchemes", "bearerAuth", {
 | 
				
			||||||
 | 
					  type: "apiKey",
 | 
				
			||||||
 | 
					  bearerFormat: "JWT",
 | 
				
			||||||
 | 
					  in: "header",
 | 
				
			||||||
 | 
					  name: "Authorization",
 | 
				
			||||||
 | 
					  description:
 | 
				
			||||||
 | 
					    "API token created in Comfy Deploy <a href='/api-keys' target='_blank' style='text-decoration: underline;'>/api-keys</a>",
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const handler = handle(app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const GET = handler;
 | 
				
			||||||
 | 
					export const POST = handler;
 | 
				
			||||||
@ -1,167 +0,0 @@
 | 
				
			|||||||
import { parseDataSafe } from "../../../../lib/parseDataSafe";
 | 
					 | 
				
			||||||
import { createRun } from "../../../../server/createRun";
 | 
					 | 
				
			||||||
import { db } from "@/db/db";
 | 
					 | 
				
			||||||
import { deploymentsTable } from "@/db/schema";
 | 
					 | 
				
			||||||
import { isKeyRevoked } from "@/server/curdApiKeys";
 | 
					 | 
				
			||||||
import { getRunsData } from "@/server/getRunsOutput";
 | 
					 | 
				
			||||||
import { parseJWT } from "@/server/parseJWT";
 | 
					 | 
				
			||||||
import { replaceCDNUrl } from "@/server/replaceCDNUrl";
 | 
					 | 
				
			||||||
import { eq } from "drizzle-orm";
 | 
					 | 
				
			||||||
import { NextResponse } from "next/server";
 | 
					 | 
				
			||||||
import { z } from "zod";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const dynamic = "force-dynamic";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Request = z.object({
 | 
					 | 
				
			||||||
  deployment_id: z.string(),
 | 
					 | 
				
			||||||
  inputs: z.record(z.string()).optional(),
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Request2 = z.object({
 | 
					 | 
				
			||||||
  run_id: z.string(),
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function checkToken(request: Request) {
 | 
					 | 
				
			||||||
  const token = request.headers.get("Authorization")?.split(" ")?.[1]; // Assuming token is sent as "Bearer your_token"
 | 
					 | 
				
			||||||
  const userData = token ? parseJWT(token) : undefined;
 | 
					 | 
				
			||||||
  if (!userData || token === undefined) {
 | 
					 | 
				
			||||||
    return {
 | 
					 | 
				
			||||||
      error: new NextResponse("Invalid or expired token", {
 | 
					 | 
				
			||||||
        status: 401,
 | 
					 | 
				
			||||||
      }),
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  } else {
 | 
					 | 
				
			||||||
    const revokedKey = await isKeyRevoked(token);
 | 
					 | 
				
			||||||
    if (revokedKey)
 | 
					 | 
				
			||||||
      return {
 | 
					 | 
				
			||||||
        error: new NextResponse("Revoked token", {
 | 
					 | 
				
			||||||
          status: 401,
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return {
 | 
					 | 
				
			||||||
    data: userData,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function GET(request: Request) {
 | 
					 | 
				
			||||||
  const apiKeyTokenData = await checkToken(request);
 | 
					 | 
				
			||||||
  if (apiKeyTokenData.error) return apiKeyTokenData.error;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const [data, error] = await parseDataSafe(Request2, request);
 | 
					 | 
				
			||||||
  if (!data || error) return error;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // return NextResponse.json(
 | 
					 | 
				
			||||||
  //   await db
 | 
					 | 
				
			||||||
  //     .select()
 | 
					 | 
				
			||||||
  //     .from(workflowTable)
 | 
					 | 
				
			||||||
  //     .innerJoin(
 | 
					 | 
				
			||||||
  //       workflowRunsTable,
 | 
					 | 
				
			||||||
  //       eq(workflowTable.id, workflowRunsTable.workflow_id)
 | 
					 | 
				
			||||||
  //     )
 | 
					 | 
				
			||||||
  //     .where(
 | 
					 | 
				
			||||||
  //       and(
 | 
					 | 
				
			||||||
  //         eq(workflowTable.id, workflowRunsTable.workflow_id),
 | 
					 | 
				
			||||||
  //         apiKeyTokenData.data.org_id
 | 
					 | 
				
			||||||
  //           ? eq(workflowTable.org_id, apiKeyTokenData.data.org_id)
 | 
					 | 
				
			||||||
  //           : eq(workflowTable.user_id, apiKeyTokenData.data.user_id!)
 | 
					 | 
				
			||||||
  //       )
 | 
					 | 
				
			||||||
  //     ),
 | 
					 | 
				
			||||||
  //   {
 | 
					 | 
				
			||||||
  //     status: 200,
 | 
					 | 
				
			||||||
  //   }
 | 
					 | 
				
			||||||
  // );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const run = await getRunsData(apiKeyTokenData.data, data.run_id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (!run) return new NextResponse("Run not found", { status: 404 });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if (run?.status === "success" && run?.outputs?.length > 0) {
 | 
					 | 
				
			||||||
    for (let i = 0; i < run.outputs.length; i++) {
 | 
					 | 
				
			||||||
      const output = run.outputs[i];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (output.data?.images !== undefined) {
 | 
					 | 
				
			||||||
        for (let j = 0; j < output.data?.images.length; j++) {
 | 
					 | 
				
			||||||
          const element = output.data?.images[j];
 | 
					 | 
				
			||||||
          element.url = replaceCDNUrl(
 | 
					 | 
				
			||||||
            `${process.env.SPACES_ENDPOINT}/${process.env.SPACES_BUCKET}/outputs/runs/${run.id}/${element.filename}`
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      } else if (output.data?.files !== undefined) {
 | 
					 | 
				
			||||||
        for (let j = 0; j < output.data?.files.length; j++) {
 | 
					 | 
				
			||||||
          const element = output.data?.files[j];
 | 
					 | 
				
			||||||
          element.url = replaceCDNUrl(
 | 
					 | 
				
			||||||
            `${process.env.SPACES_ENDPOINT}/${process.env.SPACES_BUCKET}/outputs/runs/${run.id}/${element.filename}`
 | 
					 | 
				
			||||||
          );
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return NextResponse.json(run, {
 | 
					 | 
				
			||||||
    status: 200,
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function POST(request: Request) {
 | 
					 | 
				
			||||||
  const apiKeyTokenData = await checkToken(request);
 | 
					 | 
				
			||||||
  if (apiKeyTokenData.error) return apiKeyTokenData.error;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const [data, error] = await parseDataSafe(Request, request);
 | 
					 | 
				
			||||||
  if (!data || error) return error;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const origin = new URL(request.url).origin;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  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.data,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if ("error" in run_id) throw new Error(run_id.error);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return NextResponse.json(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        run_id: "workflow_run_id" in run_id ? run_id.workflow_run_id : "",
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        status: 200,
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  } catch (error: any) {
 | 
					 | 
				
			||||||
    return NextResponse.json(
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        error: error.message,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      {
 | 
					 | 
				
			||||||
        status: 500,
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										91
									
								
								web/src/app/(docs)/docs/endpoints/page.mdx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								web/src/app/(docs)/docs/endpoints/page.mdx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					export const metadata = {
 | 
				
			||||||
 | 
					  title: 'Workflow API',
 | 
				
			||||||
 | 
					  description:
 | 
				
			||||||
 | 
					    'Get started with API integration to run any deploy ComfyUI workflow',
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{/* # Workflow API */}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{/* Get started with API integration to run any deploy ComfyUI workflow */}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<SwaggerUI url={"/api/doc"}></SwaggerUI>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{/* ## Trigger a run {{ tag: 'POST', label: '/api/run' }}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<Row>
 | 
				
			||||||
 | 
					  <Col>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Trigger a run with a deployment id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ### Optional attributes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <Properties>
 | 
				
			||||||
 | 
					      <Property name="conversation_id" type="string">
 | 
				
			||||||
 | 
					        Limit to attachments from a given conversation.
 | 
				
			||||||
 | 
					      </Property>
 | 
				
			||||||
 | 
					      <Property name="limit" type="integer">
 | 
				
			||||||
 | 
					        Limit the number of attachments returned.
 | 
				
			||||||
 | 
					      </Property>
 | 
				
			||||||
 | 
					    </Properties>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  </Col>
 | 
				
			||||||
 | 
					  <Col sticky>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <CodeGroup title="Request" tag="GET" label="/v1/attachments">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```bash {{ title: 'cURL' }}
 | 
				
			||||||
 | 
					    curl -G https://api.protocol.chat/v1/attachments \
 | 
				
			||||||
 | 
					      -H "Authorization: Bearer {token}" \
 | 
				
			||||||
 | 
					      -d conversation_id="xgQQXg3hrtjh7AvZ" \
 | 
				
			||||||
 | 
					      -d limit=10
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```js
 | 
				
			||||||
 | 
					    import ApiClient from '@example/protocol-api'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const client = new ApiClient(token)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await client.attachments.list()
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```python
 | 
				
			||||||
 | 
					    from protocol_api import ApiClient
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    client = ApiClient(token)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    client.attachments.list()
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```php
 | 
				
			||||||
 | 
					    $client = new \Protocol\ApiClient($token);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $client->attachments->list();
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    </CodeGroup>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```json {{ title: 'Response' }}
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "has_more": false,
 | 
				
			||||||
 | 
					      "data": [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "id": "Nc6yKKMpcxiiFxp6",
 | 
				
			||||||
 | 
					          "message_id": "LoPsJaMcPBuFNjg1",
 | 
				
			||||||
 | 
					          "filename": "Invoice_room_service__Plaza_Hotel.pdf",
 | 
				
			||||||
 | 
					          "file_url": "https://assets.protocol.chat/attachments/Invoice_room_service__Plaza_Hotel.pdf",
 | 
				
			||||||
 | 
					          "file_type": "application/pdf",
 | 
				
			||||||
 | 
					          "file_size": 21352,
 | 
				
			||||||
 | 
					          "created_at": 692233200
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          "id": "hSIhXBhNe8X1d8Et"
 | 
				
			||||||
 | 
					          // ...
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      ]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  </Col>
 | 
				
			||||||
 | 
					</Row>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--- */}
 | 
				
			||||||
@ -238,6 +238,10 @@ export const navigation: Array<NavGroup> = [
 | 
				
			|||||||
      { title: "Installation", href: "/docs/install" },
 | 
					      { title: "Installation", href: "/docs/install" },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    title: "API",
 | 
				
			||||||
 | 
					    links: [{ title: "Endpoints", href: "/docs/endpoints" }],
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Navigation(props: React.ComponentPropsWithoutRef<"nav">) {
 | 
					export function Navigation(props: React.ComponentPropsWithoutRef<"nav">) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								web/src/components/docs/SwaggerUIClient.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								web/src/components/docs/SwaggerUIClient.css
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					.swagger-ui .info {
 | 
				
			||||||
 | 
					    margin: 0 !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										39
									
								
								web/src/components/docs/SwaggerUIClient.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								web/src/components/docs/SwaggerUIClient.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "./SwaggerUIClient.css";
 | 
				
			||||||
 | 
					import type { ComponentProps } from "react";
 | 
				
			||||||
 | 
					import React from "react";
 | 
				
			||||||
 | 
					import _SwaggerUI from "swagger-ui-react";
 | 
				
			||||||
 | 
					import "swagger-ui-react/swagger-ui.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Create the layout component
 | 
				
			||||||
 | 
					class AugmentingLayout extends React.Component {
 | 
				
			||||||
 | 
					  render() {
 | 
				
			||||||
 | 
					    const { getComponent } = this.props;
 | 
				
			||||||
 | 
					    const BaseLayout = getComponent("BaseLayout", true);
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <div className="not-prose">
 | 
				
			||||||
 | 
					        <BaseLayout />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const AugmentingLayoutPlugin = () => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    components: {
 | 
				
			||||||
 | 
					      AugmentingLayout: AugmentingLayout,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function SwaggerUI(props: ComponentProps<typeof _SwaggerUI>) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <_SwaggerUI
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					      persistAuthorization={true}
 | 
				
			||||||
 | 
					      plugins={[AugmentingLayoutPlugin]}
 | 
				
			||||||
 | 
					      layout="AugmentingLayout"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -2,9 +2,16 @@ import { Feedback } from "@/components/docs/Feedback";
 | 
				
			|||||||
import { Heading } from "@/components/docs/Heading";
 | 
					import { Heading } from "@/components/docs/Heading";
 | 
				
			||||||
import { Prose } from "@/components/docs/Prose";
 | 
					import { Prose } from "@/components/docs/Prose";
 | 
				
			||||||
import { cn } from "@/lib/utils";
 | 
					import { cn } from "@/lib/utils";
 | 
				
			||||||
 | 
					// import { SwaggerUI } from "@hono/swagger-ui";
 | 
				
			||||||
import clsx from "clsx";
 | 
					import clsx from "clsx";
 | 
				
			||||||
 | 
					import dynamic from "next/dynamic";
 | 
				
			||||||
 | 
					// import _SwaggerUI from "swagger-ui-react";
 | 
				
			||||||
import Link from "next/link";
 | 
					import Link from "next/link";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const SwaggerUI = dynamic(() => import("./SwaggerUIClient"), {
 | 
				
			||||||
 | 
					  ssr: false,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const a = Link;
 | 
					export const a = Link;
 | 
				
			||||||
export { Button } from "@/components/docs/Button";
 | 
					export { Button } from "@/components/docs/Button";
 | 
				
			||||||
export { CodeGroup, Code as code, Pre as pre } from "@/components/docs/Code";
 | 
					export { CodeGroup, Code as code, Pre as pre } from "@/components/docs/Code";
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										309
									
								
								web/src/lib/drizzle-zod-hono.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										309
									
								
								web/src/lib/drizzle-zod-hono.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,309 @@
 | 
				
			|||||||
 | 
					import { z } from "@hono/zod-openapi";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  type Assume,
 | 
				
			||||||
 | 
					  type Column,
 | 
				
			||||||
 | 
					  type DrizzleTypeError,
 | 
				
			||||||
 | 
					  type Equal,
 | 
				
			||||||
 | 
					  getTableColumns,
 | 
				
			||||||
 | 
					  is,
 | 
				
			||||||
 | 
					  type Simplify,
 | 
				
			||||||
 | 
					  type Table,
 | 
				
			||||||
 | 
					} from "drizzle-orm";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  MySqlChar,
 | 
				
			||||||
 | 
					  MySqlVarBinary,
 | 
				
			||||||
 | 
					  MySqlVarChar,
 | 
				
			||||||
 | 
					} from "drizzle-orm/mysql-core";
 | 
				
			||||||
 | 
					import { type PgArray, PgChar, PgUUID, PgVarchar } from "drizzle-orm/pg-core";
 | 
				
			||||||
 | 
					import { SQLiteText } from "drizzle-orm/sqlite-core";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const literalSchema = z.union([z.string(), z.number(), z.boolean(), z.null()]);
 | 
				
			||||||
 | 
					type Literal = z.infer<typeof literalSchema>;
 | 
				
			||||||
 | 
					type Json = Literal | { [key: string]: Json } | Json[];
 | 
				
			||||||
 | 
					export const jsonSchema: z.ZodType<Json> = z.lazy(() =>
 | 
				
			||||||
 | 
					  z.union([literalSchema, z.array(jsonSchema), z.record(jsonSchema)])
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MapInsertColumnToZod<
 | 
				
			||||||
 | 
					  TColumn extends Column,
 | 
				
			||||||
 | 
					  TType extends z.ZodTypeAny
 | 
				
			||||||
 | 
					> = TColumn["_"]["notNull"] extends false
 | 
				
			||||||
 | 
					  ? z.ZodOptional<z.ZodNullable<TType>>
 | 
				
			||||||
 | 
					  : TColumn["_"]["hasDefault"] extends true
 | 
				
			||||||
 | 
					  ? z.ZodOptional<TType>
 | 
				
			||||||
 | 
					  : TType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MapSelectColumnToZod<
 | 
				
			||||||
 | 
					  TColumn extends Column,
 | 
				
			||||||
 | 
					  TType extends z.ZodTypeAny
 | 
				
			||||||
 | 
					> = TColumn["_"]["notNull"] extends false ? z.ZodNullable<TType> : TType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MapColumnToZod<
 | 
				
			||||||
 | 
					  TColumn extends Column,
 | 
				
			||||||
 | 
					  TType extends z.ZodTypeAny,
 | 
				
			||||||
 | 
					  TMode extends "insert" | "select"
 | 
				
			||||||
 | 
					> = TMode extends "insert"
 | 
				
			||||||
 | 
					  ? MapInsertColumnToZod<TColumn, TType>
 | 
				
			||||||
 | 
					  : MapSelectColumnToZod<TColumn, TType>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type MaybeOptional<
 | 
				
			||||||
 | 
					  TColumn extends Column,
 | 
				
			||||||
 | 
					  TType extends z.ZodTypeAny,
 | 
				
			||||||
 | 
					  TMode extends "insert" | "select",
 | 
				
			||||||
 | 
					  TNoOptional extends boolean
 | 
				
			||||||
 | 
					> = TNoOptional extends true ? TType : MapColumnToZod<TColumn, TType, TMode>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type GetZodType<TColumn extends Column> =
 | 
				
			||||||
 | 
					  TColumn["_"]["dataType"] extends infer TDataType
 | 
				
			||||||
 | 
					    ? TDataType extends "custom"
 | 
				
			||||||
 | 
					      ? z.ZodAny
 | 
				
			||||||
 | 
					      : TDataType extends "json"
 | 
				
			||||||
 | 
					      ? z.ZodType<Json>
 | 
				
			||||||
 | 
					      : TColumn extends { enumValues: [string, ...string[]] }
 | 
				
			||||||
 | 
					      ? Equal<TColumn["enumValues"], [string, ...string[]]> extends true
 | 
				
			||||||
 | 
					        ? z.ZodString
 | 
				
			||||||
 | 
					        : z.ZodEnum<TColumn["enumValues"]>
 | 
				
			||||||
 | 
					      : TDataType extends "array"
 | 
				
			||||||
 | 
					      ? z.ZodArray<
 | 
				
			||||||
 | 
					          GetZodType<Assume<TColumn["_"], { baseColumn: Column }>["baseColumn"]>
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					      : TDataType extends "bigint"
 | 
				
			||||||
 | 
					      ? z.ZodBigInt
 | 
				
			||||||
 | 
					      : TDataType extends "number"
 | 
				
			||||||
 | 
					      ? z.ZodNumber
 | 
				
			||||||
 | 
					      : TDataType extends "string"
 | 
				
			||||||
 | 
					      ? z.ZodString
 | 
				
			||||||
 | 
					      : TDataType extends "boolean"
 | 
				
			||||||
 | 
					      ? z.ZodBoolean
 | 
				
			||||||
 | 
					      : TDataType extends "date"
 | 
				
			||||||
 | 
					      ? //   ? z.ZodDate
 | 
				
			||||||
 | 
					        z.ZodString
 | 
				
			||||||
 | 
					      : z.ZodAny
 | 
				
			||||||
 | 
					    : never;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ValueOrUpdater<T, TUpdaterArg> = T | ((arg: TUpdaterArg) => T);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type UnwrapValueOrUpdater<T> = T extends ValueOrUpdater<infer U, any>
 | 
				
			||||||
 | 
					  ? U
 | 
				
			||||||
 | 
					  : never;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type Refine<TTable extends Table, TMode extends "select" | "insert"> = {
 | 
				
			||||||
 | 
					  [K in keyof TTable["_"]["columns"]]?: ValueOrUpdater<
 | 
				
			||||||
 | 
					    z.ZodTypeAny,
 | 
				
			||||||
 | 
					    TMode extends "select"
 | 
				
			||||||
 | 
					      ? BuildSelectSchema<TTable, {}, true>
 | 
				
			||||||
 | 
					      : BuildInsertSchema<TTable, {}, true>
 | 
				
			||||||
 | 
					  >;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type BuildInsertSchema<
 | 
				
			||||||
 | 
					  TTable extends Table,
 | 
				
			||||||
 | 
					  TRefine extends Refine<TTable, "insert"> | {},
 | 
				
			||||||
 | 
					  TNoOptional extends boolean = false
 | 
				
			||||||
 | 
					> = TTable["_"]["columns"] extends infer TColumns extends Record<
 | 
				
			||||||
 | 
					  string,
 | 
				
			||||||
 | 
					  Column<any>
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
 | 
					  ? {
 | 
				
			||||||
 | 
					      [K in keyof TColumns & string]: MaybeOptional<
 | 
				
			||||||
 | 
					        TColumns[K],
 | 
				
			||||||
 | 
					        K extends keyof TRefine
 | 
				
			||||||
 | 
					          ? Assume<UnwrapValueOrUpdater<TRefine[K]>, z.ZodTypeAny>
 | 
				
			||||||
 | 
					          : GetZodType<TColumns[K]>,
 | 
				
			||||||
 | 
					        "insert",
 | 
				
			||||||
 | 
					        TNoOptional
 | 
				
			||||||
 | 
					      >;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  : never;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type BuildSelectSchema<
 | 
				
			||||||
 | 
					  TTable extends Table,
 | 
				
			||||||
 | 
					  TRefine extends Refine<TTable, "select">,
 | 
				
			||||||
 | 
					  TNoOptional extends boolean = false
 | 
				
			||||||
 | 
					> = Simplify<{
 | 
				
			||||||
 | 
					  [K in keyof TTable["_"]["columns"]]: MaybeOptional<
 | 
				
			||||||
 | 
					    TTable["_"]["columns"][K],
 | 
				
			||||||
 | 
					    K extends keyof TRefine
 | 
				
			||||||
 | 
					      ? Assume<UnwrapValueOrUpdater<TRefine[K]>, z.ZodTypeAny>
 | 
				
			||||||
 | 
					      : GetZodType<TTable["_"]["columns"][K]>,
 | 
				
			||||||
 | 
					    "select",
 | 
				
			||||||
 | 
					    TNoOptional
 | 
				
			||||||
 | 
					  >;
 | 
				
			||||||
 | 
					}>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function createInsertSchema<
 | 
				
			||||||
 | 
					  TTable extends Table,
 | 
				
			||||||
 | 
					  TRefine extends Refine<TTable, "insert"> = Refine<TTable, "insert">
 | 
				
			||||||
 | 
					>(
 | 
				
			||||||
 | 
					  table: TTable,
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * @param refine Refine schema fields
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  refine?: {
 | 
				
			||||||
 | 
					    [K in keyof TRefine]: K extends keyof TTable["_"]["columns"]
 | 
				
			||||||
 | 
					      ? TRefine[K]
 | 
				
			||||||
 | 
					      : DrizzleTypeError<`Column '${K &
 | 
				
			||||||
 | 
					          string}' does not exist in table '${TTable["_"]["name"]}'`>;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					): z.ZodObject<
 | 
				
			||||||
 | 
					  BuildInsertSchema<
 | 
				
			||||||
 | 
					    TTable,
 | 
				
			||||||
 | 
					    Equal<TRefine, Refine<TTable, "insert">> extends true ? {} : TRefine
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					> {
 | 
				
			||||||
 | 
					  const columns = getTableColumns(table);
 | 
				
			||||||
 | 
					  const columnEntries = Object.entries(columns);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let schemaEntries = Object.fromEntries(
 | 
				
			||||||
 | 
					    columnEntries.map(([name, column]) => {
 | 
				
			||||||
 | 
					      return [name, mapColumnToSchema(column)];
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (refine) {
 | 
				
			||||||
 | 
					    schemaEntries = Object.assign(
 | 
				
			||||||
 | 
					      schemaEntries,
 | 
				
			||||||
 | 
					      Object.fromEntries(
 | 
				
			||||||
 | 
					        Object.entries(refine).map(([name, refineColumn]) => {
 | 
				
			||||||
 | 
					          return [
 | 
				
			||||||
 | 
					            name,
 | 
				
			||||||
 | 
					            typeof refineColumn === "function"
 | 
				
			||||||
 | 
					              ? refineColumn(
 | 
				
			||||||
 | 
					                  schemaEntries as BuildInsertSchema<TTable, {}, true>
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					              : refineColumn,
 | 
				
			||||||
 | 
					          ];
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const [name, column] of columnEntries) {
 | 
				
			||||||
 | 
					    if (!column.notNull) {
 | 
				
			||||||
 | 
					      schemaEntries[name] = schemaEntries[name]!.nullable().optional();
 | 
				
			||||||
 | 
					    } else if (column.hasDefault) {
 | 
				
			||||||
 | 
					      schemaEntries[name] = schemaEntries[name]!.optional();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return z.object(schemaEntries) as any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function createSelectSchema<
 | 
				
			||||||
 | 
					  TTable extends Table,
 | 
				
			||||||
 | 
					  TRefine extends Refine<TTable, "select"> = Refine<TTable, "select">
 | 
				
			||||||
 | 
					>(
 | 
				
			||||||
 | 
					  table: TTable,
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * @param refine Refine schema fields
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  refine?: {
 | 
				
			||||||
 | 
					    [K in keyof TRefine]: K extends keyof TTable["_"]["columns"]
 | 
				
			||||||
 | 
					      ? TRefine[K]
 | 
				
			||||||
 | 
					      : DrizzleTypeError<`Column '${K &
 | 
				
			||||||
 | 
					          string}' does not exist in table '${TTable["_"]["name"]}'`>;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					): z.ZodObject<
 | 
				
			||||||
 | 
					  BuildSelectSchema<
 | 
				
			||||||
 | 
					    TTable,
 | 
				
			||||||
 | 
					    Equal<TRefine, Refine<TTable, "select">> extends true ? {} : TRefine
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					> {
 | 
				
			||||||
 | 
					  const columns = getTableColumns(table);
 | 
				
			||||||
 | 
					  const columnEntries = Object.entries(columns);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let schemaEntries = Object.fromEntries(
 | 
				
			||||||
 | 
					    columnEntries.map(([name, column]) => {
 | 
				
			||||||
 | 
					      return [name, mapColumnToSchema(column)];
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (refine) {
 | 
				
			||||||
 | 
					    schemaEntries = Object.assign(
 | 
				
			||||||
 | 
					      schemaEntries,
 | 
				
			||||||
 | 
					      Object.fromEntries(
 | 
				
			||||||
 | 
					        Object.entries(refine).map(([name, refineColumn]) => {
 | 
				
			||||||
 | 
					          return [
 | 
				
			||||||
 | 
					            name,
 | 
				
			||||||
 | 
					            typeof refineColumn === "function"
 | 
				
			||||||
 | 
					              ? refineColumn(
 | 
				
			||||||
 | 
					                  schemaEntries as BuildSelectSchema<TTable, {}, true>
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					              : refineColumn,
 | 
				
			||||||
 | 
					          ];
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const [name, column] of columnEntries) {
 | 
				
			||||||
 | 
					    if (!column.notNull) {
 | 
				
			||||||
 | 
					      schemaEntries[name] = schemaEntries[name]!.nullable();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return z.object(schemaEntries) as any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isWithEnum(
 | 
				
			||||||
 | 
					  column: Column
 | 
				
			||||||
 | 
					): column is typeof column & { enumValues: [string, ...string[]] } {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    "enumValues" in column &&
 | 
				
			||||||
 | 
					    Array.isArray(column.enumValues) &&
 | 
				
			||||||
 | 
					    column.enumValues.length > 0
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function mapColumnToSchema(column: Column): z.ZodTypeAny {
 | 
				
			||||||
 | 
					  let type: z.ZodTypeAny | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (isWithEnum(column)) {
 | 
				
			||||||
 | 
					    type = column.enumValues.length ? z.enum(column.enumValues) : z.string();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!type) {
 | 
				
			||||||
 | 
					    if (is(column, PgUUID)) {
 | 
				
			||||||
 | 
					      type = z.string().uuid();
 | 
				
			||||||
 | 
					    } else if (column.dataType === "custom") {
 | 
				
			||||||
 | 
					      type = z.any();
 | 
				
			||||||
 | 
					    } else if (column.dataType === "json") {
 | 
				
			||||||
 | 
					      type = jsonSchema;
 | 
				
			||||||
 | 
					    } else if (column.dataType === "array") {
 | 
				
			||||||
 | 
					      type = z.array(
 | 
				
			||||||
 | 
					        mapColumnToSchema((column as PgArray<any, any>).baseColumn)
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } else if (column.dataType === "number") {
 | 
				
			||||||
 | 
					      type = z.number();
 | 
				
			||||||
 | 
					    } else if (column.dataType === "bigint") {
 | 
				
			||||||
 | 
					      type = z.bigint();
 | 
				
			||||||
 | 
					    } else if (column.dataType === "boolean") {
 | 
				
			||||||
 | 
					      type = z.boolean();
 | 
				
			||||||
 | 
					    } else if (column.dataType === "date") {
 | 
				
			||||||
 | 
					      //   type = z.date();
 | 
				
			||||||
 | 
					      type = z.string();
 | 
				
			||||||
 | 
					    } else if (column.dataType === "string") {
 | 
				
			||||||
 | 
					      let sType = z.string();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        (is(column, PgChar) ||
 | 
				
			||||||
 | 
					          is(column, PgVarchar) ||
 | 
				
			||||||
 | 
					          is(column, MySqlVarChar) ||
 | 
				
			||||||
 | 
					          is(column, MySqlVarBinary) ||
 | 
				
			||||||
 | 
					          is(column, MySqlChar) ||
 | 
				
			||||||
 | 
					          is(column, SQLiteText)) &&
 | 
				
			||||||
 | 
					        typeof column.length === "number"
 | 
				
			||||||
 | 
					      ) {
 | 
				
			||||||
 | 
					        sType = sType.max(column.length);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      type = sType;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!type) {
 | 
				
			||||||
 | 
					    type = z.any();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return type;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -20,29 +20,7 @@ export async function getRunsOutput(run_id: string) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export async function getRunsData(user: APIKeyUserType, run_id: string) {
 | 
					export async function getRunsData(user: APIKeyUserType, run_id: string) {
 | 
				
			||||||
  const data = await db.query.workflowRunsTable.findFirst({
 | 
					  const data = await db.query.workflowRunsTable.findFirst({
 | 
				
			||||||
    where: and(
 | 
					    where: and(eq(workflowRunsTable.id, run_id)),
 | 
				
			||||||
      eq(workflowRunsTable.id, run_id)
 | 
					 | 
				
			||||||
      // inArray(
 | 
					 | 
				
			||||||
      //   workflowRunsTable.workflow_id,
 | 
					 | 
				
			||||||
      //   db
 | 
					 | 
				
			||||||
      //     .select({
 | 
					 | 
				
			||||||
      //       id: workflowTable.id,
 | 
					 | 
				
			||||||
      //     })
 | 
					 | 
				
			||||||
      //     .from(workflowTable)
 | 
					 | 
				
			||||||
      //     .innerJoin(
 | 
					 | 
				
			||||||
      //       workflowRunsTable,
 | 
					 | 
				
			||||||
      //       eq(workflowTable.id, workflowRunsTable.workflow_id)
 | 
					 | 
				
			||||||
      //     )
 | 
					 | 
				
			||||||
      //     .where(
 | 
					 | 
				
			||||||
      //       and(
 | 
					 | 
				
			||||||
      //         eq(workflowTable.id, workflowRunsTable.workflow_id),
 | 
					 | 
				
			||||||
      //         user.org_id
 | 
					 | 
				
			||||||
      //           ? eq(workflowTable.org_id, user.org_id)
 | 
					 | 
				
			||||||
      //           : eq(workflowTable.user_id, user.user_id!)
 | 
					 | 
				
			||||||
      //       )
 | 
					 | 
				
			||||||
      //     )
 | 
					 | 
				
			||||||
      // )
 | 
					 | 
				
			||||||
    ),
 | 
					 | 
				
			||||||
    with: {
 | 
					    with: {
 | 
				
			||||||
      workflow: {
 | 
					      workflow: {
 | 
				
			||||||
        columns: {
 | 
					        columns: {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user