forked from tsai/budibase
12 changed files with 549 additions and 497 deletions
@ -1,330 +0,0 @@ |
|||
let accessLevels = require("../../../utilities/accessLevels") |
|||
let conditions = require("../../../workflows/logic").LogicConditions |
|||
|
|||
const ACTION = { |
|||
SAVE_RECORD: { |
|||
name: "Save Record", |
|||
tagline: "Save a {{inputs.enriched.model.name}} record", |
|||
icon: "ri-save-3-fill", |
|||
description: "Save a record to your database", |
|||
type: "ACTION", |
|||
inputs: {}, |
|||
schema: { |
|||
inputs: { |
|||
properties: { |
|||
record: { |
|||
type: "object", |
|||
properties: { |
|||
modelId: { |
|||
type: "string", |
|||
customType: "model", |
|||
title: "Table", |
|||
}, |
|||
}, |
|||
customType: "record", |
|||
title: "Table", |
|||
default: {}, |
|||
required: ["modelId"], |
|||
}, |
|||
}, |
|||
required: ["record"], |
|||
}, |
|||
outputs: { |
|||
properties: { |
|||
response: { |
|||
type: "object", |
|||
description: "The response from the table", |
|||
}, |
|||
success: { |
|||
type: "boolean", |
|||
description: "Whether the action was successful", |
|||
}, |
|||
id: { |
|||
type: "string", |
|||
description: "The identifier of the new record", |
|||
}, |
|||
revision: { |
|||
type: "string", |
|||
description: "The revision of the new record", |
|||
}, |
|||
}, |
|||
required: ["success", "id", "revision"], |
|||
}, |
|||
}, |
|||
}, |
|||
DELETE_RECORD: { |
|||
description: "Delete a record from your database", |
|||
icon: "ri-delete-bin-line", |
|||
name: "Delete Record", |
|||
tagline: "Delete a {{inputs.enriched.model.name}} record", |
|||
type: "ACTION", |
|||
inputs: {}, |
|||
schema: { |
|||
inputs: { |
|||
properties: { |
|||
modelId: { |
|||
type: "string", |
|||
customType: "model", |
|||
title: "Table", |
|||
}, |
|||
id: { |
|||
type: "string", |
|||
title: "Record ID", |
|||
}, |
|||
revision: { |
|||
type: "string", |
|||
title: "Record Revision", |
|||
}, |
|||
}, |
|||
required: ["modelId", "id", "revision"], |
|||
}, |
|||
outputs: { |
|||
properties: { |
|||
record: { |
|||
type: "object", |
|||
customType: "record", |
|||
description: "The deleted record", |
|||
}, |
|||
response: { |
|||
type: "object", |
|||
description: "The response from the table", |
|||
}, |
|||
success: { |
|||
type: "boolean", |
|||
description: "Whether the action was successful", |
|||
}, |
|||
}, |
|||
required: ["record", "success"], |
|||
}, |
|||
}, |
|||
}, |
|||
CREATE_USER: { |
|||
description: "Create a new user", |
|||
tagline: "Create user {{inputs.username}}", |
|||
icon: "ri-user-add-fill", |
|||
name: "Create User", |
|||
type: "ACTION", |
|||
inputs: {}, |
|||
schema: { |
|||
inputs: { |
|||
properties: { |
|||
username: { |
|||
type: "string", |
|||
title: "Username", |
|||
}, |
|||
password: { |
|||
type: "string", |
|||
customType: "password", |
|||
title: "Password", |
|||
}, |
|||
accessLevelId: { |
|||
type: "string", |
|||
title: "Access Level", |
|||
default: accessLevels.POWERUSER_LEVEL_ID, |
|||
enum: accessLevels.ACCESS_LEVELS, |
|||
}, |
|||
}, |
|||
required: ["username", "password", "accessLevelId"], |
|||
}, |
|||
outputs: { |
|||
properties: { |
|||
id: { |
|||
type: "string", |
|||
description: "The identifier of the new user", |
|||
}, |
|||
revision: { |
|||
type: "string", |
|||
description: "The revision of the new user", |
|||
}, |
|||
response: { |
|||
type: "object", |
|||
description: "The response from the user table", |
|||
}, |
|||
success: { |
|||
type: "boolean", |
|||
description: "Whether the action was successful", |
|||
}, |
|||
}, |
|||
required: ["id", "revision", "success"], |
|||
}, |
|||
}, |
|||
}, |
|||
SEND_EMAIL: { |
|||
description: "Send an email.", |
|||
tagline: "Send email to {{inputs.to}}", |
|||
icon: "ri-mail-open-fill", |
|||
name: "Send Email", |
|||
type: "ACTION", |
|||
inputs: {}, |
|||
schema: { |
|||
inputs: { |
|||
properties: { |
|||
to: { |
|||
type: "string", |
|||
title: "Send To", |
|||
}, |
|||
from: { |
|||
type: "string", |
|||
title: "Send From", |
|||
}, |
|||
subject: { |
|||
type: "string", |
|||
title: "Email Subject", |
|||
}, |
|||
contents: { |
|||
type: "string", |
|||
title: "Email Contents", |
|||
}, |
|||
}, |
|||
required: ["to", "from", "subject", "contents"], |
|||
}, |
|||
outputs: { |
|||
properties: { |
|||
success: { |
|||
type: "boolean", |
|||
description: "Whether the email was sent", |
|||
}, |
|||
response: { |
|||
type: "object", |
|||
description: |
|||
"A response from the email client, this may be an error", |
|||
}, |
|||
}, |
|||
required: ["success"], |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
const LOGIC = { |
|||
FILTER: { |
|||
name: "Filter", |
|||
tagline: "{{inputs.filter}} {{inputs.condition}} {{inputs.value}}", |
|||
icon: "ri-git-branch-line", |
|||
description: "Filter any workflows which do not meet certain conditions", |
|||
type: "LOGIC", |
|||
inputs: {}, |
|||
schema: { |
|||
inputs: { |
|||
properties: { |
|||
filter: { |
|||
type: "string", |
|||
title: "Reference Value", |
|||
}, |
|||
condition: { |
|||
type: "string", |
|||
title: "Condition", |
|||
enum: conditions, |
|||
default: "equals", |
|||
}, |
|||
value: { |
|||
type: "string", |
|||
title: "Comparison Value", |
|||
}, |
|||
}, |
|||
required: ["filter", "condition", "value"], |
|||
}, |
|||
outputs: { |
|||
properties: { |
|||
success: { |
|||
type: "boolean", |
|||
description: "Whether the logic block passed", |
|||
}, |
|||
}, |
|||
required: ["success"], |
|||
}, |
|||
}, |
|||
}, |
|||
DELAY: { |
|||
name: "Delay", |
|||
icon: "ri-time-fill", |
|||
tagline: "Delay for {{inputs.time}} milliseconds", |
|||
description: "Delay the workflow until an amount of time has passed", |
|||
inputs: {}, |
|||
schema: { |
|||
inputs: { |
|||
properties: { |
|||
time: { |
|||
type: "number", |
|||
title: "Delay in milliseconds", |
|||
}, |
|||
}, |
|||
required: ["time"], |
|||
}, |
|||
}, |
|||
type: "LOGIC", |
|||
}, |
|||
} |
|||
|
|||
const TRIGGER = { |
|||
RECORD_SAVED: { |
|||
name: "Record Saved", |
|||
event: "record:save", |
|||
icon: "ri-save-line", |
|||
tagline: "Record is added to {{inputs.enriched.model.name}}", |
|||
description: "Fired when a record is saved to your database", |
|||
inputs: {}, |
|||
schema: { |
|||
inputs: { |
|||
properties: { |
|||
modelId: { |
|||
type: "string", |
|||
customType: "model", |
|||
title: "Table", |
|||
}, |
|||
}, |
|||
required: ["modelId"], |
|||
}, |
|||
outputs: { |
|||
properties: { |
|||
record: { |
|||
type: "object", |
|||
customType: "record", |
|||
description: "The new record that was saved", |
|||
}, |
|||
}, |
|||
required: ["record"], |
|||
}, |
|||
}, |
|||
type: "TRIGGER", |
|||
}, |
|||
RECORD_DELETED: { |
|||
name: "Record Deleted", |
|||
event: "record:delete", |
|||
icon: "ri-delete-bin-line", |
|||
tagline: "Record is deleted from {{inputs.enriched.model.name}}", |
|||
description: "Fired when a record is deleted from your database", |
|||
inputs: {}, |
|||
schema: { |
|||
inputs: { |
|||
properties: { |
|||
modelId: { |
|||
type: "string", |
|||
customType: "model", |
|||
title: "Table", |
|||
}, |
|||
}, |
|||
required: ["modelId"], |
|||
}, |
|||
outputs: { |
|||
properties: { |
|||
record: { |
|||
type: "object", |
|||
customType: "record", |
|||
description: "The record that was deleted", |
|||
}, |
|||
}, |
|||
required: ["record"], |
|||
}, |
|||
}, |
|||
type: "TRIGGER", |
|||
}, |
|||
} |
|||
|
|||
// This contains the definitions for the steps and triggers that make up a workflow, a workflow comprises
|
|||
// of many steps and a single trigger
|
|||
module.exports = { |
|||
ACTION, |
|||
LOGIC, |
|||
TRIGGER, |
|||
} |
|||
@ -1,31 +1,20 @@ |
|||
const wait = ms => new Promise(resolve => setTimeout(resolve, ms)) |
|||
let filter = require("./steps/filter") |
|||
let delay = require("./steps/delay") |
|||
|
|||
let LOGIC = { |
|||
DELAY: async function delay(inputs) { |
|||
await wait(inputs.time) |
|||
}, |
|||
let BUILTIN_LOGIC = { |
|||
DELAY: delay.run, |
|||
FILTER: filter.run, |
|||
} |
|||
|
|||
FILTER: async function filter(inputs) { |
|||
const { field, condition, value } = inputs |
|||
switch (condition) { |
|||
case "equals": |
|||
if (field !== value) return |
|||
break |
|||
default: |
|||
return |
|||
} |
|||
}, |
|||
let BUILTIN_DEFINITIONS = { |
|||
DELAY: delay.definition, |
|||
FILTER: filter.definition, |
|||
} |
|||
|
|||
module.exports.getLogic = function(logicName) { |
|||
if (LOGIC[logicName] != null) { |
|||
return LOGIC[logicName] |
|||
if (BUILTIN_LOGIC[logicName] != null) { |
|||
return BUILTIN_LOGIC[logicName] |
|||
} |
|||
} |
|||
|
|||
module.exports.LogicConditions = [ |
|||
"Equals", |
|||
"Not equals", |
|||
"Greater than", |
|||
"Less than", |
|||
] |
|||
module.exports.BUILTIN_DEFINITIONS = BUILTIN_DEFINITIONS |
|||
|
|||
@ -0,0 +1,82 @@ |
|||
const userController = require("../../api/controllers/user") |
|||
let accessLevels = require("../../utilities/accessLevels") |
|||
|
|||
module.exports.definition = { |
|||
description: "Create a new user", |
|||
tagline: "Create user {{inputs.username}}", |
|||
icon: "ri-user-add-fill", |
|||
name: "Create User", |
|||
type: "ACTION", |
|||
inputs: {}, |
|||
schema: { |
|||
inputs: { |
|||
properties: { |
|||
username: { |
|||
type: "string", |
|||
title: "Username", |
|||
}, |
|||
password: { |
|||
type: "string", |
|||
customType: "password", |
|||
title: "Password", |
|||
}, |
|||
accessLevelId: { |
|||
type: "string", |
|||
title: "Access Level", |
|||
default: accessLevels.POWERUSER_LEVEL_ID, |
|||
enum: accessLevels.ACCESS_LEVELS, |
|||
}, |
|||
}, |
|||
required: ["username", "password", "accessLevelId"], |
|||
}, |
|||
outputs: { |
|||
properties: { |
|||
id: { |
|||
type: "string", |
|||
description: "The identifier of the new user", |
|||
}, |
|||
revision: { |
|||
type: "string", |
|||
description: "The revision of the new user", |
|||
}, |
|||
response: { |
|||
type: "object", |
|||
description: "The response from the user table", |
|||
}, |
|||
success: { |
|||
type: "boolean", |
|||
description: "Whether the action was successful", |
|||
}, |
|||
}, |
|||
required: ["id", "revision", "success"], |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
module.exports.run = async function({ inputs, instanceId }) { |
|||
const { username, password, accessLevelId } = inputs |
|||
const ctx = { |
|||
user: { |
|||
instanceId: instanceId, |
|||
}, |
|||
request: { |
|||
body: { username, password, accessLevelId }, |
|||
}, |
|||
} |
|||
|
|||
try { |
|||
await userController.create(ctx) |
|||
return { |
|||
response: ctx.body, |
|||
id: ctx.body._id, |
|||
revision: ctx.body._rev, |
|||
success: ctx.status === 200, |
|||
} |
|||
} catch (err) { |
|||
console.error(err) |
|||
return { |
|||
success: false, |
|||
response: err, |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,25 @@ |
|||
const wait = ms => new Promise(resolve => setTimeout(resolve, ms)) |
|||
|
|||
module.exports.definition = { |
|||
name: "Delay", |
|||
icon: "ri-time-fill", |
|||
tagline: "Delay for {{inputs.time}} milliseconds", |
|||
description: "Delay the workflow until an amount of time has passed", |
|||
inputs: {}, |
|||
schema: { |
|||
inputs: { |
|||
properties: { |
|||
time: { |
|||
type: "number", |
|||
title: "Delay in milliseconds", |
|||
}, |
|||
}, |
|||
required: ["time"], |
|||
}, |
|||
}, |
|||
type: "LOGIC", |
|||
} |
|||
|
|||
module.exports.run = async function delay({ inputs }) { |
|||
await wait(inputs.time) |
|||
} |
|||
@ -0,0 +1,77 @@ |
|||
const recordController = require("../../api/controllers/record") |
|||
|
|||
module.exports.definition = { |
|||
description: "Delete a record from your database", |
|||
icon: "ri-delete-bin-line", |
|||
name: "Delete Record", |
|||
tagline: "Delete a {{inputs.enriched.model.name}} record", |
|||
type: "ACTION", |
|||
inputs: {}, |
|||
schema: { |
|||
inputs: { |
|||
properties: { |
|||
modelId: { |
|||
type: "string", |
|||
customType: "model", |
|||
title: "Table", |
|||
}, |
|||
id: { |
|||
type: "string", |
|||
title: "Record ID", |
|||
}, |
|||
revision: { |
|||
type: "string", |
|||
title: "Record Revision", |
|||
}, |
|||
}, |
|||
required: ["modelId", "id", "revision"], |
|||
}, |
|||
outputs: { |
|||
properties: { |
|||
record: { |
|||
type: "object", |
|||
customType: "record", |
|||
description: "The deleted record", |
|||
}, |
|||
response: { |
|||
type: "object", |
|||
description: "The response from the table", |
|||
}, |
|||
success: { |
|||
type: "boolean", |
|||
description: "Whether the action was successful", |
|||
}, |
|||
}, |
|||
required: ["record", "success"], |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
module.exports.run = async function({ inputs, instanceId }) { |
|||
// TODO: better logging of when actions are missed due to missing parameters
|
|||
if (inputs.id == null || inputs.revision == null) { |
|||
return |
|||
} |
|||
let ctx = { |
|||
params: { |
|||
modelId: inputs.modelId, |
|||
recordId: inputs.id, |
|||
revId: inputs.revision, |
|||
}, |
|||
user: { instanceId }, |
|||
} |
|||
|
|||
try { |
|||
await recordController.destroy(ctx) |
|||
return { |
|||
response: ctx.body, |
|||
success: ctx.status === 200, |
|||
} |
|||
} catch (err) { |
|||
console.error(err) |
|||
return { |
|||
success: false, |
|||
response: err, |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
const LogicConditions = { |
|||
EQUALS: "Equals", |
|||
NOT_EQUALS: "Not equals", |
|||
GREATER_THAN: "Greater than", |
|||
LESS_THAN: "Less than", |
|||
} |
|||
|
|||
module.exports.definition = { |
|||
name: "Filter", |
|||
tagline: "{{inputs.filter}} {{inputs.condition}} {{inputs.value}}", |
|||
icon: "ri-git-branch-line", |
|||
description: "Filter any workflows which do not meet certain conditions", |
|||
type: "LOGIC", |
|||
inputs: {}, |
|||
schema: { |
|||
inputs: { |
|||
properties: { |
|||
filter: { |
|||
type: "string", |
|||
title: "Reference Value", |
|||
}, |
|||
condition: { |
|||
type: "string", |
|||
title: "Condition", |
|||
enum: Object.values(LogicConditions), |
|||
default: "equals", |
|||
}, |
|||
value: { |
|||
type: "string", |
|||
title: "Comparison Value", |
|||
}, |
|||
}, |
|||
required: ["filter", "condition", "value"], |
|||
}, |
|||
outputs: { |
|||
properties: { |
|||
success: { |
|||
type: "boolean", |
|||
description: "Whether the logic block passed", |
|||
}, |
|||
}, |
|||
required: ["success"], |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
module.exports.run = async function filter({ inputs }) { |
|||
const { field, condition, value } = inputs |
|||
let success |
|||
if (typeof field !== "object" && typeof value !== "object") { |
|||
switch (condition) { |
|||
case LogicConditions.EQUALS: |
|||
success = field === value |
|||
break |
|||
case LogicConditions.NOT_EQUALS: |
|||
success = field !== value |
|||
break |
|||
case LogicConditions.GREATER_THAN: |
|||
success = field > value |
|||
break |
|||
case LogicConditions.LESS_THAN: |
|||
success = field < value |
|||
break |
|||
default: |
|||
return |
|||
} |
|||
} else { |
|||
success = false |
|||
} |
|||
return { success } |
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
const recordController = require("../../api/controllers/record") |
|||
|
|||
module.exports.definition = { |
|||
name: "Save Record", |
|||
tagline: "Save a {{inputs.enriched.model.name}} record", |
|||
icon: "ri-save-3-fill", |
|||
description: "Save a record to your database", |
|||
type: "ACTION", |
|||
inputs: {}, |
|||
schema: { |
|||
inputs: { |
|||
properties: { |
|||
record: { |
|||
type: "object", |
|||
properties: { |
|||
modelId: { |
|||
type: "string", |
|||
customType: "model", |
|||
title: "Table", |
|||
}, |
|||
}, |
|||
customType: "record", |
|||
title: "The record to be written", |
|||
default: {}, |
|||
required: ["modelId"], |
|||
}, |
|||
}, |
|||
required: ["record"], |
|||
}, |
|||
outputs: { |
|||
properties: { |
|||
response: { |
|||
type: "object", |
|||
description: "The response from the table", |
|||
}, |
|||
success: { |
|||
type: "boolean", |
|||
description: "Whether the action was successful", |
|||
}, |
|||
id: { |
|||
type: "string", |
|||
description: "The identifier of the new record", |
|||
}, |
|||
revision: { |
|||
type: "string", |
|||
description: "The revision of the new record", |
|||
}, |
|||
}, |
|||
required: ["success", "id", "revision"], |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
module.exports.run = async function({ inputs, instanceId }) { |
|||
// TODO: better logging of when actions are missed due to missing parameters
|
|||
if (inputs.record == null || inputs.record.modelId == null) { |
|||
return |
|||
} |
|||
// have to clean up the record, remove the model from it
|
|||
const ctx = { |
|||
params: { |
|||
modelId: inputs.record.modelId, |
|||
}, |
|||
request: { |
|||
body: inputs.record, |
|||
}, |
|||
user: { instanceId }, |
|||
} |
|||
|
|||
try { |
|||
await recordController.save(ctx) |
|||
return { |
|||
response: ctx.body, |
|||
id: ctx.body._id, |
|||
revision: ctx.body._rev, |
|||
success: ctx.status === 200, |
|||
} |
|||
} catch (err) { |
|||
console.error(err) |
|||
return { |
|||
success: false, |
|||
response: err, |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
const sgMail = require("@sendgrid/mail") |
|||
sgMail.setApiKey(process.env.SENDGRID_API_KEY) |
|||
|
|||
module.exports.definition = { |
|||
description: "Send an email.", |
|||
tagline: "Send email to {{inputs.to}}", |
|||
icon: "ri-mail-open-fill", |
|||
name: "Send Email", |
|||
type: "ACTION", |
|||
inputs: {}, |
|||
schema: { |
|||
inputs: { |
|||
properties: { |
|||
to: { |
|||
type: "string", |
|||
title: "Send To", |
|||
}, |
|||
from: { |
|||
type: "string", |
|||
title: "Send From", |
|||
}, |
|||
subject: { |
|||
type: "string", |
|||
title: "Email Subject", |
|||
}, |
|||
contents: { |
|||
type: "string", |
|||
title: "Email Contents", |
|||
}, |
|||
}, |
|||
required: ["to", "from", "subject", "contents"], |
|||
}, |
|||
outputs: { |
|||
properties: { |
|||
success: { |
|||
type: "boolean", |
|||
description: "Whether the email was sent", |
|||
}, |
|||
response: { |
|||
type: "object", |
|||
description: "A response from the email client, this may be an error", |
|||
}, |
|||
}, |
|||
required: ["success"], |
|||
}, |
|||
}, |
|||
} |
|||
|
|||
module.exports.run = async function({ inputs }) { |
|||
const msg = { |
|||
to: inputs.to, |
|||
from: inputs.from, |
|||
subject: inputs.subject, |
|||
text: inputs.text, |
|||
} |
|||
|
|||
try { |
|||
let response = await sgMail.send(msg) |
|||
return { |
|||
success: true, |
|||
response, |
|||
} |
|||
} catch (err) { |
|||
console.error(err) |
|||
return { |
|||
success: false, |
|||
response: err, |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue