Compare commits
1 Commits
shared-con
...
AUT-405
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a650e3beaa |
@@ -59,8 +59,8 @@
|
|||||||
"http-proxy-agent": "^7.0.0",
|
"http-proxy-agent": "^7.0.0",
|
||||||
"https-proxy-agent": "^7.0.1",
|
"https-proxy-agent": "^7.0.1",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
|
"knex": "^2.4.0",
|
||||||
"libphonenumber-js": "^1.10.48",
|
"libphonenumber-js": "^1.10.48",
|
||||||
"knex": "^2.5.1",
|
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"luxon": "2.5.2",
|
"luxon": "2.5.2",
|
||||||
"memory-cache": "^0.2.0",
|
"memory-cache": "^0.2.0",
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
"node-html-markdown": "^1.3.0",
|
"node-html-markdown": "^1.3.0",
|
||||||
"nodemailer": "6.7.0",
|
"nodemailer": "6.7.0",
|
||||||
"oauth-1.0a": "^2.2.6",
|
"oauth-1.0a": "^2.2.6",
|
||||||
"objection": "^3.1.1",
|
"objection": "^3.0.0",
|
||||||
"passport": "^0.6.0",
|
"passport": "^0.6.0",
|
||||||
"pg": "^8.7.1",
|
"pg": "^8.7.1",
|
||||||
"php-serialize": "^4.0.2",
|
"php-serialize": "^4.0.2",
|
||||||
|
34
packages/backend/src/apps/amazon-s3/assets/favicon.svg
Normal file
34
packages/backend/src/apps/amazon-s3/assets/favicon.svg
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="428" height="512" viewBox="0 0 428 512">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #e25444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-1, .cls-2, .cls-3 {
|
||||||
|
fill-rule: evenodd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-2 {
|
||||||
|
fill: #7b1d13;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cls-3 {
|
||||||
|
fill: #58150d;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path class="cls-1" d="M378,99L295,257l83,158,34-19V118Z"/>
|
||||||
|
<path class="cls-2" d="M378,99L212,118,127.5,257,212,396l166,19V99Z"/>
|
||||||
|
<path class="cls-3" d="M43,99L16,111V403l27,12L212,257Z"/>
|
||||||
|
<path class="cls-1" d="M42.637,98.667l169.587,47.111V372.444L42.637,415.111V98.667Z"/>
|
||||||
|
<path class="cls-3" d="M212.313,170.667l-72.008-11.556,72.008-81.778,71.83,81.778Z"/>
|
||||||
|
<path class="cls-3" d="M284.143,159.111l-71.919,11.733-71.919-11.733V77.333"/>
|
||||||
|
<path class="cls-3" d="M212.313,342.222l-72.008,13.334,72.008,70.222,71.83-70.222Z"/>
|
||||||
|
<path class="cls-2" d="M212,16L140,54V159l72.224-20.333Z"/>
|
||||||
|
<path class="cls-2" d="M212.224,196.444l-71.919,7.823V309.105l71.919,8.228V196.444Z"/>
|
||||||
|
<path class="cls-2" d="M212.224,373.333L140.305,355.3V458.363L212.224,496V373.333Z"/>
|
||||||
|
<path class="cls-1" d="M284.143,355.3l-71.919,18.038V496l71.919-37.637V355.3Z"/>
|
||||||
|
<path class="cls-1" d="M212.224,196.444l71.919,7.823V309.105l-71.919,8.228V196.444Z"/>
|
||||||
|
<path class="cls-1" d="M212,16l72,38V159l-72-20V16Z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
56
packages/backend/src/apps/amazon-s3/auth/index.ts
Normal file
56
packages/backend/src/apps/amazon-s3/auth/index.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import verifyCredentials from './verify-credentials';
|
||||||
|
import isStillVerified from './is-still-verified';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
key: 'oAuthRedirectUrl',
|
||||||
|
label: 'OAuth Redirect URL',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: true,
|
||||||
|
value: '{WEB_APP_URL}/app/amazon-s3/connections/add',
|
||||||
|
placeholder: null,
|
||||||
|
description:
|
||||||
|
'When asked to input a redirect URL in AWS, enter the URL above.',
|
||||||
|
clickToCopy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'accessKeyId',
|
||||||
|
label: 'Access Key ID',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'secretAccessKey',
|
||||||
|
label: 'Secret Access Key',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description: null,
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'screenName',
|
||||||
|
label: 'Screen Name',
|
||||||
|
type: 'string' as const,
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
value: null,
|
||||||
|
placeholder: null,
|
||||||
|
description:
|
||||||
|
'Screen name of your connection to be used on Automatisch UI.',
|
||||||
|
clickToCopy: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
verifyCredentials,
|
||||||
|
isStillVerified,
|
||||||
|
};
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
import getCurrentUser from '../common/get-current-user';
|
||||||
|
|
||||||
|
const isStillVerified = async ($: IGlobalVariable) => {
|
||||||
|
const currentUser = await getCurrentUser($);
|
||||||
|
return !!currentUser.resourceName;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default isStillVerified;
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
|
const verifyCredentials = async ($: IGlobalVariable) => {
|
||||||
|
const { data } = await $.http.get('/');
|
||||||
|
|
||||||
|
console.log('data:', data);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default verifyCredentials;
|
153
packages/backend/src/apps/amazon-s3/common/add-auth-header.ts
Normal file
153
packages/backend/src/apps/amazon-s3/common/add-auth-header.ts
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import { IJSONObject, TBeforeRequest } from '@automatisch/types';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import { getISODate, getYYYYMMDD } from './get-current-date';
|
||||||
|
|
||||||
|
function hmac(key: string | Buffer, data: string) {
|
||||||
|
return crypto.createHmac('sha256', key).update(data).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
function hmacWoHex(key: Buffer | string, data: string) {
|
||||||
|
return crypto.createHmac('sha256', key).update(data).digest();
|
||||||
|
}
|
||||||
|
|
||||||
|
function hash(data: string) {
|
||||||
|
return crypto.createHash('sha256').update(data).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareCanonicalRequest(
|
||||||
|
method: string,
|
||||||
|
path: string,
|
||||||
|
queryParams: IJSONObject | string,
|
||||||
|
headers: IJSONObject,
|
||||||
|
payload: string
|
||||||
|
) {
|
||||||
|
const canonicalRequest = [method, encodeURIComponent(path)];
|
||||||
|
|
||||||
|
// Step 3: Canonical Query String
|
||||||
|
if (typeof queryParams === 'string') {
|
||||||
|
canonicalRequest.push('');
|
||||||
|
} else {
|
||||||
|
const sortedQueryParams = Object.keys(queryParams)
|
||||||
|
.map(
|
||||||
|
(key) =>
|
||||||
|
`${encodeURIComponent(key)}=${encodeURIComponent(
|
||||||
|
queryParams[key] as string
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
.sort();
|
||||||
|
canonicalRequest.push(sortedQueryParams.join('&'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Canonical Headers
|
||||||
|
const sortedHeaders = Object.keys(headers)
|
||||||
|
.sort()
|
||||||
|
.map((key) => `${key.toLowerCase()}:${(headers[key] as string).trim()}`);
|
||||||
|
|
||||||
|
canonicalRequest.push(sortedHeaders.join('\n'));
|
||||||
|
|
||||||
|
// Step 5: Signed Headers
|
||||||
|
const signedHeaders = Object.keys(headers)
|
||||||
|
.sort()
|
||||||
|
.map((key) => key.toLowerCase())
|
||||||
|
.join(';');
|
||||||
|
canonicalRequest.push(signedHeaders);
|
||||||
|
|
||||||
|
const hashedPayload = hash(payload);
|
||||||
|
canonicalRequest.push(hashedPayload);
|
||||||
|
|
||||||
|
return canonicalRequest.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareStringToSign(
|
||||||
|
datetime: string,
|
||||||
|
credentialScope: string,
|
||||||
|
hashedCanonicalRequest: string
|
||||||
|
) {
|
||||||
|
const stringToSign = [
|
||||||
|
'AWS4-HMAC-SHA256',
|
||||||
|
datetime,
|
||||||
|
credentialScope,
|
||||||
|
hashedCanonicalRequest,
|
||||||
|
];
|
||||||
|
|
||||||
|
return stringToSign.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateSigningKey(
|
||||||
|
secretKey: string,
|
||||||
|
date: string,
|
||||||
|
region: string,
|
||||||
|
service: string
|
||||||
|
) {
|
||||||
|
const dateKey = hmacWoHex('AWS4' + secretKey, date);
|
||||||
|
const dateRegionKey = hmacWoHex(dateKey, region);
|
||||||
|
const dateRegionServiceKey = hmacWoHex(dateRegionKey, service);
|
||||||
|
const signingKey = hmacWoHex(dateRegionServiceKey, 'aws4_request');
|
||||||
|
return signingKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAuthorizationHeader(
|
||||||
|
accessKey: string,
|
||||||
|
credentialScope: string,
|
||||||
|
signedHeaders: string,
|
||||||
|
signature: string
|
||||||
|
) {
|
||||||
|
return `AWS4-HMAC-SHA256 Credential=${accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||||
|
const accessKeyId = $.auth.data.accessKeyId as string;
|
||||||
|
const secretAccessKey = $.auth.data.secretAccessKey as string;
|
||||||
|
const date = getYYYYMMDD();
|
||||||
|
const formattedDate = getISODate();
|
||||||
|
const region = 'us-east-1';
|
||||||
|
const method = 'GET';
|
||||||
|
const path = '/';
|
||||||
|
const queryParams = '';
|
||||||
|
const payload = '';
|
||||||
|
const headers = {
|
||||||
|
Host: 's3.amazonaws.com',
|
||||||
|
'X-Amz-Content-Sha256': hash(payload),
|
||||||
|
'X-Amz-Date': formattedDate,
|
||||||
|
};
|
||||||
|
const headerKeys = Object.keys(headers)
|
||||||
|
.sort()
|
||||||
|
.map((header) => header.toLowerCase())
|
||||||
|
.join(';');
|
||||||
|
|
||||||
|
const canonicalRequest = prepareCanonicalRequest(
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
queryParams,
|
||||||
|
headers,
|
||||||
|
payload
|
||||||
|
);
|
||||||
|
|
||||||
|
const stringToSign = prepareStringToSign(
|
||||||
|
formattedDate,
|
||||||
|
`${date}/${region}/s3/aws4_request`,
|
||||||
|
hash(canonicalRequest)
|
||||||
|
);
|
||||||
|
|
||||||
|
const signingKey = calculateSigningKey(secretAccessKey, date, region, 's3');
|
||||||
|
|
||||||
|
const signature = hmac(signingKey, stringToSign);
|
||||||
|
|
||||||
|
const authorizationHeader = createAuthorizationHeader(
|
||||||
|
accessKeyId,
|
||||||
|
`${date}/${region}/s3/aws4_request`,
|
||||||
|
headerKeys,
|
||||||
|
signature
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($.auth.data?.secretAccessKey && $.auth.data?.accessKeyId) {
|
||||||
|
requestConfig.headers.Authorization = authorizationHeader;
|
||||||
|
requestConfig.headers['Host'] = 's3.amazonaws.com';
|
||||||
|
requestConfig.headers['X-Amz-Content-Sha256'] = hash(payload);
|
||||||
|
requestConfig.headers['X-Amz-Date'] = formattedDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default addAuthHeader;
|
@@ -0,0 +1,13 @@
|
|||||||
|
export const getYYYYMMDD = () => {
|
||||||
|
const today = new Date();
|
||||||
|
const year = today.getFullYear();
|
||||||
|
const month = (today.getMonth() + 1).toString().padStart(2, '0');
|
||||||
|
const day = today.getDate().toString().padStart(2, '0');
|
||||||
|
|
||||||
|
const formattedDate = `${year}${month}${day}`;
|
||||||
|
return formattedDate;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getISODate = () => {
|
||||||
|
return new Date().toISOString().replace(/[:-]|\.\d{3}/g, '');
|
||||||
|
};
|
@@ -1,9 +1,7 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
|
|
||||||
const getCurrentUser = async ($: IGlobalVariable) => {
|
const getCurrentUser = async ($: IGlobalVariable) => {
|
||||||
const { data: currentUser } = await $.http.get(
|
const { data: currentUser } = await $.http.get('/');
|
||||||
'https://people.googleapis.com/v1/people/me?personFields=names,emailAddresses'
|
|
||||||
);
|
|
||||||
return currentUser;
|
return currentUser;
|
||||||
};
|
};
|
||||||
|
|
0
packages/backend/src/apps/amazon-s3/index.d.ts
vendored
Normal file
0
packages/backend/src/apps/amazon-s3/index.d.ts
vendored
Normal file
16
packages/backend/src/apps/amazon-s3/index.ts
Normal file
16
packages/backend/src/apps/amazon-s3/index.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import defineApp from '../../helpers/define-app';
|
||||||
|
import addAuthHeader from './common/add-auth-header';
|
||||||
|
import auth from './auth';
|
||||||
|
|
||||||
|
export default defineApp({
|
||||||
|
name: 'Amazon S3',
|
||||||
|
key: 'amazon-s3',
|
||||||
|
baseUrl: '',
|
||||||
|
apiBaseUrl: 'https://s3.amazonaws.com',
|
||||||
|
iconUrl: '{BASE_URL}/apps/amazon-s3/assets/favicon.svg',
|
||||||
|
authDocUrl: 'https://automatisch.io/docs/apps/amazon-s3/connection',
|
||||||
|
primaryColor: '7B1D13',
|
||||||
|
supportsConnections: true,
|
||||||
|
beforeRequest: [addAuthHeader],
|
||||||
|
auth,
|
||||||
|
});
|
@@ -1,102 +0,0 @@
|
|||||||
import defineAction from '../../../../helpers/define-action';
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Create a scheduled event',
|
|
||||||
key: 'createScheduledEvent',
|
|
||||||
description: 'Creates a scheduled event',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Type',
|
|
||||||
key: 'entityType',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
options: [
|
|
||||||
{ label: 'Stage channel', value: 1 },
|
|
||||||
{ label: 'Voice channel', value: 2 },
|
|
||||||
{ label: 'External', value: 3 }
|
|
||||||
],
|
|
||||||
additionalFields: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicFields',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listExternalScheduledEventFields',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'parameters.entityType',
|
|
||||||
value: '{parameters.entityType}',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Name',
|
|
||||||
key: 'name',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Description',
|
|
||||||
key: 'description',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Image',
|
|
||||||
key: 'image',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
description: 'Image as DataURI scheme [data:image/<jpeg/png/gif>;base64,BASE64_ENCODED_<JPEG/PNG/GIF>_IMAGE_DATA]',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
type entity_metadata = {
|
|
||||||
location: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type guild_event = {
|
|
||||||
channel_id: number,
|
|
||||||
name: string,
|
|
||||||
privacy_level: number,
|
|
||||||
scheduled_start_time: string,
|
|
||||||
scheduled_end_time?: string,
|
|
||||||
description?: string,
|
|
||||||
entity_type?: number,
|
|
||||||
entity_metadata?: entity_metadata,
|
|
||||||
image?: string, //data:image/jpeg;base64,BASE64_ENCODED_JPEG_IMAGE_DATA
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const data: guild_event = {
|
|
||||||
channel_id: $.step.parameters.channel_id as number,
|
|
||||||
name: $.step.parameters.name as string,
|
|
||||||
privacy_level: 2,
|
|
||||||
scheduled_start_time: $.step.parameters.scheduledStartTime as string,
|
|
||||||
scheduled_end_time: $.step.parameters.scheduledEndTime as string,
|
|
||||||
description: $.step.parameters.description as string,
|
|
||||||
entity_type: $.step.parameters.entityType as number,
|
|
||||||
image: $.step.parameters.image as string,
|
|
||||||
};
|
|
||||||
|
|
||||||
const isExternal = $.step.parameters.entityType === 3;
|
|
||||||
if (isExternal) {
|
|
||||||
data.entity_metadata = {
|
|
||||||
location: $.step.parameters.location as string,
|
|
||||||
};
|
|
||||||
data.channel_id = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await $.http?.post(
|
|
||||||
`/guilds/${$.auth.data.guildId}/scheduled-events`,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
|
|
||||||
$.setActionItem({ raw: response.data });
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,4 +1,3 @@
|
|||||||
import sendMessageToChannel from './send-message-to-channel';
|
import sendMessageToChannel from './send-message-to-channel';
|
||||||
import createScheduledEvent from './create-scheduled-event';
|
|
||||||
|
|
||||||
export default [sendMessageToChannel, createScheduledEvent];
|
export default [sendMessageToChannel];
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import listChannels from './list-channels';
|
import listChannels from './list-channels';
|
||||||
import listVoiceChannels from './list-voice-channels';
|
|
||||||
|
|
||||||
export default [listChannels, listVoiceChannels];
|
export default [listChannels];
|
||||||
|
@@ -1,34 +0,0 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'List voice channels',
|
|
||||||
key: 'listVoiceChannels',
|
|
||||||
|
|
||||||
async run($: IGlobalVariable) {
|
|
||||||
const channels: {
|
|
||||||
data: IJSONObject[];
|
|
||||||
error: IJSONObject | null;
|
|
||||||
} = {
|
|
||||||
data: [],
|
|
||||||
error: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await $.http.get(
|
|
||||||
`/guilds/${$.auth.data.guildId}/channels`
|
|
||||||
);
|
|
||||||
|
|
||||||
channels.data = response.data
|
|
||||||
.filter((channel: IJSONObject) => {
|
|
||||||
// filter in voice and stage channels only
|
|
||||||
return channel.type === 2 || channel.type === 13;
|
|
||||||
})
|
|
||||||
.map((channel: IJSONObject) => {
|
|
||||||
return {
|
|
||||||
value: channel.id,
|
|
||||||
name: channel.name,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return channels;
|
|
||||||
},
|
|
||||||
};
|
|
@@ -1,3 +0,0 @@
|
|||||||
import listExternalScheduledEventFields from './list-external-scheduled-event-fields';
|
|
||||||
|
|
||||||
export default [listExternalScheduledEventFields];
|
|
@@ -1,83 +0,0 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
|
||||||
export default {
|
|
||||||
name: 'List external scheduled event fields',
|
|
||||||
key: 'listExternalScheduledEventFields',
|
|
||||||
|
|
||||||
async run($: IGlobalVariable) {
|
|
||||||
const isExternal = $.step.parameters.entityType === 3;
|
|
||||||
|
|
||||||
if (isExternal) {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: 'Location',
|
|
||||||
key: 'location',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
description: 'The location of the event (1-100 characters). This will be omitted if type is NOT EXTERNAL',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Start-Time',
|
|
||||||
key: 'scheduledStartTime',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
description: 'The time the event will start [ISO8601]',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'End-Time',
|
|
||||||
key: 'scheduledEndTime',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
description: 'The time the event will end [ISO8601]. This will be omitted if type is NOT EXTERNAL',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: 'Channel',
|
|
||||||
key: 'channel_id',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: true,
|
|
||||||
description: 'Pick a voice or stage channel to link the event to. This will be omitted if type is EXTERNAL',
|
|
||||||
variables: true,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listVoiceChannels',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Location',
|
|
||||||
key: 'location',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
description: 'The location of the event (1-100 characters). This will be omitted if type is NOT EXTERNAL',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Start-Time',
|
|
||||||
key: 'scheduledStartTime',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
description: 'The time the event will start [ISO8601]',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'End-Time',
|
|
||||||
key: 'scheduledEndTime',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
description: 'The time the event will end [ISO8601]. This will be omitted if type is NOT EXTERNAL',
|
|
||||||
variables: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
},
|
|
||||||
};
|
|
@@ -4,7 +4,6 @@ import auth from './auth';
|
|||||||
import dynamicData from './dynamic-data';
|
import dynamicData from './dynamic-data';
|
||||||
import actions from './actions';
|
import actions from './actions';
|
||||||
import triggers from './triggers';
|
import triggers from './triggers';
|
||||||
import dynamicFields from './dynamic-fields';
|
|
||||||
|
|
||||||
export default defineApp({
|
export default defineApp({
|
||||||
name: 'Discord',
|
name: 'Discord',
|
||||||
@@ -18,7 +17,6 @@ export default defineApp({
|
|||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
dynamicData,
|
dynamicData,
|
||||||
dynamicFields,
|
|
||||||
triggers,
|
triggers,
|
||||||
actions,
|
actions,
|
||||||
});
|
});
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
import { IGlobalVariable } from '@automatisch/types';
|
||||||
import getCurrentUser from '../common/get-current-user';
|
import getCurrentUser from '../../amazon-s3/common/get-current-user';
|
||||||
|
|
||||||
const isStillVerified = async ($: IGlobalVariable) => {
|
const isStillVerified = async ($: IGlobalVariable) => {
|
||||||
const currentUser = await getCurrentUser($);
|
const currentUser = await getCurrentUser($);
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { IField, IGlobalVariable } from '@automatisch/types';
|
import { IField, IGlobalVariable } from '@automatisch/types';
|
||||||
import getCurrentUser from '../common/get-current-user';
|
import getCurrentUser from '../../amazon-s3/common/get-current-user';
|
||||||
|
|
||||||
type TUser = {
|
type TUser = {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import newDatabaseItems from './new-database-items';
|
import newDatabaseItems from './new-database-items';
|
||||||
import updatedDatabaseItems from './updated-database-items';
|
|
||||||
|
|
||||||
export default [newDatabaseItems, updatedDatabaseItems];
|
export default [newDatabaseItems];
|
||||||
|
@@ -1,33 +0,0 @@
|
|||||||
import defineTrigger from '../../../../helpers/define-trigger';
|
|
||||||
import updatedDatabaseItems from './updated-database-items';
|
|
||||||
|
|
||||||
export default defineTrigger({
|
|
||||||
name: 'Updated database items',
|
|
||||||
key: 'updatedDatabaseItems',
|
|
||||||
pollInterval: 15,
|
|
||||||
description:
|
|
||||||
'Triggers when there is an update to an item in a chosen database',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Database',
|
|
||||||
key: 'databaseId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
variables: false,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listDatabases',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
await updatedDatabaseItems($);
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,51 +0,0 @@
|
|||||||
import { IGlobalVariable } from '@automatisch/types';
|
|
||||||
|
|
||||||
type DatabaseItem = {
|
|
||||||
id: string;
|
|
||||||
last_edited_time: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type ResponseData = {
|
|
||||||
results: DatabaseItem[];
|
|
||||||
next_cursor?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Payload = {
|
|
||||||
sorts: [
|
|
||||||
{
|
|
||||||
timestamp: 'created_time' | 'last_edited_time';
|
|
||||||
direction: 'ascending' | 'descending';
|
|
||||||
}
|
|
||||||
];
|
|
||||||
start_cursor?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const updatedDatabaseItems = async ($: IGlobalVariable) => {
|
|
||||||
const payload: Payload = {
|
|
||||||
sorts: [
|
|
||||||
{
|
|
||||||
timestamp: 'last_edited_time',
|
|
||||||
direction: 'descending',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const databaseId = $.step.parameters.databaseId as string;
|
|
||||||
const path = `/v1/databases/${databaseId}/query`;
|
|
||||||
do {
|
|
||||||
const response = await $.http.post<ResponseData>(path, payload);
|
|
||||||
|
|
||||||
payload.start_cursor = response.data.next_cursor;
|
|
||||||
|
|
||||||
for (const databaseItem of response.data.results) {
|
|
||||||
$.pushTriggerItem({
|
|
||||||
raw: databaseItem,
|
|
||||||
meta: {
|
|
||||||
internalId: `${databaseItem.id}-${databaseItem.last_edited_time}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} while (payload.start_cursor);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default updatedDatabaseItems;
|
|
@@ -1,22 +1,10 @@
|
|||||||
import { TBeforeRequest } from '@automatisch/types';
|
import { TBeforeRequest } from '@automatisch/types';
|
||||||
import appConfig from '../../../config/app';
|
|
||||||
|
|
||||||
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
const addAuthHeader: TBeforeRequest = ($, requestConfig) => {
|
||||||
const screenName = $.auth.data?.screenName as string;
|
|
||||||
if ($.auth.data?.accessToken) {
|
if ($.auth.data?.accessToken) {
|
||||||
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
|
requestConfig.headers.Authorization = `${$.auth.data.tokenType} ${$.auth.data.accessToken}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (screenName) {
|
|
||||||
requestConfig.headers[
|
|
||||||
'User-Agent'
|
|
||||||
] = `web:automatisch:${appConfig.version} (by /u/${screenName})`;
|
|
||||||
} else {
|
|
||||||
requestConfig.headers[
|
|
||||||
'User-Agent'
|
|
||||||
] = `web:automatisch:${appConfig.version}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return requestConfig;
|
return requestConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -1,3 +0,0 @@
|
|||||||
import removeImageBackground from './remove-image-background';
|
|
||||||
|
|
||||||
export default [removeImageBackground];
|
|
@@ -1,82 +0,0 @@
|
|||||||
import defineAction from '../../../../helpers/define-action';
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Remove image background',
|
|
||||||
key: 'removeImageBackground',
|
|
||||||
description:
|
|
||||||
'Removes the background of an image.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Image file',
|
|
||||||
key: 'imageFileB64',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
description: 'Provide a JPG or PNG file in Base64 format, up to 12 MB (see remove.bg/supported-images)',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Size',
|
|
||||||
key: 'size',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: true,
|
|
||||||
value: 'auto',
|
|
||||||
options: [
|
|
||||||
{ label: 'Auto', value: 'auto' },
|
|
||||||
{ label: 'Preview (up to 0.25 megapixels)', value: 'preview' },
|
|
||||||
{ label: 'Full (up to 10 megapixels)', value: 'full' },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Background color',
|
|
||||||
key: 'bgColor',
|
|
||||||
type: 'string' as const,
|
|
||||||
description: 'Adds a solid color background. Can be a hex color code (e.g. 81d4fa, fff) or a color name (e.g. green)',
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Background image URL',
|
|
||||||
key: 'bgImageUrl',
|
|
||||||
type: 'string' as const,
|
|
||||||
description: 'Adds a background image from a URL.',
|
|
||||||
required: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Output image format',
|
|
||||||
key: 'outputFormat',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
description: 'Note: Use PNG to preserve transparency',
|
|
||||||
required: true,
|
|
||||||
value: 'auto',
|
|
||||||
options: [
|
|
||||||
{ label: 'Auto', value: 'auto' },
|
|
||||||
{ label: 'PNG', value: 'png' },
|
|
||||||
{ label: 'JPG', value: 'jpg' },
|
|
||||||
{ label: 'ZIP', value: 'zip' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
async run($) {
|
|
||||||
const imageFileB64 = $.step.parameters.imageFileB64 as string;
|
|
||||||
const size = $.step.parameters.size as string;
|
|
||||||
const bgColor = $.step.parameters.bgColor as string;
|
|
||||||
const bgImageUrl = $.step.parameters.bgImageUrl as string;
|
|
||||||
const outputFormat = $.step.parameters.outputFormat as string;
|
|
||||||
|
|
||||||
const body = JSON.stringify({
|
|
||||||
image_file_b64: imageFileB64,
|
|
||||||
size: size,
|
|
||||||
bg_color: bgColor,
|
|
||||||
bg_image_url: bgImageUrl,
|
|
||||||
format: outputFormat
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await $.http.post('/removebg', body, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
$.setActionItem({ raw: response.data });
|
|
||||||
}
|
|
||||||
});
|
|
@@ -1,7 +1,6 @@
|
|||||||
import defineApp from '../../helpers/define-app';
|
import defineApp from '../../helpers/define-app';
|
||||||
import addAuthHeader from './common/add-auth-header';
|
import addAuthHeader from './common/add-auth-header';
|
||||||
import auth from './auth';
|
import auth from './auth';
|
||||||
import actions from './actions';
|
|
||||||
|
|
||||||
export default defineApp({
|
export default defineApp({
|
||||||
name: 'Remove.bg',
|
name: 'Remove.bg',
|
||||||
@@ -14,5 +13,4 @@ export default defineApp({
|
|||||||
primaryColor: '55636c',
|
primaryColor: '55636c',
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
actions,
|
|
||||||
});
|
});
|
||||||
|
@@ -1,102 +0,0 @@
|
|||||||
export const fields = [
|
|
||||||
{
|
|
||||||
label: 'Name',
|
|
||||||
key: 'name',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
description: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Email',
|
|
||||||
key: 'email',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
description:
|
|
||||||
'It is essential to be distinctive. Zendesk prohibits the existence of identical users sharing the same email address.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Details',
|
|
||||||
key: 'details',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Notes',
|
|
||||||
key: 'notes',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description:
|
|
||||||
'Within this field, you have the capability to save any remarks or comments you may have concerning the user.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Phone',
|
|
||||||
key: 'phone',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description:
|
|
||||||
"The user's contact number should be entered in the following format: +1 (555) 123-4567.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Tags',
|
|
||||||
key: 'tags',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description: 'A comma separated list of tags.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Role',
|
|
||||||
key: 'role',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description:
|
|
||||||
"It can take on one of the designated roles: 'end-user', 'agent', or 'admin'. If a different value is set or none is specified, the default is 'end-user.'",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Organization',
|
|
||||||
key: 'organizationId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description: 'Assign this user to a specific organization.',
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listOrganizations',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'External Id',
|
|
||||||
key: 'externalId',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description:
|
|
||||||
'An exclusive external identifier; you can utilize this to link organizations with an external record.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Verified',
|
|
||||||
key: 'verified',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
description:
|
|
||||||
"Specify if you can verify that the user's assertion of their identity is accurate.",
|
|
||||||
variables: true,
|
|
||||||
options: [
|
|
||||||
{ label: 'True', value: 'true' },
|
|
||||||
{ label: 'False', value: 'false' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
@@ -1,53 +0,0 @@
|
|||||||
import { IJSONObject } from '@automatisch/types';
|
|
||||||
import defineAction from '../../../../helpers/define-action';
|
|
||||||
import { fields } from './fields';
|
|
||||||
|
|
||||||
type Payload = {
|
|
||||||
user: IJSONObject;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Create user',
|
|
||||||
key: 'createUser',
|
|
||||||
description: 'Creates a new user.',
|
|
||||||
arguments: fields,
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
email,
|
|
||||||
details,
|
|
||||||
notes,
|
|
||||||
phone,
|
|
||||||
role,
|
|
||||||
organizationId,
|
|
||||||
externalId,
|
|
||||||
verified,
|
|
||||||
} = $.step.parameters;
|
|
||||||
|
|
||||||
const tags = $.step.parameters.tags as string;
|
|
||||||
const formattedTags = tags.split(',');
|
|
||||||
|
|
||||||
const payload: Payload = {
|
|
||||||
user: {
|
|
||||||
name,
|
|
||||||
email,
|
|
||||||
details,
|
|
||||||
notes,
|
|
||||||
phone,
|
|
||||||
organization_id: organizationId,
|
|
||||||
external_id: externalId,
|
|
||||||
verified: verified || 'false',
|
|
||||||
tags: formattedTags,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (role) {
|
|
||||||
payload.user.role = role;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await $.http.post('/api/v2/users', payload);
|
|
||||||
|
|
||||||
$.setActionItem({ raw: response.data });
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,35 +0,0 @@
|
|||||||
import defineAction from '../../../../helpers/define-action';
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Delete ticket',
|
|
||||||
key: 'deleteTicket',
|
|
||||||
description: 'Deletes an existing ticket.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Ticket',
|
|
||||||
key: 'ticketId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
description: 'Select the ticket you want to delete.',
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listFirstPageOfTickets',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const ticketId = $.step.parameters.ticketId;
|
|
||||||
|
|
||||||
const response = await $.http.delete(`/api/v2/tickets/${ticketId}`);
|
|
||||||
|
|
||||||
$.setActionItem({ raw: { data: response.data } });
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,43 +0,0 @@
|
|||||||
import defineAction from '../../../../helpers/define-action';
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Delete user',
|
|
||||||
key: 'deleteUser',
|
|
||||||
description: 'Deletes an existing user.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'User',
|
|
||||||
key: 'userId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
description: 'Select the user you want to modify.',
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listUsers',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'parameters.showUserRole',
|
|
||||||
value: 'true',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'parameters.includeAllUsers',
|
|
||||||
value: 'true',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const userId = $.step.parameters.userId;
|
|
||||||
|
|
||||||
const response = await $.http.delete(`/api/v2/users/${userId}`);
|
|
||||||
|
|
||||||
$.setActionItem({ raw: response.data });
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,32 +0,0 @@
|
|||||||
import defineAction from '../../../../helpers/define-action';
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Find ticket',
|
|
||||||
key: 'findTicket',
|
|
||||||
description: 'Finds an existing ticket.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'Query',
|
|
||||||
key: 'query',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
description:
|
|
||||||
'Write a search string that specifies the way we will search for the ticket in Zendesk.',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const query = $.step.parameters.query;
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
query: `type:ticket ${query}`,
|
|
||||||
sort_by: 'created_at',
|
|
||||||
sort_order: 'desc',
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await $.http.get('/api/v2/search', { params });
|
|
||||||
|
|
||||||
$.setActionItem({ raw: response.data.results[0] });
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,15 +1,3 @@
|
|||||||
import createTicket from './create-ticket';
|
import createTicket from './create-ticket';
|
||||||
import createUser from './create-user';
|
|
||||||
import deleteTicket from './delete-ticket';
|
|
||||||
import deleteUser from './delete-user';
|
|
||||||
import findTicket from './find-ticket';
|
|
||||||
import updateTicket from './update-ticket';
|
|
||||||
|
|
||||||
export default [
|
export default [createTicket];
|
||||||
createTicket,
|
|
||||||
createUser,
|
|
||||||
deleteTicket,
|
|
||||||
deleteUser,
|
|
||||||
findTicket,
|
|
||||||
updateTicket,
|
|
||||||
];
|
|
||||||
|
@@ -1,167 +0,0 @@
|
|||||||
export const fields = [
|
|
||||||
{
|
|
||||||
label: 'Ticket',
|
|
||||||
key: 'ticketId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
description: 'Select the ticket you want to change.',
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listFirstPageOfTickets',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Subject',
|
|
||||||
key: 'subject',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Assignee',
|
|
||||||
key: 'assigneeId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description:
|
|
||||||
'Note: An error occurs if the assignee is not in the default group (or the specific group chosen below).',
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listUsers',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'parameters.showUserRole',
|
|
||||||
value: 'true',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'parameters.includeAdmins',
|
|
||||||
value: 'true',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Group',
|
|
||||||
key: 'groupId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description: 'Allocate this ticket to a specific group.',
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listGroups',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'New Status',
|
|
||||||
key: 'status',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description: '',
|
|
||||||
options: [
|
|
||||||
{ label: 'New', value: 'new' },
|
|
||||||
{ label: 'Open', value: 'open' },
|
|
||||||
{ label: 'Pending', value: 'pending' },
|
|
||||||
{ label: 'Hold', value: 'hold' },
|
|
||||||
{ label: 'Solved', value: 'solved' },
|
|
||||||
{ label: 'Closed', value: 'closed' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'New comment to add to the ticket',
|
|
||||||
key: 'comment',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Should the first comment be public?',
|
|
||||||
key: 'publicOrNot',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description: '',
|
|
||||||
options: [
|
|
||||||
{ label: 'Yes', value: 'yes' },
|
|
||||||
{ label: 'No', value: 'no' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Tags',
|
|
||||||
key: 'tags',
|
|
||||||
type: 'string' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description: 'A comma separated list of tags.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Type',
|
|
||||||
key: 'type',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description: '',
|
|
||||||
options: [
|
|
||||||
{ label: 'Problem', value: 'problem' },
|
|
||||||
{ label: 'Incident', value: 'incident' },
|
|
||||||
{ label: 'Question', value: 'question' },
|
|
||||||
{ label: 'Task', value: 'task' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Priority',
|
|
||||||
key: 'priority',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description: '',
|
|
||||||
options: [
|
|
||||||
{ label: 'Urgent', value: 'urgent' },
|
|
||||||
{ label: 'High', value: 'high' },
|
|
||||||
{ label: 'Normal', value: 'normal' },
|
|
||||||
{ label: 'Low', value: 'low' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Submitter',
|
|
||||||
key: 'submitterId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: false,
|
|
||||||
variables: true,
|
|
||||||
description: '',
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listUsers',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'parameters.includeAdmins',
|
|
||||||
value: 'false',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
@@ -1,57 +0,0 @@
|
|||||||
import defineAction from '../../../../helpers/define-action';
|
|
||||||
import { fields } from './fields';
|
|
||||||
import isEmpty from 'lodash/isEmpty';
|
|
||||||
import omitBy from 'lodash/omitBy';
|
|
||||||
|
|
||||||
export default defineAction({
|
|
||||||
name: 'Update ticket',
|
|
||||||
key: 'updateTicket',
|
|
||||||
description: 'Modify the status of an existing ticket or append comments.',
|
|
||||||
arguments: fields,
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const {
|
|
||||||
ticketId,
|
|
||||||
subject,
|
|
||||||
assigneeId,
|
|
||||||
groupId,
|
|
||||||
status,
|
|
||||||
comment,
|
|
||||||
publicOrNot,
|
|
||||||
type,
|
|
||||||
priority,
|
|
||||||
submitterId,
|
|
||||||
} = $.step.parameters;
|
|
||||||
|
|
||||||
const tags = $.step.parameters.tags as string;
|
|
||||||
const formattedTags = tags.split(',');
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
subject,
|
|
||||||
assignee_id: assigneeId,
|
|
||||||
group_id: groupId,
|
|
||||||
status,
|
|
||||||
comment: {
|
|
||||||
body: comment,
|
|
||||||
public: publicOrNot,
|
|
||||||
},
|
|
||||||
tags: formattedTags,
|
|
||||||
type,
|
|
||||||
priority,
|
|
||||||
submitter_id: submitterId,
|
|
||||||
};
|
|
||||||
|
|
||||||
const fieldsToRemoveIfEmpty = ['group_id', 'status', 'type', 'priority'];
|
|
||||||
|
|
||||||
const filteredPayload = omitBy(
|
|
||||||
payload,
|
|
||||||
(value, key) => fieldsToRemoveIfEmpty.includes(key) && isEmpty(value)
|
|
||||||
);
|
|
||||||
|
|
||||||
const response = await $.http.put(`/api/v2/tickets/${ticketId}`, {
|
|
||||||
ticket: filteredPayload,
|
|
||||||
});
|
|
||||||
|
|
||||||
$.setActionItem({ raw: response.data });
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,20 +1,13 @@
|
|||||||
import listUsers from './list-users';
|
import listUsers from './list-users';
|
||||||
import listBrands from './list-brands';
|
import listBrands from './list-brands';
|
||||||
import listFirstPageOfTickets from './list-first-page-of-tickets';
|
|
||||||
import listGroups from './list-groups';
|
import listGroups from './list-groups';
|
||||||
import listOrganizations from './list-organizations';
|
|
||||||
import listSharingAgreements from './list-sharing-agreements';
|
import listSharingAgreements from './list-sharing-agreements';
|
||||||
import listTicketForms from './list-ticket-forms';
|
import listTicketForms from './list-ticket-forms';
|
||||||
import listViews from './list-views';
|
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
listUsers,
|
listUsers,
|
||||||
listBrands,
|
listBrands,
|
||||||
listFirstPageOfTickets,
|
|
||||||
listGroups,
|
listGroups,
|
||||||
listOrganizations,
|
|
||||||
listSharingAgreements,
|
listSharingAgreements,
|
||||||
listFirstPageOfTickets,
|
|
||||||
listTicketForms,
|
listTicketForms,
|
||||||
listViews,
|
|
||||||
];
|
];
|
||||||
|
@@ -1,33 +0,0 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'List first page of tickets',
|
|
||||||
key: 'listFirstPageOfTickets',
|
|
||||||
|
|
||||||
async run($: IGlobalVariable) {
|
|
||||||
const tickets: {
|
|
||||||
data: IJSONObject[];
|
|
||||||
} = {
|
|
||||||
data: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
'page[size]': 100,
|
|
||||||
sort: '-id',
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await $.http.get('/api/v2/tickets', { params });
|
|
||||||
const allTickets = response.data.tickets;
|
|
||||||
|
|
||||||
if (allTickets?.length) {
|
|
||||||
for (const ticket of allTickets) {
|
|
||||||
tickets.data.push({
|
|
||||||
value: ticket.id,
|
|
||||||
name: ticket.subject,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tickets;
|
|
||||||
},
|
|
||||||
};
|
|
@@ -21,7 +21,7 @@ export default {
|
|||||||
const response = await $.http.get('/api/v2/groups', { params });
|
const response = await $.http.get('/api/v2/groups', { params });
|
||||||
const allGroups = response?.data?.groups;
|
const allGroups = response?.data?.groups;
|
||||||
hasMore = response?.data?.meta?.has_more;
|
hasMore = response?.data?.meta?.has_more;
|
||||||
params['page[after]'] = response.data.meta?.after_cursor;
|
params['page[after]'] = response.data.links?.after_cursor;
|
||||||
|
|
||||||
if (allGroups?.length) {
|
if (allGroups?.length) {
|
||||||
for (const group of allGroups) {
|
for (const group of allGroups) {
|
||||||
|
@@ -1,38 +0,0 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'List organizations',
|
|
||||||
key: 'listOrganizations',
|
|
||||||
|
|
||||||
async run($: IGlobalVariable) {
|
|
||||||
const organizations: {
|
|
||||||
data: IJSONObject[];
|
|
||||||
} = {
|
|
||||||
data: [],
|
|
||||||
};
|
|
||||||
let hasMore;
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
'page[size]': 100,
|
|
||||||
'page[after]': undefined as unknown as string,
|
|
||||||
};
|
|
||||||
|
|
||||||
do {
|
|
||||||
const response = await $.http.get('/api/v2/organizations', { params });
|
|
||||||
const allOrganizations = response?.data?.organizations;
|
|
||||||
hasMore = response?.data?.meta?.has_more;
|
|
||||||
params['page[after]'] = response.data.meta?.after_cursor;
|
|
||||||
|
|
||||||
if (allOrganizations?.length) {
|
|
||||||
for (const organization of allOrganizations) {
|
|
||||||
organizations.data.push({
|
|
||||||
value: organization.id,
|
|
||||||
name: organization.name,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (hasMore);
|
|
||||||
|
|
||||||
return organizations;
|
|
||||||
},
|
|
||||||
};
|
|
@@ -25,7 +25,7 @@ export default {
|
|||||||
const response = await $.http.get('/api/v2/users', { params });
|
const response = await $.http.get('/api/v2/users', { params });
|
||||||
const allUsers = response?.data?.users;
|
const allUsers = response?.data?.users;
|
||||||
hasMore = response?.data?.meta?.has_more;
|
hasMore = response?.data?.meta?.has_more;
|
||||||
params['page[after]'] = response.data.meta?.after_cursor;
|
params['page[after]'] = response.data.links?.after_cursor;
|
||||||
|
|
||||||
if (allUsers?.length) {
|
if (allUsers?.length) {
|
||||||
for (const user of allUsers) {
|
for (const user of allUsers) {
|
||||||
|
@@ -1,38 +0,0 @@
|
|||||||
import { IGlobalVariable, IJSONObject } from '@automatisch/types';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'List views',
|
|
||||||
key: 'listViews',
|
|
||||||
|
|
||||||
async run($: IGlobalVariable) {
|
|
||||||
const views: {
|
|
||||||
data: IJSONObject[];
|
|
||||||
} = {
|
|
||||||
data: [],
|
|
||||||
};
|
|
||||||
let hasMore;
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
'page[size]': 100,
|
|
||||||
'page[after]': undefined as unknown as string,
|
|
||||||
};
|
|
||||||
|
|
||||||
do {
|
|
||||||
const response = await $.http.get('/api/v2/views', { params });
|
|
||||||
const allViews = response?.data?.views;
|
|
||||||
hasMore = response?.data?.meta?.has_more;
|
|
||||||
params['page[after]'] = response.data.meta?.after_cursor;
|
|
||||||
|
|
||||||
if (allViews?.length) {
|
|
||||||
for (const view of allViews) {
|
|
||||||
views.data.push({
|
|
||||||
value: view.id,
|
|
||||||
name: view.title,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (hasMore);
|
|
||||||
|
|
||||||
return views;
|
|
||||||
},
|
|
||||||
};
|
|
@@ -1,7 +1,6 @@
|
|||||||
import defineApp from '../../helpers/define-app';
|
import defineApp from '../../helpers/define-app';
|
||||||
import addAuthHeader from './common/add-auth-headers';
|
import addAuthHeader from './common/add-auth-headers';
|
||||||
import auth from './auth';
|
import auth from './auth';
|
||||||
import triggers from './triggers';
|
|
||||||
import actions from './actions';
|
import actions from './actions';
|
||||||
import dynamicData from './dynamic-data';
|
import dynamicData from './dynamic-data';
|
||||||
|
|
||||||
@@ -16,7 +15,6 @@ export default defineApp({
|
|||||||
supportsConnections: true,
|
supportsConnections: true,
|
||||||
beforeRequest: [addAuthHeader],
|
beforeRequest: [addAuthHeader],
|
||||||
auth,
|
auth,
|
||||||
triggers,
|
|
||||||
actions,
|
actions,
|
||||||
dynamicData,
|
dynamicData,
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +0,0 @@
|
|||||||
import newTickets from './new-tickets';
|
|
||||||
import newUsers from './new-users';
|
|
||||||
|
|
||||||
export default [newTickets, newUsers];
|
|
@@ -1,59 +0,0 @@
|
|||||||
import defineTrigger from '../../../../helpers/define-trigger';
|
|
||||||
|
|
||||||
export default defineTrigger({
|
|
||||||
name: 'New tickets',
|
|
||||||
key: 'newTickets',
|
|
||||||
pollInterval: 15,
|
|
||||||
description: 'Triggers when a new ticket is created in a specific view.',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
label: 'View',
|
|
||||||
key: 'viewId',
|
|
||||||
type: 'dropdown' as const,
|
|
||||||
required: true,
|
|
||||||
variables: true,
|
|
||||||
source: {
|
|
||||||
type: 'query',
|
|
||||||
name: 'getDynamicData',
|
|
||||||
arguments: [
|
|
||||||
{
|
|
||||||
name: 'key',
|
|
||||||
value: 'listViews',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const viewId = $.step.parameters.viewId;
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
'page[size]': 100,
|
|
||||||
'page[after]': undefined as unknown as string,
|
|
||||||
sort_by: 'nice_id',
|
|
||||||
sort_order: 'desc',
|
|
||||||
};
|
|
||||||
let hasMore;
|
|
||||||
|
|
||||||
do {
|
|
||||||
const response = await $.http.get(`/api/v2/views/${viewId}/tickets`, {
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
const allTickets = response?.data?.tickets;
|
|
||||||
hasMore = response?.data?.meta?.has_more;
|
|
||||||
params['page[after]'] = response.data.meta?.after_cursor;
|
|
||||||
|
|
||||||
if (allTickets?.length) {
|
|
||||||
for (const ticket of allTickets) {
|
|
||||||
$.pushTriggerItem({
|
|
||||||
raw: ticket,
|
|
||||||
meta: {
|
|
||||||
internalId: ticket.id.toString(),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (hasMore);
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,83 +0,0 @@
|
|||||||
import Crypto from 'crypto';
|
|
||||||
import defineTrigger from '../../../../helpers/define-trigger';
|
|
||||||
|
|
||||||
export default defineTrigger({
|
|
||||||
name: 'New users',
|
|
||||||
key: 'newUsers',
|
|
||||||
type: 'webhook',
|
|
||||||
description: 'Triggers upon the creation of a new user.',
|
|
||||||
|
|
||||||
async run($) {
|
|
||||||
const dataItem = {
|
|
||||||
raw: $.request.body,
|
|
||||||
meta: {
|
|
||||||
internalId: Crypto.randomUUID(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
$.pushTriggerItem(dataItem);
|
|
||||||
},
|
|
||||||
|
|
||||||
async testRun($) {
|
|
||||||
const params = {
|
|
||||||
query: 'type:user',
|
|
||||||
sort_by: 'created_at',
|
|
||||||
sort_order: 'desc',
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await $.http.get('/api/v2/search', { params });
|
|
||||||
|
|
||||||
const lastUser = response.data.results[0];
|
|
||||||
|
|
||||||
const computedWebhookEvent = {
|
|
||||||
id: Crypto.randomUUID(),
|
|
||||||
time: lastUser.created_at,
|
|
||||||
type: 'zen:event-type:user.created',
|
|
||||||
event: {},
|
|
||||||
detail: {
|
|
||||||
id: lastUser.id,
|
|
||||||
role: lastUser.role,
|
|
||||||
email: lastUser.email,
|
|
||||||
created_at: lastUser.created_at,
|
|
||||||
updated_at: lastUser.updated_at,
|
|
||||||
external_id: lastUser.external_id,
|
|
||||||
organization_id: lastUser.organization_id,
|
|
||||||
default_group_id: lastUser.default_group_id,
|
|
||||||
},
|
|
||||||
subject: `zen:user:${lastUser.id}`,
|
|
||||||
account_id: '',
|
|
||||||
zendesk_event_version: '2022-11-06',
|
|
||||||
};
|
|
||||||
|
|
||||||
const dataItem = {
|
|
||||||
raw: computedWebhookEvent,
|
|
||||||
meta: {
|
|
||||||
internalId: computedWebhookEvent.id,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
$.pushTriggerItem(dataItem);
|
|
||||||
},
|
|
||||||
|
|
||||||
async registerHook($) {
|
|
||||||
const payload = {
|
|
||||||
webhook: {
|
|
||||||
name: `Flow ID: ${$.flow.id}`,
|
|
||||||
status: 'active',
|
|
||||||
subscriptions: ['zen:event-type:user.created'],
|
|
||||||
endpoint: $.webhookUrl,
|
|
||||||
http_method: 'POST',
|
|
||||||
request_format: 'json',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await $.http.post('/api/v2/webhooks', payload);
|
|
||||||
const id = response.data.webhook.id;
|
|
||||||
|
|
||||||
await $.flow.setRemoteWebhookId(id);
|
|
||||||
},
|
|
||||||
|
|
||||||
async unregisterHook($) {
|
|
||||||
await $.http.delete(`/api/v2/webhooks/${$.flow.remoteWebhookId}`);
|
|
||||||
},
|
|
||||||
});
|
|
@@ -1,15 +0,0 @@
|
|||||||
import { Knex } from 'knex';
|
|
||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
|
||||||
return knex.schema.createTable('shared_connections', (table) => {
|
|
||||||
table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()'));
|
|
||||||
table.uuid('connection_id').notNullable().references('id').inTable('connections');
|
|
||||||
table.uuid('role_id').notNullable().references('id').inTable('roles');
|
|
||||||
|
|
||||||
table.timestamps(true, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
|
||||||
return knex.schema.dropTable('shared_connections');
|
|
||||||
}
|
|
@@ -19,7 +19,6 @@ import login from './mutations/login';
|
|||||||
import registerUser from './mutations/register-user.ee';
|
import registerUser from './mutations/register-user.ee';
|
||||||
import resetConnection from './mutations/reset-connection';
|
import resetConnection from './mutations/reset-connection';
|
||||||
import resetPassword from './mutations/reset-password.ee';
|
import resetPassword from './mutations/reset-password.ee';
|
||||||
import shareConnection from './mutations/share-connection.ee';
|
|
||||||
import updateAppAuthClient from './mutations/update-app-auth-client.ee';
|
import updateAppAuthClient from './mutations/update-app-auth-client.ee';
|
||||||
import updateAppConfig from './mutations/update-app-config.ee';
|
import updateAppConfig from './mutations/update-app-config.ee';
|
||||||
import updateConfig from './mutations/update-config.ee';
|
import updateConfig from './mutations/update-config.ee';
|
||||||
@@ -56,7 +55,6 @@ const mutationResolvers = {
|
|||||||
registerUser,
|
registerUser,
|
||||||
resetConnection,
|
resetConnection,
|
||||||
resetPassword,
|
resetPassword,
|
||||||
shareConnection,
|
|
||||||
updateAppAuthClient,
|
updateAppAuthClient,
|
||||||
updateAppConfig,
|
updateAppConfig,
|
||||||
updateConfig,
|
updateConfig,
|
||||||
|
@@ -28,11 +28,11 @@ const createFlow = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (connectionId) {
|
if (connectionId) {
|
||||||
const connection = await context.currentUser
|
const hasConnection = await context.currentUser
|
||||||
.relatedConnectionsQuery()
|
.$relatedQuery('connections')
|
||||||
.findById(connectionId);
|
.findById(connectionId);
|
||||||
|
|
||||||
if (!connection) {
|
if (!hasConnection) {
|
||||||
throw new Error('The connection does not exist!');
|
throw new Error('The connection does not exist!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import Connection from '../../models/connection';
|
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
input: {
|
input: {
|
||||||
@@ -12,13 +11,10 @@ const deleteConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const conditions = context.currentUser.can('delete', 'Connection');
|
context.currentUser.can('delete', 'Connection');
|
||||||
const userConnections = context.currentUser.$relatedQuery('connections');
|
|
||||||
const allConnections = Connection.query();
|
|
||||||
const baseQuery = conditions.isCreator ? userConnections : allConnections;
|
|
||||||
|
|
||||||
await baseQuery
|
await context.currentUser
|
||||||
.clone()
|
.$relatedQuery('connections')
|
||||||
.delete()
|
.delete()
|
||||||
.findOne({
|
.findOne({
|
||||||
id: params.input.id,
|
id: params.input.id,
|
||||||
|
@@ -4,7 +4,6 @@ import deleteUserQueue from '../../queues/delete-user.ee';
|
|||||||
import flowQueue from '../../queues/flow';
|
import flowQueue from '../../queues/flow';
|
||||||
import Flow from '../../models/flow';
|
import Flow from '../../models/flow';
|
||||||
import Execution from '../../models/execution';
|
import Execution from '../../models/execution';
|
||||||
import User from '../../models/user';
|
|
||||||
import ExecutionStep from '../../models/execution-step';
|
import ExecutionStep from '../../models/execution-step';
|
||||||
import appConfig from '../../config/app';
|
import appConfig from '../../config/app';
|
||||||
|
|
||||||
@@ -15,87 +14,51 @@ const deleteCurrentUser = async (
|
|||||||
) => {
|
) => {
|
||||||
const id = context.currentUser.id;
|
const id = context.currentUser.id;
|
||||||
|
|
||||||
try {
|
const flows = await context.currentUser.$relatedQuery('flows').where({
|
||||||
await User.transaction(async (trx) => {
|
active: true,
|
||||||
const flows = await context.currentUser
|
});
|
||||||
.$relatedQuery('flows', trx)
|
|
||||||
.where({
|
|
||||||
active: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { count } = await context.currentUser
|
const repeatableJobs = await flowQueue.getRepeatableJobs();
|
||||||
.$relatedQuery('connections', trx)
|
|
||||||
.joinRelated('sharedConnections')
|
|
||||||
.joinRelated('steps')
|
|
||||||
.join('flows', function () {
|
|
||||||
this
|
|
||||||
.on(
|
|
||||||
'flows.id', '=', 'steps.flow_id'
|
|
||||||
)
|
|
||||||
.andOnVal(
|
|
||||||
'flows.user_id', '<>', id
|
|
||||||
)
|
|
||||||
.andOnVal(
|
|
||||||
'flows.active', '=', true
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.count()
|
|
||||||
.first();
|
|
||||||
|
|
||||||
if (count) {
|
for (const flow of flows) {
|
||||||
throw new Error('The shared connections must be removed first!');
|
const job = repeatableJobs.find((job) => job.id === flow.id);
|
||||||
}
|
|
||||||
|
|
||||||
const executionIds = (
|
if (job) {
|
||||||
await context.currentUser
|
await flowQueue.removeRepeatableByKey(job.key);
|
||||||
.$relatedQuery('executions', trx)
|
|
||||||
.select('executions.id')
|
|
||||||
).map((execution: Execution) => execution.id);
|
|
||||||
const flowIds = flows.map((flow) => flow.id);
|
|
||||||
|
|
||||||
await ExecutionStep.query(trx).delete().whereIn('execution_id', executionIds);
|
|
||||||
await context.currentUser.$relatedQuery('executions', trx).delete();
|
|
||||||
await context.currentUser.$relatedQuery('steps', trx).delete();
|
|
||||||
await Flow.query(trx).whereIn('id', flowIds).delete();
|
|
||||||
await context.currentUser.$relatedQuery('connections', trx).delete();
|
|
||||||
await context.currentUser.$relatedQuery('identities', trx).delete();
|
|
||||||
|
|
||||||
if (appConfig.isCloud) {
|
|
||||||
await context.currentUser.$relatedQuery('subscriptions', trx).delete();
|
|
||||||
await context.currentUser.$relatedQuery('usageData', trx).delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
await context.currentUser.$query(trx).delete();
|
|
||||||
|
|
||||||
const jobName = `Delete user - ${id}`;
|
|
||||||
const jobPayload = { id };
|
|
||||||
const millisecondsFor30Days = Duration.fromObject({ days: 30 }).toMillis();
|
|
||||||
const jobOptions = {
|
|
||||||
delay: millisecondsFor30Days,
|
|
||||||
};
|
|
||||||
|
|
||||||
// must be done as the last action as this cannot be reverted via the transaction!
|
|
||||||
const repeatableJobs = await flowQueue.getRepeatableJobs();
|
|
||||||
|
|
||||||
for (const flow of flows) {
|
|
||||||
const job = repeatableJobs.find((job) => job.id === flow.id);
|
|
||||||
|
|
||||||
if (job) {
|
|
||||||
await flowQueue.removeRepeatableByKey(job.key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await deleteUserQueue.add(jobName, jobPayload, jobOptions);
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof Error) {
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('The user deletion has failed!');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const executionIds = (
|
||||||
|
await context.currentUser
|
||||||
|
.$relatedQuery('executions')
|
||||||
|
.select('executions.id')
|
||||||
|
).map((execution: Execution) => execution.id);
|
||||||
|
const flowIds = flows.map((flow) => flow.id);
|
||||||
|
|
||||||
|
await ExecutionStep.query().delete().whereIn('execution_id', executionIds);
|
||||||
|
await context.currentUser.$relatedQuery('executions').delete();
|
||||||
|
await context.currentUser.$relatedQuery('steps').delete();
|
||||||
|
await Flow.query().whereIn('id', flowIds).delete();
|
||||||
|
await context.currentUser.$relatedQuery('connections').delete();
|
||||||
|
await context.currentUser.$relatedQuery('identities').delete();
|
||||||
|
|
||||||
|
if (appConfig.isCloud) {
|
||||||
|
await context.currentUser.$relatedQuery('subscriptions').delete();
|
||||||
|
await context.currentUser.$relatedQuery('usageData').delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
await context.currentUser.$query().delete();
|
||||||
|
|
||||||
|
const jobName = `Delete user - ${id}`;
|
||||||
|
const jobPayload = { id };
|
||||||
|
const millisecondsFor30Days = Duration.fromObject({ days: 30 }).toMillis();
|
||||||
|
const jobOptions = {
|
||||||
|
delay: millisecondsFor30Days,
|
||||||
|
};
|
||||||
|
|
||||||
|
await deleteUserQueue.add(jobName, jobPayload, jobOptions);
|
||||||
|
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default deleteCurrentUser;
|
export default deleteCurrentUser;
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import Connection from '../../models/connection';
|
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
input: {
|
input: {
|
||||||
@@ -12,13 +11,10 @@ const resetConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const conditions = context.currentUser.can('update', 'Connection');
|
context.currentUser.can('create', 'Connection');
|
||||||
const userConnections = context.currentUser.$relatedQuery('connections');
|
|
||||||
const allConnections = Connection.query();
|
|
||||||
const baseQuery = conditions.isCreator ? userConnections : allConnections;
|
|
||||||
|
|
||||||
let connection = await baseQuery
|
let connection = await context.currentUser
|
||||||
.clone()
|
.$relatedQuery('connections')
|
||||||
.findOne({
|
.findOne({
|
||||||
id: params.input.id,
|
id: params.input.id,
|
||||||
})
|
})
|
||||||
|
@@ -1,55 +0,0 @@
|
|||||||
import Context from '../../types/express/context';
|
|
||||||
import Connection from '../../models/connection';
|
|
||||||
import SharedConnection from '../../models/shared-connection';
|
|
||||||
|
|
||||||
type Params = {
|
|
||||||
input: {
|
|
||||||
id: string;
|
|
||||||
roleIds: string[];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const shareConnection = async (
|
|
||||||
_parent: unknown,
|
|
||||||
params: Params,
|
|
||||||
context: Context
|
|
||||||
) => {
|
|
||||||
const conditions = context.currentUser.can('update', 'Connection');
|
|
||||||
|
|
||||||
if (conditions.isCreator) return;
|
|
||||||
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
roleIds,
|
|
||||||
} = params.input;
|
|
||||||
|
|
||||||
const connection = await Connection
|
|
||||||
.query()
|
|
||||||
.findById(id)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const updatedConnection = await Connection.transaction(async (trx) => {
|
|
||||||
await connection.$relatedQuery('sharedConnections', trx).delete();
|
|
||||||
|
|
||||||
if (roleIds?.length) {
|
|
||||||
const sharedConnections = roleIds.map((roleId) => ({
|
|
||||||
roleId,
|
|
||||||
connectionId: connection.id,
|
|
||||||
}));
|
|
||||||
|
|
||||||
await SharedConnection.query().insert(sharedConnections);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await Connection
|
|
||||||
.query(trx)
|
|
||||||
.findById(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
return updatedConnection;
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error('The connection sharing preferences could not be updated!');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default shareConnection;
|
|
@@ -1,7 +1,6 @@
|
|||||||
import { IJSONObject } from '@automatisch/types';
|
import { IJSONObject } from '@automatisch/types';
|
||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
import AppAuthClient from '../../models/app-auth-client';
|
import AppAuthClient from '../../models/app-auth-client';
|
||||||
import Connection from '../../models/connection';
|
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
input: {
|
input: {
|
||||||
@@ -16,13 +15,10 @@ const updateConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const conditions = context.currentUser.can('update', 'Connection');
|
context.currentUser.can('create', 'Connection');
|
||||||
const userConnections = context.currentUser.$relatedQuery('connections');
|
|
||||||
const allConnections = Connection.query();
|
|
||||||
const baseQuery = conditions.isCreator ? userConnections : allConnections;
|
|
||||||
|
|
||||||
let connection = await baseQuery
|
let connection = await context.currentUser
|
||||||
.clone()
|
.$relatedQuery('connections')
|
||||||
.findOne({
|
.findOne({
|
||||||
id: params.input.id,
|
id: params.input.id,
|
||||||
})
|
})
|
||||||
|
@@ -45,11 +45,10 @@ const updateStep = async (
|
|||||||
|
|
||||||
canSeeAllConnections = !conditions.isCreator;
|
canSeeAllConnections = !conditions.isCreator;
|
||||||
} catch {
|
} catch {
|
||||||
// The user does not have permission to read any connections!
|
// void
|
||||||
throw new Error('The connection does not exist!');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const userConnections = context.currentUser.relatedConnectionsQuery();
|
const userConnections = context.currentUser.$relatedQuery('connections');
|
||||||
const allConnections = Connection.query();
|
const allConnections = Connection.query();
|
||||||
const baseConnectionsQuery = canSeeAllConnections ? allConnections : userConnections;
|
const baseConnectionsQuery = canSeeAllConnections ? allConnections : userConnections;
|
||||||
|
|
||||||
|
@@ -9,55 +9,28 @@ type Params = {
|
|||||||
const getApp = async (_parent: unknown, params: Params, context: Context) => {
|
const getApp = async (_parent: unknown, params: Params, context: Context) => {
|
||||||
const conditions = context.currentUser.can('read', 'Connection');
|
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);
|
const app = await App.findOneByKey(params.key);
|
||||||
|
|
||||||
if (context.currentUser) {
|
if (context.currentUser) {
|
||||||
const userConnections = context.currentUser.relatedConnectionsQuery();
|
const connections = await connectionBaseQuery
|
||||||
const allConnections = Connection.query();
|
.clone()
|
||||||
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
|
.select('connections.*')
|
||||||
|
|
||||||
const connections = await Connection.query()
|
|
||||||
.with('connections', connectionBaseQuery)
|
|
||||||
.with(
|
|
||||||
'connections_with_flow_count',
|
|
||||||
Connection.query()
|
|
||||||
.clearSelect()
|
|
||||||
.select('connections.id')
|
|
||||||
.leftJoinRelated('steps')
|
|
||||||
.leftJoin('flows', function () {
|
|
||||||
this
|
|
||||||
.on(
|
|
||||||
'flows.id',
|
|
||||||
'=',
|
|
||||||
'steps.flow_id',
|
|
||||||
)
|
|
||||||
|
|
||||||
if (conditions.isCreator) {
|
|
||||||
this.andOnVal(
|
|
||||||
'flows.user_id',
|
|
||||||
'=',
|
|
||||||
context.currentUser.id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.where({
|
|
||||||
'connections.key': params.key,
|
|
||||||
'connections.draft': false,
|
|
||||||
})
|
|
||||||
.countDistinct('steps.flow_id as flowCount')
|
|
||||||
.groupBy('connections.id')
|
|
||||||
)
|
|
||||||
.select(
|
|
||||||
'connections.*',
|
|
||||||
'connections_with_flow_count.flowCount as flowCount'
|
|
||||||
)
|
|
||||||
.from('connections')
|
|
||||||
.withGraphFetched({
|
.withGraphFetched({
|
||||||
appConfig: true,
|
appConfig: true,
|
||||||
appAuthClient: true
|
appAuthClient: true
|
||||||
})
|
})
|
||||||
.joinRaw('join connections_with_flow_count on connections.id = connections_with_flow_count.id')
|
.fullOuterJoinRelated('steps')
|
||||||
.orderBy('connections.created_at', 'desc');
|
.where({
|
||||||
|
'connections.key': params.key,
|
||||||
|
'connections.draft': false,
|
||||||
|
})
|
||||||
|
.countDistinct('steps.flow_id as flowCount')
|
||||||
|
.groupBy('connections.id')
|
||||||
|
.orderBy('created_at', 'desc');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...app,
|
...app,
|
||||||
|
@@ -15,7 +15,7 @@ const getConnectedApps = async (
|
|||||||
) => {
|
) => {
|
||||||
const conditions = context.currentUser.can('read', 'Connection');
|
const conditions = context.currentUser.can('read', 'Connection');
|
||||||
|
|
||||||
const userConnections = context.currentUser.relatedConnectionsQuery();
|
const userConnections = context.currentUser.$relatedQuery('connections');
|
||||||
const allConnections = Connection.query();
|
const allConnections = Connection.query();
|
||||||
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
|
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
|
||||||
|
|
||||||
@@ -25,9 +25,8 @@ const getConnectedApps = async (
|
|||||||
|
|
||||||
let apps = await App.findAll(params.name);
|
let apps = await App.findAll(params.name);
|
||||||
|
|
||||||
const connections = await Connection
|
const connections = await connectionBaseQuery
|
||||||
.query()
|
.clone()
|
||||||
.with('connections', connectionBaseQuery)
|
|
||||||
.select('connections.key')
|
.select('connections.key')
|
||||||
.where({ draft: false })
|
.where({ draft: false })
|
||||||
.count('connections.id as count')
|
.count('connections.id as count')
|
||||||
|
@@ -1,29 +0,0 @@
|
|||||||
import Context from '../../types/express/context';
|
|
||||||
import Connection from '../../models/connection';
|
|
||||||
|
|
||||||
type Params = {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSharedConnectionRoleIds = async (
|
|
||||||
_parent: unknown,
|
|
||||||
params: Params,
|
|
||||||
context: Context
|
|
||||||
) => {
|
|
||||||
const conditions = context.currentUser.can('update', 'Connection');
|
|
||||||
|
|
||||||
if (conditions.isCreator) return;
|
|
||||||
|
|
||||||
const connection = await Connection
|
|
||||||
.query()
|
|
||||||
.findById(params.id)
|
|
||||||
.throwIfNotFound();
|
|
||||||
|
|
||||||
const sharedConnections = await connection.$relatedQuery('sharedConnections');
|
|
||||||
|
|
||||||
const roleIds = sharedConnections.map(({ roleId }) => roleId);
|
|
||||||
|
|
||||||
return roleIds;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getSharedConnectionRoleIds;
|
|
@@ -13,15 +13,15 @@ const testConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const conditions = context.currentUser.can('read', 'Connection');
|
const conditions = context.currentUser.can('update', 'Connection');
|
||||||
const userConnections = context.currentUser.relatedConnectionsQuery();
|
const userConnections = context.currentUser.$relatedQuery('connections');
|
||||||
const allConnections = Connection.query();
|
const allConnections = Connection.query();
|
||||||
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
|
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
|
||||||
|
|
||||||
let connection = await connectionBaseQuery
|
let connection = await connectionBaseQuery
|
||||||
.clone()
|
.clone()
|
||||||
.findOne({
|
.findOne({
|
||||||
'connections.id': params.id,
|
id: params.id,
|
||||||
})
|
})
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
@@ -24,7 +24,6 @@ import getRole from './queries/get-role.ee';
|
|||||||
import getRoles from './queries/get-roles.ee';
|
import getRoles from './queries/get-roles.ee';
|
||||||
import getSamlAuthProviderRoleMappings from './queries/get-saml-auth-provider-role-mappings.ee';
|
import getSamlAuthProviderRoleMappings from './queries/get-saml-auth-provider-role-mappings.ee';
|
||||||
import getSamlAuthProvider from './queries/get-saml-auth-provider.ee';
|
import getSamlAuthProvider from './queries/get-saml-auth-provider.ee';
|
||||||
import getSharedConnectionRoleIds from './queries/get-shared-connection-role-ids.ee';
|
|
||||||
import getStepWithTestExecutions from './queries/get-step-with-test-executions';
|
import getStepWithTestExecutions from './queries/get-step-with-test-executions';
|
||||||
import getSubscriptionStatus from './queries/get-subscription-status.ee';
|
import getSubscriptionStatus from './queries/get-subscription-status.ee';
|
||||||
import getTrialStatus from './queries/get-trial-status.ee';
|
import getTrialStatus from './queries/get-trial-status.ee';
|
||||||
@@ -61,7 +60,6 @@ const queryResolvers = {
|
|||||||
getRoles,
|
getRoles,
|
||||||
getSamlAuthProvider,
|
getSamlAuthProvider,
|
||||||
getSamlAuthProviderRoleMappings,
|
getSamlAuthProviderRoleMappings,
|
||||||
getSharedConnectionRoleIds,
|
|
||||||
getStepWithTestExecutions,
|
getStepWithTestExecutions,
|
||||||
getSubscriptionStatus,
|
getSubscriptionStatus,
|
||||||
getTrialStatus,
|
getTrialStatus,
|
||||||
|
@@ -53,7 +53,6 @@ type Query {
|
|||||||
getNotifications: [Notification]
|
getNotifications: [Notification]
|
||||||
getSamlAuthProvider: SamlAuthProvider
|
getSamlAuthProvider: SamlAuthProvider
|
||||||
getSamlAuthProviderRoleMappings(id: String!): [SamlAuthProvidersRoleMapping]
|
getSamlAuthProviderRoleMappings(id: String!): [SamlAuthProvidersRoleMapping]
|
||||||
getSharedConnectionRoleIds(id: String!): [String]
|
|
||||||
getSubscriptionStatus: GetSubscriptionStatus
|
getSubscriptionStatus: GetSubscriptionStatus
|
||||||
getTrialStatus: GetTrialStatus
|
getTrialStatus: GetTrialStatus
|
||||||
getUser(id: String!): User
|
getUser(id: String!): User
|
||||||
@@ -84,7 +83,6 @@ type Mutation {
|
|||||||
registerUser(input: RegisterUserInput): User
|
registerUser(input: RegisterUserInput): User
|
||||||
resetConnection(input: ResetConnectionInput): Connection
|
resetConnection(input: ResetConnectionInput): Connection
|
||||||
resetPassword(input: ResetPasswordInput): Boolean
|
resetPassword(input: ResetPasswordInput): Boolean
|
||||||
shareConnection(input: ShareConnectionInput): Connection
|
|
||||||
updateAppAuthClient(input: UpdateAppAuthClientInput): AppAuthClient
|
updateAppAuthClient(input: UpdateAppAuthClientInput): AppAuthClient
|
||||||
updateAppConfig(input: UpdateAppConfigInput): AppConfig
|
updateAppConfig(input: UpdateAppConfigInput): AppConfig
|
||||||
updateConfig(input: JSONObject): JSONObject
|
updateConfig(input: JSONObject): JSONObject
|
||||||
@@ -246,7 +244,6 @@ type AuthLink {
|
|||||||
type Connection {
|
type Connection {
|
||||||
id: String
|
id: String
|
||||||
key: String
|
key: String
|
||||||
shared: Boolean
|
|
||||||
reconnectable: Boolean
|
reconnectable: Boolean
|
||||||
appAuthClientId: String
|
appAuthClientId: String
|
||||||
formattedData: ConnectionData
|
formattedData: ConnectionData
|
||||||
@@ -813,11 +810,6 @@ input ExecutionFiltersInput {
|
|||||||
status: String
|
status: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input ShareConnectionInput {
|
|
||||||
id: String!
|
|
||||||
roleIds: [String]
|
|
||||||
}
|
|
||||||
|
|
||||||
schema {
|
schema {
|
||||||
query: Query
|
query: Query
|
||||||
mutation: Mutation
|
mutation: Mutation
|
||||||
|
@@ -1,18 +1,18 @@
|
|||||||
import { IJSONObject, IRequest } from '@automatisch/types';
|
import { QueryContext, ModelOptions } from 'objection';
|
||||||
import { AES, enc } from 'crypto-js';
|
|
||||||
import type { RelationMappings } from 'objection';
|
import type { RelationMappings } from 'objection';
|
||||||
import { ModelOptions, QueryContext } from 'objection';
|
import { AES, enc } from 'crypto-js';
|
||||||
import appConfig from '../config/app';
|
import { IRequest } from '@automatisch/types';
|
||||||
import globalVariable from '../helpers/global-variable';
|
|
||||||
import Telemetry from '../helpers/telemetry';
|
|
||||||
import App from './app';
|
import App from './app';
|
||||||
import AppAuthClient from './app-auth-client';
|
|
||||||
import AppConfig from './app-config';
|
import AppConfig from './app-config';
|
||||||
|
import AppAuthClient from './app-auth-client';
|
||||||
import Base from './base';
|
import Base from './base';
|
||||||
import ExtendedQueryBuilder from './query-builder';
|
|
||||||
import SharedConnection from './shared-connection';
|
|
||||||
import Step from './step';
|
|
||||||
import User from './user';
|
import User from './user';
|
||||||
|
import Step from './step';
|
||||||
|
import ExtendedQueryBuilder from './query-builder';
|
||||||
|
import appConfig from '../config/app';
|
||||||
|
import { IJSONObject } from '@automatisch/types';
|
||||||
|
import Telemetry from '../helpers/telemetry';
|
||||||
|
import globalVariable from '../helpers/global-variable';
|
||||||
|
|
||||||
class Connection extends Base {
|
class Connection extends Base {
|
||||||
id!: string;
|
id!: string;
|
||||||
@@ -24,9 +24,6 @@ class Connection extends Base {
|
|||||||
draft: boolean;
|
draft: boolean;
|
||||||
count?: number;
|
count?: number;
|
||||||
flowCount?: number;
|
flowCount?: number;
|
||||||
sharedConnections?: SharedConnection[];
|
|
||||||
// computed via `User.relevantConnectionsQuery`
|
|
||||||
shared?: boolean;
|
|
||||||
user?: User;
|
user?: User;
|
||||||
steps?: Step[];
|
steps?: Step[];
|
||||||
triggerSteps?: Step[];
|
triggerSteps?: Step[];
|
||||||
@@ -49,7 +46,6 @@ class Connection extends Base {
|
|||||||
appAuthClientId: { type: 'string', format: 'uuid' },
|
appAuthClientId: { type: 'string', format: 'uuid' },
|
||||||
verified: { type: 'boolean', default: false },
|
verified: { type: 'boolean', default: false },
|
||||||
draft: { type: 'boolean' },
|
draft: { type: 'boolean' },
|
||||||
shared: { type: 'boolean', readOnly: true, },
|
|
||||||
deletedAt: { type: 'string' },
|
deletedAt: { type: 'string' },
|
||||||
createdAt: { type: 'string' },
|
createdAt: { type: 'string' },
|
||||||
updatedAt: { type: 'string' },
|
updatedAt: { type: 'string' },
|
||||||
@@ -104,14 +100,6 @@ class Connection extends Base {
|
|||||||
to: 'app_auth_clients.id',
|
to: 'app_auth_clients.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
sharedConnections: {
|
|
||||||
relation: Base.HasManyRelation,
|
|
||||||
modelClass: SharedConnection,
|
|
||||||
join: {
|
|
||||||
from: 'connections.id',
|
|
||||||
to: 'shared_connections.connection_id',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
get reconnectable() {
|
get reconnectable() {
|
||||||
|
@@ -1,45 +0,0 @@
|
|||||||
import Base from './base';
|
|
||||||
import Role from './role';
|
|
||||||
import User from './user';
|
|
||||||
|
|
||||||
class SharedConnection extends Base {
|
|
||||||
id!: string;
|
|
||||||
roleId!: string;
|
|
||||||
connectionId!: string;
|
|
||||||
|
|
||||||
static tableName = 'shared_connections';
|
|
||||||
|
|
||||||
static jsonSchema = {
|
|
||||||
type: 'object',
|
|
||||||
required: ['roleId', 'connectionId'],
|
|
||||||
|
|
||||||
properties: {
|
|
||||||
id: { type: 'string', format: 'uuid' },
|
|
||||||
roleId: { type: 'string', format: 'uuid' },
|
|
||||||
connectionId: { type: 'string', format: 'uuid' },
|
|
||||||
createdAt: { type: 'string' },
|
|
||||||
updatedAt: { type: 'string' },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static relationMappings = () => ({
|
|
||||||
roles: {
|
|
||||||
relation: Base.HasManyRelation,
|
|
||||||
modelClass: Role,
|
|
||||||
join: {
|
|
||||||
from: 'shared_connections.role_id',
|
|
||||||
to: 'roles.id',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
users: {
|
|
||||||
relation: Base.HasManyRelation,
|
|
||||||
modelClass: User,
|
|
||||||
join: {
|
|
||||||
from: 'shared_connections.role_id',
|
|
||||||
to: 'users.role_id',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SharedConnection;
|
|
@@ -1,7 +1,7 @@
|
|||||||
import bcrypt from 'bcrypt';
|
import bcrypt from 'bcrypt';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import crypto from 'node:crypto';
|
import crypto from 'node:crypto';
|
||||||
import { raw, ModelOptions, QueryContext } from 'objection';
|
import { ModelOptions, QueryContext } from 'objection';
|
||||||
|
|
||||||
import appConfig from '../config/app';
|
import appConfig from '../config/app';
|
||||||
import { hasValidLicense } from '../helpers/license.ee';
|
import { hasValidLicense } from '../helpers/license.ee';
|
||||||
@@ -28,7 +28,6 @@ class User extends Base {
|
|||||||
resetPasswordTokenSentAt: string;
|
resetPasswordTokenSentAt: string;
|
||||||
trialExpiryDate: string;
|
trialExpiryDate: string;
|
||||||
connections?: Connection[];
|
connections?: Connection[];
|
||||||
sharedConnections?: Connection[];
|
|
||||||
flows?: Flow[];
|
flows?: Flow[];
|
||||||
steps?: Step[];
|
steps?: Step[];
|
||||||
executions?: Execution[];
|
executions?: Execution[];
|
||||||
@@ -70,18 +69,6 @@ class User extends Base {
|
|||||||
to: 'connections.user_id',
|
to: 'connections.user_id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
sharedConnections: {
|
|
||||||
relation: Base.ManyToManyRelation,
|
|
||||||
modelClass: Connection,
|
|
||||||
join: {
|
|
||||||
from: 'users.role_id',
|
|
||||||
through: {
|
|
||||||
from: 'shared_connections.role_id',
|
|
||||||
to: 'shared_connections.connection_id',
|
|
||||||
},
|
|
||||||
to: 'connections.id',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
flows: {
|
flows: {
|
||||||
relation: Base.HasManyRelation,
|
relation: Base.HasManyRelation,
|
||||||
modelClass: Flow,
|
modelClass: Flow,
|
||||||
@@ -178,40 +165,6 @@ class User extends Base {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
relatedConnectionsQuery() {
|
|
||||||
return Connection
|
|
||||||
.query()
|
|
||||||
.select('connections.*', raw('shared_connections.role_id IS NOT NULL as shared'))
|
|
||||||
.leftJoin(
|
|
||||||
'shared_connections',
|
|
||||||
'connections.id',
|
|
||||||
'=',
|
|
||||||
'shared_connections.connection_id'
|
|
||||||
)
|
|
||||||
.join(
|
|
||||||
'users',
|
|
||||||
function () {
|
|
||||||
this
|
|
||||||
.on(
|
|
||||||
'users.id',
|
|
||||||
'=',
|
|
||||||
'connections.user_id',
|
|
||||||
)
|
|
||||||
.orOn(
|
|
||||||
'users.role_id',
|
|
||||||
'=',
|
|
||||||
'shared_connections.role_id'
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
'users.id',
|
|
||||||
'=',
|
|
||||||
this.id
|
|
||||||
)
|
|
||||||
.groupBy('connections.id', 'shared_connections.role_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
login(password: string) {
|
login(password: string) {
|
||||||
return bcrypt.compare(password, this.password);
|
return bcrypt.compare(password, this.password);
|
||||||
}
|
}
|
||||||
|
@@ -303,10 +303,7 @@ export default defineConfig({
|
|||||||
text: 'Remove.bg',
|
text: 'Remove.bg',
|
||||||
collapsible: true,
|
collapsible: true,
|
||||||
collapsed: true,
|
collapsed: true,
|
||||||
items: [
|
items: [{ text: 'Connection', link: '/apps/removebg/connection' }],
|
||||||
{ text: 'Actions', link: '/apps/removebg/actions' },
|
|
||||||
{ text: 'Connection', link: '/apps/removebg/connection' }
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'RSS',
|
text: 'RSS',
|
||||||
|
@@ -3,8 +3,6 @@ favicon: /favicons/discord.svg
|
|||||||
items:
|
items:
|
||||||
- name: Send a message to channel
|
- name: Send a message to channel
|
||||||
desc: Sends a message to a specific channel you specify.
|
desc: Sends a message to a specific channel you specify.
|
||||||
- name: Create a scheduled event
|
|
||||||
desc: Creates a scheduled event.
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
@@ -3,8 +3,6 @@ favicon: /favicons/notion.svg
|
|||||||
items:
|
items:
|
||||||
- name: New database items
|
- name: New database items
|
||||||
desc: Triggers when a new database item is created.
|
desc: Triggers when a new database item is created.
|
||||||
- name: Updated database items
|
|
||||||
desc: Triggers when there is an update to an item in a chosen database.
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
@@ -1,12 +0,0 @@
|
|||||||
---
|
|
||||||
favicon: /favicons/removebg.svg
|
|
||||||
items:
|
|
||||||
- name: Remove Image Background
|
|
||||||
desc: Remove backgrounds 100% automatically in 5 seconds with one click.
|
|
||||||
---
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import CustomListing from '../../components/CustomListing.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<CustomListing />
|
|
@@ -3,16 +3,6 @@ favicon: /favicons/zendesk.svg
|
|||||||
items:
|
items:
|
||||||
- name: Create ticket
|
- name: Create ticket
|
||||||
desc: Creates a new ticket.
|
desc: Creates a new ticket.
|
||||||
- name: Create user
|
|
||||||
desc: Creates a new user.
|
|
||||||
- name: Delete ticket
|
|
||||||
desc: Deletes an existing ticket.
|
|
||||||
- name: Delete user
|
|
||||||
desc: Deletes an existing user.
|
|
||||||
- name: Find ticket
|
|
||||||
desc: Finds an existing ticket.
|
|
||||||
- name: Update ticket
|
|
||||||
desc: Modify the status of an existing ticket or append comments.
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
@@ -1,14 +0,0 @@
|
|||||||
---
|
|
||||||
favicon: /favicons/zendesk.svg
|
|
||||||
items:
|
|
||||||
- name: New tickets
|
|
||||||
desc: Triggers when a new ticket is created in a specific view.
|
|
||||||
- name: New users
|
|
||||||
desc: Triggers upon the creation of a new user.
|
|
||||||
---
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import CustomListing from '../../components/CustomListing.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<CustomListing />
|
|
@@ -31,7 +31,7 @@ The following integrations are currently supported by Automatisch.
|
|||||||
- [PostgreSQL](/apps/postgresql/actions)
|
- [PostgreSQL](/apps/postgresql/actions)
|
||||||
- [Pushover](/apps/pushover/actions)
|
- [Pushover](/apps/pushover/actions)
|
||||||
- [Reddit](/apps/reddit/triggers)
|
- [Reddit](/apps/reddit/triggers)
|
||||||
- [Remove.bg](/apps/removebg/actions)
|
- [Remove.bg](/apps/removebg/connection)
|
||||||
- [RSS](/apps/rss/triggers)
|
- [RSS](/apps/rss/triggers)
|
||||||
- [Salesforce](/apps/salesforce/triggers)
|
- [Salesforce](/apps/salesforce/triggers)
|
||||||
- [Scheduler](/apps/scheduler/triggers)
|
- [Scheduler](/apps/scheduler/triggers)
|
||||||
|
1
packages/types/index.d.ts
vendored
1
packages/types/index.d.ts
vendored
@@ -23,7 +23,6 @@ export interface IConnection {
|
|||||||
formattedData?: IJSONObject;
|
formattedData?: IJSONObject;
|
||||||
userId: string;
|
userId: string;
|
||||||
verified: boolean;
|
verified: boolean;
|
||||||
shared?: boolean;
|
|
||||||
count?: number;
|
count?: number;
|
||||||
flowCount?: number;
|
flowCount?: number;
|
||||||
appData?: IApp;
|
appData?: IApp;
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -25,52 +25,12 @@
|
|||||||
-->
|
-->
|
||||||
<title>Automatisch</title>
|
<title>Automatisch</title>
|
||||||
|
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link
|
<link
|
||||||
rel="preload"
|
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap"
|
||||||
href="/fonts/Inter-Regular.ttf"
|
rel="stylesheet"
|
||||||
as="font"
|
|
||||||
crossorigin
|
|
||||||
type="font/ttf"
|
|
||||||
/>
|
/>
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
href="/fonts/Inter-Medium.ttf"
|
|
||||||
as="font"
|
|
||||||
crossorigin
|
|
||||||
type="font/ttf"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
href="/fonts/Inter-Bold.ttf"
|
|
||||||
as="font"
|
|
||||||
crossorigin
|
|
||||||
type="font/ttf"
|
|
||||||
/>
|
|
||||||
<style>
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Inter';
|
|
||||||
src: url('/fonts/Inter-Regular.ttf') format('truetype');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Inter';
|
|
||||||
src: url('/fonts/Inter-Medium.ttf') format('truetype');
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Inter';
|
|
||||||
src: url('/fonts/Inter-Bold.ttf') format('truetype');
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
@@ -1,168 +0,0 @@
|
|||||||
import type { IApp, IField, IJSONObject } from '@automatisch/types';
|
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
|
||||||
import Alert from '@mui/material/Alert';
|
|
||||||
import Dialog from '@mui/material/Dialog';
|
|
||||||
import DialogContent from '@mui/material/DialogContent';
|
|
||||||
import DialogContentText from '@mui/material/DialogContentText';
|
|
||||||
import DialogTitle from '@mui/material/DialogTitle';
|
|
||||||
import * as React from 'react';
|
|
||||||
import { FieldValues, SubmitHandler } from 'react-hook-form';
|
|
||||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
|
||||||
|
|
||||||
import AppAuthClientsDialog from 'components/AppAuthClientsDialog/index.ee';
|
|
||||||
import InputCreator from 'components/InputCreator';
|
|
||||||
import * as URLS from 'config/urls';
|
|
||||||
import useAuthenticateApp from 'hooks/useAuthenticateApp.ee';
|
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
|
||||||
import { generateExternalLink } from '../../helpers/translationValues';
|
|
||||||
import { Form } from './style';
|
|
||||||
|
|
||||||
type AdminApplicationConnectionCreateProps = {
|
|
||||||
onClose: (response: Record<string, unknown>) => void;
|
|
||||||
application: IApp;
|
|
||||||
connectionId?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function AdminApplicationConnectionCreate(
|
|
||||||
props: AdminApplicationConnectionCreateProps
|
|
||||||
): React.ReactElement {
|
|
||||||
const { application, connectionId, onClose } = props;
|
|
||||||
const { name, authDocUrl, key, auth } = application;
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
const formatMessage = useFormatMessage();
|
|
||||||
const [error, setError] = React.useState<IJSONObject | null>(null);
|
|
||||||
const [inProgress, setInProgress] = React.useState(false);
|
|
||||||
const hasConnection = Boolean(connectionId);
|
|
||||||
const useShared = searchParams.get('shared') === 'true';
|
|
||||||
const appAuthClientId = searchParams.get('appAuthClientId') || undefined;
|
|
||||||
const { authenticate } = useAuthenticateApp({
|
|
||||||
appKey: key,
|
|
||||||
connectionId,
|
|
||||||
appAuthClientId,
|
|
||||||
useShared: !!appAuthClientId,
|
|
||||||
});
|
|
||||||
|
|
||||||
React.useEffect(function relayProviderData() {
|
|
||||||
if (window.opener) {
|
|
||||||
window.opener.postMessage({
|
|
||||||
source: 'automatisch',
|
|
||||||
payload: { search: window.location.search, hash: window.location.hash },
|
|
||||||
});
|
|
||||||
window.close();
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
React.useEffect(
|
|
||||||
function initiateSharedAuthenticationForGivenAuthClient() {
|
|
||||||
if (!appAuthClientId) return;
|
|
||||||
if (!authenticate) return;
|
|
||||||
|
|
||||||
const asyncAuthenticate = async () => {
|
|
||||||
await authenticate();
|
|
||||||
|
|
||||||
navigate(URLS.ADMIN_APP_CONNECTIONS(key));
|
|
||||||
};
|
|
||||||
|
|
||||||
asyncAuthenticate();
|
|
||||||
},
|
|
||||||
[appAuthClientId, authenticate]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClientClick = (appAuthClientId: string) =>
|
|
||||||
navigate(
|
|
||||||
URLS.ADMIN_APP_CONNECTIONS_CREATE_WITH_AUTH_CLIENT_ID(
|
|
||||||
key,
|
|
||||||
appAuthClientId
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleAuthClientsDialogClose = () =>
|
|
||||||
navigate(URLS.ADMIN_APP_CONNECTIONS(key));
|
|
||||||
|
|
||||||
const submitHandler: SubmitHandler<FieldValues> = React.useCallback(
|
|
||||||
async (data) => {
|
|
||||||
if (!authenticate) return;
|
|
||||||
|
|
||||||
setInProgress(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await authenticate({
|
|
||||||
fields: data,
|
|
||||||
});
|
|
||||||
onClose(response as Record<string, unknown>);
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as IJSONObject;
|
|
||||||
console.log(error);
|
|
||||||
setError((error.graphQLErrors as IJSONObject[])?.[0]);
|
|
||||||
} finally {
|
|
||||||
setInProgress(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[authenticate]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (useShared)
|
|
||||||
return (
|
|
||||||
<AppAuthClientsDialog
|
|
||||||
appKey={key}
|
|
||||||
onClose={handleAuthClientsDialogClose}
|
|
||||||
onClientClick={handleClientClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (appAuthClientId) return <React.Fragment />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={true} onClose={onClose}>
|
|
||||||
<DialogTitle>
|
|
||||||
{hasConnection
|
|
||||||
? formatMessage('adminAppsConnections.reconnectConnection')
|
|
||||||
: formatMessage('adminAppsConnections.createConnection')}
|
|
||||||
</DialogTitle>
|
|
||||||
|
|
||||||
{authDocUrl && (
|
|
||||||
<Alert severity="info" sx={{ fontWeight: 300 }}>
|
|
||||||
{formatMessage('adminAppsConnections.callToDocs', {
|
|
||||||
appName: name,
|
|
||||||
docsLink: generateExternalLink(authDocUrl),
|
|
||||||
})}
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{error && (
|
|
||||||
<Alert
|
|
||||||
severity="error"
|
|
||||||
sx={{ mt: 1, fontWeight: 500, wordBreak: 'break-all' }}
|
|
||||||
>
|
|
||||||
{error.message}
|
|
||||||
{error.details && (
|
|
||||||
<pre style={{ whiteSpace: 'pre-wrap' }}>
|
|
||||||
{JSON.stringify(error.details, null, 2)}
|
|
||||||
</pre>
|
|
||||||
)}
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<DialogContent>
|
|
||||||
<DialogContentText tabIndex={-1} component="div">
|
|
||||||
<Form onSubmit={submitHandler}>
|
|
||||||
{auth?.fields?.map((field: IField) => (
|
|
||||||
<InputCreator key={field.key} schema={field} />
|
|
||||||
))}
|
|
||||||
|
|
||||||
<LoadingButton
|
|
||||||
type="submit"
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
sx={{ boxShadow: 2 }}
|
|
||||||
loading={inProgress}
|
|
||||||
>
|
|
||||||
{formatMessage('adminAppsConnections.submit')}
|
|
||||||
</LoadingButton>
|
|
||||||
</Form>
|
|
||||||
</DialogContentText>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -1,9 +0,0 @@
|
|||||||
import { styled } from '@mui/material/styles';
|
|
||||||
import BaseForm from 'components/Form';
|
|
||||||
|
|
||||||
export const Form = styled(BaseForm)(({ theme }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: theme.spacing(2),
|
|
||||||
paddingTop: theme.spacing(1),
|
|
||||||
}));
|
|
@@ -1,70 +0,0 @@
|
|||||||
import { useFieldArray, useFormContext } from 'react-hook-form';
|
|
||||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
|
||||||
import Divider from '@mui/material/Divider';
|
|
||||||
import Checkbox from '@mui/material/Checkbox';
|
|
||||||
|
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
|
||||||
import ControlledCheckbox from 'components/ControlledCheckbox';
|
|
||||||
import { Stack } from '@mui/material';
|
|
||||||
|
|
||||||
type Roles = { id: string; name: string; checked: boolean }[];
|
|
||||||
|
|
||||||
function RolesFieldArray() {
|
|
||||||
const formatMessage = useFormatMessage();
|
|
||||||
const { control, watch, setValue } = useFormContext();
|
|
||||||
const fieldArrayData = useFieldArray({
|
|
||||||
control,
|
|
||||||
name: 'roles',
|
|
||||||
});
|
|
||||||
|
|
||||||
const fields = fieldArrayData.fields as Roles;
|
|
||||||
const watchedFields = watch('roles') as Roles;
|
|
||||||
const allFieldsSelected = watchedFields.every((field) => field.checked);
|
|
||||||
const allFieldsDeselected = watchedFields.every((field) => !field.checked);
|
|
||||||
|
|
||||||
const handleSelectAllClick = () => {
|
|
||||||
setValue(
|
|
||||||
'roles',
|
|
||||||
watchedFields.map((field) => ({ ...field, checked: !allFieldsSelected })),
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Stack direction="column" spacing={1}>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
color="primary"
|
|
||||||
indeterminate={!(allFieldsSelected || allFieldsDeselected)}
|
|
||||||
checked={allFieldsSelected}
|
|
||||||
onChange={handleSelectAllClick}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={
|
|
||||||
allFieldsSelected
|
|
||||||
? formatMessage('adminAppsConnections.deselectAll')
|
|
||||||
: formatMessage('adminAppsConnections.selectAll')
|
|
||||||
}
|
|
||||||
sx={{ margin: 0 }}
|
|
||||||
/>
|
|
||||||
<Divider />
|
|
||||||
{fields.map((role, index) => {
|
|
||||||
return (
|
|
||||||
<FormControlLabel
|
|
||||||
key={role.id}
|
|
||||||
control={
|
|
||||||
<ControlledCheckbox
|
|
||||||
name={`roles.${index}.checked`}
|
|
||||||
defaultValue={role.checked}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label={role.name}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RolesFieldArray;
|
|
@@ -1,140 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
import Dialog from '@mui/material/Dialog';
|
|
||||||
import DialogTitle from '@mui/material/DialogTitle';
|
|
||||||
import DialogContent from '@mui/material/DialogContent';
|
|
||||||
import DialogContentText from '@mui/material/DialogContentText';
|
|
||||||
import LoadingButton from '@mui/lab/LoadingButton';
|
|
||||||
import Stack from '@mui/material/Stack';
|
|
||||||
import Alert from '@mui/material/Alert';
|
|
||||||
import { CircularProgress } from '@mui/material';
|
|
||||||
import { useMutation } from '@apollo/client';
|
|
||||||
import { IApp, IRole } from '@automatisch/types';
|
|
||||||
import { FieldValues, SubmitHandler } from 'react-hook-form';
|
|
||||||
|
|
||||||
import { SHARE_CONNECTION } from 'graphql/mutations/share-connection';
|
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
|
||||||
import useSharedConnectionRoleIds from 'hooks/useSharedConnectionRoleIds';
|
|
||||||
import useRoles from 'hooks/useRoles.ee';
|
|
||||||
|
|
||||||
import RolesFieldArray from './RolesFieldArray';
|
|
||||||
import { Form } from './style';
|
|
||||||
|
|
||||||
type AdminApplicationConnectionShareProps = {
|
|
||||||
onClose: (response: Record<string, unknown>) => void;
|
|
||||||
application: IApp;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Params = {
|
|
||||||
connectionId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function generateRolesData(roles: IRole[], roleIds: string[]) {
|
|
||||||
return roles.map(({ id, name }) => ({
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
checked: roleIds.includes(id),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AdminApplicationConnectionShare(
|
|
||||||
props: AdminApplicationConnectionShareProps
|
|
||||||
): React.ReactElement {
|
|
||||||
const { onClose } = props;
|
|
||||||
const { connectionId } = useParams() as Params;
|
|
||||||
const formatMessage = useFormatMessage();
|
|
||||||
const [
|
|
||||||
shareConnection,
|
|
||||||
{ loading: loadingShareConnection, error: shareConnectionError },
|
|
||||||
] = useMutation(SHARE_CONNECTION, {
|
|
||||||
context: { autoSnackbar: false },
|
|
||||||
});
|
|
||||||
const {
|
|
||||||
roleIds,
|
|
||||||
loading: roleIdsLoading,
|
|
||||||
error: roleIdsError,
|
|
||||||
} = useSharedConnectionRoleIds(connectionId, {
|
|
||||||
context: { autoSnackbar: false },
|
|
||||||
});
|
|
||||||
const { roles, loading: rolesLoading, error: rolesError } = useRoles();
|
|
||||||
|
|
||||||
const error = shareConnectionError || roleIdsError || rolesError;
|
|
||||||
const showDialogContent =
|
|
||||||
!roleIdsLoading && !rolesLoading && !roleIdsError && !rolesError;
|
|
||||||
|
|
||||||
const submitHandler: SubmitHandler<FieldValues> = React.useCallback(
|
|
||||||
async (data) => {
|
|
||||||
const roles = data.roles as {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
checked: boolean;
|
|
||||||
}[];
|
|
||||||
|
|
||||||
const response = await shareConnection({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
id: connectionId,
|
|
||||||
roleIds: roles
|
|
||||||
.filter((role) => role.checked)
|
|
||||||
.map((role) => role.id),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
onClose(response as Record<string, unknown>);
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const defaultValues = React.useMemo(
|
|
||||||
() => ({
|
|
||||||
roles: generateRolesData(roles, roleIds),
|
|
||||||
}),
|
|
||||||
[roles, roleIds]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog open={true} onClose={onClose}>
|
|
||||||
<DialogTitle>
|
|
||||||
{formatMessage('adminAppsConnections.shareConnection')}
|
|
||||||
</DialogTitle>
|
|
||||||
{error && (
|
|
||||||
<Alert
|
|
||||||
severity="error"
|
|
||||||
sx={{ mt: 1, fontWeight: 500, wordBreak: 'break-all' }}
|
|
||||||
>
|
|
||||||
{error.message}
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
{(roleIdsLoading || rolesLoading) && (
|
|
||||||
<CircularProgress sx={{ display: 'block', margin: '20px auto' }} />
|
|
||||||
)}
|
|
||||||
{showDialogContent && (
|
|
||||||
<DialogContent sx={{ pt: '0px !important' }}>
|
|
||||||
<DialogContentText tabIndex={-1} component="div">
|
|
||||||
<Form
|
|
||||||
defaultValues={defaultValues}
|
|
||||||
onSubmit={submitHandler}
|
|
||||||
render={({ formState: { isDirty } }) => {
|
|
||||||
return (
|
|
||||||
<Stack direction="column">
|
|
||||||
<RolesFieldArray />
|
|
||||||
<LoadingButton
|
|
||||||
type="submit"
|
|
||||||
variant="contained"
|
|
||||||
color="primary"
|
|
||||||
sx={{ boxShadow: 2, mt: 5 }}
|
|
||||||
disabled={!isDirty}
|
|
||||||
loading={loadingShareConnection}
|
|
||||||
>
|
|
||||||
{formatMessage('adminAppsConnections.submit')}
|
|
||||||
</LoadingButton>
|
|
||||||
</Stack>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
></Form>
|
|
||||||
</DialogContentText>
|
|
||||||
</DialogContent>
|
|
||||||
)}
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -1,9 +0,0 @@
|
|||||||
import { styled } from '@mui/material/styles';
|
|
||||||
import BaseForm from 'components/Form';
|
|
||||||
|
|
||||||
export const Form = styled(BaseForm)(({ theme }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: theme.spacing(2),
|
|
||||||
paddingTop: theme.spacing(1),
|
|
||||||
}));
|
|
@@ -1,85 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import Menu from '@mui/material/Menu';
|
|
||||||
import type { PopoverProps } from '@mui/material/Popover';
|
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
|
||||||
import type { IConnection } from '@automatisch/types';
|
|
||||||
|
|
||||||
import * as URLS from 'config/urls';
|
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
|
||||||
|
|
||||||
type Action = {
|
|
||||||
type: 'test' | 'reconnect' | 'delete' | 'shareConnection';
|
|
||||||
};
|
|
||||||
|
|
||||||
type ContextMenuProps = {
|
|
||||||
appKey: string;
|
|
||||||
connection: IConnection;
|
|
||||||
onClose: () => void;
|
|
||||||
onMenuItemClick: (event: React.MouseEvent, action: Action) => void;
|
|
||||||
anchorEl: PopoverProps['anchorEl'];
|
|
||||||
disableReconnection: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ContextMenu(
|
|
||||||
props: ContextMenuProps
|
|
||||||
): React.ReactElement {
|
|
||||||
const {
|
|
||||||
appKey,
|
|
||||||
connection,
|
|
||||||
onClose,
|
|
||||||
onMenuItemClick,
|
|
||||||
anchorEl,
|
|
||||||
disableReconnection,
|
|
||||||
} = props;
|
|
||||||
const formatMessage = useFormatMessage();
|
|
||||||
|
|
||||||
const createActionHandler = React.useCallback(
|
|
||||||
(action: Action) => {
|
|
||||||
return function clickHandler(event: React.MouseEvent) {
|
|
||||||
onMenuItemClick(event, action);
|
|
||||||
|
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
},
|
|
||||||
[onMenuItemClick, onClose]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu
|
|
||||||
open={true}
|
|
||||||
onClose={onClose}
|
|
||||||
hideBackdrop={false}
|
|
||||||
anchorEl={anchorEl}
|
|
||||||
>
|
|
||||||
<MenuItem onClick={createActionHandler({ type: 'test' })}>
|
|
||||||
{formatMessage('adminAppsConnections.testConnection')}
|
|
||||||
</MenuItem>
|
|
||||||
|
|
||||||
<MenuItem
|
|
||||||
component={Link}
|
|
||||||
disabled={disableReconnection}
|
|
||||||
to={URLS.ADMIN_APP_RECONNECT_CONNECTION(
|
|
||||||
appKey,
|
|
||||||
connection.id,
|
|
||||||
connection.appAuthClientId
|
|
||||||
)}
|
|
||||||
onClick={createActionHandler({ type: 'reconnect' })}
|
|
||||||
>
|
|
||||||
{formatMessage('adminAppsConnections.reconnect')}
|
|
||||||
</MenuItem>
|
|
||||||
|
|
||||||
<MenuItem
|
|
||||||
component={Link}
|
|
||||||
to={URLS.ADMIN_APP_SHARE_CONNECTION(appKey, connection.id)}
|
|
||||||
onClick={createActionHandler({ type: 'shareConnection' })}
|
|
||||||
>
|
|
||||||
{formatMessage('adminAppsConnections.shareConnection')}
|
|
||||||
</MenuItem>
|
|
||||||
|
|
||||||
<MenuItem onClick={createActionHandler({ type: 'delete' })}>
|
|
||||||
{formatMessage('adminAppsConnections.delete')}
|
|
||||||
</MenuItem>
|
|
||||||
</Menu>
|
|
||||||
);
|
|
||||||
}
|
|
@@ -1,155 +0,0 @@
|
|||||||
import type { IConnection } from '@automatisch/types';
|
|
||||||
import { useLazyQuery, useMutation } from '@apollo/client';
|
|
||||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
|
||||||
import ErrorIcon from '@mui/icons-material/Error';
|
|
||||||
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
|
|
||||||
import Box from '@mui/material/Box';
|
|
||||||
import Card from '@mui/material/Card';
|
|
||||||
import CardActionArea from '@mui/material/CardActionArea';
|
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
|
||||||
import Stack from '@mui/material/Stack';
|
|
||||||
import { DateTime } from 'luxon';
|
|
||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import { DELETE_CONNECTION } from 'graphql/mutations/delete-connection';
|
|
||||||
import { TEST_CONNECTION } from 'graphql/queries/test-connection';
|
|
||||||
import useEnqueueSnackbar from 'hooks/useEnqueueSnackbar';
|
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
|
||||||
|
|
||||||
import ConnectionContextMenu from '../AppConnectionContextMenu';
|
|
||||||
import { CardContent, Typography } from './style';
|
|
||||||
|
|
||||||
type AppConnectionRowProps = {
|
|
||||||
connection: IConnection;
|
|
||||||
};
|
|
||||||
|
|
||||||
function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
|
|
||||||
const enqueueSnackbar = useEnqueueSnackbar();
|
|
||||||
const [verificationVisible, setVerificationVisible] = React.useState(false);
|
|
||||||
const [testConnection, { called: testCalled, loading: testLoading }] =
|
|
||||||
useLazyQuery(TEST_CONNECTION, {
|
|
||||||
fetchPolicy: 'network-only',
|
|
||||||
onCompleted: () => {
|
|
||||||
setTimeout(() => setVerificationVisible(false), 3000);
|
|
||||||
},
|
|
||||||
onError: () => {
|
|
||||||
setTimeout(() => setVerificationVisible(false), 3000);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const [deleteConnection] = useMutation(DELETE_CONNECTION);
|
|
||||||
|
|
||||||
const formatMessage = useFormatMessage();
|
|
||||||
const { id, key, formattedData, verified, createdAt, reconnectable, shared } =
|
|
||||||
props.connection;
|
|
||||||
|
|
||||||
const contextButtonRef = React.useRef<SVGSVGElement | null>(null);
|
|
||||||
const [anchorEl, setAnchorEl] = React.useState<SVGSVGElement | null>(null);
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
setAnchorEl(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onContextMenuClick = () => setAnchorEl(contextButtonRef.current);
|
|
||||||
const onContextMenuAction = React.useCallback(
|
|
||||||
async (event, action: { [key: string]: string }) => {
|
|
||||||
if (action.type === 'delete') {
|
|
||||||
await deleteConnection({
|
|
||||||
variables: { input: { id } },
|
|
||||||
update: (cache) => {
|
|
||||||
const connectionCacheId = cache.identify({
|
|
||||||
__typename: 'Connection',
|
|
||||||
id,
|
|
||||||
});
|
|
||||||
|
|
||||||
cache.evict({
|
|
||||||
id: connectionCacheId,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
enqueueSnackbar(formatMessage('adminAppsConnections.deletedMessage'), {
|
|
||||||
variant: 'success',
|
|
||||||
});
|
|
||||||
} else if (action.type === 'test') {
|
|
||||||
setVerificationVisible(true);
|
|
||||||
testConnection({ variables: { id } });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[deleteConnection, id, testConnection, formatMessage, enqueueSnackbar]
|
|
||||||
);
|
|
||||||
|
|
||||||
const relativeCreatedAt = DateTime.fromMillis(
|
|
||||||
parseInt(createdAt, 10)
|
|
||||||
).toRelative();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Card sx={{ my: 2 }}>
|
|
||||||
<CardActionArea onClick={onContextMenuClick}>
|
|
||||||
<CardContent>
|
|
||||||
<Stack justifyContent="center" alignItems="flex-start" spacing={1}>
|
|
||||||
<Typography variant="h6" sx={{ textAlign: 'left' }}>
|
|
||||||
{formattedData?.screenName} {shared && 'shared'}
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Typography variant="caption">
|
|
||||||
{formatMessage('adminAppsConnections.addedAt', {
|
|
||||||
datetime: relativeCreatedAt,
|
|
||||||
})}
|
|
||||||
</Typography>
|
|
||||||
</Stack>
|
|
||||||
|
|
||||||
<Box>
|
|
||||||
<Stack direction="row" alignItems="center" spacing={1}>
|
|
||||||
{verificationVisible && testCalled && testLoading && (
|
|
||||||
<>
|
|
||||||
<CircularProgress size={16} />
|
|
||||||
<Typography variant="caption">
|
|
||||||
{formatMessage('adminAppsConnections.testing')}
|
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{verificationVisible && testCalled && !testLoading && verified && (
|
|
||||||
<>
|
|
||||||
<CheckCircleIcon fontSize="small" color="success" />
|
|
||||||
<Typography variant="caption">
|
|
||||||
{formatMessage('adminAppsConnections.testSuccessful')}
|
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{verificationVisible &&
|
|
||||||
testCalled &&
|
|
||||||
!testLoading &&
|
|
||||||
!verified && (
|
|
||||||
<>
|
|
||||||
<ErrorIcon fontSize="small" color="error" />
|
|
||||||
<Typography variant="caption">
|
|
||||||
{formatMessage('adminAppsConnections.testFailed')}
|
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box>
|
|
||||||
<MoreHorizIcon ref={contextButtonRef} />
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
|
||||||
</CardActionArea>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{anchorEl && (
|
|
||||||
<ConnectionContextMenu
|
|
||||||
appKey={key}
|
|
||||||
connection={props.connection}
|
|
||||||
disableReconnection={!reconnectable}
|
|
||||||
onClose={handleClose}
|
|
||||||
onMenuItemClick={onContextMenuAction}
|
|
||||||
anchorEl={anchorEl}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AppConnectionRow;
|
|
@@ -1,16 +0,0 @@
|
|||||||
import { styled } from '@mui/material/styles';
|
|
||||||
import MuiCardContent from '@mui/material/CardContent';
|
|
||||||
import MuiTypography from '@mui/material/Typography';
|
|
||||||
|
|
||||||
export const CardContent = styled(MuiCardContent)(({ theme }) => ({
|
|
||||||
display: 'grid',
|
|
||||||
gridTemplateRows: 'auto',
|
|
||||||
gridTemplateColumns: '1fr auto auto auto',
|
|
||||||
gridColumnGap: theme.spacing(2),
|
|
||||||
alignItems: 'center',
|
|
||||||
}));
|
|
||||||
|
|
||||||
export const Typography = styled(MuiTypography)(() => ({
|
|
||||||
textAlign: 'center',
|
|
||||||
display: 'inline-block',
|
|
||||||
}));
|
|
@@ -1,55 +0,0 @@
|
|||||||
import { Link } from 'react-router-dom';
|
|
||||||
import { useQuery } from '@apollo/client';
|
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
|
||||||
import Stack from '@mui/material/Stack';
|
|
||||||
import Button from '@mui/material/Button';
|
|
||||||
import type { IConnection } from '@automatisch/types';
|
|
||||||
|
|
||||||
import { GET_APP_CONNECTIONS } from 'graphql/queries/get-app-connections';
|
|
||||||
import * as URLS from 'config/urls';
|
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
|
||||||
import NoResultFound from 'components/NoResultFound';
|
|
||||||
|
|
||||||
import AppConnectionRow from './AppConnectionRow';
|
|
||||||
|
|
||||||
type AdminApplicationConnectionsProps = { appKey: string };
|
|
||||||
|
|
||||||
function AdminApplicationConnections(
|
|
||||||
props: AdminApplicationConnectionsProps
|
|
||||||
): React.ReactElement {
|
|
||||||
const { appKey } = props;
|
|
||||||
const formatMessage = useFormatMessage();
|
|
||||||
const { data, loading } = useQuery(GET_APP_CONNECTIONS, {
|
|
||||||
variables: { key: appKey },
|
|
||||||
});
|
|
||||||
const appConnections: IConnection[] = data?.getApp?.connections || [];
|
|
||||||
|
|
||||||
if (loading)
|
|
||||||
return <CircularProgress sx={{ display: 'block', margin: '20px auto' }} />;
|
|
||||||
|
|
||||||
if (appConnections.length === 0) {
|
|
||||||
return (
|
|
||||||
<NoResultFound
|
|
||||||
to={URLS.ADMIN_APP_CONNECTIONS_CREATE(appKey)}
|
|
||||||
text={formatMessage('adminAppsConnections.noConnections')}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
{appConnections.map((appConnection) => (
|
|
||||||
<AppConnectionRow key={appConnection.id} connection={appConnection} />
|
|
||||||
))}
|
|
||||||
<Stack justifyContent="flex-end" direction="row">
|
|
||||||
<Link to={URLS.ADMIN_APP_CONNECTIONS_CREATE(appKey)}>
|
|
||||||
<Button variant="contained" sx={{ mt: 2 }} component="div">
|
|
||||||
{formatMessage('adminAppsConnections.createConnection')}
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
</Stack>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AdminApplicationConnections;
|
|
@@ -53,7 +53,6 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
|
|||||||
createdAt,
|
createdAt,
|
||||||
flowCount,
|
flowCount,
|
||||||
reconnectable,
|
reconnectable,
|
||||||
shared,
|
|
||||||
} = props.connection;
|
} = props.connection;
|
||||||
|
|
||||||
const contextButtonRef = React.useRef<SVGSVGElement | null>(null);
|
const contextButtonRef = React.useRef<SVGSVGElement | null>(null);
|
||||||
@@ -106,7 +105,7 @@ function AppConnectionRow(props: AppConnectionRowProps): React.ReactElement {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<Stack justifyContent="center" alignItems="flex-start" spacing={1}>
|
<Stack justifyContent="center" alignItems="flex-start" spacing={1}>
|
||||||
<Typography variant="h6" sx={{ textAlign: 'left' }}>
|
<Typography variant="h6" sx={{ textAlign: 'left' }}>
|
||||||
{formattedData?.screenName} {shared && 'shared'}
|
{formattedData?.screenName}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography variant="caption">
|
<Typography variant="caption">
|
||||||
|
@@ -103,13 +103,6 @@ export const ADMIN_APP_AUTH_CLIENTS_PATTERN = `${ADMIN_SETTINGS}/apps/:appKey/au
|
|||||||
export const ADMIN_APP_CONNECTIONS_PATTERN = `${ADMIN_SETTINGS}/apps/:appKey/connections`;
|
export const ADMIN_APP_CONNECTIONS_PATTERN = `${ADMIN_SETTINGS}/apps/:appKey/connections`;
|
||||||
export const ADMIN_APP_CONNECTIONS = (appKey: string) =>
|
export const ADMIN_APP_CONNECTIONS = (appKey: string) =>
|
||||||
`${ADMIN_SETTINGS}/apps/${appKey}/connections`;
|
`${ADMIN_SETTINGS}/apps/${appKey}/connections`;
|
||||||
export const ADMIN_APP_CONNECTIONS_CREATE = (appKey: string, shared = false) =>
|
|
||||||
`${ADMIN_SETTINGS}/apps/${appKey}/connections/create?shared=${shared}`;
|
|
||||||
export const ADMIN_APP_CONNECTIONS_CREATE_WITH_AUTH_CLIENT_ID = (
|
|
||||||
appKey: string,
|
|
||||||
appAuthClientId: string
|
|
||||||
) =>
|
|
||||||
`${ADMIN_SETTINGS}/apps/${appKey}/connections/create?appAuthClientId=${appAuthClientId}`;
|
|
||||||
export const ADMIN_APP_SETTINGS = (appKey: string) =>
|
export const ADMIN_APP_SETTINGS = (appKey: string) =>
|
||||||
`${ADMIN_SETTINGS}/apps/${appKey}/settings`;
|
`${ADMIN_SETTINGS}/apps/${appKey}/settings`;
|
||||||
export const ADMIN_APP_AUTH_CLIENTS = (appKey: string) =>
|
export const ADMIN_APP_AUTH_CLIENTS = (appKey: string) =>
|
||||||
@@ -118,23 +111,6 @@ export const ADMIN_APP_AUTH_CLIENT = (appKey: string, id: string) =>
|
|||||||
`${ADMIN_SETTINGS}/apps/${appKey}/auth-clients/${id}`;
|
`${ADMIN_SETTINGS}/apps/${appKey}/auth-clients/${id}`;
|
||||||
export const ADMIN_APP_AUTH_CLIENTS_CREATE = (appKey: string) =>
|
export const ADMIN_APP_AUTH_CLIENTS_CREATE = (appKey: string) =>
|
||||||
`${ADMIN_SETTINGS}/apps/${appKey}/auth-clients/create`;
|
`${ADMIN_SETTINGS}/apps/${appKey}/auth-clients/create`;
|
||||||
export const ADMIN_APP_RECONNECT_CONNECTION = (
|
|
||||||
appKey: string,
|
|
||||||
connectionId: string,
|
|
||||||
appAuthClientId?: string
|
|
||||||
) => {
|
|
||||||
const path = `${ADMIN_SETTINGS}/apps/${appKey}/connections/${connectionId}/reconnect`;
|
|
||||||
|
|
||||||
if (appAuthClientId) {
|
|
||||||
return `${path}?appAuthClientId=${appAuthClientId}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
};
|
|
||||||
export const ADMIN_APP_SHARE_CONNECTION = (
|
|
||||||
appKey: string,
|
|
||||||
connectionId: string
|
|
||||||
) => `${ADMIN_SETTINGS}/apps/${appKey}/connections/${connectionId}/share`;
|
|
||||||
|
|
||||||
export const DASHBOARD = FLOWS;
|
export const DASHBOARD = FLOWS;
|
||||||
|
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
import { gql } from '@apollo/client';
|
|
||||||
|
|
||||||
export const SHARE_CONNECTION = gql`
|
|
||||||
mutation ShareConnection($input: ShareConnectionInput) {
|
|
||||||
shareConnection(input: $input) {
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
@@ -7,7 +7,6 @@ export const GET_APP_CONNECTIONS = gql`
|
|||||||
connections {
|
connections {
|
||||||
id
|
id
|
||||||
key
|
key
|
||||||
shared
|
|
||||||
reconnectable
|
reconnectable
|
||||||
appAuthClientId
|
appAuthClientId
|
||||||
verified
|
verified
|
||||||
|
@@ -1,7 +0,0 @@
|
|||||||
import { gql } from '@apollo/client';
|
|
||||||
|
|
||||||
export const GET_SHARED_CONNECTION_ROLE_IDS = gql`
|
|
||||||
query GetSharedConnectionRoleIds($id: String!) {
|
|
||||||
getSharedConnectionRoleIds(id: $id)
|
|
||||||
}
|
|
||||||
`;
|
|
@@ -5,16 +5,13 @@ import { GET_ROLES } from 'graphql/queries/get-roles.ee';
|
|||||||
|
|
||||||
type QueryResponse = {
|
type QueryResponse = {
|
||||||
getRoles: IRole[];
|
getRoles: IRole[];
|
||||||
};
|
}
|
||||||
|
|
||||||
export default function useRoles() {
|
export default function useRoles() {
|
||||||
const { data, loading, error } = useQuery<QueryResponse>(GET_ROLES, {
|
const { data, loading } = useQuery<QueryResponse>(GET_ROLES, { context: { autoSnackbar: false } });
|
||||||
context: { autoSnackbar: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
roles: data?.getRoles || [],
|
roles: data?.getRoles || [],
|
||||||
loading,
|
loading
|
||||||
error,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -1,32 +0,0 @@
|
|||||||
import * as React from 'react';
|
|
||||||
import { LazyQueryHookOptions, useLazyQuery } from '@apollo/client';
|
|
||||||
|
|
||||||
import { GET_SHARED_CONNECTION_ROLE_IDS } from 'graphql/queries/get-shared-connection-role-ids';
|
|
||||||
|
|
||||||
type QueryResponse = {
|
|
||||||
getSharedConnectionRoleIds: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function useSharedConnectionRoleIds(
|
|
||||||
connectionId: string,
|
|
||||||
options?: LazyQueryHookOptions
|
|
||||||
) {
|
|
||||||
const [getSharedConnectionRoleIds, { data, loading, error }] =
|
|
||||||
useLazyQuery<QueryResponse>(GET_SHARED_CONNECTION_ROLE_IDS, options);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (connectionId) {
|
|
||||||
getSharedConnectionRoleIds({
|
|
||||||
variables: {
|
|
||||||
id: connectionId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [connectionId]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
roleIds: data?.getSharedConnectionRoleIds || [],
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
};
|
|
||||||
}
|
|
@@ -12,6 +12,7 @@ import LiveChat from 'components/LiveChat/index.ee';
|
|||||||
import routes from 'routes';
|
import routes from 'routes';
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Router>
|
<Router>
|
||||||
<SnackbarProvider>
|
<SnackbarProvider>
|
||||||
|
@@ -265,21 +265,5 @@
|
|||||||
"authClient.buttonSubmit": "Submit",
|
"authClient.buttonSubmit": "Submit",
|
||||||
"authClient.inputName": "Name",
|
"authClient.inputName": "Name",
|
||||||
"authClient.inputActive": "Active",
|
"authClient.inputActive": "Active",
|
||||||
"updateAuthClient.title": "Update auth client",
|
"updateAuthClient.title": "Update auth client"
|
||||||
"adminAppsConnections.noConnections": "You don't have any connections yet.",
|
|
||||||
"adminAppsConnections.createConnection": "Create connection",
|
|
||||||
"adminAppsConnections.deletedMessage": "The connection has been deleted.",
|
|
||||||
"adminAppsConnections.addedAt": "added {datetime}",
|
|
||||||
"adminAppsConnections.testing": "Testing...",
|
|
||||||
"adminAppsConnections.testSuccessful": "Test successful",
|
|
||||||
"adminAppsConnections.testFailed": "Test failed",
|
|
||||||
"adminAppsConnections.testConnection": "Test connection",
|
|
||||||
"adminAppsConnections.delete": "Delete",
|
|
||||||
"adminAppsConnections.reconnect": "Reconnect",
|
|
||||||
"adminAppsConnections.shareConnection": "Share connection",
|
|
||||||
"adminAppsConnections.reconnectConnection": "Reconnect connection",
|
|
||||||
"adminAppsConnections.callToDocs": "Visit <docsLink>our documentation</docsLink> to see how to add connection for {appName}.",
|
|
||||||
"adminAppsConnections.submit": "Submit",
|
|
||||||
"adminAppsConnections.selectAll": "Select all roles",
|
|
||||||
"adminAppsConnections.deselectAll": "Deselect all roles"
|
|
||||||
}
|
}
|
||||||
|
@@ -27,26 +27,9 @@ import AdminApplicationSettings from 'components/AdminApplicationSettings';
|
|||||||
import AdminApplicationAuthClients from 'components/AdminApplicationAuthClients';
|
import AdminApplicationAuthClients from 'components/AdminApplicationAuthClients';
|
||||||
import AdminApplicationCreateAuthClient from 'components/AdminApplicationCreateAuthClient';
|
import AdminApplicationCreateAuthClient from 'components/AdminApplicationCreateAuthClient';
|
||||||
import AdminApplicationUpdateAuthClient from 'components/AdminApplicationUpdateAuthClient';
|
import AdminApplicationUpdateAuthClient from 'components/AdminApplicationUpdateAuthClient';
|
||||||
import AdminApplicationConnections from 'components/AdminApplicationConnections';
|
|
||||||
import AdminApplicationConnectionCreate from 'components/AdminApplicationConnectionCreate';
|
|
||||||
import AdminApplicationConnectionShare from 'components/AdminApplicationConnectionShare';
|
|
||||||
|
|
||||||
type AdminApplicationParams = {
|
type AdminApplicationParams = {
|
||||||
appKey: string;
|
appKey: string;
|
||||||
connectionId?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ReconnectConnection = (props: any): React.ReactElement => {
|
|
||||||
const { application, onClose } = props;
|
|
||||||
const { connectionId } = useParams() as AdminApplicationParams;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AdminApplicationConnectionCreate
|
|
||||||
onClose={onClose}
|
|
||||||
application={application}
|
|
||||||
connectionId={connectionId}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function AdminApplication(): React.ReactElement | null {
|
export default function AdminApplication(): React.ReactElement | null {
|
||||||
@@ -74,7 +57,6 @@ export default function AdminApplication(): React.ReactElement | null {
|
|||||||
const app = data?.getApp || {};
|
const app = data?.getApp || {};
|
||||||
|
|
||||||
const goToAuthClientsPage = () => navigate('auth-clients');
|
const goToAuthClientsPage = () => navigate('auth-clients');
|
||||||
const goToConnectionsPage = () => navigate('connections');
|
|
||||||
|
|
||||||
if (loading) return null;
|
if (loading) return null;
|
||||||
|
|
||||||
@@ -138,7 +120,7 @@ export default function AdminApplication(): React.ReactElement | null {
|
|||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={`/connections/*`}
|
path={`/connections/*`}
|
||||||
element={<AdminApplicationConnections appKey={appKey} />}
|
element={<div>App connections</div>}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/"
|
path="/"
|
||||||
@@ -171,33 +153,6 @@ export default function AdminApplication(): React.ReactElement | null {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route
|
|
||||||
path="/connections/create"
|
|
||||||
element={
|
|
||||||
<AdminApplicationConnectionCreate
|
|
||||||
onClose={goToConnectionsPage}
|
|
||||||
application={app}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/connections/:connectionId/reconnect"
|
|
||||||
element={
|
|
||||||
<ReconnectConnection
|
|
||||||
application={app}
|
|
||||||
onClose={goToConnectionsPage}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route
|
|
||||||
path="/connections/:connectionId/share"
|
|
||||||
element={
|
|
||||||
<AdminApplicationConnectionShare
|
|
||||||
onClose={goToConnectionsPage}
|
|
||||||
application={app}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Routes>
|
</Routes>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@@ -65,8 +65,8 @@ function RoleMappings({ provider, providerLoading }: RoleMappingsProps) {
|
|||||||
enqueueSnackbar(formatMessage('roleMappingsForm.successfullySaved'), {
|
enqueueSnackbar(formatMessage('roleMappingsForm.successfullySaved'), {
|
||||||
variant: 'success',
|
variant: 'success',
|
||||||
SnackbarProps: {
|
SnackbarProps: {
|
||||||
'data-test': 'snackbar-update-role-mappings-success',
|
'data-test': 'snackbar-update-role-mappings-success'
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@@ -5,12 +5,13 @@ import Stack from '@mui/material/Stack';
|
|||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import { Divider, Typography } from '@mui/material';
|
|
||||||
|
|
||||||
import useRoles from 'hooks/useRoles.ee';
|
import useRoles from 'hooks/useRoles.ee';
|
||||||
import useFormatMessage from 'hooks/useFormatMessage';
|
import useFormatMessage from 'hooks/useFormatMessage';
|
||||||
|
|
||||||
import ControlledAutocomplete from 'components/ControlledAutocomplete';
|
import ControlledAutocomplete from 'components/ControlledAutocomplete';
|
||||||
import TextField from 'components/TextField';
|
import TextField from 'components/TextField';
|
||||||
|
import { Divider, Typography } from '@mui/material';
|
||||||
|
|
||||||
function generateRoleOptions(roles: IRole[]) {
|
function generateRoleOptions(roles: IRole[]) {
|
||||||
return roles?.map(({ name: label, id: value }) => ({ label, value }));
|
return roles?.map(({ name: label, id: value }) => ({ label, value }));
|
||||||
|
35
yarn.lock
35
yarn.lock
@@ -7106,11 +7106,6 @@ commander@7.1.0:
|
|||||||
resolved "https://registry.npmjs.org/commander/-/commander-7.1.0.tgz"
|
resolved "https://registry.npmjs.org/commander/-/commander-7.1.0.tgz"
|
||||||
integrity sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg==
|
integrity sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg==
|
||||||
|
|
||||||
commander@^10.0.0:
|
|
||||||
version "10.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
|
|
||||||
integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
|
|
||||||
|
|
||||||
commander@^2.20.0:
|
commander@^2.20.0:
|
||||||
version "2.20.3"
|
version "2.20.3"
|
||||||
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
|
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
|
||||||
@@ -7126,7 +7121,7 @@ commander@^8.3.0:
|
|||||||
resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz"
|
resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz"
|
||||||
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
|
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
|
||||||
|
|
||||||
commander@^9.0.0:
|
commander@^9.0.0, commander@^9.1.0:
|
||||||
version "9.5.0"
|
version "9.5.0"
|
||||||
resolved "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz"
|
resolved "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz"
|
||||||
integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==
|
integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==
|
||||||
@@ -12266,13 +12261,13 @@ klona@^2.0.4, klona@^2.0.5:
|
|||||||
resolved "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz"
|
resolved "https://registry.npmjs.org/klona/-/klona-2.0.5.tgz"
|
||||||
integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==
|
integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==
|
||||||
|
|
||||||
knex@^2.5.1:
|
knex@^2.4.0:
|
||||||
version "2.5.1"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/knex/-/knex-2.5.1.tgz#a6c6b449866cf4229f070c17411f23871ba52ef9"
|
resolved "https://registry.npmjs.org/knex/-/knex-2.4.0.tgz"
|
||||||
integrity sha512-z78DgGKUr4SE/6cm7ku+jHvFT0X97aERh/f0MUKAKgFnwCYBEW4TFBqtHWFYiJFid7fMrtpZ/gxJthvz5mEByA==
|
integrity sha512-i0GWwqYp1Hs2yvc2rlDO6nzzkLhwdyOZKRdsMTB8ZxOs2IXQyL5rBjSbS1krowCh6V65T4X9CJaKtuIfkaPGSA==
|
||||||
dependencies:
|
dependencies:
|
||||||
colorette "2.0.19"
|
colorette "2.0.19"
|
||||||
commander "^10.0.0"
|
commander "^9.1.0"
|
||||||
debug "4.3.4"
|
debug "4.3.4"
|
||||||
escalade "^3.1.1"
|
escalade "^3.1.1"
|
||||||
esm "^3.2.25"
|
esm "^3.2.25"
|
||||||
@@ -12280,7 +12275,7 @@ knex@^2.5.1:
|
|||||||
getopts "2.3.0"
|
getopts "2.3.0"
|
||||||
interpret "^2.2.0"
|
interpret "^2.2.0"
|
||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
pg-connection-string "2.6.1"
|
pg-connection-string "2.5.0"
|
||||||
rechoir "^0.8.0"
|
rechoir "^0.8.0"
|
||||||
resolve-from "^5.0.0"
|
resolve-from "^5.0.0"
|
||||||
tarn "^3.0.2"
|
tarn "^3.0.2"
|
||||||
@@ -13864,13 +13859,12 @@ object.values@^1.1.0, object.values@^1.1.5:
|
|||||||
define-properties "^1.1.3"
|
define-properties "^1.1.3"
|
||||||
es-abstract "^1.19.1"
|
es-abstract "^1.19.1"
|
||||||
|
|
||||||
objection@^3.1.1:
|
objection@^3.0.0:
|
||||||
version "3.1.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/objection/-/objection-3.1.1.tgz#b744d4ff13c01863d6edec773f1315c964442510"
|
resolved "https://registry.npmjs.org/objection/-/objection-3.0.1.tgz"
|
||||||
integrity sha512-v8dqQrFwZm9gRN3ZF4abF+hL6Jm5EbcUjOxVDan0lheOev0sggGGHBP8jgesZ68I0XXBjDFjGXCjTPZsWDu49A==
|
integrity sha512-rqNnyQE+C55UHjdpTOJEKQHJGZ/BGtBBtgxdUpKG4DQXRUmqxfmgS/MhPWxB9Pw0mLSVLEltr6soD4c0Sddy0Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv "^8.6.2"
|
ajv "^8.6.2"
|
||||||
ajv-formats "^2.1.1"
|
|
||||||
db-errors "^0.2.3"
|
db-errors "^0.2.3"
|
||||||
|
|
||||||
obuf@^1.0.0, obuf@^1.1.2:
|
obuf@^1.0.0, obuf@^1.1.2:
|
||||||
@@ -14391,12 +14385,7 @@ performance-now@^2.1.0:
|
|||||||
resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz"
|
resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz"
|
||||||
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
|
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
|
||||||
|
|
||||||
pg-connection-string@2.6.1:
|
pg-connection-string@2.5.0, pg-connection-string@^2.5.0:
|
||||||
version "2.6.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.1.tgz#78c23c21a35dd116f48e12e23c0965e8d9e2cbfb"
|
|
||||||
integrity sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==
|
|
||||||
|
|
||||||
pg-connection-string@^2.5.0:
|
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz"
|
resolved "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz"
|
||||||
integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==
|
integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==
|
||||||
|
Reference in New Issue
Block a user