Compare commits
13 Commits
v0.8.0
...
add-loadin
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d3bc3a796b | ||
![]() |
9e64af4793 | ||
![]() |
b581f539e2 | ||
![]() |
aac1295c10 | ||
![]() |
e8f2802ee0 | ||
![]() |
75b3730a70 | ||
![]() |
af29dc9c3f | ||
![]() |
181cb5f335 | ||
![]() |
94e560c262 | ||
![]() |
f802061722 | ||
![]() |
58a7f6eec6 | ||
![]() |
5e11d3cc4d | ||
![]() |
399fb8312a |
@@ -33,7 +33,32 @@ services:
|
|||||||
- '6379:6379'
|
- '6379:6379'
|
||||||
expose:
|
expose:
|
||||||
- 6379
|
- 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:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
redis_data:
|
redis_data:
|
||||||
|
keycloak:
|
||||||
|
@@ -4,7 +4,7 @@ WORKDIR /automatisch
|
|||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apk --no-cache add --virtual build-dependencies python3 build-base && \
|
apk --no-cache add --virtual build-dependencies python3 build-base && \
|
||||||
yarn global add @automatisch/cli@0.8.0 --network-timeout 1000000 && \
|
yarn global add @automatisch/cli@0.7.1 --network-timeout 1000000 && \
|
||||||
rm -rf /usr/local/share/.cache/ && \
|
rm -rf /usr/local/share/.cache/ && \
|
||||||
apk del build-dependencies
|
apk del build-dependencies
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
FROM automatischio/automatisch:0.8.0
|
FROM automatischio/automatisch:0.7.1
|
||||||
WORKDIR /automatisch
|
WORKDIR /automatisch
|
||||||
|
|
||||||
RUN apk add --no-cache openssl dos2unix
|
RUN apk add --no-cache openssl dos2unix
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
"version": "0.8.0",
|
"version": "0.7.1",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"command": {
|
"command": {
|
||||||
|
@@ -2,18 +2,55 @@ import appConfig from '../../src/config/app';
|
|||||||
import logger from '../../src/helpers/logger';
|
import logger from '../../src/helpers/logger';
|
||||||
import client from './client';
|
import client from './client';
|
||||||
import User from '../../src/models/user';
|
import User from '../../src/models/user';
|
||||||
|
import Role from '../../src/models/role';
|
||||||
|
import Permission from '../../src/models/permission';
|
||||||
import '../../src/config/orm';
|
import '../../src/config/orm';
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
export async function createUser(
|
export async function createUser(
|
||||||
email = 'user@automatisch.io',
|
email = 'user@automatisch.io',
|
||||||
password = 'sample'
|
password = 'sample'
|
||||||
) {
|
) {
|
||||||
const UNIQUE_VIOLATION_CODE = '23505';
|
const UNIQUE_VIOLATION_CODE = '23505';
|
||||||
|
|
||||||
|
await seedPermissionsIfNeeded();
|
||||||
|
|
||||||
|
const role = await createOrFetchRole();
|
||||||
const userParams = {
|
const userParams = {
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
fullName: 'Initial admin',
|
fullName: 'Initial admin',
|
||||||
role: 'admin',
|
roleId: role.id,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@@ -12,6 +12,7 @@ const knexConfig = {
|
|||||||
database: appConfig.postgresDatabase,
|
database: appConfig.postgresDatabase,
|
||||||
ssl: appConfig.postgresEnableSsl,
|
ssl: appConfig.postgresEnableSsl,
|
||||||
},
|
},
|
||||||
|
asyncStackTraces: appConfig.isDev,
|
||||||
searchPath: [appConfig.postgresSchema],
|
searchPath: [appConfig.postgresSchema],
|
||||||
pool: { min: 0, max: 20 },
|
pool: { min: 0, max: 20 },
|
||||||
migrations: {
|
migrations: {
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/backend",
|
"name": "@automatisch/backend",
|
||||||
"version": "0.8.0",
|
"version": "0.7.1",
|
||||||
"license": "See LICENSE file",
|
"license": "See LICENSE file",
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"scripts": {
|
"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",
|
"worker": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/worker.ts",
|
||||||
"build": "tsc && yarn copy-statics",
|
"build": "tsc && yarn copy-statics",
|
||||||
"build:watch": "nodemon --watch 'src/**/*.ts' --watch 'bin/**/*.ts' --exec yarn build --ext ts",
|
"build:watch": "nodemon --watch 'src/**/*.ts' --watch 'bin/**/*.ts' --exec yarn build --ext ts",
|
||||||
@@ -22,14 +22,17 @@
|
|||||||
"prebuild": "rm -rf ./dist"
|
"prebuild": "rm -rf ./dist"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@automatisch/web": "^0.8.0",
|
"@automatisch/web": "^0.7.1",
|
||||||
"@bull-board/express": "^3.10.1",
|
"@bull-board/express": "^3.10.1",
|
||||||
|
"@casl/ability": "^6.5.0",
|
||||||
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
"@graphql-tools/graphql-file-loader": "^7.3.4",
|
||||||
"@graphql-tools/load": "^7.5.2",
|
"@graphql-tools/load": "^7.5.2",
|
||||||
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
"@rudderstack/rudder-sdk-node": "^1.1.2",
|
"@rudderstack/rudder-sdk-node": "^1.1.2",
|
||||||
"@sentry/node": "^7.42.0",
|
"@sentry/node": "^7.42.0",
|
||||||
"@sentry/tracing": "^7.42.0",
|
"@sentry/tracing": "^7.42.0",
|
||||||
"@types/luxon": "^2.3.1",
|
"@types/luxon": "^2.3.1",
|
||||||
|
"@types/passport": "^1.0.12",
|
||||||
"@types/xmlrpc": "^1.3.7",
|
"@types/xmlrpc": "^1.3.7",
|
||||||
"ajv-formats": "^2.1.1",
|
"ajv-formats": "^2.1.1",
|
||||||
"axios": "0.24.0",
|
"axios": "0.24.0",
|
||||||
@@ -50,8 +53,6 @@
|
|||||||
"graphql-type-json": "^0.3.2",
|
"graphql-type-json": "^0.3.2",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"http-errors": "~1.6.3",
|
"http-errors": "~1.6.3",
|
||||||
"http-proxy-agent": "^7.0.0",
|
|
||||||
"https-proxy-agent": "^7.0.1",
|
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
"knex": "^2.4.0",
|
"knex": "^2.4.0",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
@@ -62,6 +63,7 @@
|
|||||||
"nodemailer": "6.7.0",
|
"nodemailer": "6.7.0",
|
||||||
"oauth-1.0a": "^2.2.6",
|
"oauth-1.0a": "^2.2.6",
|
||||||
"objection": "^3.0.0",
|
"objection": "^3.0.0",
|
||||||
|
"passport": "^0.6.0",
|
||||||
"pg": "^8.7.1",
|
"pg": "^8.7.1",
|
||||||
"php-serialize": "^4.0.2",
|
"php-serialize": "^4.0.2",
|
||||||
"stripe": "^11.13.0",
|
"stripe": "^11.13.0",
|
||||||
@@ -104,7 +106,7 @@
|
|||||||
"url": "https://github.com/automatisch/automatisch/issues"
|
"url": "https://github.com/automatisch/automatisch/issues"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@automatisch/types": "^0.8.0",
|
"@automatisch/types": "^0.7.1",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/bull": "^3.15.8",
|
"@types/bull": "^3.15.8",
|
||||||
"@types/cors": "^2.8.12",
|
"@types/cors": "^2.8.12",
|
||||||
|
@@ -17,6 +17,7 @@ import {
|
|||||||
} from './helpers/create-bull-board-handler';
|
} from './helpers/create-bull-board-handler';
|
||||||
import injectBullBoardHandler from './helpers/inject-bull-board-handler';
|
import injectBullBoardHandler from './helpers/inject-bull-board-handler';
|
||||||
import router from './routes';
|
import router from './routes';
|
||||||
|
import configurePassport from './helpers/passport';
|
||||||
|
|
||||||
createBullBoardHandler(serverAdapter);
|
createBullBoardHandler(serverAdapter);
|
||||||
|
|
||||||
@@ -50,6 +51,9 @@ app.use(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
app.use(cors(corsOptions));
|
app.use(cors(corsOptions));
|
||||||
|
|
||||||
|
configurePassport(app);
|
||||||
|
|
||||||
app.use('/', router);
|
app.use('/', router);
|
||||||
|
|
||||||
webUIHandler(app);
|
webUIHandler(app);
|
||||||
|
@@ -6,7 +6,7 @@ import actions from './actions';
|
|||||||
import dynamicData from './dynamic-data';
|
import dynamicData from './dynamic-data';
|
||||||
|
|
||||||
export default defineApp({
|
export default defineApp({
|
||||||
name: 'GitHub',
|
name: 'Github',
|
||||||
key: 'github',
|
key: 'github',
|
||||||
baseUrl: 'https://github.com',
|
baseUrl: 'https://github.com',
|
||||||
apiBaseUrl: 'https://api.github.com',
|
apiBaseUrl: 'https://api.github.com',
|
||||||
|
@@ -9,11 +9,11 @@ export default {
|
|||||||
// ref:
|
// ref:
|
||||||
// - https://docs.gitlab.com/ee/api/projects.html#list-all-projects
|
// - https://docs.gitlab.com/ee/api/projects.html#list-all-projects
|
||||||
// - https://docs.gitlab.com/ee/api/rest/index.html#keyset-based-pagination
|
// - https://docs.gitlab.com/ee/api/rest/index.html#keyset-based-pagination
|
||||||
|
|
||||||
const firstPageRequest = $.http.get('/api/v4/projects', {
|
const firstPageRequest = $.http.get('/api/v4/projects', {
|
||||||
params: {
|
params: {
|
||||||
simple: true,
|
simple: true,
|
||||||
pagination: 'keyset',
|
pagination: 'keyset',
|
||||||
membership: true,
|
|
||||||
order_by: 'id',
|
order_by: 'id',
|
||||||
sort: 'asc',
|
sort: 'asc',
|
||||||
},
|
},
|
||||||
|
@@ -6,7 +6,7 @@ import triggers from './triggers';
|
|||||||
import dynamicData from './dynamic-data';
|
import dynamicData from './dynamic-data';
|
||||||
|
|
||||||
export default defineApp({
|
export default defineApp({
|
||||||
name: 'GitLab',
|
name: 'Gitlab',
|
||||||
key: 'gitlab',
|
key: 'gitlab',
|
||||||
baseUrl: 'https://gitlab.com',
|
baseUrl: 'https://gitlab.com',
|
||||||
apiBaseUrl: 'https://gitlab.com',
|
apiBaseUrl: 'https://gitlab.com',
|
||||||
|
@@ -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,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,5 +1,4 @@
|
|||||||
import createSpreadsheet from './create-spreadsheet';
|
import createSpreadsheet from './create-spreadsheet';
|
||||||
import createSpreadsheetRow from './create-spreadsheet-row';
|
import createSpreadsheetRow from './create-spreadsheet-row';
|
||||||
import createWorksheet from './create-worksheet';
|
|
||||||
|
|
||||||
export default [createSpreadsheet, createSpreadsheetRow, createWorksheet];
|
export default [createSpreadsheet, createSpreadsheetRow];
|
||||||
|
@@ -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,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
@@ -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,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
@@ -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],
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
@@ -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];
|
|
@@ -1,4 +1,3 @@
|
|||||||
import listDatabases from './list-databases';
|
import listDatabases from './list-databases';
|
||||||
import listParentPages from './list-parent-pages';
|
|
||||||
|
|
||||||
export default [listDatabases, listParentPages];
|
export default [listDatabases];
|
||||||
|
@@ -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;
|
|
||||||
},
|
|
||||||
};
|
|
@@ -3,7 +3,6 @@ import addAuthHeader from './common/add-auth-header';
|
|||||||
import addNotionVersionHeader from './common/add-notion-version-header';
|
import addNotionVersionHeader from './common/add-notion-version-header';
|
||||||
import auth from './auth';
|
import auth from './auth';
|
||||||
import triggers from './triggers';
|
import triggers from './triggers';
|
||||||
import actions from './actions';
|
|
||||||
import dynamicData from './dynamic-data';
|
import dynamicData from './dynamic-data';
|
||||||
|
|
||||||
export default defineApp({
|
export default defineApp({
|
||||||
@@ -15,9 +14,11 @@ export default defineApp({
|
|||||||
authDocUrl: 'https://automatisch.io/docs/apps/notion/connection',
|
authDocUrl: 'https://automatisch.io/docs/apps/notion/connection',
|
||||||
primaryColor: '000000',
|
primaryColor: '000000',
|
||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader, addNotionVersionHeader],
|
beforeRequest: [
|
||||||
|
addAuthHeader,
|
||||||
|
addNotionVersionHeader,
|
||||||
|
],
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
triggers,
|
||||||
actions,
|
|
||||||
dynamicData,
|
dynamicData,
|
||||||
});
|
});
|
||||||
|
@@ -0,0 +1,32 @@
|
|||||||
|
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');
|
||||||
|
|
||||||
|
for (const { role } of uniqueUserRoles) {
|
||||||
|
// skip empty roles
|
||||||
|
if (!role) continue;
|
||||||
|
|
||||||
|
await knex('roles').insert({
|
||||||
|
name: capitalize(role),
|
||||||
|
key: lowerCase(role),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
return knex.schema.dropTable('roles');
|
||||||
|
}
|
@@ -0,0 +1,25 @@
|
|||||||
|
import { Knex } from 'knex';
|
||||||
|
|
||||||
|
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.string('action').notNullable();
|
||||||
|
table.string('subject').notNullable();
|
||||||
|
|
||||||
|
table.timestamps(true, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
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> {
|
||||||
|
return knex.schema.dropTable('permissions');
|
||||||
|
}
|
@@ -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');
|
||||||
|
}
|
@@ -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,23 @@
|
|||||||
|
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.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,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');
|
||||||
|
});
|
||||||
|
}
|
@@ -1,47 +1,59 @@
|
|||||||
import createConnection from './mutations/create-connection';
|
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 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 updateConnection from './mutations/update-connection';
|
||||||
|
import updateCurrentUser from './mutations/update-current-user';
|
||||||
import updateFlow from './mutations/update-flow';
|
import updateFlow from './mutations/update-flow';
|
||||||
import updateFlowStatus from './mutations/update-flow-status';
|
import updateFlowStatus from './mutations/update-flow-status';
|
||||||
import executeFlow from './mutations/execute-flow';
|
import updateRole from './mutations/update-role.ee';
|
||||||
import deleteFlow from './mutations/delete-flow';
|
|
||||||
import duplicateFlow from './mutations/duplicate-flow';
|
|
||||||
import createStep from './mutations/create-step';
|
|
||||||
import updateStep from './mutations/update-step';
|
import updateStep from './mutations/update-step';
|
||||||
import deleteStep from './mutations/delete-step';
|
import updateUser from './mutations/update-user.ee';
|
||||||
import createUser from './mutations/create-user.ee';
|
import verifyConnection from './mutations/verify-connection';
|
||||||
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';
|
|
||||||
|
|
||||||
const mutationResolvers = {
|
const mutationResolvers = {
|
||||||
createConnection,
|
createConnection,
|
||||||
generateAuthUrl,
|
|
||||||
updateConnection,
|
|
||||||
resetConnection,
|
|
||||||
verifyConnection,
|
|
||||||
deleteConnection,
|
|
||||||
createFlow,
|
createFlow,
|
||||||
|
createRole,
|
||||||
|
createStep,
|
||||||
|
createUser,
|
||||||
|
deleteConnection,
|
||||||
|
deleteCurrentUser,
|
||||||
|
deleteFlow,
|
||||||
|
deleteRole,
|
||||||
|
deleteStep,
|
||||||
|
deleteUser,
|
||||||
|
duplicateFlow,
|
||||||
|
executeFlow,
|
||||||
|
forgotPassword,
|
||||||
|
generateAuthUrl,
|
||||||
|
login,
|
||||||
|
registerUser,
|
||||||
|
resetConnection,
|
||||||
|
resetPassword,
|
||||||
|
updateConnection,
|
||||||
|
updateCurrentUser,
|
||||||
|
updateUser,
|
||||||
updateFlow,
|
updateFlow,
|
||||||
updateFlowStatus,
|
updateFlowStatus,
|
||||||
executeFlow,
|
updateRole,
|
||||||
deleteFlow,
|
|
||||||
duplicateFlow,
|
|
||||||
createStep,
|
|
||||||
updateStep,
|
updateStep,
|
||||||
deleteStep,
|
verifyConnection,
|
||||||
createUser,
|
|
||||||
deleteUser,
|
|
||||||
updateUser,
|
|
||||||
forgotPassword,
|
|
||||||
resetPassword,
|
|
||||||
login,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default mutationResolvers;
|
export default mutationResolvers;
|
||||||
|
@@ -13,6 +13,8 @@ const createConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('create', 'Connection');
|
||||||
|
|
||||||
await App.findOneByKey(params.input.key);
|
await App.findOneByKey(params.input.key);
|
||||||
|
|
||||||
return await context.currentUser.$relatedQuery('connections').insert({
|
return await context.currentUser.$relatedQuery('connections').insert({
|
||||||
|
@@ -14,6 +14,8 @@ const createFlow = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('create', 'Flow');
|
||||||
|
|
||||||
const connectionId = params?.input?.connectionId;
|
const connectionId = params?.input?.connectionId;
|
||||||
const appKey = params?.input?.triggerAppKey;
|
const appKey = params?.input?.triggerAppKey;
|
||||||
|
|
||||||
|
32
packages/backend/src/graphql/mutations/create-role.ee.ts
Normal file
32
packages/backend/src/graphql/mutations/create-role.ee.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import kebabCase from 'lodash/kebabCase';
|
||||||
|
import Role from '../../models/role';
|
||||||
|
import Permission from '../../models/permission';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
input: {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
permissions: Permission[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
|
const createRole = async (_parent: unknown, params: Params) => {
|
||||||
|
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,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('update', 'Flow');
|
||||||
|
|
||||||
const { input } = params;
|
const { input } = params;
|
||||||
|
|
||||||
if (input.appKey && input.key) {
|
if (input.appKey && input.key) {
|
||||||
|
@@ -5,11 +5,13 @@ type Params = {
|
|||||||
fullName: string;
|
fullName: string;
|
||||||
email: string;
|
email: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
roleId: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
const createUser = async (_parent: unknown, params: Params) => {
|
const createUser = async (_parent: unknown, params: Params) => {
|
||||||
const { fullName, email, password } = params.input;
|
const { fullName, email, password, roleId } = params.input;
|
||||||
|
|
||||||
const existingUser = await User.query().findOne({ email });
|
const existingUser = await User.query().findOne({ email });
|
||||||
|
|
||||||
@@ -21,7 +23,7 @@ const createUser = async (_parent: unknown, params: Params) => {
|
|||||||
fullName,
|
fullName,
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
role: 'user',
|
roleId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
|
@@ -11,6 +11,8 @@ const deleteConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('delete', 'Connection');
|
||||||
|
|
||||||
await context.currentUser
|
await context.currentUser
|
||||||
.$relatedQuery('connections')
|
.$relatedQuery('connections')
|
||||||
.delete()
|
.delete()
|
||||||
|
@@ -0,0 +1,23 @@
|
|||||||
|
import { Duration } from 'luxon';
|
||||||
|
import Context from '../../types/express/context';
|
||||||
|
import deleteUserQueue from '../../queues/delete-user.ee';
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
|
const deleteUser = 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 deleteUser;
|
@@ -13,6 +13,8 @@ const deleteFlow = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('delete', 'Flow');
|
||||||
|
|
||||||
const flow = await context.currentUser
|
const flow = await context.currentUser
|
||||||
.$relatedQuery('flows')
|
.$relatedQuery('flows')
|
||||||
.findOne({
|
.findOne({
|
||||||
|
31
packages/backend/src/graphql/mutations/delete-role.ee.ts
Normal file
31
packages/backend/src/graphql/mutations/delete-role.ee.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import Role from '../../models/role';
|
||||||
|
import Context from '../../types/express/context';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
input: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
|
const deleteRole = async (
|
||||||
|
_parent: unknown,
|
||||||
|
params: Params,
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
|
const role = await Role.query().findById(params.input.id).throwIfNotFound();
|
||||||
|
|
||||||
|
if (role.isAdmin) {
|
||||||
|
throw new Error('Admin role cannot be deleted!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default deleteRole;
|
@@ -11,6 +11,8 @@ const deleteStep = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('update', 'Flow');
|
||||||
|
|
||||||
const step = await context.currentUser
|
const step = await context.currentUser
|
||||||
.$relatedQuery('steps')
|
.$relatedQuery('steps')
|
||||||
.withGraphFetched('flow')
|
.withGraphFetched('flow')
|
||||||
|
@@ -1,11 +1,23 @@
|
|||||||
import Context from '../../types/express/context';
|
|
||||||
import deleteUserQueue from '../../queues/delete-user.ee';
|
|
||||||
import { Duration } from 'luxon';
|
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) => {
|
type Params = {
|
||||||
const id = context.currentUser.id;
|
input: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
await context.currentUser.$query().delete();
|
// TODO: access
|
||||||
|
const deleteUser = async (
|
||||||
|
_parent: unknown,
|
||||||
|
params: Params,
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
|
const id = params.input.id;
|
||||||
|
|
||||||
|
await User.query().deleteById(id);
|
||||||
|
|
||||||
const jobName = `Delete user - ${id}`;
|
const jobName = `Delete user - ${id}`;
|
||||||
const jobPayload = { id };
|
const jobPayload = { id };
|
||||||
|
@@ -53,6 +53,8 @@ const duplicateFlow = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('create', 'Flow');
|
||||||
|
|
||||||
const flow = await context.currentUser
|
const flow = await context.currentUser
|
||||||
.$relatedQuery('flows')
|
.$relatedQuery('flows')
|
||||||
.withGraphJoined('[steps]')
|
.withGraphJoined('[steps]')
|
||||||
|
@@ -12,6 +12,8 @@ const executeFlow = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('update', 'Flow');
|
||||||
|
|
||||||
const { stepId } = params.input;
|
const { stepId } = params.input;
|
||||||
|
|
||||||
const untilStep = await context.currentUser
|
const untilStep = await context.currentUser
|
||||||
|
@@ -13,6 +13,8 @@ const generateAuthUrl = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('create', 'Connection');
|
||||||
|
|
||||||
const connection = await context.currentUser
|
const connection = await context.currentUser
|
||||||
.$relatedQuery('connections')
|
.$relatedQuery('connections')
|
||||||
.findOne({
|
.findOne({
|
||||||
|
@@ -13,7 +13,7 @@ const TOKEN_EXPIRES_IN = '14d';
|
|||||||
|
|
||||||
const login = async (_parent: unknown, params: Params) => {
|
const login = async (_parent: unknown, params: Params) => {
|
||||||
const user = await User.query().findOne({
|
const user = await User.query().findOne({
|
||||||
email: params.input.email.toLowerCase(),
|
email: params.input.email,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user && (await user.login(params.input.password))) {
|
if (user && (await user.login(params.input.password))) {
|
||||||
|
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,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('create', 'Connection');
|
||||||
|
|
||||||
let connection = await context.currentUser
|
let connection = await context.currentUser
|
||||||
.$relatedQuery('connections')
|
.$relatedQuery('connections')
|
||||||
.findOne({
|
.findOne({
|
||||||
|
@@ -13,6 +13,8 @@ const updateConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('create', 'Connection');
|
||||||
|
|
||||||
let connection = await context.currentUser
|
let connection = await context.currentUser
|
||||||
.$relatedQuery('connections')
|
.$relatedQuery('connections')
|
||||||
.findOne({
|
.findOne({
|
||||||
|
@@ -8,7 +8,7 @@ type Params = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateUser = async (
|
const updateCurrentUser = async (
|
||||||
_parent: unknown,
|
_parent: unknown,
|
||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
@@ -22,4 +22,4 @@ const updateUser = async (
|
|||||||
return user;
|
return user;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default updateUser;
|
export default updateCurrentUser;
|
@@ -18,6 +18,8 @@ const updateFlowStatus = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('publish', 'Flow');
|
||||||
|
|
||||||
let flow = await context.currentUser
|
let flow = await context.currentUser
|
||||||
.$relatedQuery('flows')
|
.$relatedQuery('flows')
|
||||||
.findOne({
|
.findOne({
|
||||||
@@ -55,7 +57,7 @@ const updateFlowStatus = async (
|
|||||||
} else {
|
} else {
|
||||||
if (newActiveValue) {
|
if (newActiveValue) {
|
||||||
flow = await flow.$query().patchAndFetch({
|
flow = await flow.$query().patchAndFetch({
|
||||||
published_at: new Date().toISOString(),
|
publishedAt: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const jobName = `${JOB_NAME}-${flow.id}`;
|
const jobName = `${JOB_NAME}-${flow.id}`;
|
||||||
@@ -78,9 +80,12 @@ const updateFlowStatus = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flow = await flow.$query().withGraphFetched('steps').patchAndFetch({
|
flow = await flow
|
||||||
active: newActiveValue,
|
.$query()
|
||||||
});
|
.withGraphFetched('steps')
|
||||||
|
.patchAndFetch({
|
||||||
|
active: newActiveValue,
|
||||||
|
});
|
||||||
|
|
||||||
return flow;
|
return flow;
|
||||||
};
|
};
|
||||||
|
@@ -12,6 +12,8 @@ const updateFlow = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('update', 'Flow');
|
||||||
|
|
||||||
let flow = await context.currentUser
|
let flow = await context.currentUser
|
||||||
.$relatedQuery('flows')
|
.$relatedQuery('flows')
|
||||||
.findOne({
|
.findOne({
|
||||||
|
44
packages/backend/src/graphql/mutations/update-role.ee.ts
Normal file
44
packages/backend/src/graphql/mutations/update-role.ee.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import Context from '../../types/express/context';
|
||||||
|
import Role from '../../models/role';
|
||||||
|
import Permission from '../../models/permission';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
input: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
permissions: Permission[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
|
const updateUser = async (
|
||||||
|
_parent: unknown,
|
||||||
|
params: Params,
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
permissions,
|
||||||
|
} = params.input;
|
||||||
|
|
||||||
|
const role = await Role.query().findById(id).throwIfNotFound();
|
||||||
|
|
||||||
|
// TODO: delete the unrelated items!
|
||||||
|
await role.$relatedQuery('permissions').unrelate();
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default updateUser;
|
@@ -23,6 +23,8 @@ const updateStep = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('update', 'Flow');
|
||||||
|
|
||||||
const { input } = params;
|
const { input } = params;
|
||||||
|
|
||||||
let step = await context.currentUser
|
let step = await context.currentUser
|
||||||
|
34
packages/backend/src/graphql/mutations/update-user.ee.ts
Normal file
34
packages/backend/src/graphql/mutations/update-user.ee.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import Context from '../../types/express/context';
|
||||||
|
import User from '../../models/user';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
input: {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
fullName: string;
|
||||||
|
role: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
|
const updateUser = async (
|
||||||
|
_parent: unknown,
|
||||||
|
params: Params,
|
||||||
|
context: Context
|
||||||
|
) => {
|
||||||
|
const user = await User.query()
|
||||||
|
.patchAndFetchById(
|
||||||
|
params.input.id,
|
||||||
|
{
|
||||||
|
email: params.input.email,
|
||||||
|
fullName: params.input.fullName,
|
||||||
|
roleId: params.input.role.id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default updateUser;
|
@@ -13,6 +13,8 @@ const verifyConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('create', 'Connection');
|
||||||
|
|
||||||
let connection = await context.currentUser
|
let connection = await context.currentUser
|
||||||
.$relatedQuery('connections')
|
.$relatedQuery('connections')
|
||||||
.findOne({
|
.findOne({
|
||||||
|
@@ -6,6 +6,8 @@ type Params = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getApp = async (_parent: unknown, params: Params, context: Context) => {
|
const getApp = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
|
context.currentUser.can('read', 'Connection');
|
||||||
|
|
||||||
const app = await App.findOneByKey(params.key);
|
const app = await App.findOneByKey(params.key);
|
||||||
|
|
||||||
if (context.currentUser) {
|
if (context.currentUser) {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import App from '../../models/app';
|
|
||||||
import { IApp } from '@automatisch/types';
|
import { IApp } from '@automatisch/types';
|
||||||
|
import App from '../../models/app';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
name: string;
|
name: string;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
|
import { IConnection } from '@automatisch/types';
|
||||||
import App from '../../models/app';
|
import App from '../../models/app';
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import { IApp, IConnection } from '@automatisch/types';
|
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -11,6 +11,8 @@ const getConnectedApps = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('read', 'Connection');
|
||||||
|
|
||||||
let apps = await App.findAll(params.name);
|
let apps = await App.findAll(params.name);
|
||||||
|
|
||||||
const connections = await context.currentUser
|
const connections = await context.currentUser
|
||||||
|
@@ -16,6 +16,8 @@ const getDynamicData = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('update', 'Flow');
|
||||||
|
|
||||||
const step = await context.currentUser
|
const step = await context.currentUser
|
||||||
.$relatedQuery('steps')
|
.$relatedQuery('steps')
|
||||||
.withGraphFetched({
|
.withGraphFetched({
|
||||||
|
@@ -14,6 +14,8 @@ const getDynamicFields = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('update', 'Flow');
|
||||||
|
|
||||||
const step = await context.currentUser
|
const step = await context.currentUser
|
||||||
.$relatedQuery('steps')
|
.$relatedQuery('steps')
|
||||||
.withGraphFetched({
|
.withGraphFetched({
|
||||||
|
@@ -12,6 +12,8 @@ const getExecutionSteps = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('read', 'Execution');
|
||||||
|
|
||||||
const execution = await context.currentUser
|
const execution = await context.currentUser
|
||||||
.$relatedQuery('executions')
|
.$relatedQuery('executions')
|
||||||
.withSoftDeleted()
|
.withSoftDeleted()
|
||||||
|
@@ -9,6 +9,8 @@ const getExecution = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('read', 'Execution');
|
||||||
|
|
||||||
const execution = await context.currentUser
|
const execution = await context.currentUser
|
||||||
.$relatedQuery('executions')
|
.$relatedQuery('executions')
|
||||||
.withGraphFetched({
|
.withGraphFetched({
|
||||||
|
@@ -12,6 +12,8 @@ const getExecutions = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('read', 'Execution');
|
||||||
|
|
||||||
const selectStatusStatement = `
|
const selectStatusStatement = `
|
||||||
case
|
case
|
||||||
when count(*) filter (where execution_steps.status = 'failure') > 0
|
when count(*) filter (where execution_steps.status = 'failure') > 0
|
||||||
|
@@ -5,6 +5,8 @@ type Params = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getFlow = async (_parent: unknown, params: Params, context: Context) => {
|
const getFlow = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
|
context.currentUser.can('read', 'Flow');
|
||||||
|
|
||||||
const flow = await context.currentUser
|
const flow = await context.currentUser
|
||||||
.$relatedQuery('flows')
|
.$relatedQuery('flows')
|
||||||
.withGraphJoined('[steps.[connection]]')
|
.withGraphJoined('[steps.[connection]]')
|
||||||
|
@@ -10,6 +10,8 @@ type Params = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getFlows = async (_parent: unknown, params: Params, context: Context) => {
|
const getFlows = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
|
context.currentUser.can('read', 'Flow');
|
||||||
|
|
||||||
const flowsQuery = context.currentUser
|
const flowsQuery = context.currentUser
|
||||||
.$relatedQuery('flows')
|
.$relatedQuery('flows')
|
||||||
.joinRelated({
|
.joinRelated({
|
||||||
|
76
packages/backend/src/graphql/queries/get-permissions.ee.ts
Normal file
76
packages/backend/src/graphql/queries/get-permissions.ee.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
const getPermissions = async () => {
|
||||||
|
const Connection = {
|
||||||
|
label: 'Connection',
|
||||||
|
key: 'Connection',
|
||||||
|
};
|
||||||
|
|
||||||
|
const Flow = {
|
||||||
|
label: 'Flow',
|
||||||
|
key: 'Flow',
|
||||||
|
};
|
||||||
|
|
||||||
|
const Execution = {
|
||||||
|
label: 'Execution',
|
||||||
|
key: 'Execution',
|
||||||
|
};
|
||||||
|
|
||||||
|
const permissions = {
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
key: 'isCreator',
|
||||||
|
label: 'Is creator'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
label: 'Create',
|
||||||
|
action: 'create',
|
||||||
|
subjects: [
|
||||||
|
Connection.key,
|
||||||
|
Flow.key,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Read',
|
||||||
|
action: 'read',
|
||||||
|
subjects: [
|
||||||
|
Connection.key,
|
||||||
|
Execution.key,
|
||||||
|
Flow.key,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Update',
|
||||||
|
action: 'update',
|
||||||
|
subjects: [
|
||||||
|
Connection.key,
|
||||||
|
Flow.key,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Delete',
|
||||||
|
action: 'delete',
|
||||||
|
subjects: [
|
||||||
|
Connection.key,
|
||||||
|
Flow.key,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Publish',
|
||||||
|
action: 'publish',
|
||||||
|
subjects: [
|
||||||
|
Flow.key,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
subjects: [
|
||||||
|
Connection,
|
||||||
|
Flow,
|
||||||
|
Execution
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
return permissions;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getPermissions;
|
13
packages/backend/src/graphql/queries/get-role.ee.ts
Normal file
13
packages/backend/src/graphql/queries/get-role.ee.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import Context from '../../types/express/context';
|
||||||
|
import Role from '../../models/role';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
|
const getRole = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
|
return await Role.query().findById(params.id).throwIfNotFound();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getRole;
|
9
packages/backend/src/graphql/queries/get-roles.ee.ts
Normal file
9
packages/backend/src/graphql/queries/get-roles.ee.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import Context from '../../types/express/context';
|
||||||
|
import Role from '../../models/role';
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
|
const getRoles = async (_parent: unknown, params: unknown, context: Context) => {
|
||||||
|
return await Role.query();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getRoles;
|
@@ -0,0 +1,9 @@
|
|||||||
|
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
|
||||||
|
|
||||||
|
const getSamlAuthProviders = async () => {
|
||||||
|
const providers = await SamlAuthProvider.query();
|
||||||
|
|
||||||
|
return providers;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getSamlAuthProviders;
|
@@ -11,6 +11,8 @@ const getStepWithTestExecutions = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('update', 'Flow');
|
||||||
|
|
||||||
const step = await context.currentUser
|
const step = await context.currentUser
|
||||||
.$relatedQuery('steps')
|
.$relatedQuery('steps')
|
||||||
.findOne({ 'steps.id': params.stepId })
|
.findOne({ 'steps.id': params.stepId })
|
||||||
|
22
packages/backend/src/graphql/queries/get-user.ts
Normal file
22
packages/backend/src/graphql/queries/get-user.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import Context from '../../types/express/context';
|
||||||
|
import User from '../../models/user';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
id: string
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
|
const getUser = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
|
return await User
|
||||||
|
.query()
|
||||||
|
.leftJoinRelated({
|
||||||
|
role: true
|
||||||
|
})
|
||||||
|
.withGraphFetched({
|
||||||
|
role: true
|
||||||
|
})
|
||||||
|
.findById(params.id)
|
||||||
|
.throwIfNotFound();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getUser;
|
25
packages/backend/src/graphql/queries/get-users.ts
Normal file
25
packages/backend/src/graphql/queries/get-users.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import Context from '../../types/express/context';
|
||||||
|
import paginate from '../../helpers/pagination';
|
||||||
|
import User from '../../models/user';
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
limit: number;
|
||||||
|
offset: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: access
|
||||||
|
const getUsers = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
|
const usersQuery = User
|
||||||
|
.query()
|
||||||
|
.leftJoinRelated({
|
||||||
|
role: true
|
||||||
|
})
|
||||||
|
.withGraphFetched({
|
||||||
|
role: true
|
||||||
|
})
|
||||||
|
.orderBy('full_name', 'desc');
|
||||||
|
|
||||||
|
return paginate(usersQuery, params.limit, params.offset);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getUsers;
|
@@ -12,6 +12,8 @@ const testConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
|
context.currentUser.can('update', 'Connection');
|
||||||
|
|
||||||
let connection = await context.currentUser
|
let connection = await context.currentUser
|
||||||
.$relatedQuery('connections')
|
.$relatedQuery('connections')
|
||||||
.findOne({
|
.findOne({
|
||||||
|
@@ -1,47 +1,59 @@
|
|||||||
import getApps from './queries/get-apps';
|
|
||||||
import getApp from './queries/get-app';
|
import getApp from './queries/get-app';
|
||||||
|
import getApps from './queries/get-apps';
|
||||||
|
import getAutomatischInfo from './queries/get-automatisch-info';
|
||||||
|
import getBillingAndUsage from './queries/get-billing-and-usage.ee';
|
||||||
import getConnectedApps from './queries/get-connected-apps';
|
import getConnectedApps from './queries/get-connected-apps';
|
||||||
import testConnection from './queries/test-connection';
|
import getCurrentUser from './queries/get-current-user';
|
||||||
import getFlow from './queries/get-flow';
|
|
||||||
import getFlows from './queries/get-flows';
|
|
||||||
import getStepWithTestExecutions from './queries/get-step-with-test-executions';
|
|
||||||
import getExecution from './queries/get-execution';
|
|
||||||
import getExecutions from './queries/get-executions';
|
|
||||||
import getExecutionSteps from './queries/get-execution-steps';
|
|
||||||
import getDynamicData from './queries/get-dynamic-data';
|
import getDynamicData from './queries/get-dynamic-data';
|
||||||
import getDynamicFields from './queries/get-dynamic-fields';
|
import getDynamicFields from './queries/get-dynamic-fields';
|
||||||
import getCurrentUser from './queries/get-current-user';
|
import getExecution from './queries/get-execution';
|
||||||
import getPaymentPlans from './queries/get-payment-plans.ee';
|
import getExecutionSteps from './queries/get-execution-steps';
|
||||||
import getPaddleInfo from './queries/get-paddle-info.ee';
|
import getExecutions from './queries/get-executions';
|
||||||
import getBillingAndUsage from './queries/get-billing-and-usage.ee';
|
import getFlow from './queries/get-flow';
|
||||||
|
import getFlows from './queries/get-flows';
|
||||||
|
import getUser from './queries/get-user';
|
||||||
|
import getUsers from './queries/get-users';
|
||||||
import getInvoices from './queries/get-invoices.ee';
|
import getInvoices from './queries/get-invoices.ee';
|
||||||
import getAutomatischInfo from './queries/get-automatisch-info';
|
import getPaddleInfo from './queries/get-paddle-info.ee';
|
||||||
import getTrialStatus from './queries/get-trial-status.ee';
|
import getPaymentPlans from './queries/get-payment-plans.ee';
|
||||||
|
import getPermissions from './queries/get-permissions.ee';
|
||||||
|
import getRole from './queries/get-role.ee';
|
||||||
|
import getRoles from './queries/get-roles.ee';
|
||||||
|
import getSamlAuthProviders from './queries/get-saml-auth-providers.ee';
|
||||||
|
import getStepWithTestExecutions from './queries/get-step-with-test-executions';
|
||||||
import getSubscriptionStatus from './queries/get-subscription-status.ee';
|
import getSubscriptionStatus from './queries/get-subscription-status.ee';
|
||||||
|
import getTrialStatus from './queries/get-trial-status.ee';
|
||||||
import healthcheck from './queries/healthcheck';
|
import healthcheck from './queries/healthcheck';
|
||||||
|
import testConnection from './queries/test-connection';
|
||||||
|
|
||||||
const queryResolvers = {
|
const queryResolvers = {
|
||||||
getApps,
|
|
||||||
getApp,
|
getApp,
|
||||||
|
getApps,
|
||||||
|
getAutomatischInfo,
|
||||||
|
getBillingAndUsage,
|
||||||
getConnectedApps,
|
getConnectedApps,
|
||||||
testConnection,
|
getCurrentUser,
|
||||||
getFlow,
|
getDynamicData,
|
||||||
getFlows,
|
getDynamicFields,
|
||||||
getStepWithTestExecutions,
|
|
||||||
getExecution,
|
getExecution,
|
||||||
getExecutions,
|
getExecutions,
|
||||||
getExecutionSteps,
|
getExecutionSteps,
|
||||||
getDynamicData,
|
getFlow,
|
||||||
getDynamicFields,
|
getFlows,
|
||||||
getCurrentUser,
|
|
||||||
getPaymentPlans,
|
|
||||||
getPaddleInfo,
|
|
||||||
getBillingAndUsage,
|
|
||||||
getInvoices,
|
getInvoices,
|
||||||
getAutomatischInfo,
|
getPaddleInfo,
|
||||||
getTrialStatus,
|
getPaymentPlans,
|
||||||
|
getPermissions,
|
||||||
|
getRole,
|
||||||
|
getRoles,
|
||||||
|
getSamlAuthProviders,
|
||||||
|
getStepWithTestExecutions,
|
||||||
getSubscriptionStatus,
|
getSubscriptionStatus,
|
||||||
|
getTrialStatus,
|
||||||
|
getUser,
|
||||||
|
getUsers,
|
||||||
healthcheck,
|
healthcheck,
|
||||||
|
testConnection,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default queryResolvers;
|
export default queryResolvers;
|
||||||
|
@@ -41,31 +41,46 @@ type Query {
|
|||||||
getAutomatischInfo: GetAutomatischInfo
|
getAutomatischInfo: GetAutomatischInfo
|
||||||
getTrialStatus: GetTrialStatus
|
getTrialStatus: GetTrialStatus
|
||||||
getSubscriptionStatus: GetSubscriptionStatus
|
getSubscriptionStatus: GetSubscriptionStatus
|
||||||
|
getSamlAuthProviders: [GetSamlAuthProviders]
|
||||||
|
getUsers(
|
||||||
|
limit: Int!
|
||||||
|
offset: Int!
|
||||||
|
): UserConnection
|
||||||
|
getUser(id: String!): User
|
||||||
|
getRoles: [Role]
|
||||||
|
getRole(id: String!): Role
|
||||||
|
getPermissions: Permissions
|
||||||
healthcheck: AppHealth
|
healthcheck: AppHealth
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
createConnection(input: CreateConnectionInput): Connection
|
createConnection(input: CreateConnectionInput): Connection
|
||||||
generateAuthUrl(input: GenerateAuthUrlInput): AuthLink
|
|
||||||
updateConnection(input: UpdateConnectionInput): Connection
|
|
||||||
resetConnection(input: ResetConnectionInput): Connection
|
|
||||||
verifyConnection(input: VerifyConnectionInput): Connection
|
|
||||||
deleteConnection(input: DeleteConnectionInput): Boolean
|
|
||||||
createFlow(input: CreateFlowInput): Flow
|
createFlow(input: CreateFlowInput): Flow
|
||||||
|
createRole(input: CreateRoleInput): Role
|
||||||
|
createStep(input: CreateStepInput): Step
|
||||||
|
createUser(input: CreateUserInput): User
|
||||||
|
deleteConnection(input: DeleteConnectionInput): Boolean
|
||||||
|
deleteCurrentUser: Boolean
|
||||||
|
deleteFlow(input: DeleteFlowInput): Boolean
|
||||||
|
deleteRole(input: DeleteRoleInput): Boolean
|
||||||
|
deleteStep(input: DeleteStepInput): Step
|
||||||
|
deleteUser(input: DeleteUserInput): Boolean
|
||||||
|
duplicateFlow(input: DuplicateFlowInput): Flow
|
||||||
|
executeFlow(input: ExecuteFlowInput): executeFlowType
|
||||||
|
forgotPassword(input: ForgotPasswordInput): Boolean
|
||||||
|
generateAuthUrl(input: GenerateAuthUrlInput): AuthLink
|
||||||
|
login(input: LoginInput): Auth
|
||||||
|
registerUser(input: RegisterUserInput): User
|
||||||
|
resetConnection(input: ResetConnectionInput): Connection
|
||||||
|
resetPassword(input: ResetPasswordInput): Boolean
|
||||||
|
updateConnection(input: UpdateConnectionInput): Connection
|
||||||
|
updateCurrentUser(input: UpdateCurrentUserInput): User
|
||||||
updateFlow(input: UpdateFlowInput): Flow
|
updateFlow(input: UpdateFlowInput): Flow
|
||||||
updateFlowStatus(input: UpdateFlowStatusInput): Flow
|
updateFlowStatus(input: UpdateFlowStatusInput): Flow
|
||||||
executeFlow(input: ExecuteFlowInput): executeFlowType
|
updateRole(input: UpdateRoleInput): Role
|
||||||
deleteFlow(input: DeleteFlowInput): Boolean
|
|
||||||
duplicateFlow(input: DuplicateFlowInput): Flow
|
|
||||||
createStep(input: CreateStepInput): Step
|
|
||||||
updateStep(input: UpdateStepInput): Step
|
updateStep(input: UpdateStepInput): Step
|
||||||
deleteStep(input: DeleteStepInput): Step
|
|
||||||
createUser(input: CreateUserInput): User
|
|
||||||
deleteUser: Boolean
|
|
||||||
updateUser(input: UpdateUserInput): User
|
updateUser(input: UpdateUserInput): User
|
||||||
forgotPassword(input: ForgotPasswordInput): Boolean
|
verifyConnection(input: VerifyConnectionInput): Connection
|
||||||
resetPassword(input: ResetPasswordInput): Boolean
|
|
||||||
login(input: LoginInput): Auth
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -277,6 +292,15 @@ type Execution {
|
|||||||
flow: Flow
|
flow: Flow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserConnection {
|
||||||
|
edges: [UserEdge]
|
||||||
|
pageInfo: PageInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserEdge {
|
||||||
|
node: User
|
||||||
|
}
|
||||||
|
|
||||||
input CreateConnectionInput {
|
input CreateConnectionInput {
|
||||||
key: String!
|
key: String!
|
||||||
formattedData: JSONObject!
|
formattedData: JSONObject!
|
||||||
@@ -360,9 +384,31 @@ input CreateUserInput {
|
|||||||
fullName: String!
|
fullName: String!
|
||||||
email: String!
|
email: String!
|
||||||
password: String!
|
password: String!
|
||||||
|
role: UserRoleInput!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UserRoleInput {
|
||||||
|
id: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input UpdateUserInput {
|
input UpdateUserInput {
|
||||||
|
id: String!
|
||||||
|
fullName: String
|
||||||
|
email: String
|
||||||
|
role: UserRoleInput
|
||||||
|
}
|
||||||
|
|
||||||
|
input DeleteUserInput {
|
||||||
|
id: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input RegisterUserInput {
|
||||||
|
fullName: String!
|
||||||
|
email: String!
|
||||||
|
password: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateCurrentUserInput {
|
||||||
email: String
|
email: String
|
||||||
password: String
|
password: String
|
||||||
fullName: String
|
fullName: String
|
||||||
@@ -382,6 +428,29 @@ input LoginInput {
|
|||||||
password: String!
|
password: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input PermissionInput {
|
||||||
|
action: String!
|
||||||
|
subject: String!
|
||||||
|
conditions: [String]
|
||||||
|
}
|
||||||
|
|
||||||
|
input CreateRoleInput {
|
||||||
|
name: String!
|
||||||
|
description: String
|
||||||
|
permissions: [PermissionInput]
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateRoleInput {
|
||||||
|
id: String!
|
||||||
|
name: String!
|
||||||
|
description: String
|
||||||
|
permissions: [PermissionInput]
|
||||||
|
}
|
||||||
|
|
||||||
|
input DeleteRoleInput {
|
||||||
|
id: String!
|
||||||
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
|
The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
|
||||||
"""
|
"""
|
||||||
@@ -453,11 +522,21 @@ type User {
|
|||||||
id: String
|
id: String
|
||||||
fullName: String
|
fullName: String
|
||||||
email: String
|
email: String
|
||||||
role: String
|
role: Role
|
||||||
|
permissions: [Permission]
|
||||||
createdAt: String
|
createdAt: String
|
||||||
updatedAt: String
|
updatedAt: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Role {
|
||||||
|
id: String
|
||||||
|
name: String
|
||||||
|
key: String
|
||||||
|
description: String
|
||||||
|
isAdmin: Boolean
|
||||||
|
permissions: [Permission]
|
||||||
|
}
|
||||||
|
|
||||||
type PageInfo {
|
type PageInfo {
|
||||||
currentPage: Int!
|
currentPage: Int!
|
||||||
totalPages: Int!
|
totalPages: Int!
|
||||||
@@ -554,6 +633,41 @@ type PaymentPlan {
|
|||||||
productId: String
|
productId: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetSamlAuthProviders {
|
||||||
|
id: String
|
||||||
|
name: String
|
||||||
|
issuer: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Permission {
|
||||||
|
action: String
|
||||||
|
subject: String
|
||||||
|
conditions: [String]
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO: emphasize it's a catalog item
|
||||||
|
type Permissions {
|
||||||
|
actions: [Action]
|
||||||
|
subjects: [Subject]
|
||||||
|
conditions: [Condition]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Action {
|
||||||
|
label: String
|
||||||
|
action: String
|
||||||
|
subjects: [String]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Condition {
|
||||||
|
key: String
|
||||||
|
label: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type Subject {
|
||||||
|
label: String
|
||||||
|
key: String
|
||||||
|
}
|
||||||
|
|
||||||
schema {
|
schema {
|
||||||
query: Query
|
query: Query
|
||||||
mutation: Mutation
|
mutation: Mutation
|
||||||
|
@@ -12,7 +12,17 @@ const isAuthenticated = rule()(async (_parent, _args, req) => {
|
|||||||
const { userId } = jwt.verify(token, appConfig.appSecretKey) as {
|
const { userId } = jwt.verify(token, appConfig.appSecretKey) as {
|
||||||
userId: string;
|
userId: string;
|
||||||
};
|
};
|
||||||
req.currentUser = await User.query().findById(userId).throwIfNotFound();
|
req.currentUser = await User
|
||||||
|
.query()
|
||||||
|
.findById(userId)
|
||||||
|
.leftJoinRelated({
|
||||||
|
role: true,
|
||||||
|
permissions: true,
|
||||||
|
})
|
||||||
|
.withGraphFetched({
|
||||||
|
role: true,
|
||||||
|
permissions: true,
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -25,13 +35,14 @@ const authentication = shield(
|
|||||||
Query: {
|
Query: {
|
||||||
'*': isAuthenticated,
|
'*': isAuthenticated,
|
||||||
getAutomatischInfo: allow,
|
getAutomatischInfo: allow,
|
||||||
|
getSamlAuthProviders: allow,
|
||||||
healthcheck: allow,
|
healthcheck: allow,
|
||||||
},
|
},
|
||||||
Mutation: {
|
Mutation: {
|
||||||
'*': isAuthenticated,
|
'*': isAuthenticated,
|
||||||
login: allow,
|
registerUser: allow,
|
||||||
createUser: allow,
|
|
||||||
forgotPassword: allow,
|
forgotPassword: allow,
|
||||||
|
login: allow,
|
||||||
resetPassword: allow,
|
resetPassword: allow,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -1,24 +0,0 @@
|
|||||||
import axios, { AxiosRequestConfig } from 'axios';
|
|
||||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
||||||
import { HttpProxyAgent } from 'http-proxy-agent';
|
|
||||||
|
|
||||||
const config: AxiosRequestConfig = {};
|
|
||||||
const httpProxyUrl = process.env.http_proxy;
|
|
||||||
const httpsProxyUrl = process.env.https_proxy;
|
|
||||||
const supportsProxy = httpProxyUrl || httpsProxyUrl;
|
|
||||||
|
|
||||||
if (supportsProxy) {
|
|
||||||
if (httpProxyUrl) {
|
|
||||||
config.httpAgent = new HttpProxyAgent(process.env.http_proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (httpsProxyUrl) {
|
|
||||||
config.httpsAgent = new HttpsProxyAgent(process.env.https_proxy);
|
|
||||||
}
|
|
||||||
|
|
||||||
config.proxy = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const axiosWithProxyInstance = axios.create(config);
|
|
||||||
|
|
||||||
export default axiosWithProxyInstance;
|
|
@@ -1,4 +1,3 @@
|
|||||||
// TODO: replace with axios-with-proxy when needed
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import appConfig from '../../config/app';
|
import appConfig from '../../config/app';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
// TODO: replace with axios-with-proxy
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import appConfig from '../config/app';
|
import appConfig from '../config/app';
|
||||||
import memoryCache from 'memory-cache';
|
import memoryCache from 'memory-cache';
|
||||||
|
@@ -3,7 +3,7 @@ import ExecutionStep from '../models/execution-step';
|
|||||||
import get from 'lodash.get';
|
import get from 'lodash.get';
|
||||||
|
|
||||||
// INFO: don't remove space in allowed character group!
|
// INFO: don't remove space in allowed character group!
|
||||||
const variableRegExp = /({{step\.[\da-zA-Z-]+(?:\.[\da-zA-Z-_ :]+)+}})/g;
|
const variableRegExp = /({{step\.[\da-zA-Z-]+(?:\.[\da-zA-Z-_ ]+)+}})/g;
|
||||||
|
|
||||||
export default function computeParameters(
|
export default function computeParameters(
|
||||||
parameters: Step['parameters'],
|
parameters: Step['parameters'],
|
||||||
@@ -42,7 +42,7 @@ export default function computeParameters(
|
|||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
[key]: value.map((item) => computeParameters(item, executionSteps)),
|
[key]: value.map(item => computeParameters(item, executionSteps)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
14
packages/backend/src/helpers/create-auth-token-by-user-id.ts
Normal file
14
packages/backend/src/helpers/create-auth-token-by-user-id.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import appConfig from '../config/app';
|
||||||
|
|
||||||
|
const TOKEN_EXPIRES_IN = '14d';
|
||||||
|
|
||||||
|
const createAuthTokenByUserId = (userId: string) => {
|
||||||
|
const token = jwt.sign({ userId }, appConfig.appSecretKey, {
|
||||||
|
expiresIn: TOKEN_EXPIRES_IN,
|
||||||
|
});
|
||||||
|
|
||||||
|
return token;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createAuthTokenByUserId;
|
@@ -0,0 +1,48 @@
|
|||||||
|
import SamlAuthProvider from '../models/saml-auth-provider.ee';
|
||||||
|
import User from '../models/user';
|
||||||
|
import Identity from '../models/identity.ee';
|
||||||
|
|
||||||
|
const getUser = (user: Record<string, unknown>, providerConfig: SamlAuthProvider) => ({
|
||||||
|
name: user[providerConfig.firstnameAttributeName],
|
||||||
|
surname: user[providerConfig.surnameAttributeName],
|
||||||
|
id: user.nameID,
|
||||||
|
email: user[providerConfig.emailAttributeName],
|
||||||
|
role: user[providerConfig.roleAttributeName],
|
||||||
|
})
|
||||||
|
|
||||||
|
const findOrCreateUserBySamlIdentity = async (userIdentity: Record<string, unknown>, samlAuthProvider: SamlAuthProvider) => {
|
||||||
|
const mappedUser = getUser(userIdentity, samlAuthProvider);
|
||||||
|
const identity = await Identity.query().findOne({
|
||||||
|
remote_id: mappedUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (identity) {
|
||||||
|
const user = await identity.$relatedQuery('user');
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdUser = await User.query().insertGraph({
|
||||||
|
fullName: [
|
||||||
|
mappedUser.name,
|
||||||
|
mappedUser.surname
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' '),
|
||||||
|
email: mappedUser.email as string,
|
||||||
|
roleId: samlAuthProvider.defaultRoleId,
|
||||||
|
identities: [
|
||||||
|
{
|
||||||
|
remoteId: mappedUser.id as string,
|
||||||
|
providerId: samlAuthProvider.id,
|
||||||
|
providerType: 'saml'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
relate: ['identities']
|
||||||
|
}).returning('*');
|
||||||
|
|
||||||
|
return createdUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default findOrCreateUserBySamlIdentity;
|
@@ -1,10 +1,8 @@
|
|||||||
import { IHttpClientParams } from '@automatisch/types';
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
import { AxiosRequestConfig } from 'axios';
|
|
||||||
import { URL } from 'node:url';
|
|
||||||
export { AxiosInstance as IHttpClient } from 'axios';
|
export { AxiosInstance as IHttpClient } from 'axios';
|
||||||
|
import { IHttpClientParams } from '@automatisch/types';
|
||||||
|
import { URL } from 'url';
|
||||||
import HttpError from '../../errors/http';
|
import HttpError from '../../errors/http';
|
||||||
import axios from '../axios-with-proxy';
|
|
||||||
|
|
||||||
const removeBaseUrlForAbsoluteUrls = (
|
const removeBaseUrlForAbsoluteUrls = (
|
||||||
requestConfig: AxiosRequestConfig
|
requestConfig: AxiosRequestConfig
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import { Model } from 'objection';
|
import { Model } from 'objection';
|
||||||
import ExtendedQueryBuilder from '../models/query-builder';
|
import ExtendedQueryBuilder from '../models/query-builder';
|
||||||
|
import type Base from '../models/base';
|
||||||
|
|
||||||
const paginate = async (
|
const paginate = async (
|
||||||
query: ExtendedQueryBuilder<Model, Model[]>,
|
query: ExtendedQueryBuilder<Model, Model[]>,
|
||||||
limit: number,
|
limit: number,
|
||||||
offset: number
|
offset: number,
|
||||||
) => {
|
) => {
|
||||||
if (limit < 1 || limit > 100) {
|
if (limit < 1 || limit > 100) {
|
||||||
throw new Error('Limit must be between 1 and 100');
|
throw new Error('Limit must be between 1 and 100');
|
||||||
@@ -20,11 +21,9 @@ const paginate = async (
|
|||||||
currentPage: Math.ceil(offset / limit + 1),
|
currentPage: Math.ceil(offset / limit + 1),
|
||||||
totalPages: Math.ceil(count / limit),
|
totalPages: Math.ceil(count / limit),
|
||||||
},
|
},
|
||||||
edges: records.map((record: Model) => {
|
edges: records.map((record: Base) => ({
|
||||||
return {
|
node: record,
|
||||||
node: record,
|
})),
|
||||||
};
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
84
packages/backend/src/helpers/passport.ts
Normal file
84
packages/backend/src/helpers/passport.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { URL } from 'node:url';
|
||||||
|
import { IRequest } from '@automatisch/types';
|
||||||
|
import { MultiSamlStrategy } from '@node-saml/passport-saml';
|
||||||
|
import { Express } from 'express';
|
||||||
|
import passport from 'passport';
|
||||||
|
|
||||||
|
import appConfig from '../config/app';
|
||||||
|
import createAuthTokenByUserId from '../helpers/create-auth-token-by-user-id';
|
||||||
|
import SamlAuthProvider from '../models/saml-auth-provider.ee';
|
||||||
|
import findOrCreateUserBySamlIdentity from './find-or-create-user-by-saml-identity.ee'
|
||||||
|
|
||||||
|
export default function configurePassport(app: Express) {
|
||||||
|
app.use(passport.initialize({
|
||||||
|
userProperty: 'currentUser',
|
||||||
|
}));
|
||||||
|
|
||||||
|
passport.use(new MultiSamlStrategy(
|
||||||
|
{
|
||||||
|
passReqToCallback: true,
|
||||||
|
getSamlOptions: async function (request, done) {
|
||||||
|
const { issuer } = request.params;
|
||||||
|
const notFoundIssuer = new Error('Issuer cannot be found!');
|
||||||
|
|
||||||
|
if (!issuer) return done(notFoundIssuer);
|
||||||
|
|
||||||
|
const authProvider = await SamlAuthProvider.query().findOne({
|
||||||
|
issuer: request.params.issuer as string,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!authProvider) {
|
||||||
|
return done(notFoundIssuer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return done(null, authProvider.config);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async function (request, user: Record<string, unknown>, done) {
|
||||||
|
const { issuer } = request.params;
|
||||||
|
const notFoundIssuer = new Error('Issuer cannot be found!');
|
||||||
|
|
||||||
|
if (!issuer) return done(notFoundIssuer);
|
||||||
|
|
||||||
|
const authProvider = await SamlAuthProvider.query().findOne({
|
||||||
|
issuer: request.params.issuer as string,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!authProvider) {
|
||||||
|
return done(notFoundIssuer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const foundUserWithIdentity = await findOrCreateUserBySamlIdentity(user, authProvider);
|
||||||
|
return done(null, foundUserWithIdentity as unknown as Record<string, unknown>);
|
||||||
|
},
|
||||||
|
function (request, user: Record<string, unknown>, done: (error: any, user: Record<string, unknown>) => void) {
|
||||||
|
return done(null, null);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
app.get('/login/saml/:issuer',
|
||||||
|
passport.authenticate('saml',
|
||||||
|
{
|
||||||
|
session: false,
|
||||||
|
successRedirect: '/',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
app.post(
|
||||||
|
'/login/saml/:issuer/callback',
|
||||||
|
passport.authenticate('saml', {
|
||||||
|
session: false,
|
||||||
|
failureRedirect: '/',
|
||||||
|
failureFlash: true,
|
||||||
|
}),
|
||||||
|
(req: IRequest, res) => {
|
||||||
|
const token = createAuthTokenByUserId(req.currentUser.id);
|
||||||
|
|
||||||
|
const redirectUrl = new URL(
|
||||||
|
`/login/callback?token=${token}`,
|
||||||
|
appConfig.webAppUrl,
|
||||||
|
).toString();
|
||||||
|
res.redirect(redirectUrl);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
@@ -40,6 +40,9 @@ class Connection extends Base {
|
|||||||
userId: { type: 'string', format: 'uuid' },
|
userId: { type: 'string', format: 'uuid' },
|
||||||
verified: { type: 'boolean', default: false },
|
verified: { type: 'boolean', default: false },
|
||||||
draft: { type: 'boolean' },
|
draft: { type: 'boolean' },
|
||||||
|
deletedAt: { type: 'string' },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -31,6 +31,9 @@ class ExecutionStep extends Base {
|
|||||||
dataOut: { type: ['object', 'null'] },
|
dataOut: { type: ['object', 'null'] },
|
||||||
status: { type: 'string', enum: ['success', 'failure'] },
|
status: { type: 'string', enum: ['success', 'failure'] },
|
||||||
errorDetails: { type: ['object', 'null'] },
|
errorDetails: { type: ['object', 'null'] },
|
||||||
|
deletedAt: { type: 'string' },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -22,6 +22,9 @@ class Execution extends Base {
|
|||||||
flowId: { type: 'string', format: 'uuid' },
|
flowId: { type: 'string', format: 'uuid' },
|
||||||
testRun: { type: 'boolean', default: false },
|
testRun: { type: 'boolean', default: false },
|
||||||
internalId: { type: 'string' },
|
internalId: { type: 'string' },
|
||||||
|
deletedAt: { type: 'string' },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -19,7 +19,7 @@ class Flow extends Base {
|
|||||||
status: 'paused' | 'published' | 'draft';
|
status: 'paused' | 'published' | 'draft';
|
||||||
steps: Step[];
|
steps: Step[];
|
||||||
triggerStep: Step;
|
triggerStep: Step;
|
||||||
published_at: string;
|
publishedAt: string;
|
||||||
remoteWebhookId: string;
|
remoteWebhookId: string;
|
||||||
executions?: Execution[];
|
executions?: Execution[];
|
||||||
lastExecution?: Execution;
|
lastExecution?: Execution;
|
||||||
@@ -37,6 +37,10 @@ class Flow extends Base {
|
|||||||
userId: { type: 'string', format: 'uuid' },
|
userId: { type: 'string', format: 'uuid' },
|
||||||
remoteWebhookId: { type: 'string' },
|
remoteWebhookId: { type: 'string' },
|
||||||
active: { type: 'boolean' },
|
active: { type: 'boolean' },
|
||||||
|
publishedAt: { type: 'string' },
|
||||||
|
deletedAt: { type: 'string' },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
53
packages/backend/src/models/identity.ee.ts
Normal file
53
packages/backend/src/models/identity.ee.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import Base from './base';
|
||||||
|
import SamlAuthProvider from './saml-auth-provider.ee';
|
||||||
|
import User from './user';
|
||||||
|
|
||||||
|
class Identity extends Base {
|
||||||
|
id!: string;
|
||||||
|
remoteId!: string;
|
||||||
|
userId!: string;
|
||||||
|
providerId!: string;
|
||||||
|
providerType!: 'saml';
|
||||||
|
|
||||||
|
static tableName = 'identities';
|
||||||
|
|
||||||
|
static jsonSchema = {
|
||||||
|
type: 'object',
|
||||||
|
required: [
|
||||||
|
'providerId',
|
||||||
|
'remoteId',
|
||||||
|
'userId',
|
||||||
|
'providerType',
|
||||||
|
],
|
||||||
|
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string', format: 'uuid' },
|
||||||
|
userId: { type: 'string', format: 'uuid' },
|
||||||
|
remoteId: { type: 'string', minLength: 1 },
|
||||||
|
providerId: { type: 'string', format: 'uuid' },
|
||||||
|
providerType: { type: 'string', enum: ['saml'] },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static relationMappings = () => ({
|
||||||
|
user: {
|
||||||
|
relation: Base.BelongsToOneRelation,
|
||||||
|
modelClass: User,
|
||||||
|
join: {
|
||||||
|
from: 'users.id',
|
||||||
|
to: 'identities.user_id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
samlAuthProvider: {
|
||||||
|
relation: Base.BelongsToOneRelation,
|
||||||
|
modelClass: SamlAuthProvider,
|
||||||
|
join: {
|
||||||
|
from: 'saml_auth_providers.id',
|
||||||
|
to: 'identities.provider_id'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Identity;
|
26
packages/backend/src/models/permission.ts
Normal file
26
packages/backend/src/models/permission.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import Base from './base';
|
||||||
|
|
||||||
|
class Permission extends Base {
|
||||||
|
id: string;
|
||||||
|
action: string;
|
||||||
|
subject: string;
|
||||||
|
conditions: string[];
|
||||||
|
|
||||||
|
static tableName = 'permissions';
|
||||||
|
|
||||||
|
static jsonSchema = {
|
||||||
|
type: 'object',
|
||||||
|
required: ['action', 'subject'],
|
||||||
|
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string', format: 'uuid' },
|
||||||
|
action: { type: 'string', minLength: 1 },
|
||||||
|
subject: { type: 'string', minLength: 1 },
|
||||||
|
conditions: { type: 'array', items: { type: 'string' } },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Permission;
|
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
Model,
|
Model,
|
||||||
Page,
|
Page,
|
||||||
|
ModelClass,
|
||||||
PartialModelObject,
|
PartialModelObject,
|
||||||
ForClassMethod,
|
ForClassMethod,
|
||||||
AnyQueryBuilder,
|
AnyQueryBuilder,
|
||||||
@@ -8,6 +9,10 @@ import {
|
|||||||
|
|
||||||
const DELETED_COLUMN_NAME = 'deleted_at';
|
const DELETED_COLUMN_NAME = 'deleted_at';
|
||||||
|
|
||||||
|
const supportsSoftDeletion = (modelClass: ModelClass<any>) => {
|
||||||
|
return modelClass.jsonSchema.properties.deletedAt;
|
||||||
|
}
|
||||||
|
|
||||||
const buildQueryBuidlerForClass = (): ForClassMethod => {
|
const buildQueryBuidlerForClass = (): ForClassMethod => {
|
||||||
return (modelClass) => {
|
return (modelClass) => {
|
||||||
const qb: AnyQueryBuilder = Model.QueryBuilder.forClass.call(
|
const qb: AnyQueryBuilder = Model.QueryBuilder.forClass.call(
|
||||||
@@ -15,7 +20,7 @@ const buildQueryBuidlerForClass = (): ForClassMethod => {
|
|||||||
modelClass
|
modelClass
|
||||||
);
|
);
|
||||||
qb.onBuild((builder) => {
|
qb.onBuild((builder) => {
|
||||||
if (!builder.context().withSoftDeleted) {
|
if (!builder.context().withSoftDeleted && supportsSoftDeletion(qb.modelClass())) {
|
||||||
builder.whereNull(
|
builder.whereNull(
|
||||||
`${qb.modelClass().tableName}.${DELETED_COLUMN_NAME}`
|
`${qb.modelClass().tableName}.${DELETED_COLUMN_NAME}`
|
||||||
);
|
);
|
||||||
@@ -38,9 +43,13 @@ class ExtendedQueryBuilder<M extends Model, R = M[]> extends Model.QueryBuilder<
|
|||||||
static forClass: ForClassMethod = buildQueryBuidlerForClass();
|
static forClass: ForClassMethod = buildQueryBuidlerForClass();
|
||||||
|
|
||||||
delete() {
|
delete() {
|
||||||
return this.patch({
|
if (supportsSoftDeletion(this.modelClass())) {
|
||||||
[DELETED_COLUMN_NAME]: new Date().toISOString(),
|
return this.patch({
|
||||||
} as unknown as PartialModelObject<M>);
|
[DELETED_COLUMN_NAME]: new Date().toISOString(),
|
||||||
|
} as unknown as PartialModelObject<M>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
hardDelete() {
|
hardDelete() {
|
||||||
|
61
packages/backend/src/models/role.ts
Normal file
61
packages/backend/src/models/role.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import Base from './base';
|
||||||
|
import Permission from './permission';
|
||||||
|
import User from './user';
|
||||||
|
|
||||||
|
class Role extends Base {
|
||||||
|
id!: string;
|
||||||
|
name!: string;
|
||||||
|
key: string;
|
||||||
|
description: string;
|
||||||
|
users?: User[];
|
||||||
|
permissions?: Permission[];
|
||||||
|
|
||||||
|
static tableName = 'roles';
|
||||||
|
|
||||||
|
static jsonSchema = {
|
||||||
|
type: 'object',
|
||||||
|
required: ['name', 'key'],
|
||||||
|
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string', format: 'uuid' },
|
||||||
|
name: { type: 'string', minLength: 1 },
|
||||||
|
key: { type: 'string', minLength: 1 },
|
||||||
|
description: { type: ['string', 'null'], minLength: 1, maxLength: 255 },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static get virtualAttributes() {
|
||||||
|
return ['isAdmin'];
|
||||||
|
}
|
||||||
|
|
||||||
|
static relationMappings = () => ({
|
||||||
|
users: {
|
||||||
|
relation: Base.HasManyRelation,
|
||||||
|
modelClass: User,
|
||||||
|
join: {
|
||||||
|
from: 'roles.id',
|
||||||
|
to: 'users.role_id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
relation: Base.ManyToManyRelation,
|
||||||
|
modelClass: Permission,
|
||||||
|
join: {
|
||||||
|
from: 'roles.id',
|
||||||
|
through: {
|
||||||
|
from: 'roles_permissions.role_id',
|
||||||
|
to: 'roles_permissions.permission_id',
|
||||||
|
},
|
||||||
|
to: 'permissions.id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
get isAdmin() {
|
||||||
|
return this.key === 'admin';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Role;
|
79
packages/backend/src/models/saml-auth-provider.ee.ts
Normal file
79
packages/backend/src/models/saml-auth-provider.ee.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { URL } from 'node:url';
|
||||||
|
import type { SamlConfig } from '@node-saml/passport-saml';
|
||||||
|
import appConfig from '../config/app';
|
||||||
|
import Base from './base';
|
||||||
|
import Identity from './identity.ee';
|
||||||
|
|
||||||
|
class SamlAuthProvider extends Base {
|
||||||
|
id!: string;
|
||||||
|
name: string;
|
||||||
|
certificate: string;
|
||||||
|
signatureAlgorithm: SamlConfig["signatureAlgorithm"];
|
||||||
|
issuer: string;
|
||||||
|
entryPoint: string;
|
||||||
|
firstnameAttributeName: string;
|
||||||
|
surnameAttributeName: string;
|
||||||
|
emailAttributeName: string;
|
||||||
|
roleAttributeName: string;
|
||||||
|
defaultRoleId: string;
|
||||||
|
|
||||||
|
static tableName = 'saml_auth_providers';
|
||||||
|
|
||||||
|
static jsonSchema = {
|
||||||
|
type: 'object',
|
||||||
|
required: [
|
||||||
|
'name',
|
||||||
|
'certificate',
|
||||||
|
'signatureAlgorithm',
|
||||||
|
'entryPoint',
|
||||||
|
'issuer',
|
||||||
|
'firstnameAttributeName',
|
||||||
|
'surnameAttributeName',
|
||||||
|
'emailAttributeName',
|
||||||
|
'roleAttributeName',
|
||||||
|
'defaultRoleId',
|
||||||
|
],
|
||||||
|
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string', format: 'uuid' },
|
||||||
|
name: { type: 'string', minLength: 1 },
|
||||||
|
certificate: { type: 'string', minLength: 1 },
|
||||||
|
signatureAlgorithm: { type: 'string', enum: ['sha1', 'sha256', 'sha512'] },
|
||||||
|
issuer: { type: 'string', minLength: 1 },
|
||||||
|
entryPoint: { type: 'string', minLength: 1 },
|
||||||
|
firstnameAttributeName: { type: 'string', minLength: 1 },
|
||||||
|
surnameAttributeName: { type: 'string', minLength: 1 },
|
||||||
|
emailAttributeName: { type: 'string', minLength: 1 },
|
||||||
|
roleAttributeName: { type: 'string', minLength: 1 },
|
||||||
|
defaultRoleId: { type: 'string', format: 'uuid' }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static relationMappings = () => ({
|
||||||
|
identities: {
|
||||||
|
relation: Base.HasOneRelation,
|
||||||
|
modelClass: Identity,
|
||||||
|
join: {
|
||||||
|
from: 'identities.provider_id',
|
||||||
|
to: 'saml_auth_providers.id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
get config(): SamlConfig {
|
||||||
|
const callbackUrl = new URL(
|
||||||
|
`/login/saml/${this.issuer}/callback`,
|
||||||
|
appConfig.baseUrl
|
||||||
|
).toString();
|
||||||
|
|
||||||
|
return {
|
||||||
|
callbackUrl,
|
||||||
|
cert: this.certificate,
|
||||||
|
entryPoint: this.entryPoint,
|
||||||
|
issuer: this.issuer,
|
||||||
|
signatureAlgorithm: this.signatureAlgorithm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SamlAuthProvider;
|
@@ -46,6 +46,9 @@ class Step extends Base {
|
|||||||
position: { type: 'integer' },
|
position: { type: 'integer' },
|
||||||
parameters: { type: 'object' },
|
parameters: { type: 'object' },
|
||||||
webhookPath: { type: ['string', 'null'] },
|
webhookPath: { type: ['string', 'null'] },
|
||||||
|
deletedAt: { type: 'string' },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -46,6 +46,9 @@ class Subscription extends Base {
|
|||||||
nextBillDate: { type: 'string' },
|
nextBillDate: { type: 'string' },
|
||||||
lastBillDate: { type: 'string' },
|
lastBillDate: { type: 'string' },
|
||||||
cancellationEffectiveDate: { type: 'string' },
|
cancellationEffectiveDate: { type: 'string' },
|
||||||
|
deletedAt: { type: 'string' },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,7 +87,7 @@ class Subscription extends Base {
|
|||||||
return (
|
return (
|
||||||
this.status === 'deleted' &&
|
this.status === 'deleted' &&
|
||||||
Number(this.cancellationEffectiveDate) >
|
Number(this.cancellationEffectiveDate) >
|
||||||
DateTime.now().startOf('day').toMillis()
|
DateTime.now().startOf('day').toMillis()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -24,6 +24,9 @@ class UsageData extends Base {
|
|||||||
subscriptionId: { type: 'string', format: 'uuid' },
|
subscriptionId: { type: 'string', format: 'uuid' },
|
||||||
consumedTaskCount: { type: 'integer' },
|
consumedTaskCount: { type: 'integer' },
|
||||||
nextResetAt: { type: 'string' },
|
nextResetAt: { type: 'string' },
|
||||||
|
deletedAt: { type: 'string' },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,14 +1,20 @@
|
|||||||
|
import crypto from 'node:crypto';
|
||||||
import { QueryContext, ModelOptions } from 'objection';
|
import { QueryContext, ModelOptions } from 'objection';
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
import crypto from 'crypto';
|
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
import { PureAbility, fieldPatternMatcher, mongoQueryMatcher } from '@casl/ability';
|
||||||
|
import type { Subject } from '@casl/ability';
|
||||||
|
|
||||||
import appConfig from '../config/app';
|
import appConfig from '../config/app';
|
||||||
import Base from './base';
|
import Base from './base';
|
||||||
import ExtendedQueryBuilder from './query-builder';
|
import ExtendedQueryBuilder from './query-builder';
|
||||||
import Connection from './connection';
|
import Connection from './connection';
|
||||||
import Flow from './flow';
|
import Flow from './flow';
|
||||||
import Step from './step';
|
import Step from './step';
|
||||||
|
import Role from './role';
|
||||||
|
import Permission from './permission';
|
||||||
import Execution from './execution';
|
import Execution from './execution';
|
||||||
|
import Identity from './identity.ee';
|
||||||
import UsageData from './usage-data.ee';
|
import UsageData from './usage-data.ee';
|
||||||
import Subscription from './subscription.ee';
|
import Subscription from './subscription.ee';
|
||||||
|
|
||||||
@@ -16,8 +22,8 @@ class User extends Base {
|
|||||||
id!: string;
|
id!: string;
|
||||||
fullName!: string;
|
fullName!: string;
|
||||||
email!: string;
|
email!: string;
|
||||||
|
roleId: string;
|
||||||
password!: string;
|
password!: string;
|
||||||
role: string;
|
|
||||||
resetPasswordToken: string;
|
resetPasswordToken: string;
|
||||||
resetPasswordTokenSentAt: string;
|
resetPasswordTokenSentAt: string;
|
||||||
trialExpiryDate: string;
|
trialExpiryDate: string;
|
||||||
@@ -29,19 +35,28 @@ class User extends Base {
|
|||||||
currentUsageData?: UsageData;
|
currentUsageData?: UsageData;
|
||||||
subscriptions?: Subscription[];
|
subscriptions?: Subscription[];
|
||||||
currentSubscription?: Subscription;
|
currentSubscription?: Subscription;
|
||||||
|
role: Role;
|
||||||
|
permissions: Permission[];
|
||||||
|
identities: Identity[];
|
||||||
|
|
||||||
static tableName = 'users';
|
static tableName = 'users';
|
||||||
|
|
||||||
static jsonSchema = {
|
static jsonSchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
required: ['fullName', 'email', 'password'],
|
required: ['fullName', 'email'],
|
||||||
|
|
||||||
properties: {
|
properties: {
|
||||||
id: { type: 'string', format: 'uuid' },
|
id: { type: 'string', format: 'uuid' },
|
||||||
fullName: { type: 'string', minLength: 1 },
|
fullName: { type: 'string', minLength: 1 },
|
||||||
email: { type: 'string', format: 'email', minLength: 1, maxLength: 255 },
|
email: { type: 'string', format: 'email', minLength: 1, maxLength: 255 },
|
||||||
password: { type: 'string', minLength: 1, maxLength: 255 },
|
password: { type: 'string' },
|
||||||
role: { type: 'string', enum: ['admin', 'user'] },
|
resetPasswordToken: { type: 'string' },
|
||||||
|
resetPasswordTokenSentAt: { type: 'string' },
|
||||||
|
trialExpiryDate: { type: 'string' },
|
||||||
|
roleId: { type: 'string', format: 'uuid' },
|
||||||
|
deletedAt: { type: 'string' },
|
||||||
|
createdAt: { type: 'string' },
|
||||||
|
updatedAt: { type: 'string' },
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -124,6 +139,34 @@ class User extends Base {
|
|||||||
builder.orderBy('created_at', 'desc').limit(1).first();
|
builder.orderBy('created_at', 'desc').limit(1).first();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
role: {
|
||||||
|
relation: Base.HasOneRelation,
|
||||||
|
modelClass: Role,
|
||||||
|
join: {
|
||||||
|
from: 'roles.id',
|
||||||
|
to: 'users.role_id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
relation: Base.ManyToManyRelation,
|
||||||
|
modelClass: Permission,
|
||||||
|
join: {
|
||||||
|
from: 'users.role_id',
|
||||||
|
through: {
|
||||||
|
from: 'roles_permissions.role_id',
|
||||||
|
to: 'roles_permissions.permission_id',
|
||||||
|
},
|
||||||
|
to: 'permissions.id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
identities: {
|
||||||
|
relation: Base.HasManyRelation,
|
||||||
|
modelClass: Identity,
|
||||||
|
join: {
|
||||||
|
from: 'identities.user_id',
|
||||||
|
to: 'users.id',
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
login(password: string) {
|
login(password: string) {
|
||||||
@@ -158,7 +201,9 @@ class User extends Base {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async generateHash() {
|
async generateHash() {
|
||||||
this.password = await bcrypt.hash(this.password, 10);
|
if (this.password) {
|
||||||
|
this.password = await bcrypt.hash(this.password, 10);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async startTrialPeriod() {
|
async startTrialPeriod() {
|
||||||
@@ -232,9 +277,7 @@ class User extends Base {
|
|||||||
async $beforeUpdate(opt: ModelOptions, queryContext: QueryContext) {
|
async $beforeUpdate(opt: ModelOptions, queryContext: QueryContext) {
|
||||||
await super.$beforeUpdate(opt, queryContext);
|
await super.$beforeUpdate(opt, queryContext);
|
||||||
|
|
||||||
if (this.password) {
|
await this.generateHash();
|
||||||
await this.generateHash();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async $afterInsert(queryContext: QueryContext) {
|
async $afterInsert(queryContext: QueryContext) {
|
||||||
@@ -248,6 +291,34 @@ class User extends Base {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get ability() {
|
||||||
|
if (!this.permissions) {
|
||||||
|
throw new Error('User.permissions must be fetched!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're not using mongo, but our fields, conditions match
|
||||||
|
return new PureAbility(this.permissions, {
|
||||||
|
conditionsMatcher: mongoQueryMatcher,
|
||||||
|
fieldMatcher: fieldPatternMatcher
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
can(action: string, subject: Subject) {
|
||||||
|
const can = this.ability.can(action, subject);
|
||||||
|
|
||||||
|
if (!can) throw new Error('Not authorized!');
|
||||||
|
|
||||||
|
return can;
|
||||||
|
}
|
||||||
|
|
||||||
|
cannot(action: string, subject: Subject) {
|
||||||
|
const cannot = this.ability.cannot(action, subject);
|
||||||
|
|
||||||
|
if (cannot) throw new Error('Not authorized!');
|
||||||
|
|
||||||
|
return cannot;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default User;
|
export default User;
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@automatisch/cli",
|
"name": "@automatisch/cli",
|
||||||
"version": "0.8.0",
|
"version": "0.7.1",
|
||||||
"license": "See LICENSE file",
|
"license": "See LICENSE file",
|
||||||
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
"description": "The open source Zapier alternative. Build workflow automation without spending time and money.",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"version": "oclif readme && git add README.md"
|
"version": "oclif readme && git add README.md"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@automatisch/backend": "^0.8.0",
|
"@automatisch/backend": "^0.7.1",
|
||||||
"@oclif/core": "^1",
|
"@oclif/core": "^1",
|
||||||
"@oclif/plugin-help": "^5",
|
"@oclif/plugin-help": "^5",
|
||||||
"@oclif/plugin-plugins": "^2.0.1",
|
"@oclif/plugin-plugins": "^2.0.1",
|
||||||
|
@@ -15593,6 +15593,11 @@ winston@^3.6.0, winston@^3.7.1:
|
|||||||
triple-beam "^1.3.0"
|
triple-beam "^1.3.0"
|
||||||
winston-transport "^4.5.0"
|
winston-transport "^4.5.0"
|
||||||
|
|
||||||
|
word-wrap@^1.2.3, word-wrap@~1.2.3:
|
||||||
|
version "1.2.3"
|
||||||
|
resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz"
|
||||||
|
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
||||||
|
|
||||||
workbox-background-sync@6.5.4:
|
workbox-background-sync@6.5.4:
|
||||||
version "6.5.4"
|
version "6.5.4"
|
||||||
resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz#3141afba3cc8aa2ae14c24d0f6811374ba8ff6a9"
|
resolved "https://registry.yarnpkg.com/workbox-background-sync/-/workbox-background-sync-6.5.4.tgz#3141afba3cc8aa2ae14c24d0f6811374ba8ff6a9"
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user