UNPKG

@budibase/server

Version:
284 lines (249 loc) • 7.14 kB
import { context, docIds, HTTPError, utils } from "@budibase/backend-core" import { isViewId, automations as sharedAutomations, } from "@budibase/shared-core" import { Automation, AutomationTriggerStepId, DocumentType, RowActionData, SEPARATOR, TableRowActions, User, VirtualDocumentType, WithDocMetadata, } from "@budibase/types" import sdk from "../.." import * as triggers from "../../../automations/triggers" import { generateRowActionsID } from "../../../db/utils" import automations from "../automations" async function ensureUniqueAndThrow( doc: TableRowActions, name: string, existingRowActionId?: string ) { const names = await getNames(Object.values(doc.actions)) name = name.toLowerCase().trim() if ( Object.entries(names).find( ([automationId, automationName]) => automationName.toLowerCase().trim() === name && automationId !== existingRowActionId ) ) { throw new HTTPError("A row action with the same name already exists.", 409) } } export async function create(tableId: string, rowAction: { name: string }) { const action = { name: rowAction.name.trim() } const db = context.getWorkspaceDB() const rowActionsId = generateRowActionsID(tableId) let doc = await db.tryGet<TableRowActions>(rowActionsId) if (!doc) { doc = { _id: rowActionsId, actions: {} } } await ensureUniqueAndThrow(doc, action.name) const appId = context.getWorkspaceId() if (!appId) { throw new Error("Could not get the current appId") } const newRowActionId = `${ VirtualDocumentType.ROW_ACTION }${SEPARATOR}${utils.newid()}` const automation = await automations.create({ name: action.name, appId, definition: { trigger: { id: "trigger", ...sharedAutomations.triggers.definitions.ROW_ACTION, stepId: AutomationTriggerStepId.ROW_ACTION, inputs: { tableId, rowActionId: newRowActionId, }, }, steps: [], }, }) doc.actions[newRowActionId] = { automationId: automation._id!, permissions: { table: { runAllowed: true }, views: {}, }, } await db.put(doc) return { id: newRowActionId, name: automation.name, ...doc.actions[newRowActionId], } } export async function get(tableId: string, rowActionId: string) { const actionsDoc = await getAllForTable(tableId) const rowAction = actionsDoc?.actions[rowActionId] if (!rowAction) { throw new HTTPError( `Row action '${rowActionId}' not found in '${tableId}'`, 400 ) } return rowAction } export async function getAllForTable(tableId: string) { const db = context.getWorkspaceDB() const rowActionsId = generateRowActionsID(tableId) return await db.tryGet<TableRowActions>(rowActionsId) } export async function getAll() { const db = context.getWorkspaceDB() return ( await db.allDocs<WithDocMetadata<TableRowActions>>( docIds.getDocParams(DocumentType.ROW_ACTIONS, undefined, { include_docs: true, }) ) ).rows.map(row => row.doc!) } export async function deleteAll(tableId: string) { const db = context.getWorkspaceDB() const doc = await getAllForTable(tableId) if (!doc) { return } const automationIds = Object.values(doc.actions).map(a => a.automationId) const automations = await db.getMultiple<Automation>(automationIds) for (const automation of automations) { await sdk.automations.remove(automation._id!, automation._rev!) } await db.remove(doc) } export async function docExists(tableId: string) { const db = context.getWorkspaceDB() const rowActionsId = generateRowActionsID(tableId) const result = await db.exists(rowActionsId) return result } async function updateDoc( tableId: string, rowActionId: string, transformer: ( tableRowActions: TableRowActions ) => TableRowActions | Promise<TableRowActions> ) { const actionsDoc = await getAllForTable(tableId) const rowAction = actionsDoc?.actions[rowActionId] if (!rowAction) { throw new HTTPError( `Row action '${rowActionId}' not found in '${tableId}'`, 400 ) } const updated = await transformer(actionsDoc) const db = context.getWorkspaceDB() await db.put(updated) return { id: rowActionId, ...updated.actions[rowActionId], } } async function guardView(tableId: string, viewId: string) { let view if (isViewId(viewId)) { view = await sdk.views.get(viewId) } if (!view || view.tableId !== tableId) { throw new HTTPError(`View '${viewId}' not found in '${tableId}'`, 400) } } export async function setTablePermission(tableId: string, rowActionId: string) { return await updateDoc(tableId, rowActionId, async actionsDoc => { actionsDoc.actions[rowActionId].permissions.table.runAllowed = true return actionsDoc }) } export async function unsetTablePermission( tableId: string, rowActionId: string ) { return await updateDoc(tableId, rowActionId, async actionsDoc => { actionsDoc.actions[rowActionId].permissions.table.runAllowed = false return actionsDoc }) } export async function setViewPermission( tableId: string, rowActionId: string, viewId: string ) { await guardView(tableId, viewId) return await updateDoc(tableId, rowActionId, async actionsDoc => { actionsDoc.actions[rowActionId].permissions.views[viewId] = { runAllowed: true, } return actionsDoc }) } export async function unsetViewPermission( tableId: string, rowActionId: string, viewId: string ) { await guardView(tableId, viewId) return await updateDoc(tableId, rowActionId, async actionsDoc => { delete actionsDoc.actions[rowActionId].permissions.views[viewId] return actionsDoc }) } export async function remove(tableId: string, rowActionId: string) { return await updateDoc(tableId, rowActionId, async actionsDoc => { const { automationId } = actionsDoc.actions[rowActionId] const automation = await automations.get(automationId) await automations.remove(automation._id, automation._rev) delete actionsDoc.actions[rowActionId] return actionsDoc }) } export async function run( tableId: any, rowActionId: any, rowId: string, user: User ) { const table = await sdk.tables.getTable(tableId) if (!table) { throw new HTTPError("Table not found", 404) } const { automationId } = await get(tableId, rowActionId) const automation = await sdk.automations.get(automationId) const row = await sdk.rows.find(tableId, rowId) await triggers.externalTrigger( automation, { fields: { id: row._id, revision: row._rev, row, table, }, user, appId: context.getWorkspaceId(), }, { getResponses: true } ) } export async function getNames(actions: RowActionData[]) { const automations = await sdk.automations.find( actions.map(({ automationId }) => automationId) ) const automationNames = automations.reduce<Record<string, string>>( (names, a) => { names[a._id] = a.name return names }, {} ) return automationNames }