Compare commits

..

13 Commits

Author SHA1 Message Date
Rıdvan Akca
d3bc3a796b feat(auth): add loading state for user and role management 2023-07-31 17:56:31 +03:00
Ali BARIN
9e64af4793 feat(auth): add user and role management 2023-07-26 18:12:54 +00:00
Ali BARIN
b581f539e2 feat(sso): introduce authentication with SAML 2023-07-18 21:07:31 +00:00
Ali BARIN
aac1295c10 feat(authorization): add update connection checks 2023-07-18 21:06:26 +00:00
Ali BARIN
e8f2802ee0 feat(authorization): add read connection checks 2023-07-18 21:06:22 +00:00
Ali BARIN
75b3730a70 feat(authorization): add delete flow checks 2023-07-18 21:06:18 +00:00
Ali BARIN
af29dc9c3f feat(authorization): add create flow checks 2023-07-18 21:06:08 +00:00
Ali BARIN
181cb5f335 feat(authorization): add delete connection checks 2023-07-18 21:06:04 +00:00
Ali BARIN
94e560c262 feat(authorization): add create connection checks 2023-07-18 21:06:00 +00:00
Ali BARIN
f802061722 feat(authorization): add read execution checks 2023-07-18 21:05:53 +00:00
Ali BARIN
58a7f6eec6 feat(authorization): add update flow checks 2023-07-18 21:03:43 +00:00
Ali BARIN
5e11d3cc4d feat(authorization): add read flow checks 2023-07-18 21:03:29 +00:00
Ali BARIN
399fb8312a feat: introduce role based access control 2023-07-17 23:16:01 +03:00
284 changed files with 1912 additions and 7394 deletions

View File

@@ -1,31 +0,0 @@
name: Automatisch UI Tests
on:
schedule:
- cron: '0 12 * * *'
workflow_dispatch:
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- name: Install dependencies
run: yarn
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Run Playwright tests
working-directory: ./packages/e2e-tests
env:
LOGIN_EMAIL: ${{ secrets.LOGIN_EMAIL }}
LOGIN_PASSWORD: ${{ secrets.LOGIN_PASSWORD }}
run: yarn test
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: test-results/
retention-days: 30

View File

@@ -4,7 +4,7 @@ WORKDIR /automatisch
RUN \
apk --no-cache add --virtual build-dependencies python3 build-base && \
yarn global add @automatisch/cli@0.9.1 --network-timeout 1000000 && \
yarn global add @automatisch/cli@0.7.1 --network-timeout 1000000 && \
rm -rf /usr/local/share/.cache/ && \
apk del build-dependencies

View File

@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
FROM automatischio/automatisch:0.9.1
FROM automatischio/automatisch:0.7.1
WORKDIR /automatisch
RUN apk add --no-cache openssl dos2unix

View File

@@ -2,7 +2,7 @@
"packages": [
"packages/*"
],
"version": "0.9.1",
"version": "0.7.1",
"npmClient": "yarn",
"useWorkspaces": true,
"command": {

View File

@@ -3,16 +3,36 @@ import logger from '../../src/helpers/logger';
import client from './client';
import User from '../../src/models/user';
import Role from '../../src/models/role';
import Permission from '../../src/models/permission';
import '../../src/config/orm';
async function fetchAdminRole() {
const role = await Role
.query()
.where({
key: 'admin'
})
.limit(1)
.first();
async function seedPermissionsIfNeeded() {
const existingPermissions = await Permission.query().limit(1).first();
if (!existingPermissions) return;
const getPermission = (subject: string, actions: string[]) => actions.map(action => ({ subject, action }));
await Permission.query().insert([
...getPermission('Connection', ['create', 'read', 'delete', 'update']),
...getPermission('Execution', ['read']),
...getPermission('Flow', ['create', 'delete', 'publish', 'read', 'update']),
...getPermission('Role', ['create', 'delete', 'read', 'update']),
...getPermission('User', ['create', 'delete', 'read', 'update']),
])
}
async function createOrFetchRole() {
const role = await Role.query().limit(1).first();
if (!role) {
const createdRole = await Role.query().insertAndFetch({
name: 'Admin',
key: 'admin',
});
return createdRole;
}
return role;
}
@@ -23,7 +43,9 @@ export async function createUser(
) {
const UNIQUE_VIOLATION_CODE = '23505';
const role = await fetchAdminRole();
await seedPermissionsIfNeeded();
const role = await createOrFetchRole();
const userParams = {
email,
password,

View File

@@ -1,6 +1,6 @@
{
"name": "@automatisch/backend",
"version": "0.9.1",
"version": "0.7.1",
"license": "See LICENSE file",
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
"scripts": {
@@ -22,7 +22,7 @@
"prebuild": "rm -rf ./dist"
},
"dependencies": {
"@automatisch/web": "^0.9.1",
"@automatisch/web": "^0.7.1",
"@bull-board/express": "^3.10.1",
"@casl/ability": "^6.5.0",
"@graphql-tools/graphql-file-loader": "^7.3.4",
@@ -53,8 +53,6 @@
"graphql-type-json": "^0.3.2",
"handlebars": "^4.7.7",
"http-errors": "~1.6.3",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.1",
"jsonwebtoken": "^9.0.0",
"knex": "^2.4.0",
"lodash.get": "^4.4.2",
@@ -62,14 +60,12 @@
"memory-cache": "^0.2.0",
"morgan": "^1.10.0",
"multer": "1.4.5-lts.1",
"node-html-markdown": "^1.3.0",
"nodemailer": "6.7.0",
"oauth-1.0a": "^2.2.6",
"objection": "^3.0.0",
"passport": "^0.6.0",
"pg": "^8.7.1",
"php-serialize": "^4.0.2",
"showdown": "^2.1.0",
"stripe": "^11.13.0",
"winston": "^3.7.1",
"xmlrpc": "^1.3.2"
@@ -110,7 +106,7 @@
"url": "https://github.com/automatisch/automatisch/issues"
},
"devDependencies": {
"@automatisch/types": "^0.9.1",
"@automatisch/types": "^0.7.1",
"@types/bcrypt": "^5.0.0",
"@types/bull": "^3.15.8",
"@types/cors": "^2.8.12",
@@ -126,7 +122,6 @@
"@types/nodemailer": "^6.4.4",
"@types/pg": "^8.6.1",
"@types/pino": "^7.0.5",
"@types/showdown": "^2.0.1",
"ava": "^3.15.0",
"nodemon": "^2.0.13",
"sinon": "^11.1.2",

View File

@@ -1,3 +0,0 @@
import text from './text';
export default [text];

View File

@@ -1,64 +0,0 @@
import defineAction from '../../../../helpers/define-action';
import capitalize from './transformers/capitalize';
import htmlToMarkdown from './transformers/html-to-markdown';
import markdownToHtml from './transformers/markdown-to-html';
import useDefaultValue from './transformers/use-default-value';
import extractEmailAddress from './transformers/extract-email-address';
const transformers = {
capitalize,
htmlToMarkdown,
markdownToHtml,
useDefaultValue,
extractEmailAddress,
};
export default defineAction({
name: 'Text',
key: 'text',
description:
'Transform text data to capitalize, extract emails, apply default value, and much more.',
arguments: [
{
label: 'Transform',
key: 'transform',
type: 'dropdown' as const,
required: true,
description: 'Pick a channel to send the message to.',
variables: true,
options: [
{ label: 'Capitalize', value: 'capitalize' },
{ label: 'Convert HTML to Markdown', value: 'htmlToMarkdown' },
{ label: 'Convert Markdown to HTML', value: 'markdownToHtml' },
{ label: 'Use Default Value', value: 'useDefaultValue' },
{ label: 'Extract Email Address', value: 'extractEmailAddress' },
],
additionalFields: {
type: 'query',
name: 'getDynamicFields',
arguments: [
{
name: 'key',
value: 'listTransformOptions',
},
{
name: 'parameters.transform',
value: '{parameters.transform}',
},
],
},
},
],
async run($) {
const transformerName = $.step.parameters
.transform as keyof typeof transformers;
const output = transformers[transformerName]($);
$.setActionItem({
raw: {
output,
},
});
},
});

View File

@@ -1,11 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
import { capitalize as lodashCapitalize } from 'lodash';
const capitalize = ($: IGlobalVariable) => {
const input = $.step.parameters.input as string;
const capitalizedInput = input.replace(/\w+/g, lodashCapitalize);
return capitalizedInput;
};
export default capitalize;

View File

@@ -1,12 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
const extractEmailAddress = ($: IGlobalVariable) => {
const input = $.step.parameters.input as string;
const emailRegexp =
/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
const email = input.match(emailRegexp);
return email ? email[0] : '';
};
export default extractEmailAddress;

View File

@@ -1,11 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
import { NodeHtmlMarkdown } from 'node-html-markdown';
const htmlToMarkdown = ($: IGlobalVariable) => {
const input = $.step.parameters.input as string;
const markdown = NodeHtmlMarkdown.translate(input);
return markdown;
};
export default htmlToMarkdown;

View File

@@ -1,13 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
import showdown from 'showdown';
const converter = new showdown.Converter();
const markdownToHtml = ($: IGlobalVariable) => {
const input = $.step.parameters.input as string;
const html = converter.makeHtml(input);
return html;
};
export default markdownToHtml;

View File

@@ -1,13 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
const useDefaultValue = ($: IGlobalVariable) => {
const input = $.step.parameters.input as string;
if (input && input.trim().length > 0) {
return input;
}
return $.step.parameters.defaultValue as string;
};
export default useDefaultValue;

View File

@@ -1,3 +0,0 @@
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 4H20M4 12H20M4 20H20M4 8H14M4 16H14" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 243 B

View File

@@ -1,3 +0,0 @@
import listTransformOptions from './list-transform-options';
export default [listTransformOptions];

View File

@@ -1,23 +0,0 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
import capitalize from './options/capitalize';
import htmlToMarkdown from './options/html-to-markdown';
import markdownToHtml from './options/markdown-to-html';
import useDefaultValue from './options/use-default-value';
import extractEmailAddress from './options/extract-email-address';
const options: IJSONObject = {
capitalize,
htmlToMarkdown,
markdownToHtml,
useDefaultValue,
extractEmailAddress,
};
export default {
name: 'List fields after transform',
key: 'listTransformOptions',
async run($: IGlobalVariable) {
return options[$.step.parameters.transform as string];
},
};

View File

@@ -1,12 +0,0 @@
const capitalize = [
{
label: 'Input',
key: 'input',
type: 'string' as const,
required: true,
description: 'Text that will be capitalized.',
variables: true,
},
];
export default capitalize;

View File

@@ -1,12 +0,0 @@
const extractEmailAddress = [
{
label: 'Input',
key: 'input',
type: 'string' as const,
required: true,
description: 'Text that will be searched for an email address.',
variables: true,
},
];
export default extractEmailAddress;

View File

@@ -1,12 +0,0 @@
const htmlToMarkdown = [
{
label: 'Input',
key: 'input',
type: 'string' as const,
required: true,
description: 'HTML that will be converted to Markdown.',
variables: true,
},
];
export default htmlToMarkdown;

View File

@@ -1,12 +0,0 @@
const markdownToHtml = [
{
label: 'Input',
key: 'input',
type: 'string' as const,
required: true,
description: 'Markdown text that will be converted to HTML.',
variables: true,
},
];
export default markdownToHtml;

View File

@@ -1,21 +0,0 @@
const useDefaultValue = [
{
label: 'Input',
key: 'input',
type: 'string' as const,
required: true,
description: 'Text you want to check whether it is empty or not.',
variables: true,
},
{
label: 'Default Value',
key: 'defaultValue',
type: 'string' as const,
required: true,
description:
'Text that will be used as a default value if the input is empty.',
variables: true,
},
];
export default useDefaultValue;

View File

@@ -1,16 +0,0 @@
import defineApp from '../../helpers/define-app';
import actions from './actions';
import dynamicFields from './dynamic-fields';
export default defineApp({
name: 'Formatter',
key: 'formatter',
iconUrl: '{BASE_URL}/apps/formatter/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/formatter/connection',
supportsConnections: false,
baseUrl: '',
apiBaseUrl: '',
primaryColor: '001F52',
actions,
dynamicFields,
});

View File

@@ -6,7 +6,7 @@ import actions from './actions';
import dynamicData from './dynamic-data';
export default defineApp({
name: 'GitHub',
name: 'Github',
key: 'github',
baseUrl: 'https://github.com',
apiBaseUrl: 'https://api.github.com',

View File

@@ -25,12 +25,6 @@ const verifyCredentials = async ($: IGlobalVariable) => {
$.auth.data.accessToken = data.access_token;
const currentUser = await getCurrentUser($);
const screenName = [
currentUser.username,
$.auth.data.instanceUrl,
]
.filter(Boolean)
.join(' @ ');
await $.auth.set({
clientId: $.auth.data.clientId,
@@ -40,7 +34,7 @@ const verifyCredentials = async ($: IGlobalVariable) => {
scope: data.scope,
tokenType: data.token_type,
userId: currentUser.id,
screenName,
screenName: `${currentUser.username} @ ${$.auth.data.instanceUrl}`,
});
};

View File

@@ -9,11 +9,11 @@ export default {
// ref:
// - https://docs.gitlab.com/ee/api/projects.html#list-all-projects
// - https://docs.gitlab.com/ee/api/rest/index.html#keyset-based-pagination
const firstPageRequest = $.http.get('/api/v4/projects', {
params: {
simple: true,
pagination: 'keyset',
membership: true,
order_by: 'id',
sort: 'asc',
},

View File

@@ -6,7 +6,7 @@ import triggers from './triggers';
import dynamicData from './dynamic-data';
export default defineApp({
name: 'GitLab',
name: 'Gitlab',
key: 'gitlab',
baseUrl: 'https://gitlab.com',
apiBaseUrl: 'https://gitlab.com',

View File

@@ -1,191 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import defineAction from '../../../../helpers/define-action';
type THeaders = {
__id: string;
header: string;
}[];
type TSheetsResponse = {
sheets: {
properties: {
sheetId: string;
title: string;
};
}[];
};
type TBody = {
requests: IJSONObject[];
};
export default defineAction({
name: 'Create worksheet',
key: 'createWorksheet',
description:
'Create a blank worksheet with a title. Optionally, provide headers.',
arguments: [
{
label: 'Drive',
key: 'driveId',
type: 'dropdown' as const,
required: false,
description:
'The Google Drive where your spreadsheet resides. If nothing is selected, then your personal Google Drive will be used.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listDrives',
},
],
},
},
{
label: 'Spreadsheet',
key: 'spreadsheetId',
type: 'dropdown' as const,
required: true,
dependsOn: ['parameters.driveId'],
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listSpreadsheets',
},
{
name: 'parameters.driveId',
value: '{parameters.driveId}',
},
],
},
},
{
label: 'Title',
key: 'title',
type: 'string' as const,
required: true,
description: '',
variables: true,
},
{
label: 'Headers',
key: 'headers',
type: 'dynamic' as const,
required: false,
fields: [
{
label: 'Header',
key: 'header',
type: 'string' as const,
required: true,
variables: true,
},
],
},
{
label: 'Overwrite',
key: 'overwrite',
type: 'dropdown' as const,
required: false,
value: false,
description:
'If a worksheet with the specified title exists, its content would be lost. Please, use with caution.',
variables: true,
options: [
{
label: 'Yes',
value: 'true',
},
{
label: 'No',
value: 'false',
},
],
},
],
async run($) {
const {
data: { sheets },
} = await $.http.get<TSheetsResponse>(
`/v4/spreadsheets/${$.step.parameters.spreadsheetId}`
);
const selectedSheet = sheets.find(
(sheet) => sheet.properties.title === $.step.parameters.title
);
const headers = $.step.parameters.headers as THeaders;
const values = headers.map((entry) => entry.header);
const body: TBody = {
requests: [
{
addSheet: {
properties: {
title: $.step.parameters.title,
},
},
},
],
};
if ($.step.parameters.overwrite === 'true' && selectedSheet) {
body.requests.unshift({
deleteSheet: {
sheetId: selectedSheet.properties.sheetId,
},
});
}
const { data } = await $.http.post(
`https://sheets.googleapis.com/v4/spreadsheets/${$.step.parameters.spreadsheetId}:batchUpdate`,
body
);
if (values.length) {
const body = {
requests: [
{
updateCells: {
rows: [
{
values: values.map((header) => ({
userEnteredValue: { stringValue: header },
})),
},
],
fields: '*',
start: {
sheetId:
data.replies[data.replies.length - 1].addSheet.properties
.sheetId,
rowIndex: 0,
columnIndex: 0,
},
},
},
],
};
const { data: response } = await $.http.post(
`https://sheets.googleapis.com/v4/spreadsheets/${$.step.parameters.spreadsheetId}:batchUpdate`,
body
);
$.setActionItem({
raw: response,
});
return;
}
$.setActionItem({
raw: data,
});
},
});

View File

@@ -1,5 +1,4 @@
import createSpreadsheet from './create-spreadsheet';
import createSpreadsheetRow from './create-spreadsheet-row';
import createWorksheet from './create-worksheet';
export default [createSpreadsheet, createSpreadsheetRow, createWorksheet];
export default [createSpreadsheet, createSpreadsheetRow];

View File

@@ -1,100 +0,0 @@
import { IJSONArray, IJSONObject } from '@automatisch/types';
import defineAction from '../../../../helpers/define-action';
type TBody = {
parent: IJSONObject;
properties: IJSONObject;
children: IJSONArray;
};
export default defineAction({
name: 'Create database item',
key: 'createDatabaseItem',
description: 'Creates an item in a database.',
arguments: [
{
label: 'Database',
key: 'databaseId',
type: 'dropdown' as const,
required: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listDatabases',
},
],
},
},
{
label: 'Name',
key: 'name',
type: 'string' as const,
required: false,
description:
'This field has a 2000 character limit. Any characters beyond 2000 will not be included.',
variables: true,
},
{
label: 'Content',
key: 'content',
type: 'string' as const,
required: false,
description:
'The text to add to the page body. The max length for this field is 2000 characters. Any characters beyond 2000 will not be included.',
variables: true,
},
],
async run($) {
const name = $.step.parameters.name as string;
const truncatedName = name.slice(0, 2000);
const content = $.step.parameters.content as string;
const truncatedContent = content.slice(0, 2000);
const body: TBody = {
parent: {
database_id: $.step.parameters.databaseId,
},
properties: {},
children: [],
};
if (name) {
body.properties.Name = {
title: [
{
text: {
content: truncatedName,
},
},
],
};
}
if (content) {
body.children = [
{
object: 'block',
paragraph: {
rich_text: [
{
text: {
content: truncatedContent,
},
},
],
},
},
];
}
const { data } = await $.http.post('/v1/pages', body);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -1,104 +0,0 @@
import { IJSONArray, IJSONObject } from '@automatisch/types';
import defineAction from '../../../../helpers/define-action';
type TBody = {
parent: IJSONObject;
properties: IJSONObject;
children: IJSONArray;
};
export default defineAction({
name: 'Create page',
key: 'createPage',
description: 'Creates a page inside a parent page',
arguments: [
{
label: 'Parent page',
key: 'parentPageId',
type: 'dropdown' as const,
required: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listParentPages',
},
],
},
},
{
label: 'Title',
key: 'title',
type: 'string' as const,
required: false,
description:
'This field has a 2000 character limit. Any characters beyond 2000 will not be included.',
variables: true,
},
{
label: 'Content',
key: 'content',
type: 'string' as const,
required: false,
description:
'The text to add to the page body. The max length for this field is 2000 characters. Any characters beyond 2000 will not be included.',
variables: true,
},
],
async run($) {
const parentPageId = $.step.parameters.parentPageId as string;
const title = $.step.parameters.title as string;
const truncatedTitle = title.slice(0, 2000);
const content = $.step.parameters.content as string;
const truncatedContent = content.slice(0, 2000);
const body: TBody = {
parent: {
page_id: parentPageId,
},
properties: {},
children: [],
};
if (title) {
body.properties.title = {
type: 'title',
title: [
{
text: {
content: truncatedTitle,
},
},
],
};
}
if (content) {
body.children = [
{
object: 'block',
type: 'paragraph',
paragraph: {
rich_text: [
{
type: 'text',
text: {
content: truncatedContent,
},
},
],
},
},
];
}
const { data } = await $.http.post('/v1/pages', body);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -1,70 +0,0 @@
import { IJSONArray, IJSONObject } from '@automatisch/types';
import defineAction from '../../../../helpers/define-action';
type TBody = {
filter: IJSONObject;
sorts: IJSONArray;
};
export default defineAction({
name: 'Find database item',
key: 'findDatabaseItem',
description: 'Searches for an item in a database by property.',
arguments: [
{
label: 'Database',
key: 'databaseId',
type: 'dropdown' as const,
required: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listDatabases',
},
],
},
},
{
label: 'Name',
key: 'name',
type: 'string' as const,
required: false,
description:
'This field has a 2000 character limit. Any characters beyond 2000 will not be included.',
variables: true,
},
],
async run($) {
const databaseId = $.step.parameters.databaseId as string;
const name = $.step.parameters.name as string;
const truncatedName = name.slice(0, 2000);
const body: TBody = {
filter: {
property: 'Name',
rich_text: {
equals: truncatedName,
},
},
sorts: [
{
timestamp: 'last_edited_time',
direction: 'descending',
},
],
};
const { data } = await $.http.post(
`/v1/databases/${databaseId}/query`,
body
);
$.setActionItem({
raw: data.results[0],
});
},
});

View File

@@ -1,5 +0,0 @@
import createDatabaseItem from './create-database-item';
import createPage from './create-page';
import findDatabaseItem from './find-database-item';
export default [createDatabaseItem, createPage, findDatabaseItem];

View File

@@ -1,4 +1,3 @@
import listDatabases from './list-databases';
import listParentPages from './list-parent-pages';
export default [listDatabases, listParentPages];
export default [listDatabases];

View File

@@ -1,70 +0,0 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
type Page = {
id: string;
properties: {
title: {
title: [
{
plain_text: string;
}
];
};
};
parent: {
workspace: boolean;
};
};
type ResponseData = {
results: Page[];
next_cursor?: string;
};
type Payload = {
filter: {
value: 'page';
property: 'object';
};
start_cursor?: string;
};
export default {
name: 'List parent pages',
key: 'listParentPages',
async run($: IGlobalVariable) {
const parentPages: {
data: IJSONObject[];
error: IJSONObject | null;
} = {
data: [],
error: null,
};
const payload: Payload = {
filter: {
value: 'page',
property: 'object',
},
};
do {
const response = await $.http.post<ResponseData>('/v1/search', payload);
payload.start_cursor = response.data.next_cursor;
const topLevelPages = response.data.results.filter(
(page) => page.parent.workspace
);
for (const pages of topLevelPages) {
parentPages.data.push({
value: pages.id as string,
name: pages.properties.title.title[0].plain_text as string,
});
}
} while (payload.start_cursor);
return parentPages;
},
};

View File

@@ -3,7 +3,6 @@ import addAuthHeader from './common/add-auth-header';
import addNotionVersionHeader from './common/add-notion-version-header';
import auth from './auth';
import triggers from './triggers';
import actions from './actions';
import dynamicData from './dynamic-data';
export default defineApp({
@@ -15,9 +14,11 @@ export default defineApp({
authDocUrl: 'https://automatisch.io/docs/apps/notion/connection',
primaryColor: '000000',
supportsConnections: true,
beforeRequest: [addAuthHeader, addNotionVersionHeader],
beforeRequest: [
addAuthHeader,
addNotionVersionHeader,
],
auth,
triggers,
actions,
dynamicData,
});

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<svg width="256px" height="255px" viewBox="0 0 256 255" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g fill="#464342">
<path d="M18.1239675,127.500488 C18.1239675,170.795707 43.284813,208.211252 79.7700163,225.941854 L27.5938862,82.985626 C21.524813,96.5890081 18.1239675,111.643057 18.1239675,127.500488 L18.1239675,127.500488 Z M201.345041,121.980878 C201.345041,108.462829 196.489366,99.1011382 192.324683,91.8145041 C186.780098,82.8045528 181.583089,75.1745041 181.583089,66.1645528 C181.583089,56.1097886 189.208976,46.7501789 199.950569,46.7501789 C200.435512,46.7501789 200.89548,46.8105366 201.367935,46.8375935 C181.907772,29.0091707 155.981008,18.1239675 127.50465,18.1239675 C89.2919675,18.1239675 55.6727154,37.7298211 36.1147317,67.4258211 C38.6809756,67.5028293 41.0994472,67.5569431 43.1536911,67.5569431 C54.5946016,67.5569431 72.3043902,66.1687154 72.3043902,66.1687154 C78.2007154,65.8211382 78.8958699,74.4814309 73.0057886,75.1786667 C73.0057886,75.1786667 67.0803252,75.8759024 60.4867642,76.2213984 L100.318699,194.699447 L124.25574,122.909138 L107.214049,76.2172358 C101.323967,75.8717398 95.744,75.1745041 95.744,75.1745041 C89.8497561,74.8290081 90.540748,65.8169756 96.4349919,66.1645528 C96.4349919,66.1645528 114.498602,67.5527805 125.246439,67.5527805 C136.685268,67.5527805 154.397138,66.1645528 154.397138,66.1645528 C160.297626,65.8169756 160.990699,74.4772683 155.098537,75.1745041 C155.098537,75.1745041 149.160585,75.8717398 142.579512,76.2172358 L182.107577,193.798244 L193.017756,157.340098 C197.746472,142.211122 201.345041,131.34465 201.345041,121.980878 L201.345041,121.980878 Z M129.42361,137.068228 L96.6056585,232.43135 C106.404423,235.31187 116.76722,236.887415 127.50465,236.887415 C140.242211,236.887415 152.457366,234.685398 163.827512,230.68722 C163.534049,230.218927 163.267642,229.721496 163.049106,229.180358 L129.42361,137.068228 L129.42361,137.068228 Z M223.481756,75.0225691 C223.95213,78.5066667 224.218537,82.2467642 224.218537,86.2699187 C224.218537,97.3694959 222.145561,109.846894 215.901659,125.448325 L182.490537,222.04774 C215.00878,203.085008 236.881171,167.854829 236.881171,127.502569 C236.883252,108.485724 232.025496,90.603187 223.481756,75.0225691 L223.481756,75.0225691 Z M127.50465,0 C57.2003902,0 0,57.1962276 0,127.500488 C0,197.813073 57.2003902,255.00722 127.50465,255.00722 C197.806829,255.00722 255.015545,197.813073 255.015545,127.500488 C255.013463,57.1962276 197.806829,0 127.50465,0 L127.50465,0 Z M127.50465,249.162927 C60.4243252,249.162927 5.84637398,194.584976 5.84637398,127.500488 C5.84637398,60.4201626 60.4222439,5.84637398 127.50465,5.84637398 C194.582894,5.84637398 249.156683,60.4201626 249.156683,127.500488 C249.156683,194.584976 194.582894,249.162927 127.50465,249.162927 L127.50465,249.162927 Z"></path>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,24 +0,0 @@
import { URL, URLSearchParams } from 'node:url';
import { IGlobalVariable } from '@automatisch/types';
import appConfig from '../../../config/app';
import getInstanceUrl from '../common/get-instance-url';
export default async function generateAuthUrl($: IGlobalVariable) {
const successUrl = new URL(
'/app/wordpress/connections/add',
appConfig.webAppUrl
).toString();
const baseUrl = getInstanceUrl($);
const searchParams = new URLSearchParams({
app_name: 'automatisch',
success_url: successUrl,
});
const url = new URL(`/wp-admin/authorize-application.php?${searchParams}`, baseUrl).toString();
await $.auth.set({
url,
});
}

View File

@@ -1,24 +0,0 @@
import generateAuthUrl from './generate-auth-url';
import isStillVerified from './is-still-verified';
import verifyCredentials from './verify-credentials';
export default {
fields: [
{
key: 'instanceUrl',
label: 'WordPress instance URL',
type: 'string' as const,
required: false,
readOnly: false,
value: null,
placeholder: null,
description: 'Your WordPress instance URL.',
docUrl: 'https://automatisch.io/docs/wordpress#instance-url',
clickToCopy: true,
},
],
generateAuthUrl,
isStillVerified,
verifyCredentials,
};

View File

@@ -1,9 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
const isStillVerified = async ($: IGlobalVariable) => {
await $.http.get('?rest_route=/wp/v2/settings');
return true;
};
export default isStillVerified;

View File

@@ -1,24 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
const verifyCredentials = async ($: IGlobalVariable) => {
const instanceUrl = $.auth.data.instanceUrl as string;
const password = $.auth.data.password as string;
const siteUrl = $.auth.data.site_url as string;
const url = $.auth.data.url as string;
const userLogin = $.auth.data.user_login as string;
if (!password) {
throw new Error('Failed while authorizing!');
}
await $.auth.set({
screenName: `${userLogin} @ ${siteUrl}`,
instanceUrl,
password,
siteUrl,
url,
userLogin,
});
};
export default verifyCredentials;

View File

@@ -1,17 +0,0 @@
import { TBeforeRequest } from '@automatisch/types';
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
const userLogin = $.auth.data.userLogin as string;
const password = $.auth.data.password as string;
if (userLogin && password) {
requestConfig.auth = {
username: userLogin,
password,
};
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -1,7 +0,0 @@
import { IGlobalVariable } from '@automatisch/types';
const getInstanceUrl = ($: IGlobalVariable): string => {
return $.auth.data.instanceUrl as string;
};
export default getInstanceUrl;

View File

@@ -1,12 +0,0 @@
import { TBeforeRequest } from '@automatisch/types';
const setBaseUrl: TBeforeRequest = ($, requestConfig) => {
const instanceUrl = $.auth.data.instanceUrl as string;
if (instanceUrl) {
requestConfig.baseURL = instanceUrl;
}
return requestConfig;
};
export default setBaseUrl;

View File

@@ -1,3 +0,0 @@
import listStatuses from './list-statuses';
export default [listStatuses];

View File

@@ -1,37 +0,0 @@
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
type Status = {
slug: string;
name: string;
}
type Statuses = Record<string, Status>;
export default {
name: 'List statuses',
key: 'listStatuses',
async run($: IGlobalVariable) {
const statuses: {
data: IJSONObject[];
} = {
data: [],
};
const { data } = await $.http.get<Statuses>('?rest_route=/wp/v2/statuses');
if (!data) return statuses;
const values = Object.values(data);
if (!values?.length) return statuses;
for (const status of values) {
statuses.data.push({
value: status.slug,
name: status.name,
})
}
return statuses;
},
};

View File

@@ -1,21 +0,0 @@
import defineApp from '../../helpers/define-app';
import addAuthHeader from './common/add-auth-header';
import setBaseUrl from './common/set-base-url';
import auth from './auth';
import triggers from './triggers';
import dynamicData from './dynamic-data';
export default defineApp({
name: 'WordPress',
key: 'wordpress',
iconUrl: '{BASE_URL}/apps/wordpress/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/wordpress/connection',
supportsConnections: true,
baseUrl: 'https://wordpress.com',
apiBaseUrl: '',
primaryColor: '464342',
beforeRequest: [setBaseUrl, addAuthHeader],
auth,
triggers,
dynamicData,
});

View File

@@ -1,3 +0,0 @@
import newPost from './new-post';
export default [newPost];

View File

@@ -1,60 +0,0 @@
import defineTrigger from '../../../../helpers/define-trigger';
export default defineTrigger({
name: 'New post',
key: 'newPost',
description: 'Triggers when a new post is created.',
arguments: [
{
label: 'Status',
key: 'status',
type: 'dropdown' as const,
required: true,
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listStatuses',
},
],
},
},
],
async run($) {
const params = {
per_page: 100,
page: 1,
order: 'desc',
orderby: 'date',
status: $.step.parameters.status || '',
};
let totalPages = 1;
do {
const {
data,
headers
} = await $.http.get('?rest_route=/wp/v2/posts', { params });
params.page = params.page + 1;
totalPages = Number(headers['x-wp-totalpages']);
if (data.length) {
for (const post of data) {
const dataItem = {
raw: post,
meta: {
internalId: post.id.toString(),
},
};
$.pushTriggerItem(dataItem);
}
}
} while (params.page <= totalPages);
},
});

View File

@@ -16,27 +16,13 @@ export async function up(knex: Knex): Promise<void> {
.select('role')
.groupBy('role');
let shouldCreateAdminRole = true;
for (const { role } of uniqueUserRoles) {
// skip empty roles
if (!role) continue;
const lowerCaseRole = lowerCase(role);
if (lowerCaseRole === 'admin') {
shouldCreateAdminRole = false;
}
await knex('roles').insert({
name: capitalize(role),
key: lowerCaseRole,
});
}
if (shouldCreateAdminRole) {
await knex('roles').insert({
name: 'Admin',
key: 'admin',
key: lowerCase(role),
});
}
}

View File

@@ -1,46 +1,23 @@
import { Knex } from 'knex';
const getPermissionForRole = (roleId: string, subject: string, actions: string[], conditions: string[] = []) => actions
.map(action => ({
role_id: roleId,
subject,
action,
conditions,
}));
const getPermission = (subject: string, actions: string[]) => actions.map(action => ({ subject, action }));
export async function up(knex: Knex): Promise<void> {
await knex.schema.createTable('permissions', (table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table.uuid('role_id').references('id').inTable('roles');
table.string('action').notNullable();
table.string('subject').notNullable();
table.jsonb('conditions').notNullable().defaultTo([]);
table.timestamps(true, true);
});
const roles = await knex('roles').select(['id', 'key']) as { id: string, key: string }[];
for (const role of roles) {
// `admin` role should have no conditions unlike others by default
const isAdmin = role.key === 'admin';
const roleConditions = isAdmin ? [] : ['isCreator'];
// default permissions
await knex('permissions').insert([
...getPermissionForRole(role.id, 'Connection', ['create', 'read', 'delete', 'update'], roleConditions),
...getPermissionForRole(role.id, 'Execution', ['read'], roleConditions),
...getPermissionForRole(role.id, 'Flow', ['create', 'delete', 'publish', 'read', 'update'], roleConditions),
]);
// admin specific permission
if (isAdmin) {
await knex('permissions').insert([
...getPermissionForRole(role.id, 'User', ['create', 'read', 'delete', 'update']),
...getPermissionForRole(role.id, 'Role', ['create', 'read', 'delete', 'update']),
]);
}
}
await knex('permissions').insert([
...getPermission('Connection', ['create', 'read', 'delete', 'update']),
...getPermission('Execution', ['read']),
...getPermission('Flow', ['create', 'delete', 'publish', 'read', 'update']),
...getPermission('Role', ['create', 'delete', 'read', 'update']),
...getPermission('User', ['create', 'delete', 'read', 'update']),
]);
}
export async function down(knex: Knex): Promise<void> {

View File

@@ -0,0 +1,25 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
await knex.schema.createTable('roles_permissions', (table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table.uuid('role_id').references('id').inTable('roles');
table.uuid('permission_id').references('id').inTable('permissions');
});
const roles = await knex('roles').select('id');
const permissions = await knex('permissions').select('id');
for (const role of roles) {
for (const permission of permissions) {
await knex('roles_permissions').insert({
role_id: role.id,
permission_id: permission.id,
});
}
}
}
export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('roles_permissions');
}

View File

@@ -13,7 +13,6 @@ export async function up(knex: Knex): Promise<void> {
table.text('email_attribute_name').notNullable();
table.text('role_attribute_name').notNullable();
table.uuid('default_role_id').references('id').inTable('roles');
table.boolean('active').defaultTo(false);
table.timestamps(true, true);
});

View File

@@ -0,0 +1,13 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
return knex.schema.alterTable('permissions', (table) => {
table.jsonb('conditions').notNullable().defaultTo([]);
});
}
export async function down(knex: Knex): Promise<void> {
return knex.schema.alterTable('permissions', (table) => {
table.dropColumn('conditions');
});
}

View File

@@ -1,33 +0,0 @@
import { Knex } from 'knex';
const getPermissionForRole = (
roleId: string,
subject: string,
actions: string[]
) =>
actions.map((action) => ({
role_id: roleId,
subject,
action,
conditions: [],
}));
export async function up(knex: Knex): Promise<void> {
const role = (await knex('roles')
.first(['id', 'key'])
.where({ key: 'admin' })
.limit(1)) as { id: string; key: string };
await knex('permissions').insert(
getPermissionForRole(role.id, 'SamlAuthProvider', [
'create',
'read',
'delete',
'update',
])
);
}
export async function down(knex: Knex): Promise<void> {
await knex('permissions').where({ subject: 'SamlAuthProvider' }).delete();
}

View File

@@ -1,15 +0,0 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('config', (table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table.string('key').unique().notNullable();
table.jsonb('value').notNullable().defaultTo({});
table.timestamps(true, true);
});
}
export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('config');
}

View File

@@ -1,30 +0,0 @@
import { Knex } from 'knex';
const getPermissionForRole = (
roleId: string,
subject: string,
actions: string[]
) =>
actions.map((action) => ({
role_id: roleId,
subject,
action,
conditions: [],
}));
export async function up(knex: Knex): Promise<void> {
const role = (await knex('roles')
.first(['id', 'key'])
.where({ key: 'admin' })
.limit(1)) as { id: string; key: string };
await knex('permissions').insert(
getPermissionForRole(role.id, 'Config', [
'update',
])
);
}
export async function down(knex: Knex): Promise<void> {
await knex('permissions').where({ subject: 'Config' }).delete();
}

View File

@@ -1,24 +0,0 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable(
'saml_auth_providers_role_mappings',
(table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table
.uuid('saml_auth_provider_id')
.references('id')
.inTable('saml_auth_providers');
table.uuid('role_id').references('id').inTable('roles');
table.string('remote_role_name').notNullable();
table.unique(['saml_auth_provider_id', 'remote_role_name']);
table.timestamps(true, true);
}
);
}
export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('saml_auth_providers_role_mappings');
}

View File

@@ -1,17 +0,0 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('app_configs', (table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table.string('key').unique().notNullable();
table.boolean('allow_custom_connection').notNullable().defaultTo(false);
table.boolean('shared').notNullable().defaultTo(false);
table.boolean('disabled').notNullable().defaultTo(false);
table.timestamps(true, true);
});
}
export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('app_configs');
}

View File

@@ -1,17 +0,0 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
return knex.schema.createTable('app_auth_clients', (table) => {
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
table.string('name').unique().notNullable();
table.uuid('app_config_id').notNullable().references('id').inTable('app_configs');
table.text('auth_defaults').notNullable();
table.boolean('active').notNullable().defaultTo(false);
table.timestamps(true, true);
});
}
export async function down(knex: Knex): Promise<void> {
return knex.schema.dropTable('app_auth_clients');
}

View File

@@ -1,13 +0,0 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
await knex.schema.table('connections', async (table) => {
table.uuid('app_auth_client_id').references('id').inTable('app_auth_clients');
});
}
export async function down(knex: Knex): Promise<void> {
return await knex.schema.table('connections', (table) => {
table.dropColumn('app_auth_client_id');
});
}

View File

@@ -1,33 +0,0 @@
import { Knex } from 'knex';
const getPermissionForRole = (
roleId: string,
subject: string,
actions: string[]
) =>
actions.map((action) => ({
role_id: roleId,
subject,
action,
conditions: [],
}));
export async function up(knex: Knex): Promise<void> {
const role = (await knex('roles')
.first(['id', 'key'])
.where({ key: 'admin' })
.limit(1)) as { id: string; key: string };
await knex('permissions').insert(
getPermissionForRole(role.id, 'App', [
'create',
'read',
'delete',
'update',
])
);
}
export async function down(knex: Knex): Promise<void> {
await knex('permissions').where({ subject: 'App' }).delete();
}

View File

@@ -1,25 +0,0 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
const role = await knex('roles')
.select('id')
.whereIn('key', ['user', 'admin'])
.orderBy('key', 'desc')
.limit(1)
.first();
if (role) {
// backfill nulls
await knex('users').whereNull('role_id').update({ role_id: role.id });
}
return await knex.schema.alterTable('users', (table) => {
table.uuid('role_id').notNullable().alter();
});
}
export async function down(knex: Knex): Promise<void> {
return await knex.schema.alterTable('users', (table) => {
table.uuid('role_id').nullable().alter();
});
}

View File

@@ -1,35 +0,0 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
const users = await knex('users').whereNotNull('deleted_at');
const userIds = users.map((user) => user.id);
const flows = await knex('flows').whereIn('user_id', userIds);
const flowIds = flows.map((flow) => flow.id);
const executions = await knex('executions').whereIn('flow_id', flowIds);
const executionIds = executions.map((execution) => execution.id);
await knex('execution_steps').whereIn('execution_id', executionIds).update({
deleted_at: knex.fn.now(),
});
await knex('executions').whereIn('id', executionIds).update({
deleted_at: knex.fn.now(),
});
await knex('steps').whereIn('flow_id', flowIds).update({
deleted_at: knex.fn.now(),
});
await knex('flows').whereIn('id', flowIds).update({
deleted_at: knex.fn.now(),
});
await knex('connections').whereIn('user_id', userIds).update({
deleted_at: knex.fn.now(),
});
}
export async function down(): Promise<void> {
// void
}

View File

@@ -1,5 +1,3 @@
import createAppAuthClient from './mutations/create-app-auth-client.ee';
import createAppConfig from './mutations/create-app-config.ee';
import createConnection from './mutations/create-connection';
import createFlow from './mutations/create-flow';
import createRole from './mutations/create-role.ee';
@@ -19,9 +17,6 @@ import login from './mutations/login';
import registerUser from './mutations/register-user.ee';
import resetConnection from './mutations/reset-connection';
import resetPassword from './mutations/reset-password.ee';
import updateAppAuthClient from './mutations/update-app-auth-client.ee';
import updateAppConfig from './mutations/update-app-config.ee';
import updateConfig from './mutations/update-config.ee';
import updateConnection from './mutations/update-connection';
import updateCurrentUser from './mutations/update-current-user';
import updateFlow from './mutations/update-flow';
@@ -29,13 +24,9 @@ import updateFlowStatus from './mutations/update-flow-status';
import updateRole from './mutations/update-role.ee';
import updateStep from './mutations/update-step';
import updateUser from './mutations/update-user.ee';
import upsertSamlAuthProvider from './mutations/upsert-saml-auth-provider.ee';
import upsertSamlAuthProvidersRoleMappings from './mutations/upsert-saml-auth-providers-role-mappings.ee';
import verifyConnection from './mutations/verify-connection';
const mutationResolvers = {
createAppAuthClient,
createAppConfig,
createConnection,
createFlow,
createRole,
@@ -55,18 +46,13 @@ const mutationResolvers = {
registerUser,
resetConnection,
resetPassword,
updateAppAuthClient,
updateAppConfig,
updateConfig,
updateConnection,
updateCurrentUser,
updateUser,
updateFlow,
updateFlowStatus,
updateRole,
updateStep,
updateUser,
upsertSamlAuthProvider,
upsertSamlAuthProvidersRoleMappings,
verifyConnection,
};

View File

@@ -1,35 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import AppConfig from '../../models/app-config';
import Context from '../../types/express/context';
type Params = {
input: {
appConfigId: string;
name: string;
formattedAuthDefaults?: IJSONObject;
active?: boolean;
};
};
const createAppAuthClient = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('update', 'App');
const appConfig = await AppConfig
.query()
.findById(params.input.appConfigId)
.throwIfNotFound();
const appAuthClient = await appConfig
.$relatedQuery('appAuthClients')
.insert(
params.input
);
return appAuthClient;
};
export default createAppAuthClient;

View File

@@ -1,36 +0,0 @@
import App from '../../models/app';
import AppConfig from '../../models/app-config';
import Context from '../../types/express/context';
type Params = {
input: {
key: string;
allowCustomConnection?: boolean;
shared?: boolean;
disabled?: boolean;
};
};
const createAppConfig = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('update', 'App');
const key = params.input.key;
const app = await App.findOneByKey(key);
if (!app) throw new Error('The app cannot be found!');
const appConfig = await AppConfig
.query()
.insert(
params.input
);
return appConfig;
};
export default createAppConfig;

View File

@@ -1,16 +1,13 @@
import { IJSONObject } from '@automatisch/types';
import App from '../../models/app';
import AppConfig from '../../models/app-config';
import Context from '../../types/express/context';
import { IJSONObject } from '@automatisch/types';
type Params = {
input: {
key: string;
appAuthClientId: string;
formattedData: IJSONObject;
};
};
const createConnection = async (
_parent: unknown,
params: Params,
@@ -18,42 +15,13 @@ const createConnection = async (
) => {
context.currentUser.can('create', 'Connection');
const { key, appAuthClientId } = params.input;
await App.findOneByKey(params.input.key);
const app = await App.findOneByKey(key);
const appConfig = await AppConfig.query().findOne({ key });
let formattedData = params.input.formattedData;
if (appConfig) {
if (appConfig.disabled) throw new Error('This application has been disabled for new connections!');
if (!appConfig.allowCustomConnection && formattedData) throw new Error(`Custom connections cannot be created for ${app.name}!`);
if (appConfig.shared && !formattedData) {
const authClient = await appConfig
.$relatedQuery('appAuthClients')
.findById(appAuthClientId)
.where({
active: true
})
.throwIfNotFound();
formattedData = authClient.formattedAuthDefaults;
}
}
const createdConnection = await context
.currentUser
.$relatedQuery('connections')
.insert({
key,
appAuthClientId,
formattedData,
verified: false,
});
return createdConnection;
return await context.currentUser.$relatedQuery('connections').insert({
key: params.input.key,
formattedData: params.input.formattedData,
verified: false,
});
};
export default createConnection;

View File

@@ -1,7 +1,6 @@
import kebabCase from 'lodash/kebabCase';
import Permission from '../../models/permission';
import Role from '../../models/role';
import Context from '../../types/express/context';
import Permission from '../../models/permission';
type Params = {
input: {
@@ -11,9 +10,8 @@ type Params = {
};
};
const createRole = async (_parent: unknown, params: Params, context: Context) => {
context.currentUser.can('create', 'Role');
// TODO: access
const createRole = async (_parent: unknown, params: Params) => {
const { name, description, permissions } = params.input;
const key = kebabCase(name);

View File

@@ -1,22 +1,17 @@
import User from '../../models/user';
import Role from '../../models/role';
import Context from '../../types/express/context';
type Params = {
input: {
fullName: string;
email: string;
password: string;
role: {
id: string;
};
roleId: string;
};
};
const createUser = async (_parent: unknown, params: Params, context: Context) => {
context.currentUser.can('create', 'User');
const { fullName, email, password } = params.input;
// TODO: access
const createUser = async (_parent: unknown, params: Params) => {
const { fullName, email, password, roleId } = params.input;
const existingUser = await User.query().findOne({ email });
@@ -24,23 +19,12 @@ const createUser = async (_parent: unknown, params: Params, context: Context) =>
throw new Error('User already exists!');
}
const userPayload: Partial<User> = {
const user = await User.query().insert({
fullName,
email,
password,
};
try {
context.currentUser.can('update', 'Role');
userPayload.roleId = params.input.role.id;
} catch {
// void
const role = await Role.query().findOne({ key: 'user' });
userPayload.roleId = role.id;
}
const user = await User.query().insert(userPayload);
roleId,
});
return user;
};

View File

@@ -1,28 +0,0 @@
import Context from '../../types/express/context';
import AppAuthClient from '../../models/app-auth-client';
type Params = {
input: {
id: string;
};
};
const deleteAppAuthClient = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('delete', 'App');
await AppAuthClient
.query()
.delete()
.findOne({
id: params.input.id,
})
.throwIfNotFound();
return;
};
export default deleteAppAuthClient;

View File

@@ -1,59 +1,18 @@
import { Duration } from 'luxon';
import Context from '../../types/express/context';
import deleteUserQueue from '../../queues/delete-user.ee';
import flowQueue from '../../queues/flow';
import Flow from '../../models/flow';
import Execution from '../../models/execution';
import ExecutionStep from '../../models/execution-step';
import appConfig from '../../config/app';
const deleteCurrentUser = async (
_parent: unknown,
params: never,
context: Context
) => {
// TODO: access
const deleteUser = async (_parent: unknown, params: never, context: Context) => {
const id = context.currentUser.id;
const flows = await context.currentUser.$relatedQuery('flows').where({
active: true,
});
const repeatableJobs = await flowQueue.getRepeatableJobs();
for (const flow of flows) {
const job = repeatableJobs.find((job) => job.id === flow.id);
if (job) {
await flowQueue.removeRepeatableByKey(job.key);
}
}
const executionIds = (
await context.currentUser
.$relatedQuery('executions')
.select('executions.id')
).map((execution: Execution) => execution.id);
const flowIds = flows.map((flow) => flow.id);
await ExecutionStep.query().delete().whereIn('execution_id', executionIds);
await context.currentUser.$relatedQuery('executions').delete();
await context.currentUser.$relatedQuery('steps').delete();
await Flow.query().whereIn('id', flowIds).delete();
await context.currentUser.$relatedQuery('connections').delete();
await context.currentUser.$relatedQuery('identities').delete();
if (appConfig.isCloud) {
await context.currentUser.$relatedQuery('subscriptions').delete();
await context.currentUser.$relatedQuery('usageData').delete();
}
await context.currentUser.$query().delete();
const jobName = `Delete user - ${id}`;
const jobPayload = { id };
const millisecondsFor30Days = Duration.fromObject({ days: 30 }).toMillis();
const jobOptions = {
delay: millisecondsFor30Days,
delay: millisecondsFor30Days
};
await deleteUserQueue.add(jobName, jobPayload, jobOptions);
@@ -61,4 +20,4 @@ const deleteCurrentUser = async (
return true;
};
export default deleteCurrentUser;
export default deleteUser;

View File

@@ -1,5 +1,4 @@
import Role from '../../models/role';
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
import Context from '../../types/express/context';
type Params = {
@@ -8,37 +7,22 @@ type Params = {
};
};
// TODO: access
const deleteRole = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('delete', 'Role');
const role = await Role.query().findById(params.input.id).throwIfNotFound();
const count = await role.$relatedQuery('users').resultSize();
if (count > 0) {
throw new Error('All users must be migrated away from the role!');
}
if (role.isAdmin) {
throw new Error('Admin role cannot be deleted!');
}
const samlAuthProviderUsingDefaultRole = await SamlAuthProvider.query()
.where({ default_role_id: role.id })
.limit(1)
.first();
if (samlAuthProviderUsingDefaultRole) {
throw new Error(
'You need to change the default role in the SAML configuration before deleting this role.'
);
}
// delete permissions first
await role.$relatedQuery('permissions').delete();
/**
* TODO: consider migrations for users that still have the role or
* do not let the role get deleted if there are still associated users
*/
await role.$query().delete();
return true;

View File

@@ -9,13 +9,12 @@ type Params = {
};
};
// TODO: access
const deleteUser = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('delete', 'User');
const id = params.input.id;
await User.query().deleteById(id);

View File

@@ -1,6 +1,5 @@
import Context from '../../types/express/context';
import testRun from '../../services/test-run';
import Step from '../../models/step';
type Params = {
input: {
@@ -13,16 +12,12 @@ const executeFlow = async (
params: Params,
context: Context
) => {
const conditions = context.currentUser.can('update', 'Flow');
const isCreator = conditions.isCreator;
const allSteps = Step.query();
const userSteps = context.currentUser.$relatedQuery('steps');
const baseQuery = isCreator ? userSteps : allSteps;
context.currentUser.can('update', 'Flow');
const { stepId } = params.input;
const untilStep = await baseQuery
.clone()
const untilStep = await context.currentUser
.$relatedQuery('steps')
.findById(stepId)
.throwIfNotFound();

View File

@@ -13,7 +13,7 @@ const TOKEN_EXPIRES_IN = '14d';
const login = async (_parent: unknown, params: Params) => {
const user = await User.query().findOne({
email: params.input.email.toLowerCase(),
email: params.input.email,
});
if (user && (await user.login(params.input.password))) {

View File

@@ -1,38 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import AppAuthClient from '../../models/app-auth-client';
import Context from '../../types/express/context';
type Params = {
input: {
id: string;
name: string;
formattedAuthDefaults?: IJSONObject;
active?: boolean;
};
};
const updateAppAuthClient = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('update', 'App');
const {
id,
...appAuthClientData
} = params.input;
const appAuthClient = await AppAuthClient
.query()
.findById(id)
.throwIfNotFound();
await appAuthClient
.$query()
.patch(appAuthClientData);
return appAuthClient;
};
export default updateAppAuthClient;

View File

@@ -1,39 +0,0 @@
import AppConfig from '../../models/app-config';
import Context from '../../types/express/context';
type Params = {
input: {
id: string;
allowCustomConnection?: boolean;
shared?: boolean;
disabled?: boolean;
};
};
const updateAppConfig = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('update', 'App');
const {
id,
...appConfigToUpdate
} = params.input;
const appConfig = await AppConfig
.query()
.findById(id)
.throwIfNotFound();
await appConfig
.$query()
.patch(
appConfigToUpdate
);
return appConfig;
};
export default updateAppConfig;

View File

@@ -1,52 +0,0 @@
import type { IJSONValue } from '@automatisch/types';
import Config from '../../models/config';
import Context from '../../types/express/context';
type Params = {
input: {
[index: string]: IJSONValue;
};
};
const updateConfig = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('update', 'Config');
const config = params.input;
const configKeys = Object.keys(config);
const updates = [];
for (const key of configKeys) {
const newValue = config[key];
if (newValue) {
const entryUpdate = Config.query()
.insert({
key,
value: {
data: newValue,
},
})
.onConflict('key')
.merge({
value: {
data: newValue,
},
});
updates.push(entryUpdate);
} else {
const entryUpdate = Config.query().findOne({ key }).delete();
updates.push(entryUpdate);
}
}
await Promise.all(updates);
return config;
};
export default updateConfig;

View File

@@ -1,12 +1,10 @@
import { IJSONObject } from '@automatisch/types';
import Context from '../../types/express/context';
import AppAuthClient from '../../models/app-auth-client';
import { IJSONObject } from '@automatisch/types';
type Params = {
input: {
id: string;
formattedData?: IJSONObject;
appAuthClientId?: string;
formattedData: IJSONObject;
};
};
@@ -24,21 +22,10 @@ const updateConnection = async (
})
.throwIfNotFound();
let formattedData = params.input.formattedData;
if (params.input.appAuthClientId) {
const appAuthClient = await AppAuthClient
.query()
.findById(params.input.appAuthClientId)
.throwIfNotFound();
formattedData = appAuthClient.formattedAuthDefaults;
}
connection = await connection.$query().patchAndFetch({
formattedData: {
...connection.formattedData,
...formattedData,
...params.input.formattedData,
},
});

View File

@@ -1,4 +1,3 @@
import Flow from '../../models/flow';
import Context from '../../types/express/context';
import flowQueue from '../../queues/flow';
import { REMOVE_AFTER_30_DAYS_OR_150_JOBS, REMOVE_AFTER_7_DAYS_OR_50_JOBS } from '../../helpers/remove-job-configuration';
@@ -19,14 +18,10 @@ const updateFlowStatus = async (
params: Params,
context: Context
) => {
const conditions = context.currentUser.can('publish', 'Flow');
const isCreator = conditions.isCreator;
const allFlows = Flow.query();
const userFlows = context.currentUser.$relatedQuery('flows');
const baseQuery = isCreator ? userFlows : allFlows;
context.currentUser.can('publish', 'Flow');
let flow = await baseQuery
.clone()
let flow = await context.currentUser
.$relatedQuery('flows')
.findOne({
id: params.input.id,
})

View File

@@ -1,7 +1,6 @@
import Context from '../../types/express/context';
import Role from '../../models/role';
import Permission from '../../models/permission';
import permissionCatalog from '../../helpers/permission-catalog.ee';
type Params = {
input: {
@@ -12,13 +11,12 @@ type Params = {
};
};
const updateRole = async (
// TODO: access
const updateUser = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('update', 'Role');
const {
id,
name,
@@ -26,66 +24,21 @@ const updateRole = async (
permissions,
} = params.input;
const role = await Role
.query()
.findById(id)
.throwIfNotFound();
const role = await Role.query().findById(id).throwIfNotFound();
try {
const updatedRole = await Role.transaction(async (trx) => {
await role.$relatedQuery('permissions', trx).delete();
// TODO: delete the unrelated items!
await role.$relatedQuery('permissions').unrelate();
if (permissions?.length) {
const sanitizedPermissions = permissions
.filter((permission) => {
const {
action,
subject,
conditions,
} = permission;
const relevantAction = permissionCatalog.actions.find(actionCatalogItem => actionCatalogItem.key === action);
const validSubject = relevantAction.subjects.includes(subject);
const validConditions = conditions.every(condition => {
return !!permissionCatalog
.conditions
.find((conditionCatalogItem) => conditionCatalogItem.key === condition);
})
return validSubject && validConditions;
})
.map((permission) => ({
...permission,
roleId: role.id,
}));
await Permission.query().insert(sanitizedPermissions);
// TODO: possibly assert that given permissions do actually exist in catalog
// TODO: possibly optimize it with patching the different permissions compared to current ones
return await role.$query()
.patchAndFetch(
{
name,
description,
permissions,
}
await role
.$query(trx)
.patch(
{
name,
description,
}
);
return await Role
.query(trx)
.leftJoinRelated({
permissions: true
})
.withGraphFetched({
permissions: true
})
.findById(id);
});
return updatedRole;
} catch (err) {
throw new Error('The role could not be updated!');
}
);
};
export default updateRole;
export default updateUser;

View File

@@ -1,7 +1,6 @@
import { IJSONObject } from '@automatisch/types';
import App from '../../models/app';
import Step from '../../models/step';
import Connection from '../../models/connection';
import Context from '../../types/express/context';
type Params = {
@@ -24,14 +23,12 @@ const updateStep = async (
params: Params,
context: Context
) => {
const { isCreator } = context.currentUser.can('update', 'Flow');
const userSteps = context.currentUser.$relatedQuery('steps');
const allSteps = Step.query();
const baseQuery = isCreator ? userSteps : allSteps;
context.currentUser.can('update', 'Flow');
const { input } = params;
let step = await baseQuery
let step = await context.currentUser
.$relatedQuery('steps')
.findOne({
'steps.id': input.id,
flow_id: input.flow.id,
@@ -39,24 +36,11 @@ const updateStep = async (
.throwIfNotFound();
if (input.connection.id) {
let canSeeAllConnections = false;
try {
const conditions = context.currentUser.can('read', 'Connection');
const hasConnection = await context.currentUser
.$relatedQuery('connections')
.findById(input.connection?.id);
canSeeAllConnections = !conditions.isCreator;
} catch {
// void
}
const userConnections = context.currentUser.$relatedQuery('connections');
const allConnections = Connection.query();
const baseConnectionsQuery = canSeeAllConnections ? allConnections : userConnections;
const connection = await baseConnectionsQuery
.clone()
.findById(input.connection?.id)
if (!connection) {
if (!hasConnection) {
throw new Error('The connection does not exist!');
}
}

View File

@@ -12,30 +12,21 @@ type Params = {
};
};
// TODO: access
const updateUser = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('update', 'User');
const userPayload: Partial<User> = {
email: params.input.email,
fullName: params.input.fullName,
};
try {
context.currentUser.can('update', 'Role');
userPayload.roleId = params.input.role.id;
} catch {
// void
}
const user = await User.query().patchAndFetchById(
params.input.id,
userPayload
);
const user = await User.query()
.patchAndFetchById(
params.input.id,
{
email: params.input.email,
fullName: params.input.fullName,
roleId: params.input.role.id,
}
);
return user;
};

View File

@@ -1,52 +0,0 @@
import type { SamlConfig } from '@node-saml/passport-saml';
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
import Context from '../../types/express/context';
type Params = {
input: {
name: string;
certificate: string;
signatureAlgorithm: SamlConfig['signatureAlgorithm'];
issuer: string;
entryPoint: string;
firstnameAttributeName: string;
surnameAttributeName: string;
emailAttributeName: string;
roleAttributeName: string;
defaultRoleId: string;
active: boolean;
};
};
const upsertSamlAuthProvider = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('create', 'SamlAuthProvider');
const samlAuthProviderPayload: Partial<SamlAuthProvider> = {
...params.input,
};
const existingSamlAuthProvider = await SamlAuthProvider.query()
.limit(1)
.first();
if (!existingSamlAuthProvider) {
const samlAuthProvider = await SamlAuthProvider.query().insert(
samlAuthProviderPayload
);
return samlAuthProvider;
}
const samlAuthProvider = await SamlAuthProvider.query().patchAndFetchById(
existingSamlAuthProvider.id,
samlAuthProviderPayload
);
return samlAuthProvider;
};
export default upsertSamlAuthProvider;

View File

@@ -1,54 +0,0 @@
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
import SamlAuthProvidersRoleMapping from '../../models/saml-auth-providers-role-mapping.ee';
import Context from '../../types/express/context';
type Params = {
input: {
samlAuthProviderId: string;
samlAuthProvidersRoleMappings: [
{
roleId: string;
remoteRoleName: string;
}
];
};
};
const upsertSamlAuthProvidersRoleMappings = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('update', 'SamlAuthProvider');
const samlAuthProviderId = params.input.samlAuthProviderId;
const samlAuthProvider = await SamlAuthProvider.query()
.findById(samlAuthProviderId)
.throwIfNotFound();
await samlAuthProvider
.$relatedQuery('samlAuthProvidersRoleMappings')
.delete();
if (!params.input.samlAuthProvidersRoleMappings) {
return [];
}
const samlAuthProvidersRoleMappingsData =
params.input.samlAuthProvidersRoleMappings.map(
(samlAuthProvidersRoleMapping) => ({
...samlAuthProvidersRoleMapping,
samlAuthProviderId: samlAuthProvider.id,
})
);
const samlAuthProvidersRoleMappings =
await SamlAuthProvidersRoleMapping.query().insert(
samlAuthProvidersRoleMappingsData
);
return samlAuthProvidersRoleMappings;
};
export default upsertSamlAuthProvidersRoleMappings;

View File

@@ -1,30 +0,0 @@
import AppAuthClient from '../../models/app-auth-client';
import Context from '../../types/express/context';
type Params = {
id: string;
};
const getAppAuthClient = async (_parent: unknown, params: Params, context: Context) => {
let canSeeAllClients = false;
try {
context.currentUser.can('read', 'App');
canSeeAllClients = true;
} catch {
// void
}
const appAuthClient = AppAuthClient
.query()
.findById(params.id)
.throwIfNotFound();
if (!canSeeAllClients) {
appAuthClient.where({ active: true });
}
return await appAuthClient;
};
export default getAppAuthClient;

View File

@@ -1,40 +0,0 @@
import AppConfig from '../../models/app-config';
import Context from '../../types/express/context';
type Params = {
appKey: string;
active: boolean;
};
const getAppAuthClients = async (_parent: unknown, params: Params, context: Context) => {
let canSeeAllClients = false;
try {
context.currentUser.can('read', 'App');
canSeeAllClients = true;
} catch {
// void
}
const appConfig = await AppConfig
.query()
.findOne({
key: params.appKey,
})
.throwIfNotFound();
const appAuthClients = appConfig
.$relatedQuery('appAuthClients')
.where({ active: params.active })
.skipUndefined();
if (!canSeeAllClients) {
appAuthClients.where({
active: true
})
}
return await appAuthClients;
};
export default getAppAuthClients;

View File

@@ -1,23 +0,0 @@
import AppConfig from '../../models/app-config';
import Context from '../../types/express/context';
type Params = {
key: string;
};
const getAppConfig = async (_parent: unknown, params: Params, context: Context) => {
context.currentUser.can('create', 'Connection');
const appConfig = await AppConfig
.query()
.withGraphFetched({
appAuthClients: true
})
.findOne({
key: params.key
});
return appConfig;
};
export default getAppConfig;

View File

@@ -1,5 +1,4 @@
import App from '../../models/app';
import Connection from '../../models/connection';
import Context from '../../types/express/context';
type Params = {
@@ -7,22 +6,14 @@ type Params = {
};
const getApp = async (_parent: unknown, params: Params, context: Context) => {
const conditions = context.currentUser.can('read', 'Connection');
const userConnections = context.currentUser.$relatedQuery('connections');
const allConnections = Connection.query();
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
context.currentUser.can('read', 'Connection');
const app = await App.findOneByKey(params.key);
if (context.currentUser) {
const connections = await connectionBaseQuery
.clone()
const connections = await context.currentUser
.$relatedQuery('connections')
.select('connections.*')
.withGraphFetched({
appConfig: true,
appAuthClient: true
})
.fullOuterJoinRelated('steps')
.where({
'connections.key': params.key,

View File

@@ -1,19 +1,8 @@
import appConfig from '../../config/app';
import { getLicense } from '../../helpers/license.ee';
const getAutomatischInfo = async () => {
const license = await getLicense();
const computedLicense = {
id: license ? license.id : null,
name: license ? license.name : null,
expireAt: license ? license.expireAt : null,
verified: license ? true : false,
};
return {
isCloud: appConfig.isCloud,
license: computedLicense,
};
};

View File

@@ -1,34 +0,0 @@
import { hasValidLicense } from '../../helpers/license.ee';
import Config from '../../models/config';
import Context from '../../types/express/context';
type Params = {
keys: string[];
};
const getConfig = async (
_parent: unknown,
params: Params,
context: Context
) => {
if (!await hasValidLicense()) return {};
const configQuery = Config
.query();
if (Array.isArray(params.keys)) {
configQuery.whereIn('key', params.keys);
}
const config = await configQuery;
return config.reduce((computedConfig, configEntry) => {
const { key, value } = configEntry;
computedConfig[key] = value?.data;
return computedConfig;
}, {} as Record<string, unknown>);
};
export default getConfig;

View File

@@ -1,8 +1,6 @@
import { IConnection } from '@automatisch/types';
import App from '../../models/app';
import Context from '../../types/express/context';
import Flow from '../../models/flow';
import Connection from '../../models/connection';
type Params = {
name: string;
@@ -13,27 +11,19 @@ const getConnectedApps = async (
params: Params,
context: Context
) => {
const conditions = context.currentUser.can('read', 'Connection');
const userConnections = context.currentUser.$relatedQuery('connections');
const allConnections = Connection.query();
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
const userFlows = context.currentUser.$relatedQuery('flows');
const allFlows = Flow.query();
const flowBaseQuery = conditions.isCreator ? userFlows : allFlows;
context.currentUser.can('read', 'Connection');
let apps = await App.findAll(params.name);
const connections = await connectionBaseQuery
.clone()
const connections = await context.currentUser
.$relatedQuery('connections')
.select('connections.key')
.where({ draft: false })
.count('connections.id as count')
.groupBy('connections.key');
const flows = await flowBaseQuery
.clone()
const flows = await context.currentUser
.$relatedQuery('flows')
.withGraphJoined('steps')
.orderBy('created_at', 'desc');

View File

@@ -1,7 +1,6 @@
import { IDynamicData, IJSONObject } from '@automatisch/types';
import Context from '../../types/express/context';
import App from '../../models/app';
import Step from '../../models/step';
import ExecutionStep from '../../models/execution-step';
import globalVariable from '../../helpers/global-variable';
import computeParameters from '../../helpers/compute-parameters';
@@ -17,13 +16,10 @@ const getDynamicData = async (
params: Params,
context: Context
) => {
const conditions = context.currentUser.can('update', 'Flow');
const userSteps = context.currentUser.$relatedQuery('steps');
const allSteps = Step.query();
const stepBaseQuery = conditions.isCreator ? userSteps : allSteps;
context.currentUser.can('update', 'Flow');
const step = await stepBaseQuery
.clone()
const step = await context.currentUser
.$relatedQuery('steps')
.withGraphFetched({
connection: true,
flow: true,

View File

@@ -1,7 +1,6 @@
import { IDynamicFields, IJSONObject } from '@automatisch/types';
import Context from '../../types/express/context';
import App from '../../models/app';
import Step from '../../models/step';
import globalVariable from '../../helpers/global-variable';
type Params = {
@@ -15,13 +14,10 @@ const getDynamicFields = async (
params: Params,
context: Context
) => {
const conditions = context.currentUser.can('update', 'Flow');
const userSteps = context.currentUser.$relatedQuery('steps');
const allSteps = Step.query();
const stepBaseQuery = conditions.isCreator ? userSteps : allSteps;
context.currentUser.can('update', 'Flow');
const step = await stepBaseQuery
.clone()
const step = await context.currentUser
.$relatedQuery('steps')
.withGraphFetched({
connection: true,
flow: true,
@@ -32,7 +28,7 @@ const getDynamicFields = async (
const connection = step.connection;
if (!step.appKey) return null;
if (!connection || !step.appKey) return null;
const app = await App.findOneByKey(step.appKey);
const $ = await globalVariable({ connection, app, flow: step.flow, step });

View File

@@ -1,6 +1,5 @@
import Context from '../../types/express/context';
import paginate from '../../helpers/pagination';
import Execution from '../../models/execution';
type Params = {
executionId: string;
@@ -13,13 +12,10 @@ const getExecutionSteps = async (
params: Params,
context: Context
) => {
const conditions = context.currentUser.can('read', 'Execution');
const userExecutions = context.currentUser.$relatedQuery('executions');
const allExecutions = Execution.query();
const executionBaseQuery = conditions.isCreator ? userExecutions : allExecutions;
context.currentUser.can('read', 'Execution');
const execution = await executionBaseQuery
.clone()
const execution = await context.currentUser
.$relatedQuery('executions')
.withSoftDeleted()
.findById(params.executionId)
.throwIfNotFound();

View File

@@ -1,5 +1,4 @@
import Context from '../../types/express/context';
import Execution from '../../models/execution';
type Params = {
executionId: string;
@@ -10,13 +9,10 @@ const getExecution = async (
params: Params,
context: Context
) => {
const conditions = context.currentUser.can('read', 'Execution');
const userExecutions = context.currentUser.$relatedQuery('executions');
const allExecutions = Execution.query();
const executionBaseQuery = conditions.isCreator ? userExecutions : allExecutions;
context.currentUser.can('read', 'Execution');
const execution = await executionBaseQuery
.clone()
const execution = await context.currentUser
.$relatedQuery('executions')
.withGraphFetched({
flow: {
steps: true,

View File

@@ -1,6 +1,5 @@
import { raw } from 'objection';
import Context from '../../types/express/context';
import Execution from '../../models/execution';
import paginate from '../../helpers/pagination';
type Params = {
@@ -13,11 +12,7 @@ const getExecutions = async (
params: Params,
context: Context
) => {
const conditions = context.currentUser.can('read', 'Execution');
const userExecutions = context.currentUser.$relatedQuery('executions');
const allExecutions = Execution.query();
const executionBaseQuery = conditions.isCreator ? userExecutions : allExecutions;
context.currentUser.can('read', 'Execution');
const selectStatusStatement = `
case
@@ -28,8 +23,8 @@ const getExecutions = async (
as status
`;
const executions = executionBaseQuery
.clone()
const executions = context.currentUser
.$relatedQuery('executions')
.joinRelated('executionSteps as execution_steps')
.select('executions.*', raw(selectStatusStatement))
.withSoftDeleted()

View File

@@ -1,18 +1,14 @@
import Context from '../../types/express/context';
import Flow from '../../models/flow';
type Params = {
id: string;
};
const getFlow = async (_parent: unknown, params: Params, context: Context) => {
const conditions = context.currentUser.can('read', 'Flow');
const userFlows = context.currentUser.$relatedQuery('flows');
const allFlows = Flow.query();
const baseQuery = conditions.isCreator ? userFlows : allFlows;
context.currentUser.can('read', 'Flow');
const flow = await baseQuery
.clone()
const flow = await context.currentUser
.$relatedQuery('flows')
.withGraphJoined('[steps.[connection]]')
.orderBy('steps.position', 'asc')
.findOne({ 'flows.id': params.id })

Some files were not shown because too many files have changed in this diff Show More