@lead-routing/cli 0.8.0 → 0.8.2
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
|
@@ -12,6 +12,12 @@ import { join as join5 } from "path";
|
|
|
12
12
|
import { intro, outro, note as note3, log as log7, confirm, cancel as cancel3, isCancel as isCancel3, password as promptPassword, select, text as text3 } from "@clack/prompts";
|
|
13
13
|
import chalk2 from "chalk";
|
|
14
14
|
|
|
15
|
+
// src/utils/crypto.ts
|
|
16
|
+
import { randomBytes } from "crypto";
|
|
17
|
+
function generateSecret(bytes = 32) {
|
|
18
|
+
return randomBytes(bytes).toString("hex");
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
// src/steps/prerequisites.ts
|
|
16
22
|
import { log } from "@clack/prompts";
|
|
17
23
|
async function checkPrerequisites() {
|
|
@@ -96,14 +102,6 @@ async function collectSshConfig(opts = {}) {
|
|
|
96
102
|
|
|
97
103
|
// src/steps/collect-config.ts
|
|
98
104
|
import { text as text2, password as password2, note as note2, cancel as cancel2, isCancel as isCancel2 } from "@clack/prompts";
|
|
99
|
-
|
|
100
|
-
// src/utils/crypto.ts
|
|
101
|
-
import { randomBytes } from "crypto";
|
|
102
|
-
function generateSecret(bytes = 32) {
|
|
103
|
-
return randomBytes(bytes).toString("hex");
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// src/steps/collect-config.ts
|
|
107
105
|
function bail2(value) {
|
|
108
106
|
if (isCancel2(value)) {
|
|
109
107
|
cancel2("Setup cancelled.");
|
|
@@ -111,7 +109,7 @@ function bail2(value) {
|
|
|
111
109
|
}
|
|
112
110
|
throw new Error("Unexpected cancel");
|
|
113
111
|
}
|
|
114
|
-
async function collectConfig(opts = {}, authEmail) {
|
|
112
|
+
async function collectConfig(opts = {}, authEmail, authPassword) {
|
|
115
113
|
const crmType = opts.crmType ?? "salesforce";
|
|
116
114
|
note2(
|
|
117
115
|
"You will need:\n \u2022 Public HTTPS URLs for the web app and routing engine",
|
|
@@ -168,14 +166,19 @@ async function collectConfig(opts = {}, authEmail) {
|
|
|
168
166
|
});
|
|
169
167
|
if (isCancel2(adminEmail)) bail2(adminEmail);
|
|
170
168
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
169
|
+
let adminPassword;
|
|
170
|
+
if (authPassword) {
|
|
171
|
+
adminPassword = authPassword;
|
|
172
|
+
} else {
|
|
173
|
+
adminPassword = await password2({
|
|
174
|
+
message: "Admin password (min 8 characters)",
|
|
175
|
+
validate: (v) => {
|
|
176
|
+
if (!v) return "Required";
|
|
177
|
+
if (v.length < 8) return "Must be at least 8 characters";
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
if (isCancel2(adminPassword)) bail2(adminPassword);
|
|
181
|
+
}
|
|
179
182
|
const sessionSecret = generateSecret(32);
|
|
180
183
|
const engineWebhookSecret = generateSecret(32);
|
|
181
184
|
const internalApiKey = generateSecret(32);
|
|
@@ -276,6 +279,24 @@ function renderDockerCompose(c) {
|
|
|
276
279
|
timeout: 5s
|
|
277
280
|
retries: 6${engineDependsOn}
|
|
278
281
|
`;
|
|
282
|
+
const langfuseService = c.managedLangfuse ? `
|
|
283
|
+
langfuse:
|
|
284
|
+
image: langfuse/langfuse:2
|
|
285
|
+
restart: unless-stopped
|
|
286
|
+
environment:
|
|
287
|
+
DATABASE_URL: postgresql://leadrouting:${dbPassword}@postgres:5432/langfuse
|
|
288
|
+
NEXTAUTH_URL: ${c.langfuseUrl ?? ""}
|
|
289
|
+
NEXTAUTH_SECRET: ${c.langfuseSecret ?? ""}
|
|
290
|
+
SALT: ${c.langfuseSalt ?? ""}
|
|
291
|
+
TELEMETRY_ENABLED: "false"
|
|
292
|
+
HOSTNAME: "0.0.0.0"
|
|
293
|
+
PORT: "3000"
|
|
294
|
+
depends_on:
|
|
295
|
+
postgres:
|
|
296
|
+
condition: service_healthy
|
|
297
|
+
` : "";
|
|
298
|
+
const caddyDeps = ["web", "engine"];
|
|
299
|
+
if (c.managedLangfuse) caddyDeps.push("langfuse");
|
|
279
300
|
const caddyService = `
|
|
280
301
|
caddy:
|
|
281
302
|
image: caddy:2-alpine
|
|
@@ -289,8 +310,7 @@ function renderDockerCompose(c) {
|
|
|
289
310
|
- caddy_data:/data
|
|
290
311
|
- caddy_config:/config
|
|
291
312
|
depends_on:
|
|
292
|
-
-
|
|
293
|
-
- engine
|
|
313
|
+
${caddyDeps.map((d) => ` - ${d}`).join("\n")}
|
|
294
314
|
`;
|
|
295
315
|
const volumes = buildVolumes(c.managedDb, c.managedRedis);
|
|
296
316
|
return [
|
|
@@ -303,6 +323,7 @@ function renderDockerCompose(c) {
|
|
|
303
323
|
redisService.trimEnd(),
|
|
304
324
|
webService.trimEnd(),
|
|
305
325
|
engineService.trimEnd(),
|
|
326
|
+
langfuseService.trimEnd(),
|
|
306
327
|
caddyService.trimEnd(),
|
|
307
328
|
volumes
|
|
308
329
|
].filter(Boolean).join("\n");
|
|
@@ -373,6 +394,14 @@ function renderEnvWeb(c) {
|
|
|
373
394
|
`HUBSPOT_CLIENT_SECRET=${c.hubspotClientSecret ?? "f95949ac-6464-40d3-bdaa-893c48749951"}`,
|
|
374
395
|
`HUBSPOT_APP_ID=${c.hubspotAppId ?? "35016223"}`,
|
|
375
396
|
`HUBSPOT_REDIRECT_URI=${c.appUrl}/api/auth/hubspot/callback`
|
|
397
|
+
] : [],
|
|
398
|
+
...c.langfuseEnabled ? [
|
|
399
|
+
``,
|
|
400
|
+
`# Langfuse (agent evaluation dashboard)`,
|
|
401
|
+
`LANGFUSE_ENABLED=true`,
|
|
402
|
+
`LANGFUSE_URL=http://langfuse:3000`,
|
|
403
|
+
`LANGFUSE_PUBLIC_KEY=${c.langfusePublicKey ?? ""}`,
|
|
404
|
+
`LANGFUSE_SECRET_KEY=${c.langfuseSecretKey ?? ""}`
|
|
376
405
|
] : []
|
|
377
406
|
].join("\n");
|
|
378
407
|
}
|
|
@@ -414,11 +443,18 @@ function renderEnvEngine(c) {
|
|
|
414
443
|
}
|
|
415
444
|
|
|
416
445
|
// src/templates/caddy.ts
|
|
417
|
-
function renderCaddyfile(appUrl, engineUrl) {
|
|
446
|
+
function renderCaddyfile(appUrl, engineUrl, langfuseUrl) {
|
|
418
447
|
const appHost = new URL(appUrl).hostname;
|
|
419
448
|
const engineParsed = new URL(engineUrl);
|
|
420
449
|
const engineHost = engineParsed.hostname;
|
|
421
450
|
const enginePort = engineParsed.port;
|
|
451
|
+
let langfuseHostname = "";
|
|
452
|
+
if (langfuseUrl) {
|
|
453
|
+
try {
|
|
454
|
+
langfuseHostname = new URL(langfuseUrl).hostname;
|
|
455
|
+
} catch {
|
|
456
|
+
}
|
|
457
|
+
}
|
|
422
458
|
const isSameDomain = engineHost === appHost;
|
|
423
459
|
if (isSameDomain && enginePort) {
|
|
424
460
|
return [
|
|
@@ -451,6 +487,13 @@ function renderCaddyfile(appUrl, engineUrl) {
|
|
|
451
487
|
` health_interval 15s`,
|
|
452
488
|
` }`,
|
|
453
489
|
`}`,
|
|
490
|
+
...langfuseHostname ? [
|
|
491
|
+
``,
|
|
492
|
+
`${langfuseHostname} {`,
|
|
493
|
+
` import security_headers`,
|
|
494
|
+
` reverse_proxy langfuse:3000`,
|
|
495
|
+
`}`
|
|
496
|
+
] : [],
|
|
454
497
|
``,
|
|
455
498
|
`# Marketing site \u2014 served independently`,
|
|
456
499
|
`openedgeai.tech {`,
|
|
@@ -491,6 +534,13 @@ function renderCaddyfile(appUrl, engineUrl) {
|
|
|
491
534
|
` health_interval 15s`,
|
|
492
535
|
` }`,
|
|
493
536
|
`}`,
|
|
537
|
+
...langfuseHostname ? [
|
|
538
|
+
``,
|
|
539
|
+
`${langfuseHostname} {`,
|
|
540
|
+
` import security_headers`,
|
|
541
|
+
` reverse_proxy langfuse:3000`,
|
|
542
|
+
`}`
|
|
543
|
+
] : [],
|
|
494
544
|
``,
|
|
495
545
|
`# Marketing site \u2014 served independently`,
|
|
496
546
|
`openedgeai.tech {`,
|
|
@@ -540,7 +590,7 @@ function getCliVersion() {
|
|
|
540
590
|
return "0.1.0";
|
|
541
591
|
}
|
|
542
592
|
}
|
|
543
|
-
function generateFiles(cfg, sshCfg, license = { licenseTier: "free" }) {
|
|
593
|
+
function generateFiles(cfg, sshCfg, license = { licenseTier: "free" }, agentApi) {
|
|
544
594
|
const dir = join2(process.cwd(), "lead-routing");
|
|
545
595
|
mkdirSync(dir, { recursive: true });
|
|
546
596
|
const dockerEngineUrl = `http://engine:3001`;
|
|
@@ -548,12 +598,16 @@ function generateFiles(cfg, sshCfg, license = { licenseTier: "free" }) {
|
|
|
548
598
|
managedDb: cfg.managedDb,
|
|
549
599
|
managedRedis: cfg.managedRedis,
|
|
550
600
|
dbPassword: cfg.dbPassword,
|
|
551
|
-
redisPassword: cfg.redisPassword
|
|
601
|
+
redisPassword: cfg.redisPassword,
|
|
602
|
+
managedLangfuse: !!agentApi,
|
|
603
|
+
langfuseUrl: agentApi?.langfuseUrl,
|
|
604
|
+
langfuseSecret: agentApi?.langfuseSecret,
|
|
605
|
+
langfuseSalt: agentApi?.langfuseSalt
|
|
552
606
|
});
|
|
553
607
|
const composeFile = join2(dir, "docker-compose.yml");
|
|
554
608
|
writeFileSync2(composeFile, composeContent, "utf8");
|
|
555
609
|
log3.success("Generated docker-compose.yml");
|
|
556
|
-
const caddyfileContent = renderCaddyfile(cfg.appUrl, cfg.engineUrl);
|
|
610
|
+
const caddyfileContent = renderCaddyfile(cfg.appUrl, cfg.engineUrl, agentApi?.langfuseUrl);
|
|
557
611
|
writeFileSync2(join2(dir, "Caddyfile"), caddyfileContent, "utf8");
|
|
558
612
|
log3.success("Generated Caddyfile");
|
|
559
613
|
const envWebContent = renderEnvWeb({
|
|
@@ -574,7 +628,8 @@ function generateFiles(cfg, sshCfg, license = { licenseTier: "free" }) {
|
|
|
574
628
|
crmType: cfg.crmType,
|
|
575
629
|
hubspotClientId: cfg.hubspotClientId,
|
|
576
630
|
hubspotClientSecret: cfg.hubspotClientSecret,
|
|
577
|
-
hubspotAppId: cfg.hubspotAppId
|
|
631
|
+
hubspotAppId: cfg.hubspotAppId,
|
|
632
|
+
langfuseEnabled: !!agentApi
|
|
578
633
|
});
|
|
579
634
|
const envWeb = join2(dir, ".env.web");
|
|
580
635
|
writeFileSync2(envWeb, envWebContent, "utf8");
|
|
@@ -612,6 +667,8 @@ function generateFiles(cfg, sshCfg, license = { licenseTier: "free" }) {
|
|
|
612
667
|
engineWebhookSecret: cfg.engineWebhookSecret,
|
|
613
668
|
licenseKey: license.licenseKey,
|
|
614
669
|
licenseTier: license.licenseTier,
|
|
670
|
+
enableAgentApi: !!agentApi,
|
|
671
|
+
langfuseUrl: agentApi?.langfuseUrl,
|
|
615
672
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
616
673
|
version: getCliVersion()
|
|
617
674
|
});
|
|
@@ -1245,6 +1302,7 @@ async function runInit(options = {}) {
|
|
|
1245
1302
|
return;
|
|
1246
1303
|
}
|
|
1247
1304
|
let auth;
|
|
1305
|
+
let authPassword;
|
|
1248
1306
|
try {
|
|
1249
1307
|
auth = await requireAuth();
|
|
1250
1308
|
} catch {
|
|
@@ -1314,6 +1372,7 @@ After verifying, press Enter to continue.`,
|
|
|
1314
1372
|
}
|
|
1315
1373
|
saveCredentials({ token, customer, storedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1316
1374
|
auth = { token, customer, storedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1375
|
+
authPassword = signupPw;
|
|
1317
1376
|
} catch (err) {
|
|
1318
1377
|
log7.error(err instanceof Error ? err.message : "Login failed");
|
|
1319
1378
|
process.exit(1);
|
|
@@ -1344,6 +1403,7 @@ After verifying, press Enter to continue.`,
|
|
|
1344
1403
|
}
|
|
1345
1404
|
saveCredentials({ token, customer, storedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1346
1405
|
auth = { token, customer, storedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1406
|
+
authPassword = loginPw;
|
|
1347
1407
|
} catch (err) {
|
|
1348
1408
|
log7.error(err instanceof Error ? err.message : "Login failed");
|
|
1349
1409
|
process.exit(1);
|
|
@@ -1432,13 +1492,46 @@ Install URL: ${chalk2.cyan(MANAGED_PACKAGE_INSTALL_URL)}`,
|
|
|
1432
1492
|
externalDb: options.externalDb,
|
|
1433
1493
|
externalRedis: options.externalRedis,
|
|
1434
1494
|
crmType
|
|
1435
|
-
}, auth.customer.email);
|
|
1495
|
+
}, auth.customer.email, authPassword);
|
|
1436
1496
|
await checkDnsResolvable(cfg.appUrl, cfg.engineUrl);
|
|
1497
|
+
const enableAgentApi = await confirm({
|
|
1498
|
+
message: "Enable AI agent API? (Langfuse eval dashboard + MCP composite tools)",
|
|
1499
|
+
initialValue: true
|
|
1500
|
+
});
|
|
1501
|
+
if (isCancel3(enableAgentApi)) {
|
|
1502
|
+
cancel3("Setup cancelled.");
|
|
1503
|
+
process.exit(0);
|
|
1504
|
+
}
|
|
1505
|
+
let langfuseUrl = "";
|
|
1506
|
+
let langfuseSecret = "";
|
|
1507
|
+
let langfuseSalt = "";
|
|
1508
|
+
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
|
+
langfuseSecret = generateSecret(32);
|
|
1523
|
+
langfuseSalt = generateSecret(16);
|
|
1524
|
+
}
|
|
1437
1525
|
log7.step("Step 6/9 Generating config files");
|
|
1438
1526
|
const { dir } = generateFiles(cfg, sshCfg, {
|
|
1439
1527
|
licenseKey: licenseResult.key,
|
|
1440
1528
|
licenseTier: licenseResult.tier
|
|
1441
|
-
}
|
|
1529
|
+
}, enableAgentApi ? {
|
|
1530
|
+
langfuseUrl,
|
|
1531
|
+
langfuseSecret,
|
|
1532
|
+
langfuseSalt,
|
|
1533
|
+
dbPassword: cfg.dbPassword
|
|
1534
|
+
} : void 0);
|
|
1442
1535
|
note3(
|
|
1443
1536
|
`Local config directory: ${chalk2.cyan(dir)}
|
|
1444
1537
|
Files created: docker-compose.yml, Caddyfile, .env.web, .env.engine, lead-routing.json`,
|
|
@@ -1460,6 +1553,12 @@ Files created: docker-compose.yml, Caddyfile, .env.web, .env.engine, lead-routin
|
|
|
1460
1553
|
await uploadFiles(ssh, dir, remoteDir);
|
|
1461
1554
|
log7.step("Step 8/9 Starting services");
|
|
1462
1555
|
await startServices(ssh, remoteDir);
|
|
1556
|
+
if (enableAgentApi) {
|
|
1557
|
+
log7.step("Creating Langfuse database...");
|
|
1558
|
+
await ssh.execCommand(`docker exec $(docker ps -qf "name=postgres") psql -U leadrouting -d postgres -c "CREATE DATABASE langfuse OWNER leadrouting;" 2>/dev/null || true`);
|
|
1559
|
+
await ssh.execCommand(`cd ${remoteDir} && docker compose restart langfuse`);
|
|
1560
|
+
log7.success("Langfuse database created");
|
|
1561
|
+
}
|
|
1463
1562
|
log7.step("Step 9/9 Verifying health");
|
|
1464
1563
|
let healthy = false;
|
|
1465
1564
|
while (!healthy) {
|
|
@@ -1514,7 +1613,12 @@ Files created: docker-compose.yml, Caddyfile, .env.web, .env.engine, lead-routin
|
|
|
1514
1613
|
const { dir: newDir } = generateFiles(cfg, sshCfg, {
|
|
1515
1614
|
licenseKey: licenseResult.key,
|
|
1516
1615
|
licenseTier: licenseResult.tier
|
|
1517
|
-
}
|
|
1616
|
+
}, enableAgentApi ? {
|
|
1617
|
+
langfuseUrl,
|
|
1618
|
+
langfuseSecret,
|
|
1619
|
+
langfuseSalt,
|
|
1620
|
+
dbPassword: cfg.dbPassword
|
|
1621
|
+
} : void 0);
|
|
1518
1622
|
await uploadFiles(ssh, newDir, remoteDir);
|
|
1519
1623
|
log7.step("Restarting services with new config...");
|
|
1520
1624
|
await startServices(ssh, remoteDir);
|
|
@@ -1572,7 +1676,7 @@ Click "Connect HubSpot" to authorize the integration.`,
|
|
|
1572
1676
|
"Content-Type": "application/json",
|
|
1573
1677
|
"Cookie": cookieHeader
|
|
1574
1678
|
},
|
|
1575
|
-
body: JSON.stringify({ name: "Claude Code MCP", scopes: ["read", "write", "route"] })
|
|
1679
|
+
body: JSON.stringify({ name: "Claude Code MCP", scopes: ["read", "write", "route", "agent"] })
|
|
1576
1680
|
});
|
|
1577
1681
|
if (tokenRes.ok) {
|
|
1578
1682
|
const tokenData = await tokenRes.json();
|
|
@@ -1586,7 +1690,7 @@ Click "Connect HubSpot" to authorize the integration.`,
|
|
|
1586
1690
|
if (webhookSecret) {
|
|
1587
1691
|
const mcpDir = join5(homedir3(), ".lead-routing");
|
|
1588
1692
|
mkdirSync3(mcpDir, { recursive: true });
|
|
1589
|
-
const mcpConfig = { appUrl: cfg.appUrl, engineUrl: cfg.engineUrl, webhookSecret };
|
|
1693
|
+
const mcpConfig = { appUrl: cfg.appUrl, engineUrl: cfg.engineUrl, webhookSecret, crmType: cfg.crmType || "salesforce" };
|
|
1590
1694
|
if (apiToken) mcpConfig.apiToken = apiToken;
|
|
1591
1695
|
writeFileSync4(
|
|
1592
1696
|
join5(mcpDir, "mcp.json"),
|
|
@@ -1605,12 +1709,15 @@ Click "Connect HubSpot" to authorize the integration.`,
|
|
|
1605
1709
|
` : ` ${chalk2.cyan("2.")} Go to Integrations \u2192 HubSpot \u2192 Connect
|
|
1606
1710
|
${chalk2.cyan("3.")} Authorize the HubSpot integration
|
|
1607
1711
|
`;
|
|
1712
|
+
const agentApiLines = enableAgentApi ? ` Langfuse dashboard: ${chalk2.cyan(langfuseUrl)}
|
|
1713
|
+
MCP config: ~/.lead-routing/mcp.json
|
|
1714
|
+
` : "";
|
|
1608
1715
|
outro(
|
|
1609
1716
|
chalk2.green("\u2714 You're live!") + `
|
|
1610
1717
|
|
|
1611
1718
|
Dashboard: ${chalk2.cyan(cfg.appUrl)}
|
|
1612
1719
|
Routing engine: ${chalk2.cyan(cfg.engineUrl)}
|
|
1613
|
-
|
|
1720
|
+
` + agentApiLines + `
|
|
1614
1721
|
Admin email: ${chalk2.white(cfg.adminEmail)}
|
|
1615
1722
|
|
|
1616
1723
|
` + chalk2.bold(" Next steps:\n") + ` ${chalk2.cyan("1.")} Open ${chalk2.cyan(cfg.appUrl)} and log in
|
|
@@ -0,0 +1,76 @@
|
|
|
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;
|