Compare commits

..

3 Commits

Author SHA1 Message Date
Faruk AYDIN
6fcd223496 fix: Do not use context explicitly for docs-change file 2024-03-19 17:47:26 +01:00
Faruk AYDIN
baddf4653c fix: don't import github explicitly for github script 2024-03-19 17:44:11 +01:00
Faruk AYDIN
d1e01d673a docs: Test whether docs-change workflow works or not 2024-03-19 17:39:27 +01:00
256 changed files with 2589 additions and 4723 deletions

View File

@@ -1,204 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Create draft',
key: 'createDraft',
description: 'Create a new draft email message.',
arguments: [
{
label: 'Subject',
key: 'subject',
type: 'string',
required: true,
description: '',
variables: true,
},
{
label: 'TOs',
key: 'tos',
type: 'dynamic',
required: false,
description: '',
fields: [
{
label: 'To',
key: 'to',
type: 'string',
required: false,
variables: true,
},
],
},
{
label: 'CCs',
key: 'ccs',
type: 'dynamic',
required: false,
description: '',
fields: [
{
label: 'CC',
key: 'cc',
type: 'string',
required: false,
variables: true,
},
],
},
{
label: 'BCCs',
key: 'bccs',
type: 'dynamic',
required: false,
description: '',
fields: [
{
label: 'BCC',
key: 'bcc',
type: 'string',
required: false,
variables: true,
},
],
},
{
label: 'From',
key: 'from',
type: 'dropdown',
required: false,
description:
'Select an email address or alias from your Gmail Account. Defaults to the primary email address.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listEmails',
},
],
},
},
{
label: 'From Name',
key: 'fromName',
type: 'string',
required: false,
description: '',
variables: true,
},
{
label: 'Body Type',
key: 'bodyType',
type: 'dropdown',
required: false,
description: '',
variables: true,
options: [
{
label: 'plain',
value: 'plain',
},
{
label: 'html',
value: 'html',
},
],
},
{
label: 'Body',
key: 'emailBody',
type: 'string',
required: true,
description: '',
variables: true,
},
{
label: 'Signature',
key: 'signature',
type: 'dropdown',
required: false,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listSignatures',
},
],
},
},
],
async run($) {
const {
tos,
ccs,
bccs,
from,
fromName,
subject,
bodyType,
emailBody,
signature,
} = $.step.parameters;
const userId = $.auth.data.userId;
const allTos = tos?.map((entry) => entry.to);
const allCcs = ccs?.map((entry) => entry.cc);
const allBccs = bccs?.map((entry) => entry.bcc);
const contentType =
bodyType === 'html'
? 'text/html; charset="UTF-8"'
: 'text/plain; charset="UTF-8"';
const email =
'From: ' +
fromName +
' <' +
from +
'>' +
'\r\n' +
'To: ' +
allTos.join(',') +
'\r\n' +
'Cc: ' +
allCcs.join(',') +
'\r\n' +
'Bcc: ' +
allBccs.join(',') +
'\r\n' +
'Subject: ' +
subject +
'\r\n' +
'Content-Type: ' +
contentType +
'\r\n' +
'\r\n' +
emailBody +
'\r\n' +
'\r\n' +
signature;
const base64EncodedEmailBody = Buffer.from(email).toString('base64');
const body = {
message: {
raw: base64EncodedEmailBody,
},
};
const { data } = await $.http.post(
`/gmail/v1/users/${userId}/drafts`,
body
);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -1,7 +0,0 @@
import createDraft from './create-draft/index.js';
import replyToEmail from './reply-to-email/index.js';
import sendEmail from './send-email/index.js';
import sendToTrash from './send-to-trash/index.js';
import starEmail from './star-email/index.js';
export default [createDraft, replyToEmail, sendEmail, sendToTrash, starEmail];

View File

@@ -1,228 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Reply to email',
key: 'replyToEmail',
description: 'Respond to an email.',
arguments: [
{
label: 'Thread',
key: 'threadId',
type: 'dropdown',
required: false,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listThreads',
},
],
},
},
{
label: 'TOs',
key: 'tos',
type: 'dynamic',
required: false,
description: 'Who will receive this email?',
fields: [
{
label: 'To',
key: 'to',
type: 'string',
required: false,
variables: true,
},
],
},
{
label: 'CCs',
key: 'ccs',
type: 'dynamic',
required: false,
description:
'Who else needs to be included in the CC field of this email?',
fields: [
{
label: 'CC',
key: 'cc',
type: 'string',
required: false,
variables: true,
},
],
},
{
label: 'BCCs',
key: 'bccs',
type: 'dynamic',
required: false,
description:
'Who else needs to be included in the BCC field of this email?',
fields: [
{
label: 'BCC',
key: 'bcc',
type: 'string',
required: false,
variables: true,
},
],
},
{
label: 'From',
key: 'from',
type: 'dropdown',
required: false,
description:
'Choose an email address or alias from your Gmail Account. This defaults to the primary email address.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listEmails',
},
],
},
},
{
label: 'From Name',
key: 'fromName',
type: 'string',
required: false,
description: '',
variables: true,
},
{
label: 'Reply To',
key: 'replyTo',
type: 'string',
required: false,
description: 'Specify a single reply address other than your own.',
variables: true,
},
{
label: 'Body Type',
key: 'bodyType',
type: 'dropdown',
required: false,
description: '',
variables: true,
options: [
{
label: 'plain',
value: 'plain',
},
{
label: 'html',
value: 'html',
},
],
},
{
label: 'Body',
key: 'emailBody',
type: 'string',
required: true,
description: '',
variables: true,
},
{
label: 'Label',
key: 'labelId',
type: 'dropdown',
required: false,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listLabels',
},
],
},
},
],
async run($) {
const {
tos,
ccs,
bccs,
from,
fromName,
replyTo,
threadId,
bodyType,
emailBody,
labelId,
} = $.step.parameters;
const userId = $.auth.data.userId;
const allTos = tos?.map((entry) => entry.to);
const allCcs = ccs?.map((entry) => entry.cc);
const allBccs = bccs?.map((entry) => entry.bcc);
const contentType =
bodyType === 'html'
? 'text/html; charset="UTF-8"'
: 'text/plain; charset="UTF-8"';
const email =
'From: ' +
fromName +
' <' +
from +
'>' +
'\r\n' +
'In-Reply-To: ' +
threadId +
'\r\n' +
'References: ' +
threadId +
'\r\n' +
'Reply-To: ' +
replyTo +
'\r\n' +
'To: ' +
allTos.join(',') +
'\r\n' +
'Cc: ' +
allCcs.join(',') +
'\r\n' +
'Bcc: ' +
allBccs.join(',') +
'\r\n' +
'Content-Type: ' +
contentType +
'\r\n' +
'\r\n' +
emailBody;
const base64EncodedEmailBody = Buffer.from(email).toString('base64');
const body = {
threadId: threadId,
labelIds: [labelId],
raw: base64EncodedEmailBody,
};
const { data } = await $.http.post(
`/gmail/v1/users/${userId}/messages/send`,
body
);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -1,234 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Send email',
key: 'sendEmail',
description: 'Send a new email message.',
arguments: [
{
label: 'TOs',
key: 'tos',
type: 'dynamic',
required: false,
description: '',
fields: [
{
label: 'To',
key: 'to',
type: 'string',
required: false,
variables: true,
},
],
},
{
label: 'CCs',
key: 'ccs',
type: 'dynamic',
required: false,
description: '',
fields: [
{
label: 'CC',
key: 'cc',
type: 'string',
required: false,
variables: true,
},
],
},
{
label: 'BCCs',
key: 'bccs',
type: 'dynamic',
required: false,
description: '',
fields: [
{
label: 'BCC',
key: 'bcc',
type: 'string',
required: false,
variables: true,
},
],
},
{
label: 'From',
key: 'from',
type: 'dropdown',
required: false,
description:
'Select an email address or alias from your Gmail Account. Defaults to the primary email address.',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listEmails',
},
],
},
},
{
label: 'From Name',
key: 'fromName',
type: 'string',
required: false,
description: '',
variables: true,
},
{
label: 'Reply To',
key: 'replyTo',
type: 'string',
required: false,
description: 'Specify a single reply address other than your own.',
variables: true,
},
{
label: 'Subject',
key: 'subject',
type: 'string',
required: true,
description: '',
variables: true,
},
{
label: 'Body Type',
key: 'bodyType',
type: 'dropdown',
required: false,
description: '',
variables: true,
options: [
{
label: 'plain',
value: 'plain',
},
{
label: 'html',
value: 'html',
},
],
},
{
label: 'Body',
key: 'emailBody',
type: 'string',
required: true,
description: '',
variables: true,
},
{
label: 'Signature',
key: 'signature',
type: 'dropdown',
required: false,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listSignatures',
},
],
},
},
{
label: 'Label',
key: 'labelId',
type: 'dropdown',
required: false,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listLabels',
},
],
},
},
],
async run($) {
const {
tos,
ccs,
bccs,
from,
fromName,
replyTo,
subject,
bodyType,
emailBody,
signature,
labelId,
} = $.step.parameters;
const userId = $.auth.data.userId;
const allTos = tos?.map((entry) => entry.to);
const allCcs = ccs?.map((entry) => entry.cc);
const allBccs = bccs?.map((entry) => entry.bcc);
const contentType =
bodyType === 'html'
? 'text/html; charset="UTF-8"'
: 'text/plain; charset="UTF-8"';
const email =
'From: ' +
fromName +
' <' +
from +
'>' +
'\r\n' +
'Reply-To: ' +
replyTo +
'\r\n' +
'To: ' +
allTos.join(',') +
'\r\n' +
'Cc: ' +
allCcs.join(',') +
'\r\n' +
'Bcc: ' +
allBccs.join(',') +
'\r\n' +
'Subject: ' +
subject +
'\r\n' +
'Content-Type: ' +
contentType +
'\r\n' +
'\r\n' +
emailBody +
'\r\n' +
'\r\n' +
signature;
const base64EncodedEmailBody = Buffer.from(email).toString('base64');
const body = {
labelIds: [labelId],
raw: base64EncodedEmailBody,
};
const { data } = await $.http.post(
`/gmail/v1/users/${userId}/messages/send`,
body
);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -1,30 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Send to trash',
key: 'sendToTrash',
description: 'Send an existing email message to the trash.',
arguments: [
{
label: 'Message ID',
key: 'messageId',
type: 'string',
required: true,
description: '',
variables: true,
},
],
async run($) {
const { messageId } = $.step.parameters;
const userId = $.auth.data.userId;
const { data } = await $.http.post(
`/gmail/v1/users/${userId}/messages/${messageId}/trash`
);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -1,45 +0,0 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Star an email',
key: 'starEmail',
description: 'Star an email message.',
arguments: [
{
label: 'Message ID',
key: 'messageId',
type: 'dropdown',
required: true,
description: '',
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listMessages',
},
],
},
},
],
async run($) {
const { messageId } = $.step.parameters;
const userId = $.auth.data.userId;
const body = {
addLabelIds: ['STARRED'],
};
const { data } = await $.http.post(
`/gmail/v1/users/${userId}/messages/${messageId}/modify`,
body
);
$.setActionItem({
raw: data,
});
},
});

View File

@@ -1,11 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 49.4 512 399.42">
<g fill="none" fill-rule="evenodd">
<g fill-rule="nonzero">
<path fill="#4285f4" d="M34.91 448.818h81.454V251L0 163.727V413.91c0 19.287 15.622 34.91 34.91 34.91z"/>
<path fill="#34a853" d="M395.636 448.818h81.455c19.287 0 34.909-15.622 34.909-34.909V163.727L395.636 251z"/>
<path fill="#fbbc04" d="M395.636 99.727V251L512 163.727v-46.545c0-43.142-49.25-67.782-83.782-41.891z"/>
</g>
<path fill="#ea4335" d="M116.364 251V99.727L256 204.455 395.636 99.727V251L256 355.727z"/>
<path fill="#c5221f" fill-rule="nonzero" d="M0 117.182v46.545L116.364 251V99.727L83.782 75.291C49.25 49.4 0 74.04 0 117.18z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 720 B

View File

@@ -1,23 +0,0 @@
import { URLSearchParams } from 'url';
import authScope from '../common/auth-scope.js';
export default async function generateAuthUrl($) {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const searchParams = new URLSearchParams({
client_id: $.auth.data.clientId,
redirect_uri: redirectUri,
prompt: 'select_account',
scope: authScope.join(' '),
response_type: 'code',
access_type: 'offline',
});
const url = `https://accounts.google.com/o/oauth2/v2/auth?${searchParams.toString()}`;
await $.auth.set({
url,
});
}

View File

@@ -1,48 +0,0 @@
import generateAuthUrl from './generate-auth-url.js';
import verifyCredentials from './verify-credentials.js';
import refreshToken from './refresh-token.js';
import isStillVerified from './is-still-verified.js';
export default {
fields: [
{
key: 'oAuthRedirectUrl',
label: 'OAuth Redirect URL',
type: 'string',
required: true,
readOnly: true,
value: '{WEB_APP_URL}/app/gmail/connections/add',
placeholder: null,
description:
'When asked to input a redirect URL in Google Cloud, enter the URL above.',
clickToCopy: true,
},
{
key: 'clientId',
label: 'Client ID',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
{
key: 'clientSecret',
label: 'Client Secret',
type: 'string',
required: true,
readOnly: false,
value: null,
placeholder: null,
description: null,
clickToCopy: false,
},
],
generateAuthUrl,
verifyCredentials,
isStillVerified,
refreshToken,
};

View File

@@ -1,8 +0,0 @@
import getCurrentUser from '../common/get-current-user.js';
const isStillVerified = async ($) => {
const currentUser = await getCurrentUser($);
return !!currentUser.resourceName;
};
export default isStillVerified;

View File

@@ -1,26 +0,0 @@
import { URLSearchParams } from 'node:url';
import authScope from '../common/auth-scope.js';
const refreshToken = async ($) => {
const params = new URLSearchParams({
client_id: $.auth.data.clientId,
client_secret: $.auth.data.clientSecret,
grant_type: 'refresh_token',
refresh_token: $.auth.data.refreshToken,
});
const { data } = await $.http.post(
'https://oauth2.googleapis.com/token',
params.toString()
);
await $.auth.set({
accessToken: data.access_token,
expiresIn: data.expires_in,
scope: authScope.join(' '),
tokenType: data.token_type,
});
};
export default refreshToken;

View File

@@ -1,43 +0,0 @@
import getCurrentUser from '../common/get-current-user.js';
const verifyCredentials = async ($) => {
const oauthRedirectUrlField = $.app.auth.fields.find(
(field) => field.key == 'oAuthRedirectUrl'
);
const redirectUri = oauthRedirectUrlField.value;
const { data } = await $.http.post(`https://oauth2.googleapis.com/token`, {
client_id: $.auth.data.clientId,
client_secret: $.auth.data.clientSecret,
code: $.auth.data.code,
grant_type: 'authorization_code',
redirect_uri: redirectUri,
});
await $.auth.set({
accessToken: data.access_token,
tokenType: data.token_type,
});
const currentUser = await getCurrentUser($);
const { displayName } = currentUser.names.find(
(name) => name.metadata.primary
);
const { value: email } = currentUser.emailAddresses.find(
(emailAddress) => emailAddress.metadata.primary
);
await $.auth.set({
clientId: $.auth.data.clientId,
clientSecret: $.auth.data.clientSecret,
scope: $.auth.data.scope,
idToken: data.id_token,
expiresIn: data.expires_in,
refreshToken: data.refresh_token,
resourceName: currentUser.resourceName,
screenName: `${displayName} - ${email}`,
userId: email,
});
};
export default verifyCredentials;

View File

@@ -1,9 +0,0 @@
const addAuthHeader = ($, requestConfig) => {
if ($.auth.data?.accessToken) {
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
}
return requestConfig;
};
export default addAuthHeader;

View File

@@ -1,8 +0,0 @@
const authScope = [
'https://www.googleapis.com/auth/gmail.compose',
'https://www.googleapis.com/auth/gmail.modify',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/userinfo.profile',
];
export default authScope;

View File

@@ -1,8 +0,0 @@
const getCurrentUser = async ($) => {
const { data: currentUser } = await $.http.get(
'https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses'
);
return currentUser;
};
export default getCurrentUser;

View File

@@ -1,13 +0,0 @@
import listEmails from './list-emails/index.js';
import listLabels from './list-labels/index.js';
import listMessages from './list-messages/index.js';
import listSignatures from './list-signatures/index.js';
import listThreads from './list-threads/index.js';
export default [
listEmails,
listLabels,
listMessages,
listSignatures,
listThreads,
];

View File

@@ -1,23 +0,0 @@
import getCurrentUser from '../../common/get-current-user.js';
export default {
name: 'List emails',
key: 'listEmails',
async run($) {
const emails = {
data: [],
};
const currentUser = await getCurrentUser($);
for (const emailAddress of currentUser.emailAddresses) {
emails.data.push({
value: emailAddress.value,
name: emailAddress.value,
});
}
return emails;
},
};

View File

@@ -1,22 +0,0 @@
export default {
name: 'List labels',
key: 'listLabels',
async run($) {
const labels = {
data: [],
};
const userId = $.auth.data.userId;
const { data } = await $.http.get(`/gmail/v1/users/${userId}/labels`);
for (const label of data.labels) {
labels.data.push({
value: label.id,
name: label.name,
});
}
return labels;
},
};

View File

@@ -1,31 +0,0 @@
export default {
name: 'List messages',
key: 'listMessages',
async run($) {
const messages = {
data: [],
};
const userId = $.auth.data.userId;
const { data } = await $.http.get(`/gmail/v1/users/${userId}/messages`);
if (data.messages) {
for (const message of data.messages) {
const { data: messageData } = await $.http.get(
`/gmail/v1/users/${userId}/messages/${message.id}`
);
const subject = messageData.payload.headers.find(
(header) => header.name === 'Subject'
);
messages.data.push({
value: message.id,
name: subject?.value,
});
}
}
return messages;
},
};

View File

@@ -1,24 +0,0 @@
export default {
name: 'List signatures',
key: 'listSignatures',
async run($) {
const signatures = {
data: [],
};
const userId = $.auth.data.userId;
const { data } = await $.http.get(
`/gmail/v1/users/${userId}/settings/sendAs`
);
for (const sendAs of data.sendAs) {
signatures.data.push({
value: sendAs.signature,
name: sendAs.sendAsEmail,
});
}
return signatures;
},
};

View File

@@ -1,31 +0,0 @@
export default {
name: 'List threads',
key: 'listThreads',
async run($) {
const threads = {
data: [],
};
const userId = $.auth.data.userId;
const { data } = await $.http.get(`/gmail/v1/users/${userId}/threads`);
if (data.threads) {
for (const thread of data.threads) {
const { data: threadData } = await $.http.get(
`/gmail/v1/users/${userId}/threads/${thread.id}`
);
const subject = threadData.messages[0].payload.headers.find(
(header) => header.name === 'Subject'
);
threads.data.push({
value: thread.id,
name: subject?.value,
});
}
}
return threads;
},
};

View File

@@ -1,22 +0,0 @@
import defineApp from '../../helpers/define-app.js';
import addAuthHeader from './common/add-auth-header.js';
import auth from './auth/index.js';
import triggers from './triggers/index.js';
import dynamicData from './dynamic-data/index.js';
import actions from './actions/index.js';
export default defineApp({
name: 'Gmail',
key: 'gmail',
baseUrl: 'https://mail.google.com',
apiBaseUrl: 'https://gmail.googleapis.com',
iconUrl: '{BASE_URL}/apps/gmail/assets/favicon.svg',
authDocUrl: 'https://automatisch.io/docs/apps/gmail/connection',
primaryColor: 'ea4335',
supportsConnections: true,
beforeRequest: [addAuthHeader],
auth,
triggers,
dynamicData,
actions,
});

View File

@@ -1,3 +0,0 @@
import newEmails from './new-emails/index.js';
export default [newEmails];

View File

@@ -1,68 +0,0 @@
import defineTrigger from '../../../../helpers/define-trigger.js';
export default defineTrigger({
name: 'New emails',
key: 'newEmails',
pollInterval: 15,
description:
'Triggers when a new email is received in the specified mailbox.',
arguments: [
{
label: 'Label',
key: 'labelId',
type: 'dropdown',
required: false,
description:
"If you don't choose a label, this Zap will trigger for all emails, including Drafts.",
variables: true,
source: {
type: 'query',
name: 'getDynamicData',
arguments: [
{
name: 'key',
value: 'listLabels',
},
],
},
},
],
async run($) {
const userId = $.auth.data.userId;
const labelId = $.step.parameters.labelId;
const params = {
maxResults: 500,
pageToken: undefined,
};
if (labelId) {
params.labelIds = labelId;
}
do {
const { data } = await $.http.get(`/gmail/v1/users/${userId}/messages`, {
params,
});
params.pageToken = data.nextPageToken;
if (!data?.messages?.length) {
return;
}
for (const message of data.messages) {
const { data: messageData } = await $.http.get(
`/gmail/v1/users/${userId}/messages/${message.id}`
);
$.pushTriggerItem({
raw: messageData,
meta: {
internalId: messageData.id,
},
});
}
} while (params.pageToken);
},
});

View File

@@ -90,7 +90,7 @@ export default defineAction({
async run($) { async run($) {
const method = $.step.parameters.method; const method = $.step.parameters.method;
const data = $.step.parameters.data || null; const data = $.step.parameters.data;
const url = $.step.parameters.url; const url = $.step.parameters.url;
const headers = $.step.parameters.headers; const headers = $.step.parameters.headers;
@@ -108,17 +108,14 @@ export default defineAction({
return result; return result;
}, {}); }, {});
let expectedResponseContentType = headersObject.accept; let contentType = headersObject['content-type'];
// in case HEAD request is not supported by the URL // in case HEAD request is not supported by the URL
try { try {
const metadataResponse = await $.http.head(url, { const metadataResponse = await $.http.head(url, {
headers: headersObject, headers: headersObject,
}); });
contentType = metadataResponse.headers['content-type'];
if (!expectedResponseContentType) {
expectedResponseContentType = metadataResponse.headers['content-type'];
}
throwIfFileSizeExceedsLimit(metadataResponse.headers['content-length']); throwIfFileSizeExceedsLimit(metadataResponse.headers['content-length']);
// eslint-disable-next-line no-empty // eslint-disable-next-line no-empty
@@ -131,7 +128,7 @@ export default defineAction({
headers: headersObject, headers: headersObject,
}; };
if (!isPossiblyTextBased(expectedResponseContentType)) { if (!isPossiblyTextBased(contentType)) {
requestData.responseType = 'arraybuffer'; requestData.responseType = 'arraybuffer';
} }
@@ -141,7 +138,7 @@ export default defineAction({
let responseData = response.data; let responseData = response.data;
if (!isPossiblyTextBased(expectedResponseContentType)) { if (!isPossiblyTextBased(contentType)) {
responseData = Buffer.from(responseData).toString('base64'); responseData = Buffer.from(responseData).toString('base64');
} }

View File

@@ -64,17 +64,32 @@ export default defineAction({
value: '1', value: '1',
description: description:
'The ID of the stage this deal will be added to. If omitted, the deal will be placed in the first stage of the default pipeline.', 'The ID of the stage this deal will be added to. If omitted, the deal will be placed in the first stage of the default pipeline.',
variables: true, options: [
source: { {
type: 'query', label: 'Qualified (Pipeline)',
name: 'getDynamicData', value: 1,
arguments: [ },
{ {
name: 'key', label: 'Contact Made (Pipeline)',
value: 'listStages', value: 2,
}, },
], {
}, label: 'Prospect Qualified (Pipeline)',
value: 3,
},
{
label: 'Needs Defined (Pipeline)',
value: 4,
},
{
label: 'Proposal Made (Pipeline)',
value: 5,
},
{
label: 'Negotiations Started (Pipeline)',
value: 6,
},
],
}, },
{ {
label: 'Owner', label: 'Owner',

View File

@@ -1,25 +1,23 @@
import listActivityTypes from './list-activity-types/index.js'; import listActivityTypes from './list-activity-types/index.js';
import listCurrencies from './list-currencies/index.js'; import listCurrencies from './list-currencies/index.js';
import listDeals from './list-deals/index.js'; import listDeals from './list-deals/index.js';
import listLeadLabels from './list-lead-labels/index.js';
import listLeads from './list-leads/index.js'; import listLeads from './list-leads/index.js';
import listOrganizationLabelField from './list-organization-label-field/index.js'; import listLeadLabels from './list-lead-labels/index.js';
import listOrganizations from './list-organizations/index.js'; import listOrganizations from './list-organizations/index.js';
import listOrganizationLabelField from './list-organization-label-field/index.js';
import listPersonLabelField from './list-person-label-field/index.js'; import listPersonLabelField from './list-person-label-field/index.js';
import listPersons from './list-persons/index.js'; import listPersons from './list-persons/index.js';
import listStages from './list-stages/index.js';
import listUsers from './list-users/index.js'; import listUsers from './list-users/index.js';
export default [ export default [
listActivityTypes, listActivityTypes,
listCurrencies, listCurrencies,
listDeals, listDeals,
listLeadLabels,
listLeads, listLeads,
listOrganizationLabelField, listLeadLabels,
listOrganizations, listOrganizations,
listOrganizationLabelField,
listPersonLabelField, listPersonLabelField,
listPersons, listPersons,
listStages,
listUsers, listUsers,
]; ];

View File

@@ -1,23 +0,0 @@
export default {
name: 'List stages',
key: 'listStages',
async run($) {
const stages = {
data: [],
};
const { data } = await $.http.get('/api/v1/stages');
if (data.data?.length) {
for (const stage of data.data) {
stages.data.push({
value: stage.id,
name: stage.name,
});
}
}
return stages;
},
};

View File

@@ -14,55 +14,24 @@ export default defineAction({
value: '200', value: '200',
}, },
{ {
label: 'Headers', label: 'JSON body',
key: 'headers', key: 'stringifiedJsonBody',
type: 'dynamic',
required: false,
description: 'Add or remove headers as needed',
fields: [
{
label: 'Key',
key: 'key',
type: 'string',
required: true,
description: 'Header key',
variables: true,
},
{
label: 'Value',
key: 'value',
type: 'string',
required: true,
description: 'Header value',
variables: true,
},
],
},
{
label: 'Body',
key: 'body',
type: 'string', type: 'string',
required: true, required: true,
description: 'The content of the response body.', description: 'The content of the JSON body. It must be a valid JSON.',
variables: true, variables: true,
}, },
], ],
async run($) { async run($) {
const statusCode = parseInt($.step.parameters.statusCode, 10); const parsedStatusCode = parseInt($.step.parameters.statusCode, 10);
const body = $.step.parameters.body; const stringifiedJsonBody = $.step.parameters.stringifiedJsonBody;
const headers = $.step.parameters.headers.reduce((result, entry) => { const parsedJsonBody = JSON.parse(stringifiedJsonBody);
return {
...result,
[entry.key]: entry.value,
};
}, {});
$.setActionItem({ $.setActionItem({
raw: { raw: {
headers, body: parsedJsonBody,
body, statusCode: parsedStatusCode,
statusCode,
}, },
}); });
}, },

View File

@@ -1,13 +0,0 @@
import User from '../../../../models/user.js';
import { renderObject, renderError } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const { email, password } = request.body;
const token = await User.authenticate(email, password);
if (token) {
return renderObject(response, { token });
}
renderError(response, [{ general: ['Incorrect email or password.'] }]);
};

View File

@@ -1,39 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import { createUser } from '../../../../../test/factories/user';
describe('POST /api/v1/access-tokens', () => {
beforeEach(async () => {
await createUser({
email: 'user@automatisch.io',
password: 'password',
});
});
it('should return the token data with correct credentials', async () => {
const response = await request(app)
.post('/api/v1/access-tokens')
.send({
email: 'user@automatisch.io',
password: 'password',
})
.expect(200);
expect(response.body.data.token.length).toBeGreaterThan(0);
});
it('should return error with incorrect credentials', async () => {
const response = await request(app)
.post('/api/v1/access-tokens')
.send({
email: 'incorrect@email.com',
password: 'incorrectpassword',
})
.expect(422);
expect(response.body.errors.general).toEqual([
'Incorrect email or password.',
]);
});
});

View File

@@ -4,7 +4,6 @@ import AppAuthClient from '../../../../../models/app-auth-client.js';
export default async (request, response) => { export default async (request, response) => {
const appAuthClient = await AppAuthClient.query() const appAuthClient = await AppAuthClient.query()
.findById(request.params.appAuthClientId) .findById(request.params.appAuthClientId)
.where({ app_key: request.params.appKey })
.throwIfNotFound(); .throwIfNotFound();
renderObject(response, appAuthClient); renderObject(response, appAuthClient);

View File

@@ -0,0 +1,52 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../../test/factories/user.js';
import getAdminAppAuthClientMock from '../../../../../../test/mocks/rest/api/v1/admin/get-app-auth-client.js';
import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js';
import { createRole } from '../../../../../../test/factories/role.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/app-auth-clients/:appAuthClientId', () => {
let currentUser, currentUserRole, currentAppAuthClient, token;
describe('with valid license key', () => {
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
currentUserRole = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: currentUserRole.id });
currentAppAuthClient = await createAppAuthClient();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified app auth client info', async () => {
const response = await request(app)
.get(`/api/v1/admin/app-auth-clients/${currentAppAuthClient.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getAdminAppAuthClientMock(currentAppAuthClient);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing app auth client UUID', async () => {
const notExistingAppAuthClientUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/admin/app-auth-clients/${notExistingAppAuthClientUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await request(app)
.get('/api/v1/admin/app-auth-clients/invalidAppAuthClientUUID')
.set('Authorization', token)
.expect(400);
});
});
});

View File

@@ -1,55 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createRole } from '../../../../../../test/factories/role.js';
import getAppAuthClientMock from '../../../../../../test/mocks/rest/api/v1/admin/apps/get-auth-client.js';
import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/apps/:appKey/auth-clients/:appAuthClientId', () => {
let currentUser, adminRole, currentAppAuthClient, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
adminRole = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: adminRole.id });
currentAppAuthClient = await createAppAuthClient({
appKey: 'deepl',
});
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified app auth client', async () => {
const response = await request(app)
.get(`/api/v1/admin/apps/deepl/auth-clients/${currentAppAuthClient.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getAppAuthClientMock(currentAppAuthClient);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing app auth client ID', async () => {
const notExistingAppAuthClientUUID = Crypto.randomUUID();
await request(app)
.get(
`/api/v1/admin/apps/deepl/auth-clients/${notExistingAppAuthClientUUID}`
)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await request(app)
.get('/api/v1/admin/apps/deepl/auth-clients/invalidAppAuthClientUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,10 +0,0 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import AppAuthClient from '../../../../../models/app-auth-client.js';
export default async (request, response) => {
const appAuthClients = await AppAuthClient.query()
.where({ app_key: request.params.appKey })
.orderBy('created_at', 'desc');
renderObject(response, appAuthClients);
};

View File

@@ -1,44 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createRole } from '../../../../../../test/factories/role.js';
import getAuthClientsMock from '../../../../../../test/mocks/rest/api/v1/admin/apps/get-auth-clients.js';
import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/apps/:appKey/auth-clients', () => {
let currentUser, adminRole, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
adminRole = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: adminRole.id });
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified app auth client info', async () => {
const appAuthClientOne = await createAppAuthClient({
appKey: 'deepl',
});
const appAuthClientTwo = await createAppAuthClient({
appKey: 'deepl',
});
const response = await request(app)
.get('/api/v1/admin/apps/deepl/auth-clients')
.set('Authorization', token)
.expect(200);
const expectedPayload = getAuthClientsMock([
appAuthClientTwo,
appAuthClientOne,
]);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -4,7 +4,7 @@ import AppAuthClient from '../../../../models/app-auth-client.js';
export default async (request, response) => { export default async (request, response) => {
const appAuthClient = await AppAuthClient.query() const appAuthClient = await AppAuthClient.query()
.findById(request.params.appAuthClientId) .findById(request.params.appAuthClientId)
.where({ app_key: request.params.appKey, active: true }) .where({ active: true })
.throwIfNotFound(); .throwIfNotFound();
renderObject(response, appAuthClient); renderObject(response, appAuthClient);

View File

@@ -4,27 +4,25 @@ import Crypto from 'crypto';
import app from '../../../../app.js'; import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js'; import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js'; import { createUser } from '../../../../../test/factories/user.js';
import getAppAuthClientMock from '../../../../../test/mocks/rest/api/v1/apps/get-auth-client.js'; import getAppAuthClientMock from '../../../../../test/mocks/rest/api/v1/admin/get-app-auth-client.js';
import { createAppAuthClient } from '../../../../../test/factories/app-auth-client.js'; import { createAppAuthClient } from '../../../../../test/factories/app-auth-client.js';
import * as license from '../../../../helpers/license.ee.js'; import * as license from '../../../../helpers/license.ee.js';
describe('GET /api/v1/apps/:appKey/auth-clients/:appAuthClientId', () => { describe('GET /api/v1/app-auth-clients/:id', () => {
let currentUser, currentAppAuthClient, token; let currentUser, currentAppAuthClient, token;
beforeEach(async () => { beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true); vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
currentUser = await createUser(); currentUser = await createUser();
currentAppAuthClient = await createAppAuthClient({ currentAppAuthClient = await createAppAuthClient();
appKey: 'deepl',
});
token = createAuthTokenByUserId(currentUser.id); token = createAuthTokenByUserId(currentUser.id);
}); });
it('should return specified app auth client', async () => { it('should return specified app auth client info', async () => {
const response = await request(app) const response = await request(app)
.get(`/api/v1/apps/deepl/auth-clients/${currentAppAuthClient.id}`) .get(`/api/v1/app-auth-clients/${currentAppAuthClient.id}`)
.set('Authorization', token) .set('Authorization', token)
.expect(200); .expect(200);
@@ -36,14 +34,14 @@ describe('GET /api/v1/apps/:appKey/auth-clients/:appAuthClientId', () => {
const notExistingAppAuthClientUUID = Crypto.randomUUID(); const notExistingAppAuthClientUUID = Crypto.randomUUID();
await request(app) await request(app)
.get(`/api/v1/apps/deepl/auth-clients/${notExistingAppAuthClientUUID}`) .get(`/api/v1/app-auth-clients/${notExistingAppAuthClientUUID}`)
.set('Authorization', token) .set('Authorization', token)
.expect(404); .expect(404);
}); });
it('should return bad request response for invalid UUID', async () => { it('should return bad request response for invalid UUID', async () => {
await request(app) await request(app)
.get('/api/v1/apps/deepl/auth-clients/invalidAppAuthClientUUID') .get('/api/v1/app-auth-clients/invalidAppAuthClientUUID')
.set('Authorization', token) .set('Authorization', token)
.expect(400); .expect(400);
}); });

View File

@@ -3,9 +3,6 @@ import AppConfig from '../../../../models/app-config.js';
export default async (request, response) => { export default async (request, response) => {
const appConfig = await AppConfig.query() const appConfig = await AppConfig.query()
.withGraphFetched({
appAuthClients: true,
})
.findOne({ .findOne({
key: request.params.appKey, key: request.params.appKey,
}) })

View File

@@ -3,11 +3,11 @@ import request from 'supertest';
import app from '../../../../app.js'; import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js'; import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js'; import { createUser } from '../../../../../test/factories/user.js';
import getAppConfigMock from '../../../../../test/mocks/rest/api/v1/apps/get-config.js'; import getAppConfigMock from '../../../../../test/mocks/rest/api/v1/app-configs/get-app-config.js';
import { createAppConfig } from '../../../../../test/factories/app-config.js'; import { createAppConfig } from '../../../../../test/factories/app-config.js';
import * as license from '../../../../helpers/license.ee.js'; import * as license from '../../../../helpers/license.ee.js';
describe('GET /api/v1/apps/:appKey/config', () => { describe('GET /api/v1/app-configs/:appKey', () => {
let currentUser, appConfig, token; let currentUser, appConfig, token;
beforeEach(async () => { beforeEach(async () => {
@@ -27,7 +27,7 @@ describe('GET /api/v1/apps/:appKey/config', () => {
it('should return specified app config info', async () => { it('should return specified app config info', async () => {
const response = await request(app) const response = await request(app)
.get(`/api/v1/apps/${appConfig.key}/config`) .get(`/api/v1/app-configs/${appConfig.key}`)
.set('Authorization', token) .set('Authorization', token)
.expect(200); .expect(200);
@@ -37,7 +37,7 @@ describe('GET /api/v1/apps/:appKey/config', () => {
it('should return not found response for not existing app key', async () => { it('should return not found response for not existing app key', async () => {
await request(app) await request(app)
.get('/api/v1/apps/not-existing-app-key/config') .get('/api/v1/app-configs/not-existing-app-key')
.set('Authorization', token) .set('Authorization', token)
.expect(404); .expect(404);
}); });

View File

@@ -1,10 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
import AppAuthClient from '../../../../models/app-auth-client.js';
export default async (request, response) => {
const appAuthClients = await AppAuthClient.query()
.where({ app_key: request.params.appKey, active: true })
.orderBy('created_at', 'desc');
renderObject(response, appAuthClients);
};

View File

@@ -1,42 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import getAuthClientsMock from '../../../../../test/mocks/rest/api/v1/apps/get-auth-clients.js';
import { createAppAuthClient } from '../../../../../test/factories/app-auth-client.js';
import * as license from '../../../../helpers/license.ee.js';
describe('GET /api/v1/apps/:appKey/auth-clients', () => {
let currentUser, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified app auth client info', async () => {
const appAuthClientOne = await createAppAuthClient({
appKey: 'deepl',
});
const appAuthClientTwo = await createAppAuthClient({
appKey: 'deepl',
});
const response = await request(app)
.get('/api/v1/apps/deepl/auth-clients')
.set('Authorization', token)
.expect(200);
const expectedPayload = getAuthClientsMock([
appAuthClientTwo,
appAuthClientOne,
]);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -1,24 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
import App from '../../../../models/app.js';
export default async (request, response) => {
const app = await App.findOneByKey(request.params.appKey);
const connections = await request.currentUser.authorizedConnections
.clone()
.select('connections.*')
.withGraphFetched({
appConfig: true,
appAuthClient: true,
})
.fullOuterJoinRelated('steps')
.where({
'connections.key': app.key,
'connections.draft': false,
})
.countDistinct('steps.flow_id as flowCount')
.groupBy('connections.id')
.orderBy('created_at', 'desc');
renderObject(response, connections);
};

View File

@@ -1,101 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createConnection } from '../../../../../test/factories/connection.js';
import { createPermission } from '../../../../../test/factories/permission.js';
import getConnectionsMock from '../../../../../test/mocks/rest/api/v1/apps/get-connections.js';
describe('GET /api/v1/apps/:appKey/connections', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the connections data of specified app for current user', async () => {
const currentUserConnectionOne = await createConnection({
userId: currentUser.id,
key: 'deepl',
draft: false,
});
const currentUserConnectionTwo = await createConnection({
userId: currentUser.id,
key: 'deepl',
draft: false,
});
await createPermission({
action: 'read',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get('/api/v1/apps/deepl/connections')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getConnectionsMock([
currentUserConnectionTwo,
currentUserConnectionOne,
]);
expect(response.body).toEqual(expectedPayload);
});
it('should return the connections data of specified app for another user', async () => {
const anotherUser = await createUser();
const anotherUserConnectionOne = await createConnection({
userId: anotherUser.id,
key: 'deepl',
draft: false,
});
const anotherUserConnectionTwo = await createConnection({
userId: anotherUser.id,
key: 'deepl',
draft: false,
});
await createPermission({
action: 'read',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get('/api/v1/apps/deepl/connections')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getConnectionsMock([
anotherUserConnectionTwo,
anotherUserConnectionOne,
]);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for invalid connection UUID', async () => {
await createPermission({
action: 'update',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.get('/api/v1/connections/invalid-connection-id/connections')
.set('Authorization', token)
.expect(404);
});
});

View File

@@ -1,14 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
let connection = await request.currentUser.authorizedConnections
.clone()
.findOne({
id: request.params.connectionId,
})
.throwIfNotFound();
connection = await connection.testAndUpdateConnection();
renderObject(response, connection);
};

View File

@@ -1,123 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createConnection } from '../../../../../test/factories/connection.js';
import { createPermission } from '../../../../../test/factories/permission.js';
describe('POST /api/v1/connections/:connectionId/test', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should update the connection as not verified for current user', async () => {
const currentUserConnection = await createConnection({
userId: currentUser.id,
key: 'deepl',
verified: true,
});
await createPermission({
action: 'read',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.post(`/api/v1/connections/${currentUserConnection.id}/test`)
.set('Authorization', token)
.expect(200);
expect(response.body.data.verified).toEqual(false);
});
it('should update the connection as not verified for another user', async () => {
const anotherUser = await createUser();
const anotherUserConnection = await createConnection({
userId: anotherUser.id,
key: 'deepl',
verified: true,
});
await createPermission({
action: 'read',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'update',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.post(`/api/v1/connections/${anotherUserConnection.id}/test`)
.set('Authorization', token)
.expect(200);
expect(response.body.data.verified).toEqual(false);
});
it('should return not found response for not existing connection UUID', async () => {
const notExistingConnectionUUID = Crypto.randomUUID();
await createPermission({
action: 'read',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.post(`/api/v1/connections/${notExistingConnectionUUID}/test`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'read',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.post('/api/v1/connections/invalidConnectionUUID/test')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -42,12 +42,9 @@ describe('GET /api/v1/executions', () => {
const currentUserExecutionTwo = await createExecution({ const currentUserExecutionTwo = await createExecution({
flowId: currentUserFlow.id, flowId: currentUserFlow.id,
deletedAt: new Date().toISOString(),
}); });
await currentUserExecutionTwo
.$query()
.patchAndFetch({ deletedAt: new Date().toISOString() });
await createPermission({ await createPermission({
action: 'read', action: 'read',
subject: 'Execution', subject: 'Execution',
@@ -90,12 +87,9 @@ describe('GET /api/v1/executions', () => {
const anotherUserExecutionTwo = await createExecution({ const anotherUserExecutionTwo = await createExecution({
flowId: anotherUserFlow.id, flowId: anotherUserFlow.id,
deletedAt: new Date().toISOString(),
}); });
await anotherUserExecutionTwo
.$query()
.patchAndFetch({ deletedAt: new Date().toISOString() });
await createPermission({ await createPermission({
action: 'read', action: 'read',
subject: 'Execution', subject: 'Execution',

View File

@@ -1,18 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const step = await request.currentUser.authorizedSteps
.clone()
.where('steps.id', request.params.stepId)
.whereNotNull('steps.app_key')
.whereNotNull('steps.connection_id')
.first()
.throwIfNotFound();
const dynamicData = await step.createDynamicData(
request.body.dynamicDataKey,
request.body.parameters
);
renderObject(response, dynamicData);
};

View File

@@ -1,244 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createConnection } from '../../../../../test/factories/connection';
import { createFlow } from '../../../../../test/factories/flow';
import { createStep } from '../../../../../test/factories/step';
import { createPermission } from '../../../../../test/factories/permission';
import listRepos from '../../../../apps/github/dynamic-data/list-repos/index.js';
import HttpError from '../../../../errors/http.js';
describe('POST /api/v1/steps/:stepId/dynamic-data', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
describe('should return dynamically created data', () => {
let repositories;
beforeEach(async () => {
repositories = [
{
value: 'automatisch/automatisch',
name: 'automatisch/automatisch',
},
{
value: 'automatisch/sample',
name: 'automatisch/sample',
},
];
vi.spyOn(listRepos, 'run').mockImplementation(async () => {
return {
data: repositories,
};
});
});
it('of the current users step', async () => {
const currentUserFlow = await createFlow({ userId: currentUser.id });
const connection = await createConnection({ userId: currentUser.id });
const actionStep = await createStep({
flowId: currentUserFlow.id,
connectionId: connection.id,
type: 'action',
appKey: 'github',
key: 'createIssue',
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.post(`/api/v1/steps/${actionStep.id}/dynamic-data`)
.set('Authorization', token)
.send({
dynamicDataKey: 'listRepos',
parameters: {},
})
.expect(200);
expect(response.body.data).toEqual(repositories);
});
it('of the another users step', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
const connection = await createConnection({ userId: anotherUser.id });
const actionStep = await createStep({
flowId: anotherUserFlow.id,
connectionId: connection.id,
type: 'action',
appKey: 'github',
key: 'createIssue',
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.post(`/api/v1/steps/${actionStep.id}/dynamic-data`)
.set('Authorization', token)
.send({
dynamicDataKey: 'listRepos',
parameters: {},
})
.expect(200);
expect(response.body.data).toEqual(repositories);
});
});
describe('should return error for dynamically created data', () => {
let errors;
beforeEach(async () => {
errors = {
message: 'Not Found',
documentation_url:
'https://docs.github.com/rest/users/users#get-a-user',
};
vi.spyOn(listRepos, 'run').mockImplementation(async () => {
throw new HttpError({ message: errors });
});
});
it('of the current users step', async () => {
const currentUserFlow = await createFlow({ userId: currentUser.id });
const connection = await createConnection({ userId: currentUser.id });
const actionStep = await createStep({
flowId: currentUserFlow.id,
connectionId: connection.id,
type: 'action',
appKey: 'github',
key: 'createIssue',
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.post(`/api/v1/steps/${actionStep.id}/dynamic-data`)
.set('Authorization', token)
.send({
dynamicDataKey: 'listRepos',
parameters: {},
})
.expect(200);
expect(response.body.errors).toEqual(errors);
});
});
it('should return not found response for not existing step UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const notExistingStepUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/steps/${notExistingStepUUID}/dynamic-data`)
.set('Authorization', token)
.expect(404);
});
it('should return not found response for existing step UUID without app key', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const step = await createStep({ appKey: null });
await request(app)
.get(`/api/v1/steps/${step.id}/dynamic-data`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.post('/api/v1/steps/invalidStepUUID/dynamic-fields')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,17 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const step = await request.currentUser.authorizedSteps
.clone()
.where('steps.id', request.params.stepId)
.whereNotNull('steps.app_key')
.first()
.throwIfNotFound();
const dynamicFields = await step.createDynamicFields(
request.body.dynamicFieldsKey,
request.body.parameters
);
renderObject(response, dynamicFields);
};

View File

@@ -1,169 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createFlow } from '../../../../../test/factories/flow';
import { createStep } from '../../../../../test/factories/step';
import { createPermission } from '../../../../../test/factories/permission';
import createDynamicFieldsMock from '../../../../../test/mocks/rest/api/v1/steps/create-dynamic-fields';
describe('POST /api/v1/steps/:stepId/dynamic-fields', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should return dynamically created fields of the current users step', async () => {
const currentUserflow = await createFlow({ userId: currentUser.id });
const actionStep = await createStep({
flowId: currentUserflow.id,
type: 'action',
appKey: 'slack',
key: 'sendMessageToChannel',
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.post(`/api/v1/steps/${actionStep.id}/dynamic-fields`)
.set('Authorization', token)
.send({
dynamicFieldsKey: 'listFieldsAfterSendAsBot',
parameters: {
sendAsBot: true,
},
})
.expect(200);
const expectedPayload = await createDynamicFieldsMock();
expect(response.body).toEqual(expectedPayload);
});
it('should return dynamically created fields of the another users step', async () => {
const anotherUser = await createUser();
const anotherUserflow = await createFlow({ userId: anotherUser.id });
const actionStep = await createStep({
flowId: anotherUserflow.id,
type: 'action',
appKey: 'slack',
key: 'sendMessageToChannel',
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.post(`/api/v1/steps/${actionStep.id}/dynamic-fields`)
.set('Authorization', token)
.send({
dynamicFieldsKey: 'listFieldsAfterSendAsBot',
parameters: {
sendAsBot: true,
},
})
.expect(200);
const expectedPayload = await createDynamicFieldsMock();
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing step UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const notExistingStepUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/steps/${notExistingStepUUID}/dynamic-fields`)
.set('Authorization', token)
.expect(404);
});
it('should return not found response for existing step UUID without app key', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const step = await createStep({ appKey: null });
await request(app)
.get(`/api/v1/steps/${step.id}/dynamic-fields`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.post('/api/v1/steps/invalidStepUUID/dynamic-fields')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,27 +0,0 @@
import { ref } from 'objection';
import ExecutionStep from '../../../../models/execution-step.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const step = await request.currentUser.authorizedSteps
.clone()
.findOne({ 'steps.id': request.params.stepId })
.throwIfNotFound();
const previousSteps = await request.currentUser.authorizedSteps
.clone()
.withGraphJoined('executionSteps')
.where('flow_id', '=', step.flowId)
.andWhere('position', '<', step.position)
.andWhere(
'executionSteps.created_at',
'=',
ExecutionStep.query()
.max('created_at')
.where('step_id', '=', ref('steps.id'))
.andWhere('status', 'success')
)
.orderBy('steps.position', 'asc');
renderObject(response, previousSteps);
};

View File

@@ -1,173 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createFlow } from '../../../../../test/factories/flow';
import { createStep } from '../../../../../test/factories/step';
import { createExecutionStep } from '../../../../../test/factories/execution-step.js';
import { createPermission } from '../../../../../test/factories/permission';
import getPreviousStepsMock from '../../../../../test/mocks/rest/api/v1/steps/get-previous-steps';
describe('GET /api/v1/steps/:stepId/previous-steps', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the previous steps of the specified step of the current user', async () => {
const currentUserflow = await createFlow({ userId: currentUser.id });
const triggerStep = await createStep({
flowId: currentUserflow.id,
type: 'trigger',
});
const actionStepOne = await createStep({
flowId: currentUserflow.id,
type: 'action',
});
const actionStepTwo = await createStep({
flowId: currentUserflow.id,
type: 'action',
});
const executionStepOne = await createExecutionStep({
stepId: triggerStep.id,
});
const executionStepTwo = await createExecutionStep({
stepId: actionStepOne.id,
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get(`/api/v1/steps/${actionStepTwo.id}/previous-steps`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getPreviousStepsMock(
[triggerStep, actionStepOne],
[executionStepOne, executionStepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return the previous steps of the specified step of another user', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
const triggerStep = await createStep({
flowId: anotherUserFlow.id,
type: 'trigger',
});
const actionStepOne = await createStep({
flowId: anotherUserFlow.id,
type: 'action',
});
const actionStepTwo = await createStep({
flowId: anotherUserFlow.id,
type: 'action',
});
const executionStepOne = await createExecutionStep({
stepId: triggerStep.id,
});
const executionStepTwo = await createExecutionStep({
stepId: actionStepOne.id,
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get(`/api/v1/steps/${actionStepTwo.id}/previous-steps`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getPreviousStepsMock(
[triggerStep, actionStepOne],
[executionStepOne, executionStepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing step UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const notExistingFlowUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/steps/${notExistingFlowUUID}/previous-steps`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.get('/api/v1/steps/invalidFlowUUID/previous-steps')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -1,7 +0,0 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const apps = await request.currentUser.getApps(request.query.name);
renderObject(response, apps, { serializer: 'App' });
};

View File

@@ -1,210 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createRole } from '../../../../../test/factories/role';
import { createUser } from '../../../../../test/factories/user';
import { createPermission } from '../../../../../test/factories/permission.js';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import { createConnection } from '../../../../../test/factories/connection.js';
import getAppsMock from '../../../../../test/mocks/rest/api/v1/users/get-apps.js';
describe('GET /api/v1/users/:userId/apps', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUserRole = await createRole();
currentUser = await createUser({ roleId: currentUserRole.id });
token = createAuthTokenByUserId(currentUser.id);
});
it('should return all apps of the current user', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'read',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const flowOne = await createFlow({ userId: currentUser.id });
await createStep({
flowId: flowOne.id,
appKey: 'webhook',
});
const flowOneActionStepConnection = await createConnection({
userId: currentUser.id,
key: 'deepl',
draft: false,
});
await createStep({
connectionId: flowOneActionStepConnection.id,
flowId: flowOne.id,
appKey: 'deepl',
});
const flowTwo = await createFlow({ userId: currentUser.id });
const flowTwoTriggerStepConnection = await createConnection({
userId: currentUser.id,
key: 'github',
draft: false,
});
await createStep({
connectionId: flowTwoTriggerStepConnection.id,
flowId: flowTwo.id,
appKey: 'github',
});
await createStep({
flowId: flowTwo.id,
appKey: 'slack',
});
const response = await request(app)
.get(`/api/v1/users/${currentUser.id}/apps`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getAppsMock();
expect(response.body).toEqual(expectedPayload);
});
it('should return all apps of the another user', async () => {
const anotherUser = await createUser();
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: [],
});
const flowOne = await createFlow({ userId: anotherUser.id });
await createStep({
flowId: flowOne.id,
appKey: 'webhook',
});
const flowOneActionStepConnection = await createConnection({
userId: anotherUser.id,
key: 'deepl',
draft: false,
});
await createStep({
connectionId: flowOneActionStepConnection.id,
flowId: flowOne.id,
appKey: 'deepl',
});
const flowTwo = await createFlow({ userId: anotherUser.id });
const flowTwoTriggerStepConnection = await createConnection({
userId: anotherUser.id,
key: 'github',
draft: false,
});
await createStep({
connectionId: flowTwoTriggerStepConnection.id,
flowId: flowTwo.id,
appKey: 'github',
});
await createStep({
flowId: flowTwo.id,
appKey: 'slack',
});
const response = await request(app)
.get(`/api/v1/users/${currentUser.id}/apps`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getAppsMock();
expect(response.body).toEqual(expectedPayload);
});
it('should return specified app of the current user', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'read',
subject: 'Connection',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const flowOne = await createFlow({ userId: currentUser.id });
await createStep({
flowId: flowOne.id,
appKey: 'webhook',
});
const flowOneActionStepConnection = await createConnection({
userId: currentUser.id,
key: 'deepl',
draft: false,
});
await createStep({
connectionId: flowOneActionStepConnection.id,
flowId: flowOne.id,
appKey: 'deepl',
});
const flowTwo = await createFlow({ userId: currentUser.id });
const flowTwoTriggerStepConnection = await createConnection({
userId: currentUser.id,
key: 'github',
draft: false,
});
await createStep({
connectionId: flowTwoTriggerStepConnection.id,
flowId: flowTwo.id,
appKey: 'github',
});
await createStep({
flowId: flowTwo.id,
appKey: 'slack',
});
const response = await request(app)
.get(`/api/v1/users/${currentUser.id}/apps?name=deepl`)
.set('Authorization', token)
.expect(200);
expect(response.body.data.length).toEqual(1);
expect(response.body.data[0].key).toEqual('deepl');
});
});

View File

@@ -26,4 +26,6 @@ export default async (request, response) => {
} }
await handlerSync(flowId, request, response); await handlerSync(flowId, request, response);
response.sendStatus(204);
}; };

View File

@@ -1,11 +0,0 @@
export async function up(knex) {
await knex.schema.table('app_auth_clients', (table) => {
table.string('app_key');
});
}
export async function down(knex) {
await knex.schema.table('app_auth_clients', (table) => {
table.dropColumn('app_key');
});
}

View File

@@ -1,17 +0,0 @@
export async function up(knex) {
const appAuthClients = await knex('app_auth_clients').select('*');
for (const appAuthClient of appAuthClients) {
const appConfig = await knex('app_configs')
.where('id', appAuthClient.app_config_id)
.first();
await knex('app_auth_clients')
.where('id', appAuthClient.id)
.update({ app_key: appConfig.key });
}
}
export async function down() {
// void
}

View File

@@ -1,11 +0,0 @@
export async function up(knex) {
await knex.schema.table('app_auth_clients', (table) => {
table.dropColumn('app_config_id');
});
}
export async function down(knex) {
await knex.schema.table('app_auth_clients', (table) => {
table.uuid('app_config_id').references('id').inTable('app_configs');
});
}

View File

@@ -1,11 +0,0 @@
export async function up(knex) {
await knex.schema.table('app_auth_clients', (table) => {
table.string('app_key').notNullable().alter();
});
}
export async function down(knex) {
await knex.schema.table('app_auth_clients', (table) => {
table.string('app_key').nullable().alter();
});
}

View File

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

View File

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

View File

@@ -0,0 +1,41 @@
import App from '../../models/app.js';
import Connection from '../../models/connection.js';
const getApp = async (_parent, params, context) => {
const conditions = context.currentUser.can('read', 'Connection');
const userConnections = context.currentUser.$relatedQuery('connections');
const allConnections = Connection.query();
const connectionBaseQuery = conditions.isCreator
? userConnections
: allConnections;
const app = await App.findOneByKey(params.key);
if (context.currentUser) {
const connections = await connectionBaseQuery
.clone()
.select('connections.*')
.withGraphFetched({
appConfig: true,
appAuthClient: true,
})
.fullOuterJoinRelated('steps')
.where({
'connections.key': params.key,
'connections.draft': false,
})
.countDistinct('steps.flow_id as flowCount')
.groupBy('connections.id')
.orderBy('created_at', 'desc');
return {
...app,
connections,
};
}
return app;
};
export default getApp;

View File

@@ -0,0 +1,101 @@
import { DateTime } from 'luxon';
import Billing from '../../helpers/billing/index.ee.js';
import ExecutionStep from '../../models/execution-step.js';
const getBillingAndUsage = async (_parent, _params, context) => {
const persistedSubscription = await context.currentUser.$relatedQuery(
'currentSubscription'
);
const subscription = persistedSubscription
? paidSubscription(persistedSubscription)
: freeTrialSubscription();
return {
subscription,
usage: {
task: executionStepCount(context),
},
};
};
const paidSubscription = (subscription) => {
const currentPlan = Billing.paddlePlans.find(
(plan) => plan.productId === subscription.paddlePlanId
);
return {
status: subscription.status,
monthlyQuota: {
title: currentPlan.limit,
action: {
type: 'link',
text: 'Cancel plan',
src: subscription.cancelUrl,
},
},
nextBillAmount: {
title: subscription.nextBillAmount
? '€' + subscription.nextBillAmount
: '---',
action: {
type: 'link',
text: 'Update payment method',
src: subscription.updateUrl,
},
},
nextBillDate: {
title: subscription.nextBillDate ? subscription.nextBillDate : '---',
action: {
type: 'text',
text: '(monthly payment)',
},
},
};
};
const freeTrialSubscription = () => {
return {
status: null,
monthlyQuota: {
title: 'Free Trial',
action: {
type: 'link',
text: 'Upgrade plan',
src: '/settings/billing/upgrade',
},
},
nextBillAmount: {
title: '---',
action: null,
},
nextBillDate: {
title: '---',
action: null,
},
};
};
const executionIds = async (context) => {
return (
await context.currentUser
.$relatedQuery('executions')
.select('executions.id')
).map((execution) => execution.id);
};
const executionStepCount = async (context) => {
const executionStepCount = await ExecutionStep.query()
.whereIn('execution_id', await executionIds(context))
.andWhere(
'created_at',
'>=',
DateTime.now().minus({ days: 30 }).toISODate()
)
.count()
.first();
return executionStepCount.count;
};
export default getBillingAndUsage;

View File

@@ -0,0 +1,32 @@
import appConfig from '../../config/app.js';
import { hasValidLicense } from '../../helpers/license.ee.js';
import Config from '../../models/config.js';
const getConfig = async (_parent, params) => {
if (!(await hasValidLicense())) return {};
const defaultConfig = {
disableNotificationsPage: appConfig.disableNotificationsPage,
disableFavicon: appConfig.disableFavicon,
additionalDrawerLink: appConfig.additionalDrawerLink,
additionalDrawerLinkText: appConfig.additionalDrawerLinkText,
};
const configQuery = Config.query();
if (Array.isArray(params.keys)) {
configQuery.whereIn('key', params.keys);
}
const config = await configQuery.orderBy('key', 'asc');
return config.reduce((computedConfig, configEntry) => {
const { key, value } = configEntry;
computedConfig[key] = value?.data;
return computedConfig;
}, defaultConfig);
};
export default getConfig;

View File

@@ -0,0 +1,140 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../app';
import { createConfig } from '../../../test/factories/config';
import appConfig from '../../config/app';
import * as license from '../../helpers/license.ee';
describe('graphQL getConfig query', () => {
let configOne, configTwo, configThree, query;
beforeEach(async () => {
configOne = await createConfig({ key: 'configOne' });
configTwo = await createConfig({ key: 'configTwo' });
configThree = await createConfig({ key: 'configThree' });
query = `
query {
getConfig
}
`;
});
describe('and without valid license', () => {
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(false);
});
describe('and correct permissions', () => {
it('should return empty config data', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = { data: { getConfig: {} } };
expect(response.body).toEqual(expectedResponsePayload);
});
});
});
describe('and with valid license', () => {
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
});
describe('and without providing specific keys', () => {
it('should return all config data', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getConfig: {
[configOne.key]: configOne.value.data,
[configTwo.key]: configTwo.value.data,
[configThree.key]: configThree.value.data,
disableNotificationsPage: false,
disableFavicon: false,
additionalDrawerLink: undefined,
additionalDrawerLinkText: undefined,
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with providing specific keys', () => {
it('should return all config data', async () => {
query = `
query {
getConfig(keys: ["configOne", "configTwo"])
}
`;
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getConfig: {
[configOne.key]: configOne.value.data,
[configTwo.key]: configTwo.value.data,
disableNotificationsPage: false,
disableFavicon: false,
additionalDrawerLink: undefined,
additionalDrawerLinkText: undefined,
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with different defaults', () => {
beforeEach(async () => {
vi.spyOn(appConfig, 'disableNotificationsPage', 'get').mockReturnValue(
true
);
vi.spyOn(appConfig, 'disableFavicon', 'get').mockReturnValue(true);
vi.spyOn(appConfig, 'additionalDrawerLink', 'get').mockReturnValue(
'https://automatisch.io'
);
vi.spyOn(appConfig, 'additionalDrawerLinkText', 'get').mockReturnValue(
'Automatisch'
);
});
it('should return custom config', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getConfig: {
[configOne.key]: configOne.value.data,
[configTwo.key]: configTwo.value.data,
[configThree.key]: configThree.value.data,
disableNotificationsPage: true,
disableFavicon: true,
additionalDrawerLink: 'https://automatisch.io',
additionalDrawerLinkText: 'Automatisch',
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
});
});

View File

@@ -0,0 +1,67 @@
import App from '../../models/app.js';
import Flow from '../../models/flow.js';
import Connection from '../../models/connection.js';
const getConnectedApps = async (_parent, params, context) => {
const conditions = context.currentUser.can('read', 'Connection');
const userConnections = context.currentUser.$relatedQuery('connections');
const allConnections = Connection.query();
const connectionBaseQuery = conditions.isCreator
? userConnections
: allConnections;
const userFlows = context.currentUser.$relatedQuery('flows');
const allFlows = Flow.query();
const flowBaseQuery = conditions.isCreator ? userFlows : allFlows;
let apps = await App.findAll(params.name);
const connections = await connectionBaseQuery
.clone()
.select('connections.key')
.where({ draft: false })
.count('connections.id as count')
.groupBy('connections.key');
const flows = await flowBaseQuery
.clone()
.withGraphJoined('steps')
.orderBy('created_at', 'desc');
const duplicatedUsedApps = flows
.map((flow) => flow.steps.map((step) => step.appKey))
.flat()
.filter(Boolean);
const connectionKeys = connections.map((connection) => connection.key);
const usedApps = [...new Set([...duplicatedUsedApps, ...connectionKeys])];
apps = apps
.filter((app) => {
return usedApps.includes(app.key);
})
.map((app) => {
const connection = connections.find(
(connection) => connection.key === app.key
);
app.connectionCount = connection?.count || 0;
app.flowCount = 0;
flows.forEach((flow) => {
const usedFlow = flow.steps.find((step) => step.appKey === app.key);
if (usedFlow) {
app.flowCount += 1;
}
});
return app;
})
.sort((appA, appB) => appA.name.localeCompare(appB.name));
return apps;
};
export default getConnectedApps;

View File

@@ -0,0 +1,65 @@
import App from '../../models/app.js';
import Step from '../../models/step.js';
import ExecutionStep from '../../models/execution-step.js';
import globalVariable from '../../helpers/global-variable.js';
import computeParameters from '../../helpers/compute-parameters.js';
const getDynamicData = async (_parent, params, context) => {
const conditions = context.currentUser.can('update', 'Flow');
const userSteps = context.currentUser.$relatedQuery('steps');
const allSteps = Step.query();
const stepBaseQuery = conditions.isCreator ? userSteps : allSteps;
const step = await stepBaseQuery
.clone()
.withGraphFetched({
connection: true,
flow: true,
})
.findById(params.stepId);
if (!step) return null;
const connection = step.connection;
if (!connection || !step.appKey) return null;
const flow = step.flow;
const app = await App.findOneByKey(step.appKey);
const $ = await globalVariable({ connection, app, flow, step });
const command = app.dynamicData.find((data) => data.key === params.key);
// apply run-time parameters that're not persisted yet
for (const parameterKey in params.parameters) {
const parameterValue = params.parameters[parameterKey];
$.step.parameters[parameterKey] = parameterValue;
}
const lastExecution = await flow.$relatedQuery('lastExecution');
const lastExecutionId = lastExecution?.id;
const priorExecutionSteps = lastExecutionId
? await ExecutionStep.query().where({
execution_id: lastExecutionId,
})
: [];
// compute variables in parameters
const computedParameters = computeParameters(
$.step.parameters,
priorExecutionSteps
);
$.step.parameters = computedParameters;
const fetchedData = await command.run($);
if (fetchedData.error) {
throw new Error(JSON.stringify(fetchedData.error));
}
return fetchedData.data;
};
export default getDynamicData;

View File

@@ -0,0 +1,40 @@
import App from '../../models/app.js';
import Step from '../../models/step.js';
import globalVariable from '../../helpers/global-variable.js';
const getDynamicFields = async (_parent, params, context) => {
const conditions = context.currentUser.can('update', 'Flow');
const userSteps = context.currentUser.$relatedQuery('steps');
const allSteps = Step.query();
const stepBaseQuery = conditions.isCreator ? userSteps : allSteps;
const step = await stepBaseQuery
.clone()
.withGraphFetched({
connection: true,
flow: true,
})
.findById(params.stepId);
if (!step) return null;
const connection = step.connection;
if (!step.appKey) return null;
const app = await App.findOneByKey(step.appKey);
const $ = await globalVariable({ connection, app, flow: step.flow, step });
const command = app.dynamicFields.find((data) => data.key === params.key);
for (const parameterKey in params.parameters) {
const parameterValue = params.parameters[parameterKey];
$.step.parameters[parameterKey] = parameterValue;
}
const additionalFields = (await command.run($)) || [];
return additionalFields;
};
export default getDynamicFields;

View File

@@ -0,0 +1,19 @@
import Flow from '../../models/flow.js';
const getFlow = async (_parent, params, context) => {
const conditions = context.currentUser.can('read', 'Flow');
const userFlows = context.currentUser.$relatedQuery('flows');
const allFlows = Flow.query();
const baseQuery = conditions.isCreator ? userFlows : allFlows;
const flow = await baseQuery
.clone()
.withGraphJoined('[steps.[connection]]')
.orderBy('steps.position', 'asc')
.findOne({ 'flows.id': params.id })
.throwIfNotFound();
return flow;
};
export default getFlow;

View File

@@ -0,0 +1,240 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../app';
import appConfig from '../../config/app';
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
import { createRole } from '../../../test/factories/role';
import { createPermission } from '../../../test/factories/permission';
import { createUser } from '../../../test/factories/user';
import { createFlow } from '../../../test/factories/flow';
import { createStep } from '../../../test/factories/step';
import { createConnection } from '../../../test/factories/connection';
describe('graphQL getFlow query', () => {
const query = (flowId) => {
return `
query {
getFlow(id: "${flowId}") {
id
name
active
status
steps {
id
type
key
appKey
iconUrl
webhookUrl
status
position
connection {
id
verified
createdAt
}
parameters
}
}
}
`;
};
describe('and without permissions', () => {
it('should throw not authorized error', async () => {
const userWithoutPermissions = await createUser();
const token = createAuthTokenByUserId(userWithoutPermissions.id);
const flow = await createFlow();
const response = await request(app)
.post('/graphql')
.set('Authorization', token)
.send({ query: query(flow.id) })
.expect(200);
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toEqual('Not authorized!');
});
});
describe('and with correct permission', () => {
let currentUser, currentUserRole, currentUserFlow;
beforeEach(async () => {
currentUserRole = await createRole();
currentUser = await createUser({ roleId: currentUserRole.id });
currentUserFlow = await createFlow({ userId: currentUser.id });
});
describe('and with isCreator condition', () => {
it('should return executions data of the current user', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const triggerStep = await createStep({
flowId: currentUserFlow.id,
type: 'trigger',
key: 'catchRawWebhook',
webhookPath: `/webhooks/flows/${currentUserFlow.id}`,
});
const actionConnection = await createConnection({
userId: currentUser.id,
formattedData: {
screenName: 'Test',
authenticationKey: 'test key',
},
});
const actionStep = await createStep({
flowId: currentUserFlow.id,
type: 'action',
connectionId: actionConnection.id,
key: 'translateText',
});
const token = createAuthTokenByUserId(currentUser.id);
const response = await request(app)
.post('/graphql')
.set('Authorization', token)
.send({ query: query(currentUserFlow.id) })
.expect(200);
const expectedResponsePayload = {
data: {
getFlow: {
active: currentUserFlow.active,
id: currentUserFlow.id,
name: currentUserFlow.name,
status: 'draft',
steps: [
{
appKey: triggerStep.appKey,
connection: null,
iconUrl: `${appConfig.baseUrl}/apps/${triggerStep.appKey}/assets/favicon.svg`,
id: triggerStep.id,
key: 'catchRawWebhook',
parameters: {},
position: 1,
status: triggerStep.status,
type: 'trigger',
webhookUrl: `${appConfig.baseUrl}/webhooks/flows/${currentUserFlow.id}`,
},
{
appKey: actionStep.appKey,
connection: {
createdAt: actionConnection.createdAt.getTime().toString(),
id: actionConnection.id,
verified: actionConnection.verified,
},
iconUrl: `${appConfig.baseUrl}/apps/${actionStep.appKey}/assets/favicon.svg`,
id: actionStep.id,
key: 'translateText',
parameters: {},
position: 1,
status: actionStep.status,
type: 'action',
webhookUrl: 'http://localhost:3000/null',
},
],
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and without isCreator condition', () => {
it('should return executions data of all users', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
const triggerStep = await createStep({
flowId: anotherUserFlow.id,
type: 'trigger',
key: 'catchRawWebhook',
webhookPath: `/webhooks/flows/${anotherUserFlow.id}`,
});
const actionConnection = await createConnection({
userId: anotherUser.id,
formattedData: {
screenName: 'Test',
authenticationKey: 'test key',
},
});
const actionStep = await createStep({
flowId: anotherUserFlow.id,
type: 'action',
connectionId: actionConnection.id,
key: 'translateText',
});
const token = createAuthTokenByUserId(currentUser.id);
const response = await request(app)
.post('/graphql')
.set('Authorization', token)
.send({ query: query(anotherUserFlow.id) })
.expect(200);
const expectedResponsePayload = {
data: {
getFlow: {
active: anotherUserFlow.active,
id: anotherUserFlow.id,
name: anotherUserFlow.name,
status: 'draft',
steps: [
{
appKey: triggerStep.appKey,
connection: null,
iconUrl: `${appConfig.baseUrl}/apps/${triggerStep.appKey}/assets/favicon.svg`,
id: triggerStep.id,
key: 'catchRawWebhook',
parameters: {},
position: 1,
status: triggerStep.status,
type: 'trigger',
webhookUrl: `${appConfig.baseUrl}/webhooks/flows/${anotherUserFlow.id}`,
},
{
appKey: actionStep.appKey,
connection: {
createdAt: actionConnection.createdAt.getTime().toString(),
id: actionConnection.id,
verified: actionConnection.verified,
},
iconUrl: `${appConfig.baseUrl}/apps/${actionStep.appKey}/assets/favicon.svg`,
id: actionStep.id,
key: 'translateText',
parameters: {},
position: 1,
status: actionStep.status,
type: 'action',
webhookUrl: 'http://localhost:3000/null',
},
],
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
});
});

View File

@@ -0,0 +1,40 @@
import Flow from '../../models/flow.js';
import paginate from '../../helpers/pagination.js';
const getFlows = async (_parent, params, context) => {
const conditions = context.currentUser.can('read', 'Flow');
const userFlows = context.currentUser.$relatedQuery('flows');
const allFlows = Flow.query();
const baseQuery = conditions.isCreator ? userFlows : allFlows;
const flowsQuery = baseQuery
.clone()
.joinRelated({
steps: true,
})
.withGraphFetched({
steps: {
connection: true,
},
})
.where((builder) => {
if (params.connectionId) {
builder.where('steps.connection_id', params.connectionId);
}
if (params.name) {
builder.where('flows.name', 'ilike', `%${params.name}%`);
}
if (params.appKey) {
builder.where('steps.app_key', params.appKey);
}
})
.groupBy('flows.id')
.orderBy('active', 'desc')
.orderBy('updated_at', 'desc');
return paginate(flowsQuery, params.limit, params.offset);
};
export default getFlows;

View File

@@ -0,0 +1,16 @@
import axios from '../../helpers/axios-with-proxy.js';
const NOTIFICATIONS_URL =
'https://notifications.automatisch.io/notifications.json';
const getNotifications = async () => {
try {
const { data: notifications = [] } = await axios.get(NOTIFICATIONS_URL);
return notifications;
} catch (err) {
return [];
}
};
export default getNotifications;

View File

@@ -0,0 +1,34 @@
import { ref } from 'objection';
import ExecutionStep from '../../models/execution-step.js';
import Step from '../../models/step.js';
const getStepWithTestExecutions = async (_parent, params, context) => {
const conditions = context.currentUser.can('update', 'Flow');
const userSteps = context.currentUser.$relatedQuery('steps');
const allSteps = Step.query();
const stepBaseQuery = conditions.isCreator ? userSteps : allSteps;
const step = await stepBaseQuery
.clone()
.findOne({ 'steps.id': params.stepId })
.throwIfNotFound();
const previousStepsWithCurrentStep = await stepBaseQuery
.clone()
.withGraphJoined('executionSteps')
.where('flow_id', '=', step.flowId)
.andWhere('position', '<', step.position)
.andWhere(
'executionSteps.created_at',
'=',
ExecutionStep.query()
.max('created_at')
.where('step_id', '=', ref('steps.id'))
.andWhere('status', 'success')
)
.orderBy('steps.position', 'asc');
return previousStepsWithCurrentStep;
};
export default getStepWithTestExecutions;

View File

@@ -0,0 +1,19 @@
import paginate from '../../helpers/pagination.js';
import User from '../../models/user.js';
const getUsers = async (_parent, params, context) => {
context.currentUser.can('read', 'User');
const usersQuery = User.query()
.leftJoinRelated({
role: true,
})
.withGraphFetched({
role: true,
})
.orderBy('full_name', 'asc');
return paginate(usersQuery, params.limit, params.offset);
};
export default getUsers;

View File

@@ -0,0 +1,148 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../app';
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
import { createRole } from '../../../test/factories/role';
import { createPermission } from '../../../test/factories/permission';
import { createUser } from '../../../test/factories/user';
describe('graphQL getUsers query', () => {
const query = `
query {
getUsers(limit: 10, offset: 0) {
pageInfo {
currentPage
totalPages
}
totalCount
edges {
node {
id
fullName
email
role {
id
name
}
}
}
}
}
`;
describe('and without permissions', () => {
it('should throw not authorized error', async () => {
const userWithoutPermissions = await createUser();
const token = createAuthTokenByUserId(userWithoutPermissions.id);
const response = await request(app)
.post('/graphql')
.set('Authorization', token)
.send({ query })
.expect(200);
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toEqual('Not authorized!');
});
});
describe('and with correct permissions', () => {
let role, currentUser, anotherUser, token, requestObject;
beforeEach(async () => {
role = await createRole({
key: 'sample',
name: 'sample',
});
await createPermission({
action: 'read',
subject: 'User',
roleId: role.id,
});
currentUser = await createUser({
roleId: role.id,
fullName: 'Current User',
});
anotherUser = await createUser({
roleId: role.id,
fullName: 'Another User',
});
token = createAuthTokenByUserId(currentUser.id);
requestObject = request(app).post('/graphql').set('Authorization', token);
});
it('should return users data', async () => {
const response = await requestObject.send({ query }).expect(200);
const expectedResponsePayload = {
data: {
getUsers: {
edges: [
{
node: {
email: anotherUser.email,
fullName: anotherUser.fullName,
id: anotherUser.id,
role: {
id: role.id,
name: role.name,
},
},
},
{
node: {
email: currentUser.email,
fullName: currentUser.fullName,
id: currentUser.id,
role: {
id: role.id,
name: role.name,
},
},
},
],
pageInfo: {
currentPage: 1,
totalPages: 1,
},
totalCount: 2,
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
it('should not return users data with password', async () => {
const query = `
query {
getUsers(limit: 10, offset: 0) {
pageInfo {
currentPage
totalPages
}
totalCount
edges {
node {
id
fullName
password
}
}
}
}
`;
const response = await requestObject.send({ query }).expect(400);
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toEqual(
'Cannot query field "password" on type "User".'
);
});
});
});

View File

@@ -0,0 +1,38 @@
import App from '../../models/app.js';
import Connection from '../../models/connection.js';
import globalVariable from '../../helpers/global-variable.js';
const testConnection = async (_parent, params, context) => {
const conditions = context.currentUser.can('update', 'Connection');
const userConnections = context.currentUser.$relatedQuery('connections');
const allConnections = Connection.query();
const connectionBaseQuery = conditions.isCreator
? userConnections
: allConnections;
let connection = await connectionBaseQuery
.clone()
.findOne({
id: params.id,
})
.throwIfNotFound();
const app = await App.findOneByKey(connection.key, false);
const $ = await globalVariable({ connection, app });
let isStillVerified;
try {
isStillVerified = !!(await app.auth.isStillVerified($));
} catch {
isStillVerified = false;
}
connection = await connection.$query().patchAndFetch({
formattedData: connection.formattedData,
verified: isStillVerified,
});
return connection;
};
export default testConnection;

View File

@@ -0,0 +1,33 @@
import getApp from './queries/get-app.js';
import getAppAuthClient from './queries/get-app-auth-client.ee.js';
import getAppAuthClients from './queries/get-app-auth-clients.ee.js';
import getBillingAndUsage from './queries/get-billing-and-usage.ee.js';
import getConfig from './queries/get-config.ee.js';
import getConnectedApps from './queries/get-connected-apps.js';
import getDynamicData from './queries/get-dynamic-data.js';
import getDynamicFields from './queries/get-dynamic-fields.js';
import getFlow from './queries/get-flow.js';
import getFlows from './queries/get-flows.js';
import getNotifications from './queries/get-notifications.js';
import getStepWithTestExecutions from './queries/get-step-with-test-executions.js';
import getUsers from './queries/get-users.js';
import testConnection from './queries/test-connection.js';
const queryResolvers = {
getApp,
getAppAuthClient,
getAppAuthClients,
getBillingAndUsage,
getConfig,
getConnectedApps,
getDynamicData,
getDynamicFields,
getFlow,
getFlows,
getNotifications,
getStepWithTestExecutions,
getUsers,
testConnection,
};
export default queryResolvers;

View File

@@ -1,6 +1,8 @@
import mutationResolvers from './mutation-resolvers.js'; import mutationResolvers from './mutation-resolvers.js';
import queryResolvers from './query-resolvers.js';
const resolvers = { const resolvers = {
Query: queryResolvers,
Mutation: mutationResolvers, Mutation: mutationResolvers,
}; };

View File

@@ -1,6 +1,34 @@
type Query { type Query {
placeholderQuery(name: String): Boolean getApp(key: String!): App
getAppAuthClient(id: String!): AppAuthClient
getAppAuthClients(appKey: String!, active: Boolean): [AppAuthClient]
getConnectedApps(name: String): [App]
testConnection(id: String!): Connection
getFlow(id: String!): Flow
getFlows(
limit: Int!
offset: Int!
appKey: String
connectionId: String
name: String
): FlowConnection
getStepWithTestExecutions(stepId: String!): [Step]
getDynamicData(
stepId: String!
key: String!
parameters: JSONObject
): JSONObject
getDynamicFields(
stepId: String!
key: String!
parameters: JSONObject
): [SubstepArgument]
getBillingAndUsage: GetBillingAndUsage
getConfig(keys: [String]): JSONObject
getNotifications: [Notification]
getUsers(limit: Int!, offset: Int!): UserConnection
} }
type Mutation { type Mutation {
createAppConfig(input: CreateAppConfigInput): AppConfig createAppConfig(input: CreateAppConfigInput): AppConfig
createAppAuthClient(input: CreateAppAuthClientInput): AppAuthClient createAppAuthClient(input: CreateAppAuthClientInput): AppAuthClient
@@ -229,6 +257,15 @@ type Field {
options: [SubstepArgumentOption] options: [SubstepArgumentOption]
} }
type FlowConnection {
edges: [FlowEdge]
pageInfo: PageInfo
}
type FlowEdge {
node: Flow
}
enum FlowStatus { enum FlowStatus {
paused paused
published published
@@ -267,6 +304,16 @@ type SamlAuthProvidersRoleMapping {
remoteRoleName: String remoteRoleName: String
} }
type UserConnection {
edges: [UserEdge]
pageInfo: PageInfo
totalCount: Int
}
type UserEdge {
node: User
}
input CreateConnectionInput { input CreateConnectionInput {
key: String! key: String!
appAuthClientId: String appAuthClientId: String
@@ -550,6 +597,43 @@ type License {
verified: Boolean verified: Boolean
} }
type GetBillingAndUsage {
subscription: Subscription
usage: Usage
}
type MonthlyQuota {
title: String
action: BillingCardAction
}
type NextBillAmount {
title: String
action: BillingCardAction
}
type NextBillDate {
title: String
action: BillingCardAction
}
type BillingCardAction {
type: String
text: String
src: String
}
type Subscription {
status: String
monthlyQuota: MonthlyQuota
nextBillAmount: NextBillAmount
nextBillDate: NextBillDate
}
type Usage {
task: Int
}
type Permission { type Permission {
id: String id: String
action: String action: String
@@ -608,6 +692,13 @@ input UpdateAppAuthClientInput {
active: Boolean active: Boolean
} }
type Notification {
name: String
createdAt: String
documentationUrl: String
description: String
}
schema { schema {
query: Query query: Query
mutation: Mutation mutation: Mutation

View File

@@ -40,6 +40,11 @@ export const authenticateUser = async (request, response, next) => {
const isAuthenticatedRule = rule()(isAuthenticated); const isAuthenticatedRule = rule()(isAuthenticated);
export const authenticationRules = { export const authenticationRules = {
Query: {
'*': isAuthenticatedRule,
getConfig: allow,
getNotifications: allow,
},
Mutation: { Mutation: {
'*': isAuthenticatedRule, '*': isAuthenticatedRule,
forgotPassword: allow, forgotPassword: allow,

View File

@@ -42,21 +42,19 @@ describe('authentication rules', () => {
const { queries, mutations } = getQueryAndMutationNames(authenticationRules); const { queries, mutations } = getQueryAndMutationNames(authenticationRules);
if (queries.length) { describe('for queries', () => {
describe('for queries', () => { queries.forEach((query) => {
queries.forEach((query) => { it(`should apply correct rule for query: ${query}`, () => {
it(`should apply correct rule for query: ${query}`, () => { const ruleApplied = authenticationRules.Query[query];
const ruleApplied = authenticationRules.Query[query];
if (query === '*') { if (query === '*') {
expect(ruleApplied.func).toBe(isAuthenticated); expect(ruleApplied.func).toBe(isAuthenticated);
} else { } else {
expect(ruleApplied).toEqual(allow); expect(ruleApplied).toEqual(allow);
} }
});
}); });
}); });
} });
describe('for mutations', () => { describe('for mutations', () => {
mutations.forEach((mutation) => { mutations.forEach((mutation) => {

View File

@@ -7,10 +7,6 @@ const authorizationList = {
action: 'read', action: 'read',
subject: 'User', subject: 'User',
}, },
'GET /api/v1/users/:userId/apps': {
action: 'read',
subject: 'Connection',
},
'GET /api/v1/flows/:flowId': { 'GET /api/v1/flows/:flowId': {
action: 'read', action: 'read',
subject: 'Flow', subject: 'Flow',
@@ -23,34 +19,14 @@ const authorizationList = {
action: 'read', action: 'read',
subject: 'Flow', subject: 'Flow',
}, },
'GET /api/v1/steps/:stepId/previous-steps': {
action: 'update',
subject: 'Flow',
},
'POST /api/v1/steps/:stepId/dynamic-fields': {
action: 'update',
subject: 'Flow',
},
'POST /api/v1/steps/:stepId/dynamic-data': {
action: 'update',
subject: 'Flow',
},
'GET /api/v1/connections/:connectionId/flows': { 'GET /api/v1/connections/:connectionId/flows': {
action: 'read', action: 'read',
subject: 'Flow', subject: 'Flow',
}, },
'POST /api/v1/connections/:connectionId/test': {
action: 'update',
subject: 'Connection',
},
'GET /api/v1/apps/:appKey/flows': { 'GET /api/v1/apps/:appKey/flows': {
action: 'read', action: 'read',
subject: 'Flow', subject: 'Flow',
}, },
'GET /api/v1/apps/:appKey/connections': {
action: 'read',
subject: 'Connection',
},
'GET /api/v1/executions/:executionId': { 'GET /api/v1/executions/:executionId': {
action: 'read', action: 'read',
subject: 'Execution', subject: 'Execution',

View File

@@ -2,7 +2,7 @@ import axios from 'axios';
import { HttpsProxyAgent } from 'https-proxy-agent'; import { HttpsProxyAgent } from 'https-proxy-agent';
import { HttpProxyAgent } from 'http-proxy-agent'; import { HttpProxyAgent } from 'http-proxy-agent';
const config = axios.defaults; const config = {};
const httpProxyUrl = process.env.http_proxy; const httpProxyUrl = process.env.http_proxy;
const httpsProxyUrl = process.env.https_proxy; const httpsProxyUrl = process.env.https_proxy;
const supportsProxy = httpProxyUrl || httpsProxyUrl; const supportsProxy = httpProxyUrl || httpsProxyUrl;

View File

@@ -2,7 +2,6 @@ import logger from './logger.js';
import objection from 'objection'; import objection from 'objection';
import * as Sentry from './sentry.ee.js'; import * as Sentry from './sentry.ee.js';
const { NotFoundError, DataError } = objection; const { NotFoundError, DataError } = objection;
import HttpError from '../errors/http.js';
// Do not remove `next` argument as the function signature will not fit for an error handler middleware // Do not remove `next` argument as the function signature will not fit for an error handler middleware
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
@@ -19,17 +18,6 @@ const errorHandler = (error, request, response, next) => {
response.status(400).end(); response.status(400).end();
} }
if (error instanceof HttpError) {
const httpErrorPayload = {
errors: JSON.parse(error.message),
meta: {
type: 'HttpError',
},
};
response.status(200).json(httpErrorPayload);
}
const statusCode = error.statusCode || 500; const statusCode = error.statusCode || 500;
logger.error(request.method + ' ' + request.url + ' ' + statusCode); logger.error(request.method + ' ' + request.url + ' ' + statusCode);
@@ -49,7 +37,7 @@ const errorHandler = (error, request, response, next) => {
const notFoundAppError = (error) => { const notFoundAppError = (error) => {
return ( return (
error.message.includes('An application with the') && error.message.includes('An application with the') ||
error.message.includes("key couldn't be found.") error.message.includes("key couldn't be found.")
); );
}; };

View File

@@ -9,7 +9,7 @@ const stream = {
const registerGraphQLToken = () => { const registerGraphQLToken = () => {
morgan.token('graphql-query', (req) => { morgan.token('graphql-query', (req) => {
if (req.body.query) { if (req.body.query) {
return `\n GraphQL ${req.body.query}`; return `GraphQL ${req.body.query}`;
} }
}); });
}; };
@@ -17,7 +17,7 @@ const registerGraphQLToken = () => {
registerGraphQLToken(); registerGraphQLToken();
const morganMiddleware = morgan( const morganMiddleware = morgan(
':method :url :status :res[content-length] - :response-time ms :graphql-query', ':method :url :status :res[content-length] - :response-time ms\n:graphql-query',
{ stream } { stream }
); );

View File

@@ -44,22 +44,4 @@ const renderObject = (response, object, options) => {
return response.json(computedPayload); return response.json(computedPayload);
}; };
const renderError = (response, errors, status, type) => { export { renderObject };
const errorStatus = status || 422;
const errorType = type || 'ValidationError';
const payload = {
errors: errors.reduce((acc, error) => {
const key = Object.keys(error)[0];
acc[key] = error[key];
return acc;
}, {}),
meta: {
type: errorType,
},
};
return response.status(errorStatus).send(payload);
};
export { renderObject, renderError };

View File

@@ -75,20 +75,9 @@ export default async (flowId, request, response) => {
}); });
if (actionStep.key === 'respondWith' && !response.headersSent) { if (actionStep.key === 'respondWith' && !response.headersSent) {
const { headers, statusCode, body } = actionExecutionStep.dataOut;
// we set the custom response headers
if (headers) {
for (const [key, value] of Object.entries(headers)) {
if (key) {
response.set(key, value);
}
}
}
// we send the response only if it's not sent yet. This allows us to early respond from the flow. // we send the response only if it's not sent yet. This allows us to early respond from the flow.
response.status(statusCode); response.status(actionExecutionStep.dataOut.statusCode);
response.send(body); response.send(actionExecutionStep.dataOut.body);
} }
} }
} }

View File

@@ -1,6 +1,7 @@
import AES from 'crypto-js/aes.js'; import AES from 'crypto-js/aes.js';
import enc from 'crypto-js/enc-utf8.js'; import enc from 'crypto-js/enc-utf8.js';
import appConfig from '../config/app.js'; import appConfig from '../config/app.js';
import AppConfig from './app-config.js';
import Base from './base.js'; import Base from './base.js';
class AppAuthClient extends Base { class AppAuthClient extends Base {
@@ -8,11 +9,11 @@ class AppAuthClient extends Base {
static jsonSchema = { static jsonSchema = {
type: 'object', type: 'object',
required: ['name', 'appKey', 'formattedAuthDefaults'], required: ['name', 'appConfigId', 'formattedAuthDefaults'],
properties: { properties: {
id: { type: 'string', format: 'uuid' }, id: { type: 'string', format: 'uuid' },
appKey: { type: 'string' }, appConfigId: { type: 'string', format: 'uuid' },
active: { type: 'boolean' }, active: { type: 'boolean' },
authDefaults: { type: ['string', 'null'] }, authDefaults: { type: ['string', 'null'] },
formattedAuthDefaults: { type: 'object' }, formattedAuthDefaults: { type: 'object' },
@@ -21,6 +22,17 @@ class AppAuthClient extends Base {
}, },
}; };
static relationMappings = () => ({
appConfig: {
relation: Base.BelongsToOneRelation,
modelClass: AppConfig,
join: {
from: 'app_auth_clients.app_config_id',
to: 'app_configs.id',
},
},
});
encryptData() { encryptData() {
if (!this.eligibleForEncryption()) return; if (!this.eligibleForEncryption()) return;

View File

@@ -1,6 +1,6 @@
import App from './app.js'; import App from './app.js';
import AppAuthClient from './app-auth-client.js';
import Base from './base.js'; import Base from './base.js';
import AppAuthClient from './app-auth-client.js';
class AppConfig extends Base { class AppConfig extends Base {
static tableName = 'app_configs'; static tableName = 'app_configs';
@@ -18,21 +18,21 @@ class AppConfig extends Base {
}, },
}; };
static get virtualAttributes() {
return ['canConnect', 'canCustomConnect'];
}
static relationMappings = () => ({ static relationMappings = () => ({
appAuthClients: { appAuthClients: {
relation: Base.HasManyRelation, relation: Base.HasManyRelation,
modelClass: AppAuthClient, modelClass: AppAuthClient,
join: { join: {
from: 'app_configs.key', from: 'app_configs.id',
to: 'app_auth_clients.app_key', to: 'app_auth_clients.app_config_id',
}, },
}, },
}); });
static get virtualAttributes() {
return ['canConnect', 'canCustomConnect'];
}
get canCustomConnect() { get canCustomConnect() {
return !this.disabled && this.allowCustomConnection; return !this.disabled && this.allowCustomConnection;
} }

View File

@@ -153,24 +153,6 @@ class Connection extends Base {
return await App.findOneByKey(this.key); return await App.findOneByKey(this.key);
} }
async testAndUpdateConnection() {
const app = await this.getApp();
const $ = await globalVariable({ connection: this, app });
let isStillVerified;
try {
isStillVerified = !!(await app.auth.isStillVerified($));
} catch {
isStillVerified = false;
}
return await this.$query().patchAndFetch({
formattedData: this.formattedData,
verified: isStillVerified,
});
}
async verifyWebhook(request) { async verifyWebhook(request) {
if (!this.key) return true; if (!this.key) return true;

View File

@@ -160,7 +160,7 @@ class Flow extends Base {
} }
async isPaused() { async isPaused() {
const user = await this.$relatedQuery('user').withSoftDeleted(); const user = await this.$relatedQuery('user');
const allowedToRunFlows = await user.isAllowedToRunFlows(); const allowedToRunFlows = await user.isAllowedToRunFlows();
return allowedToRunFlows ? false : true; return allowedToRunFlows ? false : true;
} }

View File

@@ -7,8 +7,6 @@ import Connection from './connection.js';
import ExecutionStep from './execution-step.js'; import ExecutionStep from './execution-step.js';
import Telemetry from '../helpers/telemetry/index.js'; import Telemetry from '../helpers/telemetry/index.js';
import appConfig from '../config/app.js'; import appConfig from '../config/app.js';
import globalVariable from '../helpers/global-variable.js';
import computeParameters from '../helpers/compute-parameters.js';
class Step extends Base { class Step extends Base {
static tableName = 'steps'; static tableName = 'steps';
@@ -198,59 +196,6 @@ class Step extends Base {
return existingArguments; return existingArguments;
} }
async createDynamicFields(dynamicFieldsKey, parameters) {
const connection = await this.$relatedQuery('connection');
const flow = await this.$relatedQuery('flow');
const app = await this.getApp();
const $ = await globalVariable({ connection, app, flow, step: this });
const command = app.dynamicFields.find(
(data) => data.key === dynamicFieldsKey
);
for (const parameterKey in parameters) {
const parameterValue = parameters[parameterKey];
$.step.parameters[parameterKey] = parameterValue;
}
const dynamicFields = (await command.run($)) || [];
return dynamicFields;
}
async createDynamicData(dynamicDataKey, parameters) {
const connection = await this.$relatedQuery('connection');
const flow = await this.$relatedQuery('flow');
const app = await this.getApp();
const $ = await globalVariable({ connection, app, flow, step: this });
const command = app.dynamicData.find((data) => data.key === dynamicDataKey);
for (const parameterKey in parameters) {
const parameterValue = parameters[parameterKey];
$.step.parameters[parameterKey] = parameterValue;
}
const lastExecution = await flow.$relatedQuery('lastExecution');
const lastExecutionId = lastExecution?.id;
const priorExecutionSteps = lastExecutionId
? await ExecutionStep.query().where({
execution_id: lastExecutionId,
})
: [];
const computedParameters = computeParameters(
$.step.parameters,
priorExecutionSteps
);
$.step.parameters = computedParameters;
const dynamicData = (await command.run($)).data;
return dynamicData;
}
async updateWebhookUrl() { async updateWebhookUrl() {
if (this.isAction) return this; if (this.isAction) return this;

View File

@@ -5,9 +5,7 @@ import crypto from 'node:crypto';
import appConfig from '../config/app.js'; import appConfig from '../config/app.js';
import { hasValidLicense } from '../helpers/license.ee.js'; import { hasValidLicense } from '../helpers/license.ee.js';
import userAbility from '../helpers/user-ability.js'; import userAbility from '../helpers/user-ability.js';
import createAuthTokenByUserId from '../helpers/create-auth-token-by-user-id.js';
import Base from './base.js'; import Base from './base.js';
import App from './app.js';
import Connection from './connection.js'; import Connection from './connection.js';
import Execution from './execution.js'; import Execution from './execution.js';
import Flow from './flow.js'; import Flow from './flow.js';
@@ -156,13 +154,6 @@ class User extends Base {
return conditions.isCreator ? this.$relatedQuery('steps') : Step.query(); return conditions.isCreator ? this.$relatedQuery('steps') : Step.query();
} }
get authorizedConnections() {
const conditions = this.can('read', 'Connection');
return conditions.isCreator
? this.$relatedQuery('connections')
: Connection.query();
}
get authorizedExecutions() { get authorizedExecutions() {
const conditions = this.can('read', 'Execution'); const conditions = this.can('read', 'Execution');
return conditions.isCreator return conditions.isCreator
@@ -170,17 +161,6 @@ class User extends Base {
: Execution.query(); : Execution.query();
} }
static async authenticate(email, password) {
const user = await User.query().findOne({
email: email?.toLowerCase() || null,
});
if (user && (await user.login(password))) {
const token = createAuthTokenByUserId(user.id);
return token;
}
}
login(password) { login(password) {
return bcrypt.compare(password, this.password); return bcrypt.compare(password, this.password);
} }
@@ -314,56 +294,6 @@ class User extends Base {
return invoices; return invoices;
} }
async getApps(name) {
const connections = await this.authorizedConnections
.clone()
.select('connections.key')
.where({ draft: false })
.count('connections.id as count')
.groupBy('connections.key');
const flows = await this.authorizedFlows
.clone()
.withGraphJoined('steps')
.orderBy('created_at', 'desc');
const duplicatedUsedApps = flows
.map((flow) => flow.steps.map((step) => step.appKey))
.flat()
.filter(Boolean);
const connectionKeys = connections.map((connection) => connection.key);
const usedApps = [...new Set([...duplicatedUsedApps, ...connectionKeys])];
let apps = await App.findAll(name);
apps = apps
.filter((app) => {
return usedApps.includes(app.key);
})
.map((app) => {
const connection = connections.find(
(connection) => connection.key === app.key
);
app.connectionCount = connection?.count || 0;
app.flowCount = 0;
flows.forEach((flow) => {
const usedFlow = flow.steps.find((step) => step.appKey === app.key);
if (usedFlow) {
app.flowCount += 1;
}
});
return app;
})
.sort((appA, appB) => appA.name.localeCompare(appB.name));
return apps;
}
async $beforeInsert(queryContext) { async $beforeInsert(queryContext) {
await super.$beforeInsert(queryContext); await super.$beforeInsert(queryContext);

View File

@@ -1,9 +0,0 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import createAccessTokenAction from '../../../controllers/api/v1/access-tokens/create-access-token.js';
const router = Router();
router.post('/', asyncHandler(createAccessTokenAction));
export default router;

View File

@@ -3,25 +3,16 @@ import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../../helpers/authentication.js'; import { authenticateUser } from '../../../../helpers/authentication.js';
import { authorizeAdmin } from '../../../../helpers/authorization.js'; import { authorizeAdmin } from '../../../../helpers/authorization.js';
import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js'; import { checkIsEnterprise } from '../../../../helpers/check-is-enterprise.js';
import getAuthClientsAction from '../../../../controllers/api/v1/admin/apps/get-auth-clients.ee.js'; import getAdminAppAuthClientsAction from '../../../../controllers/api/v1/admin/app-auth-clients/get-app-auth-client.js';
import getAuthClientAction from '../../../../controllers/api/v1/admin/apps/get-auth-client.ee.js';
const router = Router(); const router = Router();
router.get( router.get(
'/:appKey/auth-clients', '/:appAuthClientId',
authenticateUser, authenticateUser,
authorizeAdmin, authorizeAdmin,
checkIsEnterprise, checkIsEnterprise,
asyncHandler(getAuthClientsAction) asyncHandler(getAdminAppAuthClientsAction)
);
router.get(
'/:appKey/auth-clients/:appAuthClientId',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
asyncHandler(getAuthClientAction)
); );
export default router; export default router;

View File

@@ -0,0 +1,16 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../helpers/authentication.js';
import { checkIsEnterprise } from '../../../helpers/check-is-enterprise.js';
import getAppAuthClientAction from '../../../controllers/api/v1/app-auth-clients/get-app-auth-client.js';
const router = Router();
router.get(
'/:appAuthClientId',
authenticateUser,
checkIsEnterprise,
asyncHandler(getAppAuthClientAction)
);
export default router;

View File

@@ -0,0 +1,16 @@
import { Router } from 'express';
import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../helpers/authentication.js';
import { checkIsEnterprise } from '../../../helpers/check-is-enterprise.js';
import getAppConfigAction from '../../../controllers/api/v1/app-configs/get-app-config.ee.js';
const router = Router();
router.get(
'/:appKey',
authenticateUser,
checkIsEnterprise,
asyncHandler(getAppConfigAction)
);
export default router;

View File

@@ -2,14 +2,9 @@ import { Router } from 'express';
import asyncHandler from 'express-async-handler'; import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../helpers/authentication.js'; import { authenticateUser } from '../../../helpers/authentication.js';
import { authorizeUser } from '../../../helpers/authorization.js'; import { authorizeUser } from '../../../helpers/authorization.js';
import { checkIsEnterprise } from '../../../helpers/check-is-enterprise.js';
import getAppAction from '../../../controllers/api/v1/apps/get-app.js'; import getAppAction from '../../../controllers/api/v1/apps/get-app.js';
import getAppsAction from '../../../controllers/api/v1/apps/get-apps.js'; import getAppsAction from '../../../controllers/api/v1/apps/get-apps.js';
import getAuthAction from '../../../controllers/api/v1/apps/get-auth.js'; import getAuthAction from '../../../controllers/api/v1/apps/get-auth.js';
import getConnectionsAction from '../../../controllers/api/v1/apps/get-connections.js';
import getConfigAction from '../../../controllers/api/v1/apps/get-config.ee.js';
import getAuthClientsAction from '../../../controllers/api/v1/apps/get-auth-clients.ee.js';
import getAuthClientAction from '../../../controllers/api/v1/apps/get-auth-client.ee.js';
import getTriggersAction from '../../../controllers/api/v1/apps/get-triggers.js'; import getTriggersAction from '../../../controllers/api/v1/apps/get-triggers.js';
import getTriggerSubstepsAction from '../../../controllers/api/v1/apps/get-trigger-substeps.js'; import getTriggerSubstepsAction from '../../../controllers/api/v1/apps/get-trigger-substeps.js';
import getActionsAction from '../../../controllers/api/v1/apps/get-actions.js'; import getActionsAction from '../../../controllers/api/v1/apps/get-actions.js';
@@ -22,34 +17,6 @@ router.get('/', authenticateUser, asyncHandler(getAppsAction));
router.get('/:appKey', authenticateUser, asyncHandler(getAppAction)); router.get('/:appKey', authenticateUser, asyncHandler(getAppAction));
router.get('/:appKey/auth', authenticateUser, asyncHandler(getAuthAction)); router.get('/:appKey/auth', authenticateUser, asyncHandler(getAuthAction));
router.get(
'/:appKey/connections',
authenticateUser,
authorizeUser,
asyncHandler(getConnectionsAction)
);
router.get(
'/:appKey/config',
authenticateUser,
checkIsEnterprise,
asyncHandler(getConfigAction)
);
router.get(
'/:appKey/auth-clients',
authenticateUser,
checkIsEnterprise,
asyncHandler(getAuthClientsAction)
);
router.get(
'/:appKey/auth-clients/:appAuthClientId',
authenticateUser,
checkIsEnterprise,
asyncHandler(getAuthClientAction)
);
router.get( router.get(
'/:appKey/triggers', '/:appKey/triggers',
authenticateUser, authenticateUser,

View File

@@ -3,7 +3,6 @@ import asyncHandler from 'express-async-handler';
import { authenticateUser } from '../../../helpers/authentication.js'; import { authenticateUser } from '../../../helpers/authentication.js';
import { authorizeUser } from '../../../helpers/authorization.js'; import { authorizeUser } from '../../../helpers/authorization.js';
import getFlowsAction from '../../../controllers/api/v1/connections/get-flows.js'; import getFlowsAction from '../../../controllers/api/v1/connections/get-flows.js';
import createTestAction from '../../../controllers/api/v1/connections/create-test.js';
const router = Router(); const router = Router();
@@ -14,11 +13,4 @@ router.get(
asyncHandler(getFlowsAction) asyncHandler(getFlowsAction)
); );
router.post(
'/:connectionId/test',
authenticateUser,
authorizeUser,
asyncHandler(createTestAction)
);
export default router; export default router;

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