Compare commits
52 Commits
v0.8.0
...
migrate-fl
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9752e2c4d2 | ||
![]() |
a5b31da3cc | ||
![]() |
8f7785e9d2 | ||
![]() |
69297c2dd8 | ||
![]() |
1c8e9fac7c | ||
![]() |
590780a539 | ||
![]() |
cbd1f47e87 | ||
![]() |
f89cff4e4a | ||
![]() |
cb08e0bf9f | ||
![]() |
3b54b29a99 | ||
![]() |
25983e046c | ||
![]() |
a6a124d2e6 | ||
![]() |
c7e1d30553 | ||
![]() |
6cc8c45634 | ||
![]() |
ee9a9114b7 | ||
![]() |
11f00f866c | ||
![]() |
03ea61ba81 | ||
![]() |
f6c500c998 | ||
![]() |
b590f0f98f | ||
![]() |
ef9359b208 | ||
![]() |
efd243a340 | ||
![]() |
bafb8b86db | ||
![]() |
84b701747f | ||
![]() |
ec42446daa | ||
![]() |
5046c4c911 | ||
![]() |
ce8c9906cb | ||
![]() |
6fb5482bba | ||
![]() |
58189963f5 | ||
![]() |
f488a71304 | ||
![]() |
4b706e004d | ||
![]() |
40e10cc270 | ||
![]() |
41db227eb3 | ||
![]() |
43eea965c5 | ||
![]() |
8101c9f0bc | ||
![]() |
b4cda90338 | ||
![]() |
7ca37c412e | ||
![]() |
e4e3356dc9 | ||
![]() |
0deaa03218 | ||
![]() |
a7104c41a2 | ||
![]() |
5176b8c322 | ||
![]() |
c37c70446d | ||
![]() |
63abc8a2c8 | ||
![]() |
ba5c038e3b | ||
![]() |
a6669415f5 | ||
![]() |
4086fad867 | ||
![]() |
8a71c13078 | ||
![]() |
5d77f64e76 | ||
![]() |
0d092b977f | ||
![]() |
69582ff83d | ||
![]() |
a5c7da331a | ||
![]() |
8e842296b7 | ||
![]() |
7db14d1df7 |
@@ -33,7 +33,32 @@ services:
|
||||
- '6379:6379'
|
||||
expose:
|
||||
- 6379
|
||||
keycloak:
|
||||
image: quay.io/keycloak/keycloak:21.1
|
||||
restart: always
|
||||
container_name: keycloak
|
||||
environment:
|
||||
- KEYCLOAK_ADMIN=admin
|
||||
- KEYCLOAK_ADMIN_PASSWORD=admin
|
||||
- KC_DB=postgres
|
||||
- KC_DB_URL_HOST=postgres
|
||||
- KC_DB_URL_DATABASE=keycloak
|
||||
- KC_DB_USERNAME=automatisch_user
|
||||
- KC_DB_PASSWORD=automatisch_password
|
||||
- KC_HEALTH_ENABLED=true
|
||||
ports:
|
||||
- "8080:8080"
|
||||
command: start-dev
|
||||
depends_on:
|
||||
- postgres
|
||||
healthcheck:
|
||||
test: "curl -f http://localhost:8080/health/ready || exit 1"
|
||||
volumes:
|
||||
- keycloak:/opt/keycloak/data/
|
||||
expose:
|
||||
- 8080
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
keycloak:
|
||||
|
25
.github/workflows/playwright.yml
vendored
Normal file
25
.github/workflows/playwright.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Automatisch UI Test
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 12 * * *'
|
||||
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
|
||||
run: yarn playwright test
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
@@ -2,18 +2,33 @@ import appConfig from '../../src/config/app';
|
||||
import logger from '../../src/helpers/logger';
|
||||
import client from './client';
|
||||
import User from '../../src/models/user';
|
||||
import Role from '../../src/models/role';
|
||||
import '../../src/config/orm';
|
||||
|
||||
async function fetchAdminRole() {
|
||||
const role = await Role
|
||||
.query()
|
||||
.where({
|
||||
key: 'admin'
|
||||
})
|
||||
.limit(1)
|
||||
.first();
|
||||
|
||||
return role;
|
||||
}
|
||||
|
||||
export async function createUser(
|
||||
email = 'user@automatisch.io',
|
||||
password = 'sample'
|
||||
) {
|
||||
const UNIQUE_VIOLATION_CODE = '23505';
|
||||
|
||||
const role = await fetchAdminRole();
|
||||
const userParams = {
|
||||
email,
|
||||
password,
|
||||
fullName: 'Initial admin',
|
||||
role: 'admin',
|
||||
roleId: role.id,
|
||||
};
|
||||
|
||||
try {
|
||||
|
@@ -12,6 +12,7 @@ const knexConfig = {
|
||||
database: appConfig.postgresDatabase,
|
||||
ssl: appConfig.postgresEnableSsl,
|
||||
},
|
||||
asyncStackTraces: appConfig.isDev,
|
||||
searchPath: [appConfig.postgresSchema],
|
||||
pool: { min: 0, max: 20 },
|
||||
migrations: {
|
||||
|
@@ -4,7 +4,7 @@
|
||||
"license": "See LICENSE file",
|
||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||
"scripts": {
|
||||
"dev": "ts-node-dev --exit-child src/server.ts",
|
||||
"dev": "ts-node-dev --watch 'src/graphql/schema.graphql' --exit-child src/server.ts",
|
||||
"worker": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/worker.ts",
|
||||
"build": "tsc && yarn copy-statics",
|
||||
"build:watch": "nodemon --watch 'src/**/*.ts' --watch 'bin/**/*.ts' --exec yarn build --ext ts",
|
||||
@@ -24,12 +24,15 @@
|
||||
"dependencies": {
|
||||
"@automatisch/web": "^0.8.0",
|
||||
"@bull-board/express": "^3.10.1",
|
||||
"@casl/ability": "^6.5.0",
|
||||
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
||||
"@graphql-tools/load": "^7.5.2",
|
||||
"@node-saml/passport-saml": "^4.0.4",
|
||||
"@rudderstack/rudder-sdk-node": "^1.1.2",
|
||||
"@sentry/node": "^7.42.0",
|
||||
"@sentry/tracing": "^7.42.0",
|
||||
"@types/luxon": "^2.3.1",
|
||||
"@types/passport": "^1.0.12",
|
||||
"@types/xmlrpc": "^1.3.7",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"axios": "0.24.0",
|
||||
@@ -59,11 +62,14 @@
|
||||
"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"
|
||||
@@ -120,6 +126,7 @@
|
||||
"@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",
|
||||
|
@@ -17,6 +17,7 @@ import {
|
||||
} from './helpers/create-bull-board-handler';
|
||||
import injectBullBoardHandler from './helpers/inject-bull-board-handler';
|
||||
import router from './routes';
|
||||
import configurePassport from './helpers/passport';
|
||||
|
||||
createBullBoardHandler(serverAdapter);
|
||||
|
||||
@@ -50,6 +51,9 @@ app.use(
|
||||
})
|
||||
);
|
||||
app.use(cors(corsOptions));
|
||||
|
||||
configurePassport(app);
|
||||
|
||||
app.use('/', router);
|
||||
|
||||
webUIHandler(app);
|
||||
|
3
packages/backend/src/apps/formatter/actions/index.ts
Normal file
3
packages/backend/src/apps/formatter/actions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import text from './text';
|
||||
|
||||
export default [text];
|
64
packages/backend/src/apps/formatter/actions/text/index.ts
Normal file
64
packages/backend/src/apps/formatter/actions/text/index.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
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,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
@@ -0,0 +1,11 @@
|
||||
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;
|
@@ -0,0 +1,12 @@
|
||||
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;
|
@@ -0,0 +1,11 @@
|
||||
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;
|
@@ -0,0 +1,13 @@
|
||||
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;
|
@@ -0,0 +1,13 @@
|
||||
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;
|
3
packages/backend/src/apps/formatter/assets/favicon.svg
Normal file
3
packages/backend/src/apps/formatter/assets/favicon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<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>
|
After Width: | Height: | Size: 243 B |
@@ -0,0 +1,3 @@
|
||||
import listTransformOptions from './list-transform-options';
|
||||
|
||||
export default [listTransformOptions];
|
@@ -0,0 +1,23 @@
|
||||
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];
|
||||
},
|
||||
};
|
@@ -0,0 +1,12 @@
|
||||
const capitalize = [
|
||||
{
|
||||
label: 'Input',
|
||||
key: 'input',
|
||||
type: 'string' as const,
|
||||
required: true,
|
||||
description: 'Text that will be capitalized.',
|
||||
variables: true,
|
||||
},
|
||||
];
|
||||
|
||||
export default capitalize;
|
@@ -0,0 +1,12 @@
|
||||
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;
|
@@ -0,0 +1,12 @@
|
||||
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;
|
@@ -0,0 +1,12 @@
|
||||
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;
|
@@ -0,0 +1,21 @@
|
||||
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;
|
0
packages/backend/src/apps/formatter/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/formatter/index.d.ts
vendored
Normal file
16
packages/backend/src/apps/formatter/index.ts
Normal file
16
packages/backend/src/apps/formatter/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
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,
|
||||
});
|
@@ -25,6 +25,12 @@ 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,
|
||||
@@ -34,7 +40,7 @@ const verifyCredentials = async ($: IGlobalVariable) => {
|
||||
scope: data.scope,
|
||||
tokenType: data.token_type,
|
||||
userId: currentUser.id,
|
||||
screenName: `${currentUser.username} @ ${$.auth.data.instanceUrl}`,
|
||||
screenName,
|
||||
});
|
||||
};
|
||||
|
||||
|
6
packages/backend/src/apps/wordpress/assets/favicon.svg
Normal file
6
packages/backend/src/apps/wordpress/assets/favicon.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?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>
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,24 @@
|
||||
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,
|
||||
});
|
||||
}
|
24
packages/backend/src/apps/wordpress/auth/index.ts
Normal file
24
packages/backend/src/apps/wordpress/auth/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
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,
|
||||
};
|
@@ -0,0 +1,9 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const isStillVerified = async ($: IGlobalVariable) => {
|
||||
await $.http.get('?rest_route=/wp/v2/settings');
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export default isStillVerified;
|
@@ -0,0 +1,24 @@
|
||||
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;
|
@@ -0,0 +1,17 @@
|
||||
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;
|
@@ -0,0 +1,7 @@
|
||||
import { IGlobalVariable } from '@automatisch/types';
|
||||
|
||||
const getInstanceUrl = ($: IGlobalVariable): string => {
|
||||
return $.auth.data.instanceUrl as string;
|
||||
};
|
||||
|
||||
export default getInstanceUrl;
|
12
packages/backend/src/apps/wordpress/common/set-base-url.ts
Normal file
12
packages/backend/src/apps/wordpress/common/set-base-url.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
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;
|
@@ -0,0 +1,3 @@
|
||||
import listStatuses from './list-statuses';
|
||||
|
||||
export default [listStatuses];
|
@@ -0,0 +1,37 @@
|
||||
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;
|
||||
},
|
||||
};
|
0
packages/backend/src/apps/wordpress/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/wordpress/index.d.ts
vendored
Normal file
21
packages/backend/src/apps/wordpress/index.ts
Normal file
21
packages/backend/src/apps/wordpress/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
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,
|
||||
});
|
3
packages/backend/src/apps/wordpress/triggers/index.ts
Normal file
3
packages/backend/src/apps/wordpress/triggers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import newPost from './new-post';
|
||||
|
||||
export default [newPost];
|
@@ -0,0 +1,60 @@
|
||||
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);
|
||||
},
|
||||
});
|
@@ -0,0 +1,46 @@
|
||||
import { Knex } from 'knex';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
import lowerCase from 'lodash/lowerCase';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.createTable('roles', (table) => {
|
||||
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
|
||||
table.string('name').notNullable();
|
||||
table.string('key').notNullable();
|
||||
table.string('description');
|
||||
|
||||
table.timestamps(true, true);
|
||||
});
|
||||
|
||||
const uniqueUserRoles = await knex('users')
|
||||
.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',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
return knex.schema.dropTable('roles');
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
const getPermissionForRole = (roleId: string, subject: string, actions: string[], conditions: string[] = []) => actions
|
||||
.map(action => ({
|
||||
role_id: roleId,
|
||||
subject,
|
||||
action,
|
||||
conditions,
|
||||
}));
|
||||
|
||||
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']),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
return knex.schema.dropTable('permissions');
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.table('users', async (table) => {
|
||||
table.uuid('role_id').references('id').inTable('roles');
|
||||
});
|
||||
|
||||
const theRole = await knex('roles').select('id').limit(1).first();
|
||||
const roles = await knex('roles').select('id', 'key');
|
||||
|
||||
for (const role of roles) {
|
||||
await knex('users')
|
||||
.where({
|
||||
role: role.key
|
||||
})
|
||||
.update({
|
||||
role_id: role.id
|
||||
});
|
||||
}
|
||||
|
||||
// backfill not-migratables
|
||||
await knex('users').whereNull('role_id').update({ role_id: theRole.id });
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
return await knex.schema.table('users', (table) => {
|
||||
table.dropColumn('role_id');
|
||||
});
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.table('users', async (table) => {
|
||||
table.dropColumn('role');
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
return await knex.schema.table('users', (table) => {
|
||||
table.string('role').defaultTo('user');
|
||||
});
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
return knex.schema.createTable('saml_auth_providers', (table) => {
|
||||
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
|
||||
table.string('name').notNullable();
|
||||
table.text('certificate').notNullable();
|
||||
table.string('signature_algorithm').notNullable();
|
||||
table.string('issuer').notNullable();
|
||||
table.text('entry_point').notNullable();
|
||||
table.text('firstname_attribute_name').notNullable();
|
||||
table.text('surname_attribute_name').notNullable();
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
return knex.schema.dropTable('saml_auth_providers');
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
return knex.schema.createTable('identities', (table) => {
|
||||
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
|
||||
table.uuid('user_id').references('id').inTable('users');
|
||||
table.string('remote_id').notNullable();
|
||||
table.string('provider_id').notNullable();
|
||||
table.string('provider_type').notNullable();
|
||||
|
||||
table.timestamps(true, true);
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
return knex.schema.dropTable('identities');
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
return await knex.schema.alterTable('users', (table) => {
|
||||
table.string('password').nullable().alter();
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(): Promise<void> {
|
||||
// void
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
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();
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
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');
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
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();
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
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');
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
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');
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
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');
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
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');
|
||||
});
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
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();
|
||||
}
|
@@ -1,47 +1,73 @@
|
||||
import createAppAuthClient from './mutations/create-app-auth-client.ee';
|
||||
import createAppConfig from './mutations/create-app-config.ee';
|
||||
import createConnection from './mutations/create-connection';
|
||||
import generateAuthUrl from './mutations/generate-auth-url';
|
||||
import updateConnection from './mutations/update-connection';
|
||||
import resetConnection from './mutations/reset-connection';
|
||||
import verifyConnection from './mutations/verify-connection';
|
||||
import deleteConnection from './mutations/delete-connection';
|
||||
import createFlow from './mutations/create-flow';
|
||||
import createRole from './mutations/create-role.ee';
|
||||
import createStep from './mutations/create-step';
|
||||
import createUser from './mutations/create-user.ee';
|
||||
import deleteConnection from './mutations/delete-connection';
|
||||
import deleteCurrentUser from './mutations/delete-current-user.ee';
|
||||
import deleteFlow from './mutations/delete-flow';
|
||||
import deleteRole from './mutations/delete-role.ee';
|
||||
import deleteStep from './mutations/delete-step';
|
||||
import deleteUser from './mutations/delete-user.ee';
|
||||
import duplicateFlow from './mutations/duplicate-flow';
|
||||
import executeFlow from './mutations/execute-flow';
|
||||
import forgotPassword from './mutations/forgot-password.ee';
|
||||
import generateAuthUrl from './mutations/generate-auth-url';
|
||||
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';
|
||||
import updateFlowStatus from './mutations/update-flow-status';
|
||||
import executeFlow from './mutations/execute-flow';
|
||||
import deleteFlow from './mutations/delete-flow';
|
||||
import duplicateFlow from './mutations/duplicate-flow';
|
||||
import createStep from './mutations/create-step';
|
||||
import updateRole from './mutations/update-role.ee';
|
||||
import updateStep from './mutations/update-step';
|
||||
import deleteStep from './mutations/delete-step';
|
||||
import createUser from './mutations/create-user.ee';
|
||||
import deleteUser from './mutations/delete-user.ee';
|
||||
import updateUser from './mutations/update-user';
|
||||
import forgotPassword from './mutations/forgot-password.ee';
|
||||
import resetPassword from './mutations/reset-password.ee';
|
||||
import login from './mutations/login';
|
||||
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,
|
||||
generateAuthUrl,
|
||||
updateConnection,
|
||||
resetConnection,
|
||||
verifyConnection,
|
||||
deleteConnection,
|
||||
createFlow,
|
||||
createRole,
|
||||
createStep,
|
||||
createUser,
|
||||
deleteConnection,
|
||||
deleteCurrentUser,
|
||||
deleteFlow,
|
||||
deleteRole,
|
||||
deleteStep,
|
||||
deleteUser,
|
||||
duplicateFlow,
|
||||
executeFlow,
|
||||
forgotPassword,
|
||||
generateAuthUrl,
|
||||
login,
|
||||
registerUser,
|
||||
resetConnection,
|
||||
resetPassword,
|
||||
updateAppAuthClient,
|
||||
updateAppConfig,
|
||||
updateConfig,
|
||||
updateConnection,
|
||||
updateCurrentUser,
|
||||
updateFlow,
|
||||
updateFlowStatus,
|
||||
executeFlow,
|
||||
deleteFlow,
|
||||
duplicateFlow,
|
||||
createStep,
|
||||
updateRole,
|
||||
updateStep,
|
||||
deleteStep,
|
||||
createUser,
|
||||
deleteUser,
|
||||
updateUser,
|
||||
forgotPassword,
|
||||
resetPassword,
|
||||
login,
|
||||
upsertSamlAuthProvider,
|
||||
upsertSamlAuthProvidersRoleMappings,
|
||||
verifyConnection,
|
||||
};
|
||||
|
||||
export default mutationResolvers;
|
||||
|
@@ -0,0 +1,35 @@
|
||||
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;
|
@@ -0,0 +1,36 @@
|
||||
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;
|
@@ -1,25 +1,59 @@
|
||||
import App from '../../models/app';
|
||||
import Context from '../../types/express/context';
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
import App from '../../models/app';
|
||||
import AppConfig from '../../models/app-config';
|
||||
import Context from '../../types/express/context';
|
||||
|
||||
type Params = {
|
||||
input: {
|
||||
key: string;
|
||||
appAuthClientId: string;
|
||||
formattedData: IJSONObject;
|
||||
};
|
||||
};
|
||||
|
||||
const createConnection = async (
|
||||
_parent: unknown,
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
await App.findOneByKey(params.input.key);
|
||||
context.currentUser.can('create', 'Connection');
|
||||
|
||||
return await context.currentUser.$relatedQuery('connections').insert({
|
||||
key: params.input.key,
|
||||
formattedData: params.input.formattedData,
|
||||
const { key, appAuthClientId } = params.input;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
export default createConnection;
|
||||
|
@@ -14,6 +14,8 @@ const createFlow = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('create', 'Flow');
|
||||
|
||||
const connectionId = params?.input?.connectionId;
|
||||
const appKey = params?.input?.triggerAppKey;
|
||||
|
||||
|
34
packages/backend/src/graphql/mutations/create-role.ee.ts
Normal file
34
packages/backend/src/graphql/mutations/create-role.ee.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import kebabCase from 'lodash/kebabCase';
|
||||
import Permission from '../../models/permission';
|
||||
import Role from '../../models/role';
|
||||
import Context from '../../types/express/context';
|
||||
|
||||
type Params = {
|
||||
input: {
|
||||
name: string;
|
||||
description: string;
|
||||
permissions: Permission[];
|
||||
};
|
||||
};
|
||||
|
||||
const createRole = async (_parent: unknown, params: Params, context: Context) => {
|
||||
context.currentUser.can('create', 'Role');
|
||||
|
||||
const { name, description, permissions } = params.input;
|
||||
const key = kebabCase(name);
|
||||
|
||||
const existingRole = await Role.query().findOne({ key });
|
||||
|
||||
if (existingRole) {
|
||||
throw new Error('Role already exists!');
|
||||
}
|
||||
|
||||
return await Role.query().insertGraph({
|
||||
key,
|
||||
name,
|
||||
description,
|
||||
permissions,
|
||||
}, { relate: ['permissions'] }).returning('*');
|
||||
};
|
||||
|
||||
export default createRole;
|
@@ -22,6 +22,8 @@ const createStep = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('update', 'Flow');
|
||||
|
||||
const { input } = params;
|
||||
|
||||
if (input.appKey && input.key) {
|
||||
|
@@ -1,14 +1,21 @@
|
||||
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;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
const createUser = async (_parent: unknown, params: Params) => {
|
||||
const createUser = async (_parent: unknown, params: Params, context: Context) => {
|
||||
context.currentUser.can('create', 'User');
|
||||
|
||||
const { fullName, email, password } = params.input;
|
||||
|
||||
const existingUser = await User.query().findOne({ email });
|
||||
@@ -17,12 +24,23 @@ const createUser = async (_parent: unknown, params: Params) => {
|
||||
throw new Error('User already exists!');
|
||||
}
|
||||
|
||||
const user = await User.query().insert({
|
||||
const userPayload: Partial<User> = {
|
||||
fullName,
|
||||
email,
|
||||
password,
|
||||
role: 'user',
|
||||
});
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
return user;
|
||||
};
|
||||
|
@@ -0,0 +1,28 @@
|
||||
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;
|
@@ -11,6 +11,8 @@ const deleteConnection = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('delete', 'Connection');
|
||||
|
||||
await context.currentUser
|
||||
.$relatedQuery('connections')
|
||||
.delete()
|
||||
|
@@ -0,0 +1,22 @@
|
||||
import { Duration } from 'luxon';
|
||||
import Context from '../../types/express/context';
|
||||
import deleteUserQueue from '../../queues/delete-user.ee';
|
||||
|
||||
const deleteCurrentUser = async (_parent: unknown, params: never, context: Context) => {
|
||||
const id = context.currentUser.id;
|
||||
|
||||
await context.currentUser.$query().delete();
|
||||
|
||||
const jobName = `Delete user - ${id}`;
|
||||
const jobPayload = { id };
|
||||
const millisecondsFor30Days = Duration.fromObject({ days: 30 }).toMillis();
|
||||
const jobOptions = {
|
||||
delay: millisecondsFor30Days
|
||||
};
|
||||
|
||||
await deleteUserQueue.add(jobName, jobPayload, jobOptions);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export default deleteCurrentUser;
|
@@ -13,6 +13,8 @@ const deleteFlow = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('delete', 'Flow');
|
||||
|
||||
const flow = await context.currentUser
|
||||
.$relatedQuery('flows')
|
||||
.findOne({
|
||||
|
47
packages/backend/src/graphql/mutations/delete-role.ee.ts
Normal file
47
packages/backend/src/graphql/mutations/delete-role.ee.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import Role from '../../models/role';
|
||||
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
|
||||
import Context from '../../types/express/context';
|
||||
|
||||
type Params = {
|
||||
input: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
|
||||
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();
|
||||
await role.$query().delete();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export default deleteRole;
|
@@ -11,6 +11,8 @@ const deleteStep = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('update', 'Flow');
|
||||
|
||||
const step = await context.currentUser
|
||||
.$relatedQuery('steps')
|
||||
.withGraphFetched('flow')
|
||||
|
@@ -1,11 +1,24 @@
|
||||
import Context from '../../types/express/context';
|
||||
import deleteUserQueue from '../../queues/delete-user.ee';
|
||||
import { Duration } from 'luxon';
|
||||
import Context from '../../types/express/context';
|
||||
import User from '../../models/user';
|
||||
import deleteUserQueue from '../../queues/delete-user.ee';
|
||||
|
||||
const deleteUser = async (_parent: unknown, params: never, context: Context) => {
|
||||
const id = context.currentUser.id;
|
||||
type Params = {
|
||||
input: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
|
||||
await context.currentUser.$query().delete();
|
||||
const deleteUser = async (
|
||||
_parent: unknown,
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('delete', 'User');
|
||||
|
||||
const id = params.input.id;
|
||||
|
||||
await User.query().deleteById(id);
|
||||
|
||||
const jobName = `Delete user - ${id}`;
|
||||
const jobPayload = { id };
|
||||
|
@@ -53,6 +53,8 @@ const duplicateFlow = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('create', 'Flow');
|
||||
|
||||
const flow = await context.currentUser
|
||||
.$relatedQuery('flows')
|
||||
.withGraphJoined('[steps]')
|
||||
|
@@ -12,6 +12,8 @@ const executeFlow = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('update', 'Flow');
|
||||
|
||||
const { stepId } = params.input;
|
||||
|
||||
const untilStep = await context.currentUser
|
||||
|
@@ -13,6 +13,8 @@ const generateAuthUrl = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('create', 'Connection');
|
||||
|
||||
const connection = await context.currentUser
|
||||
.$relatedQuery('connections')
|
||||
.findOne({
|
||||
|
33
packages/backend/src/graphql/mutations/register-user.ee.ts
Normal file
33
packages/backend/src/graphql/mutations/register-user.ee.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import User from '../../models/user';
|
||||
import Role from '../../models/role';
|
||||
|
||||
type Params = {
|
||||
input: {
|
||||
fullName: string;
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
};
|
||||
|
||||
const registerUser = async (_parent: unknown, params: Params) => {
|
||||
const { fullName, email, password } = params.input;
|
||||
|
||||
const existingUser = await User.query().findOne({ email });
|
||||
|
||||
if (existingUser) {
|
||||
throw new Error('User already exists!');
|
||||
}
|
||||
|
||||
const role = await Role.query().findOne({ key: 'user' });
|
||||
|
||||
const user = await User.query().insert({
|
||||
fullName,
|
||||
email,
|
||||
password,
|
||||
roleId: role.id,
|
||||
});
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
export default registerUser;
|
@@ -11,6 +11,8 @@ const resetConnection = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('create', 'Connection');
|
||||
|
||||
let connection = await context.currentUser
|
||||
.$relatedQuery('connections')
|
||||
.findOne({
|
||||
|
@@ -0,0 +1,38 @@
|
||||
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;
|
@@ -0,0 +1,39 @@
|
||||
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;
|
44
packages/backend/src/graphql/mutations/update-config.ee.ts
Normal file
44
packages/backend/src/graphql/mutations/update-config.ee.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
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];
|
||||
|
||||
const entryUpdate = Config
|
||||
.query()
|
||||
.insert({
|
||||
key,
|
||||
value: {
|
||||
data: newValue
|
||||
}
|
||||
})
|
||||
.onConflict('key')
|
||||
.merge({
|
||||
value: {
|
||||
data: newValue
|
||||
}
|
||||
});
|
||||
|
||||
updates.push(entryUpdate);
|
||||
}
|
||||
|
||||
await Promise.all(updates);
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
export default updateConfig;
|
@@ -1,10 +1,12 @@
|
||||
import Context from '../../types/express/context';
|
||||
import { IJSONObject } from '@automatisch/types';
|
||||
import Context from '../../types/express/context';
|
||||
import AppAuthClient from '../../models/app-auth-client';
|
||||
|
||||
type Params = {
|
||||
input: {
|
||||
id: string;
|
||||
formattedData: IJSONObject;
|
||||
formattedData?: IJSONObject;
|
||||
appAuthClientId?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -13,6 +15,8 @@ const updateConnection = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('create', 'Connection');
|
||||
|
||||
let connection = await context.currentUser
|
||||
.$relatedQuery('connections')
|
||||
.findOne({
|
||||
@@ -20,10 +24,21 @@ 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,
|
||||
...params.input.formattedData,
|
||||
...formattedData,
|
||||
},
|
||||
});
|
||||
|
||||
|
@@ -8,7 +8,7 @@ type Params = {
|
||||
};
|
||||
};
|
||||
|
||||
const updateUser = async (
|
||||
const updateCurrentUser = async (
|
||||
_parent: unknown,
|
||||
params: Params,
|
||||
context: Context
|
||||
@@ -22,4 +22,4 @@ const updateUser = async (
|
||||
return user;
|
||||
};
|
||||
|
||||
export default updateUser;
|
||||
export default updateCurrentUser;
|
@@ -18,6 +18,8 @@ const updateFlowStatus = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('publish', 'Flow');
|
||||
|
||||
let flow = await context.currentUser
|
||||
.$relatedQuery('flows')
|
||||
.findOne({
|
||||
@@ -55,7 +57,7 @@ const updateFlowStatus = async (
|
||||
} else {
|
||||
if (newActiveValue) {
|
||||
flow = await flow.$query().patchAndFetch({
|
||||
published_at: new Date().toISOString(),
|
||||
publishedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
const jobName = `${JOB_NAME}-${flow.id}`;
|
||||
@@ -78,7 +80,10 @@ const updateFlowStatus = async (
|
||||
}
|
||||
}
|
||||
|
||||
flow = await flow.$query().withGraphFetched('steps').patchAndFetch({
|
||||
flow = await flow
|
||||
.$query()
|
||||
.withGraphFetched('steps')
|
||||
.patchAndFetch({
|
||||
active: newActiveValue,
|
||||
});
|
||||
|
||||
|
@@ -12,6 +12,8 @@ const updateFlow = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('update', 'Flow');
|
||||
|
||||
let flow = await context.currentUser
|
||||
.$relatedQuery('flows')
|
||||
.findOne({
|
||||
|
91
packages/backend/src/graphql/mutations/update-role.ee.ts
Normal file
91
packages/backend/src/graphql/mutations/update-role.ee.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
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: {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
permissions: Permission[];
|
||||
};
|
||||
};
|
||||
|
||||
const updateRole = async (
|
||||
_parent: unknown,
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('update', 'Role');
|
||||
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
permissions,
|
||||
} = params.input;
|
||||
|
||||
const role = await Role
|
||||
.query()
|
||||
.findById(id)
|
||||
.throwIfNotFound();
|
||||
|
||||
try {
|
||||
const updatedRole = await Role.transaction(async (trx) => {
|
||||
await role.$relatedQuery('permissions', trx).delete();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
@@ -23,6 +23,8 @@ const updateStep = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('update', 'Flow');
|
||||
|
||||
const { input } = params;
|
||||
|
||||
let step = await context.currentUser
|
||||
|
43
packages/backend/src/graphql/mutations/update-user.ee.ts
Normal file
43
packages/backend/src/graphql/mutations/update-user.ee.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import Context from '../../types/express/context';
|
||||
import User from '../../models/user';
|
||||
|
||||
type Params = {
|
||||
input: {
|
||||
id: string;
|
||||
email: string;
|
||||
fullName: string;
|
||||
role: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
return user;
|
||||
};
|
||||
|
||||
export default updateUser;
|
@@ -0,0 +1,52 @@
|
||||
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;
|
@@ -0,0 +1,54 @@
|
||||
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;
|
@@ -13,6 +13,8 @@ const verifyConnection = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
context.currentUser.can('create', 'Connection');
|
||||
|
||||
let connection = await context.currentUser
|
||||
.$relatedQuery('connections')
|
||||
.findOne({
|
||||
|
@@ -0,0 +1,30 @@
|
||||
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;
|
@@ -0,0 +1,40 @@
|
||||
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;
|
23
packages/backend/src/graphql/queries/get-app-config.ee.ts
Normal file
23
packages/backend/src/graphql/queries/get-app-config.ee.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
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;
|
@@ -1,4 +1,5 @@
|
||||
import App from '../../models/app';
|
||||
import Connection from '../../models/connection';
|
||||
import Context from '../../types/express/context';
|
||||
|
||||
type Params = {
|
||||
@@ -6,12 +7,22 @@ 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;
|
||||
|
||||
const app = await App.findOneByKey(params.key);
|
||||
|
||||
if (context.currentUser) {
|
||||
const connections = await context.currentUser
|
||||
.$relatedQuery('connections')
|
||||
const connections = await connectionBaseQuery
|
||||
.clone()
|
||||
.select('connections.*')
|
||||
.withGraphFetched({
|
||||
appConfig: true,
|
||||
appAuthClient: true
|
||||
})
|
||||
.fullOuterJoinRelated('steps')
|
||||
.where({
|
||||
'connections.key': params.key,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import App from '../../models/app';
|
||||
import { IApp } from '@automatisch/types';
|
||||
import App from '../../models/app';
|
||||
|
||||
type Params = {
|
||||
name: string;
|
||||
|
@@ -1,8 +1,19 @@
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
||||
|
34
packages/backend/src/graphql/queries/get-config.ee.ts
Normal file
34
packages/backend/src/graphql/queries/get-config.ee.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
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;
|
@@ -1,6 +1,8 @@
|
||||
import { IConnection } from '@automatisch/types';
|
||||
import App from '../../models/app';
|
||||
import Context from '../../types/express/context';
|
||||
import { IApp, IConnection } from '@automatisch/types';
|
||||
import Flow from '../../models/flow';
|
||||
import Connection from '../../models/connection';
|
||||
|
||||
type Params = {
|
||||
name: string;
|
||||
@@ -11,17 +13,27 @@ 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;
|
||||
|
||||
let apps = await App.findAll(params.name);
|
||||
|
||||
const connections = await context.currentUser
|
||||
.$relatedQuery('connections')
|
||||
const connections = await connectionBaseQuery
|
||||
.clone()
|
||||
.select('connections.key')
|
||||
.where({ draft: false })
|
||||
.count('connections.id as count')
|
||||
.groupBy('connections.key');
|
||||
|
||||
const flows = await context.currentUser
|
||||
.$relatedQuery('flows')
|
||||
const flows = await flowBaseQuery
|
||||
.clone()
|
||||
.withGraphJoined('steps')
|
||||
.orderBy('created_at', 'desc');
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
@@ -16,8 +17,13 @@ const getDynamicData = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
const step = await context.currentUser
|
||||
.$relatedQuery('steps')
|
||||
const conditions = context.currentUser.can('update', 'Flow');
|
||||
const userSteps = context.currentUser.$relatedQuery('steps');
|
||||
const allSteps = Step.query();
|
||||
const stepBaseQuery = conditions.isCreator ? userSteps : allSteps;
|
||||
|
||||
const step = await stepBaseQuery
|
||||
.clone()
|
||||
.withGraphFetched({
|
||||
connection: true,
|
||||
flow: true,
|
||||
|
@@ -1,6 +1,7 @@
|
||||
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 = {
|
||||
@@ -14,8 +15,13 @@ const getDynamicFields = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
const step = await context.currentUser
|
||||
.$relatedQuery('steps')
|
||||
const conditions = context.currentUser.can('update', 'Flow');
|
||||
const userSteps = context.currentUser.$relatedQuery('steps');
|
||||
const allSteps = Step.query();
|
||||
const stepBaseQuery = conditions.isCreator ? userSteps : allSteps;
|
||||
|
||||
const step = await stepBaseQuery
|
||||
.clone()
|
||||
.withGraphFetched({
|
||||
connection: true,
|
||||
flow: true,
|
||||
@@ -26,7 +32,7 @@ const getDynamicFields = async (
|
||||
|
||||
const connection = step.connection;
|
||||
|
||||
if (!connection || !step.appKey) return null;
|
||||
if (!step.appKey) return null;
|
||||
|
||||
const app = await App.findOneByKey(step.appKey);
|
||||
const $ = await globalVariable({ connection, app, flow: step.flow, step });
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import Context from '../../types/express/context';
|
||||
import paginate from '../../helpers/pagination';
|
||||
import Execution from '../../models/execution';
|
||||
|
||||
type Params = {
|
||||
executionId: string;
|
||||
@@ -12,8 +13,13 @@ const getExecutionSteps = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
const execution = await context.currentUser
|
||||
.$relatedQuery('executions')
|
||||
const conditions = context.currentUser.can('read', 'Execution');
|
||||
const userExecutions = context.currentUser.$relatedQuery('executions');
|
||||
const allExecutions = Execution.query();
|
||||
const executionBaseQuery = conditions.isCreator ? userExecutions : allExecutions;
|
||||
|
||||
const execution = await executionBaseQuery
|
||||
.clone()
|
||||
.withSoftDeleted()
|
||||
.findById(params.executionId)
|
||||
.throwIfNotFound();
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import Context from '../../types/express/context';
|
||||
import Execution from '../../models/execution';
|
||||
|
||||
type Params = {
|
||||
executionId: string;
|
||||
@@ -9,8 +10,13 @@ const getExecution = async (
|
||||
params: Params,
|
||||
context: Context
|
||||
) => {
|
||||
const execution = await context.currentUser
|
||||
.$relatedQuery('executions')
|
||||
const conditions = context.currentUser.can('read', 'Execution');
|
||||
const userExecutions = context.currentUser.$relatedQuery('executions');
|
||||
const allExecutions = Execution.query();
|
||||
const executionBaseQuery = conditions.isCreator ? userExecutions : allExecutions;
|
||||
|
||||
const execution = await executionBaseQuery
|
||||
.clone()
|
||||
.withGraphFetched({
|
||||
flow: {
|
||||
steps: true,
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { raw } from 'objection';
|
||||
import Context from '../../types/express/context';
|
||||
import Execution from '../../models/execution';
|
||||
import paginate from '../../helpers/pagination';
|
||||
|
||||
type Params = {
|
||||
@@ -12,6 +13,12 @@ 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;
|
||||
|
||||
const selectStatusStatement = `
|
||||
case
|
||||
when count(*) filter (where execution_steps.status = 'failure') > 0
|
||||
@@ -21,8 +28,8 @@ const getExecutions = async (
|
||||
as status
|
||||
`;
|
||||
|
||||
const executions = context.currentUser
|
||||
.$relatedQuery('executions')
|
||||
const executions = executionBaseQuery
|
||||
.clone()
|
||||
.joinRelated('executionSteps as execution_steps')
|
||||
.select('executions.*', raw(selectStatusStatement))
|
||||
.withSoftDeleted()
|
||||
|
@@ -1,12 +1,18 @@
|
||||
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 flow = await context.currentUser
|
||||
.$relatedQuery('flows')
|
||||
const conditions = context.currentUser.can('read', 'Flow');
|
||||
const userFlows = context.currentUser.$relatedQuery('flows');
|
||||
const allFlows = Flow.query();
|
||||
const baseQuery = conditions.isCreator ? userFlows : allFlows;
|
||||
|
||||
const flow = await baseQuery
|
||||
.clone()
|
||||
.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
Reference in New Issue
Block a user