diff --git a/web-plugin/index.js b/web-plugin/index.js
index 60726c8..47e71d3 100644
--- a/web-plugin/index.js
+++ b/web-plugin/index.js
@@ -120,7 +120,7 @@ function addButton() {
const graph = app.graph;
const snapshot = await fetch("/snapshot/get_current").then((x) => x.json());
- console.log(snapshot);
+ // console.log(snapshot);
if (!snapshot) {
showError(
@@ -154,7 +154,7 @@ function addButton() {
const deployMetaNode = deployMeta[0];
- console.log(deployMetaNode);
+ // console.log(deployMetaNode);
const workflow_name = deployMetaNode.widgets[0].value;
const workflow_id = deployMetaNode.widgets[1].value;
@@ -168,20 +168,23 @@ function addButton() {
// const endpoint = localStorage.getItem("endpoint") ?? "";
// const apiKey = localStorage.getItem("apiKey");
- const { endpoint, apiKey } = getData();
+ const { endpoint, apiKey, displayName } = getData();
if (!endpoint || !apiKey || apiKey === "" || endpoint === "") {
configDialog.show();
return;
}
- const ok = await confirmDialog.confirm("Confirm deployment", "A new version will be deployed, are you conform?")
+ const ok = await confirmDialog.confirm(
+ "Confirm deployment -> " + displayName,
+ "A new version will be deployed, are you conform?",
+ );
if (!ok) return;
title.innerText = "Deploying...";
title.style.color = "orange";
- console.log(prompt);
+ // console.log(prompt);
// TODO trim the ending / from endpoint is there is
if (endpoint.endsWith("/")) {
@@ -191,15 +194,17 @@ function addButton() {
const apiRoute = endpoint + "/api/upload";
// const userId = apiKey
try {
+ const body = {
+ workflow_name,
+ workflow_id,
+ workflow: prompt.workflow,
+ workflow_api: prompt.output,
+ snapshot: snapshot,
+ };
+ console.log(body);
let data = await fetch(apiRoute, {
method: "POST",
- body: JSON.stringify({
- workflow_name,
- workflow_id,
- workflow: prompt.workflow,
- workflow_api: prompt.output,
- snapshot: snapshot,
- }),
+ body: JSON.stringify(body),
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + apiKey,
@@ -301,6 +306,17 @@ export class InfoDialog extends ComfyDialog {
this.element.style.display = "flex";
this.element.style.zIndex = 1001;
}
+
+ showMessage(title, message) {
+ this.show(`
+
+
${title}
+
+
+ `);
+ }
}
export class InputDialog extends InfoDialog {
@@ -367,7 +383,6 @@ export class InputDialog extends InfoDialog {
}
}
-
export class ConfirmDialog extends InfoDialog {
callback = undefined;
@@ -456,6 +471,8 @@ function getData(environment) {
export class ConfigDialog extends ComfyDialog {
container = null;
+ poll = null;
+ timeout = null;
constructor() {
super();
@@ -498,17 +515,22 @@ export class ConfigDialog extends ComfyDialog {
close() {
this.element.style.display = "none";
+ clearInterval(this.poll);
+ clearTimeout(this.timeout);
}
- save() {
+ save(api_key, displayName) {
+ if (!displayName) displayName = getData().displayName;
+
const deployOption = this.container.querySelector("#deployOption").value;
localStorage.setItem("comfy_deploy_env", deployOption);
const endpoint = this.container.querySelector("#endpoint").value;
- const apiKey = this.container.querySelector("#apiKey").value;
+ const apiKey = api_key ?? this.container.querySelector("#apiKey").value;
const data = {
endpoint,
apiKey,
+ displayName,
};
localStorage.setItem(
"comfy_deploy_env_data_" + deployOption,
@@ -527,12 +549,8 @@ export class ConfigDialog extends ComfyDialog {
Comfy Deploy Config
`;
+ const button = this.container.querySelector("#loginButton");
+ button.onclick = () => {
+ const uuid =
+ Math.random().toString(36).substring(2, 15) +
+ Math.random().toString(36).substring(2, 15);
+ window.open(data.endpoint + "/auth-request/" + uuid, "_blank");
+
+ this.timeout = setTimeout(() => {
+ clearInterval(poll);
+ infoDialog.showMessage(
+ "Timeout",
+ "Wait too long for the response, please try re-login",
+ );
+ }, 30000); // Stop polling after 30 seconds
+
+ this.poll = setInterval(() => {
+ fetch(data.endpoint + "/api/auth-response/" + uuid)
+ .then((response) => response.json())
+ .then((json) => {
+ if (json.api_key) {
+ this.save(json.api_key, json.name);
+ this.container.querySelector("#apiKey").value = json.api_key;
+ infoDialog.show();
+ clearInterval(this.poll);
+ clearTimeout(this.timeout);
+ infoDialog.showMessage(
+ "Authenticated",
+ "You will be able to upload workflow to " + json.name,
+ );
+ }
+ })
+ .catch((error) => {
+ console.error("Error:", error);
+ clearInterval(this.poll);
+ clearTimeout(this.timeout);
+ infoDialog.showMessage("Error", error);
+ });
+ }, 2000);
+ };
+
const apiKeyInput = this.container.querySelector("#apiKey");
- apiKeyInput.addEventListener("paste", function (e) {
+ apiKeyInput.addEventListener("paste", (e) => {
e.stopPropagation();
});
diff --git a/web/bun.lockb b/web/bun.lockb
index c5cc14e..650dd43 100755
Binary files a/web/bun.lockb and b/web/bun.lockb differ
diff --git a/web/drizzle/0033_awesome_human_fly.sql b/web/drizzle/0033_awesome_human_fly.sql
new file mode 100644
index 0000000..4eb44af
--- /dev/null
+++ b/web/drizzle/0033_awesome_human_fly.sql
@@ -0,0 +1,8 @@
+CREATE TABLE IF NOT EXISTS "comfyui_deploy"."auth_requests" (
+ "request_id" text PRIMARY KEY NOT NULL,
+ "user_id" text,
+ "org_id" text,
+ "api_hash" text,
+ "created_at" timestamp DEFAULT now() NOT NULL,
+ "updated_at" timestamp DEFAULT now() NOT NULL
+);
diff --git a/web/drizzle/0034_even_lady_ursula.sql b/web/drizzle/0034_even_lady_ursula.sql
new file mode 100644
index 0000000..6e48c4a
--- /dev/null
+++ b/web/drizzle/0034_even_lady_ursula.sql
@@ -0,0 +1 @@
+ALTER TABLE "comfyui_deploy"."auth_requests" ADD COLUMN "expired_date" timestamp;
\ No newline at end of file
diff --git a/web/drizzle/meta/0033_snapshot.json b/web/drizzle/meta/0033_snapshot.json
new file mode 100644
index 0000000..5e67823
--- /dev/null
+++ b/web/drizzle/meta/0033_snapshot.json
@@ -0,0 +1,824 @@
+{
+ "id": "97662b25-3992-4859-9bdc-560e2a70daea",
+ "prevId": "1425ee00-66fb-4541-8da7-19b217944545",
+ "version": "5",
+ "dialect": "pg",
+ "tables": {
+ "api_keys": {
+ "name": "api_keys",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revoked": {
+ "name": "revoked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "api_keys_user_id_users_id_fk": {
+ "name": "api_keys_user_id_users_id_fk",
+ "tableFrom": "api_keys",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "api_keys_key_unique": {
+ "name": "api_keys_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "key"
+ ]
+ }
+ }
+ },
+ "auth_requests": {
+ "name": "auth_requests",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "request_id": {
+ "name": "request_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "api_hash": {
+ "name": "api_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "deployments": {
+ "name": "deployments",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workflow_version_id": {
+ "name": "workflow_version_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "machine_id": {
+ "name": "machine_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "share_slug": {
+ "name": "share_slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "showcase_media": {
+ "name": "showcase_media",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "environment": {
+ "name": "environment",
+ "type": "deployment_environment",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "deployments_user_id_users_id_fk": {
+ "name": "deployments_user_id_users_id_fk",
+ "tableFrom": "deployments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployments_workflow_version_id_workflow_versions_id_fk": {
+ "name": "deployments_workflow_version_id_workflow_versions_id_fk",
+ "tableFrom": "deployments",
+ "tableTo": "workflow_versions",
+ "columnsFrom": [
+ "workflow_version_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "deployments_workflow_id_workflows_id_fk": {
+ "name": "deployments_workflow_id_workflows_id_fk",
+ "tableFrom": "deployments",
+ "tableTo": "workflows",
+ "columnsFrom": [
+ "workflow_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployments_machine_id_machines_id_fk": {
+ "name": "deployments_machine_id_machines_id_fk",
+ "tableFrom": "deployments",
+ "tableTo": "machines",
+ "columnsFrom": [
+ "machine_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "deployments_share_slug_unique": {
+ "name": "deployments_share_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "share_slug"
+ ]
+ }
+ }
+ },
+ "machines": {
+ "name": "machines",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpoint": {
+ "name": "endpoint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "disabled": {
+ "name": "disabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "auth_token": {
+ "name": "auth_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "type": {
+ "name": "type",
+ "type": "machine_type",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'classic'"
+ },
+ "status": {
+ "name": "status",
+ "type": "machine_status",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ready'"
+ },
+ "snapshot": {
+ "name": "snapshot",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "models": {
+ "name": "models",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gpu": {
+ "name": "gpu",
+ "type": "machine_gpu",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "build_machine_instance_id": {
+ "name": "build_machine_instance_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "build_log": {
+ "name": "build_log",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "machines_user_id_users_id_fk": {
+ "name": "machines_user_id_users_id_fk",
+ "tableFrom": "machines",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "users": {
+ "name": "users",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "workflow_run_outputs": {
+ "name": "workflow_run_outputs",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "data": {
+ "name": "data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflow_run_outputs_run_id_workflow_runs_id_fk": {
+ "name": "workflow_run_outputs_run_id_workflow_runs_id_fk",
+ "tableFrom": "workflow_run_outputs",
+ "tableTo": "workflow_runs",
+ "columnsFrom": [
+ "run_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "workflow_runs": {
+ "name": "workflow_runs",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workflow_version_id": {
+ "name": "workflow_version_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workflow_inputs": {
+ "name": "workflow_inputs",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "machine_id": {
+ "name": "machine_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "origin": {
+ "name": "origin",
+ "type": "workflow_run_origin",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'api'"
+ },
+ "status": {
+ "name": "status",
+ "type": "workflow_run_status",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'not-started'"
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflow_runs_workflow_version_id_workflow_versions_id_fk": {
+ "name": "workflow_runs_workflow_version_id_workflow_versions_id_fk",
+ "tableFrom": "workflow_runs",
+ "tableTo": "workflow_versions",
+ "columnsFrom": [
+ "workflow_version_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "workflow_runs_workflow_id_workflows_id_fk": {
+ "name": "workflow_runs_workflow_id_workflows_id_fk",
+ "tableFrom": "workflow_runs",
+ "tableTo": "workflows",
+ "columnsFrom": [
+ "workflow_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_runs_machine_id_machines_id_fk": {
+ "name": "workflow_runs_machine_id_machines_id_fk",
+ "tableFrom": "workflow_runs",
+ "tableTo": "machines",
+ "columnsFrom": [
+ "machine_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "workflows": {
+ "name": "workflows",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflows_user_id_users_id_fk": {
+ "name": "workflows_user_id_users_id_fk",
+ "tableFrom": "workflows",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "workflow_versions": {
+ "name": "workflow_versions",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workflow": {
+ "name": "workflow",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workflow_api": {
+ "name": "workflow_api",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "snapshot": {
+ "name": "snapshot",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflow_versions_workflow_id_workflows_id_fk": {
+ "name": "workflow_versions_workflow_id_workflows_id_fk",
+ "tableFrom": "workflow_versions",
+ "tableTo": "workflows",
+ "columnsFrom": [
+ "workflow_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ }
+ },
+ "enums": {
+ "deployment_environment": {
+ "name": "deployment_environment",
+ "values": {
+ "staging": "staging",
+ "production": "production",
+ "public-share": "public-share"
+ }
+ },
+ "machine_gpu": {
+ "name": "machine_gpu",
+ "values": {
+ "T4": "T4",
+ "A10G": "A10G",
+ "A100": "A100"
+ }
+ },
+ "machine_status": {
+ "name": "machine_status",
+ "values": {
+ "ready": "ready",
+ "building": "building",
+ "error": "error"
+ }
+ },
+ "machine_type": {
+ "name": "machine_type",
+ "values": {
+ "classic": "classic",
+ "runpod-serverless": "runpod-serverless",
+ "modal-serverless": "modal-serverless",
+ "comfy-deploy-serverless": "comfy-deploy-serverless"
+ }
+ },
+ "workflow_run_origin": {
+ "name": "workflow_run_origin",
+ "values": {
+ "manual": "manual",
+ "api": "api",
+ "public-share": "public-share"
+ }
+ },
+ "workflow_run_status": {
+ "name": "workflow_run_status",
+ "values": {
+ "not-started": "not-started",
+ "running": "running",
+ "uploading": "uploading",
+ "success": "success",
+ "failed": "failed"
+ }
+ }
+ },
+ "schemas": {
+ "comfyui_deploy": "comfyui_deploy"
+ },
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ }
+}
\ No newline at end of file
diff --git a/web/drizzle/meta/0034_snapshot.json b/web/drizzle/meta/0034_snapshot.json
new file mode 100644
index 0000000..2337aab
--- /dev/null
+++ b/web/drizzle/meta/0034_snapshot.json
@@ -0,0 +1,830 @@
+{
+ "id": "8d654f92-7f7e-420f-bbd3-73b6b27adf35",
+ "prevId": "97662b25-3992-4859-9bdc-560e2a70daea",
+ "version": "5",
+ "dialect": "pg",
+ "tables": {
+ "api_keys": {
+ "name": "api_keys",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revoked": {
+ "name": "revoked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "api_keys_user_id_users_id_fk": {
+ "name": "api_keys_user_id_users_id_fk",
+ "tableFrom": "api_keys",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "api_keys_key_unique": {
+ "name": "api_keys_key_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "key"
+ ]
+ }
+ }
+ },
+ "auth_requests": {
+ "name": "auth_requests",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "request_id": {
+ "name": "request_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "api_hash": {
+ "name": "api_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expired_date": {
+ "name": "expired_date",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "deployments": {
+ "name": "deployments",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workflow_version_id": {
+ "name": "workflow_version_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "machine_id": {
+ "name": "machine_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "share_slug": {
+ "name": "share_slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "showcase_media": {
+ "name": "showcase_media",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "environment": {
+ "name": "environment",
+ "type": "deployment_environment",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "deployments_user_id_users_id_fk": {
+ "name": "deployments_user_id_users_id_fk",
+ "tableFrom": "deployments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployments_workflow_version_id_workflow_versions_id_fk": {
+ "name": "deployments_workflow_version_id_workflow_versions_id_fk",
+ "tableFrom": "deployments",
+ "tableTo": "workflow_versions",
+ "columnsFrom": [
+ "workflow_version_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "deployments_workflow_id_workflows_id_fk": {
+ "name": "deployments_workflow_id_workflows_id_fk",
+ "tableFrom": "deployments",
+ "tableTo": "workflows",
+ "columnsFrom": [
+ "workflow_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "deployments_machine_id_machines_id_fk": {
+ "name": "deployments_machine_id_machines_id_fk",
+ "tableFrom": "deployments",
+ "tableTo": "machines",
+ "columnsFrom": [
+ "machine_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "deployments_share_slug_unique": {
+ "name": "deployments_share_slug_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "share_slug"
+ ]
+ }
+ }
+ },
+ "machines": {
+ "name": "machines",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "endpoint": {
+ "name": "endpoint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "disabled": {
+ "name": "disabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "auth_token": {
+ "name": "auth_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "type": {
+ "name": "type",
+ "type": "machine_type",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'classic'"
+ },
+ "status": {
+ "name": "status",
+ "type": "machine_status",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'ready'"
+ },
+ "snapshot": {
+ "name": "snapshot",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "models": {
+ "name": "models",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "gpu": {
+ "name": "gpu",
+ "type": "machine_gpu",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "build_machine_instance_id": {
+ "name": "build_machine_instance_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "build_log": {
+ "name": "build_log",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "machines_user_id_users_id_fk": {
+ "name": "machines_user_id_users_id_fk",
+ "tableFrom": "machines",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "users": {
+ "name": "users",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "workflow_run_outputs": {
+ "name": "workflow_run_outputs",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "data": {
+ "name": "data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflow_run_outputs_run_id_workflow_runs_id_fk": {
+ "name": "workflow_run_outputs_run_id_workflow_runs_id_fk",
+ "tableFrom": "workflow_run_outputs",
+ "tableTo": "workflow_runs",
+ "columnsFrom": [
+ "run_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "workflow_runs": {
+ "name": "workflow_runs",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workflow_version_id": {
+ "name": "workflow_version_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workflow_inputs": {
+ "name": "workflow_inputs",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "machine_id": {
+ "name": "machine_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "origin": {
+ "name": "origin",
+ "type": "workflow_run_origin",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'api'"
+ },
+ "status": {
+ "name": "status",
+ "type": "workflow_run_status",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'not-started'"
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflow_runs_workflow_version_id_workflow_versions_id_fk": {
+ "name": "workflow_runs_workflow_version_id_workflow_versions_id_fk",
+ "tableFrom": "workflow_runs",
+ "tableTo": "workflow_versions",
+ "columnsFrom": [
+ "workflow_version_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "workflow_runs_workflow_id_workflows_id_fk": {
+ "name": "workflow_runs_workflow_id_workflows_id_fk",
+ "tableFrom": "workflow_runs",
+ "tableTo": "workflows",
+ "columnsFrom": [
+ "workflow_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_runs_machine_id_machines_id_fk": {
+ "name": "workflow_runs_machine_id_machines_id_fk",
+ "tableFrom": "workflow_runs",
+ "tableTo": "machines",
+ "columnsFrom": [
+ "machine_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "workflows": {
+ "name": "workflows",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "org_id": {
+ "name": "org_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflows_user_id_users_id_fk": {
+ "name": "workflows_user_id_users_id_fk",
+ "tableFrom": "workflows",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "workflow_versions": {
+ "name": "workflow_versions",
+ "schema": "comfyui_deploy",
+ "columns": {
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "workflow": {
+ "name": "workflow",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workflow_api": {
+ "name": "workflow_api",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "snapshot": {
+ "name": "snapshot",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workflow_versions_workflow_id_workflows_id_fk": {
+ "name": "workflow_versions_workflow_id_workflows_id_fk",
+ "tableFrom": "workflow_versions",
+ "tableTo": "workflows",
+ "columnsFrom": [
+ "workflow_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ }
+ },
+ "enums": {
+ "deployment_environment": {
+ "name": "deployment_environment",
+ "values": {
+ "staging": "staging",
+ "production": "production",
+ "public-share": "public-share"
+ }
+ },
+ "machine_gpu": {
+ "name": "machine_gpu",
+ "values": {
+ "T4": "T4",
+ "A10G": "A10G",
+ "A100": "A100"
+ }
+ },
+ "machine_status": {
+ "name": "machine_status",
+ "values": {
+ "ready": "ready",
+ "building": "building",
+ "error": "error"
+ }
+ },
+ "machine_type": {
+ "name": "machine_type",
+ "values": {
+ "classic": "classic",
+ "runpod-serverless": "runpod-serverless",
+ "modal-serverless": "modal-serverless",
+ "comfy-deploy-serverless": "comfy-deploy-serverless"
+ }
+ },
+ "workflow_run_origin": {
+ "name": "workflow_run_origin",
+ "values": {
+ "manual": "manual",
+ "api": "api",
+ "public-share": "public-share"
+ }
+ },
+ "workflow_run_status": {
+ "name": "workflow_run_status",
+ "values": {
+ "not-started": "not-started",
+ "running": "running",
+ "uploading": "uploading",
+ "success": "success",
+ "failed": "failed"
+ }
+ }
+ },
+ "schemas": {
+ "comfyui_deploy": "comfyui_deploy"
+ },
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ }
+}
\ No newline at end of file
diff --git a/web/drizzle/meta/_journal.json b/web/drizzle/meta/_journal.json
index a262f43..3fbae3d 100644
--- a/web/drizzle/meta/_journal.json
+++ b/web/drizzle/meta/_journal.json
@@ -232,6 +232,20 @@
"when": 1705806921697,
"tag": "0032_shallow_vermin",
"breakpoints": true
+ },
+ {
+ "idx": 33,
+ "version": "5",
+ "when": 1705853314500,
+ "tag": "0033_awesome_human_fly",
+ "breakpoints": true
+ },
+ {
+ "idx": 34,
+ "version": "5",
+ "when": 1705902960991,
+ "tag": "0034_even_lady_ursula",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/web/migrate.mts b/web/migrate.mts
index 5f4df12..972740d 100644
--- a/web/migrate.mts
+++ b/web/migrate.mts
@@ -14,8 +14,12 @@ if (sslMode === "false") sslMode = false;
let connectionString = process.env.POSTGRES_URL!;
-const isDevContainer = process.env.VSCODE_DEV_CONTAINER !== undefined;
-if (isDevContainer) connectionString = connectionString.replace("localhost","host.docker.internal")
+const isDevContainer = process.env.REMOTE_CONTAINERS !== undefined;
+if (isDevContainer)
+ connectionString = connectionString.replace(
+ "localhost",
+ "host.docker.internal",
+ );
const sql = postgres(connectionString, { max: 1, ssl: sslMode as any });
const db = drizzle(sql, {
@@ -23,16 +27,16 @@ const db = drizzle(sql, {
});
let retries = 5;
-while(retries) {
+while (retries) {
try {
await sql`SELECT NOW()`;
- console.log('Database is live');
+ console.log("Database is live");
break;
} catch (error) {
- console.error('Database is not live yet', error);
+ console.error("Database is not live yet", error);
retries -= 1;
console.log(`Retries left: ${retries}`);
- await new Promise(res => setTimeout(res, 1000));
+ await new Promise((res) => setTimeout(res, 1000));
}
}
diff --git a/web/package.json b/web/package.json
index 588cbb8..faf351d 100644
--- a/web/package.json
+++ b/web/package.json
@@ -72,6 +72,7 @@
"mdx-annotations": "^0.1.4",
"million": "latest",
"mitata": "^0.1.6",
+ "ms": "^2.1.3",
"nanoid": "^5.0.4",
"next": "14.1",
"next-plausible": "^3.12.0",
diff --git a/web/src/app/(app)/api/[[...routes]]/route.ts b/web/src/app/(app)/api/[[...routes]]/route.ts
index ea42f63..e8c7cdd 100644
--- a/web/src/app/(app)/api/[[...routes]]/route.ts
+++ b/web/src/app/(app)/api/[[...routes]]/route.ts
@@ -1,4 +1,3 @@
-import { app } from "../../../../routes/app";
import { registerCreateRunRoute } from "@/routes/registerCreateRunRoute";
import { registerGetOutputRoute } from "@/routes/registerGetOutputRoute";
import { registerUploadRoute } from "@/routes/registerUploadRoute";
@@ -6,6 +5,9 @@ import { isKeyRevoked } from "@/server/curdApiKeys";
import { parseJWT } from "@/server/parseJWT";
import type { Context, Next } from "hono";
import { handle } from "hono/vercel";
+import { app } from "../../../../routes/app";
+import { registerWorkflowUploadRoute } from "@/routes/registerWorkflowUploadRoute";
+import { registerGetAuthResponse } from "@/routes/registerGetAuthResponse";
export const dynamic = "force-dynamic";
export const maxDuration = 300; // 5 minutes
@@ -21,7 +23,10 @@ async function checkAuth(c: Context, next: Next) {
const userData = token ? parseJWT(token) : undefined;
if (!userData || token === undefined) {
return c.text("Invalid or expired token", 401);
- } else {
+ }
+
+ // If the key has expiration, this is a temporary key and not in our db, so we can skip checking
+ if (userData.exp === undefined) {
const revokedKey = await isKeyRevoked(token);
if (revokedKey) return c.text("Revoked token", 401);
}
@@ -31,18 +36,20 @@ async function checkAuth(c: Context, next: Next) {
await next();
}
-app.use("/run", async (c, next) => {
- return checkAuth(c, next);
-});
-
-app.use("/upload-url", async (c, next) => {
- return checkAuth(c, next);
-});
+app.use("/run", checkAuth);
+app.use("/upload-url", checkAuth);
+app.use("/upload-workflow", checkAuth);
+// create run endpoint
registerCreateRunRoute(app);
registerGetOutputRoute(app);
+
+// file upload endpoint
registerUploadRoute(app);
+registerWorkflowUploadRoute(app);
+registerGetAuthResponse(app);
+
// The OpenAPI documentation will be available at /doc
app.doc("/doc", {
openapi: "3.0.0",
diff --git a/web/src/app/(app)/api/file-upload/route.ts b/web/src/app/(app)/api/file-upload/route.ts
index 8f5ba7b..f132181 100644
--- a/web/src/app/(app)/api/file-upload/route.ts
+++ b/web/src/app/(app)/api/file-upload/route.ts
@@ -1,11 +1,12 @@
-import { parseDataSafe } from "../../../../lib/parseDataSafe";
import { handleResourceUpload } from "@/server/resource";
import { NextResponse } from "next/server";
import { z } from "zod";
+import { parseDataSafe } from "../../../../lib/parseDataSafe";
const Request = z.object({
file_name: z.string(),
run_id: z.string(),
+
type: z.string(),
});
@@ -29,7 +30,7 @@ export async function GET(request: Request) {
{
url: uploadUrl,
},
- { status: 200 }
+ { status: 200 },
);
} catch (error: unknown) {
const errorMessage =
@@ -38,7 +39,7 @@ export async function GET(request: Request) {
{
error: errorMessage,
},
- { status: 500 }
+ { status: 500 },
);
}
}
diff --git a/web/src/app/(app)/api/upload/route.ts b/web/src/app/(app)/api/upload/route.ts
index 932169b..2a98b40 100644
--- a/web/src/app/(app)/api/upload/route.ts
+++ b/web/src/app/(app)/api/upload/route.ts
@@ -1,17 +1,14 @@
-import { createNewWorkflow } from "../../../../server/createNewWorkflow";
-import { parseJWT } from "../../../../server/parseJWT";
-import { db } from "@/db/db";
-import {
- snapshotType,
- workflowAPIType,
- workflowTable,
- workflowType,
- workflowVersionTable,
-} from "@/db/schema";
+import { snapshotType, workflowAPIType, workflowType } from "@/db/schema";
import { parseDataSafe } from "@/lib/parseDataSafe";
-import { eq, sql } from "drizzle-orm";
import { NextResponse } from "next/server";
import { z } from "zod";
+import {
+ createNewWorkflow,
+ createNewWorkflowVersion,
+} from "../../../../server/createNewWorkflow";
+import { parseJWT } from "../../../../server/parseJWT";
+
+// This is will be deprecated
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
@@ -55,7 +52,7 @@ export async function POST(request: Request) {
const [data, error] = await parseDataSafe(
UploadRequest,
request,
- corsHeaders
+ corsHeaders,
);
if (!data || error) return error;
@@ -75,7 +72,7 @@ export async function POST(request: Request) {
// Case 1 new workflow
try {
- if ((!workflow_id || workflow_id.length == 0) && workflow_name) {
+ if ((!workflow_id || workflow_id.length === 0) && workflow_name) {
// Create a new parent workflow
const { workflow_id: _workflow_id, version: _version } =
await createNewWorkflow({
@@ -91,56 +88,17 @@ export async function POST(request: Request) {
workflow_id = _workflow_id;
version = _version;
-
- // const workflow_parent = await db
- // .insert(workflowTable)
- // .values({
- // user_id,
- // name: workflow_name,
- // org_id: org_id,
- // })
- // .returning();
-
- // workflow_id = workflow_parent[0].id;
-
- // // Create a new version
- // const data = await db
- // .insert(workflowVersionTable)
- // .values({
- // workflow_id: workflow_id,
- // workflow,
- // workflow_api,
- // version: 1,
- // snapshot: snapshot,
- // })
- // .returning();
- // version = data[0].version;
} else if (workflow_id) {
// Case 2 update workflow
- const data = await db
- .insert(workflowVersionTable)
- .values({
- workflow_id,
- workflow: workflow,
+ const { version: _version } = await createNewWorkflowVersion({
+ workflow_id: workflow_id,
+ workflowData: {
+ workflow,
workflow_api,
- // version: sql`${workflowVersionTable.version} + 1`,
- snapshot: snapshot,
- version: sql`(
- SELECT COALESCE(MAX(version), 0) + 1
- FROM ${workflowVersionTable}
- WHERE workflow_id = ${workflow_id}
- )`,
- })
- .returning();
- version = data[0].version;
-
- await db
- .update(workflowTable)
- .set({
- updated_at: new Date(),
- })
- .where(eq(workflowTable.id, workflow_id))
- .returning();
+ snapshot,
+ },
+ });
+ version = _version;
} else {
return NextResponse.json(
{
@@ -150,7 +108,7 @@ export async function POST(request: Request) {
status: 500,
statusText: "Invalid request",
headers: corsHeaders,
- }
+ },
);
}
} catch (error: any) {
@@ -162,7 +120,7 @@ export async function POST(request: Request) {
status: 500,
statusText: "Invalid request",
headers: corsHeaders,
- }
+ },
);
}
@@ -174,6 +132,6 @@ export async function POST(request: Request) {
{
status: 200,
headers: corsHeaders,
- }
+ },
);
}
diff --git a/web/src/app/(app)/auth-request/[request_id]/page.tsx b/web/src/app/(app)/auth-request/[request_id]/page.tsx
new file mode 100644
index 0000000..a419b96
--- /dev/null
+++ b/web/src/app/(app)/auth-request/[request_id]/page.tsx
@@ -0,0 +1,54 @@
+import { ButtonAction } from "@/components/ButtonActionLoader";
+import { Button } from "@/components/ui/button";
+import { createAuthRequest } from "@/server/curdApiKeys";
+import { auth } from "@clerk/nextjs";
+import { redirect } from "next/navigation";
+import { getOrgOrUserDisplayName } from "../../../../server/getOrgOrUserDisplayName";
+import { db } from "@/db/db";
+import { eq } from "drizzle-orm";
+import { authRequestsTable } from "@/db/schema";
+
+export default async function Home({
+ params,
+}: {
+ params: { request_id: string };
+}) {
+ const { userId, orgId } = await auth();
+
+ if (!userId) redirect("/");
+
+ if (!params.request_id)
+ return (
+
+ No valid request_id
+
+ );
+
+ const existingResult = await db.query.authRequestsTable.findFirst({
+ where: eq(authRequestsTable.request_id, params.request_id),
+ });
+
+ if (existingResult?.api_hash) {
+ return (
+
+ Request already consumed.
+
+ );
+ }
+
+ const userName = await getOrgOrUserDisplayName(orgId, userId);
+
+ return (
+
+
Grant API Access to {userName}
+
+
+ );
+}
diff --git a/web/src/components/ButtonActionLoader.tsx b/web/src/components/ButtonActionLoader.tsx
index 568e87d..5edb68e 100644
--- a/web/src/components/ButtonActionLoader.tsx
+++ b/web/src/components/ButtonActionLoader.tsx
@@ -4,10 +4,10 @@ import { LoadingIcon } from "@/components/LoadingIcon";
import { callServerPromise } from "@/components/callServerPromise";
import { Button } from "@/components/ui/button";
import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useAuth, useClerk } from "@clerk/nextjs";
import { MoreVertical } from "lucide-react";
@@ -15,80 +15,79 @@ import { useRouter } from "next/navigation";
import { useState } from "react";
export function ButtonAction({
- action,
- children,
- routerAction = "back",
- ...rest
+ action,
+ children,
+ routerAction = "back",
+ ...rest
}: {
- action: () => Promise;
- routerAction?: "refresh" | "back";
- children: React.ReactNode;
+ action: () => Promise;
+ routerAction?: "refresh" | "back" | "do-nothing";
+ children: React.ReactNode;
}) {
- const [pending, setPending] = useState(false);
- const router = useRouter();
+ const [pending, setPending] = useState(false);
+ const router = useRouter();
- return (
-
+ );
}
export function ButtonActionMenu(props: {
- title?: string;
- actions: {
- title: string;
- action: () => Promise;
- }[];
+ title?: string;
+ actions: {
+ title: string;
+ action: () => Promise;
+ }[];
}) {
- const user = useAuth();
- const [isLoading, setIsLoading] = useState(false);
- const clerk = useClerk();
+ const user = useAuth();
+ const [isLoading, setIsLoading] = useState(false);
+ const clerk = useClerk();
- return (
-
-
-
- {props.title}
- {isLoading ? : }
-
-
-
- {props.actions.map((action) => (
- {
- if (!user.isSignedIn) {
- clerk.openSignIn({
- redirectUrl: window.location.href,
- });
- return;
- }
+ return (
+
+
+
+ {props.title}
+ {isLoading ? : }
+
+
+
+ {props.actions.map((action) => (
+ {
+ if (!user.isSignedIn) {
+ clerk.openSignIn({
+ redirectUrl: window.location.href,
+ });
+ return;
+ }
- setIsLoading(true);
- await callServerPromise(action.action());
- setIsLoading(false);
- }}
- >
- {action.title}
-
- ))}
-
-
- );
+ setIsLoading(true);
+ await callServerPromise(action.action());
+ setIsLoading(false);
+ }}
+ >
+ {action.title}
+
+ ))}
+
+
+ );
}
diff --git a/web/src/db/db.ts b/web/src/db/db.ts
index 33aa553..2cd8d5d 100644
--- a/web/src/db/db.ts
+++ b/web/src/db/db.ts
@@ -1,6 +1,6 @@
-import * as schema from "./schema";
-import { neonConfig, Pool } from "@neondatabase/serverless";
+import { Pool, neonConfig } from "@neondatabase/serverless";
import { drizzle as neonDrizzle } from "drizzle-orm/neon-serverless";
+import * as schema from "./schema";
const isDevContainer = process.env.REMOTE_CONTAINERS !== undefined;
@@ -9,7 +9,7 @@ if (process.env.VERCEL_ENV !== "production") {
// Set the WebSocket proxy to work with the local instance
if (isDevContainer) {
// Running inside a VS Code devcontainer
- neonConfig.wsProxy = (host) => `host.docker.internal:5481/v1`;
+ neonConfig.wsProxy = (host) => "host.docker.internal:5481/v1";
} else {
// Not running inside a VS Code devcontainer
neonConfig.wsProxy = (host) => `${host}:5481/v1`;
@@ -26,5 +26,5 @@ export const db = neonDrizzle(
}),
{
schema,
- }
+ },
);
diff --git a/web/src/db/schema.ts b/web/src/db/schema.ts
index 20372ff..02bbedf 100644
--- a/web/src/db/schema.ts
+++ b/web/src/db/schema.ts
@@ -1,13 +1,13 @@
import { type InferSelectModel, relations } from "drizzle-orm";
import {
- boolean,
- integer,
- jsonb,
- pgEnum,
- pgSchema,
- text,
- timestamp,
- uuid,
+ boolean,
+ integer,
+ jsonb,
+ pgEnum,
+ pgSchema,
+ text,
+ timestamp,
+ uuid,
} from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { z } from "zod";
@@ -87,7 +87,7 @@ export const workflowVersionRelations = relations(
fields: [workflowVersionTable.workflow_id],
references: [workflowTable.id],
}),
- })
+ }),
);
export const workflowRunStatus = pgEnum("workflow_run_status", [
@@ -130,30 +130,30 @@ export const machinesStatus = pgEnum("machine_status", [
// We still want to keep the workflow run record.
export const workflowRunsTable = dbSchema.table("workflow_runs", {
- id: uuid("id").primaryKey().defaultRandom().notNull(),
- // when workflow version deleted, still want to keep this record
- workflow_version_id: uuid("workflow_version_id").references(
- () => workflowVersionTable.id,
- {
- onDelete: "set null",
- },
- ),
- workflow_inputs:
- jsonb("workflow_inputs").$type>(),
- workflow_id: uuid("workflow_id")
- .notNull()
- .references(() => workflowTable.id, {
- onDelete: "cascade",
- }),
- // when machine deleted, still want to keep this record
- machine_id: uuid("machine_id").references(() => machinesTable.id, {
- onDelete: "set null",
- }),
- origin: workflowRunOrigin("origin").notNull().default("api"),
- status: workflowRunStatus("status").notNull().default("not-started"),
- ended_at: timestamp("ended_at"),
- created_at: timestamp("created_at").defaultNow().notNull(),
- started_at: timestamp("started_at"),
+ id: uuid("id").primaryKey().defaultRandom().notNull(),
+ // when workflow version deleted, still want to keep this record
+ workflow_version_id: uuid("workflow_version_id").references(
+ () => workflowVersionTable.id,
+ {
+ onDelete: "set null",
+ },
+ ),
+ workflow_inputs:
+ jsonb("workflow_inputs").$type>(),
+ workflow_id: uuid("workflow_id")
+ .notNull()
+ .references(() => workflowTable.id, {
+ onDelete: "cascade",
+ }),
+ // when machine deleted, still want to keep this record
+ machine_id: uuid("machine_id").references(() => machinesTable.id, {
+ onDelete: "set null",
+ }),
+ origin: workflowRunOrigin("origin").notNull().default("api"),
+ status: workflowRunStatus("status").notNull().default("not-started"),
+ ended_at: timestamp("ended_at"),
+ created_at: timestamp("created_at").defaultNow().notNull(),
+ started_at: timestamp("started_at"),
});
export const workflowRunRelations = relations(
@@ -172,7 +172,7 @@ export const workflowRunRelations = relations(
fields: [workflowRunsTable.workflow_id],
references: [workflowTable.id],
}),
- })
+ }),
);
// We still want to keep the workflow run record.
@@ -196,7 +196,7 @@ export const workflowOutputRelations = relations(
fields: [workflowRunOutputs.run_id],
references: [workflowRunsTable.id],
}),
- })
+ }),
);
// when user delete, also delete all the workflow versions
@@ -229,7 +229,7 @@ export const snapshotType = z.object({
z.object({
hash: z.string(),
disabled: z.boolean(),
- })
+ }),
),
file_custom_nodes: z.array(z.any()),
});
@@ -244,7 +244,7 @@ export const showcaseMedia = z.array(
z.object({
url: z.string(),
isCover: z.boolean().default(false),
- })
+ }),
);
export const showcaseMediaNullable = z
@@ -252,36 +252,36 @@ export const showcaseMediaNullable = z
z.object({
url: z.string(),
isCover: z.boolean().default(false),
- })
+ }),
)
.nullable();
export const deploymentsTable = dbSchema.table("deployments", {
- id: uuid("id").primaryKey().defaultRandom().notNull(),
- user_id: text("user_id")
- .references(() => usersTable.id, {
- onDelete: "cascade",
- })
- .notNull(),
- org_id: text("org_id"),
- workflow_version_id: uuid("workflow_version_id")
- .notNull()
- .references(() => workflowVersionTable.id),
- workflow_id: uuid("workflow_id")
- .notNull()
- .references(() => workflowTable.id, {
- onDelete: "cascade",
- }),
- machine_id: uuid("machine_id")
- .notNull()
- .references(() => machinesTable.id),
- share_slug: text("share_slug").unique(),
- description: text("description"),
- showcase_media:
- jsonb("showcase_media").$type>(),
- environment: deploymentEnvironment("environment").notNull(),
- created_at: timestamp("created_at").defaultNow().notNull(),
- updated_at: timestamp("updated_at").defaultNow().notNull(),
+ id: uuid("id").primaryKey().defaultRandom().notNull(),
+ user_id: text("user_id")
+ .references(() => usersTable.id, {
+ onDelete: "cascade",
+ })
+ .notNull(),
+ org_id: text("org_id"),
+ workflow_version_id: uuid("workflow_version_id")
+ .notNull()
+ .references(() => workflowVersionTable.id),
+ workflow_id: uuid("workflow_id")
+ .notNull()
+ .references(() => workflowTable.id, {
+ onDelete: "cascade",
+ }),
+ machine_id: uuid("machine_id")
+ .notNull()
+ .references(() => machinesTable.id),
+ share_slug: text("share_slug").unique(),
+ description: text("description"),
+ showcase_media:
+ jsonb("showcase_media").$type>(),
+ environment: deploymentEnvironment("environment").notNull(),
+ created_at: timestamp("created_at").defaultNow().notNull(),
+ updated_at: timestamp("updated_at").defaultNow().notNull(),
});
export const publicShareDeployment = z.object({
@@ -331,6 +331,16 @@ export const apiKeyTable = dbSchema.table("api_keys", {
updated_at: timestamp("updated_at").defaultNow().notNull(),
});
+export const authRequestsTable = dbSchema.table("auth_requests", {
+ request_id: text("request_id").primaryKey().notNull(),
+ user_id: text("user_id"),
+ org_id: text("org_id"),
+ api_hash: text("api_hash"),
+ created_at: timestamp("created_at").defaultNow().notNull(),
+ expired_date: timestamp("expired_date"),
+ updated_at: timestamp("updated_at").defaultNow().notNull(),
+});
+
export type UserType = InferSelectModel;
export type WorkflowType = InferSelectModel;
export type MachineType = InferSelectModel;
diff --git a/web/src/routes/newId.ts b/web/src/routes/newId.ts
new file mode 100644
index 0000000..6c3d144
--- /dev/null
+++ b/web/src/routes/newId.ts
@@ -0,0 +1,13 @@
+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("_");
+}
diff --git a/web/src/routes/registerCreateRunRoute.ts b/web/src/routes/registerCreateRunRoute.ts
index 376c8d4..98143eb 100644
--- a/web/src/routes/registerCreateRunRoute.ts
+++ b/web/src/routes/registerCreateRunRoute.ts
@@ -1,10 +1,10 @@
-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 { createRoute, z } from "@hono/zod-openapi";
import { eq } from "drizzle-orm";
+import { createRun } from "../server/createRun";
const createRunRoute = createRoute({
method: "post",
@@ -99,7 +99,7 @@ export const registerCreateRunRoute = (app: App) => {
},
{
status: 500,
- }
+ },
);
}
});
diff --git a/web/src/routes/registerGetAuthResponse.ts b/web/src/routes/registerGetAuthResponse.ts
new file mode 100644
index 0000000..196dfbb
--- /dev/null
+++ b/web/src/routes/registerGetAuthResponse.ts
@@ -0,0 +1,150 @@
+import { db } from "@/db/db";
+import { authRequestsTable } 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";
+import jwt from "jsonwebtoken";
+import crypto from "crypto";
+import { getOrgOrUserDisplayName } from "@/server/getOrgOrUserDisplayName";
+import ms from "ms";
+
+const route = createRoute({
+ method: "get",
+ path: "/auth-response/:request_id",
+ tags: ["comfyui"],
+ summary: "Get an API Key with code",
+ description:
+ "This endpoints is specifically built for ComfyUI workflow upload.",
+ request: {
+ params: z.object({
+ request_id: z.string(),
+ }),
+ },
+ responses: {
+ 200: {
+ content: {
+ "application/json": {
+ schema: z.object({
+ api_key: z.string(),
+ name: z.string(),
+ }),
+ },
+ },
+ description: "The returned API Key",
+ },
+ 201: {
+ content: {
+ "application/json": {
+ schema: z.object({
+ message: z.string(),
+ }),
+ },
+ },
+ description: "The API key is not yet ready",
+ },
+ 500: {
+ content: {
+ "application/json": {
+ schema: z.object({
+ error: z.string(),
+ }),
+ },
+ },
+ description: "Error when fetching the API Key with code",
+ },
+ ...authError,
+ },
+});
+
+const corsHeaders = {
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Methods": "GET, OPTIONS",
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
+};
+
+export const registerGetAuthResponse = (app: App) => {
+ return app.openapi(route, async (c) => {
+ const { request_id } = c.req.valid("param");
+
+ try {
+ const result = await db.query.authRequestsTable.findFirst({
+ where: eq(authRequestsTable.request_id, request_id),
+ });
+
+ if (result?.api_hash) {
+ return c.json(
+ {
+ message: "Already used.",
+ },
+ {
+ status: 201,
+ headers: corsHeaders,
+ },
+ );
+ }
+
+ if (result && result.user_id) {
+ const expireTime = "1w";
+ const token = jwt.sign(
+ { user_id: result.user_id, org_id: result.org_id },
+ process.env.JWT_SECRET!,
+ {
+ expiresIn: expireTime,
+ },
+ );
+
+ const hash = crypto.createHash("sha256").update(token).digest("hex");
+
+ const now = new Date();
+ const expiryDate = new Date(now.getTime() + ms(expireTime));
+
+ await db
+ .update(authRequestsTable)
+ .set({
+ api_hash: hash,
+ expired_date: expiryDate,
+ })
+ .where(eq(authRequestsTable.request_id, request_id));
+
+ const userName = await getOrgOrUserDisplayName(
+ result.org_id,
+ result.user_id,
+ );
+
+ return c.json(
+ {
+ api_key: token,
+ name: userName,
+ },
+ {
+ status: 200,
+ headers: corsHeaders,
+ },
+ );
+ }
+ } catch (error: unknown) {
+ const errorMessage =
+ error instanceof Error ? error.message : "Unknown error";
+ return c.json(
+ {
+ error: errorMessage,
+ },
+ {
+ statusText: "Invalid request",
+ status: 500,
+ headers: corsHeaders,
+ },
+ );
+ }
+ return c.json(
+ {
+ message: "Not ready yet.",
+ },
+ {
+ status: 201,
+ headers: corsHeaders,
+ },
+ );
+ });
+};
diff --git a/web/src/routes/registerUploadRoute.ts b/web/src/routes/registerUploadRoute.ts
index b58d76b..d318b66 100644
--- a/web/src/routes/registerUploadRoute.ts
+++ b/web/src/routes/registerUploadRoute.ts
@@ -3,20 +3,7 @@ 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("_");
-}
+import { newId } from "./newId";
const uploadUrlRoute = createRoute({
method: "get",
@@ -96,7 +83,7 @@ export const registerUploadRoute = (app: App) => {
file_id: id,
download_url: await getFileDownloadUrl(filePath),
},
- 200
+ 200,
);
} catch (error: unknown) {
const errorMessage =
@@ -107,7 +94,7 @@ export const registerUploadRoute = (app: App) => {
},
{
status: 500,
- }
+ },
);
}
});
diff --git a/web/src/routes/registerWorkflowUploadRoute.ts b/web/src/routes/registerWorkflowUploadRoute.ts
new file mode 100644
index 0000000..b6fe96d
--- /dev/null
+++ b/web/src/routes/registerWorkflowUploadRoute.ts
@@ -0,0 +1,164 @@
+import { snapshotType, workflowAPIType, workflowType } from "@/db/schema";
+import type { App } from "@/routes/app";
+import { authError } from "@/routes/authError";
+import {
+ createNewWorkflow,
+ createNewWorkflowVersion,
+} from "@/server/createNewWorkflow";
+import { z, createRoute } from "@hono/zod-openapi";
+
+const route = createRoute({
+ method: "post",
+ path: "/upload-workflow",
+ tags: ["comfyui"],
+ summary: "Upload workflow from ComfyUI",
+ description:
+ "This endpoints is specifically built for ComfyUI workflow upload.",
+ request: {
+ body: {
+ content: {
+ "application/json": {
+ schema: z.object({
+ workflow_id: z.string().optional(),
+ workflow_name: z.string().min(1).optional(),
+ workflow: workflowType,
+ workflow_api: workflowAPIType,
+ snapshot: snapshotType,
+ }),
+ },
+ },
+ },
+ },
+ responses: {
+ 200: {
+ content: {
+ "application/json": {
+ schema: z.object({
+ workflow_id: z.string(),
+ version: z.string(),
+ }),
+ },
+ },
+ description: "Retrieve the output",
+ },
+ 500: {
+ content: {
+ "application/json": {
+ schema: z.object({
+ error: z.string(),
+ }),
+ },
+ },
+ description: "Error when uploading the workflow",
+ },
+ ...authError,
+ },
+});
+
+const corsHeaders = {
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
+};
+
+export const registerWorkflowUploadRoute = (app: App) => {
+ app.openapi(route, async (c) => {
+ const {
+ // user_id,
+ workflow,
+ workflow_api,
+ workflow_id: _workflow_id,
+ workflow_name,
+ snapshot,
+ } = c.req.valid("json");
+ const { org_id, user_id } = c.get("apiKeyTokenData")!;
+
+ if (!user_id)
+ return c.json(
+ {
+ error: "Invalid user_id",
+ },
+ {
+ headers: corsHeaders,
+ status: 500,
+ },
+ );
+
+ let workflow_id = _workflow_id;
+
+ let version = -1;
+
+ try {
+ if ((!workflow_id || workflow_id.length === 0) && workflow_name) {
+ // Create a new parent workflow
+ const { workflow_id: _workflow_id, version: _version } =
+ await createNewWorkflow({
+ user_id: user_id,
+ org_id: org_id,
+ workflow_name: workflow_name,
+ workflowData: {
+ workflow,
+ workflow_api,
+ snapshot,
+ },
+ });
+
+ workflow_id = _workflow_id;
+ version = _version;
+ } else if (workflow_id) {
+ // Case 2 update workflow
+ const { version: _version } = await createNewWorkflowVersion({
+ workflow_id: workflow_id,
+ workflowData: {
+ workflow,
+ workflow_api,
+ snapshot,
+ },
+ });
+ version = _version;
+ } else {
+ return c.json(
+ {
+ error: "Invalid request, missing either workflow_id or name",
+ },
+ {
+ status: 500,
+ statusText: "Invalid request",
+ headers: corsHeaders,
+ },
+ );
+ }
+ } catch (error: unknown) {
+ const errorMessage =
+ error instanceof Error ? error.message : "Unknown error";
+ return c.json(
+ {
+ error: errorMessage,
+ },
+ {
+ statusText: "Invalid request",
+ status: 500,
+ headers: corsHeaders,
+ },
+ );
+ }
+
+ return c.json(
+ {
+ workflow_id: workflow_id,
+ version: version,
+ },
+ {
+ status: 200,
+ headers: corsHeaders,
+ },
+ );
+ });
+
+ app.route("/upload-workflow").options(async (c) => {
+ return new Response(null, {
+ status: 204,
+ headers: corsHeaders,
+ });
+ });
+};
diff --git a/web/src/server/APIKeyBodyRequest.ts b/web/src/server/APIKeyBodyRequest.ts
index e7b92bc..3f71a00 100644
--- a/web/src/server/APIKeyBodyRequest.ts
+++ b/web/src/server/APIKeyBodyRequest.ts
@@ -1,9 +1,10 @@
import { z } from "zod";
export const APIKeyBodyRequest = z.object({
- user_id: z.string().optional(),
- org_id: z.string().optional(),
+ user_id: z.string().optional().nullable(),
+ org_id: z.string().optional().nullable(),
iat: z.number(),
+ exp: z.number().optional(),
});
export type APIKeyUserType = z.infer;
diff --git a/web/src/server/createNewWorkflow.ts b/web/src/server/createNewWorkflow.ts
index 3636ff9..faa00d7 100644
--- a/web/src/server/createNewWorkflow.ts
+++ b/web/src/server/createNewWorkflow.ts
@@ -1,6 +1,46 @@
import { db } from "@/db/db";
import type { WorkflowVersionType } from "@/db/schema";
import { workflowTable, workflowVersionTable } from "@/db/schema";
+import { eq, sql } from "drizzle-orm";
+
+export async function createNewWorkflowVersion({
+ workflow_id,
+ workflowData,
+}: {
+ workflow_id: string;
+ workflowData: Pick<
+ WorkflowVersionType,
+ "workflow" | "workflow_api" | "snapshot"
+ >;
+}) {
+ // Add a new version
+ const data = await db
+ .insert(workflowVersionTable)
+ .values({
+ workflow_id,
+ ...workflowData,
+ version: sql`(
+ SELECT COALESCE(MAX(version), 0) + 1
+ FROM ${workflowVersionTable}
+ WHERE workflow_id = ${workflow_id}
+ )`,
+ })
+ .returning();
+ const version = data[0].version;
+
+ // Touch up the last updated time
+ await db
+ .update(workflowTable)
+ .set({
+ updated_at: new Date(),
+ })
+ .where(eq(workflowTable.id, workflow_id))
+ .returning();
+
+ return {
+ version,
+ };
+}
export async function createNewWorkflow({
workflow_name,
@@ -10,7 +50,7 @@ export async function createNewWorkflow({
}: {
workflow_name: string;
user_id: string;
- org_id?: string;
+ org_id?: string | null;
workflowData: Pick<
WorkflowVersionType,
"workflow" | "workflow_api" | "snapshot"
diff --git a/web/src/server/curdApiKeys.ts b/web/src/server/curdApiKeys.ts
index 252bddd..b15b625 100644
--- a/web/src/server/curdApiKeys.ts
+++ b/web/src/server/curdApiKeys.ts
@@ -1,23 +1,28 @@
"use server";
import { db } from "@/db/db";
-import { apiKeyTable } from "@/db/schema";
+import { apiKeyTable, authRequestsTable } from "@/db/schema";
+import { withServerPromise } from "@/server/withServerPromise";
import { auth } from "@clerk/nextjs";
import { and, desc, eq, isNull } from "drizzle-orm";
import jwt from "jsonwebtoken";
import { revalidatePath } from "next/cache";
-// export const nanoid = customAlphabet(
-// "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
-// );
+export const createAuthRequest = withServerPromise(
+ async (request_id: string) => {
+ const { userId, orgId } = auth();
-// const prefixes = {
-// cd: "cd",
-// } as const;
+ const result = await db.insert(authRequestsTable).values({
+ request_id: request_id,
+ user_id: userId,
+ org_id: orgId,
+ });
-// function newId(prefix: keyof typeof prefixes): string {
-// return [prefixes[prefix], nanoid(16)].join("_");
-// }
+ return {
+ message: "Auth request created, you may now return to your application.",
+ };
+ },
+);
export async function addNewAPIKey(name: string) {
const { userId, orgId } = auth();
@@ -29,7 +34,7 @@ export async function addNewAPIKey(name: string) {
if (orgId) {
token = jwt.sign(
{ user_id: userId, org_id: orgId },
- process.env.JWT_SECRET!
+ process.env.JWT_SECRET!,
);
} else {
token = jwt.sign({ user_id: userId }, process.env.JWT_SECRET!);
@@ -93,7 +98,7 @@ export async function getAPIKeys() {
where: and(
eq(apiKeyTable.user_id, userId),
isNull(apiKeyTable.org_id),
- eq(apiKeyTable.revoked, false)
+ eq(apiKeyTable.revoked, false),
),
orderBy: desc(apiKeyTable.created_at),
});
diff --git a/web/src/server/getOrgOrUserDisplayName.tsx b/web/src/server/getOrgOrUserDisplayName.tsx
new file mode 100644
index 0000000..562b2c5
--- /dev/null
+++ b/web/src/server/getOrgOrUserDisplayName.tsx
@@ -0,0 +1,18 @@
+import { db } from "@/db/db";
+import { usersTable } from "@/db/schema";
+import { clerkClient } from "@clerk/nextjs";
+import { eq } from "drizzle-orm";
+
+export async function getOrgOrUserDisplayName(
+ orgId: string | undefined | null,
+ userId: string,
+) {
+ return orgId
+ ? await clerkClient.organizations
+ .getOrganization({
+ organizationId: orgId,
+ })
+ .then((x) => x.name)
+ : (await db.select().from(usersTable).where(eq(usersTable.id, userId)))[0]
+ .name;
+}