Compare commits

..

29 Commits

Author SHA1 Message Date
Faruk AYDIN
ea81697b5f fix: Eslint offenses for backend package 2024-01-04 12:32:07 +01:00
Faruk AYDIN
2ad23ff2e3 chore: Modify eslint configuration to use node env 2024-01-04 12:31:56 +01:00
Faruk AYDIN
aa08b6c339 chore: Add eslint file specific to the backend package 2024-01-04 12:25:57 +01:00
Faruk AYDIN
c00a8704fc feat: Convert bin files to use JS 2024-01-04 12:17:48 +01:00
Faruk AYDIN
b11781efcf feat: Convert all queues folder to js files 2023-12-28 13:55:28 +01:00
Faruk AYDIN
889f6f4935 feat: Convert root query and mutation resolvers to js 2023-12-28 13:53:56 +01:00
Faruk AYDIN
8682f22e68 feat: Convert all mutation files to js 2023-12-28 13:53:16 +01:00
Faruk AYDIN
cbbb76a6c5 feat: Convert all query files to JS 2023-12-28 13:39:31 +01:00
Faruk AYDIN
cd91f8c711 feat: Convert workers to use js files 2023-12-28 13:24:53 +01:00
Faruk AYDIN
84f501954b feat: Convert routes folder to the js files 2023-12-28 13:22:23 +01:00
Faruk AYDIN
40ae83ed6a feat: Convert service folder to js files 2023-12-28 13:19:46 +01:00
Faruk AYDIN
c6c02d7c18 feat: Convert error handler ts file to js file 2023-12-28 13:17:09 +01:00
Faruk AYDIN
8dab118279 feat: Convert ts files to js files for errors directory 2023-12-28 13:14:58 +01:00
Faruk AYDIN
7bf74dd867 feat: Convert ts files to js files for controllers 2023-12-28 13:11:00 +01:00
Faruk AYDIN
9f7e2b986b feat: Convert ts files to js files for config folder 2023-12-28 13:09:24 +01:00
Faruk AYDIN
e66b492df3 chore: Allow JS files for tsconfig 2023-12-28 13:03:38 +01:00
QAComet
d070e976b0 test(e2e-tests): run only on relevant changes in pull requests (#1495) 2023-12-15 18:51:58 +01:00
Ali BARIN
0caf6bfabb Merge pull request #1494 from automatisch/aut-551
feat: hide notifications page in mation instances
2023-12-15 18:00:08 +01:00
Ali BARIN
b842d7938f feat: hide notifications page in mation instances 2023-12-15 16:28:41 +00:00
Ali BARIN
cebbf84375 Merge pull request #1491 from automatisch/aut-548
feat: apply conditional mation styling
2023-12-15 11:40:12 +01:00
Ali BARIN
8608431490 feat: add conditional mation logo by default 2023-12-14 15:59:37 +00:00
Ali BARIN
78ba18b176 feat: apply conditional mation styling 2023-12-14 14:28:07 +00:00
Ali BARIN
f8c30c8526 Merge pull request #1475 from automatisch/aut-547
feat(queries/getAutomatischInfo): add mation
2023-12-14 11:15:35 +01:00
QAComet
693c9b85a5 test: run UI workflow only on changes outside of backend apps (#1462) 2023-12-14 10:45:15 +01:00
Ali BARIN
70bb7defd1 feat(queries/getAutomatischInfo): add mation 2023-12-12 17:37:27 +00:00
Ali BARIN
160377ca31 Merge pull request #1473 from automatisch/aut-545
docs(salesforce): update connection steps
2023-12-12 18:17:07 +01:00
Ali BARIN
2c0ce77a4e docs(salesforce): update connection steps 2023-12-12 17:08:15 +00:00
Ali BARIN
77fbb0c9da Merge pull request #1470 from automatisch/aut-538
fix(odoo): introduce secure connection option
2023-12-11 16:44:11 +01:00
Ali BARIN
5971425d23 fix(odoo): introduce secure connection option 2023-12-11 15:36:50 +00:00
183 changed files with 964 additions and 2675 deletions

View File

@@ -4,6 +4,11 @@ on:
branches:
- main
pull_request:
paths:
- 'packages/backend/**'
- 'packages/e2e-tests/**'
- 'packages/web/**'
- '!packages/backend/src/apps/**'
workflow_dispatch:
env:

View File

@@ -0,0 +1,28 @@
module.exports = {
root: true,
env: {
node: true,
},
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
overrides: [
{
files: ['**/*.test.ts', '**/test/**/*.ts'],
rules: {
'@typescript-eslint/ban-ts-comment': ['off'],
'@typescript-eslint/no-explicit-any': ['off'],
},
},
{
files: ['**/*.ts'],
rules: {
'@typescript-eslint/no-explicit-any': ['off'],
},
},
],
};

View File

@@ -6,10 +6,9 @@ import Role from '../../src/models/role';
import '../../src/config/orm';
async function fetchAdminRole() {
const role = await Role
.query()
const role = await Role.query()
.where({
key: 'admin'
key: 'admin',
})
.limit(1)
.first();
@@ -41,7 +40,7 @@ export async function createUser(
logger.info('No need to seed a user.');
}
} catch (err) {
if ((err as any).nativeError.code !== UNIQUE_VIOLATION_CODE) {
if (err.nativeError.code !== UNIQUE_VIOLATION_CODE) {
throw err;
}
@@ -68,7 +67,7 @@ export const createDatabase = async (database = appConfig.postgresDatabase) => {
await client.query(`CREATE DATABASE ${database}`);
logger.info(`Database: ${database} created!`);
} catch (err) {
if ((err as any).code !== DUPLICATE_DB_CODE) {
if (err.code !== DUPLICATE_DB_CODE) {
throw err;
}
@@ -85,7 +84,7 @@ export const createDatabaseUser = async (user = appConfig.postgresUsername) => {
return result;
} catch (err) {
if ((err as any).code !== DUPLICATE_OBJECT_CODE) {
if (err.code !== DUPLICATE_OBJECT_CODE) {
throw err;
}

View File

@@ -59,8 +59,8 @@
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.1",
"jsonwebtoken": "^9.0.0",
"knex": "^2.4.0",
"libphonenumber-js": "^1.10.48",
"knex": "^2.5.1",
"lodash.get": "^4.4.2",
"luxon": "2.5.2",
"memory-cache": "^0.2.0",
@@ -69,7 +69,7 @@
"node-html-markdown": "^1.3.0",
"nodemailer": "6.7.0",
"oauth-1.0a": "^2.2.6",
"objection": "^3.1.1",
"objection": "^3.0.0",
"passport": "^0.6.0",
"pg": "^8.7.1",
"php-serialize": "^4.0.2",

View File

@@ -1,4 +1,3 @@
import qs from 'qs';
import defineAction from '../../../../helpers/define-action';
export default defineAction({
@@ -19,7 +18,8 @@ export default defineAction({
key: 'message',
type: 'string' as const,
required: true,
description: 'Message body to be sent, set to triggered if empty or not passed.',
description:
'Message body to be sent, set to triggered if empty or not passed.',
variables: true,
},
{
@@ -67,22 +67,15 @@ export default defineAction({
key: 'delay',
type: 'string' as const,
required: false,
description: 'Timestamp or duration for delayed delivery. For example, 30min or 9am.',
description:
'Timestamp or duration for delayed delivery. For example, 30min or 9am.',
variables: true,
},
],
async run($) {
const {
topic,
message,
title,
email,
click,
attach,
filename,
delay
} = $.step.parameters;
const { topic, message, title, email, click, attach, filename, delay } =
$.step.parameters;
const payload = {
topic,
message,
@@ -91,7 +84,7 @@ export default defineAction({
click,
attach,
filename,
delay
delay,
};
const response = await $.http.post('/', payload);

View File

@@ -11,7 +11,7 @@ export default {
readOnly: false,
value: null,
placeholder: null,
description: 'Host name of your Odoo Server',
description: 'Host name of your Odoo Server (e.g. sub.domain.com without the protocol)',
clickToCopy: false,
},
{
@@ -25,6 +25,27 @@ export default {
description: 'Port that the host is running on, defaults to 443 (HTTPS)',
clickToCopy: false,
},
{
key: 'secure',
label: 'Secure',
type: 'dropdown' as const,
required: true,
readOnly: false,
value: 'true',
description: 'True if the host communicates via secure protocol.',
variables: false,
clickToCopy: false,
options: [
{
label: 'True',
value: 'true',
},
{
label: 'False',
value: 'false',
},
],
},
{
key: 'databaseName',
label: 'Database Name',
@@ -40,7 +61,7 @@ export default {
key: 'email',
label: 'Email Address',
type: 'string' as const,
requires: true,
required: true,
readOnly: false,
value: null,
placeholder: null,

View File

@@ -32,8 +32,10 @@ export const asyncMethodCall = async <T = number>($: IGlobalVariable, { method,
export const getClient = ($: IGlobalVariable, { path = 'common' }) => {
const host = $.auth.data.host as string;
const port = Number($.auth.data.port as string);
const secure = $.auth.data.secure === 'true';
const createClientFunction = secure ? xmlrpc.createSecureClient : xmlrpc.createClient;
return xmlrpc.createClient(
return createClientFunction(
{
host,
port,

View File

@@ -1,4 +1,3 @@
import qs from 'qs';
import defineAction from '../../../../helpers/define-action';
export default defineAction({
@@ -11,7 +10,8 @@ export default defineAction({
key: 'chatId',
type: 'string' as const,
required: true,
description: 'Unique identifier for the target chat or username of the target channel (in the format @channelusername).',
description:
'Unique identifier for the target chat or username of the target channel (in the format @channelusername).',
variables: true,
},
{
@@ -28,7 +28,8 @@ export default defineAction({
type: 'dropdown' as const,
required: false,
value: false,
description: 'Sends the message silently. Users will receive a notification with no sound.',
description:
'Sends the message silently. Users will receive a notification with no sound.',
variables: true,
options: [
{

View File

@@ -1,6 +1,7 @@
import { URL } from 'node:url';
import * as dotenv from 'dotenv';
import path from 'path';
import process from 'node:process';
if (process.env.APP_ENV === 'test') {
dotenv.config({ path: path.resolve(__dirname, '../../.env.test') });
@@ -8,56 +9,6 @@ if (process.env.APP_ENV === 'test') {
dotenv.config();
}
type AppConfig = {
host: string;
protocol: string;
port: string;
webAppUrl: string;
webhookUrl: string;
appEnv: string;
logLevel: string;
isDev: boolean;
isTest: boolean;
isProd: boolean;
postgresDatabase: string;
postgresSchema: string;
postgresPort: number;
postgresHost: string;
postgresUsername: string;
postgresPassword?: string;
version: string;
postgresEnableSsl: boolean;
baseUrl: string;
encryptionKey: string;
webhookSecretKey: string;
appSecretKey: string;
serveWebAppSeparately: boolean;
redisHost: string;
redisPort: number;
redisUsername: string;
redisPassword: string;
redisTls: boolean;
enableBullMQDashboard: boolean;
bullMQDashboardUsername: string;
bullMQDashboardPassword: string;
telemetryEnabled: boolean;
requestBodySizeLimit: string;
smtpHost: string;
smtpPort: number;
smtpSecure: boolean;
smtpUser: string;
smtpPassword: string;
fromEmail: string;
isCloud: boolean;
isSelfHosted: boolean;
paddleVendorId: number;
paddleVendorAuthCode: string;
paddlePublicKey: string;
licenseKey: string;
sentryDsn: string;
CI: boolean;
};
const host = process.env.HOST || 'localhost';
const protocol = process.env.PROTOCOL || 'http';
const port = process.env.PORT || '3000';
@@ -84,7 +35,7 @@ webhookUrl = webhookUrl.substring(0, webhookUrl.length - 1);
const appEnv = process.env.APP_ENV || 'development';
const appConfig: AppConfig = {
const appConfig = {
host,
protocol,
port,
@@ -127,6 +78,7 @@ const appConfig: AppConfig = {
fromEmail: process.env.FROM_EMAIL,
isCloud: process.env.AUTOMATISCH_CLOUD === 'true',
isSelfHosted: process.env.AUTOMATISCH_CLOUD !== 'true',
isMation: process.env.MATION === 'true',
paddleVendorId: Number(process.env.PADDLE_VENDOR_ID),
paddleVendorAuthCode: process.env.PADDLE_VENDOR_AUTH_CODE,
paddlePublicKey: process.env.PADDLE_PUBLIC_KEY,

View File

@@ -4,11 +4,10 @@ import process from 'process';
import pg from 'pg';
pg.types.setTypeParser(20, 'text', parseInt);
import knex from 'knex';
import type { Knex } from 'knex';
import knexConfig from '../../knexfile';
import logger from '../helpers/logger';
export const client: Knex = knex(knexConfig);
export const client = knex(knexConfig);
const CONNECTION_REFUSED = 'ECONNREFUSED';

View File

@@ -1,16 +1,6 @@
import appConfig from './app';
type TRedisConfig = {
host: string,
port: number,
username?: string,
password?: string,
tls?: Record<string, unknown>,
enableReadyCheck?: boolean,
enableOfflineQueue: boolean,
}
const redisConfig: TRedisConfig = {
const redisConfig = {
host: appConfig.redisHost,
port: appConfig.redisPort,
username: appConfig.redisUsername,

View File

@@ -1,11 +1,9 @@
import { Response } from 'express';
import { IJSONObject, IRequest } from '@automatisch/types';
import crypto from 'crypto';
import { serialize } from 'php-serialize';
import Billing from '../../helpers/billing/index.ee';
import appConfig from '../../config/app';
export default async (request: IRequest, response: Response) => {
export default async (request, response) => {
if (!verifyWebhook(request)) {
return response.sendStatus(401);
}
@@ -23,14 +21,14 @@ export default async (request: IRequest, response: Response) => {
return response.sendStatus(200);
};
const verifyWebhook = (request: IRequest) => {
const verifyWebhook = (request) => {
const signature = request.body.p_signature;
const keys = Object.keys(request.body)
.filter((key) => key !== 'p_signature')
.sort();
const sorted: IJSONObject = {};
const sorted = {};
keys.forEach((key) => {
sorted[key] = request.body[key];
});

View File

@@ -1,12 +1,10 @@
import path from 'node:path';
import { Response } from 'express';
import { IRequest } from '@automatisch/types';
import Connection from '../../models/connection';
import logger from '../../helpers/logger';
import handler from '../../helpers/webhook-handler';
export default async (request: IRequest, response: Response) => {
export default async (request, response) => {
const computedRequestPayload = {
headers: request.headers,
body: request.body,
@@ -22,7 +20,7 @@ export default async (request: IRequest, response: Response) => {
.findById(connectionId)
.throwIfNotFound();
if (!await connection.verifyWebhook(request)) {
if (!(await connection.verifyWebhook(request))) {
return response.sendStatus(401);
}

View File

@@ -1,11 +1,8 @@
import { Response } from 'express';
import { IRequest } from '@automatisch/types';
import Flow from '../../models/flow';
import logger from '../../helpers/logger';
import handler from '../../helpers/webhook-handler';
export default async (request: IRequest, response: Response) => {
export default async (request, response) => {
const computedRequestPayload = {
headers: request.headers,
body: request.body,

View File

@@ -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');
}

View File

@@ -1,22 +1,22 @@
import { IJSONObject } from '@automatisch/types';
export default class BaseError extends Error {
details = {};
statusCode?: number;
constructor(error?: string | IJSONObject) {
let computedError: Record<string, unknown>;
constructor(error) {
let computedError;
try {
computedError = JSON.parse(error as string);
computedError = JSON.parse(error);
} catch {
computedError = (typeof error === 'string' || Array.isArray(error)) ? { error } : error;
computedError =
typeof error === 'string' || Array.isArray(error) ? { error } : error;
}
let computedMessage: string;
let computedMessage;
try {
// challenge to input to see if it is stringified JSON
JSON.parse(error as string);
computedMessage = error as string;
JSON.parse(error);
computedMessage = error;
} catch {
if (typeof error === 'string') {
computedMessage = error;

View File

@@ -0,0 +1,10 @@
import BaseError from './base';
export default class GenerateAuthUrlError extends BaseError {
constructor(error) {
const computedError = error.response?.data || error.message;
super(computedError);
this.message = `Error occured while creating authorization URL!`;
}
}

View File

@@ -1,14 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import BaseError from './base';
export default class GenerateAuthUrlError extends BaseError {
constructor(error: IJSONObject) {
const computedError =
((error.response as IJSONObject)?.data as IJSONObject) ||
(error.message as string);
super(computedError);
this.message = `Error occured while creating authorization URL!`;
}
}

View File

@@ -0,0 +1,10 @@
import BaseError from './base';
export default class HttpError extends BaseError {
constructor(error) {
const computedError = error.response?.data || error.message;
super(computedError);
this.response = error.response;
}
}

View File

@@ -1,17 +0,0 @@
import type { AxiosResponse, AxiosError } from 'axios';
import { IJSONObject } from '@automatisch/types';
import BaseError from './base';
export default class HttpError extends BaseError {
response: AxiosResponse;
constructor(error: AxiosError) {
const computedError =
error.response?.data as IJSONObject ||
error.message as string;
super(computedError);
this.response = error.response;
}
}

View File

@@ -19,7 +19,6 @@ import login from './mutations/login';
import registerUser from './mutations/register-user.ee';
import resetConnection from './mutations/reset-connection';
import resetPassword from './mutations/reset-password.ee';
import shareConnection from './mutations/share-connection.ee';
import updateAppAuthClient from './mutations/update-app-auth-client.ee';
import updateAppConfig from './mutations/update-app-config.ee';
import updateConfig from './mutations/update-config.ee';
@@ -56,7 +55,6 @@ const mutationResolvers = {
registerUser,
resetConnection,
resetPassword,
shareConnection,
updateAppAuthClient,
updateAppConfig,
updateConfig,

View File

@@ -0,0 +1,17 @@
import AppConfig from '../../models/app-config';
const createAppAuthClient = async (_parent, params, context) => {
context.currentUser.can('update', 'App');
const appConfig = await AppConfig.query()
.findById(params.input.appConfigId)
.throwIfNotFound();
const appAuthClient = await appConfig
.$relatedQuery('appAuthClients')
.insert(params.input);
return appAuthClient;
};
export default createAppAuthClient;

View File

@@ -1,35 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import AppConfig from '../../models/app-config';
import Context from '../../types/express/context';
type Params = {
input: {
appConfigId: string;
name: string;
formattedAuthDefaults?: IJSONObject;
active?: boolean;
};
};
const createAppAuthClient = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('update', 'App');
const appConfig = await AppConfig
.query()
.findById(params.input.appConfigId)
.throwIfNotFound();
const appAuthClient = await appConfig
.$relatedQuery('appAuthClients')
.insert(
params.input
);
return appAuthClient;
};
export default createAppAuthClient;

View File

@@ -0,0 +1,18 @@
import App from '../../models/app';
import AppConfig from '../../models/app-config';
const createAppConfig = async (_parent, params, context) => {
context.currentUser.can('update', 'App');
const key = params.input.key;
const app = await App.findOneByKey(key);
if (!app) throw new Error('The app cannot be found!');
const appConfig = await AppConfig.query().insert(params.input);
return appConfig;
};
export default createAppConfig;

View File

@@ -1,36 +0,0 @@
import App from '../../models/app';
import AppConfig from '../../models/app-config';
import Context from '../../types/express/context';
type Params = {
input: {
key: string;
allowCustomConnection?: boolean;
shared?: boolean;
disabled?: boolean;
};
};
const createAppConfig = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('update', 'App');
const key = params.input.key;
const app = await App.findOneByKey(key);
if (!app) throw new Error('The app cannot be found!');
const appConfig = await AppConfig
.query()
.insert(
params.input
);
return appConfig;
};
export default createAppConfig;

View File

@@ -1,21 +1,7 @@
import { IJSONObject } from '@automatisch/types';
import App from '../../models/app';
import AppConfig from '../../models/app-config';
import Context from '../../types/express/context';
type Params = {
input: {
key: string;
appAuthClientId: string;
formattedData: IJSONObject;
};
};
const createConnection = async (
_parent: unknown,
params: Params,
context: Context
) => {
const createConnection = async (_parent, params, context) => {
context.currentUser.can('create', 'Connection');
const { key, appAuthClientId } = params.input;
@@ -26,16 +12,20 @@ const createConnection = async (
let formattedData = params.input.formattedData;
if (appConfig) {
if (appConfig.disabled) throw new Error('This application has been disabled for new connections!');
if (appConfig.disabled)
throw new Error(
'This application has been disabled for new connections!'
);
if (!appConfig.allowCustomConnection && formattedData) throw new Error(`Custom connections cannot be created for ${app.name}!`);
if (!appConfig.allowCustomConnection && formattedData)
throw new Error(`Custom connections cannot be created for ${app.name}!`);
if (appConfig.shared && !formattedData) {
const authClient = await appConfig
.$relatedQuery('appAuthClients')
.findById(appAuthClientId)
.where({
active: true
active: true,
})
.throwIfNotFound();
@@ -43,8 +33,7 @@ const createConnection = async (
}
}
const createdConnection = await context
.currentUser
const createdConnection = await context.currentUser
.$relatedQuery('connections')
.insert({
key,

View File

@@ -1,19 +1,7 @@
import App from '../../models/app';
import Step from '../../models/step';
import Context from '../../types/express/context';
type Params = {
input: {
triggerAppKey: string;
connectionId: string;
};
};
const createFlow = async (
_parent: unknown,
params: Params,
context: Context
) => {
const createFlow = async (_parent, params, context) => {
context.currentUser.can('create', 'Flow');
const connectionId = params?.input?.connectionId;
@@ -28,11 +16,11 @@ const createFlow = async (
});
if (connectionId) {
const connection = await context.currentUser
.relatedConnectionsQuery()
const hasConnection = await context.currentUser
.$relatedQuery('connections')
.findById(connectionId);
if (!connection) {
if (!hasConnection) {
throw new Error('The connection does not exist!');
}
}

View File

@@ -0,0 +1,29 @@
import kebabCase from 'lodash/kebabCase';
import Role from '../../models/role';
const createRole = async (_parent, params, context) => {
context.currentUser.can('create', 'Role');
const { name, description, permissions } = params.input;
const key = kebabCase(name);
const existingRole = await Role.query().findOne({ key });
if (existingRole) {
throw new Error('Role already exists!');
}
return await Role.query()
.insertGraph(
{
key,
name,
description,
permissions,
},
{ relate: ['permissions'] }
)
.returning('*');
};
export default createRole;

View File

@@ -1,34 +0,0 @@
import kebabCase from 'lodash/kebabCase';
import Permission from '../../models/permission';
import Role from '../../models/role';
import Context from '../../types/express/context';
type Params = {
input: {
name: string;
description: string;
permissions: Permission[];
};
};
const createRole = async (_parent: unknown, params: Params, context: Context) => {
context.currentUser.can('create', 'Role');
const { name, description, permissions } = params.input;
const key = kebabCase(name);
const existingRole = await Role.query().findOne({ key });
if (existingRole) {
throw new Error('Role already exists!');
}
return await Role.query().insertGraph({
key,
name,
description,
permissions,
}, { relate: ['permissions'] }).returning('*');
};
export default createRole;

View File

@@ -1,28 +1,7 @@
import App from '../../models/app';
import Flow from '../../models/flow';
import Context from '../../types/express/context';
type Params = {
input: {
key: string;
appKey: string;
flow: {
id: string;
};
connection: {
id: string;
};
previousStep: {
id: string;
};
};
};
const createStep = async (
_parent: unknown,
params: Params,
context: Context
) => {
const createStep = async (_parent, params, context) => {
const conditions = context.currentUser.can('update', 'Flow');
const userFlows = context.currentUser.$relatedQuery('flows');
const allFlows = Flow.query();

View File

@@ -1,23 +1,7 @@
import User from '../../models/user';
import Role from '../../models/role';
import Context from '../../types/express/context';
type Params = {
input: {
fullName: string;
email: string;
password: string;
role: {
id: string;
};
};
};
const createUser = async (
_parent: unknown,
params: Params,
context: Context
) => {
const createUser = async (_parent, params, context) => {
context.currentUser.can('create', 'User');
const { fullName, email, password } = params.input;
@@ -30,7 +14,7 @@ const createUser = async (
throw new Error('User already exists!');
}
const userPayload: Partial<User> = {
const userPayload = {
fullName,
email,
password,

View File

@@ -1,21 +1,9 @@
import Context from '../../types/express/context';
import AppAuthClient from '../../models/app-auth-client';
type Params = {
input: {
id: string;
};
};
const deleteAppAuthClient = async (
_parent: unknown,
params: Params,
context: Context
) => {
const deleteAppAuthClient = async (_parent, params, context) => {
context.currentUser.can('delete', 'App');
await AppAuthClient
.query()
await AppAuthClient.query()
.delete()
.findOne({
id: params.input.id,

View File

@@ -0,0 +1,15 @@
const deleteConnection = async (_parent, params, context) => {
context.currentUser.can('delete', 'Connection');
await context.currentUser
.$relatedQuery('connections')
.delete()
.findOne({
id: params.input.id,
})
.throwIfNotFound();
return;
};
export default deleteConnection;

View File

@@ -1,31 +0,0 @@
import Context from '../../types/express/context';
import Connection from '../../models/connection';
type Params = {
input: {
id: string;
};
};
const deleteConnection = async (
_parent: unknown,
params: Params,
context: Context
) => {
const conditions = context.currentUser.can('delete', 'Connection');
const userConnections = context.currentUser.$relatedQuery('connections');
const allConnections = Connection.query();
const baseQuery = conditions.isCreator ? userConnections : allConnections;
await baseQuery
.clone()
.delete()
.findOne({
id: params.input.id,
})
.throwIfNotFound();
return;
};
export default deleteConnection;

View File

@@ -0,0 +1,58 @@
import { Duration } from 'luxon';
import deleteUserQueue from '../../queues/delete-user.ee';
import flowQueue from '../../queues/flow';
import Flow from '../../models/flow';
import ExecutionStep from '../../models/execution-step';
import appConfig from '../../config/app';
const deleteCurrentUser = async (_parent, params, context) => {
const id = context.currentUser.id;
const flows = await context.currentUser.$relatedQuery('flows').where({
active: true,
});
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);
}
}
const executionIds = (
await context.currentUser
.$relatedQuery('executions')
.select('executions.id')
).map((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;

View File

@@ -1,101 +0,0 @@
import { Duration } from 'luxon';
import Context from '../../types/express/context';
import deleteUserQueue from '../../queues/delete-user.ee';
import flowQueue from '../../queues/flow';
import Flow from '../../models/flow';
import Execution from '../../models/execution';
import User from '../../models/user';
import ExecutionStep from '../../models/execution-step';
import appConfig from '../../config/app';
const deleteCurrentUser = async (
_parent: unknown,
params: never,
context: Context
) => {
const id = context.currentUser.id;
try {
await User.transaction(async (trx) => {
const flows = await context.currentUser
.$relatedQuery('flows', trx)
.where({
active: true,
});
const { count } = await context.currentUser
.$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) {
throw new Error('The shared connections must be removed first!');
}
const executionIds = (
await context.currentUser
.$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!');
}
};
export default deleteCurrentUser;

View File

@@ -1,21 +1,9 @@
import Context from '../../types/express/context';
import Flow from '../../models/flow';
import Execution from '../../models/execution';
import ExecutionStep from '../../models/execution-step';
import globalVariable from '../../helpers/global-variable';
import logger from '../../helpers/logger';
type Params = {
input: {
id: string;
};
};
const deleteFlow = async (
_parent: unknown,
params: Params,
context: Context
) => {
const deleteFlow = async (_parent, params, context) => {
const conditions = context.currentUser.can('delete', 'Flow');
const isCreator = conditions.isCreator;
const allFlows = Flow.query();
@@ -43,13 +31,15 @@ const deleteFlow = async (
await trigger.unregisterHook($);
} catch (error) {
// suppress error as the remote resource might have been already deleted
logger.debug(`Failed to unregister webhook for flow ${flow.id}: ${error.message}`);
logger.debug(
`Failed to unregister webhook for flow ${flow.id}: ${error.message}`
);
}
}
const executionIds = (
await flow.$relatedQuery('executions').select('executions.id')
).map((execution: Execution) => execution.id);
).map((execution) => execution.id);
await ExecutionStep.query().delete().whereIn('execution_id', executionIds);

View File

@@ -1,18 +1,7 @@
import Role from '../../models/role';
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
import Context from '../../types/express/context';
type Params = {
input: {
id: string;
};
};
const deleteRole = async (
_parent: unknown,
params: Params,
context: Context
) => {
const deleteRole = async (_parent, params, context) => {
context.currentUser.can('delete', 'Role');
const role = await Role.query().findById(params.input.id).throwIfNotFound();

View File

@@ -1,16 +1,4 @@
import Context from '../../types/express/context';
type Params = {
input: {
id: string;
};
};
const deleteStep = async (
_parent: unknown,
params: Params,
context: Context
) => {
const deleteStep = async (_parent, params, context) => {
context.currentUser.can('update', 'Flow');
const step = await context.currentUser

View File

@@ -1,19 +1,8 @@
import { Duration } from 'luxon';
import Context from '../../types/express/context';
import User from '../../models/user';
import deleteUserQueue from '../../queues/delete-user.ee';
type Params = {
input: {
id: string;
};
};
const deleteUser = async (
_parent: unknown,
params: Params,
context: Context
) => {
const deleteUser = async (_parent, params, context) => {
context.currentUser.can('delete', 'User');
const id = params.input.id;
@@ -24,7 +13,7 @@ const deleteUser = async (
const jobPayload = { id };
const millisecondsFor30Days = Duration.fromObject({ days: 30 }).toMillis();
const jobOptions = {
delay: millisecondsFor30Days
delay: millisecondsFor30Days,
};
await deleteUserQueue.add(jobName, jobPayload, jobOptions);

View File

@@ -1,15 +1,4 @@
import Context from '../../types/express/context';
import Step from '../../models/step';
type Params = {
input: {
id: string;
};
};
type NewStepIds = Record<string, string>;
function updateStepId(value: string, newStepIds: NewStepIds) {
function updateStepId(value, newStepIds) {
let newValue = value;
const stepIdEntries = Object.entries(newStepIds);
@@ -24,9 +13,9 @@ function updateStepId(value: string, newStepIds: NewStepIds) {
return newValue;
}
function updateStepVariables(parameters: Step['parameters'], newStepIds: NewStepIds): Step['parameters'] {
function updateStepVariables(parameters, newStepIds) {
const entries = Object.entries(parameters);
return entries.reduce((result, [key, value]: [string, unknown]) => {
return entries.reduce((result, [key, value]) => {
if (typeof value === 'string') {
return {
...result,
@@ -37,7 +26,7 @@ function updateStepVariables(parameters: Step['parameters'], newStepIds: NewStep
if (Array.isArray(value)) {
return {
...result,
[key]: value.map(item => updateStepVariables(item, newStepIds)),
[key]: value.map((item) => updateStepVariables(item, newStepIds)),
};
}
@@ -48,11 +37,7 @@ function updateStepVariables(parameters: Step['parameters'], newStepIds: NewStep
}, {});
}
const duplicateFlow = async (
_parent: unknown,
params: Params,
context: Context
) => {
const duplicateFlow = async (_parent, params, context) => {
context.currentUser.can('create', 'Flow');
const flow = await context.currentUser
@@ -69,17 +54,16 @@ const duplicateFlow = async (
active: false,
});
const newStepIds: NewStepIds = {};
const newStepIds = {};
for (const step of flow.steps) {
const duplicatedStep = await duplicatedFlow.$relatedQuery('steps')
.insert({
key: step.key,
appKey: step.appKey,
type: step.type,
connectionId: step.connectionId,
position: step.position,
parameters: updateStepVariables(step.parameters, newStepIds),
});
const duplicatedStep = await duplicatedFlow.$relatedQuery('steps').insert({
key: step.key,
appKey: step.appKey,
type: step.type,
connectionId: step.connectionId,
position: step.position,
parameters: updateStepVariables(step.parameters, newStepIds),
});
if (duplicatedStep.isTrigger) {
await duplicatedStep.updateWebhookUrl();

View File

@@ -1,18 +1,7 @@
import Context from '../../types/express/context';
import testRun from '../../services/test-run';
import Step from '../../models/step';
type Params = {
input: {
stepId: string;
};
};
const executeFlow = async (
_parent: unknown,
params: Params,
context: Context
) => {
const executeFlow = async (_parent, params, context) => {
const conditions = context.currentUser.can('update', 'Flow');
const isCreator = conditions.isCreator;
const allSteps = Step.query();
@@ -21,10 +10,7 @@ const executeFlow = async (
const { stepId } = params.input;
const untilStep = await baseQuery
.clone()
.findById(stepId)
.throwIfNotFound();
const untilStep = await baseQuery.clone().findById(stepId).throwIfNotFound();
const { executionStep } = await testRun({ stepId });

View File

@@ -6,13 +6,7 @@ import {
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
} from '../../helpers/remove-job-configuration';
type Params = {
input: {
email: string;
};
};
const forgotPassword = async (_parent: unknown, params: Params) => {
const forgotPassword = async (_parent, params) => {
const { email } = params.input;
const user = await User.query().findOne({ email: email.toLowerCase() });

View File

@@ -1,18 +1,7 @@
import Context from '../../types/express/context';
import globalVariable from '../../helpers/global-variable';
import App from '../../models/app';
type Params = {
input: {
id: string;
};
};
const generateAuthUrl = async (
_parent: unknown,
params: Params,
context: Context
) => {
const generateAuthUrl = async (_parent, params, context) => {
context.currentUser.can('create', 'Connection');
const connection = await context.currentUser

View File

@@ -1,14 +1,7 @@
import User from '../../models/user';
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
type Params = {
input: {
email: string;
password: string;
};
};
const login = async (_parent: unknown, params: Params) => {
const login = async (_parent, params) => {
const user = await User.query().findOne({
email: params.input.email.toLowerCase(),
});

View File

@@ -1,15 +1,7 @@
import User from '../../models/user';
import Role from '../../models/role';
type Params = {
input: {
fullName: string;
email: string;
password: string;
};
};
const registerUser = async (_parent: unknown, params: Params) => {
const registerUser = async (_parent, params) => {
const { fullName, email, password } = params.input;
const existingUser = await User.query().findOne({

View File

@@ -0,0 +1,22 @@
const resetConnection = async (_parent, params, context) => {
context.currentUser.can('create', 'Connection');
let connection = await context.currentUser
.$relatedQuery('connections')
.findOne({
id: params.input.id,
})
.throwIfNotFound();
if (!connection.formattedData) {
return null;
}
connection = await connection.$query().patchAndFetch({
formattedData: { screenName: connection.formattedData.screenName },
});
return connection;
};
export default resetConnection;

View File

@@ -1,38 +0,0 @@
import Context from '../../types/express/context';
import Connection from '../../models/connection';
type Params = {
input: {
id: string;
};
};
const resetConnection = async (
_parent: unknown,
params: Params,
context: Context
) => {
const conditions = context.currentUser.can('update', 'Connection');
const userConnections = context.currentUser.$relatedQuery('connections');
const allConnections = Connection.query();
const baseQuery = conditions.isCreator ? userConnections : allConnections;
let connection = await baseQuery
.clone()
.findOne({
id: params.input.id,
})
.throwIfNotFound();
if (!connection.formattedData) {
return null;
}
connection = await connection.$query().patchAndFetch({
formattedData: { screenName: connection.formattedData.screenName },
});
return connection;
};
export default resetConnection;

View File

@@ -1,13 +1,6 @@
import User from '../../models/user';
type Params = {
input: {
token: string;
password: string;
};
};
const resetPassword = async (_parent: unknown, params: Params) => {
const resetPassword = async (_parent, params) => {
const { token, password } = params.input;
if (!token) {

View File

@@ -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;

View File

@@ -0,0 +1,17 @@
import AppAuthClient from '../../models/app-auth-client';
const updateAppAuthClient = async (_parent, params, context) => {
context.currentUser.can('update', 'App');
const { id, ...appAuthClientData } = params.input;
const appAuthClient = await AppAuthClient.query()
.findById(id)
.throwIfNotFound();
await appAuthClient.$query().patch(appAuthClientData);
return appAuthClient;
};
export default updateAppAuthClient;

View File

@@ -1,38 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import AppAuthClient from '../../models/app-auth-client';
import Context from '../../types/express/context';
type Params = {
input: {
id: string;
name: string;
formattedAuthDefaults?: IJSONObject;
active?: boolean;
};
};
const updateAppAuthClient = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('update', 'App');
const {
id,
...appAuthClientData
} = params.input;
const appAuthClient = await AppAuthClient
.query()
.findById(id)
.throwIfNotFound();
await appAuthClient
.$query()
.patch(appAuthClientData);
return appAuthClient;
};
export default updateAppAuthClient;

View File

@@ -0,0 +1,15 @@
import AppConfig from '../../models/app-config';
const updateAppConfig = async (_parent, params, context) => {
context.currentUser.can('update', 'App');
const { id, ...appConfigToUpdate } = params.input;
const appConfig = await AppConfig.query().findById(id).throwIfNotFound();
await appConfig.$query().patch(appConfigToUpdate);
return appConfig;
};
export default updateAppConfig;

View File

@@ -1,39 +0,0 @@
import AppConfig from '../../models/app-config';
import Context from '../../types/express/context';
type Params = {
input: {
id: string;
allowCustomConnection?: boolean;
shared?: boolean;
disabled?: boolean;
};
};
const updateAppConfig = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('update', 'App');
const {
id,
...appConfigToUpdate
} = params.input;
const appConfig = await AppConfig
.query()
.findById(id)
.throwIfNotFound();
await appConfig
.$query()
.patch(
appConfigToUpdate
);
return appConfig;
};
export default updateAppConfig;

View File

@@ -1,18 +1,6 @@
import type { IJSONValue } from '@automatisch/types';
import Config from '../../models/config';
import Context from '../../types/express/context';
type Params = {
input: {
[index: string]: IJSONValue;
};
};
const updateConfig = async (
_parent: unknown,
params: Params,
context: Context
) => {
const updateConfig = async (_parent, params, context) => {
context.currentUser.can('update', 'Config');
const config = params.input;

View File

@@ -0,0 +1,33 @@
import AppAuthClient from '../../models/app-auth-client';
const updateConnection = async (_parent, params, context) => {
context.currentUser.can('create', 'Connection');
let connection = await context.currentUser
.$relatedQuery('connections')
.findOne({
id: params.input.id,
})
.throwIfNotFound();
let formattedData = params.input.formattedData;
if (params.input.appAuthClientId) {
const appAuthClient = await AppAuthClient.query()
.findById(params.input.appAuthClientId)
.throwIfNotFound();
formattedData = appAuthClient.formattedAuthDefaults;
}
connection = await connection.$query().patchAndFetch({
formattedData: {
...connection.formattedData,
...formattedData,
},
});
return connection;
};
export default updateConnection;

View File

@@ -1,52 +0,0 @@
import { IJSONObject } from '@automatisch/types';
import Context from '../../types/express/context';
import AppAuthClient from '../../models/app-auth-client';
import Connection from '../../models/connection';
type Params = {
input: {
id: string;
formattedData?: IJSONObject;
appAuthClientId?: string;
};
};
const updateConnection = async (
_parent: unknown,
params: Params,
context: Context
) => {
const conditions = context.currentUser.can('update', 'Connection');
const userConnections = context.currentUser.$relatedQuery('connections');
const allConnections = Connection.query();
const baseQuery = conditions.isCreator ? userConnections : allConnections;
let connection = await baseQuery
.clone()
.findOne({
id: params.input.id,
})
.throwIfNotFound();
let formattedData = params.input.formattedData;
if (params.input.appAuthClientId) {
const appAuthClient = await AppAuthClient
.query()
.findById(params.input.appAuthClientId)
.throwIfNotFound();
formattedData = appAuthClient.formattedAuthDefaults;
}
connection = await connection.$query().patchAndFetch({
formattedData: {
...connection.formattedData,
...formattedData,
},
});
return connection;
};
export default updateConnection;

View File

@@ -0,0 +1,11 @@
const updateCurrentUser = async (_parent, params, context) => {
const user = await context.currentUser.$query().patchAndFetch({
email: params.input.email,
password: params.input.password,
fullName: params.input.fullName,
});
return user;
};
export default updateCurrentUser;

View File

@@ -1,25 +0,0 @@
import Context from '../../types/express/context';
type Params = {
input: {
email: string;
password: string;
fullName: string;
};
};
const updateCurrentUser = async (
_parent: unknown,
params: Params,
context: Context
) => {
const user = await context.currentUser.$query().patchAndFetch({
email: params.input.email,
password: params.input.password,
fullName: params.input.fullName,
});
return user;
};
export default updateCurrentUser;

View File

@@ -1,24 +1,15 @@
import Flow from '../../models/flow';
import Context from '../../types/express/context';
import flowQueue from '../../queues/flow';
import { REMOVE_AFTER_30_DAYS_OR_150_JOBS, REMOVE_AFTER_7_DAYS_OR_50_JOBS } from '../../helpers/remove-job-configuration';
import {
REMOVE_AFTER_30_DAYS_OR_150_JOBS,
REMOVE_AFTER_7_DAYS_OR_50_JOBS,
} from '../../helpers/remove-job-configuration';
import globalVariable from '../../helpers/global-variable';
type Params = {
input: {
id: string;
active: boolean;
};
};
const JOB_NAME = 'flow';
const EVERY_15_MINUTES_CRON = '*/15 * * * *';
const updateFlowStatus = async (
_parent: unknown,
params: Params,
context: Context
) => {
const updateFlowStatus = async (_parent, params, context) => {
const conditions = context.currentUser.can('publish', 'Flow');
const isCreator = conditions.isCreator;
const allFlows = Flow.query();
@@ -74,7 +65,7 @@ const updateFlowStatus = async (
repeat: repeatOptions,
jobId: flow.id,
removeOnComplete: REMOVE_AFTER_7_DAYS_OR_50_JOBS,
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS
removeOnFail: REMOVE_AFTER_30_DAYS_OR_150_JOBS,
}
);
} else {
@@ -85,12 +76,9 @@ const updateFlowStatus = async (
}
}
flow = await flow
.$query()
.withGraphFetched('steps')
.patchAndFetch({
active: newActiveValue,
});
flow = await flow.$query().withGraphFetched('steps').patchAndFetch({
active: newActiveValue,
});
return flow;
};

View File

@@ -1,17 +1,4 @@
import Context from '../../types/express/context';
type Params = {
input: {
id: string;
name: string;
};
};
const updateFlow = async (
_parent: unknown,
params: Params,
context: Context
) => {
const updateFlow = async (_parent, params, context) => {
context.currentUser.can('update', 'Flow');
let flow = await context.currentUser

View File

@@ -1,35 +1,13 @@
import Context from '../../types/express/context';
import Role from '../../models/role';
import Permission from '../../models/permission';
import permissionCatalog from '../../helpers/permission-catalog.ee';
type Params = {
input: {
id: string;
name: string;
description: string;
permissions: Permission[];
};
};
const updateRole = async (
_parent: unknown,
params: Params,
context: Context
) => {
const updateRole = async (_parent, params, context) => {
context.currentUser.can('update', 'Role');
const {
id,
name,
description,
permissions,
} = params.input;
const { id, name, description, permissions } = params.input;
const role = await Role
.query()
.findById(id)
.throwIfNotFound();
const role = await Role.query().findById(id).throwIfNotFound();
try {
const updatedRole = await Role.transaction(async (trx) => {
@@ -38,19 +16,17 @@ const updateRole = async (
if (permissions?.length) {
const sanitizedPermissions = permissions
.filter((permission) => {
const {
action,
subject,
conditions,
} = permission;
const { action, subject, conditions } = permission;
const relevantAction = permissionCatalog.actions.find(actionCatalogItem => actionCatalogItem.key === action);
const relevantAction = permissionCatalog.actions.find(
(actionCatalogItem) => actionCatalogItem.key === action
);
const validSubject = relevantAction.subjects.includes(subject);
const validConditions = conditions.every(condition => {
return !!permissionCatalog
.conditions
.find((conditionCatalogItem) => conditionCatalogItem.key === condition);
})
const validConditions = conditions.every((condition) => {
return !!permissionCatalog.conditions.find(
(conditionCatalogItem) => conditionCatalogItem.key === condition
);
});
return validSubject && validConditions;
})
@@ -62,22 +38,17 @@ const updateRole = async (
await Permission.query().insert(sanitizedPermissions);
}
await role
.$query(trx)
.patch(
{
name,
description,
}
);
await role.$query(trx).patch({
name,
description,
});
return await Role
.query(trx)
return await Role.query(trx)
.leftJoinRelated({
permissions: true
permissions: true,
})
.withGraphFetched({
permissions: true
permissions: true,
})
.findById(id);
});

View File

@@ -1,29 +1,8 @@
import { IJSONObject } from '@automatisch/types';
import App from '../../models/app';
import Step from '../../models/step';
import Connection from '../../models/connection';
import Context from '../../types/express/context';
type Params = {
input: {
id: string;
key: string;
appKey: string;
parameters: IJSONObject;
flow: {
id: string;
};
connection: {
id: string;
};
};
};
const updateStep = async (
_parent: unknown,
params: Params,
context: Context
) => {
const updateStep = async (_parent, params, context) => {
const { isCreator } = context.currentUser.can('update', 'Flow');
const userSteps = context.currentUser.$relatedQuery('steps');
const allSteps = Step.query();
@@ -45,17 +24,18 @@ const updateStep = async (
canSeeAllConnections = !conditions.isCreator;
} catch {
// The user does not have permission to read any connections!
throw new Error('The connection does not exist!');
// void
}
const userConnections = context.currentUser.relatedConnectionsQuery();
const userConnections = context.currentUser.$relatedQuery('connections');
const allConnections = Connection.query();
const baseConnectionsQuery = canSeeAllConnections ? allConnections : userConnections;
const baseConnectionsQuery = canSeeAllConnections
? allConnections
: userConnections;
const connection = await baseConnectionsQuery
.clone()
.findById(input.connection?.id)
.findById(input.connection?.id);
if (!connection) {
throw new Error('The connection does not exist!');

View File

@@ -1,25 +1,9 @@
import Context from '../../types/express/context';
import User from '../../models/user';
type Params = {
input: {
id: string;
email: string;
fullName: string;
role: {
id: string;
};
};
};
const updateUser = async (
_parent: unknown,
params: Params,
context: Context
) => {
const updateUser = async (_parent, params, context) => {
context.currentUser.can('update', 'User');
const userPayload: Partial<User> = {
const userPayload = {
email: params.input.email,
fullName: params.input.fullName,
};

View File

@@ -0,0 +1,30 @@
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
const upsertSamlAuthProvider = async (_parent, params, context) => {
context.currentUser.can('create', 'SamlAuthProvider');
const samlAuthProviderPayload = {
...params.input,
};
const existingSamlAuthProvider = await SamlAuthProvider.query()
.limit(1)
.first();
if (!existingSamlAuthProvider) {
const samlAuthProvider = await SamlAuthProvider.query().insert(
samlAuthProviderPayload
);
return samlAuthProvider;
}
const samlAuthProvider = await SamlAuthProvider.query().patchAndFetchById(
existingSamlAuthProvider.id,
samlAuthProviderPayload
);
return samlAuthProvider;
};
export default upsertSamlAuthProvider;

View File

@@ -1,52 +0,0 @@
import type { SamlConfig } from '@node-saml/passport-saml';
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
import Context from '../../types/express/context';
type Params = {
input: {
name: string;
certificate: string;
signatureAlgorithm: SamlConfig['signatureAlgorithm'];
issuer: string;
entryPoint: string;
firstnameAttributeName: string;
surnameAttributeName: string;
emailAttributeName: string;
roleAttributeName: string;
defaultRoleId: string;
active: boolean;
};
};
const upsertSamlAuthProvider = async (
_parent: unknown,
params: Params,
context: Context
) => {
context.currentUser.can('create', 'SamlAuthProvider');
const samlAuthProviderPayload: Partial<SamlAuthProvider> = {
...params.input,
};
const existingSamlAuthProvider = await SamlAuthProvider.query()
.limit(1)
.first();
if (!existingSamlAuthProvider) {
const samlAuthProvider = await SamlAuthProvider.query().insert(
samlAuthProviderPayload
);
return samlAuthProvider;
}
const samlAuthProvider = await SamlAuthProvider.query().patchAndFetchById(
existingSamlAuthProvider.id,
samlAuthProviderPayload
);
return samlAuthProvider;
};
export default upsertSamlAuthProvider;

View File

@@ -1,24 +1,11 @@
import SamlAuthProvider from '../../models/saml-auth-provider.ee';
import SamlAuthProvidersRoleMapping from '../../models/saml-auth-providers-role-mapping.ee';
import Context from '../../types/express/context';
import isEmpty from 'lodash/isEmpty';
type Params = {
input: {
samlAuthProviderId: string;
samlAuthProvidersRoleMappings: [
{
roleId: string;
remoteRoleName: string;
}
];
};
};
const upsertSamlAuthProvidersRoleMappings = async (
_parent: unknown,
params: Params,
context: Context
_parent,
params,
context
) => {
context.currentUser.can('update', 'SamlAuthProvider');

View File

@@ -1,18 +1,7 @@
import Context from '../../types/express/context';
import App from '../../models/app';
import globalVariable from '../../helpers/global-variable';
type Params = {
input: {
id: string;
};
};
const verifyConnection = async (
_parent: unknown,
params: Params,
context: Context
) => {
const verifyConnection = async (_parent, params, context) => {
context.currentUser.can('create', 'Connection');
let connection = await context.currentUser

View File

@@ -1,11 +1,6 @@
import AppAuthClient from '../../models/app-auth-client';
import Context from '../../types/express/context';
type Params = {
id: string;
};
const getAppAuthClient = async (_parent: unknown, params: Params, context: Context) => {
const getAppAuthClient = async (_parent, params, context) => {
let canSeeAllClients = false;
try {
context.currentUser.can('read', 'App');
@@ -15,8 +10,7 @@ const getAppAuthClient = async (_parent: unknown, params: Params, context: Conte
// void
}
const appAuthClient = AppAuthClient
.query()
const appAuthClient = AppAuthClient.query()
.findById(params.id)
.throwIfNotFound();

View File

@@ -1,12 +1,6 @@
import AppConfig from '../../models/app-config';
import Context from '../../types/express/context';
type Params = {
appKey: string;
active: boolean;
};
const getAppAuthClients = async (_parent: unknown, params: Params, context: Context) => {
const getAppAuthClients = async (_parent, params, context) => {
let canSeeAllClients = false;
try {
context.currentUser.can('read', 'App');
@@ -16,8 +10,7 @@ const getAppAuthClients = async (_parent: unknown, params: Params, context: Cont
// void
}
const appConfig = await AppConfig
.query()
const appConfig = await AppConfig.query()
.findOne({
key: params.appKey,
})
@@ -30,8 +23,8 @@ const getAppAuthClients = async (_parent: unknown, params: Params, context: Cont
if (!canSeeAllClients) {
appAuthClients.where({
active: true
})
active: true,
});
}
return await appAuthClients;

View File

@@ -0,0 +1,17 @@
import AppConfig from '../../models/app-config';
const getAppConfig = async (_parent, params, context) => {
context.currentUser.can('create', 'Connection');
const appConfig = await AppConfig.query()
.withGraphFetched({
appAuthClients: true,
})
.findOne({
key: params.key,
});
return appConfig;
};
export default getAppConfig;

View File

@@ -1,23 +0,0 @@
import AppConfig from '../../models/app-config';
import Context from '../../types/express/context';
type Params = {
key: string;
};
const getAppConfig = async (_parent: unknown, params: Params, context: Context) => {
context.currentUser.can('create', 'Connection');
const appConfig = await AppConfig
.query()
.withGraphFetched({
appAuthClients: true
})
.findOne({
key: params.key
});
return appConfig;
};
export default getAppConfig;

View File

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

View File

@@ -1,71 +0,0 @@
import App from '../../models/app';
import Connection from '../../models/connection';
import Context from '../../types/express/context';
type Params = {
key: string;
};
const getApp = async (_parent: unknown, params: Params, context: Context) => {
const conditions = context.currentUser.can('read', 'Connection');
const app = await App.findOneByKey(params.key);
if (context.currentUser) {
const userConnections = context.currentUser.relatedConnectionsQuery();
const allConnections = Connection.query();
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
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({
appConfig: true,
appAuthClient: true
})
.joinRaw('join connections_with_flow_count on connections.id = connections_with_flow_count.id')
.orderBy('connections.created_at', 'desc');
return {
...app,
connections,
};
}
return app;
};
export default getApp;

View File

@@ -0,0 +1,17 @@
import App from '../../models/app';
const getApps = async (_parent, params) => {
const apps = await App.findAll(params.name);
if (params.onlyWithTriggers) {
return apps.filter((app) => app.triggers?.length);
}
if (params.onlyWithActions) {
return apps.filter((app) => app.actions?.length);
}
return apps;
};
export default getApps;

View File

@@ -1,24 +0,0 @@
import { IApp } from '@automatisch/types';
import App from '../../models/app';
type Params = {
name: string;
onlyWithTriggers: boolean;
onlyWithActions: boolean;
};
const getApps = async (_parent: unknown, params: Params) => {
const apps = await App.findAll(params.name);
if (params.onlyWithTriggers) {
return apps.filter((app: IApp) => app.triggers?.length);
}
if (params.onlyWithActions) {
return apps.filter((app: IApp) => app.actions?.length);
}
return apps;
};
export default getApps;

View File

@@ -13,6 +13,7 @@ const getAutomatischInfo = async () => {
return {
isCloud: appConfig.isCloud,
isMation: appConfig.isMation,
license: computedLicense,
};
};

View File

@@ -9,6 +9,7 @@ describe('graphQL getAutomatischInfo query', () => {
query {
getAutomatischInfo {
isCloud
isMation
license {
id
name
@@ -24,6 +25,7 @@ describe('graphQL getAutomatischInfo query', () => {
jest.spyOn(license, 'getLicense').mockResolvedValue(false);
jest.replaceProperty(appConfig, 'isCloud', false);
jest.replaceProperty(appConfig, 'isMation', false);
});
it('should return empty license data', async () => {
@@ -36,6 +38,7 @@ describe('graphQL getAutomatischInfo query', () => {
data: {
getAutomatischInfo: {
isCloud: false,
isMation: false,
license: {
id: null,
name: null,
@@ -77,6 +80,7 @@ describe('graphQL getAutomatischInfo query', () => {
data: {
getAutomatischInfo: {
isCloud: true,
isMation: false,
license: {
expireAt: '2025-08-09T10:56:54.144Z',
id: '123123',
@@ -105,6 +109,69 @@ describe('graphQL getAutomatischInfo query', () => {
const expectedResponsePayload = {
data: {
getAutomatischInfo: {
isCloud: false,
isMation: false,
license: {
expireAt: '2025-08-09T10:56:54.144Z',
id: '123123',
name: 'Test License',
verified: true,
},
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with mation flag enabled', () => {
beforeEach(async () => {
jest.replaceProperty(appConfig, 'isCloud', false);
jest.replaceProperty(appConfig, 'isMation', true);
});
it('should return all license data', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getAutomatischInfo: {
isCloud: false,
isMation: true,
license: {
expireAt: '2025-08-09T10:56:54.144Z',
id: '123123',
name: 'Test License',
verified: true,
},
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with mation flag disabled', () => {
beforeEach(async () => {
jest.replaceProperty(appConfig, 'isCloud', false);
jest.replaceProperty(appConfig, 'isMation', false);
});
it('should return all license data', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getAutomatischInfo: {
isMation: false,
isCloud: false,
license: {
expireAt: '2025-08-09T10:56:54.144Z',

View File

@@ -1,17 +1,8 @@
import { DateTime } from 'luxon';
import { TSubscription } from '@automatisch/types';
import Context from '../../types/express/context';
import Billing from '../../helpers/billing/index.ee';
import Execution from '../../models/execution';
import ExecutionStep from '../../models/execution-step';
import Subscription from '../../models/subscription.ee';
const getBillingAndUsage = async (
_parent: unknown,
_params: unknown,
context: Context
) => {
const getBillingAndUsage = async (_parent, _params, context) => {
const persistedSubscription = await context.currentUser.$relatedQuery(
'currentSubscription'
);
@@ -28,7 +19,7 @@ const getBillingAndUsage = async (
};
};
const paidSubscription = (subscription: Subscription): TSubscription => {
const paidSubscription = (subscription) => {
const currentPlan = Billing.paddlePlans.find(
(plan) => plan.productId === subscription.paddlePlanId
);
@@ -63,7 +54,7 @@ const paidSubscription = (subscription: Subscription): TSubscription => {
};
};
const freeTrialSubscription = (): TSubscription => {
const freeTrialSubscription = () => {
return {
status: null,
monthlyQuota: {
@@ -85,15 +76,15 @@ const freeTrialSubscription = (): TSubscription => {
};
};
const executionIds = async (context: Context) => {
const executionIds = async (context) => {
return (
await context.currentUser
.$relatedQuery('executions')
.select('executions.id')
).map((execution: Execution) => execution.id);
).map((execution) => execution.id);
};
const executionStepCount = async (context: Context) => {
const executionStepCount = async (context) => {
const executionStepCount = await ExecutionStep.query()
.whereIn('execution_id', await executionIds(context))
.andWhere(

View File

@@ -1,11 +1,7 @@
import { hasValidLicense } from '../../helpers/license.ee';
import Config from '../../models/config';
type Params = {
keys: string[];
};
const getConfig = async (_parent: unknown, params: Params) => {
const getConfig = async (_parent, params) => {
if (!(await hasValidLicense())) return {};
const configQuery = Config.query();
@@ -22,7 +18,7 @@ const getConfig = async (_parent: unknown, params: Params) => {
computedConfig[key] = value?.data;
return computedConfig;
}, {} as Record<string, unknown>);
}, {});
};
export default getConfig;

View File

@@ -1,23 +1,15 @@
import { IConnection } from '@automatisch/types';
import App from '../../models/app';
import Context from '../../types/express/context';
import Flow from '../../models/flow';
import Connection from '../../models/connection';
type Params = {
name: string;
};
const getConnectedApps = async (
_parent: unknown,
params: Params,
context: Context
) => {
const getConnectedApps = async (_parent, params, context) => {
const conditions = context.currentUser.can('read', 'Connection');
const userConnections = context.currentUser.relatedConnectionsQuery();
const userConnections = context.currentUser.$relatedQuery('connections');
const allConnections = Connection.query();
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
const connectionBaseQuery = conditions.isCreator
? userConnections
: allConnections;
const userFlows = context.currentUser.$relatedQuery('flows');
const allFlows = Flow.query();
@@ -25,9 +17,8 @@ const getConnectedApps = async (
let apps = await App.findAll(params.name);
const connections = await Connection
.query()
.with('connections', connectionBaseQuery)
const connections = await connectionBaseQuery
.clone()
.select('connections.key')
.where({ draft: false })
.count('connections.id as count')
@@ -52,7 +43,7 @@ const getConnectedApps = async (
})
.map((app) => {
const connection = connections.find(
(connection) => (connection as IConnection).key === app.key
(connection) => connection.key === app.key
);
app.connectionCount = connection?.count || 0;

View File

@@ -0,0 +1,5 @@
const getCurrentUser = async (_parent, _params, context) => {
return context.currentUser;
};
export default getCurrentUser;

View File

@@ -1,11 +0,0 @@
import Context from '../../types/express/context';
const getCurrentUser = async (
_parent: unknown,
_params: unknown,
context: Context
) => {
return context.currentUser;
};
export default getCurrentUser;

View File

@@ -1,22 +1,10 @@
import { IDynamicData, IJSONObject } from '@automatisch/types';
import Context from '../../types/express/context';
import App from '../../models/app';
import Step from '../../models/step';
import ExecutionStep from '../../models/execution-step';
import globalVariable from '../../helpers/global-variable';
import computeParameters from '../../helpers/compute-parameters';
type Params = {
stepId: string;
key: string;
parameters: IJSONObject;
};
const getDynamicData = async (
_parent: unknown,
params: Params,
context: Context
) => {
const getDynamicData = async (_parent, params, context) => {
const conditions = context.currentUser.can('update', 'Flow');
const userSteps = context.currentUser.$relatedQuery('steps');
const allSteps = Step.query();
@@ -40,9 +28,7 @@ const getDynamicData = async (
const app = await App.findOneByKey(step.appKey);
const $ = await globalVariable({ connection, app, flow, step });
const command = app.dynamicData.find(
(data: IDynamicData) => data.key === params.key
);
const command = app.dynamicData.find((data) => data.key === params.key);
// apply run-time parameters that're not persisted yet
for (const parameterKey in params.parameters) {
@@ -53,12 +39,17 @@ const getDynamicData = async (
const lastExecution = await flow.$relatedQuery('lastExecution');
const lastExecutionId = lastExecution?.id;
const priorExecutionSteps = lastExecutionId ? await ExecutionStep.query().where({
execution_id: lastExecutionId,
}) : [];
const priorExecutionSteps = lastExecutionId
? await ExecutionStep.query().where({
execution_id: lastExecutionId,
})
: [];
// compute variables in parameters
const computedParameters = computeParameters($.step.parameters, priorExecutionSteps);
const computedParameters = computeParameters(
$.step.parameters,
priorExecutionSteps
);
$.step.parameters = computedParameters;

View File

@@ -1,20 +1,8 @@
import { IDynamicFields, IJSONObject } from '@automatisch/types';
import Context from '../../types/express/context';
import App from '../../models/app';
import Step from '../../models/step';
import globalVariable from '../../helpers/global-variable';
type Params = {
stepId: string;
key: string;
parameters: IJSONObject;
};
const getDynamicFields = async (
_parent: unknown,
params: Params,
context: Context
) => {
const getDynamicFields = async (_parent, params, context) => {
const conditions = context.currentUser.can('update', 'Flow');
const userSteps = context.currentUser.$relatedQuery('steps');
const allSteps = Step.query();
@@ -37,16 +25,14 @@ const getDynamicFields = async (
const app = await App.findOneByKey(step.appKey);
const $ = await globalVariable({ connection, app, flow: step.flow, step });
const command = app.dynamicFields.find(
(data: IDynamicFields) => data.key === params.key
);
const command = app.dynamicFields.find((data) => data.key === params.key);
for (const parameterKey in params.parameters) {
const parameterValue = params.parameters[parameterKey];
$.step.parameters[parameterKey] = parameterValue;
}
const additionalFields = await command.run($) || [];
const additionalFields = (await command.run($)) || [];
return additionalFields;
};

View File

@@ -1,22 +1,13 @@
import Context from '../../types/express/context';
import paginate from '../../helpers/pagination';
import Execution from '../../models/execution';
type Params = {
executionId: string;
limit: number;
offset: number;
};
const getExecutionSteps = async (
_parent: unknown,
params: Params,
context: Context
) => {
const getExecutionSteps = async (_parent, params, context) => {
const conditions = context.currentUser.can('read', 'Execution');
const userExecutions = context.currentUser.$relatedQuery('executions');
const allExecutions = Execution.query();
const executionBaseQuery = conditions.isCreator ? userExecutions : allExecutions;
const executionBaseQuery = conditions.isCreator
? userExecutions
: allExecutions;
const execution = await executionBaseQuery
.clone()

View File

@@ -1,19 +1,12 @@
import Context from '../../types/express/context';
import Execution from '../../models/execution';
type Params = {
executionId: string;
};
const getExecution = async (
_parent: unknown,
params: Params,
context: Context
) => {
const getExecution = async (_parent, params, context) => {
const conditions = context.currentUser.can('read', 'Execution');
const userExecutions = context.currentUser.$relatedQuery('executions');
const allExecutions = Execution.query();
const executionBaseQuery = conditions.isCreator ? userExecutions : allExecutions;
const executionBaseQuery = conditions.isCreator
? userExecutions
: allExecutions;
const execution = await executionBaseQuery
.clone()

View File

@@ -1,36 +1,18 @@
import { raw } from 'objection';
import { DateTime } from 'luxon';
import Context from '../../types/express/context';
import Execution from '../../models/execution';
import paginate from '../../helpers/pagination';
type Filters = {
flowId?: string;
status?: string;
createdAt?: {
from?: string;
to?: string;
};
}
type Params = {
limit: number;
offset: number;
filters?: Filters;
};
const getExecutions = async (
_parent: unknown,
params: Params,
context: Context
) => {
const getExecutions = async (_parent, params, context) => {
const conditions = context.currentUser.can('read', 'Execution');
const filters = params.filters;
const userExecutions = context.currentUser.$relatedQuery('executions');
const allExecutions = Execution.query();
const executionBaseQuery = conditions.isCreator ? userExecutions : allExecutions;
const executionBaseQuery = conditions.isCreator
? userExecutions
: allExecutions;
const selectStatusStatement = `
case
@@ -48,8 +30,7 @@ const getExecutions = async (
.groupBy('executions.id')
.orderBy('created_at', 'desc');
const computedExecutions = Execution
.query()
const computedExecutions = Execution.query()
.with('executions', executions)
.withSoftDeleted()
.withGraphFetched({
@@ -69,20 +50,16 @@ const getExecutions = async (
if (filters?.createdAt) {
const createdAtFilter = filters.createdAt;
if (createdAtFilter.from) {
const isoFromDateTime = DateTime
.fromMillis(
parseInt(createdAtFilter.from, 10)
)
.toISO();
const isoFromDateTime = DateTime.fromMillis(
parseInt(createdAtFilter.from, 10)
).toISO();
computedExecutions.where('executions.created_at', '>=', isoFromDateTime);
}
if (createdAtFilter.to) {
const isoToDateTime = DateTime
.fromMillis(
parseInt(createdAtFilter.to, 10)
)
.toISO();
const isoToDateTime = DateTime.fromMillis(
parseInt(createdAtFilter.to, 10)
).toISO();
computedExecutions.where('executions.created_at', '<=', isoToDateTime);
}
}

View File

@@ -1,11 +1,6 @@
import Context from '../../types/express/context';
import Flow from '../../models/flow';
type Params = {
id: string;
};
const getFlow = async (_parent: unknown, params: Params, context: Context) => {
const getFlow = async (_parent, params, context) => {
const conditions = context.currentUser.can('read', 'Flow');
const userFlows = context.currentUser.$relatedQuery('flows');
const allFlows = Flow.query();

View File

@@ -1,16 +1,7 @@
import Flow from '../../models/flow';
import Context from '../../types/express/context';
import paginate from '../../helpers/pagination';
type Params = {
appKey?: string;
connectionId?: string;
name?: string;
limit: number;
offset: number;
};
const getFlows = async (_parent: unknown, params: Params, context: Context) => {
const getFlows = async (_parent, params, context) => {
const conditions = context.currentUser.can('read', 'Flow');
const userFlows = context.currentUser.$relatedQuery('flows');
const allFlows = Flow.query();

View File

@@ -1,12 +1,9 @@
import Context from '../../types/express/context';
import Billing from '../../helpers/billing/index.ee';
const getInvoices = async (
_parent: unknown,
_params: unknown,
context: Context
) => {
const subscription = await context.currentUser.$relatedQuery('currentSubscription');
const getInvoices = async (_parent, _params, context) => {
const subscription = await context.currentUser.$relatedQuery(
'currentSubscription'
);
if (!subscription) {
return;

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