@lead-routing/cli 0.8.3 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -112,38 +112,35 @@ function bail2(value) {
112
112
  async function collectConfig(opts = {}, authEmail, authPassword) {
113
113
  const crmType = opts.crmType ?? "salesforce";
114
114
  note2(
115
- "You will need:\n \u2022 Public HTTPS URLs for the web app and routing engine",
115
+ "You will need:\n \u2022 A domain with wildcard DNS (*.acme.com) pointing to your server",
116
116
  "Before you begin"
117
117
  );
118
- const appUrl = await text2({
119
- message: "App URL (public URL where the web app will be accessible)",
120
- placeholder: "https://routing.acme.com",
118
+ const domain = await text2({
119
+ message: "Your domain (we'll create app/api/evals/mcp subdomains):",
120
+ placeholder: "acme.com",
121
121
  validate: (v) => {
122
- if (!v) return "Required";
123
- try {
124
- const u = new URL(v);
125
- if (u.protocol !== "https:") return "Must be an HTTPS URL (required for Salesforce OAuth)";
126
- } catch {
127
- return "Must be a valid URL (e.g. https://routing.acme.com)";
128
- }
122
+ if (!v?.trim()) return "Domain is required";
123
+ const clean = v.trim().replace(/^https?:\/\//, "").replace(/\/+$/, "");
124
+ if (!clean.includes(".")) return "Enter a valid domain (e.g. acme.com)";
129
125
  }
130
126
  });
131
- if (isCancel2(appUrl)) bail2(appUrl);
132
- const crmLabel = crmType === "hubspot" ? "HubSpot" : "Salesforce";
133
- const engineUrl = await text2({
134
- message: `Engine URL (public URL ${crmLabel} will use to route leads)`,
135
- placeholder: "https://engine.acme.com or https://acme.com:3001",
136
- validate: (v) => {
137
- if (!v) return "Required";
138
- try {
139
- const u = new URL(v);
140
- if (u.protocol !== "https:") return `Must be an HTTPS URL (${crmLabel} requires HTTPS)`;
141
- } catch {
142
- return "Must be a valid URL (e.g. https://engine.acme.com)";
143
- }
144
- }
145
- });
146
- if (isCancel2(engineUrl)) bail2(engineUrl);
127
+ if (isCancel2(domain)) bail2(domain);
128
+ const baseDomain = domain.trim().replace(/^https?:\/\//, "").replace(/\/+$/, "");
129
+ const appUrl = `https://app.${baseDomain}`;
130
+ const engineUrl = `https://api.${baseDomain}`;
131
+ const langfuseUrl = `https://evals.${baseDomain}`;
132
+ const mcpUrl = `https://mcp.${baseDomain}`;
133
+ note2(
134
+ [
135
+ `App: ${appUrl}`,
136
+ `Engine: ${engineUrl}`,
137
+ `Evals: ${langfuseUrl}`,
138
+ `MCP: ${mcpUrl}`,
139
+ "",
140
+ `Add this DNS record: *.${baseDomain} -> A -> <your server IP>`
141
+ ].join("\n"),
142
+ "URLs"
143
+ );
147
144
  const dbPassword = generateSecret(16);
148
145
  const managedDb = !opts.externalDb;
149
146
  const databaseUrl = opts.externalDb ?? `postgresql://leadrouting:${dbPassword}@postgres:5432/leadrouting`;
@@ -186,8 +183,11 @@ async function collectConfig(opts = {}, authEmail, authPassword) {
186
183
  const hubspotClientId = void 0;
187
184
  const hubspotClientSecret = void 0;
188
185
  return {
189
- appUrl: appUrl.trim().replace(/\/+$/, ""),
190
- engineUrl: engineUrl.trim().replace(/\/+$/, ""),
186
+ appUrl,
187
+ engineUrl,
188
+ baseDomain,
189
+ langfuseUrl,
190
+ mcpUrl,
191
191
  crmType,
192
192
  managedDb,
193
193
  databaseUrl,
@@ -294,9 +294,27 @@ function renderDockerCompose(c) {
294
294
  depends_on:
295
295
  postgres:
296
296
  condition: service_healthy
297
+ ` : "";
298
+ const mcpService = c.managedMcp ? `
299
+ mcp:
300
+ image: ghcr.io/atgatzby/lead-routing-mcp:latest
301
+ restart: unless-stopped
302
+ environment:
303
+ APP_URL: http://web:3000
304
+ ENGINE_URL: http://engine:3001
305
+ API_TOKEN: ${c.mcpApiToken ?? ""}
306
+ WEBHOOK_SECRET: ${c.mcpWebhookSecret ?? ""}
307
+ CRM_TYPE: ${c.mcpCrmType ?? "salesforce"}
308
+ MCP_PUBLIC_URL: ${c.mcpUrl ?? ""}
309
+ PORT: "3100"
310
+ TRANSPORT: http
311
+ depends_on:
312
+ web:
313
+ condition: service_healthy
297
314
  ` : "";
298
315
  const caddyDeps = ["web", "engine"];
299
316
  if (c.managedLangfuse) caddyDeps.push("langfuse");
317
+ if (c.managedMcp) caddyDeps.push("mcp");
300
318
  const caddyService = `
301
319
  caddy:
302
320
  image: caddy:2-alpine
@@ -324,6 +342,7 @@ ${caddyDeps.map((d) => ` - ${d}`).join("\n")}
324
342
  webService.trimEnd(),
325
343
  engineService.trimEnd(),
326
344
  langfuseService.trimEnd(),
345
+ mcpService.trimEnd(),
327
346
  caddyService.trimEnd(),
328
347
  volumes
329
348
  ].filter(Boolean).join("\n");
@@ -443,68 +462,32 @@ function renderEnvEngine(c) {
443
462
  }
444
463
 
445
464
  // src/templates/caddy.ts
446
- function renderCaddyfile(appUrl, engineUrl, langfuseUrl) {
447
- const appHost = new URL(appUrl).hostname;
448
- const engineParsed = new URL(engineUrl);
465
+ function renderCaddyfile(appUrlOrConfig, engineUrl, langfuseUrl) {
466
+ let config2;
467
+ if (typeof appUrlOrConfig === "string") {
468
+ config2 = { appUrl: appUrlOrConfig, engineUrl, langfuseUrl };
469
+ } else {
470
+ config2 = appUrlOrConfig;
471
+ }
472
+ const appHost = new URL(config2.appUrl).hostname;
473
+ const engineParsed = new URL(config2.engineUrl);
449
474
  const engineHost = engineParsed.hostname;
450
475
  const enginePort = engineParsed.port;
451
476
  let langfuseHostname = "";
452
- if (langfuseUrl) {
477
+ if (config2.langfuseUrl) {
453
478
  try {
454
- langfuseHostname = new URL(langfuseUrl).hostname;
479
+ langfuseHostname = new URL(config2.langfuseUrl).hostname;
455
480
  } catch {
456
481
  }
457
482
  }
458
- const isSameDomain = engineHost === appHost;
459
- if (isSameDomain && enginePort) {
460
- return [
461
- `# Lead Routing \u2014 Caddyfile`,
462
- `# Generated by lead-routing CLI`,
463
- `# Caddy auto-provisions SSL certificates via Let's Encrypt`,
464
- ``,
465
- `(security_headers) {`,
466
- ` header {`,
467
- ` X-Content-Type-Options nosniff`,
468
- ` X-Frame-Options DENY`,
469
- ` Referrer-Policy strict-origin-when-cross-origin`,
470
- ` Permissions-Policy interest-cohort=()`,
471
- ` Strict-Transport-Security "max-age=31536000; includeSubDomains"`,
472
- ` }`,
473
- `}`,
474
- ``,
475
- `${appHost} {`,
476
- ` import security_headers`,
477
- ` reverse_proxy web:3000 {`,
478
- ` health_uri /api/health`,
479
- ` health_interval 15s`,
480
- ` }`,
481
- `}`,
482
- ``,
483
- `${appHost}:${enginePort} {`,
484
- ` import security_headers`,
485
- ` reverse_proxy engine:3001 {`,
486
- ` health_uri /health`,
487
- ` health_interval 15s`,
488
- ` }`,
489
- `}`,
490
- ...langfuseHostname ? [
491
- ``,
492
- `${langfuseHostname} {`,
493
- ` import security_headers`,
494
- ` reverse_proxy langfuse:3000`,
495
- `}`
496
- ] : [],
497
- ``,
498
- `# Marketing site \u2014 served independently`,
499
- `openedgeai.tech {`,
500
- ` import security_headers`,
501
- ` root * /srv/marketing-site`,
502
- ` file_server`,
503
- ` try_files {path} /index.html`,
504
- `}`
505
- ].join("\n");
483
+ let mcpHostname = "";
484
+ if (config2.mcpEnabled && config2.mcpUrl) {
485
+ try {
486
+ mcpHostname = new URL(config2.mcpUrl).hostname;
487
+ } catch {
488
+ }
506
489
  }
507
- return [
490
+ const header = [
508
491
  `# Lead Routing \u2014 Caddyfile`,
509
492
  `# Generated by lead-routing CLI`,
510
493
  `# Caddy auto-provisions SSL certificates via Let's Encrypt`,
@@ -517,7 +500,9 @@ function renderCaddyfile(appUrl, engineUrl, langfuseUrl) {
517
500
  ` Permissions-Policy interest-cohort=()`,
518
501
  ` Strict-Transport-Security "max-age=31536000; includeSubDomains"`,
519
502
  ` }`,
520
- `}`,
503
+ `}`
504
+ ];
505
+ const appBlock = [
521
506
  ``,
522
507
  `${appHost} {`,
523
508
  ` import security_headers`,
@@ -525,22 +510,34 @@ function renderCaddyfile(appUrl, engineUrl, langfuseUrl) {
525
510
  ` health_uri /api/health`,
526
511
  ` health_interval 15s`,
527
512
  ` }`,
528
- `}`,
513
+ `}`
514
+ ];
515
+ const engineAddress = engineHost === appHost && enginePort ? `${engineHost}:${enginePort}` : engineHost;
516
+ const engineBlock = [
529
517
  ``,
530
- `${engineHost} {`,
518
+ `${engineAddress} {`,
531
519
  ` import security_headers`,
532
520
  ` reverse_proxy engine:3001 {`,
533
521
  ` health_uri /health`,
534
522
  ` health_interval 15s`,
535
523
  ` }`,
536
- `}`,
537
- ...langfuseHostname ? [
538
- ``,
539
- `${langfuseHostname} {`,
540
- ` import security_headers`,
541
- ` reverse_proxy langfuse:3000`,
542
- `}`
543
- ] : [],
524
+ `}`
525
+ ];
526
+ const langfuseBlock = langfuseHostname ? [
527
+ ``,
528
+ `${langfuseHostname} {`,
529
+ ` import security_headers`,
530
+ ` reverse_proxy langfuse:3000`,
531
+ `}`
532
+ ] : [];
533
+ const mcpBlock = mcpHostname ? [
534
+ ``,
535
+ `${mcpHostname} {`,
536
+ ` import security_headers`,
537
+ ` reverse_proxy mcp:3100`,
538
+ `}`
539
+ ] : [];
540
+ const marketingBlock = [
544
541
  ``,
545
542
  `# Marketing site \u2014 served independently`,
546
543
  `openedgeai.tech {`,
@@ -549,6 +546,14 @@ function renderCaddyfile(appUrl, engineUrl, langfuseUrl) {
549
546
  ` file_server`,
550
547
  ` try_files {path} /index.html`,
551
548
  `}`
549
+ ];
550
+ return [
551
+ ...header,
552
+ ...appBlock,
553
+ ...engineBlock,
554
+ ...langfuseBlock,
555
+ ...mcpBlock,
556
+ ...marketingBlock
552
557
  ].join("\n");
553
558
  }
554
559
 
@@ -602,12 +607,23 @@ function generateFiles(cfg, sshCfg, license = { licenseTier: "free" }, agentApi)
602
607
  managedLangfuse: !!agentApi,
603
608
  langfuseUrl: agentApi?.langfuseUrl,
604
609
  langfuseSecret: agentApi?.langfuseSecret,
605
- langfuseSalt: agentApi?.langfuseSalt
610
+ langfuseSalt: agentApi?.langfuseSalt,
611
+ managedMcp: !!agentApi,
612
+ mcpUrl: cfg.mcpUrl,
613
+ mcpWebhookSecret: cfg.engineWebhookSecret,
614
+ mcpCrmType: cfg.crmType
606
615
  });
607
616
  const composeFile = join2(dir, "docker-compose.yml");
608
617
  writeFileSync2(composeFile, composeContent, "utf8");
609
618
  log3.success("Generated docker-compose.yml");
610
- const caddyfileContent = renderCaddyfile(cfg.appUrl, cfg.engineUrl, agentApi?.langfuseUrl);
619
+ const caddyfileContent = renderCaddyfile({
620
+ appUrl: cfg.appUrl,
621
+ engineUrl: cfg.engineUrl,
622
+ baseDomain: cfg.baseDomain,
623
+ langfuseUrl: agentApi ? cfg.langfuseUrl : void 0,
624
+ mcpEnabled: !!agentApi,
625
+ mcpUrl: cfg.mcpUrl
626
+ });
611
627
  writeFileSync2(join2(dir, "Caddyfile"), caddyfileContent, "utf8");
612
628
  log3.success("Generated Caddyfile");
613
629
  const envWebContent = renderEnvWeb({
@@ -650,6 +666,8 @@ function generateFiles(cfg, sshCfg, license = { licenseTier: "free" }, agentApi)
650
666
  writeConfig(dir, {
651
667
  appUrl: cfg.appUrl,
652
668
  engineUrl: cfg.engineUrl,
669
+ baseDomain: cfg.baseDomain,
670
+ mcpUrl: cfg.mcpUrl,
653
671
  crmType: cfg.crmType,
654
672
  installDir: dir,
655
673
  remoteDir: sshCfg.remoteDir,
@@ -1502,23 +1520,10 @@ Install URL: ${chalk2.cyan(MANAGED_PACKAGE_INSTALL_URL)}`,
1502
1520
  cancel3("Setup cancelled.");
1503
1521
  process.exit(0);
1504
1522
  }
1505
- let langfuseUrl = "";
1523
+ let langfuseUrl = cfg.langfuseUrl;
1506
1524
  let langfuseSecret = "";
1507
1525
  let langfuseSalt = "";
1508
1526
  if (enableAgentApi) {
1509
- const appDomain = new URL(cfg.appUrl).hostname;
1510
- const baseDomain = appDomain.replace(/^app\d*\./, "");
1511
- const suggestedLangfuse = `evals.${baseDomain}`;
1512
- const lfUrl = await text3({
1513
- message: "Langfuse dashboard URL:",
1514
- initialValue: `https://${suggestedLangfuse}`,
1515
- placeholder: `https://${suggestedLangfuse}`
1516
- });
1517
- if (isCancel3(lfUrl)) {
1518
- cancel3("Setup cancelled.");
1519
- process.exit(0);
1520
- }
1521
- langfuseUrl = lfUrl.trim();
1522
1527
  langfuseSecret = generateSecret(32);
1523
1528
  langfuseSalt = generateSecret(16);
1524
1529
  }
@@ -1691,6 +1696,7 @@ Click "Connect HubSpot" to authorize the integration.`,
1691
1696
  const mcpDir = join5(homedir3(), ".lead-routing");
1692
1697
  mkdirSync3(mcpDir, { recursive: true });
1693
1698
  const mcpConfig = { appUrl: cfg.appUrl, engineUrl: cfg.engineUrl, webhookSecret, crmType: cfg.crmType || "salesforce" };
1699
+ if (cfg.mcpUrl) mcpConfig.mcpUrl = cfg.mcpUrl;
1694
1700
  if (apiToken) mcpConfig.apiToken = apiToken;
1695
1701
  writeFileSync4(
1696
1702
  join5(mcpDir, "mcp.json"),
@@ -1709,18 +1715,21 @@ Click "Connect HubSpot" to authorize the integration.`,
1709
1715
  ` : ` ${chalk2.cyan("2.")} Go to Integrations \u2192 HubSpot \u2192 Connect
1710
1716
  ${chalk2.cyan("3.")} Authorize the HubSpot integration
1711
1717
  `;
1712
- const agentApiLines = enableAgentApi ? ` Langfuse dashboard: ${chalk2.cyan(langfuseUrl)}
1713
- MCP config: ~/.lead-routing/mcp.json
1718
+ const agentApiLines = enableAgentApi ? ` Evals: ${chalk2.cyan(cfg.langfuseUrl)}
1719
+ MCP: ${chalk2.cyan(cfg.mcpUrl)}
1720
+ MCP config: ~/.lead-routing/mcp.json
1721
+ ` : "";
1722
+ const dnsLine = cfg.baseDomain ? `
1723
+ DNS: Add ${chalk2.white(`*.${cfg.baseDomain}`)} -> A -> <server IP>
1714
1724
  ` : "";
1715
1725
  outro(
1716
1726
  chalk2.green("\u2714 You're live!") + `
1717
1727
 
1718
1728
  Dashboard: ${chalk2.cyan(cfg.appUrl)}
1719
- Routing engine: ${chalk2.cyan(cfg.engineUrl)}
1729
+ Engine: ${chalk2.cyan(cfg.engineUrl)}
1720
1730
  ` + agentApiLines + `
1721
1731
  Admin email: ${chalk2.white(cfg.adminEmail)}
1722
-
1723
- ` + chalk2.bold(" Next steps:\n") + ` ${chalk2.cyan("1.")} Open ${chalk2.cyan(cfg.appUrl)} and log in
1732
+ ` + dnsLine + "\n" + chalk2.bold(" Next steps:\n") + ` ${chalk2.cyan("1.")} Open ${chalk2.cyan(cfg.appUrl)} and log in
1724
1733
  ` + crmSteps + ` ${chalk2.cyan("4.")} Create your first routing rule
1725
1734
 
1726
1735
  Run ${chalk2.cyan("lead-routing doctor")} to check service health at any time.
@@ -86,17 +86,20 @@ DROP INDEX IF EXISTS "routing_daily_aggregates_orgId_date_ruleId_pathLabel_branc
86
86
  CREATE UNIQUE INDEX "routing_daily_aggregates_orgId_date_ruleId_pathLabel_branchId_key" ON "routing_daily_aggregates"("orgId", "date", "ruleId", "pathLabel", "branchId", "teamId", "assigneeId", "objectType");
87
87
 
88
88
  -- AlterTable routing_flows - Migrate objectType to CrmObjectType
89
- ALTER TABLE "routing_flows" ADD COLUMN "objectType_new" "CrmObjectType";
90
- UPDATE "routing_flows" SET "objectType_new" = 'LEAD' WHERE "objectType" = 'LEAD';
91
- UPDATE "routing_flows" SET "objectType_new" = 'CONTACT' WHERE "objectType" = 'CONTACT';
92
- UPDATE "routing_flows" SET "objectType_new" = 'ACCOUNT' WHERE "objectType" = 'ACCOUNT';
93
- ALTER TABLE "routing_flows" DROP COLUMN "objectType";
94
- ALTER TABLE "routing_flows" RENAME COLUMN "objectType_new" TO "objectType";
95
- ALTER TABLE "routing_flows" ALTER COLUMN "objectType" SET NOT NULL;
96
-
97
- -- Update routing_flows unique constraint
98
- DROP INDEX IF EXISTS "routing_flows_orgId_objectType_key";
99
- CREATE UNIQUE INDEX "routing_flows_orgId_objectType_key" ON "routing_flows"("orgId", "objectType");
89
+ -- (conditional: routing_flows only exists if Flow Builder migrations were applied)
90
+ DO $$ BEGIN
91
+ IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'routing_flows') THEN
92
+ ALTER TABLE "routing_flows" ADD COLUMN "objectType_new" "CrmObjectType";
93
+ UPDATE "routing_flows" SET "objectType_new" = 'LEAD' WHERE "objectType" = 'LEAD';
94
+ UPDATE "routing_flows" SET "objectType_new" = 'CONTACT' WHERE "objectType" = 'CONTACT';
95
+ UPDATE "routing_flows" SET "objectType_new" = 'ACCOUNT' WHERE "objectType" = 'ACCOUNT';
96
+ ALTER TABLE "routing_flows" DROP COLUMN "objectType";
97
+ ALTER TABLE "routing_flows" RENAME COLUMN "objectType_new" TO "objectType";
98
+ ALTER TABLE "routing_flows" ALTER COLUMN "objectType" SET NOT NULL;
99
+ DROP INDEX IF EXISTS "routing_flows_orgId_objectType_key";
100
+ CREATE UNIQUE INDEX "routing_flows_orgId_objectType_key" ON "routing_flows"("orgId", "objectType");
101
+ END IF;
102
+ END $$;
100
103
 
101
104
  -- Drop the function that depends on SfdcObjectType BEFORE dropping the type
102
105
  DROP FUNCTION IF EXISTS immutable_object_type_text("SfdcObjectType");
@@ -1,6 +1,6 @@
1
1
  generator client {
2
2
  provider = "prisma-client-js"
3
- output = "../../../node_modules/.prisma/client"
3
+ output = "../src/generated/prisma"
4
4
  }
5
5
 
6
6
  datasource db {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lead-routing/cli",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "Self-hosted deployment CLI for Lead Routing",
5
5
  "homepage": "https://github.com/lead-routing/lead-routing",
6
6
  "keywords": [
@@ -1,76 +0,0 @@
1
- -- CreateEnum
2
- CREATE TYPE "FlowStatus" AS ENUM ('DRAFT', 'ACTIVE', 'INACTIVE');
3
-
4
- -- CreateEnum
5
- CREATE TYPE "FlowNodeType" AS ENUM ('ENTRY', 'DECISION', 'BRANCH_DECISION', 'MATCH', 'ASSIGNMENT', 'UPDATE_FIELD', 'CREATE_TASK', 'FILTER', 'DEFAULT');
6
-
7
- -- AlterTable
8
- ALTER TABLE "organizations" ADD COLUMN "routingMode" JSONB;
9
-
10
- -- AlterTable
11
- ALTER TABLE "routing_logs" ADD COLUMN "flowId" TEXT,
12
- ADD COLUMN "flowNodePath" JSONB;
13
-
14
- -- CreateTable
15
- CREATE TABLE "routing_flows" (
16
- "id" TEXT NOT NULL,
17
- "orgId" TEXT NOT NULL,
18
- "objectType" "SfdcObjectType" NOT NULL,
19
- "name" TEXT NOT NULL DEFAULT 'Untitled Flow',
20
- "status" "FlowStatus" NOT NULL DEFAULT 'DRAFT',
21
- "triggerEvent" "TriggerEvent" NOT NULL DEFAULT 'BOTH',
22
- "isDryRun" BOOLEAN NOT NULL DEFAULT false,
23
- "version" INTEGER NOT NULL DEFAULT 1,
24
- "publishedAt" TIMESTAMP(3),
25
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
26
- "updatedAt" TIMESTAMP(3) NOT NULL,
27
-
28
- CONSTRAINT "routing_flows_pkey" PRIMARY KEY ("id")
29
- );
30
-
31
- -- CreateTable
32
- CREATE TABLE "flow_nodes" (
33
- "id" TEXT NOT NULL,
34
- "flowId" TEXT NOT NULL,
35
- "type" "FlowNodeType" NOT NULL,
36
- "label" TEXT,
37
- "positionX" DOUBLE PRECISION NOT NULL DEFAULT 0,
38
- "positionY" DOUBLE PRECISION NOT NULL DEFAULT 0,
39
- "config" JSONB,
40
- "sortOrder" INTEGER NOT NULL DEFAULT 0,
41
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
42
- "updatedAt" TIMESTAMP(3) NOT NULL,
43
-
44
- CONSTRAINT "flow_nodes_pkey" PRIMARY KEY ("id")
45
- );
46
-
47
- -- CreateTable
48
- CREATE TABLE "flow_edges" (
49
- "id" TEXT NOT NULL,
50
- "flowId" TEXT NOT NULL,
51
- "fromId" TEXT NOT NULL,
52
- "toId" TEXT NOT NULL,
53
- "label" TEXT,
54
- "sortOrder" INTEGER NOT NULL DEFAULT 0,
55
- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
56
-
57
- CONSTRAINT "flow_edges_pkey" PRIMARY KEY ("id")
58
- );
59
-
60
- -- CreateIndex
61
- CREATE UNIQUE INDEX "routing_flows_orgId_objectType_key" ON "routing_flows"("orgId", "objectType");
62
-
63
- -- AddForeignKey
64
- ALTER TABLE "routing_flows" ADD CONSTRAINT "routing_flows_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
65
-
66
- -- AddForeignKey
67
- ALTER TABLE "flow_nodes" ADD CONSTRAINT "flow_nodes_flowId_fkey" FOREIGN KEY ("flowId") REFERENCES "routing_flows"("id") ON DELETE CASCADE ON UPDATE CASCADE;
68
-
69
- -- AddForeignKey
70
- ALTER TABLE "flow_edges" ADD CONSTRAINT "flow_edges_flowId_fkey" FOREIGN KEY ("flowId") REFERENCES "routing_flows"("id") ON DELETE CASCADE ON UPDATE CASCADE;
71
-
72
- -- AddForeignKey
73
- ALTER TABLE "flow_edges" ADD CONSTRAINT "flow_edges_fromId_fkey" FOREIGN KEY ("fromId") REFERENCES "flow_nodes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
74
-
75
- -- AddForeignKey
76
- ALTER TABLE "flow_edges" ADD CONSTRAINT "flow_edges_toId_fkey" FOREIGN KEY ("toId") REFERENCES "flow_nodes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,3 +0,0 @@
1
- -- AlterTable
2
- ALTER TABLE "flow_edges" ADD COLUMN "sourceHandle" TEXT;
3
- ALTER TABLE "flow_edges" ADD COLUMN "targetHandle" TEXT;