@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 +124 -115
- package/dist/prisma/migrations/20260411000000_unified_crm_schema/migration.sql +14 -11
- package/dist/prisma/schema.prisma +1 -1
- package/package.json +1 -1
- package/dist/prisma/migrations/20260317000000_add_routing_flows/migration.sql +0 -76
- package/dist/prisma/migrations/20260318000000_add_flow_edge_handles/migration.sql +0 -3
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
|
|
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
|
|
119
|
-
message: "
|
|
120
|
-
placeholder: "
|
|
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 "
|
|
123
|
-
|
|
124
|
-
|
|
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(
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
190
|
-
engineUrl
|
|
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(
|
|
447
|
-
|
|
448
|
-
|
|
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
|
-
|
|
459
|
-
if (
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
|
|
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
|
-
`${
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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(
|
|
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 ? `
|
|
1713
|
-
MCP
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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");
|
package/package.json
CHANGED
|
@@ -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;
|