Compare commits
1 Commits
add-loadin
...
new-or-upd
Author | SHA1 | Date | |
---|---|---|---|
![]() |
03b357393e |
@@ -33,32 +33,7 @@ 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:
|
|
||||||
|
@@ -2,55 +2,18 @@ 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',
|
||||||
roleId: role.id,
|
role: 'admin',
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@@ -12,7 +12,6 @@ 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: {
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
"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 --watch 'src/graphql/schema.graphql' --exit-child src/server.ts",
|
"dev": "ts-node-dev --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",
|
||||||
@@ -24,16 +24,12 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@automatisch/web": "^0.7.1",
|
"@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",
|
|
||||||
"ajv-formats": "^2.1.1",
|
"ajv-formats": "^2.1.1",
|
||||||
"axios": "0.24.0",
|
"axios": "0.24.0",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
@@ -63,12 +59,10 @@
|
|||||||
"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",
|
||||||
"winston": "^3.7.1",
|
"winston": "^3.7.1"
|
||||||
"xmlrpc": "^1.3.2"
|
|
||||||
},
|
},
|
||||||
"contributors": [
|
"contributors": [
|
||||||
{
|
{
|
||||||
|
@@ -17,7 +17,6 @@ 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);
|
||||||
|
|
||||||
@@ -51,9 +50,6 @@ app.use(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
app.use(cors(corsOptions));
|
app.use(cors(corsOptions));
|
||||||
|
|
||||||
configurePassport(app);
|
|
||||||
|
|
||||||
app.use('/', router);
|
app.use('/', router);
|
||||||
|
|
||||||
webUIHandler(app);
|
webUIHandler(app);
|
||||||
|
@@ -20,7 +20,7 @@ export default defineAction({
|
|||||||
type: 'dropdown' as const,
|
type: 'dropdown' as const,
|
||||||
required: true,
|
required: true,
|
||||||
description: 'Language to translate the text to.',
|
description: 'Language to translate the text to.',
|
||||||
variables: true,
|
variables: false,
|
||||||
value: '',
|
value: '',
|
||||||
options: [
|
options: [
|
||||||
{ label: 'Bulgarian', value: 'BG' },
|
{ label: 'Bulgarian', value: 'BG' },
|
||||||
|
@@ -13,7 +13,7 @@ export default defineAction({
|
|||||||
required: true,
|
required: true,
|
||||||
value: null,
|
value: null,
|
||||||
description: 'Delay for unit, e.g. minutes, hours, days, weeks.',
|
description: 'Delay for unit, e.g. minutes, hours, days, weeks.',
|
||||||
variables: true,
|
variables: false,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: 'Minutes',
|
label: 'Minutes',
|
||||||
|
@@ -11,7 +11,7 @@ export default defineAction({
|
|||||||
type: 'dropdown' as const,
|
type: 'dropdown' as const,
|
||||||
required: true,
|
required: true,
|
||||||
description: 'Pick a channel to send the message to.',
|
description: 'Pick a channel to send the message to.',
|
||||||
variables: true,
|
variables: false,
|
||||||
source: {
|
source: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
name: 'getDynamicData',
|
name: 'getDynamicData',
|
||||||
|
@@ -19,8 +19,8 @@ export default {
|
|||||||
|
|
||||||
channels.data = response.data
|
channels.data = response.data
|
||||||
.filter((channel: IJSONObject) => {
|
.filter((channel: IJSONObject) => {
|
||||||
// filter in text channels and announcement channels only
|
// filter in text channels only
|
||||||
return channel.type === 0 || channel.type === 5;
|
return channel.type === 0;
|
||||||
})
|
})
|
||||||
.map((channel: IJSONObject) => {
|
.map((channel: IJSONObject) => {
|
||||||
return {
|
return {
|
||||||
|
@@ -11,7 +11,7 @@ export default defineAction({
|
|||||||
key: 'repo',
|
key: 'repo',
|
||||||
type: 'dropdown' as const,
|
type: 'dropdown' as const,
|
||||||
required: false,
|
required: false,
|
||||||
variables: true,
|
variables: false,
|
||||||
source: {
|
source: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
name: 'getDynamicData',
|
name: 'getDynamicData',
|
||||||
|
@@ -21,7 +21,7 @@ export default defineAction({
|
|||||||
required: false,
|
required: false,
|
||||||
description:
|
description:
|
||||||
'The Google Drive where your spreadsheet resides. If nothing is selected, then your personal Google Drive will be used.',
|
'The Google Drive where your spreadsheet resides. If nothing is selected, then your personal Google Drive will be used.',
|
||||||
variables: true,
|
variables: false,
|
||||||
source: {
|
source: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
name: 'getDynamicData',
|
name: 'getDynamicData',
|
||||||
@@ -40,7 +40,7 @@ export default defineAction({
|
|||||||
required: true,
|
required: true,
|
||||||
dependsOn: ['parameters.driveId'],
|
dependsOn: ['parameters.driveId'],
|
||||||
description: 'The spreadsheets in your Google Drive.',
|
description: 'The spreadsheets in your Google Drive.',
|
||||||
variables: true,
|
variables: false,
|
||||||
source: {
|
source: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
name: 'getDynamicData',
|
name: 'getDynamicData',
|
||||||
@@ -63,7 +63,7 @@ export default defineAction({
|
|||||||
required: true,
|
required: true,
|
||||||
dependsOn: ['parameters.spreadsheetId'],
|
dependsOn: ['parameters.spreadsheetId'],
|
||||||
description: 'The worksheets in your selected spreadsheet.',
|
description: 'The worksheets in your selected spreadsheet.',
|
||||||
variables: true,
|
variables: false,
|
||||||
source: {
|
source: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
name: 'getDynamicData',
|
name: 'getDynamicData',
|
||||||
|
@@ -1,105 +0,0 @@
|
|||||||
import defineAction from '../../../../helpers/define-action';
|
|
||||||
|
|
||||||
type THeaders = {
|
|
||||||
__id: string;
|
|
||||||
header: string;
|
|
||||||
}[];
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Create spreadsheet',
|
|
||||||
key: 'createSpreadsheet',
|
|
||||||
description:
|
|
||||||
'Create a blank spreadsheet or duplicate an existing spreadsheet. Optionally, provide headers.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Title',
|
|
||||||
key: 'title',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
description: '',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Spreadsheet to copy',
|
|
||||||
key: 'spreadsheetId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
description: 'Choose a spreadsheet to copy its data.',
|
|
||||||
variables: true,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listSpreadsheets',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Headers',
|
|
||||||
key: 'headers',
|
|
||||||
type: 'dynamic' as const,
|
|
||||||
required: false,
|
|
||||||
description:
|
|
||||||
'These headers are ignored if "Spreadsheet to Copy" is selected.',
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
label: 'Header',
|
|
||||||
key: 'header',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
if ($.step.parameters.spreadsheetId) {
|
|
||||||
const body = { name: $.step.parameters.title };
|
|
||||||
|
|
||||||
const { data } = await $.http.post(
|
|
||||||
`https://www.googleapis.com/drive/v3/files/${$.step.parameters.spreadsheetId}/copy`,
|
|
||||||
body
|
|
||||||
);
|
|
||||||
|
|
||||||
$.setActionItem({
|
|
||||||
raw: data,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const headers = $.step.parameters.headers as THeaders;
|
|
||||||
const values = headers.map((entry) => entry.header);
|
|
||||||
|
|
||||||
const spreadsheetBody = {
|
|
||||||
properties: {
|
|
||||||
title: $.step.parameters.title,
|
|
||||||
},
|
|
||||||
sheets: [
|
|
||||||
{
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
startRow: 0,
|
|
||||||
startColumn: 0,
|
|
||||||
rowData: [
|
|
||||||
{
|
|
||||||
values: values.map((header) => ({
|
|
||||||
userEnteredValue: { stringValue: header },
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const { data } = await $.http.post('/v4/spreadsheets', spreadsheetBody);
|
|
||||||
|
|
||||||
$.setActionItem({
|
|
||||||
raw: data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,4 +1,3 @@
|
|||||||
import createSpreadsheet from './create-spreadsheet';
|
|
||||||
import createSpreadsheetRow from './create-spreadsheet-row';
|
import createSpreadsheetRow from './create-spreadsheet-row';
|
||||||
|
|
||||||
export default [createSpreadsheet, createSpreadsheetRow];
|
export default [createSpreadsheetRow];
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import listDrives from './list-drives';
|
import listDrives from './list-drives';
|
||||||
import listSpreadsheets from './list-spreadsheets';
|
import listSpreadsheets from './list-spreadsheets';
|
||||||
import listWorksheets from './list-worksheets';
|
import listWorksheets from './list-worksheets';
|
||||||
|
import listColumns from './list-columns';
|
||||||
|
|
||||||
export default [listDrives, listSpreadsheets, listWorksheets];
|
export default [listDrives, listSpreadsheets, listWorksheets, listColumns];
|
||||||
|
@@ -0,0 +1,70 @@
|
|||||||
|
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||||
|
|
||||||
|
type TSheetsResponse = {
|
||||||
|
sheets: {
|
||||||
|
properties: {
|
||||||
|
sheetId: string;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
function getColumnNameByNumber(columnNumber: number) {
|
||||||
|
let columnName = '';
|
||||||
|
while (columnNumber > 0) {
|
||||||
|
const modulo = (columnNumber - 1) % 26;
|
||||||
|
columnName = String.fromCharCode(65 + modulo) + columnName;
|
||||||
|
columnNumber = Math.floor((columnNumber - modulo) / 26);
|
||||||
|
}
|
||||||
|
return columnName;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'List columns',
|
||||||
|
key: 'listColumns',
|
||||||
|
|
||||||
|
async run($: IGlobalVariable) {
|
||||||
|
const spreadsheetId = $.step.parameters.spreadsheetId as string;
|
||||||
|
|
||||||
|
const headers: {
|
||||||
|
data: IJSONObject[];
|
||||||
|
} = {
|
||||||
|
data: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!spreadsheetId) {
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: { sheets },
|
||||||
|
} = await $.http.get<TSheetsResponse>(
|
||||||
|
`/v4/spreadsheets/${$.step.parameters.spreadsheetId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedSheet = sheets.find(
|
||||||
|
(sheet) => sheet.properties.sheetId === $.step.parameters.worksheetId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!selectedSheet) return;
|
||||||
|
|
||||||
|
const sheetName = selectedSheet.properties.title;
|
||||||
|
|
||||||
|
const range = `${sheetName}!1:1`;
|
||||||
|
|
||||||
|
const { data } = await $.http.get(
|
||||||
|
`v4/spreadsheets/${$.step.parameters.spreadsheetId}/values/${range}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data.values?.length) {
|
||||||
|
for (let number = 0; number < data.values[0].length; number++) {
|
||||||
|
headers.data.push({
|
||||||
|
value: getColumnNameByNumber(number + 1),
|
||||||
|
name: data.values[0][number],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return headers;
|
||||||
|
},
|
||||||
|
};
|
@@ -1,5 +1,11 @@
|
|||||||
import newSpreadsheets from './new-spreadsheets';
|
import newSpreadsheets from './new-spreadsheets';
|
||||||
import newWorksheets from './new-worksheets';
|
import newWorksheets from './new-worksheets';
|
||||||
import newSpreadsheetRows from './new-spreadsheet-rows';
|
import newSpreadsheetRows from './new-spreadsheet-rows';
|
||||||
|
import newOrUpdatedSpreadsheetRows from './new-or-updated-spreadsheet-rows';
|
||||||
|
|
||||||
export default [newSpreadsheets, newWorksheets, newSpreadsheetRows];
|
export default [
|
||||||
|
newSpreadsheets,
|
||||||
|
newWorksheets,
|
||||||
|
newSpreadsheetRows,
|
||||||
|
newOrUpdatedSpreadsheetRows,
|
||||||
|
];
|
||||||
|
@@ -0,0 +1,109 @@
|
|||||||
|
import defineTrigger from '../../../../helpers/define-trigger';
|
||||||
|
import newOrUpdatedSpreadsheetRows from './new-or-updated-spreadsheet-rows';
|
||||||
|
|
||||||
|
export default defineTrigger({
|
||||||
|
name: 'New or updated spreadsheet rows',
|
||||||
|
key: 'newOrUpdatedSpreadsheetRows',
|
||||||
|
pollInterval: 15,
|
||||||
|
description: 'Triggers when a new row is added or modified in a spreadsheet.',
|
||||||
|
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: false,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listDrives',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Spreadsheet',
|
||||||
|
key: 'spreadsheetId',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: true,
|
||||||
|
dependsOn: ['parameters.driveId'],
|
||||||
|
description: 'The spreadsheets in your Google Drive.',
|
||||||
|
variables: false,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listSpreadsheets',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.driveId',
|
||||||
|
value: '{parameters.driveId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Worksheet',
|
||||||
|
key: 'worksheetId',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: true,
|
||||||
|
dependsOn: ['parameters.spreadsheetId'],
|
||||||
|
description:
|
||||||
|
'The worksheets in your selected spreadsheet. You must have column headers.',
|
||||||
|
variables: false,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listWorksheets',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.spreadsheetId',
|
||||||
|
value: '{parameters.spreadsheetId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Trigger Column',
|
||||||
|
key: 'triggerColumnIndex',
|
||||||
|
type: 'dropdown' as const,
|
||||||
|
required: false,
|
||||||
|
dependsOn: ['parameters.worksheetId'],
|
||||||
|
description:
|
||||||
|
'Triggers on changes to cells in this column only. Leave this field blank if you want the flow to trigger on changes to any cell within the row. Please note: All new rows will trigger the flow even if the Trigger column is empty.',
|
||||||
|
variables: false,
|
||||||
|
source: {
|
||||||
|
type: 'query',
|
||||||
|
name: 'getDynamicData',
|
||||||
|
arguments: [
|
||||||
|
{
|
||||||
|
name: 'key',
|
||||||
|
value: 'listColumns',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.spreadsheetId',
|
||||||
|
value: '{parameters.spreadsheetId}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parameters.worksheetId',
|
||||||
|
value: '{parameters.worksheetId}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
async run($) {
|
||||||
|
await newOrUpdatedSpreadsheetRows($);
|
||||||
|
},
|
||||||
|
});
|
@@ -0,0 +1,50 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
|
type TSheetsResponse = {
|
||||||
|
sheets: {
|
||||||
|
properties: {
|
||||||
|
sheetId: string;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const newOrUpdatedSpreadsheetRows = async ($: IGlobalVariable) => {
|
||||||
|
const {
|
||||||
|
data: { sheets },
|
||||||
|
} = await $.http.get<TSheetsResponse>(
|
||||||
|
`/v4/spreadsheets/${$.step.parameters.spreadsheetId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedSheet = sheets.find(
|
||||||
|
(sheet) => sheet.properties.sheetId === $.step.parameters.worksheetId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!selectedSheet) return;
|
||||||
|
|
||||||
|
const sheetName = selectedSheet.properties.title;
|
||||||
|
|
||||||
|
let range = sheetName;
|
||||||
|
|
||||||
|
if ($.step.parameters.triggerColumnIndex) {
|
||||||
|
range = `${sheetName}!${$.step.parameters.triggerColumnIndex}:${$.step.parameters.triggerColumnIndex}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await $.http.get(
|
||||||
|
`v4/spreadsheets/${$.step.parameters.spreadsheetId}/values/${range}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data.values?.length) {
|
||||||
|
for (let index = data.values.length - 1; index > 0; index--) {
|
||||||
|
const value = data.values[index];
|
||||||
|
$.pushTriggerItem({
|
||||||
|
raw: { row: value },
|
||||||
|
meta: {
|
||||||
|
internalId: `${value}-${index.toString()}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default newOrUpdatedSpreadsheetRows;
|
@@ -84,7 +84,7 @@ export default defineAction({
|
|||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
required: true,
|
required: true,
|
||||||
description: 'Header key',
|
description: 'Header key',
|
||||||
variables: true,
|
variables: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Value',
|
label: 'Value',
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
import sendMessageToChannel from './send-a-message-to-channel';
|
|
||||||
|
|
||||||
export default [sendMessageToChannel];
|
|
@@ -1,42 +0,0 @@
|
|||||||
import defineAction from '../../../../helpers/define-action';
|
|
||||||
import postMessage from './post-message';
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Send a message to channel',
|
|
||||||
key: 'sendMessageToChannel',
|
|
||||||
description: 'Sends a message to a channel you specify.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Channel',
|
|
||||||
key: 'channel',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: true,
|
|
||||||
description: 'Pick a channel to send the message to.',
|
|
||||||
variables: true,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listChannels',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Message text',
|
|
||||||
key: 'message',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
description: 'The content of your new message.',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const message = await postMessage($);
|
|
||||||
|
|
||||||
return message;
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,27 +0,0 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
|
||||||
|
|
||||||
type TData = {
|
|
||||||
channel_id: string;
|
|
||||||
message: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const postMessage = async ($: IGlobalVariable) => {
|
|
||||||
const { parameters } = $.step;
|
|
||||||
const channel_id = parameters.channel as string;
|
|
||||||
const message = parameters.message as string;
|
|
||||||
|
|
||||||
const data: TData = {
|
|
||||||
channel_id,
|
|
||||||
message,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await $.http.post('/api/v4/posts', data);
|
|
||||||
|
|
||||||
const actionData = {
|
|
||||||
raw: response?.data,
|
|
||||||
};
|
|
||||||
|
|
||||||
$.setActionItem(actionData);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default postMessage;
|
|
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
|
||||||
<g>
|
|
||||||
<path d="M6.79123171,86.9648684 C25.2351716,32.4823178 76.783459,-1.43234143 131.421839,0.0464773399 L131.421839,0.0464773399 L113.909757,20.739032 C81.4957329,26.5997669 53.5072568,48.7337413 42.5072761,81.2287969 C26.140539,129.576353 53.572705,182.545803 103.779303,199.543648 C153.985902,216.538377 207.952658,191.12264 224.319395,142.7782 C235.283535,110.390667 226.589826,75.9306053 204.563374,51.5978814 L204.563374,51.5978814 L203.21701,24.4290666 C247.371203,56.4768925 267.622761,114.633895 249.208429,169.029181 C226.546194,235.970273 153.909545,271.865521 86.9684532,249.204844 C20.0273609,226.542609 -15.8694453,153.905961 6.79123171,86.9648684 Z M165.185344,11.9237762 C165.839826,11.6401671 166.594039,11.5793938 167.321762,11.8256038 C168.035459,12.0671391 168.585536,12.5580009 168.936152,13.1595015 L168.936152,13.1595015 L169.007833,13.2763734 L169.071723,13.4103864 C169.240019,13.7313945 169.383381,14.0991514 169.450388,14.5510559 C169.582343,15.4417519 169.641535,17.5358595 169.665634,19.6808502 L169.671365,20.2662434 C169.677102,20.9486534 169.679633,21.6256073 169.680171,22.2599793 L169.680173,22.7924325 C169.678741,24.5267431 169.663874,25.8268542 169.663874,25.8268542 L169.663874,25.8268542 L170.167202,44.7600977 L170.910507,66.6151379 L171.837691,104.59538 C171.837691,104.59538 171.83785,104.602367 171.838064,104.616156 L171.838772,104.677745 C171.838883,104.691349 171.838983,104.706608 171.839058,104.723498 L171.839105,104.844231 C171.832023,107.013302 171.387173,122.892918 160.122454,133.928662 C148.009853,145.795053 133.131285,144.708923 123.451177,141.433394 C113.771069,138.154749 101.293828,129.979951 98.8800345,113.195592 C96.8283098,98.9302108 104.41287,86.9390787 106.734401,83.6627102 L106.889339,83.4459953 C107.205256,83.0081712 107.389865,82.7777388 107.389865,82.7777388 L107.389865,82.7777388 L131.197445,53.1717559 L145.064682,36.2627333 L156.965355,21.5275276 C156.965355,21.5275276 158.715313,19.1834331 160.51647,16.874806 L160.876881,16.4142586 C161.477025,15.6498178 162.070275,14.9069442 162.593713,14.2737698 L162.898895,13.907734 C163.342593,13.3805415 163.71955,12.9564826 163.983901,12.6998055 C164.292443,12.4006135 164.608776,12.205827 164.918876,12.0546727 L164.918876,12.0546727 L165.146386,11.9393591 Z" fill="#0058CC"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -1,18 +0,0 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
|
||||||
import { URL, URLSearchParams } from 'url';
|
|
||||||
import getBaseUrl from '../common/get-base-url';
|
|
||||||
|
|
||||||
export default async function generateAuthUrl($: IGlobalVariable) {
|
|
||||||
const searchParams = new URLSearchParams({
|
|
||||||
client_id: $.auth.data.clientId as string,
|
|
||||||
redirect_uri: $.auth.data.oAuthRedirectUrl as string,
|
|
||||||
response_type: 'code',
|
|
||||||
});
|
|
||||||
|
|
||||||
const baseUrl = getBaseUrl($);
|
|
||||||
const path = `/oauth/authorize?${searchParams.toString()}`;
|
|
||||||
|
|
||||||
await $.auth.set({
|
|
||||||
url: new URL(path, baseUrl).toString(),
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,57 +0,0 @@
|
|||||||
import generateAuthUrl from './generate-auth-url';
|
|
||||||
import verifyCredentials from './verify-credentials';
|
|
||||||
import isStillVerified from './is-still-verified';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
key: 'oAuthRedirectUrl',
|
|
||||||
label: 'OAuth Redirect URL',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: true,
|
|
||||||
value: '{WEB_APP_URL}/app/mattermost/connections/add',
|
|
||||||
placeholder: null,
|
|
||||||
description:
|
|
||||||
'When asked to input an OAuth callback or redirect URL in Mattermost OAuth, enter the URL above.',
|
|
||||||
clickToCopy: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'instanceUrl',
|
|
||||||
label: 'Mattermost instance URL',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: 'Your Mattermost instance URL',
|
|
||||||
clickToCopy: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'clientId',
|
|
||||||
label: 'Client id',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: null,
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'clientSecret',
|
|
||||||
label: 'Client secret',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: null,
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
generateAuthUrl,
|
|
||||||
verifyCredentials,
|
|
||||||
isStillVerified,
|
|
||||||
};
|
|
@@ -1,9 +0,0 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
|
||||||
import getCurrentUser from '../common/get-current-user';
|
|
||||||
|
|
||||||
const isStillVerified = async ($: IGlobalVariable) => {
|
|
||||||
const user = await getCurrentUser($);
|
|
||||||
return !!user.id;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default isStillVerified;
|
|
@@ -1,44 +0,0 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
|
||||||
import getCurrentUser from '../common/get-current-user';
|
|
||||||
|
|
||||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
|
||||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
|
||||||
(field) => field.key == 'oAuthRedirectUrl'
|
|
||||||
);
|
|
||||||
const redirectUri = oauthRedirectUrlField.value as string;
|
|
||||||
const params = {
|
|
||||||
client_id: $.auth.data.clientId,
|
|
||||||
client_secret: $.auth.data.clientSecret,
|
|
||||||
code: $.auth.data.code,
|
|
||||||
grant_type: 'authorization_code',
|
|
||||||
redirect_uri: redirectUri,
|
|
||||||
};
|
|
||||||
const headers = {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded', // This is not documented yet required
|
|
||||||
};
|
|
||||||
const response = await $.http.post('/oauth/access_token', null, {
|
|
||||||
params,
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: { access_token, refresh_token, scope, token_type },
|
|
||||||
} = response;
|
|
||||||
|
|
||||||
$.auth.data.accessToken = response.data.access_token;
|
|
||||||
|
|
||||||
const currentUser = await getCurrentUser($);
|
|
||||||
|
|
||||||
await $.auth.set({
|
|
||||||
clientId: $.auth.data.clientId,
|
|
||||||
clientSecret: $.auth.data.clientSecret,
|
|
||||||
accessToken: access_token,
|
|
||||||
refreshToken: refresh_token,
|
|
||||||
scope: scope,
|
|
||||||
tokenType: token_type,
|
|
||||||
userId: currentUser.id,
|
|
||||||
screenName: currentUser.username,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default verifyCredentials;
|
|
@@ -1,12 +0,0 @@
|
|||||||
import { TBeforeRequest } from '@automatisch/types';
|
|
||||||
|
|
||||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
|
||||||
if ($.auth.data?.accessToken) {
|
|
||||||
requestConfig.headers = requestConfig.headers || {};
|
|
||||||
requestConfig.headers.Authorization = `Bearer ${$.auth.data.accessToken}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return requestConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default addAuthHeader;
|
|
@@ -1,11 +0,0 @@
|
|||||||
import { TBeforeRequest } from '@automatisch/types';
|
|
||||||
|
|
||||||
const addXRequestedWithHeader: TBeforeRequest = ($, requestConfig) => {
|
|
||||||
// This is not documented yet required
|
|
||||||
// ref. https://forum.mattermost.com/t/solved-invalid-or-expired-session-please-login-again/6772
|
|
||||||
requestConfig.headers = requestConfig.headers || {};
|
|
||||||
requestConfig.headers['X-Requested-With'] = `XMLHttpRequest`;
|
|
||||||
return requestConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default addXRequestedWithHeader;
|
|
@@ -1,7 +0,0 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
|
||||||
|
|
||||||
const getBaseUrl = ($: IGlobalVariable): string => {
|
|
||||||
return $.auth.data.instanceUrl as string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getBaseUrl;
|
|
@@ -1,9 +0,0 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
|
||||||
|
|
||||||
const getCurrentUser = async ($: IGlobalVariable): Promise<IJSONObject> => {
|
|
||||||
const response = await $.http.get('/api/v4/users/me');
|
|
||||||
const currentUser = response.data;
|
|
||||||
return currentUser;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getCurrentUser;
|
|
@@ -1,9 +0,0 @@
|
|||||||
import { TBeforeRequest } from '@automatisch/types';
|
|
||||||
|
|
||||||
const setBaseUrl: TBeforeRequest = ($, requestConfig) => {
|
|
||||||
requestConfig.baseURL = $.auth.data.instanceUrl as string;
|
|
||||||
|
|
||||||
return requestConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default setBaseUrl;
|
|
@@ -1,3 +0,0 @@
|
|||||||
import listChannels from './list-channels';
|
|
||||||
|
|
||||||
export default [listChannels];
|
|
@@ -1,36 +0,0 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
|
||||||
|
|
||||||
type TChannel = {
|
|
||||||
id: string;
|
|
||||||
display_name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TResponse = {
|
|
||||||
data: TChannel[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'List channels',
|
|
||||||
key: 'listChannels',
|
|
||||||
|
|
||||||
async run($: IGlobalVariable) {
|
|
||||||
const channels: {
|
|
||||||
data: IJSONObject[];
|
|
||||||
error: IJSONObject | null;
|
|
||||||
} = {
|
|
||||||
data: [],
|
|
||||||
error: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response: TResponse = await $.http.get('/api/v4/users/me/channels'); // this endpoint will return only channels user joined, there is no endpoint to list all channels available for user
|
|
||||||
|
|
||||||
for (const channel of response.data) {
|
|
||||||
channels.data.push({
|
|
||||||
value: channel.id as string,
|
|
||||||
name: (channel.display_name as string) || (channel.id as string), // it's possible for channel to not have any name thus falling back to using id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return channels;
|
|
||||||
},
|
|
||||||
};
|
|
@@ -1,22 +0,0 @@
|
|||||||
import defineApp from '../../helpers/define-app';
|
|
||||||
import addAuthHeader from './common/add-auth-header';
|
|
||||||
import addXRequestedWithHeader from './common/add-x-requested-with-header';
|
|
||||||
import setBaseUrl from './common/set-base-url';
|
|
||||||
import auth from './auth';
|
|
||||||
import actions from './actions';
|
|
||||||
import dynamicData from './dynamic-data';
|
|
||||||
|
|
||||||
export default defineApp({
|
|
||||||
name: 'Mattermost',
|
|
||||||
key: 'mattermost',
|
|
||||||
iconUrl: '{BASE_URL}/apps/mattermost/assets/favicon.svg',
|
|
||||||
authDocUrl: 'https://automatisch.io/docs/apps/mattermost/connection',
|
|
||||||
baseUrl: 'https://mattermost.com',
|
|
||||||
apiBaseUrl: '', // there is no cloud version of this app, user always need to provide address of own instance when creating connection
|
|
||||||
primaryColor: '4a154b',
|
|
||||||
supportsConnections: true,
|
|
||||||
beforeRequest: [setBaseUrl, addXRequestedWithHeader, addAuthHeader],
|
|
||||||
auth,
|
|
||||||
actions,
|
|
||||||
dynamicData,
|
|
||||||
});
|
|
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<svg width="256px" height="268px" viewBox="0 0 256 268" version="1.1" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
|
|
||||||
<g>
|
|
||||||
<path d="M16.0924984,11.5384656 L164.089991,0.608048392 C182.268719,-0.952166138 186.940447,0.0998642306 198.370133,8.40912104 L245.613429,41.6907258 C253.405586,47.4144843 256,48.9746988 256,55.2066414 L256,237.73391 C256,249.172512 251.845372,255.939385 237.304172,256.973584 L65.4398551,267.377986 C54.5272689,267.895086 49.3295257,266.334872 43.6146827,259.050899 L8.82635648,213.813593 C2.58549836,205.486505 0,199.254562 0,191.970589 L0,29.7261093 C0,20.3737376 4.1546284,12.572665 16.0924984,11.5384656 Z" fill="#FFFFFF"></path>
|
|
||||||
<path d="M164.089991,0.608048392 L16.0924984,11.5384656 C4.1546284,12.572665 0,20.3737376 0,29.7261093 L0,191.970589 C0,199.254562 2.58549836,205.486505 8.82635648,213.813593 L43.6146827,259.050899 C49.3295257,266.334872 54.5272689,267.895086 65.4398551,267.377986 L237.304172,256.973584 C251.836456,255.939385 256,249.172512 256,237.73391 L256,55.2066414 C256,49.2956572 253.664136,47.5927945 246.790277,42.5466149 C246.394749,42.2616979 245.999494,41.9764014 245.604513,41.6907258 L198.370133,8.40912104 C186.940447,0.0998642306 182.268719,-0.952166138 164.089991,0.608048392 Z M69.3270182,52.219945 C55.2940029,53.1649893 52.1111653,53.3789615 44.1406979,46.8973846 L23.8757401,30.7781396 C21.8162569,28.6919099 22.8504562,26.0885805 28.039284,25.5714809 L170.313018,15.1759943 C182.259804,14.1328795 188.482831,18.2964234 193.154559,21.9339521 L217.556314,39.6134116 C218.599429,40.1394268 221.193843,43.2509404 218.073414,43.2509404 L71.1457825,52.0951279 L69.3270182,52.219945 Z M52.9670544,236.173696 L52.9670544,81.2221043 C52.9670544,74.455231 55.0443686,71.3348019 61.2673957,70.8087867 L230.020199,60.9303999 C235.743958,60.4133002 238.329456,64.0508289 238.329456,70.8087867 L238.329456,224.726179 C238.329456,231.493052 237.286341,237.216811 227.942885,237.73391 L66.4562234,247.095198 C57.1127673,247.612297 52.9670544,244.500784 52.9670544,236.173696 Z M212.376402,89.5313611 C213.410601,94.2120046 212.376402,98.8926482 207.695758,99.4275789 L199.912517,100.969962 L199.912517,215.373807 C193.154559,219.011336 186.931532,221.08865 181.733788,221.08865 C173.424532,221.08865 171.347217,218.485321 165.12419,210.693164 L114.225535,130.614039 L114.225535,208.089834 L130.326949,211.736279 C130.326949,211.736279 130.326949,221.097566 117.337048,221.097566 L81.523438,223.17488 C80.4803232,221.08865 81.523438,215.890907 85.1520513,214.856708 L94.5044229,212.262294 L94.5044229,109.823065 L81.523438,108.771035 C80.4803232,104.090391 83.0747371,97.3324337 90.3497945,96.8064185 L128.77565,94.2209202 L181.733788,175.334245 L181.733788,103.573292 L168.235704,102.021993 C167.192589,96.2893189 171.347217,92.1257749 176.536045,91.6175908 L212.376402,89.5313611 L212.376402,89.5313611 Z" fill="#000000"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1,21 +0,0 @@
|
|||||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
|
||||||
import { URL, URLSearchParams } from 'url';
|
|
||||||
|
|
||||||
export default async function generateAuthUrl($: IGlobalVariable) {
|
|
||||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
|
||||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
|
||||||
);
|
|
||||||
const redirectUri = oauthRedirectUrlField.value as string;
|
|
||||||
const searchParams = new URLSearchParams({
|
|
||||||
client_id: $.auth.data.clientId as string,
|
|
||||||
redirect_uri: redirectUri,
|
|
||||||
response_type: 'code',
|
|
||||||
owner: 'user',
|
|
||||||
});
|
|
||||||
|
|
||||||
const url = new URL(`/v1/oauth/authorize?${searchParams}`, $.app.apiBaseUrl).toString();
|
|
||||||
|
|
||||||
await $.auth.set({
|
|
||||||
url,
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,49 +0,0 @@
|
|||||||
import generateAuthUrl from './generate-auth-url';
|
|
||||||
import verifyCredentials from './verify-credentials';
|
|
||||||
import isStillVerified from './is-still-verified';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
key: 'oAuthRedirectUrl',
|
|
||||||
label: 'OAuth Redirect URL',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: true,
|
|
||||||
value: '{WEB_APP_URL}/app/notion/connections/add',
|
|
||||||
placeholder: null,
|
|
||||||
description:
|
|
||||||
'When asked to input an OAuth callback or redirect URL in Notion OAuth, enter the URL above.',
|
|
||||||
docUrl: 'https://automatisch.io/docs/notion#oauth-redirect-url',
|
|
||||||
clickToCopy: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'clientId',
|
|
||||||
label: 'Client ID',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: null,
|
|
||||||
docUrl: 'https://automatisch.io/docs/notion#client-id',
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'clientSecret',
|
|
||||||
label: 'Client Secret',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: null,
|
|
||||||
docUrl: 'https://automatisch.io/docs/notion#client-secret',
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
generateAuthUrl,
|
|
||||||
verifyCredentials,
|
|
||||||
isStillVerified,
|
|
||||||
};
|
|
@@ -1,9 +0,0 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
|
||||||
import getCurrentUser from '../common/get-current-user';
|
|
||||||
|
|
||||||
const isStillVerified = async ($: IGlobalVariable) => {
|
|
||||||
const user = await getCurrentUser($);
|
|
||||||
return !!user.id;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default isStillVerified;
|
|
@@ -1,53 +0,0 @@
|
|||||||
import { IGlobalVariable, IField } from '@automatisch/types';
|
|
||||||
import getCurrentUser from '../common/get-current-user';
|
|
||||||
|
|
||||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
|
||||||
const oauthRedirectUrlField = $.app.auth.fields.find(
|
|
||||||
(field: IField) => field.key == 'oAuthRedirectUrl'
|
|
||||||
);
|
|
||||||
const redirectUri = oauthRedirectUrlField.value as string;
|
|
||||||
const response = await $.http.post(
|
|
||||||
`${$.app.apiBaseUrl}/v1/oauth/token`,
|
|
||||||
{
|
|
||||||
redirect_uri: redirectUri,
|
|
||||||
code: $.auth.data.code,
|
|
||||||
grant_type: 'authorization_code',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Basic ${Buffer.from(
|
|
||||||
$.auth.data.clientId + ':' + $.auth.data.clientSecret
|
|
||||||
).toString('base64')}`,
|
|
||||||
},
|
|
||||||
additionalProperties: {
|
|
||||||
skipAddingAuthHeader: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const data = response.data;
|
|
||||||
|
|
||||||
$.auth.data.accessToken = data.access_token;
|
|
||||||
|
|
||||||
await $.auth.set({
|
|
||||||
clientId: $.auth.data.clientId,
|
|
||||||
clientSecret: $.auth.data.clientSecret,
|
|
||||||
accessToken: data.access_token,
|
|
||||||
botId: data.bot_id,
|
|
||||||
duplicatedTemplateId: data.duplicated_template_id,
|
|
||||||
owner: data.owner,
|
|
||||||
tokenType: data.token_type,
|
|
||||||
workspaceIcon: data.workspace_icon,
|
|
||||||
workspaceId: data.workspace_id,
|
|
||||||
workspaceName: data.workspace_name,
|
|
||||||
screenName: data.workspace_name,
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentUser = await getCurrentUser($);
|
|
||||||
|
|
||||||
await $.auth.set({
|
|
||||||
screenName: `${currentUser.name} @ ${data.workspace_name}`,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default verifyCredentials;
|
|
@@ -1,14 +0,0 @@
|
|||||||
import { TBeforeRequest } from '@automatisch/types';
|
|
||||||
|
|
||||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
|
||||||
if (requestConfig.additionalProperties?.skipAddingAuthHeader) return requestConfig;
|
|
||||||
|
|
||||||
if ($.auth.data?.accessToken) {
|
|
||||||
const authorizationHeader = `Bearer ${$.auth.data.accessToken}`;
|
|
||||||
requestConfig.headers.Authorization = authorizationHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
return requestConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default addAuthHeader;
|
|
@@ -1,9 +0,0 @@
|
|||||||
import { TBeforeRequest } from '@automatisch/types';
|
|
||||||
|
|
||||||
const addNotionVersionHeader: TBeforeRequest = ($, requestConfig) => {
|
|
||||||
requestConfig.headers['Notion-Version'] = '2022-06-28';
|
|
||||||
|
|
||||||
return requestConfig;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default addNotionVersionHeader;
|
|
@@ -1,17 +0,0 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
|
||||||
|
|
||||||
type Owner = {
|
|
||||||
user: {
|
|
||||||
id: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCurrentUser = async ($: IGlobalVariable): Promise<IJSONObject> => {
|
|
||||||
const userId = ($.auth.data.owner as Owner).user.id;
|
|
||||||
const response = await $.http.get(`/v1/users/${userId}`);
|
|
||||||
|
|
||||||
const currentUser = response.data;
|
|
||||||
return currentUser;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getCurrentUser;
|
|
@@ -1,3 +0,0 @@
|
|||||||
import listDatabases from './list-databases';
|
|
||||||
|
|
||||||
export default [listDatabases];
|
|
@@ -1,60 +0,0 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
|
||||||
|
|
||||||
type Database = {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
title: [
|
|
||||||
{
|
|
||||||
plain_text: string;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResponseData = {
|
|
||||||
results: Database[];
|
|
||||||
next_cursor?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Payload = {
|
|
||||||
filter: {
|
|
||||||
value: 'database';
|
|
||||||
property: 'object';
|
|
||||||
};
|
|
||||||
start_cursor?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'List databases',
|
|
||||||
key: 'listDatabases',
|
|
||||||
|
|
||||||
async run($: IGlobalVariable) {
|
|
||||||
const databases: {
|
|
||||||
data: IJSONObject[];
|
|
||||||
error: IJSONObject | null;
|
|
||||||
} = {
|
|
||||||
data: [],
|
|
||||||
error: null,
|
|
||||||
};
|
|
||||||
const payload: Payload = {
|
|
||||||
filter: {
|
|
||||||
value: 'database',
|
|
||||||
property: 'object'
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
do {
|
|
||||||
const response = await $.http.post<ResponseData>('/v1/search', payload);
|
|
||||||
|
|
||||||
payload.start_cursor = response.data.next_cursor;
|
|
||||||
|
|
||||||
for (const database of response.data.results) {
|
|
||||||
databases.data.push({
|
|
||||||
value: database.id as string,
|
|
||||||
name: database.title[0].plain_text as string,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} while (payload.start_cursor);
|
|
||||||
|
|
||||||
return databases;
|
|
||||||
},
|
|
||||||
};
|
|
@@ -1,24 +0,0 @@
|
|||||||
import defineApp from '../../helpers/define-app';
|
|
||||||
import addAuthHeader from './common/add-auth-header';
|
|
||||||
import addNotionVersionHeader from './common/add-notion-version-header';
|
|
||||||
import auth from './auth';
|
|
||||||
import triggers from './triggers';
|
|
||||||
import dynamicData from './dynamic-data';
|
|
||||||
|
|
||||||
export default defineApp({
|
|
||||||
name: 'Notion',
|
|
||||||
key: 'notion',
|
|
||||||
baseUrl: 'https://notion.com',
|
|
||||||
apiBaseUrl: 'https://api.notion.com',
|
|
||||||
iconUrl: '{BASE_URL}/apps/notion/assets/favicon.svg',
|
|
||||||
authDocUrl: 'https://automatisch.io/docs/apps/notion/connection',
|
|
||||||
primaryColor: '000000',
|
|
||||||
supportsConnections: true,
|
|
||||||
beforeRequest: [
|
|
||||||
addAuthHeader,
|
|
||||||
addNotionVersionHeader,
|
|
||||||
],
|
|
||||||
auth,
|
|
||||||
triggers,
|
|
||||||
dynamicData,
|
|
||||||
});
|
|
@@ -1,3 +0,0 @@
|
|||||||
import newDatabaseItems from './new-database-items';
|
|
||||||
|
|
||||||
export default [newDatabaseItems];
|
|
@@ -1,32 +0,0 @@
|
|||||||
import defineTrigger from '../../../../helpers/define-trigger';
|
|
||||||
import newDatabaseItems from './new-database-items';
|
|
||||||
|
|
||||||
export default defineTrigger({
|
|
||||||
name: 'New database items',
|
|
||||||
key: 'newDatabaseItems',
|
|
||||||
pollInterval: 15,
|
|
||||||
description: 'Triggers when a new database item is created',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Database',
|
|
||||||
key: 'databaseId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
variables: false,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listDatabases',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
await newDatabaseItems($);
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,50 +0,0 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
|
||||||
|
|
||||||
type DatabaseItem = {
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResponseData = {
|
|
||||||
results: DatabaseItem[];
|
|
||||||
next_cursor?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type Payload = {
|
|
||||||
sorts: [
|
|
||||||
{
|
|
||||||
timestamp: 'created_time' | 'last_edited_time';
|
|
||||||
direction: 'ascending' | 'descending';
|
|
||||||
}
|
|
||||||
];
|
|
||||||
start_cursor?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const newDatabaseItems = async ($: IGlobalVariable) => {
|
|
||||||
const payload: Payload = {
|
|
||||||
sorts: [
|
|
||||||
{
|
|
||||||
timestamp: 'created_time',
|
|
||||||
direction: 'descending'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const databaseId = $.step.parameters.databaseId as string;
|
|
||||||
const path = `/v1/databases/${databaseId}/query`;
|
|
||||||
do {
|
|
||||||
const response = await $.http.post<ResponseData>(path, payload);
|
|
||||||
|
|
||||||
payload.start_cursor = response.data.next_cursor;
|
|
||||||
|
|
||||||
for (const databaseItem of response.data.results) {
|
|
||||||
$.pushTriggerItem({
|
|
||||||
raw: databaseItem,
|
|
||||||
meta: {
|
|
||||||
internalId: databaseItem.id,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} while (payload.start_cursor);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default newDatabaseItems;
|
|
@@ -1,103 +0,0 @@
|
|||||||
import defineAction from '../../../../helpers/define-action';
|
|
||||||
import { authenticate, asyncMethodCall } from '../../common/xmlrpc-client';
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Create Lead',
|
|
||||||
key: 'createLead',
|
|
||||||
description: '',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Name',
|
|
||||||
key: 'name',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
description: 'Lead name',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Type',
|
|
||||||
key: 'type',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
label: 'Lead',
|
|
||||||
value: 'lead'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Opportunity',
|
|
||||||
value: 'opportunity'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Email",
|
|
||||||
key: 'email',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
description: 'Email of lead contact',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Contact Name",
|
|
||||||
key: 'contactName',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
description: 'Name of lead contact',
|
|
||||||
variables: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Phone Number',
|
|
||||||
key: 'phoneNumber',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
description: 'Phone number of lead contact',
|
|
||||||
variables: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Mobile Number',
|
|
||||||
key: 'mobileNumber',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
description: 'Mobile number of lead contact',
|
|
||||||
variables: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const uid = await authenticate($);
|
|
||||||
const id = await asyncMethodCall(
|
|
||||||
$,
|
|
||||||
{
|
|
||||||
method: 'execute_kw',
|
|
||||||
params: [
|
|
||||||
$.auth.data.databaseName,
|
|
||||||
uid,
|
|
||||||
$.auth.data.apiKey,
|
|
||||||
'crm.lead',
|
|
||||||
'create',
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: $.step.parameters.name,
|
|
||||||
type: $.step.parameters.type,
|
|
||||||
email_from: $.step.parameters.email,
|
|
||||||
contact_name: $.step.parameters.contactName,
|
|
||||||
phone: $.step.parameters.phoneNumber,
|
|
||||||
mobile: $.step.parameters.mobileNumber
|
|
||||||
}
|
|
||||||
]
|
|
||||||
],
|
|
||||||
path: 'object',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
$.setActionItem(
|
|
||||||
{
|
|
||||||
raw: {
|
|
||||||
id: id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
});
|
|
@@ -1,3 +0,0 @@
|
|||||||
import createLead from './create-lead';
|
|
||||||
|
|
||||||
export default [createLead];
|
|
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="600" height="191"><circle cx="527.5" cy="118.4" r="72.4" fill="#888"/><path d="M527.5 161.1c23.6 0 42.7-19.1 42.7-42.7s-19.1-42.7-42.7-42.7-42.7 19.1-42.7 42.7 19.1 42.7 42.7 42.7z" fill="#fff"/><circle cx="374" cy="118.4" r="72.4" fill="#888"/><path d="M374 161.1c23.6 0 42.7-19.1 42.7-42.7S397.6 75.7 374 75.7s-42.7 19.1-42.7 42.7 19.1 42.7 42.7 42.7z" fill="#fff"/><path d="M294.9 117.8v.6c0 40-32.4 72.4-72.4 72.4s-72.4-32.4-72.4-72.4S182.5 46 222.5 46c16.4 0 31.5 5.5 43.7 14.6V14.4A14.34 14.34 0 0 1 280.6 0c7.9 0 14.4 6.5 14.4 14.4v102.7c0 .2 0 .5-.1.7z" fill="#888"/><circle cx="222.5" cy="118.4" r="42.7" fill="#fff"/><circle cx="72.4" cy="118.2" r="72.4" fill="#9c5789"/><circle cx="71.7" cy="118.5" r="42.7" fill="#fff"/><script xmlns=""/></svg>
|
|
Before Width: | Height: | Size: 803 B |
@@ -1,65 +0,0 @@
|
|||||||
import verifyCredentials from './verify-credentials';
|
|
||||||
import isStillVerified from './is-still-verified';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
key: 'host',
|
|
||||||
label: 'Host Name',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: 'Host name of your Odoo Server',
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'port',
|
|
||||||
label: 'Port',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: '443',
|
|
||||||
placeholder: null,
|
|
||||||
description: 'Port that the host is running on, defaults to 443 (HTTPS)',
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'databaseName',
|
|
||||||
label: 'Database Name',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: 'Name of your Odoo database',
|
|
||||||
clickToCopy: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'email',
|
|
||||||
label: 'Email Address',
|
|
||||||
type: 'string' as const,
|
|
||||||
requires: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: 'Email Address of the account that will be interacting with the database',
|
|
||||||
clickToCopy: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'apiKey',
|
|
||||||
label: 'API Key',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
value: null,
|
|
||||||
placeholder: null,
|
|
||||||
description: 'API Key for your Odoo account',
|
|
||||||
clickToCopy: false
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
verifyCredentials,
|
|
||||||
isStillVerified
|
|
||||||
};
|
|
@@ -1,9 +0,0 @@
|
|||||||
import {IGlobalVariable} from '@automatisch/types';
|
|
||||||
import verifyCredentials from './verify-credentials';
|
|
||||||
|
|
||||||
const isStillVerified = async ($: IGlobalVariable) => {
|
|
||||||
await verifyCredentials($);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default isStillVerified;
|
|
@@ -1,16 +0,0 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
|
||||||
import { authenticate } from '../common/xmlrpc-client';
|
|
||||||
|
|
||||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
|
||||||
try {
|
|
||||||
await authenticate($);
|
|
||||||
|
|
||||||
await $.auth.set({
|
|
||||||
screenName: `${$.auth.data.email} @ ${$.auth.data.databaseName} - ${$.auth.data.host}`,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error('Failed while authorizing!');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default verifyCredentials;
|
|
@@ -1,67 +0,0 @@
|
|||||||
import { join } from 'node:path';
|
|
||||||
import xmlrpc from 'xmlrpc';
|
|
||||||
import { IGlobalVariable } from "@automatisch/types";
|
|
||||||
|
|
||||||
type AsyncMethodCallPayload = {
|
|
||||||
method: string;
|
|
||||||
params: any[];
|
|
||||||
path?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const asyncMethodCall = async <T = number>($: IGlobalVariable, { method, params, path }: AsyncMethodCallPayload): Promise<T> => {
|
|
||||||
return new Promise(
|
|
||||||
(resolve, reject) => {
|
|
||||||
const client = getClient($, { path });
|
|
||||||
|
|
||||||
client.methodCall(
|
|
||||||
method,
|
|
||||||
params,
|
|
||||||
(error, response) => {
|
|
||||||
if (error != null) {
|
|
||||||
// something went wrong on the server side, display the error returned by Odoo
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(response);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getClient = ($: IGlobalVariable, { path = 'common' }) => {
|
|
||||||
const host = $.auth.data.host as string;
|
|
||||||
const port = Number($.auth.data.port as string);
|
|
||||||
|
|
||||||
return xmlrpc.createClient(
|
|
||||||
{
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
path: join('/xmlrpc/2', path),
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const authenticate = async ($: IGlobalVariable) => {
|
|
||||||
const uid = await asyncMethodCall(
|
|
||||||
$,
|
|
||||||
{
|
|
||||||
method: 'authenticate',
|
|
||||||
params: [
|
|
||||||
$.auth.data.databaseName,
|
|
||||||
$.auth.data.email,
|
|
||||||
$.auth.data.apiKey,
|
|
||||||
[]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!Number.isInteger(uid)) {
|
|
||||||
// failed to authenticate
|
|
||||||
throw new Error(
|
|
||||||
'Failed to connect to the Odoo server. Please, check the credentials!'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return uid;
|
|
||||||
}
|
|
@@ -1,16 +0,0 @@
|
|||||||
import defineApp from '../../helpers/define-app';
|
|
||||||
import auth from './auth';
|
|
||||||
import actions from './actions';
|
|
||||||
|
|
||||||
export default defineApp({
|
|
||||||
name: 'Odoo',
|
|
||||||
key: 'odoo',
|
|
||||||
iconUrl: '{BASE_URL}/apps/odoo/assets/favicon.svg',
|
|
||||||
authDocUrl: 'https://automatisch.io/docs/apps/odoo/connection',
|
|
||||||
supportsConnections: true,
|
|
||||||
baseUrl: 'https://odoo.com',
|
|
||||||
apiBaseUrl: '',
|
|
||||||
primaryColor: '9c5789',
|
|
||||||
auth,
|
|
||||||
actions
|
|
||||||
});
|
|
@@ -19,7 +19,7 @@ export default defineAction({
|
|||||||
key: 'model',
|
key: 'model',
|
||||||
type: 'dropdown' as const,
|
type: 'dropdown' as const,
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
source: {
|
source: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
name: 'getDynamicData',
|
name: 'getDynamicData',
|
||||||
@@ -35,7 +35,7 @@ export default defineAction({
|
|||||||
label: 'Messages',
|
label: 'Messages',
|
||||||
key: 'messages',
|
key: 'messages',
|
||||||
type: 'dynamic' as const,
|
type: 'dynamic' as const,
|
||||||
required: true,
|
required: false,
|
||||||
description: 'Add or remove messages as needed',
|
description: 'Add or remove messages as needed',
|
||||||
value: [{ role: 'system', body: '' }],
|
value: [{ role: 'system', body: '' }],
|
||||||
fields: [
|
fields: [
|
||||||
|
@@ -14,7 +14,7 @@ export default defineAction({
|
|||||||
key: 'model',
|
key: 'model',
|
||||||
type: 'dropdown' as const,
|
type: 'dropdown' as const,
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
source: {
|
source: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
name: 'getDynamicData',
|
name: 'getDynamicData',
|
||||||
|
@@ -18,14 +18,14 @@ export default defineAction({
|
|||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
value: 'public',
|
value: 'public',
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Table name',
|
label: 'Table name',
|
||||||
key: 'table',
|
key: 'table',
|
||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Where clause entries',
|
label: 'Where clause entries',
|
||||||
@@ -38,14 +38,14 @@ export default defineAction({
|
|||||||
key: 'columnName',
|
key: 'columnName',
|
||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Operator',
|
label: 'Operator',
|
||||||
key: 'operator',
|
key: 'operator',
|
||||||
type: 'dropdown' as const,
|
type: 'dropdown' as const,
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
options: whereClauseOperators
|
options: whereClauseOperators
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -69,7 +69,7 @@ export default defineAction({
|
|||||||
key: 'parameter',
|
key: 'parameter',
|
||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Value',
|
label: 'Value',
|
||||||
@@ -102,8 +102,6 @@ export default defineAction({
|
|||||||
})
|
})
|
||||||
.del() as IJSONArray;
|
.del() as IJSONArray;
|
||||||
|
|
||||||
client.destroy();
|
|
||||||
|
|
||||||
$.setActionItem({
|
$.setActionItem({
|
||||||
raw: {
|
raw: {
|
||||||
rows: response
|
rows: response
|
||||||
|
@@ -16,14 +16,14 @@ export default defineAction({
|
|||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
value: 'public',
|
value: 'public',
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Table name',
|
label: 'Table name',
|
||||||
key: 'table',
|
key: 'table',
|
||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Column - value entries',
|
label: 'Column - value entries',
|
||||||
@@ -37,7 +37,7 @@ export default defineAction({
|
|||||||
key: 'columnName',
|
key: 'columnName',
|
||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Value',
|
label: 'Value',
|
||||||
@@ -52,7 +52,7 @@ export default defineAction({
|
|||||||
label: 'Run-time parameters',
|
label: 'Run-time parameters',
|
||||||
key: 'params',
|
key: 'params',
|
||||||
type: 'dynamic' as const,
|
type: 'dynamic' as const,
|
||||||
required: true,
|
required: false,
|
||||||
description: 'Change run-time configuration parameters with SET command',
|
description: 'Change run-time configuration parameters with SET command',
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
@@ -60,7 +60,7 @@ export default defineAction({
|
|||||||
key: 'parameter',
|
key: 'parameter',
|
||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Value',
|
label: 'Value',
|
||||||
@@ -88,8 +88,6 @@ export default defineAction({
|
|||||||
.returning('*')
|
.returning('*')
|
||||||
.insert(data) as IJSONObject;
|
.insert(data) as IJSONObject;
|
||||||
|
|
||||||
client.destroy();
|
|
||||||
|
|
||||||
$.setActionItem({ raw: response[0] as IJSONObject });
|
$.setActionItem({ raw: response[0] as IJSONObject });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@@ -27,7 +27,7 @@ export default defineAction({
|
|||||||
key: 'parameter',
|
key: 'parameter',
|
||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Value',
|
label: 'Value',
|
||||||
@@ -46,7 +46,6 @@ export default defineAction({
|
|||||||
|
|
||||||
const queryStatemnt = $.step.parameters.queryStatement;
|
const queryStatemnt = $.step.parameters.queryStatement;
|
||||||
const { rows } = await client.raw(queryStatemnt);
|
const { rows } = await client.raw(queryStatemnt);
|
||||||
client.destroy();
|
|
||||||
|
|
||||||
$.setActionItem({
|
$.setActionItem({
|
||||||
raw: {
|
raw: {
|
||||||
|
@@ -19,14 +19,14 @@ export default defineAction({
|
|||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
value: 'public',
|
value: 'public',
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Table name',
|
label: 'Table name',
|
||||||
key: 'table',
|
key: 'table',
|
||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Where clause entries',
|
label: 'Where clause entries',
|
||||||
@@ -39,14 +39,14 @@ export default defineAction({
|
|||||||
key: 'columnName',
|
key: 'columnName',
|
||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Operator',
|
label: 'Operator',
|
||||||
key: 'operator',
|
key: 'operator',
|
||||||
type: 'dropdown' as const,
|
type: 'dropdown' as const,
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
options: whereClauseOperators
|
options: whereClauseOperators
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -70,7 +70,7 @@ export default defineAction({
|
|||||||
key: 'columnName',
|
key: 'columnName',
|
||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Value',
|
label: 'Value',
|
||||||
@@ -93,7 +93,7 @@ export default defineAction({
|
|||||||
key: 'parameter',
|
key: 'parameter',
|
||||||
type: 'string' as const,
|
type: 'string' as const,
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Value',
|
label: 'Value',
|
||||||
@@ -132,8 +132,6 @@ export default defineAction({
|
|||||||
})
|
})
|
||||||
.update(data) as IJSONArray;
|
.update(data) as IJSONArray;
|
||||||
|
|
||||||
client.destroy();
|
|
||||||
|
|
||||||
$.setActionItem({
|
$.setActionItem({
|
||||||
raw: {
|
raw: {
|
||||||
rows: response
|
rows: response
|
||||||
|
@@ -5,7 +5,6 @@ import getClient from '../common/postgres-client';
|
|||||||
const verifyCredentials = async ($: IGlobalVariable) => {
|
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||||
const client = getClient($);
|
const client = getClient($);
|
||||||
const checkConnection = await client.raw('SELECT 1');
|
const checkConnection = await client.raw('SELECT 1');
|
||||||
client.destroy();
|
|
||||||
|
|
||||||
logger.debug(checkConnection);
|
logger.debug(checkConnection);
|
||||||
|
|
||||||
|
@@ -10,7 +10,6 @@ export default defineAction({
|
|||||||
key: 'object',
|
key: 'object',
|
||||||
type: 'dropdown' as const,
|
type: 'dropdown' as const,
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
|
||||||
description: 'Pick which type of object you want to search for.',
|
description: 'Pick which type of object you want to search for.',
|
||||||
source: {
|
source: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
@@ -29,7 +28,7 @@ export default defineAction({
|
|||||||
type: 'dropdown' as const,
|
type: 'dropdown' as const,
|
||||||
description: 'Pick which field to search by',
|
description: 'Pick which field to search by',
|
||||||
required: true,
|
required: true,
|
||||||
variables: true,
|
variables: false,
|
||||||
dependsOn: ['parameters.object'],
|
dependsOn: ['parameters.object'],
|
||||||
source: {
|
source: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
|
@@ -23,7 +23,7 @@ export default defineAction({
|
|||||||
'Sort messages by their match strength or by their date. Default is score.',
|
'Sort messages by their match strength or by their date. Default is score.',
|
||||||
required: true,
|
required: true,
|
||||||
value: 'score',
|
value: 'score',
|
||||||
variables: true,
|
variables: false,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: 'Match strength',
|
label: 'Match strength',
|
||||||
@@ -43,7 +43,7 @@ export default defineAction({
|
|||||||
'Sort matching messages in ascending or descending order. Default is descending.',
|
'Sort matching messages in ascending or descending order. Default is descending.',
|
||||||
required: true,
|
required: true,
|
||||||
value: 'desc',
|
value: 'desc',
|
||||||
variables: true,
|
variables: false,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: 'Descending (newest or best match first)',
|
label: 'Descending (newest or best match first)',
|
||||||
|
@@ -12,7 +12,7 @@ export default defineAction({
|
|||||||
type: 'dropdown' as const,
|
type: 'dropdown' as const,
|
||||||
required: true,
|
required: true,
|
||||||
description: 'Pick a user to send the message to.',
|
description: 'Pick a user to send the message to.',
|
||||||
variables: true,
|
variables: false,
|
||||||
source: {
|
source: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
name: 'getDynamicData',
|
name: 'getDynamicData',
|
||||||
@@ -40,7 +40,7 @@ export default defineAction({
|
|||||||
value: false,
|
value: false,
|
||||||
description:
|
description:
|
||||||
'If you choose no, this message will appear to come from you. Direct messages are always sent by bots.',
|
'If you choose no, this message will appear to come from you. Direct messages are always sent by bots.',
|
||||||
variables: true,
|
variables: false,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: 'Yes',
|
label: 'Yes',
|
||||||
|
@@ -12,7 +12,7 @@ export default defineAction({
|
|||||||
type: 'dropdown' as const,
|
type: 'dropdown' as const,
|
||||||
required: true,
|
required: true,
|
||||||
description: 'Pick a channel to send the message to.',
|
description: 'Pick a channel to send the message to.',
|
||||||
variables: true,
|
variables: false,
|
||||||
source: {
|
source: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
name: 'getDynamicData',
|
name: 'getDynamicData',
|
||||||
@@ -40,7 +40,7 @@ export default defineAction({
|
|||||||
value: false,
|
value: false,
|
||||||
description:
|
description:
|
||||||
'If you choose no, this message will appear to come from you. Direct messages are always sent by bots.',
|
'If you choose no, this message will appear to come from you. Direct messages are always sent by bots.',
|
||||||
variables: true,
|
variables: false,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: 'Yes',
|
label: 'Yes',
|
||||||
|
@@ -29,7 +29,7 @@ export default defineAction({
|
|||||||
required: false,
|
required: false,
|
||||||
value: false,
|
value: false,
|
||||||
description: 'Sends the message silently. Users will receive a notification with no sound.',
|
description: 'Sends the message silently. Users will receive a notification with no sound.',
|
||||||
variables: true,
|
variables: false,
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
label: 'Yes',
|
label: 'Yes',
|
||||||
|
@@ -10,7 +10,7 @@ export default defineAction({
|
|||||||
key: 'projectId',
|
key: 'projectId',
|
||||||
type: 'dropdown' as const,
|
type: 'dropdown' as const,
|
||||||
required: false,
|
required: false,
|
||||||
variables: true,
|
variables: false,
|
||||||
source: {
|
source: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
name: 'getDynamicData',
|
name: 'getDynamicData',
|
||||||
@@ -27,7 +27,7 @@ export default defineAction({
|
|||||||
key: 'sectionId',
|
key: 'sectionId',
|
||||||
type: 'dropdown' as const,
|
type: 'dropdown' as const,
|
||||||
required: false,
|
required: false,
|
||||||
variables: true,
|
variables: false,
|
||||||
dependsOn: ['parameters.projectId'],
|
dependsOn: ['parameters.projectId'],
|
||||||
source: {
|
source: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
|
@@ -13,7 +13,7 @@ export default defineAction({
|
|||||||
required: true,
|
required: true,
|
||||||
description:
|
description:
|
||||||
'The number to send the SMS from. Include country code. Example: 15551234567',
|
'The number to send the SMS from. Include country code. Example: 15551234567',
|
||||||
variables: true,
|
variables: false,
|
||||||
source: {
|
source: {
|
||||||
type: 'query',
|
type: 'query',
|
||||||
name: 'getDynamicData',
|
name: 'getDynamicData',
|
||||||
|
@@ -1,14 +0,0 @@
|
|||||||
import { IGlobalVariable } from "@automatisch/types";
|
|
||||||
|
|
||||||
type Response = {
|
|
||||||
sid: string;
|
|
||||||
phone_number: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function getIncomingPhoneNumber($: IGlobalVariable) {
|
|
||||||
const phoneNumberSid = $.step.parameters.phoneNumberSid as string;
|
|
||||||
const path = `/2010-04-01/Accounts/${$.auth.data.accountSid}/IncomingPhoneNumbers/${phoneNumberSid}.json`;
|
|
||||||
const response = await $.http.get<Response>(path);
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
};
|
|
@@ -1,30 +1,17 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
||||||
import getIncomingPhoneNumber from '../../common/get-incoming-phone-number';
|
|
||||||
|
|
||||||
const fetchMessages = async ($: IGlobalVariable) => {
|
const fetchMessages = async ($: IGlobalVariable) => {
|
||||||
const incomingPhoneNumber = await getIncomingPhoneNumber($);
|
const toNumber = $.step.parameters.toNumber as string;
|
||||||
|
|
||||||
let response;
|
let response;
|
||||||
let requestPath = `/2010-04-01/Accounts/${$.auth.data.accountSid}/Messages.json?To=${incomingPhoneNumber.phone_number}`;
|
let requestPath = `/2010-04-01/Accounts/${$.auth.data.accountSid}/Messages.json?To=${toNumber}`;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
response = await $.http.get(requestPath);
|
response = await $.http.get(requestPath);
|
||||||
|
|
||||||
response.data.messages.forEach((message: IJSONObject) => {
|
response.data.messages.forEach((message: IJSONObject) => {
|
||||||
const computedMessage = {
|
|
||||||
To: message.to,
|
|
||||||
Body: message.body,
|
|
||||||
From: message.from,
|
|
||||||
SmsSid: message.sid,
|
|
||||||
NumMedia: message.num_media,
|
|
||||||
SmsStatus: message.status,
|
|
||||||
AccountSid: message.account_sid,
|
|
||||||
ApiVersion: message.api_version,
|
|
||||||
NumSegments: message.num_segments,
|
|
||||||
};
|
|
||||||
|
|
||||||
const dataItem = {
|
const dataItem = {
|
||||||
raw: computedMessage,
|
raw: message,
|
||||||
meta: {
|
meta: {
|
||||||
internalId: message.date_sent as string,
|
internalId: message.date_sent as string,
|
||||||
},
|
},
|
||||||
|
@@ -34,9 +34,6 @@ export default defineTrigger({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
useSingletonWebhook: true,
|
|
||||||
singletonWebhookRefValueParameter: 'phoneNumberSid',
|
|
||||||
|
|
||||||
async testRun($) {
|
async testRun($) {
|
||||||
await fetchMessages($);
|
await fetchMessages($);
|
||||||
|
|
||||||
|
@@ -5,7 +5,6 @@ export default defineTrigger({
|
|||||||
name: 'Catch raw webhook',
|
name: 'Catch raw webhook',
|
||||||
key: 'catchRawWebhook',
|
key: 'catchRawWebhook',
|
||||||
type: 'webhook',
|
type: 'webhook',
|
||||||
showWebhookUrl: true,
|
|
||||||
description: 'Triggers when the webhook receives a request.',
|
description: 'Triggers when the webhook receives a request.',
|
||||||
|
|
||||||
async testRun($) {
|
async testRun($) {
|
||||||
|
@@ -1,40 +0,0 @@
|
|||||||
import path from 'node:path';
|
|
||||||
import { Response } from 'express';
|
|
||||||
import { IRequest } from '@automatisch/types';
|
|
||||||
|
|
||||||
import Connection from '../../models/connection';
|
|
||||||
import logger from '../../helpers/logger';
|
|
||||||
import handler from '../../helpers/webhook-handler';
|
|
||||||
|
|
||||||
export default async (request: IRequest, response: Response) => {
|
|
||||||
const computedRequestPayload = {
|
|
||||||
headers: request.headers,
|
|
||||||
body: request.body,
|
|
||||||
query: request.query,
|
|
||||||
params: request.params,
|
|
||||||
};
|
|
||||||
logger.debug(`Handling incoming webhook request at ${request.originalUrl}.`);
|
|
||||||
logger.debug(JSON.stringify(computedRequestPayload, null, 2));
|
|
||||||
|
|
||||||
const { connectionId } = request.params;
|
|
||||||
|
|
||||||
const connection = await Connection.query()
|
|
||||||
.findById(connectionId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
if (!await connection.verifyWebhook(request)) {
|
|
||||||
return response.sendStatus(401);
|
|
||||||
}
|
|
||||||
|
|
||||||
const triggerSteps = await connection
|
|
||||||
.$relatedQuery('triggerSteps')
|
|
||||||
.where('webhook_path', path.join(request.baseUrl, request.path));
|
|
||||||
|
|
||||||
if (triggerSteps.length === 0) return response.sendStatus(404);
|
|
||||||
|
|
||||||
for (const triggerStep of triggerSteps) {
|
|
||||||
await handler(triggerStep.flowId, request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
response.sendStatus(204);
|
|
||||||
};
|
|
@@ -1,34 +0,0 @@
|
|||||||
import { Response } from 'express';
|
|
||||||
import { IRequest } from '@automatisch/types';
|
|
||||||
|
|
||||||
import Flow from '../../models/flow';
|
|
||||||
import logger from '../../helpers/logger';
|
|
||||||
import handler from '../../helpers/webhook-handler';
|
|
||||||
|
|
||||||
export default async (request: IRequest, response: Response) => {
|
|
||||||
const computedRequestPayload = {
|
|
||||||
headers: request.headers,
|
|
||||||
body: request.body,
|
|
||||||
query: request.query,
|
|
||||||
params: request.params,
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.debug(`Handling incoming webhook request at ${request.originalUrl}.`);
|
|
||||||
logger.debug(JSON.stringify(computedRequestPayload, null, 2));
|
|
||||||
|
|
||||||
const flowId = request.params.flowId;
|
|
||||||
const flow = await Flow.query().findById(flowId).throwIfNotFound();
|
|
||||||
const triggerStep = await flow.getTriggerStep();
|
|
||||||
|
|
||||||
if (triggerStep.appKey !== 'webhook') {
|
|
||||||
const connection = await triggerStep.$relatedQuery('connection');
|
|
||||||
|
|
||||||
if (!(await connection.verifyWebhook(request))) {
|
|
||||||
return response.sendStatus(401);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await handler(flowId, request, response);
|
|
||||||
|
|
||||||
response.sendStatus(204);
|
|
||||||
};
|
|
@@ -2,23 +2,28 @@ import Crypto from 'node:crypto';
|
|||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { IRequest, ITriggerItem } from '@automatisch/types';
|
import { IRequest, ITriggerItem } from '@automatisch/types';
|
||||||
|
|
||||||
import Flow from '../models/flow';
|
import logger from '../../helpers/logger';
|
||||||
import { processTrigger } from '../services/trigger';
|
import Flow from '../../models/flow';
|
||||||
import actionQueue from '../queues/action';
|
import { processTrigger } from '../../services/trigger';
|
||||||
import globalVariable from './global-variable';
|
import actionQueue from '../../queues/action';
|
||||||
import QuotaExceededError from '../errors/quote-exceeded';
|
import globalVariable from '../../helpers/global-variable';
|
||||||
|
import QuotaExceededError from '../../errors/quote-exceeded';
|
||||||
import {
|
import {
|
||||||
REMOVE_AFTER_30_DAYS_OR_150_JOBS,
|
REMOVE_AFTER_30_DAYS_OR_150_JOBS,
|
||||||
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
|
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
|
||||||
} from './remove-job-configuration';
|
} from '../../helpers/remove-job-configuration';
|
||||||
|
|
||||||
|
export default async (request: IRequest, response: Response) => {
|
||||||
|
const flowId = request.params.flowId;
|
||||||
|
|
||||||
export default async (flowId: string, request: IRequest, response: Response) => {
|
|
||||||
// in case it's our built-in generic webhook trigger
|
// in case it's our built-in generic webhook trigger
|
||||||
let computedRequestPayload = {
|
let computedRequestPayload = {
|
||||||
headers: request.headers,
|
headers: request.headers,
|
||||||
body: request.body,
|
body: request.body,
|
||||||
query: request.query,
|
query: request.query,
|
||||||
};
|
};
|
||||||
|
logger.debug(`Handling incoming webhook request at ${request.originalUrl}.`);
|
||||||
|
logger.debug(JSON.stringify(computedRequestPayload, null, 2));
|
||||||
|
|
||||||
const flow = await Flow.query()
|
const flow = await Flow.query()
|
||||||
.findById(flowId)
|
.findById(flowId)
|
||||||
@@ -34,11 +39,32 @@ export default async (flowId: string, request: IRequest, response: Response) =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
const triggerStep = await flow.getTriggerStep();
|
const triggerStep = await flow.getTriggerStep();
|
||||||
|
const triggerCommand = await triggerStep.getTriggerCommand();
|
||||||
const app = await triggerStep.getApp();
|
const app = await triggerStep.getApp();
|
||||||
const isWebhookApp = app.key === 'webhook';
|
const isWebhookApp = app.key === 'webhook';
|
||||||
|
|
||||||
if ((testRun && !isWebhookApp)) {
|
if (testRun && !isWebhookApp) {
|
||||||
return response.status(404);
|
return response.sendStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (triggerCommand.type !== 'webhook') {
|
||||||
|
return response.sendStatus(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.auth?.verifyWebhook) {
|
||||||
|
const $ = await globalVariable({
|
||||||
|
flow,
|
||||||
|
connection: await triggerStep.$relatedQuery('connection'),
|
||||||
|
app,
|
||||||
|
step: triggerStep,
|
||||||
|
request,
|
||||||
|
});
|
||||||
|
|
||||||
|
const verified = await app.auth.verifyWebhook($);
|
||||||
|
|
||||||
|
if (!verified) {
|
||||||
|
return response.sendStatus(401);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// in case trigger type is 'webhook'
|
// in case trigger type is 'webhook'
|
||||||
@@ -61,7 +87,7 @@ export default async (flowId: string, request: IRequest, response: Response) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (testRun) {
|
if (testRun) {
|
||||||
return response.status(204);
|
return response.sendStatus(204);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextStep = await triggerStep.getNextStep();
|
const nextStep = await triggerStep.getNextStep();
|
||||||
@@ -80,5 +106,5 @@ export default async (flowId: string, request: IRequest, response: Response) =>
|
|||||||
|
|
||||||
await actionQueue.add(jobName, jobPayload, jobOptions);
|
await actionQueue.add(jobName, jobPayload, jobOptions);
|
||||||
|
|
||||||
return response.status(204);
|
return response.sendStatus(204);
|
||||||
};
|
};
|
@@ -1,13 +0,0 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
return knex.schema.table('steps', (table) => {
|
|
||||||
table.string('webhook_path');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
return knex.schema.table('steps', (table) => {
|
|
||||||
table.dropColumn('webhook_path');
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,16 +0,0 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
return await knex('steps')
|
|
||||||
.where('type', 'trigger')
|
|
||||||
.whereIn('app_key', ['gitlab', 'typeform', 'twilio', 'flowers-software', 'webhook'])
|
|
||||||
.update({
|
|
||||||
webhook_path: knex.raw('? || ??', ['/webhooks/flows/', knex.ref('flow_id')]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
return await knex('steps').update({
|
|
||||||
webhook_path: null
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,32 +0,0 @@
|
|||||||
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');
|
|
||||||
}
|
|
@@ -1,25 +0,0 @@
|
|||||||
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');
|
|
||||||
}
|
|
@@ -1,25 +0,0 @@
|
|||||||
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');
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
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');
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,13 +0,0 @@
|
|||||||
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');
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,23 +0,0 @@
|
|||||||
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');
|
|
||||||
}
|
|
@@ -1,17 +0,0 @@
|
|||||||
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');
|
|
||||||
}
|
|
@@ -1,11 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@@ -1,13 +0,0 @@
|
|||||||
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,59 +1,47 @@
|
|||||||
import createConnection from './mutations/create-connection';
|
import createConnection from './mutations/create-connection';
|
||||||
import createFlow from './mutations/create-flow';
|
|
||||||
import createRole from './mutations/create-role.ee';
|
|
||||||
import createStep from './mutations/create-step';
|
|
||||||
import createUser from './mutations/create-user.ee';
|
|
||||||
import deleteConnection from './mutations/delete-connection';
|
|
||||||
import deleteCurrentUser from './mutations/delete-current-user.ee';
|
|
||||||
import deleteFlow from './mutations/delete-flow';
|
|
||||||
import deleteRole from './mutations/delete-role.ee';
|
|
||||||
import deleteStep from './mutations/delete-step';
|
|
||||||
import deleteUser from './mutations/delete-user.ee';
|
|
||||||
import duplicateFlow from './mutations/duplicate-flow';
|
|
||||||
import executeFlow from './mutations/execute-flow';
|
|
||||||
import forgotPassword from './mutations/forgot-password.ee';
|
|
||||||
import generateAuthUrl from './mutations/generate-auth-url';
|
import 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 updateConnection from './mutations/update-connection';
|
||||||
import updateCurrentUser from './mutations/update-current-user';
|
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 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 updateRole from './mutations/update-role.ee';
|
import executeFlow from './mutations/execute-flow';
|
||||||
|
import deleteFlow from './mutations/delete-flow';
|
||||||
|
import duplicateFlow from './mutations/duplicate-flow';
|
||||||
|
import createStep from './mutations/create-step';
|
||||||
import updateStep from './mutations/update-step';
|
import updateStep from './mutations/update-step';
|
||||||
import updateUser from './mutations/update-user.ee';
|
import deleteStep from './mutations/delete-step';
|
||||||
import verifyConnection from './mutations/verify-connection';
|
import createUser from './mutations/create-user.ee';
|
||||||
|
import deleteUser from './mutations/delete-user.ee';
|
||||||
|
import updateUser from './mutations/update-user';
|
||||||
|
import forgotPassword from './mutations/forgot-password.ee';
|
||||||
|
import resetPassword from './mutations/reset-password.ee';
|
||||||
|
import login from './mutations/login';
|
||||||
|
|
||||||
const mutationResolvers = {
|
const mutationResolvers = {
|
||||||
createConnection,
|
createConnection,
|
||||||
createFlow,
|
|
||||||
createRole,
|
|
||||||
createStep,
|
|
||||||
createUser,
|
|
||||||
deleteConnection,
|
|
||||||
deleteCurrentUser,
|
|
||||||
deleteFlow,
|
|
||||||
deleteRole,
|
|
||||||
deleteStep,
|
|
||||||
deleteUser,
|
|
||||||
duplicateFlow,
|
|
||||||
executeFlow,
|
|
||||||
forgotPassword,
|
|
||||||
generateAuthUrl,
|
generateAuthUrl,
|
||||||
login,
|
|
||||||
registerUser,
|
|
||||||
resetConnection,
|
|
||||||
resetPassword,
|
|
||||||
updateConnection,
|
updateConnection,
|
||||||
updateCurrentUser,
|
resetConnection,
|
||||||
updateUser,
|
verifyConnection,
|
||||||
|
deleteConnection,
|
||||||
|
createFlow,
|
||||||
updateFlow,
|
updateFlow,
|
||||||
updateFlowStatus,
|
updateFlowStatus,
|
||||||
updateRole,
|
executeFlow,
|
||||||
|
deleteFlow,
|
||||||
|
duplicateFlow,
|
||||||
|
createStep,
|
||||||
updateStep,
|
updateStep,
|
||||||
verifyConnection,
|
deleteStep,
|
||||||
|
createUser,
|
||||||
|
deleteUser,
|
||||||
|
updateUser,
|
||||||
|
forgotPassword,
|
||||||
|
resetPassword,
|
||||||
|
login,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default mutationResolvers;
|
export default mutationResolvers;
|
||||||
|
@@ -13,8 +13,6 @@ 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,8 +14,6 @@ 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;
|
||||||
|
|
||||||
|
@@ -1,32 +0,0 @@
|
|||||||
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,8 +22,6 @@ 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,13 +5,11 @@ 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, roleId } = params.input;
|
const { fullName, email, password } = params.input;
|
||||||
|
|
||||||
const existingUser = await User.query().findOne({ email });
|
const existingUser = await User.query().findOne({ email });
|
||||||
|
|
||||||
@@ -23,7 +21,7 @@ const createUser = async (_parent: unknown, params: Params) => {
|
|||||||
fullName,
|
fullName,
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
roleId,
|
role: 'user',
|
||||||
});
|
});
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
|
@@ -11,8 +11,6 @@ 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()
|
||||||
|
@@ -1,23 +0,0 @@
|
|||||||
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;
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user