Compare commits
1 Commits
gmail-inte
...
snackbar-o
Author | SHA1 | Date | |
---|---|---|---|
![]() |
ec5ef28a7a |
32
.github/workflows/docs-change.yml
vendored
32
.github/workflows/docs-change.yml
vendored
@@ -1,32 +0,0 @@
|
|||||||
name: Automatisch Docs Change
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- 'packages/docs/**'
|
|
||||||
jobs:
|
|
||||||
label:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
- name: Label PR
|
|
||||||
uses: actions/github-script@v6
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const { pull_request } = context.payload;
|
|
||||||
|
|
||||||
const label = 'documentation-change';
|
|
||||||
const hasLabel = pull_request.labels.some(({ name }) => name === label);
|
|
||||||
|
|
||||||
if (!hasLabel) {
|
|
||||||
await github.rest.issues.addLabels({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
issue_number: pull_request.number,
|
|
||||||
labels: [label],
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Label "${label}" added to PR #${pull_request.number}`);
|
|
||||||
} else {
|
|
||||||
console.log(`Label "${label}" already exists on PR #${pull_request.number}`);
|
|
||||||
}
|
|
@@ -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 |
@@ -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,
|
|
||||||
});
|
|
||||||
}
|
|
@@ -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,
|
|
||||||
};
|
|
@@ -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;
|
|
@@ -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;
|
|
@@ -1,42 +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}`,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default verifyCredentials;
|
|
@@ -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;
|
|
@@ -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;
|
|
@@ -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;
|
|
@@ -1,15 +0,0 @@
|
|||||||
import defineApp from '../../helpers/define-app.js';
|
|
||||||
import addAuthHeader from './common/add-auth-header.js';
|
|
||||||
import auth from './auth/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,
|
|
||||||
});
|
|
@@ -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');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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',
|
||||||
|
@@ -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,
|
||||||
];
|
];
|
||||||
|
@@ -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;
|
|
||||||
},
|
|
||||||
};
|
|
@@ -1,3 +0,0 @@
|
|||||||
import respondWith from './respond-with/index.js';
|
|
||||||
|
|
||||||
export default [respondWith];
|
|
@@ -1,69 +0,0 @@
|
|||||||
import defineAction from '../../../../helpers/define-action.js';
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Respond with',
|
|
||||||
key: 'respondWith',
|
|
||||||
description: 'Respond with defined JSON body.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Status code',
|
|
||||||
key: 'statusCode',
|
|
||||||
type: 'string',
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
value: '200',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Headers',
|
|
||||||
key: 'headers',
|
|
||||||
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',
|
|
||||||
required: true,
|
|
||||||
description: 'The content of the response body.',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const statusCode = parseInt($.step.parameters.statusCode, 10);
|
|
||||||
const body = $.step.parameters.body;
|
|
||||||
const headers = $.step.parameters.headers.reduce((result, entry) => {
|
|
||||||
return {
|
|
||||||
...result,
|
|
||||||
[entry.key]: entry.value,
|
|
||||||
};
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
$.setActionItem({
|
|
||||||
raw: {
|
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
statusCode,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,5 +1,4 @@
|
|||||||
import defineApp from '../../helpers/define-app.js';
|
import defineApp from '../../helpers/define-app.js';
|
||||||
import actions from './actions/index.js';
|
|
||||||
import triggers from './triggers/index.js';
|
import triggers from './triggers/index.js';
|
||||||
|
|
||||||
export default defineApp({
|
export default defineApp({
|
||||||
@@ -11,6 +10,5 @@ export default defineApp({
|
|||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
apiBaseUrl: '',
|
apiBaseUrl: '',
|
||||||
primaryColor: '0059F7',
|
primaryColor: '0059F7',
|
||||||
actions,
|
|
||||||
triggers,
|
triggers,
|
||||||
});
|
});
|
||||||
|
@@ -7,20 +7,7 @@ export default defineTrigger({
|
|||||||
key: 'catchRawWebhook',
|
key: 'catchRawWebhook',
|
||||||
type: 'webhook',
|
type: 'webhook',
|
||||||
showWebhookUrl: true,
|
showWebhookUrl: true,
|
||||||
description:
|
description: 'Triggers when the webhook receives a request.',
|
||||||
'Triggers (immediately if configured) when the webhook receives a request.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Wait until flow is done',
|
|
||||||
key: 'workSynchronously',
|
|
||||||
type: 'dropdown',
|
|
||||||
required: true,
|
|
||||||
options: [
|
|
||||||
{ label: 'Yes', value: true },
|
|
||||||
{ label: 'No', value: false },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
async run($) {
|
||||||
const dataItem = {
|
const dataItem = {
|
||||||
|
@@ -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.'] }]);
|
|
||||||
};
|
|
@@ -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.',
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -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);
|
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -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);
|
|
||||||
};
|
|
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,14 +0,0 @@
|
|||||||
import { renderObject } from '../../../../../helpers/renderer.js';
|
|
||||||
import SamlAuthProvider from '../../../../../models/saml-auth-provider.ee.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const samlAuthProvider = await SamlAuthProvider.query()
|
|
||||||
.findById(request.params.samlAuthProviderId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
const roleMappings = await samlAuthProvider
|
|
||||||
.$relatedQuery('samlAuthProvidersRoleMappings')
|
|
||||||
.orderBy('remote_role_name', 'asc');
|
|
||||||
|
|
||||||
renderObject(response, roleMappings);
|
|
||||||
};
|
|
@@ -1,51 +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 { createRole } from '../../../../../../test/factories/role.js';
|
|
||||||
import { createUser } from '../../../../../../test/factories/user.js';
|
|
||||||
import { createSamlAuthProvider } from '../../../../../../test/factories/saml-auth-provider.ee.js';
|
|
||||||
import { createRoleMapping } from '../../../../../../test/factories/role-mapping.js';
|
|
||||||
import getRoleMappingsMock from '../../../../../../test/mocks/rest/api/v1/admin/saml-auth-providers/get-role-mappings.ee.js';
|
|
||||||
import * as license from '../../../../../helpers/license.ee.js';
|
|
||||||
|
|
||||||
describe('GET /api/v1/admin/saml-auth-providers/:samlAuthProviderId/role-mappings', () => {
|
|
||||||
let roleMappingOne, roleMappingTwo, samlAuthProvider, currentUser, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const role = await createRole({ key: 'admin' });
|
|
||||||
currentUser = await createUser({ roleId: role.id });
|
|
||||||
|
|
||||||
samlAuthProvider = await createSamlAuthProvider();
|
|
||||||
|
|
||||||
roleMappingOne = await createRoleMapping({
|
|
||||||
samlAuthProviderId: samlAuthProvider.id,
|
|
||||||
remoteRoleName: 'Admin',
|
|
||||||
});
|
|
||||||
|
|
||||||
roleMappingTwo = await createRoleMapping({
|
|
||||||
samlAuthProviderId: samlAuthProvider.id,
|
|
||||||
remoteRoleName: 'User',
|
|
||||||
});
|
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return role mappings', async () => {
|
|
||||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(
|
|
||||||
`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}/role-mappings`
|
|
||||||
)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = await getRoleMappingsMock([
|
|
||||||
roleMappingOne,
|
|
||||||
roleMappingTwo,
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -6,7 +6,5 @@ export default async (request, response) => {
|
|||||||
.findById(request.params.samlAuthProviderId)
|
.findById(request.params.samlAuthProviderId)
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
renderObject(response, samlAuthProvider, {
|
renderObject(response, samlAuthProvider);
|
||||||
serializer: 'AdminSamlAuthProvider',
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
@@ -7,7 +7,5 @@ export default async (request, response) => {
|
|||||||
'desc'
|
'desc'
|
||||||
);
|
);
|
||||||
|
|
||||||
renderObject(response, samlAuthProviders, {
|
renderObject(response, samlAuthProviders);
|
||||||
serializer: 'AdminSamlAuthProvider',
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
@@ -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);
|
@@ -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);
|
||||||
});
|
});
|
@@ -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);
|
|
||||||
};
|
|
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,15 +0,0 @@
|
|||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
import AppConfig from '../../../../models/app-config.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const appConfig = await AppConfig.query()
|
|
||||||
.withGraphFetched({
|
|
||||||
appAuthClients: true,
|
|
||||||
})
|
|
||||||
.findOne({
|
|
||||||
key: request.params.appKey,
|
|
||||||
})
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
renderObject(response, appConfig);
|
|
||||||
};
|
|
@@ -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 getAppConfigMock from '../../../../../test/mocks/rest/api/v1/apps/get-config.js';
|
|
||||||
import { createAppConfig } from '../../../../../test/factories/app-config.js';
|
|
||||||
import * as license from '../../../../helpers/license.ee.js';
|
|
||||||
|
|
||||||
describe('GET /api/v1/apps/:appKey/config', () => {
|
|
||||||
let currentUser, appConfig, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
|
||||||
|
|
||||||
currentUser = await createUser();
|
|
||||||
|
|
||||||
appConfig = await createAppConfig({
|
|
||||||
key: 'deepl',
|
|
||||||
allowCustomConnection: true,
|
|
||||||
shared: true,
|
|
||||||
disabled: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return specified app config info', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.get(`/api/v1/apps/${appConfig.key}/config`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = getAppConfigMock(appConfig);
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return not found response for not existing app key', async () => {
|
|
||||||
await request(app)
|
|
||||||
.get('/api/v1/apps/not-existing-app-key/config')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(404);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -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);
|
|
||||||
};
|
|
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,23 +0,0 @@
|
|||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
import App from '../../../../models/app.js';
|
|
||||||
import paginateRest from '../../../../helpers/pagination-rest.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const app = await App.findOneByKey(request.params.appKey);
|
|
||||||
|
|
||||||
const flowsQuery = request.currentUser.authorizedFlows
|
|
||||||
.clone()
|
|
||||||
.joinRelated({
|
|
||||||
steps: true,
|
|
||||||
})
|
|
||||||
.withGraphFetched({
|
|
||||||
steps: true,
|
|
||||||
})
|
|
||||||
.where('steps.app_key', app.key)
|
|
||||||
.orderBy('active', 'desc')
|
|
||||||
.orderBy('updated_at', 'desc');
|
|
||||||
|
|
||||||
const flows = await paginateRest(flowsQuery, request.query.page);
|
|
||||||
|
|
||||||
renderObject(response, flows);
|
|
||||||
};
|
|
@@ -1,129 +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 { createFlow } from '../../../../../test/factories/flow.js';
|
|
||||||
import { createStep } from '../../../../../test/factories/step.js';
|
|
||||||
import { createPermission } from '../../../../../test/factories/permission.js';
|
|
||||||
import getFlowsMock from '../../../../../test/mocks/rest/api/v1/flows/get-flows.js';
|
|
||||||
|
|
||||||
describe('GET /api/v1/apps/:appKey/flows', () => {
|
|
||||||
let currentUser, currentUserRole, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
currentUser = await createUser();
|
|
||||||
currentUserRole = await currentUser.$relatedQuery('role');
|
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the flows data of specified app for current user', async () => {
|
|
||||||
const currentUserFlowOne = await createFlow({ userId: currentUser.id });
|
|
||||||
|
|
||||||
const triggerStepFlowOne = await createStep({
|
|
||||||
flowId: currentUserFlowOne.id,
|
|
||||||
type: 'trigger',
|
|
||||||
appKey: 'webhook',
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionStepFlowOne = await createStep({
|
|
||||||
flowId: currentUserFlowOne.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentUserFlowTwo = await createFlow({ userId: currentUser.id });
|
|
||||||
|
|
||||||
await createStep({
|
|
||||||
flowId: currentUserFlowTwo.id,
|
|
||||||
type: 'trigger',
|
|
||||||
appKey: 'github',
|
|
||||||
});
|
|
||||||
|
|
||||||
await createStep({
|
|
||||||
flowId: currentUserFlowTwo.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: ['isCreator'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get('/api/v1/apps/webhook/flows')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = await getFlowsMock(
|
|
||||||
[currentUserFlowOne],
|
|
||||||
[triggerStepFlowOne, actionStepFlowOne]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the flows data of specified app for another user', async () => {
|
|
||||||
const anotherUser = await createUser();
|
|
||||||
const anotherUserFlowOne = await createFlow({ userId: anotherUser.id });
|
|
||||||
|
|
||||||
const triggerStepFlowOne = await createStep({
|
|
||||||
flowId: anotherUserFlowOne.id,
|
|
||||||
type: 'trigger',
|
|
||||||
appKey: 'webhook',
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionStepFlowOne = await createStep({
|
|
||||||
flowId: anotherUserFlowOne.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
const anotherUserFlowTwo = await createFlow({ userId: anotherUser.id });
|
|
||||||
|
|
||||||
await createStep({
|
|
||||||
flowId: anotherUserFlowTwo.id,
|
|
||||||
type: 'trigger',
|
|
||||||
appKey: 'github',
|
|
||||||
});
|
|
||||||
|
|
||||||
await createStep({
|
|
||||||
flowId: anotherUserFlowTwo.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get('/api/v1/apps/webhook/flows')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = await getFlowsMock(
|
|
||||||
[anotherUserFlowOne],
|
|
||||||
[triggerStepFlowOne, actionStepFlowOne]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return not found response for invalid app key', async () => {
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: ['isCreator'],
|
|
||||||
});
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get('/api/v1/apps/invalid-app-key/flows')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(404);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,24 +0,0 @@
|
|||||||
import appConfig from '../../../../config/app.js';
|
|
||||||
import Config from '../../../../models/config.js';
|
|
||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const defaultConfig = {
|
|
||||||
disableNotificationsPage: appConfig.disableNotificationsPage,
|
|
||||||
disableFavicon: appConfig.disableFavicon,
|
|
||||||
additionalDrawerLink: appConfig.additionalDrawerLink,
|
|
||||||
additionalDrawerLinkText: appConfig.additionalDrawerLinkText,
|
|
||||||
};
|
|
||||||
|
|
||||||
let config = await Config.query().orderBy('key', 'asc');
|
|
||||||
|
|
||||||
config = config.reduce((computedConfig, configEntry) => {
|
|
||||||
const { key, value } = configEntry;
|
|
||||||
|
|
||||||
computedConfig[key] = value?.data;
|
|
||||||
|
|
||||||
return computedConfig;
|
|
||||||
}, defaultConfig);
|
|
||||||
|
|
||||||
renderObject(response, config);
|
|
||||||
};
|
|
@@ -1,51 +0,0 @@
|
|||||||
import { vi, expect, describe, it } from 'vitest';
|
|
||||||
import request from 'supertest';
|
|
||||||
import { createConfig } from '../../../../../test/factories/config.js';
|
|
||||||
import app from '../../../../app.js';
|
|
||||||
import configMock from '../../../../../test/mocks/rest/api/v1/automatisch/config.js';
|
|
||||||
import * as license from '../../../../helpers/license.ee.js';
|
|
||||||
|
|
||||||
describe('GET /api/v1/automatisch/config', () => {
|
|
||||||
it('should return Automatisch config', async () => {
|
|
||||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
|
||||||
|
|
||||||
const logoConfig = await createConfig({
|
|
||||||
key: 'logo.svgData',
|
|
||||||
value: { data: '<svg>Sample</svg>' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const primaryDarkConfig = await createConfig({
|
|
||||||
key: 'palette.primary.dark',
|
|
||||||
value: { data: '#001F52' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const primaryLightConfig = await createConfig({
|
|
||||||
key: 'palette.primary.light',
|
|
||||||
value: { data: '#4286FF' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const primaryMainConfig = await createConfig({
|
|
||||||
key: 'palette.primary.main',
|
|
||||||
value: { data: '#0059F7' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const titleConfig = await createConfig({
|
|
||||||
key: 'title',
|
|
||||||
value: { data: 'Sample Title' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get('/api/v1/automatisch/config')
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = configMock(
|
|
||||||
logoConfig,
|
|
||||||
primaryDarkConfig,
|
|
||||||
primaryLightConfig,
|
|
||||||
primaryMainConfig,
|
|
||||||
titleConfig
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -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);
|
|
||||||
};
|
|
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,20 +0,0 @@
|
|||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
import paginateRest from '../../../../helpers/pagination-rest.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const flowsQuery = request.currentUser.authorizedFlows
|
|
||||||
.clone()
|
|
||||||
.joinRelated({
|
|
||||||
steps: true,
|
|
||||||
})
|
|
||||||
.withGraphFetched({
|
|
||||||
steps: true,
|
|
||||||
})
|
|
||||||
.where('steps.connection_id', request.params.connectionId)
|
|
||||||
.orderBy('active', 'desc')
|
|
||||||
.orderBy('updated_at', 'desc');
|
|
||||||
|
|
||||||
const flows = await paginateRest(flowsQuery, request.query.page);
|
|
||||||
|
|
||||||
renderObject(response, flows);
|
|
||||||
};
|
|
@@ -1,128 +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 { createFlow } from '../../../../../test/factories/flow.js';
|
|
||||||
import { createStep } from '../../../../../test/factories/step.js';
|
|
||||||
import { createPermission } from '../../../../../test/factories/permission.js';
|
|
||||||
import getFlowsMock from '../../../../../test/mocks/rest/api/v1/flows/get-flows.js';
|
|
||||||
|
|
||||||
describe('GET /api/v1/connections/:connectionId/flows', () => {
|
|
||||||
let currentUser, currentUserRole, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
currentUser = await createUser();
|
|
||||||
currentUserRole = await currentUser.$relatedQuery('role');
|
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the flows data of specified connection for current user', async () => {
|
|
||||||
const currentUserFlowOne = await createFlow({ userId: currentUser.id });
|
|
||||||
|
|
||||||
const currentUserConnection = await createConnection({
|
|
||||||
userId: currentUser.id,
|
|
||||||
key: 'webhook',
|
|
||||||
});
|
|
||||||
|
|
||||||
const triggerStepFlowOne = await createStep({
|
|
||||||
flowId: currentUserFlowOne.id,
|
|
||||||
type: 'trigger',
|
|
||||||
appKey: 'webhook',
|
|
||||||
connectionId: currentUserConnection.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionStepFlowOne = await createStep({
|
|
||||||
flowId: currentUserFlowOne.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentUserFlowTwo = await createFlow({ userId: currentUser.id });
|
|
||||||
|
|
||||||
await createStep({
|
|
||||||
flowId: currentUserFlowTwo.id,
|
|
||||||
type: 'trigger',
|
|
||||||
appKey: 'github',
|
|
||||||
});
|
|
||||||
|
|
||||||
await createStep({
|
|
||||||
flowId: currentUserFlowTwo.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: ['isCreator'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(`/api/v1/connections/${currentUserConnection.id}/flows`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = await getFlowsMock(
|
|
||||||
[currentUserFlowOne],
|
|
||||||
[triggerStepFlowOne, actionStepFlowOne]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the flows data of specified connection for another user', async () => {
|
|
||||||
const anotherUser = await createUser();
|
|
||||||
const anotherUserFlowOne = await createFlow({ userId: anotherUser.id });
|
|
||||||
|
|
||||||
const anotherUserConnection = await createConnection({
|
|
||||||
userId: anotherUser.id,
|
|
||||||
key: 'webhook',
|
|
||||||
});
|
|
||||||
|
|
||||||
const triggerStepFlowOne = await createStep({
|
|
||||||
flowId: anotherUserFlowOne.id,
|
|
||||||
type: 'trigger',
|
|
||||||
appKey: 'webhook',
|
|
||||||
connectionId: anotherUserConnection.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionStepFlowOne = await createStep({
|
|
||||||
flowId: anotherUserFlowOne.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
const anotherUserFlowTwo = await createFlow({ userId: anotherUser.id });
|
|
||||||
|
|
||||||
await createStep({
|
|
||||||
flowId: anotherUserFlowTwo.id,
|
|
||||||
type: 'trigger',
|
|
||||||
appKey: 'github',
|
|
||||||
});
|
|
||||||
|
|
||||||
await createStep({
|
|
||||||
flowId: anotherUserFlowTwo.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(`/api/v1/connections/${anotherUserConnection.id}/flows`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = await getFlowsMock(
|
|
||||||
[anotherUserFlowOne],
|
|
||||||
[triggerStepFlowOne, actionStepFlowOne]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -2,7 +2,6 @@ import { renderObject } from '../../../../helpers/renderer.js';
|
|||||||
|
|
||||||
export default async (request, response) => {
|
export default async (request, response) => {
|
||||||
const execution = await request.currentUser.authorizedExecutions
|
const execution = await request.currentUser.authorizedExecutions
|
||||||
.clone()
|
|
||||||
.withGraphFetched({
|
.withGraphFetched({
|
||||||
flow: {
|
flow: {
|
||||||
steps: true,
|
steps: true,
|
||||||
|
@@ -3,7 +3,6 @@ import paginateRest from '../../../../helpers/pagination-rest.js';
|
|||||||
|
|
||||||
export default async (request, response) => {
|
export default async (request, response) => {
|
||||||
const executionsQuery = request.currentUser.authorizedExecutions
|
const executionsQuery = request.currentUser.authorizedExecutions
|
||||||
.clone()
|
|
||||||
.withSoftDeleted()
|
.withSoftDeleted()
|
||||||
.orderBy('created_at', 'desc')
|
.orderBy('created_at', 'desc')
|
||||||
.withGraphFetched({
|
.withGraphFetched({
|
||||||
|
@@ -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',
|
||||||
|
@@ -2,7 +2,6 @@ import { renderObject } from '../../../../helpers/renderer.js';
|
|||||||
|
|
||||||
export default async (request, response) => {
|
export default async (request, response) => {
|
||||||
const flow = await request.currentUser.authorizedFlows
|
const flow = await request.currentUser.authorizedFlows
|
||||||
.clone()
|
|
||||||
.withGraphJoined({ steps: true })
|
.withGraphJoined({ steps: true })
|
||||||
.orderBy('steps.position', 'asc')
|
.orderBy('steps.position', 'asc')
|
||||||
.findOne({ 'flows.id': request.params.flowId })
|
.findOne({ 'flows.id': request.params.flowId })
|
||||||
|
@@ -1,21 +0,0 @@
|
|||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
import paginateRest from '../../../../helpers/pagination-rest.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const flowsQuery = request.currentUser.authorizedFlows
|
|
||||||
.clone()
|
|
||||||
.withGraphFetched({
|
|
||||||
steps: true,
|
|
||||||
})
|
|
||||||
.where((builder) => {
|
|
||||||
if (request.query.name) {
|
|
||||||
builder.where('flows.name', 'ilike', `%${request.query.name}%`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.orderBy('active', 'desc')
|
|
||||||
.orderBy('updated_at', 'desc');
|
|
||||||
|
|
||||||
const flows = await paginateRest(flowsQuery, request.query.page);
|
|
||||||
|
|
||||||
renderObject(response, flows);
|
|
||||||
};
|
|
@@ -1,118 +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 { createUser } from '../../../../../test/factories/user';
|
|
||||||
import { createFlow } from '../../../../../test/factories/flow';
|
|
||||||
import { createStep } from '../../../../../test/factories/step';
|
|
||||||
import { createPermission } from '../../../../../test/factories/permission';
|
|
||||||
import getFlowsMock from '../../../../../test/mocks/rest/api/v1/flows/get-flows.js';
|
|
||||||
|
|
||||||
describe('GET /api/v1/flows', () => {
|
|
||||||
let currentUser, currentUserRole, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
currentUser = await createUser();
|
|
||||||
currentUserRole = await currentUser.$relatedQuery('role');
|
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the flows data of current user', async () => {
|
|
||||||
const currentUserFlowOne = await createFlow({ userId: currentUser.id });
|
|
||||||
|
|
||||||
const triggerStepFlowOne = await createStep({
|
|
||||||
flowId: currentUserFlowOne.id,
|
|
||||||
type: 'trigger',
|
|
||||||
});
|
|
||||||
const actionStepFlowOne = await createStep({
|
|
||||||
flowId: currentUserFlowOne.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentUserFlowTwo = await createFlow({ userId: currentUser.id });
|
|
||||||
|
|
||||||
const triggerStepFlowTwo = await createStep({
|
|
||||||
flowId: currentUserFlowTwo.id,
|
|
||||||
type: 'trigger',
|
|
||||||
});
|
|
||||||
const actionStepFlowTwo = await createStep({
|
|
||||||
flowId: currentUserFlowTwo.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: ['isCreator'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get('/api/v1/flows')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = await getFlowsMock(
|
|
||||||
[currentUserFlowTwo, currentUserFlowOne],
|
|
||||||
[
|
|
||||||
triggerStepFlowOne,
|
|
||||||
actionStepFlowOne,
|
|
||||||
triggerStepFlowTwo,
|
|
||||||
actionStepFlowTwo,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the flows data of another user', async () => {
|
|
||||||
const anotherUser = await createUser();
|
|
||||||
|
|
||||||
const anotherUserFlowOne = await createFlow({ userId: anotherUser.id });
|
|
||||||
|
|
||||||
const triggerStepFlowOne = await createStep({
|
|
||||||
flowId: anotherUserFlowOne.id,
|
|
||||||
type: 'trigger',
|
|
||||||
});
|
|
||||||
const actionStepFlowOne = await createStep({
|
|
||||||
flowId: anotherUserFlowOne.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
const anotherUserFlowTwo = await createFlow({ userId: anotherUser.id });
|
|
||||||
|
|
||||||
const triggerStepFlowTwo = await createStep({
|
|
||||||
flowId: anotherUserFlowTwo.id,
|
|
||||||
type: 'trigger',
|
|
||||||
});
|
|
||||||
const actionStepFlowTwo = await createStep({
|
|
||||||
flowId: anotherUserFlowTwo.id,
|
|
||||||
type: 'action',
|
|
||||||
});
|
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get('/api/v1/flows')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = await getFlowsMock(
|
|
||||||
[anotherUserFlowTwo, anotherUserFlowOne],
|
|
||||||
[
|
|
||||||
triggerStepFlowOne,
|
|
||||||
actionStepFlowOne,
|
|
||||||
triggerStepFlowTwo,
|
|
||||||
actionStepFlowTwo,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,12 +0,0 @@
|
|||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
import SamlAuthProvider from '../../../../models/saml-auth-provider.ee.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const samlAuthProviders = await SamlAuthProvider.query()
|
|
||||||
.where({
|
|
||||||
active: true,
|
|
||||||
})
|
|
||||||
.orderBy('created_at', 'desc');
|
|
||||||
|
|
||||||
renderObject(response, samlAuthProviders);
|
|
||||||
};
|
|
@@ -1,30 +0,0 @@
|
|||||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import request from 'supertest';
|
|
||||||
import app from '../../../../app.js';
|
|
||||||
import { createSamlAuthProvider } from '../../../../../test/factories/saml-auth-provider.ee.js';
|
|
||||||
import getSamlAuthProvidersMock from '../../../../../test/mocks/rest/api/v1/saml-auth-providers/get-saml-auth-providers.js';
|
|
||||||
import * as license from '../../../../helpers/license.ee.js';
|
|
||||||
|
|
||||||
describe('GET /api/v1/saml-auth-providers', () => {
|
|
||||||
let samlAuthProviderOne, samlAuthProviderTwo;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
samlAuthProviderOne = await createSamlAuthProvider();
|
|
||||||
samlAuthProviderTwo = await createSamlAuthProvider();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return saml auth providers', async () => {
|
|
||||||
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get('/api/v1/saml-auth-providers')
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = await getSamlAuthProvidersMock([
|
|
||||||
samlAuthProviderTwo,
|
|
||||||
samlAuthProviderOne,
|
|
||||||
]);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -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);
|
|
||||||
};
|
|
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -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);
|
|
||||||
};
|
|
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,11 +0,0 @@
|
|||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const step = await request.currentUser.authorizedSteps
|
|
||||||
.findById(request.params.stepId)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
const connection = await step.$relatedQuery('connection').throwIfNotFound();
|
|
||||||
|
|
||||||
renderObject(response, connection);
|
|
||||||
};
|
|
@@ -1,121 +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 { createConnection } from '../../../../../test/factories/connection';
|
|
||||||
import { createFlow } from '../../../../../test/factories/flow';
|
|
||||||
import { createStep } from '../../../../../test/factories/step';
|
|
||||||
import { createPermission } from '../../../../../test/factories/permission';
|
|
||||||
import getConnectionMock from '../../../../../test/mocks/rest/api/v1/steps/get-connection';
|
|
||||||
|
|
||||||
describe('GET /api/v1/steps/:stepId/connection', () => {
|
|
||||||
let currentUser, currentUserRole, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
currentUser = await createUser();
|
|
||||||
currentUserRole = await currentUser.$relatedQuery('role');
|
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the current user connection data of specified step', async () => {
|
|
||||||
const currentUserflow = await createFlow({ userId: currentUser.id });
|
|
||||||
|
|
||||||
const currentUserConnection = await createConnection();
|
|
||||||
const triggerStep = await createStep({
|
|
||||||
flowId: currentUserflow.id,
|
|
||||||
connectionId: currentUserConnection.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: ['isCreator'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(`/api/v1/steps/${triggerStep.id}/connection`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = await getConnectionMock(currentUserConnection);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the current user connection data of specified step', async () => {
|
|
||||||
const anotherUser = await createUser();
|
|
||||||
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
|
|
||||||
|
|
||||||
const anotherUserConnection = await createConnection();
|
|
||||||
const triggerStep = await createStep({
|
|
||||||
flowId: anotherUserFlow.id,
|
|
||||||
connectionId: anotherUserConnection.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(`/api/v1/steps/${triggerStep.id}/connection`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = await getConnectionMock(anotherUserConnection);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return not found response for not existing step without connection', async () => {
|
|
||||||
const stepWithoutConnection = await createStep();
|
|
||||||
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get(`/api/v1/steps/${stepWithoutConnection.id}/connection`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(404);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return not found response for not existing step UUID', async () => {
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const notExistingFlowUUID = Crypto.randomUUID();
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get(`/api/v1/steps/${notExistingFlowUUID}/connection`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(404);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return bad request response for invalid UUID', async () => {
|
|
||||||
await createPermission({
|
|
||||||
action: 'read',
|
|
||||||
subject: 'Flow',
|
|
||||||
roleId: currentUserRole.id,
|
|
||||||
conditions: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get('/api/v1/steps/invalidFlowUUID/connection')
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(400);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -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);
|
|
||||||
};
|
|
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -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' });
|
|
||||||
};
|
|
@@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,7 +0,0 @@
|
|||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const planAndUsage = await request.currentUser.getPlanAndUsage();
|
|
||||||
|
|
||||||
renderObject(response, planAndUsage);
|
|
||||||
};
|
|
@@ -1,68 +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 { createSubscription } from '../../../../../test/factories/subscription.js';
|
|
||||||
import { createUsageData } from '../../../../../test/factories/usage-data.js';
|
|
||||||
import appConfig from '../../../../config/app.js';
|
|
||||||
import { DateTime } from 'luxon';
|
|
||||||
|
|
||||||
describe('GET /api/v1/users/:userId/plan-and-usage', () => {
|
|
||||||
let user, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate();
|
|
||||||
user = await createUser({ trialExpiryDate });
|
|
||||||
token = createAuthTokenByUserId(user.id);
|
|
||||||
|
|
||||||
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return free trial plan and usage data', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.get(`/api/v1/users/${user.id}/plan-and-usage`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponseData = {
|
|
||||||
plan: {
|
|
||||||
id: null,
|
|
||||||
limit: null,
|
|
||||||
name: 'Free Trial',
|
|
||||||
},
|
|
||||||
usage: {
|
|
||||||
task: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body.data).toEqual(expectedResponseData);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return current plan and usage data', async () => {
|
|
||||||
await createSubscription({ userId: user.id });
|
|
||||||
|
|
||||||
await createUsageData({
|
|
||||||
userId: user.id,
|
|
||||||
consumedTaskCount: 1234,
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.get(`/api/v1/users/${user.id}/plan-and-usage`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedResponseData = {
|
|
||||||
plan: {
|
|
||||||
id: '47384',
|
|
||||||
limit: '10,000',
|
|
||||||
name: '10k - monthly',
|
|
||||||
},
|
|
||||||
usage: {
|
|
||||||
task: 1234,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(response.body.data).toEqual(expectedResponseData);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,9 +0,0 @@
|
|||||||
import { renderObject } from '../../../../helpers/renderer.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const subscription = await request.currentUser
|
|
||||||
.$relatedQuery('currentSubscription')
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
renderObject(response, subscription);
|
|
||||||
};
|
|
@@ -1,51 +0,0 @@
|
|||||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import request from 'supertest';
|
|
||||||
import appConfig from '../../../../config/app.js';
|
|
||||||
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 { createSubscription } from '../../../../../test/factories/subscription.js';
|
|
||||||
import getSubscriptionMock from '../../../../../test/mocks/rest/api/v1/users/get-subscription.js';
|
|
||||||
|
|
||||||
describe('GET /api/v1/users/:userId/subscription', () => {
|
|
||||||
let currentUser, role, subscription, token;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
|
|
||||||
|
|
||||||
role = await createRole();
|
|
||||||
|
|
||||||
currentUser = await createUser({
|
|
||||||
roleId: role.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
subscription = await createSubscription({ userId: currentUser.id });
|
|
||||||
|
|
||||||
token = createAuthTokenByUserId(currentUser.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return subscription info of the current user', async () => {
|
|
||||||
const response = await request(app)
|
|
||||||
.get(`/api/v1/users/${currentUser.id}/subscription`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(200);
|
|
||||||
|
|
||||||
const expectedPayload = getSubscriptionMock(subscription);
|
|
||||||
|
|
||||||
expect(response.body).toEqual(expectedPayload);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return not found response if there is no current subscription', async () => {
|
|
||||||
const userWithoutSubscription = await createUser({
|
|
||||||
roleId: role.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
const token = createAuthTokenByUserId(userWithoutSubscription.id);
|
|
||||||
|
|
||||||
await request(app)
|
|
||||||
.get(`/api/v1/users/${userWithoutSubscription.id}/subscription`)
|
|
||||||
.set('Authorization', token)
|
|
||||||
.expect(404);
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,29 +0,0 @@
|
|||||||
import Flow from '../../models/flow.js';
|
|
||||||
import logger from '../../helpers/logger.js';
|
|
||||||
import handlerSync from '../../helpers/webhook-handler-sync.js';
|
|
||||||
|
|
||||||
export default async (request, response) => {
|
|
||||||
const computedRequestPayload = {
|
|
||||||
headers: request.headers,
|
|
||||||
body: request.body,
|
|
||||||
query: request.query,
|
|
||||||
params: request.params,
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.debug(`Handling incoming webhook request at ${request.originalUrl}.`);
|
|
||||||
logger.debug(JSON.stringify(computedRequestPayload, null, 2));
|
|
||||||
|
|
||||||
const flowId = request.params.flowId;
|
|
||||||
const flow = await Flow.query().findById(flowId).throwIfNotFound();
|
|
||||||
const triggerStep = await flow.getTriggerStep();
|
|
||||||
|
|
||||||
if (triggerStep.appKey !== 'webhook') {
|
|
||||||
const connection = await triggerStep.$relatedQuery('connection');
|
|
||||||
|
|
||||||
if (!(await connection.verifyWebhook(request))) {
|
|
||||||
return response.sendStatus(401);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await handlerSync(flowId, request, response);
|
|
||||||
};
|
|
@@ -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');
|
|
||||||
});
|
|
||||||
}
|
|
@@ -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
|
|
||||||
}
|
|
@@ -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');
|
|
||||||
});
|
|
||||||
}
|
|
@@ -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();
|
|
||||||
});
|
|
||||||
}
|
|
@@ -1,4 +1,4 @@
|
|||||||
import Step from '../../models/step.js';
|
import Step from '../../models/flow.js';
|
||||||
|
|
||||||
const deleteStep = async (_parent, params, context) => {
|
const deleteStep = async (_parent, params, context) => {
|
||||||
const conditions = context.currentUser.can('update', 'Flow');
|
const conditions = context.currentUser.can('update', 'Flow');
|
||||||
|
@@ -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;
|
@@ -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;
|
17
packages/backend/src/graphql/queries/get-app-config.ee.js
Normal file
17
packages/backend/src/graphql/queries/get-app-config.ee.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import AppConfig from '../../models/app-config.js';
|
||||||
|
|
||||||
|
const getAppConfig = async (_parent, params, context) => {
|
||||||
|
context.currentUser.can('create', 'Connection');
|
||||||
|
|
||||||
|
const appConfig = await AppConfig.query()
|
||||||
|
.withGraphFetched({
|
||||||
|
appAuthClients: true,
|
||||||
|
})
|
||||||
|
.findOne({
|
||||||
|
key: params.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
return appConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getAppConfig;
|
41
packages/backend/src/graphql/queries/get-app.js
Normal file
41
packages/backend/src/graphql/queries/get-app.js
Normal 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;
|
17
packages/backend/src/graphql/queries/get-apps.js
Normal file
17
packages/backend/src/graphql/queries/get-apps.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import App from '../../models/app.js';
|
||||||
|
|
||||||
|
const getApps = async (_parent, params) => {
|
||||||
|
const apps = await App.findAll(params.name);
|
||||||
|
|
||||||
|
if (params.onlyWithTriggers) {
|
||||||
|
return apps.filter((app) => app.triggers?.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.onlyWithActions) {
|
||||||
|
return apps.filter((app) => app.actions?.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return apps;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getApps;
|
101
packages/backend/src/graphql/queries/get-billing-and-usage.ee.js
Normal file
101
packages/backend/src/graphql/queries/get-billing-and-usage.ee.js
Normal 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;
|
32
packages/backend/src/graphql/queries/get-config.ee.js
Normal file
32
packages/backend/src/graphql/queries/get-config.ee.js
Normal 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;
|
140
packages/backend/src/graphql/queries/get-config.ee.test.js
Normal file
140
packages/backend/src/graphql/queries/get-config.ee.test.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
67
packages/backend/src/graphql/queries/get-connected-apps.js
Normal file
67
packages/backend/src/graphql/queries/get-connected-apps.js
Normal 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;
|
5
packages/backend/src/graphql/queries/get-current-user.js
Normal file
5
packages/backend/src/graphql/queries/get-current-user.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const getCurrentUser = async (_parent, _params, context) => {
|
||||||
|
return context.currentUser;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getCurrentUser;
|
@@ -0,0 +1,79 @@
|
|||||||
|
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 { createUser } from '../../../test/factories/user';
|
||||||
|
|
||||||
|
describe('graphQL getCurrentUser query', () => {
|
||||||
|
let role, currentUser, token, requestObject;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
role = await createRole({
|
||||||
|
key: 'sample',
|
||||||
|
name: 'sample',
|
||||||
|
});
|
||||||
|
|
||||||
|
currentUser = await createUser({
|
||||||
|
roleId: role.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
token = createAuthTokenByUserId(currentUser.id);
|
||||||
|
requestObject = request(app).post('/graphql').set('Authorization', token);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return user data', async () => {
|
||||||
|
const query = `
|
||||||
|
query {
|
||||||
|
getCurrentUser {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
fullName
|
||||||
|
email
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
role {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await requestObject.send({ query }).expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getCurrentUser: {
|
||||||
|
createdAt: currentUser.createdAt.getTime().toString(),
|
||||||
|
email: currentUser.email,
|
||||||
|
fullName: currentUser.fullName,
|
||||||
|
id: currentUser.id,
|
||||||
|
role: { id: role.id, name: role.name },
|
||||||
|
updatedAt: currentUser.updatedAt.getTime().toString(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not return user password', async () => {
|
||||||
|
const query = `
|
||||||
|
query {
|
||||||
|
getCurrentUser {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
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".'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
65
packages/backend/src/graphql/queries/get-dynamic-data.js
Normal file
65
packages/backend/src/graphql/queries/get-dynamic-data.js
Normal 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;
|
40
packages/backend/src/graphql/queries/get-dynamic-fields.js
Normal file
40
packages/backend/src/graphql/queries/get-dynamic-fields.js
Normal 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;
|
27
packages/backend/src/graphql/queries/get-execution-steps.js
Normal file
27
packages/backend/src/graphql/queries/get-execution-steps.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import paginate from '../../helpers/pagination.js';
|
||||||
|
import Execution from '../../models/execution.js';
|
||||||
|
|
||||||
|
const getExecutionSteps = async (_parent, params, context) => {
|
||||||
|
const conditions = context.currentUser.can('read', 'Execution');
|
||||||
|
const userExecutions = context.currentUser.$relatedQuery('executions');
|
||||||
|
const allExecutions = Execution.query();
|
||||||
|
const executionBaseQuery = conditions.isCreator
|
||||||
|
? userExecutions
|
||||||
|
: allExecutions;
|
||||||
|
|
||||||
|
const execution = await executionBaseQuery
|
||||||
|
.clone()
|
||||||
|
.withSoftDeleted()
|
||||||
|
.findById(params.executionId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
const executionSteps = execution
|
||||||
|
.$relatedQuery('executionSteps')
|
||||||
|
.withSoftDeleted()
|
||||||
|
.withGraphFetched('step')
|
||||||
|
.orderBy('created_at', 'asc');
|
||||||
|
|
||||||
|
return paginate(executionSteps, params.limit, params.offset);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getExecutionSteps;
|
25
packages/backend/src/graphql/queries/get-execution.js
Normal file
25
packages/backend/src/graphql/queries/get-execution.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import Execution from '../../models/execution.js';
|
||||||
|
|
||||||
|
const getExecution = async (_parent, params, context) => {
|
||||||
|
const conditions = context.currentUser.can('read', 'Execution');
|
||||||
|
const userExecutions = context.currentUser.$relatedQuery('executions');
|
||||||
|
const allExecutions = Execution.query();
|
||||||
|
const executionBaseQuery = conditions.isCreator
|
||||||
|
? userExecutions
|
||||||
|
: allExecutions;
|
||||||
|
|
||||||
|
const execution = await executionBaseQuery
|
||||||
|
.clone()
|
||||||
|
.withGraphFetched({
|
||||||
|
flow: {
|
||||||
|
steps: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.withSoftDeleted()
|
||||||
|
.findById(params.executionId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
return execution;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getExecution;
|
70
packages/backend/src/graphql/queries/get-executions.js
Normal file
70
packages/backend/src/graphql/queries/get-executions.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import { raw } from 'objection';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import Execution from '../../models/execution.js';
|
||||||
|
import paginate from '../../helpers/pagination.js';
|
||||||
|
|
||||||
|
const getExecutions = async (_parent, params, context) => {
|
||||||
|
const conditions = context.currentUser.can('read', 'Execution');
|
||||||
|
|
||||||
|
const filters = params.filters;
|
||||||
|
|
||||||
|
const userExecutions = context.currentUser.$relatedQuery('executions');
|
||||||
|
const allExecutions = Execution.query();
|
||||||
|
const executionBaseQuery = conditions.isCreator
|
||||||
|
? userExecutions
|
||||||
|
: allExecutions;
|
||||||
|
|
||||||
|
const selectStatusStatement = `
|
||||||
|
case
|
||||||
|
when count(*) filter (where execution_steps.status = 'failure') > 0
|
||||||
|
then 'failure'
|
||||||
|
else 'success'
|
||||||
|
end
|
||||||
|
as status
|
||||||
|
`;
|
||||||
|
|
||||||
|
const executions = executionBaseQuery
|
||||||
|
.clone()
|
||||||
|
.joinRelated('executionSteps as execution_steps')
|
||||||
|
.select('executions.*', raw(selectStatusStatement))
|
||||||
|
.groupBy('executions.id')
|
||||||
|
.orderBy('created_at', 'desc');
|
||||||
|
|
||||||
|
const computedExecutions = Execution.query()
|
||||||
|
.with('executions', executions)
|
||||||
|
.withSoftDeleted()
|
||||||
|
.withGraphFetched({
|
||||||
|
flow: {
|
||||||
|
steps: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filters?.flowId) {
|
||||||
|
computedExecutions.where('executions.flow_id', filters.flowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.status) {
|
||||||
|
computedExecutions.where('executions.status', filters.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters?.createdAt) {
|
||||||
|
const createdAtFilter = filters.createdAt;
|
||||||
|
if (createdAtFilter.from) {
|
||||||
|
const isoFromDateTime = DateTime.fromMillis(
|
||||||
|
parseInt(createdAtFilter.from, 10)
|
||||||
|
).toISO();
|
||||||
|
computedExecutions.where('executions.created_at', '>=', isoFromDateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createdAtFilter.to) {
|
||||||
|
const isoToDateTime = DateTime.fromMillis(
|
||||||
|
parseInt(createdAtFilter.to, 10)
|
||||||
|
).toISO();
|
||||||
|
computedExecutions.where('executions.created_at', '<=', isoToDateTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paginate(computedExecutions, params.limit, params.offset);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getExecutions;
|
472
packages/backend/src/graphql/queries/get-executions.test.js
Normal file
472
packages/backend/src/graphql/queries/get-executions.test.js
Normal file
@@ -0,0 +1,472 @@
|
|||||||
|
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 { createExecution } from '../../../test/factories/execution';
|
||||||
|
import { createExecutionStep } from '../../../test/factories/execution-step';
|
||||||
|
|
||||||
|
describe('graphQL getExecutions query', () => {
|
||||||
|
const query = `
|
||||||
|
query {
|
||||||
|
getExecutions(limit: 10, offset: 0) {
|
||||||
|
pageInfo {
|
||||||
|
currentPage
|
||||||
|
totalPages
|
||||||
|
}
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
testRun
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
status
|
||||||
|
flow {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
active
|
||||||
|
steps {
|
||||||
|
iconUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
describe('and without correct 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 permission', () => {
|
||||||
|
let role,
|
||||||
|
currentUser,
|
||||||
|
anotherUser,
|
||||||
|
token,
|
||||||
|
flowOne,
|
||||||
|
stepOneForFlowOne,
|
||||||
|
stepTwoForFlowOne,
|
||||||
|
executionOne,
|
||||||
|
flowTwo,
|
||||||
|
stepOneForFlowTwo,
|
||||||
|
stepTwoForFlowTwo,
|
||||||
|
executionTwo,
|
||||||
|
flowThree,
|
||||||
|
stepOneForFlowThree,
|
||||||
|
stepTwoForFlowThree,
|
||||||
|
executionThree,
|
||||||
|
expectedResponseForExecutionOne,
|
||||||
|
expectedResponseForExecutionTwo,
|
||||||
|
expectedResponseForExecutionThree;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
role = await createRole({
|
||||||
|
key: 'sample',
|
||||||
|
name: 'sample',
|
||||||
|
});
|
||||||
|
|
||||||
|
currentUser = await createUser({
|
||||||
|
roleId: role.id,
|
||||||
|
fullName: 'Current User',
|
||||||
|
});
|
||||||
|
|
||||||
|
anotherUser = await createUser();
|
||||||
|
|
||||||
|
token = createAuthTokenByUserId(currentUser.id);
|
||||||
|
|
||||||
|
flowOne = await createFlow({
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
stepOneForFlowOne = await createStep({
|
||||||
|
flowId: flowOne.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
stepTwoForFlowOne = await createStep({
|
||||||
|
flowId: flowOne.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
executionOne = await createExecution({
|
||||||
|
flowId: flowOne.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await createExecutionStep({
|
||||||
|
executionId: executionOne.id,
|
||||||
|
stepId: stepOneForFlowOne.id,
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
|
||||||
|
await createExecutionStep({
|
||||||
|
executionId: executionOne.id,
|
||||||
|
stepId: stepTwoForFlowOne.id,
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
|
||||||
|
flowTwo = await createFlow({
|
||||||
|
userId: currentUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
stepOneForFlowTwo = await createStep({
|
||||||
|
flowId: flowTwo.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
stepTwoForFlowTwo = await createStep({
|
||||||
|
flowId: flowTwo.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
executionTwo = await createExecution({
|
||||||
|
flowId: flowTwo.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await createExecutionStep({
|
||||||
|
executionId: executionTwo.id,
|
||||||
|
stepId: stepOneForFlowTwo.id,
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
|
||||||
|
await createExecutionStep({
|
||||||
|
executionId: executionTwo.id,
|
||||||
|
stepId: stepTwoForFlowTwo.id,
|
||||||
|
status: 'failure',
|
||||||
|
});
|
||||||
|
|
||||||
|
flowThree = await createFlow({
|
||||||
|
userId: anotherUser.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
stepOneForFlowThree = await createStep({
|
||||||
|
flowId: flowThree.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
stepTwoForFlowThree = await createStep({
|
||||||
|
flowId: flowThree.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
executionThree = await createExecution({
|
||||||
|
flowId: flowThree.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await createExecutionStep({
|
||||||
|
executionId: executionThree.id,
|
||||||
|
stepId: stepOneForFlowThree.id,
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
|
||||||
|
await createExecutionStep({
|
||||||
|
executionId: executionThree.id,
|
||||||
|
stepId: stepTwoForFlowThree.id,
|
||||||
|
status: 'failure',
|
||||||
|
});
|
||||||
|
|
||||||
|
expectedResponseForExecutionOne = {
|
||||||
|
node: {
|
||||||
|
createdAt: executionOne.createdAt.getTime().toString(),
|
||||||
|
flow: {
|
||||||
|
active: flowOne.active,
|
||||||
|
id: flowOne.id,
|
||||||
|
name: flowOne.name,
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
iconUrl: `${appConfig.baseUrl}/apps/${stepOneForFlowOne.appKey}/assets/favicon.svg`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowOne.appKey}/assets/favicon.svg`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
id: executionOne.id,
|
||||||
|
status: 'success',
|
||||||
|
testRun: executionOne.testRun,
|
||||||
|
updatedAt: executionOne.updatedAt.getTime().toString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expectedResponseForExecutionTwo = {
|
||||||
|
node: {
|
||||||
|
createdAt: executionTwo.createdAt.getTime().toString(),
|
||||||
|
flow: {
|
||||||
|
active: flowTwo.active,
|
||||||
|
id: flowTwo.id,
|
||||||
|
name: flowTwo.name,
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowTwo.appKey}/assets/favicon.svg`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowTwo.appKey}/assets/favicon.svg`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
id: executionTwo.id,
|
||||||
|
status: 'failure',
|
||||||
|
testRun: executionTwo.testRun,
|
||||||
|
updatedAt: executionTwo.updatedAt.getTime().toString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expectedResponseForExecutionThree = {
|
||||||
|
node: {
|
||||||
|
createdAt: executionThree.createdAt.getTime().toString(),
|
||||||
|
flow: {
|
||||||
|
active: flowThree.active,
|
||||||
|
id: flowThree.id,
|
||||||
|
name: flowThree.name,
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
iconUrl: `${appConfig.baseUrl}/apps/${stepOneForFlowThree.appKey}/assets/favicon.svg`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowThree.appKey}/assets/favicon.svg`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
id: executionThree.id,
|
||||||
|
status: 'failure',
|
||||||
|
testRun: executionThree.testRun,
|
||||||
|
updatedAt: executionThree.updatedAt.getTime().toString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and with isCreator condition', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Execution',
|
||||||
|
roleId: role.id,
|
||||||
|
conditions: ['isCreator'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return executions data of the current user', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getExecutions: {
|
||||||
|
edges: [
|
||||||
|
expectedResponseForExecutionTwo,
|
||||||
|
expectedResponseForExecutionOne,
|
||||||
|
],
|
||||||
|
pageInfo: { currentPage: 1, totalPages: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and without isCreator condition', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Execution',
|
||||||
|
roleId: role.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return executions data of all users', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getExecutions: {
|
||||||
|
edges: [
|
||||||
|
expectedResponseForExecutionThree,
|
||||||
|
expectedResponseForExecutionTwo,
|
||||||
|
expectedResponseForExecutionOne,
|
||||||
|
],
|
||||||
|
pageInfo: { currentPage: 1, totalPages: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and with filters', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await createPermission({
|
||||||
|
action: 'read',
|
||||||
|
subject: 'Execution',
|
||||||
|
roleId: role.id,
|
||||||
|
conditions: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return executions data for the specified flow', async () => {
|
||||||
|
const query = `
|
||||||
|
query {
|
||||||
|
getExecutions(limit: 10, offset: 0, filters: { flowId: "${flowOne.id}" }) {
|
||||||
|
pageInfo {
|
||||||
|
currentPage
|
||||||
|
totalPages
|
||||||
|
}
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
testRun
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
status
|
||||||
|
flow {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
active
|
||||||
|
steps {
|
||||||
|
iconUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getExecutions: {
|
||||||
|
edges: [expectedResponseForExecutionOne],
|
||||||
|
pageInfo: { currentPage: 1, totalPages: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return only executions data with success status', async () => {
|
||||||
|
const query = `
|
||||||
|
query {
|
||||||
|
getExecutions(limit: 10, offset: 0, filters: { status: "success" }) {
|
||||||
|
pageInfo {
|
||||||
|
currentPage
|
||||||
|
totalPages
|
||||||
|
}
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
testRun
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
status
|
||||||
|
flow {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
active
|
||||||
|
steps {
|
||||||
|
iconUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getExecutions: {
|
||||||
|
edges: [expectedResponseForExecutionOne],
|
||||||
|
pageInfo: { currentPage: 1, totalPages: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return only executions data within date range', async () => {
|
||||||
|
const createdAtFrom = executionOne.createdAt.getTime().toString();
|
||||||
|
|
||||||
|
const createdAtTo = executionOne.createdAt.getTime().toString();
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
query {
|
||||||
|
getExecutions(limit: 10, offset: 0, filters: { createdAt: { from: "${createdAtFrom}", to: "${createdAtTo}" }}) {
|
||||||
|
pageInfo {
|
||||||
|
currentPage
|
||||||
|
totalPages
|
||||||
|
}
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
testRun
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
status
|
||||||
|
flow {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
active
|
||||||
|
steps {
|
||||||
|
iconUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', token)
|
||||||
|
.send({ query })
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
const expectedResponsePayload = {
|
||||||
|
data: {
|
||||||
|
getExecutions: {
|
||||||
|
edges: [expectedResponseForExecutionOne],
|
||||||
|
pageInfo: { currentPage: 1, totalPages: 1 },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(response.body).toEqual(expectedResponsePayload);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
19
packages/backend/src/graphql/queries/get-flow.js
Normal file
19
packages/backend/src/graphql/queries/get-flow.js
Normal 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;
|
240
packages/backend/src/graphql/queries/get-flow.test.js
Normal file
240
packages/backend/src/graphql/queries/get-flow.test.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
40
packages/backend/src/graphql/queries/get-flows.js
Normal file
40
packages/backend/src/graphql/queries/get-flows.js
Normal 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;
|
19
packages/backend/src/graphql/queries/get-invoices.ee.js
Normal file
19
packages/backend/src/graphql/queries/get-invoices.ee.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import Billing from '../../helpers/billing/index.ee.js';
|
||||||
|
|
||||||
|
const getInvoices = async (_parent, _params, context) => {
|
||||||
|
const subscription = await context.currentUser.$relatedQuery(
|
||||||
|
'currentSubscription'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!subscription) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const invoices = await Billing.paddleClient.getInvoices(
|
||||||
|
Number(subscription.paddleSubscriptionId)
|
||||||
|
);
|
||||||
|
|
||||||
|
return invoices;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getInvoices;
|
16
packages/backend/src/graphql/queries/get-notifications.js
Normal file
16
packages/backend/src/graphql/queries/get-notifications.js
Normal 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;
|
10
packages/backend/src/graphql/queries/get-paddle-info.ee.js
Normal file
10
packages/backend/src/graphql/queries/get-paddle-info.ee.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import appConfig from '../../config/app.js';
|
||||||
|
import Billing from '../../helpers/billing/index.ee.js';
|
||||||
|
|
||||||
|
const getPaddleInfo = async () => {
|
||||||
|
if (!appConfig.isCloud) return;
|
||||||
|
|
||||||
|
return Billing.paddleInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getPaddleInfo;
|
10
packages/backend/src/graphql/queries/get-payment-plans.ee.js
Normal file
10
packages/backend/src/graphql/queries/get-payment-plans.ee.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import appConfig from '../../config/app.js';
|
||||||
|
import Billing from '../../helpers/billing/index.ee.js';
|
||||||
|
|
||||||
|
const getPaymentPlans = async () => {
|
||||||
|
if (!appConfig.isCloud) return;
|
||||||
|
|
||||||
|
return Billing.paddlePlans;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getPaymentPlans;
|
@@ -0,0 +1,7 @@
|
|||||||
|
import permissionCatalog from '../../helpers/permission-catalog.ee.js';
|
||||||
|
|
||||||
|
const getPermissionCatalog = async () => {
|
||||||
|
return permissionCatalog;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getPermissionCatalog;
|
17
packages/backend/src/graphql/queries/get-role.ee.js
Normal file
17
packages/backend/src/graphql/queries/get-role.ee.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import Role from '../../models/role.js';
|
||||||
|
|
||||||
|
const getRole = async (_parent, params, context) => {
|
||||||
|
context.currentUser.can('read', 'Role');
|
||||||
|
|
||||||
|
return await Role.query()
|
||||||
|
.leftJoinRelated({
|
||||||
|
permissions: true,
|
||||||
|
})
|
||||||
|
.withGraphFetched({
|
||||||
|
permissions: true,
|
||||||
|
})
|
||||||
|
.findById(params.id)
|
||||||
|
.throwIfNotFound();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getRole;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user