@xeplr/workflow 1.0.1
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/LICENSE +21 -0
- package/README.md +352 -0
- package/bin/www +7 -0
- package/db/xcfgSetup.js +8 -0
- package/env.required.js +41 -0
- package/index.js +313 -0
- package/lib/actionCatalog.js +193 -0
- package/lib/actions/jobRun.js +149 -0
- package/lib/actions/screenShow.js +55 -0
- package/lib/db.js +82 -0
- package/lib/envExposed.js +87 -0
- package/lib/flows.js +832 -0
- package/lib/flowsRouter.js +113 -0
- package/lib/router.js +260 -0
- package/lib/workflowRunner.js +649 -0
- package/migrations/0001_companies.sql +23 -0
- package/migrations/0002_workspaces.sql +22 -0
- package/migrations/0003_workflows.sql +35 -0
- package/migrations/0004_workflow_steps.sql +79 -0
- package/migrations/0005_workflow_runs.sql +49 -0
- package/migrations/0006_workflow_step_runs.sql +42 -0
- package/migrations/0007_workflow_resume_keys.sql +42 -0
- package/migrations/0008_workflow_run_edges.sql +43 -0
- package/migrations/0009_workflow_steps_layout.sql +11 -0
- package/migrations/0010_workflow_steps_sample_output.sql +17 -0
- package/migrations/0011_workflow_steps_params.sql +27 -0
- package/migrations/0012_workflows_kind.sql +27 -0
- package/migrations/0013_workflows_key.sql +48 -0
- package/migrations-auth/0001_workflow_access.sql +129 -0
- package/migrations-auth/0003_nav_menus.sql +62 -0
- package/migrations-auth/0004_flows_access.sql +76 -0
- package/models/Company.js +82 -0
- package/models/Workflow.js +63 -0
- package/models/WorkflowResumeKey.js +32 -0
- package/models/WorkflowRun.js +64 -0
- package/models/WorkflowRunEdge.js +49 -0
- package/models/WorkflowStep.js +66 -0
- package/models/WorkflowStepRun.js +49 -0
- package/models/Workspace.js +75 -0
- package/models/index.js +25 -0
- package/orchestration/standalone.js +123 -0
- package/package.json +69 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
-- 0002_workspaces.sql
|
|
2
|
+
-- workspaces — the second tenancy level, under a company (mtId2 on every row
|
|
3
|
+
-- that lives inside one). Where a team's workflows are organised.
|
|
4
|
+
|
|
5
|
+
CREATE TABLE "workspaces" (
|
|
6
|
+
"id" varchar(25) PRIMARY KEY,
|
|
7
|
+
"companyId" varchar(25) NOT NULL,
|
|
8
|
+
"name" varchar(255) NOT NULL,
|
|
9
|
+
"description" varchar(500),
|
|
10
|
+
"tags" jsonb,
|
|
11
|
+
"isActive" boolean DEFAULT true,
|
|
12
|
+
"mtId1" varchar(25),
|
|
13
|
+
"mtId2" varchar(25),
|
|
14
|
+
"mtId3" varchar(25),
|
|
15
|
+
"mtId4" varchar(25),
|
|
16
|
+
"recordCreatedDate" timestamp,
|
|
17
|
+
"recordModifiedDate" timestamp,
|
|
18
|
+
"recordCreatedBy" varchar(25),
|
|
19
|
+
"recordModifiedBy" varchar(25)
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
CREATE INDEX "workspaces_company_index" ON "workspaces" ("companyId");
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
-- 0003_workflows.sql
|
|
2
|
+
-- workflows — the product's one document. A workflow is an ordered list of
|
|
3
|
+
-- steps, and a step names a registered @xeplr/actions action plus the input to
|
|
4
|
+
-- call it with. Binding actions together is the whole of what this app does;
|
|
5
|
+
-- everything else exists to make that list savable, runnable and auditable.
|
|
6
|
+
--
|
|
7
|
+
-- `params` are the workflow's own declared inputs — what a caller supplies at
|
|
8
|
+
-- run time (a file path, a customer id). Steps reference them as `{params.x}`
|
|
9
|
+
-- in a bound value, so the same workflow runs against different subjects
|
|
10
|
+
-- without being edited.
|
|
11
|
+
|
|
12
|
+
CREATE TABLE "workflows" (
|
|
13
|
+
"id" varchar(25) PRIMARY KEY,
|
|
14
|
+
"name" varchar(255) NOT NULL,
|
|
15
|
+
"description" varchar(1000),
|
|
16
|
+
-- Declared inputs: [{ name, type, required, default, description, order }] —
|
|
17
|
+
-- the same shape @xeplr/actions uses for an action's inputSchema, so a
|
|
18
|
+
-- workflow reads as an action made of actions.
|
|
19
|
+
"params" jsonb,
|
|
20
|
+
-- Draft workflows are editable and not runnable; published ones are what a
|
|
21
|
+
-- trigger is allowed to start. One column rather than a separate table
|
|
22
|
+
-- because it is a property of the workflow, not a thing of its own.
|
|
23
|
+
"status" varchar(20) DEFAULT 'draft',
|
|
24
|
+
"isActive" boolean DEFAULT true,
|
|
25
|
+
"mtId1" varchar(25),
|
|
26
|
+
"mtId2" varchar(25),
|
|
27
|
+
"mtId3" varchar(25),
|
|
28
|
+
"mtId4" varchar(25),
|
|
29
|
+
"recordCreatedDate" timestamp,
|
|
30
|
+
"recordModifiedDate" timestamp,
|
|
31
|
+
"recordCreatedBy" varchar(25),
|
|
32
|
+
"recordModifiedBy" varchar(25)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
CREATE INDEX "workflows_status_index" ON "workflows" ("status");
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
-- 0004_workflow_steps.sql
|
|
2
|
+
-- workflow_steps — one call to one registered action, plus what happens after
|
|
3
|
+
-- it. `actionName` is a REFERENCE INTO THE @xeplr/actions REGISTRY, not a copy
|
|
4
|
+
-- of the action: no code, no endpoint, no credentials live here. A step whose
|
|
5
|
+
-- action the registry does not have cannot run, which is reported rather than
|
|
6
|
+
-- guessed at.
|
|
7
|
+
--
|
|
8
|
+
-- `values` is the argument object, and its string values may carry
|
|
9
|
+
-- placeholders — `{previous_step.output.x}`, `{steps.<key>.output.x}`,
|
|
10
|
+
-- `{params.x}`, and for a `kind = 'wait'` step, `{resumeKey}` — resolved by
|
|
11
|
+
-- @xeplr/schema-handler's templating against the run's context before the
|
|
12
|
+
-- action is called.
|
|
13
|
+
--
|
|
14
|
+
-- `kind`: 'auto' runs and advances the moment it finishes; 'wait' pauses the
|
|
15
|
+
-- run after starting — the command it calls is responsible for eventually
|
|
16
|
+
-- resolving the pause via workflowRunner.resumeByKey(key, output). See
|
|
17
|
+
-- 0007_workflow_resume_keys.sql for how that key is tracked.
|
|
18
|
+
--
|
|
19
|
+
-- `transitions` is an ordered array of
|
|
20
|
+
-- { condition, mode: 'single' | 'each', target }
|
|
21
|
+
-- evaluated top to bottom — first match wins. `condition` is a structured
|
|
22
|
+
-- @xeplr/expression-handler expression ({ left, op, right }) or null/absent,
|
|
23
|
+
-- which always matches (the catch-all row). `target` is another step's
|
|
24
|
+
-- `stepKey`, or the sentinel 'end_success' / 'end_failed'. A step with no
|
|
25
|
+
-- transitions at all keeps the old default: advance to the next step by
|
|
26
|
+
-- `position`. A step WITH transitions that finds no match (and has no
|
|
27
|
+
-- catch-all row) fails — silently falling through would hide a branch nobody
|
|
28
|
+
-- accounted for.
|
|
29
|
+
--
|
|
30
|
+
-- A transition marked 'each' fans the rest of that branch out once per
|
|
31
|
+
-- matching element instead of running it once — see workflow_run_edges for
|
|
32
|
+
-- how the resulting child runs relate back to this one. `joinStep`, if set,
|
|
33
|
+
-- names a step to run a single time once every one of those children has
|
|
34
|
+
-- finished (e.g. batch-move everything that was downloaded, instead of once
|
|
35
|
+
-- per item); only meaningful when at least one transition here is 'each'.
|
|
36
|
+
--
|
|
37
|
+
-- `onError` is unrelated to transitions — it governs what happens when the
|
|
38
|
+
-- action call itself throws (bad input, unreachable endpoint), not how a
|
|
39
|
+
-- successful result is routed: 'stop' (default) halts the run, 'continue'
|
|
40
|
+
-- lets the rest of the steps proceed regardless.
|
|
41
|
+
--
|
|
42
|
+
-- `stepKey` is how another step's transition or a later step's bound value
|
|
43
|
+
-- addresses this one. Author-facing and stable across reordering, which
|
|
44
|
+
-- `position` is not — insert a step in the middle and every position below it
|
|
45
|
+
-- changes, so a reference by position would silently start reading a
|
|
46
|
+
-- different step's output.
|
|
47
|
+
|
|
48
|
+
CREATE TABLE "workflow_steps" (
|
|
49
|
+
"id" varchar(25) PRIMARY KEY,
|
|
50
|
+
"workflowId" varchar(25) NOT NULL,
|
|
51
|
+
"stepKey" varchar(64) NOT NULL,
|
|
52
|
+
"name" varchar(255),
|
|
53
|
+
"actionName" varchar(128) NOT NULL,
|
|
54
|
+
"values" jsonb,
|
|
55
|
+
-- auto · wait
|
|
56
|
+
"kind" varchar(20) NOT NULL DEFAULT 'auto',
|
|
57
|
+
-- Only meaningful for kind = 'wait'. Milliseconds; null means no expiry.
|
|
58
|
+
"timeoutMs" integer,
|
|
59
|
+
-- stop · continue — what an execution failure of THIS step means for the
|
|
60
|
+
-- rest of the run.
|
|
61
|
+
"onError" varchar(20) DEFAULT 'stop',
|
|
62
|
+
"transitions" jsonb,
|
|
63
|
+
"joinStep" varchar(64),
|
|
64
|
+
"position" integer DEFAULT 0,
|
|
65
|
+
"isActive" boolean DEFAULT true,
|
|
66
|
+
"mtId1" varchar(25),
|
|
67
|
+
"mtId2" varchar(25),
|
|
68
|
+
"mtId3" varchar(25),
|
|
69
|
+
"mtId4" varchar(25),
|
|
70
|
+
"recordCreatedDate" timestamp,
|
|
71
|
+
"recordModifiedDate" timestamp,
|
|
72
|
+
"recordCreatedBy" varchar(25),
|
|
73
|
+
"recordModifiedBy" varchar(25)
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
CREATE INDEX "workflow_steps_workflow_index" ON "workflow_steps" ("workflowId");
|
|
77
|
+
-- Two steps with one key would make a `steps.<key>.output` reference (and a
|
|
78
|
+
-- transition `target`) ambiguous — resolving to whichever the reader picks.
|
|
79
|
+
CREATE UNIQUE INDEX "workflow_steps_key_unique" ON "workflow_steps" ("workflowId", "stepKey");
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
-- 0005_workflow_runs.sql
|
|
2
|
+
-- workflow_runs — one occurrence of one workflow: the thread that ties a
|
|
3
|
+
-- send-and-wait email, a UI form render, and everything that resumes them
|
|
4
|
+
-- back together. Persisted rather than kept in memory, because "what did this
|
|
5
|
+
-- do last night" is the question a workflow product exists to answer, and a
|
|
6
|
+
-- run that only lives in a process is a run nobody can look at after a
|
|
7
|
+
-- restart or a crash mid-wait.
|
|
8
|
+
--
|
|
9
|
+
-- `params` records the values the run was STARTED WITH, so a result can be
|
|
10
|
+
-- reproduced — re-resolving them later would read today's defaults over
|
|
11
|
+
-- yesterday's run.
|
|
12
|
+
--
|
|
13
|
+
-- `item` is set only on a CHILD run — the one element (out of however many an
|
|
14
|
+
-- 'each' transition matched) this particular occurrence exists to process.
|
|
15
|
+
-- Denormalized here for the runner's own hot-path reads; workflow_run_edges
|
|
16
|
+
-- is still the source of truth for the parent/child relationship and its
|
|
17
|
+
-- provenance (which step fanned out, which position this was).
|
|
18
|
+
--
|
|
19
|
+
-- `status`: queued · running · waiting · success · failed. 'waiting' is
|
|
20
|
+
-- distinct from 'running' — it means the run is paused on a wait step with a
|
|
21
|
+
-- live entry in workflow_resume_keys, not actively doing anything.
|
|
22
|
+
|
|
23
|
+
CREATE TABLE "workflow_runs" (
|
|
24
|
+
"id" varchar(25) PRIMARY KEY,
|
|
25
|
+
"workflowId" varchar(25) NOT NULL,
|
|
26
|
+
"status" varchar(20) NOT NULL DEFAULT 'queued',
|
|
27
|
+
"params" jsonb,
|
|
28
|
+
"item" jsonb,
|
|
29
|
+
-- Who or what asked: 'manual', or the name of whatever triggers it later.
|
|
30
|
+
"trigger" varchar(64) DEFAULT 'manual',
|
|
31
|
+
"startedAt" timestamp,
|
|
32
|
+
"finishedAt" timestamp,
|
|
33
|
+
"durationMs" integer,
|
|
34
|
+
-- The failure that ended the run, if one did. Per-step detail lives in
|
|
35
|
+
-- workflow_step_runs; this is the summary a list needs without a join.
|
|
36
|
+
"error" jsonb,
|
|
37
|
+
"isActive" boolean DEFAULT true,
|
|
38
|
+
"mtId1" varchar(25),
|
|
39
|
+
"mtId2" varchar(25),
|
|
40
|
+
"mtId3" varchar(25),
|
|
41
|
+
"mtId4" varchar(25),
|
|
42
|
+
"recordCreatedDate" timestamp,
|
|
43
|
+
"recordModifiedDate" timestamp,
|
|
44
|
+
"recordCreatedBy" varchar(25),
|
|
45
|
+
"recordModifiedBy" varchar(25)
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
CREATE INDEX "workflow_runs_workflow_index" ON "workflow_runs" ("workflowId");
|
|
49
|
+
CREATE INDEX "workflow_runs_status_index" ON "workflow_runs" ("status");
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
-- 0006_workflow_step_runs.sql
|
|
2
|
+
-- workflow_step_runs — what each step actually did while a run happened.
|
|
3
|
+
--
|
|
4
|
+
-- `stepKey` / `actionName` are COPIED from the step, not joined to it. A step
|
|
5
|
+
-- can be renamed, re-pointed at another action, or deleted after this ran;
|
|
6
|
+
-- the history has to keep saying what actually happened, which a join would
|
|
7
|
+
-- quietly rewrite out from under it.
|
|
8
|
+
--
|
|
9
|
+
-- `input` is the values object AFTER placeholders were resolved — what the
|
|
10
|
+
-- action was really called with, the only version worth keeping when a run
|
|
11
|
+
-- has gone wrong for reasons the template explains.
|
|
12
|
+
--
|
|
13
|
+
-- `status`: pending · running · waiting · success · failed · skipped.
|
|
14
|
+
-- 'waiting' mirrors the run's own status while this step holds a live
|
|
15
|
+
-- workflow_resume_keys row. 'skipped' is for a step a transition jumped over
|
|
16
|
+
-- — it never ran, and saying so plainly is the point of listing it as a
|
|
17
|
+
-- distinct status rather than leaving it absent.
|
|
18
|
+
|
|
19
|
+
CREATE TABLE "workflow_step_runs" (
|
|
20
|
+
"id" varchar(25) PRIMARY KEY,
|
|
21
|
+
"runId" varchar(25) NOT NULL,
|
|
22
|
+
"stepId" varchar(25),
|
|
23
|
+
"stepKey" varchar(64),
|
|
24
|
+
"actionName" varchar(128),
|
|
25
|
+
"status" varchar(20) NOT NULL DEFAULT 'pending',
|
|
26
|
+
"input" jsonb,
|
|
27
|
+
"output" jsonb,
|
|
28
|
+
"error" jsonb,
|
|
29
|
+
"durationMs" integer,
|
|
30
|
+
"position" integer DEFAULT 0,
|
|
31
|
+
"isActive" boolean DEFAULT true,
|
|
32
|
+
"mtId1" varchar(25),
|
|
33
|
+
"mtId2" varchar(25),
|
|
34
|
+
"mtId3" varchar(25),
|
|
35
|
+
"mtId4" varchar(25),
|
|
36
|
+
"recordCreatedDate" timestamp,
|
|
37
|
+
"recordModifiedDate" timestamp,
|
|
38
|
+
"recordCreatedBy" varchar(25),
|
|
39
|
+
"recordModifiedBy" varchar(25)
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
CREATE INDEX "workflow_step_runs_run_index" ON "workflow_step_runs" ("runId");
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
-- 0007_workflow_resume_keys.sql
|
|
2
|
+
-- workflow_resume_keys — the whole mechanism behind a 'wait' step, in one
|
|
3
|
+
-- table. When a wait step starts, the engine mints a random opaque `key`
|
|
4
|
+
-- here (NOT encrypted, NOT signed — just a securely random, one-time id, same
|
|
5
|
+
-- as every other row's `id` in this app) and hands it to the step's own
|
|
6
|
+
-- values as `{resumeKey}`, so the command can embed it wherever its channel
|
|
7
|
+
-- needs it: a confirmation link, a hidden field, an approval URL.
|
|
8
|
+
--
|
|
9
|
+
-- The consumer-side contract is exactly one call:
|
|
10
|
+
-- workflowRunner.resumeByKey(key, output)
|
|
11
|
+
-- It never needs to know which run or step the key belongs to — that lookup
|
|
12
|
+
-- lives entirely in this table, keyed by `key` alone. This is what lets
|
|
13
|
+
-- fan-out (see workflow_run_edges) work with zero changes to this mechanism:
|
|
14
|
+
-- once each matching item is its own child run, each one only ever has ONE
|
|
15
|
+
-- live pending key at a time, so there is never a collision to resolve.
|
|
16
|
+
--
|
|
17
|
+
-- `consumedDate` is set the moment resumeByKey succeeds OR the run advances
|
|
18
|
+
-- past this step by any other path (a transition routing elsewhere, the step
|
|
19
|
+
-- timing out) — a consumed or superseded key must not resolve a second call,
|
|
20
|
+
-- whether that call is a genuine retry or a stale/replayed link (an email
|
|
21
|
+
-- security scanner pre-fetching a confirmation URL is the case this is
|
|
22
|
+
-- actually for).
|
|
23
|
+
|
|
24
|
+
CREATE TABLE "workflow_resume_keys" (
|
|
25
|
+
"id" varchar(25) PRIMARY KEY,
|
|
26
|
+
"runId" varchar(25) NOT NULL,
|
|
27
|
+
"stepId" varchar(25) NOT NULL,
|
|
28
|
+
"key" varchar(64) NOT NULL,
|
|
29
|
+
"consumedDate" timestamp,
|
|
30
|
+
"isActive" boolean DEFAULT true,
|
|
31
|
+
"mtId1" varchar(25),
|
|
32
|
+
"mtId2" varchar(25),
|
|
33
|
+
"mtId3" varchar(25),
|
|
34
|
+
"mtId4" varchar(25),
|
|
35
|
+
"recordCreatedDate" timestamp,
|
|
36
|
+
"recordModifiedDate" timestamp,
|
|
37
|
+
"recordCreatedBy" varchar(25),
|
|
38
|
+
"recordModifiedBy" varchar(25)
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE UNIQUE INDEX "workflow_resume_keys_key_unique" ON "workflow_resume_keys" ("key");
|
|
42
|
+
CREATE INDEX "workflow_resume_keys_run_index" ON "workflow_resume_keys" ("runId");
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
-- 0008_workflow_run_edges.sql
|
|
2
|
+
-- workflow_run_edges — the ONLY place a workflow run's family tree lives.
|
|
3
|
+
-- workflow_steps stays flat by design (fan-out is a run-time decision, never
|
|
4
|
+
-- an authoring-time one — see the 'each' transition mode in
|
|
5
|
+
-- 0004_workflow_steps.sql), so nothing about a run's ancestry belongs on the
|
|
6
|
+
-- step definition. This table is the edge itself: one row per parent → child
|
|
7
|
+
-- run relationship, created once when an 'each' transition matches more than
|
|
8
|
+
-- zero elements.
|
|
9
|
+
--
|
|
10
|
+
-- `sourceStepKey` names which step's transition did the fanning out, so a
|
|
11
|
+
-- parent's timeline can say "this child came from check_unread_mail" without
|
|
12
|
+
-- guessing. `itemIndex` / `itemKey` position the child within that fan-out —
|
|
13
|
+
-- `itemKey` is a stable identifier from the item itself when one exists (an
|
|
14
|
+
-- email's message-id, say) rather than just an index, so a retry or a re-run
|
|
15
|
+
-- can still tell which item is which. `item` is the same value the child run
|
|
16
|
+
-- also carries on its own `item` column — kept here too because this table,
|
|
17
|
+
-- not the run, is what a parent's UI would actually join through to list
|
|
18
|
+
-- "everything this step fanned out into."
|
|
19
|
+
--
|
|
20
|
+
-- One child never has two parents: `childRunId` is unique. A parent can have
|
|
21
|
+
-- many children, which is the whole point.
|
|
22
|
+
|
|
23
|
+
CREATE TABLE "workflow_run_edges" (
|
|
24
|
+
"id" varchar(25) PRIMARY KEY,
|
|
25
|
+
"parentRunId" varchar(25) NOT NULL,
|
|
26
|
+
"childRunId" varchar(25) NOT NULL,
|
|
27
|
+
"sourceStepKey" varchar(64),
|
|
28
|
+
"itemIndex" integer,
|
|
29
|
+
"itemKey" varchar(255),
|
|
30
|
+
"item" jsonb,
|
|
31
|
+
"isActive" boolean DEFAULT true,
|
|
32
|
+
"mtId1" varchar(25),
|
|
33
|
+
"mtId2" varchar(25),
|
|
34
|
+
"mtId3" varchar(25),
|
|
35
|
+
"mtId4" varchar(25),
|
|
36
|
+
"recordCreatedDate" timestamp,
|
|
37
|
+
"recordModifiedDate" timestamp,
|
|
38
|
+
"recordCreatedBy" varchar(25),
|
|
39
|
+
"recordModifiedBy" varchar(25)
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
CREATE INDEX "workflow_run_edges_parent_index" ON "workflow_run_edges" ("parentRunId");
|
|
43
|
+
CREATE UNIQUE INDEX "workflow_run_edges_child_unique" ON "workflow_run_edges" ("childRunId");
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- 0009_workflow_steps_layout.sql
|
|
2
|
+
-- Canvas position is NOT execution order. `position` (0004) is an integer
|
|
3
|
+
-- used to sequence steps when a step has no transitions of its own — insert
|
|
4
|
+
-- one in the middle and every position below it shifts. Where a step sits on
|
|
5
|
+
-- the builder's free-form canvas is a separate, purely visual concern: two
|
|
6
|
+
-- steps can be dragged anywhere relative to each other without changing what
|
|
7
|
+
-- runs after what. `layout` is { x, y } in canvas pixels; null until a step
|
|
8
|
+
-- has been placed by hand, at which point the builder can lay new steps out
|
|
9
|
+
-- automatically (e.g. left to right by `position`).
|
|
10
|
+
|
|
11
|
+
ALTER TABLE "workflow_steps" ADD COLUMN "layout" jsonb;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
-- 0010_workflow_steps_sample_output.sql
|
|
2
|
+
-- What this step is EXPECTED to return, as an example — never what it did
|
|
3
|
+
-- return. Purely a builder concern, like `layout` (0009), and never read by
|
|
4
|
+
-- workflowRunner: a real run's output lives on workflow_step_runs.
|
|
5
|
+
--
|
|
6
|
+
-- It exists because binding one step to another is otherwise done from memory.
|
|
7
|
+
-- A later step's value binds to {steps.<key>.output.something} and a
|
|
8
|
+
-- transition tests output.something, and until now nothing in the product
|
|
9
|
+
-- could tell you what `something` is for a given action — you either ran the
|
|
10
|
+
-- workflow and read the run record, or you guessed. Recording a sample lets
|
|
11
|
+
-- the step editor offer the real field names in a picker, which is also what
|
|
12
|
+
-- makes a mistyped binding catchable at build time rather than at run time.
|
|
13
|
+
--
|
|
14
|
+
-- Nullable, and null simply means the picker has nothing to offer for that
|
|
15
|
+
-- step yet — every existing step keeps working untouched.
|
|
16
|
+
|
|
17
|
+
ALTER TABLE "workflow_steps" ADD COLUMN "sampleOutput" jsonb;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- 0011_workflow_steps_params.sql
|
|
2
|
+
-- `params` — what this step needs the CALLER to supply when the run is
|
|
3
|
+
-- started. Declared here, on the step, because the step is where you find out
|
|
4
|
+
-- you need it: you are binding `{params.customerEmail}` into an email-send's
|
|
5
|
+
-- `to` and there is no such parameter yet.
|
|
6
|
+
--
|
|
7
|
+
-- The workflow's run-start schema is the UNION of every step's declaration
|
|
8
|
+
-- (see workflowRunner's collectParams), not a separate list somebody has to
|
|
9
|
+
-- keep in step with these. That is the whole point of declaring it here —
|
|
10
|
+
-- adding the requirement and adding the binding are one action, and deleting
|
|
11
|
+
-- the step deletes the requirement with it rather than leaving a parameter
|
|
12
|
+
-- the run still demands and nothing reads.
|
|
13
|
+
--
|
|
14
|
+
-- Same field shape as `workflows.params` and as an action's inputSchema:
|
|
15
|
+
-- [{ name, type, required, default, description, order, sample? }]
|
|
16
|
+
-- so the engine validates a run's params with the SAME @xeplr/schema-handler
|
|
17
|
+
-- applySchema that validates a step's input against its action. One definition
|
|
18
|
+
-- of what "required" means, at both levels of the product.
|
|
19
|
+
--
|
|
20
|
+
-- `sample` is the one addition, and it is never sent anywhere: it is what the
|
|
21
|
+
-- builder's { } picker shows beside the name, so a list of parameters reads as
|
|
22
|
+
-- something you can recognise rather than something you must already know.
|
|
23
|
+
--
|
|
24
|
+
-- Nullable, and null means "this step asks nothing of the caller" — which is
|
|
25
|
+
-- every step that existed before this column did.
|
|
26
|
+
|
|
27
|
+
ALTER TABLE "workflow_steps" ADD COLUMN IF NOT EXISTS "params" jsonb;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
-- 0012_workflows_kind.sql
|
|
2
|
+
-- WHAT A WORKFLOW'S STEPS ARE MADE OF — which is a question about the BUILDER,
|
|
3
|
+
-- not about the engine.
|
|
4
|
+
--
|
|
5
|
+
-- Connecting jobs to each other opens the same canvas, saves the same rows and
|
|
6
|
+
-- is driven by the same engine. The only difference is what the palette offers
|
|
7
|
+
-- and what dropping something onto the canvas produces:
|
|
8
|
+
--
|
|
9
|
+
-- 'workflow' the action catalogue; a drop names an action
|
|
10
|
+
-- 'jobs' the job list; a drop becomes a `job-run` step whose
|
|
11
|
+
-- values carry that job's id
|
|
12
|
+
--
|
|
13
|
+
-- workflowRunner NEVER READS THIS. A job step is an ordinary wait step whose
|
|
14
|
+
-- action happens to call the jobs API, so teaching the engine about the
|
|
15
|
+
-- distinction would buy nothing and would put a product concept ("a job") into
|
|
16
|
+
-- the one file that has managed to stay ignorant of every product concept so
|
|
17
|
+
-- far. If this column were dropped tomorrow every existing run would continue
|
|
18
|
+
-- to behave identically.
|
|
19
|
+
--
|
|
20
|
+
-- A STRING, NOT A BOOLEAN, because the next one is already visible: the same
|
|
21
|
+
-- canvas over saved API calls, and after that whatever else gets connected.
|
|
22
|
+
-- `is_job_workflow` would have to be replaced the day that lands, and every
|
|
23
|
+
-- row and query that referenced it with it.
|
|
24
|
+
--
|
|
25
|
+
-- Defaulted to 'workflow' so every workflow that exists today keeps opening
|
|
26
|
+
-- the way it always has.
|
|
27
|
+
ALTER TABLE workflows ADD COLUMN IF NOT EXISTS kind VARCHAR(32) NOT NULL DEFAULT 'workflow';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
-- 0013_workflows_key.sql
|
|
2
|
+
-- `key` — HOW AN APP THAT IS NOT THIS ONE NAMES A WORKFLOW.
|
|
3
|
+
--
|
|
4
|
+
-- Added for kind = 'screens' (a "flow"): a workflow whose every step shows a
|
|
5
|
+
-- screen to a person and waits for them to submit it. A flow is designed and
|
|
6
|
+
-- run through the /flows facade (lib/flows.js) by a different app —
|
|
7
|
+
-- the one that owns the screens — and that app knows its flows by names it
|
|
8
|
+
-- chose ('onboard_employee'), not by ids minted here. Every /flows route is
|
|
9
|
+
-- addressed by this key.
|
|
10
|
+
--
|
|
11
|
+
-- ── why 'screens' needs no column of its own ────────────────────────────
|
|
12
|
+
--
|
|
13
|
+
-- `workflows.kind` (0012) is already an unconstrained VARCHAR(32) with no
|
|
14
|
+
-- CHECK and no enum type — it was written that way deliberately, because the
|
|
15
|
+
-- next kind after 'jobs' was already visible. 'screens' is that next kind, so
|
|
16
|
+
-- it lands with no DDL at all; the list of accepted values lives in the
|
|
17
|
+
-- model's jsonSchema, where a bad value is refused with the field named rather
|
|
18
|
+
-- than as a constraint violation.
|
|
19
|
+
--
|
|
20
|
+
-- The engine still never reads `kind` — a screen step is an ordinary wait step
|
|
21
|
+
-- whose action happens to do nothing. What the value decides is WHO MAY EDIT
|
|
22
|
+
-- THE ROW: the workflow document's own save route refuses a 'screens'
|
|
23
|
+
-- workflow, because those steps are generated from a design held in the other
|
|
24
|
+
-- app and a hand edit here would be silently overwritten by the next PUT
|
|
25
|
+
-- /flows/:key. Reading and running are untouched, so runs and history stay
|
|
26
|
+
-- visible in the builder.
|
|
27
|
+
--
|
|
28
|
+
-- ── the uniqueness rule ─────────────────────────────────────────────────
|
|
29
|
+
--
|
|
30
|
+
-- A key identifies a flow WITHIN A TENANT, not globally: two companies each
|
|
31
|
+
-- having an 'onboard_employee' flow is the normal case, and making the key
|
|
32
|
+
-- globally unique would let the first company to use a word take it from
|
|
33
|
+
-- everyone. So the index is over the tenant slots plus the key.
|
|
34
|
+
--
|
|
35
|
+
-- COALESCE on the mt columns because Postgres treats NULLs as distinct in a
|
|
36
|
+
-- unique index: an app that registered only one tenant level leaves mtId2
|
|
37
|
+
-- null, and without this every row would look unique to the index and the
|
|
38
|
+
-- duplicate it exists to prevent would be inserted happily.
|
|
39
|
+
--
|
|
40
|
+
-- Partial (WHERE "key" IS NOT NULL) because ordinary workflows and jobs
|
|
41
|
+
-- canvases have no key and never will — they are found by id — and hundreds of
|
|
42
|
+
-- null-keyed rows should not have to fit through this index.
|
|
43
|
+
|
|
44
|
+
ALTER TABLE "workflows" ADD COLUMN IF NOT EXISTS "key" varchar(64);
|
|
45
|
+
|
|
46
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "workflows_key_unique"
|
|
47
|
+
ON "workflows" (COALESCE("mtId1", ''), COALESCE("mtId2", ''), "key")
|
|
48
|
+
WHERE "key" IS NOT NULL;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
-- 0001_workflow_access.sql
|
|
2
|
+
-- xeplr-workflow's AUTH-DB extension data — layered on top of auth's base
|
|
3
|
+
-- migrations (runs after them: AUTH_EXT_MIGRATIONS_DIR). Adds this product's
|
|
4
|
+
-- roles + menus, maps Super Admin to everything, and applies the default
|
|
5
|
+
-- role→access matrix. All inserts are insert-if-absent (WHERE NOT EXISTS),
|
|
6
|
+
-- safe to re-run against an already-seeded DB.
|
|
7
|
+
--
|
|
8
|
+
-- Its OWN auth database (AUTH_DB_NAME=xeplr_workflow_auth), not xeplr-bi's.
|
|
9
|
+
-- Sharing one would mean a role granted in BI silently granting workflow
|
|
10
|
+
-- pages, which is not a thing anybody would have decided on purpose.
|
|
11
|
+
|
|
12
|
+
INSERT INTO "roles" (id, name, "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
13
|
+
SELECT encode(gen_random_bytes(12), 'hex'), name, true, '*', now(), now()
|
|
14
|
+
FROM (VALUES ('Super Admin'), ('CompanyAdmin'), ('Creator'), ('Viewer')) AS v(name)
|
|
15
|
+
WHERE NOT EXISTS (SELECT 1 FROM "roles" r WHERE r.name = v.name);
|
|
16
|
+
|
|
17
|
+
-- The drawer reads these: a menu row named here shows up in the nav for any
|
|
18
|
+
-- role mapped to it, and does not exist for a role that isn't.
|
|
19
|
+
-- The drawer reads these: a menu row named here shows up in the nav for any
|
|
20
|
+
-- role mapped to it, and does not exist for a role that isn't. Add the
|
|
21
|
+
-- workflow pages' rows in a LATER migration alongside the pages themselves —
|
|
22
|
+
-- seeding a menu for a page that does not exist yet puts a dead item in the
|
|
23
|
+
-- rail for everyone.
|
|
24
|
+
INSERT INTO "menus" (id, name, "menuGroup", "isPublic", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
25
|
+
SELECT encode(gen_random_bytes(12), 'hex'), name, "group", false, true, '*', now(), now()
|
|
26
|
+
FROM (VALUES
|
|
27
|
+
('Home', 'workflows:view'),
|
|
28
|
+
('Actions', 'workflows:view'),
|
|
29
|
+
('Configuration', 'configuration:view'),
|
|
30
|
+
('Access Control', 'configuration:access:view')
|
|
31
|
+
) AS v(name, "group")
|
|
32
|
+
WHERE NOT EXISTS (SELECT 1 FROM "menus" m WHERE m.name = v.name);
|
|
33
|
+
|
|
34
|
+
-- This product's own APIs. Every route in routes/index.js that should be
|
|
35
|
+
-- gate-able gets a row here, grouped to match the menuGroup/apiGroup values
|
|
36
|
+
-- the role matrix below already reads. `/public/resume/:key` is isPublic —
|
|
37
|
+
-- it's never gated by role at all (a resume click isn't made by a signed-in
|
|
38
|
+
-- user), so it's grouped for bookkeeping only, not for the matrix below to
|
|
39
|
+
-- match against.
|
|
40
|
+
INSERT INTO "apis" (id, name, "apiGroup", "isPublic", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
41
|
+
SELECT encode(gen_random_bytes(12), 'hex'), name, "group", "public", true, '*', now(), now()
|
|
42
|
+
FROM (VALUES
|
|
43
|
+
('List workflows', 'workflows:view', false),
|
|
44
|
+
('Get workflow', 'workflows:view', false),
|
|
45
|
+
('List actions', 'workflows:view', false),
|
|
46
|
+
('Save workflow', 'workflows:create', false),
|
|
47
|
+
('Delete workflow', 'workflows:create', false),
|
|
48
|
+
('Run workflow', 'workflows:run', false),
|
|
49
|
+
('Resume workflow step', 'workflows:public', true)
|
|
50
|
+
) AS v(name, "group", "public")
|
|
51
|
+
WHERE NOT EXISTS (SELECT 1 FROM "apis" a WHERE a."apiGroup" = v."group" AND a.name = v.name);
|
|
52
|
+
|
|
53
|
+
-- Super Admin → everything that now exists (base apis/pages/elements/menus +
|
|
54
|
+
-- this product's menus).
|
|
55
|
+
INSERT INTO "apisRolesMapping" (id, "roleId", "apiId", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
56
|
+
SELECT encode(gen_random_bytes(12), 'hex'), r.id, a.id, true, '*', now(), now()
|
|
57
|
+
FROM "roles" r CROSS JOIN "apis" a
|
|
58
|
+
WHERE r.name = 'Super Admin'
|
|
59
|
+
AND NOT EXISTS (SELECT 1 FROM "apisRolesMapping" m WHERE m."roleId" = r.id AND m."apiId" = a.id);
|
|
60
|
+
|
|
61
|
+
INSERT INTO "uiPagesRolesMapping" (id, "roleId", "uiPageId", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
62
|
+
SELECT encode(gen_random_bytes(12), 'hex'), r.id, p.id, true, '*', now(), now()
|
|
63
|
+
FROM "roles" r CROSS JOIN "uiPages" p
|
|
64
|
+
WHERE r.name = 'Super Admin'
|
|
65
|
+
AND NOT EXISTS (SELECT 1 FROM "uiPagesRolesMapping" m WHERE m."roleId" = r.id AND m."uiPageId" = p.id);
|
|
66
|
+
|
|
67
|
+
INSERT INTO "uiElementsRolesMapping" (id, "roleId", "uiElementId", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
68
|
+
SELECT encode(gen_random_bytes(12), 'hex'), r.id, e.id, true, '*', now(), now()
|
|
69
|
+
FROM "roles" r CROSS JOIN "uiElements" e
|
|
70
|
+
WHERE r.name = 'Super Admin'
|
|
71
|
+
AND NOT EXISTS (SELECT 1 FROM "uiElementsRolesMapping" m WHERE m."roleId" = r.id AND m."uiElementId" = e.id);
|
|
72
|
+
|
|
73
|
+
INSERT INTO "menuRolesMapping" (id, "menuId", "roleId", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
74
|
+
SELECT encode(gen_random_bytes(12), 'hex'), m.id, r.id, true, '*', now(), now()
|
|
75
|
+
FROM "roles" r CROSS JOIN "menus" m
|
|
76
|
+
WHERE r.name = 'Super Admin'
|
|
77
|
+
AND NOT EXISTS (SELECT 1 FROM "menuRolesMapping" x WHERE x."roleId" = r.id AND x."menuId" = m.id);
|
|
78
|
+
|
|
79
|
+
-- Scoped-role default access matrix (global WHAT; per-company/workspace scope
|
|
80
|
+
-- comes from userTenantsMapping, not from this).
|
|
81
|
+
|
|
82
|
+
-- CompanyAdmin: workflows:* and configuration:* (prefix).
|
|
83
|
+
INSERT INTO "menuRolesMapping" (id, "menuId", "roleId", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
84
|
+
SELECT encode(gen_random_bytes(12), 'hex'), m.id, r.id, true, '*', now(), now()
|
|
85
|
+
FROM "roles" r CROSS JOIN "menus" m
|
|
86
|
+
WHERE r.name = 'CompanyAdmin'
|
|
87
|
+
AND (m."menuGroup" LIKE 'workflows:%' OR m."menuGroup" LIKE 'configuration:%')
|
|
88
|
+
AND NOT EXISTS (SELECT 1 FROM "menuRolesMapping" x WHERE x."roleId" = r.id AND x."menuId" = m.id);
|
|
89
|
+
|
|
90
|
+
INSERT INTO "apisRolesMapping" (id, "roleId", "apiId", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
91
|
+
SELECT encode(gen_random_bytes(12), 'hex'), r.id, a.id, true, '*', now(), now()
|
|
92
|
+
FROM "roles" r CROSS JOIN "apis" a
|
|
93
|
+
WHERE r.name = 'CompanyAdmin'
|
|
94
|
+
AND (a."apiGroup" LIKE 'workflows:%' OR a."apiGroup" LIKE 'configuration:%')
|
|
95
|
+
AND NOT EXISTS (SELECT 1 FROM "apisRolesMapping" m WHERE m."roleId" = r.id AND m."apiId" = a.id);
|
|
96
|
+
|
|
97
|
+
-- Creator: can see, build and run. Note that RUNNING is deliberately a
|
|
98
|
+
-- creator's right and not a viewer's — a run has side effects (it sends mail,
|
|
99
|
+
-- moves files, writes to databases), so "can look at it" must not imply "can
|
|
100
|
+
-- fire it".
|
|
101
|
+
INSERT INTO "menuRolesMapping" (id, "menuId", "roleId", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
102
|
+
SELECT encode(gen_random_bytes(12), 'hex'), m.id, r.id, true, '*', now(), now()
|
|
103
|
+
FROM "roles" r CROSS JOIN "menus" m
|
|
104
|
+
WHERE r.name = 'Creator'
|
|
105
|
+
AND m."menuGroup" IN ('workflows:view', 'workflows:create', 'workflows:run')
|
|
106
|
+
AND NOT EXISTS (SELECT 1 FROM "menuRolesMapping" x WHERE x."roleId" = r.id AND x."menuId" = m.id);
|
|
107
|
+
|
|
108
|
+
INSERT INTO "apisRolesMapping" (id, "roleId", "apiId", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
109
|
+
SELECT encode(gen_random_bytes(12), 'hex'), r.id, a.id, true, '*', now(), now()
|
|
110
|
+
FROM "roles" r CROSS JOIN "apis" a
|
|
111
|
+
WHERE r.name = 'Creator'
|
|
112
|
+
AND a."apiGroup" IN ('workflows:view', 'workflows:create', 'workflows:run')
|
|
113
|
+
AND NOT EXISTS (SELECT 1 FROM "apisRolesMapping" m WHERE m."roleId" = r.id AND m."apiId" = a.id);
|
|
114
|
+
|
|
115
|
+
-- Viewer: workflows:view (exact). Sees the workflows and their history; fires
|
|
116
|
+
-- nothing.
|
|
117
|
+
INSERT INTO "menuRolesMapping" (id, "menuId", "roleId", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
118
|
+
SELECT encode(gen_random_bytes(12), 'hex'), m.id, r.id, true, '*', now(), now()
|
|
119
|
+
FROM "roles" r CROSS JOIN "menus" m
|
|
120
|
+
WHERE r.name = 'Viewer'
|
|
121
|
+
AND m."menuGroup" = 'workflows:view'
|
|
122
|
+
AND NOT EXISTS (SELECT 1 FROM "menuRolesMapping" x WHERE x."roleId" = r.id AND x."menuId" = m.id);
|
|
123
|
+
|
|
124
|
+
INSERT INTO "apisRolesMapping" (id, "roleId", "apiId", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
125
|
+
SELECT encode(gen_random_bytes(12), 'hex'), r.id, a.id, true, '*', now(), now()
|
|
126
|
+
FROM "roles" r CROSS JOIN "apis" a
|
|
127
|
+
WHERE r.name = 'Viewer'
|
|
128
|
+
AND a."apiGroup" = 'workflows:view'
|
|
129
|
+
AND NOT EXISTS (SELECT 1 FROM "apisRolesMapping" m WHERE m."roleId" = r.id AND m."apiId" = a.id);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
-- 0003_nav_menus.sql
|
|
2
|
+
-- The rail's new items: Dashboards, Jobs, and the two scope pickers.
|
|
3
|
+
--
|
|
4
|
+
-- A SEPARATE MIGRATION rather than an edit to 0001, because 0001 has already
|
|
5
|
+
-- run — the migrator records what it has applied and will not re-run a file
|
|
6
|
+
-- whose contents changed. Editing it would seed nothing and leave the new items
|
|
7
|
+
-- role-filtered out of everyone's drawer, which looks exactly like a
|
|
8
|
+
-- permissions bug.
|
|
9
|
+
--
|
|
10
|
+
-- Seeded alongside the pages themselves, which is the rule 0001's comment
|
|
11
|
+
-- states: a menu row for a page that does not exist puts a dead item in the
|
|
12
|
+
-- rail for everyone. Dashboards and Jobs are placeholders today, but they are
|
|
13
|
+
-- real routes that render a real page saying so — which is a different thing
|
|
14
|
+
-- from a link that goes nowhere.
|
|
15
|
+
--
|
|
16
|
+
-- Every insert is insert-if-absent, safe to re-run.
|
|
17
|
+
|
|
18
|
+
INSERT INTO "menus" (id, name, "menuGroup", "isPublic", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
19
|
+
SELECT encode(gen_random_bytes(12), 'hex'), name, "group", false, true, '*', now(), now()
|
|
20
|
+
FROM (VALUES
|
|
21
|
+
('Dashboards', 'workflows:view'),
|
|
22
|
+
('Jobs', 'workflows:view'),
|
|
23
|
+
-- The scope pickers are workflows:view rather than configuration:*. Choosing
|
|
24
|
+
-- which workspace you are looking at is not administering anything, and a
|
|
25
|
+
-- Viewer who cannot switch workspace is a Viewer locked into whichever one
|
|
26
|
+
-- they happened to land in.
|
|
27
|
+
('Select Workspace', 'workflows:view'),
|
|
28
|
+
('Select Company', 'workflows:view')
|
|
29
|
+
) AS v(name, "group")
|
|
30
|
+
WHERE NOT EXISTS (SELECT 1 FROM "menus" m WHERE m.name = v.name);
|
|
31
|
+
|
|
32
|
+
-- Super Admin → everything, including whatever was just added.
|
|
33
|
+
INSERT INTO "menuRolesMapping" (id, "menuId", "roleId", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
34
|
+
SELECT encode(gen_random_bytes(12), 'hex'), m.id, r.id, true, '*', now(), now()
|
|
35
|
+
FROM "roles" r CROSS JOIN "menus" m
|
|
36
|
+
WHERE r.name = 'Super Admin'
|
|
37
|
+
AND NOT EXISTS (SELECT 1 FROM "menuRolesMapping" x WHERE x."roleId" = r.id AND x."menuId" = m.id);
|
|
38
|
+
|
|
39
|
+
-- And the scoped roles, on the same matrix 0001 established: CompanyAdmin gets
|
|
40
|
+
-- the workflows:* prefix, Creator and Viewer get exact groups. Re-run here so
|
|
41
|
+
-- the new rows are covered — 0001's inserts only saw the menus that existed
|
|
42
|
+
-- when it ran.
|
|
43
|
+
INSERT INTO "menuRolesMapping" (id, "menuId", "roleId", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
44
|
+
SELECT encode(gen_random_bytes(12), 'hex'), m.id, r.id, true, '*', now(), now()
|
|
45
|
+
FROM "roles" r CROSS JOIN "menus" m
|
|
46
|
+
WHERE r.name = 'CompanyAdmin'
|
|
47
|
+
AND (m."menuGroup" LIKE 'workflows:%' OR m."menuGroup" LIKE 'configuration:%')
|
|
48
|
+
AND NOT EXISTS (SELECT 1 FROM "menuRolesMapping" x WHERE x."roleId" = r.id AND x."menuId" = m.id);
|
|
49
|
+
|
|
50
|
+
INSERT INTO "menuRolesMapping" (id, "menuId", "roleId", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
51
|
+
SELECT encode(gen_random_bytes(12), 'hex'), m.id, r.id, true, '*', now(), now()
|
|
52
|
+
FROM "roles" r CROSS JOIN "menus" m
|
|
53
|
+
WHERE r.name = 'Creator'
|
|
54
|
+
AND m."menuGroup" IN ('workflows:view', 'workflows:create', 'workflows:run')
|
|
55
|
+
AND NOT EXISTS (SELECT 1 FROM "menuRolesMapping" x WHERE x."roleId" = r.id AND x."menuId" = m.id);
|
|
56
|
+
|
|
57
|
+
INSERT INTO "menuRolesMapping" (id, "menuId", "roleId", "isActive", "mtId1", "recordCreatedDate", "recordModifiedDate")
|
|
58
|
+
SELECT encode(gen_random_bytes(12), 'hex'), m.id, r.id, true, '*', now(), now()
|
|
59
|
+
FROM "roles" r CROSS JOIN "menus" m
|
|
60
|
+
WHERE r.name = 'Viewer'
|
|
61
|
+
AND m."menuGroup" = 'workflows:view'
|
|
62
|
+
AND NOT EXISTS (SELECT 1 FROM "menuRolesMapping" x WHERE x."roleId" = r.id AND x."menuId" = m.id);
|