feat: add shared connection capability
This commit is contained in:
@@ -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.0.0",
|
"objection": "^3.1.1",
|
||||||
"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",
|
||||||
|
@@ -0,0 +1,15 @@
|
|||||||
|
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');
|
||||||
|
}
|
@@ -28,11 +28,11 @@ const createFlow = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (connectionId) {
|
if (connectionId) {
|
||||||
const hasConnection = await context.currentUser
|
const connection = await context.currentUser
|
||||||
.$relatedQuery('connections')
|
.relatedConnectionsQuery()
|
||||||
.findById(connectionId);
|
.findById(connectionId);
|
||||||
|
|
||||||
if (!hasConnection) {
|
if (!connection) {
|
||||||
throw new Error('The connection does not exist!');
|
throw new Error('The connection does not exist!');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
|
import Connection from '../../models/connection';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
input: {
|
input: {
|
||||||
@@ -11,10 +12,13 @@ const deleteConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
context.currentUser.can('delete', 'Connection');
|
const conditions = context.currentUser.can('delete', 'Connection');
|
||||||
|
const userConnections = context.currentUser.$relatedQuery('connections');
|
||||||
|
const allConnections = Connection.query();
|
||||||
|
const baseQuery = conditions.isCreator ? userConnections : allConnections;
|
||||||
|
|
||||||
await context.currentUser
|
await baseQuery
|
||||||
.$relatedQuery('connections')
|
.clone()
|
||||||
.delete()
|
.delete()
|
||||||
.findOne({
|
.findOne({
|
||||||
id: params.input.id,
|
id: params.input.id,
|
||||||
|
@@ -4,6 +4,7 @@ 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';
|
||||||
|
|
||||||
@@ -14,10 +15,66 @@ const deleteCurrentUser = async (
|
|||||||
) => {
|
) => {
|
||||||
const id = context.currentUser.id;
|
const id = context.currentUser.id;
|
||||||
|
|
||||||
const flows = await context.currentUser.$relatedQuery('flows').where({
|
try {
|
||||||
|
await User.transaction(async (trx) => {
|
||||||
|
const flows = await context.currentUser
|
||||||
|
.$relatedQuery('flows', trx)
|
||||||
|
.where({
|
||||||
active: true,
|
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();
|
const repeatableJobs = await flowQueue.getRepeatableJobs();
|
||||||
|
|
||||||
for (const flow of flows) {
|
for (const flow of flows) {
|
||||||
@@ -28,37 +85,17 @@ const deleteCurrentUser = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
await deleteUserQueue.add(jobName, jobPayload, jobOptions);
|
||||||
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('The user deletion has failed!');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default deleteCurrentUser;
|
export default deleteCurrentUser;
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import Context from '../../types/express/context';
|
import Context from '../../types/express/context';
|
||||||
|
import Connection from '../../models/connection';
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
input: {
|
input: {
|
||||||
@@ -11,10 +12,13 @@ const resetConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
context.currentUser.can('create', 'Connection');
|
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 context.currentUser
|
let connection = await baseQuery
|
||||||
.$relatedQuery('connections')
|
.clone()
|
||||||
.findOne({
|
.findOne({
|
||||||
id: params.input.id,
|
id: params.input.id,
|
||||||
})
|
})
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
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: {
|
||||||
@@ -15,10 +16,13 @@ const updateConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
context.currentUser.can('create', 'Connection');
|
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 context.currentUser
|
let connection = await baseQuery
|
||||||
.$relatedQuery('connections')
|
.clone()
|
||||||
.findOne({
|
.findOne({
|
||||||
id: params.input.id,
|
id: params.input.id,
|
||||||
})
|
})
|
||||||
|
@@ -45,10 +45,11 @@ const updateStep = async (
|
|||||||
|
|
||||||
canSeeAllConnections = !conditions.isCreator;
|
canSeeAllConnections = !conditions.isCreator;
|
||||||
} catch {
|
} catch {
|
||||||
// void
|
// The user does not have permission to read any connections!
|
||||||
|
throw new Error('The connection does not exist!');
|
||||||
}
|
}
|
||||||
|
|
||||||
const userConnections = context.currentUser.$relatedQuery('connections');
|
const userConnections = context.currentUser.relatedConnectionsQuery();
|
||||||
const allConnections = Connection.query();
|
const allConnections = Connection.query();
|
||||||
const baseConnectionsQuery = canSeeAllConnections ? allConnections : userConnections;
|
const baseConnectionsQuery = canSeeAllConnections ? allConnections : userConnections;
|
||||||
|
|
||||||
|
@@ -9,28 +9,55 @@ 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 connections = await connectionBaseQuery
|
const userConnections = context.currentUser.relatedConnectionsQuery();
|
||||||
.clone()
|
const allConnections = Connection.query();
|
||||||
.select('connections.*')
|
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
|
||||||
.withGraphFetched({
|
|
||||||
appConfig: true,
|
const connections = await Connection.query()
|
||||||
appAuthClient: true
|
.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
|
||||||
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.fullOuterJoinRelated('steps')
|
|
||||||
.where({
|
.where({
|
||||||
'connections.key': params.key,
|
'connections.key': params.key,
|
||||||
'connections.draft': false,
|
'connections.draft': false,
|
||||||
})
|
})
|
||||||
.countDistinct('steps.flow_id as flowCount')
|
.countDistinct('steps.flow_id as flowCount')
|
||||||
.groupBy('connections.id')
|
.groupBy('connections.id')
|
||||||
.orderBy('created_at', 'desc');
|
)
|
||||||
|
.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 {
|
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.$relatedQuery('connections');
|
const userConnections = context.currentUser.relatedConnectionsQuery();
|
||||||
const allConnections = Connection.query();
|
const allConnections = Connection.query();
|
||||||
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
|
const connectionBaseQuery = conditions.isCreator ? userConnections : allConnections;
|
||||||
|
|
||||||
@@ -25,8 +25,9 @@ const getConnectedApps = async (
|
|||||||
|
|
||||||
let apps = await App.findAll(params.name);
|
let apps = await App.findAll(params.name);
|
||||||
|
|
||||||
const connections = await connectionBaseQuery
|
const connections = await Connection
|
||||||
.clone()
|
.query()
|
||||||
|
.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')
|
||||||
|
@@ -13,15 +13,15 @@ const testConnection = async (
|
|||||||
params: Params,
|
params: Params,
|
||||||
context: Context
|
context: Context
|
||||||
) => {
|
) => {
|
||||||
const conditions = context.currentUser.can('update', 'Connection');
|
const conditions = context.currentUser.can('read', 'Connection');
|
||||||
const userConnections = context.currentUser.$relatedQuery('connections');
|
const userConnections = context.currentUser.relatedConnectionsQuery();
|
||||||
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({
|
||||||
id: params.id,
|
'connections.id': params.id,
|
||||||
})
|
})
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
|
@@ -244,6 +244,7 @@ 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
|
||||||
|
@@ -1,18 +1,18 @@
|
|||||||
import { QueryContext, ModelOptions } from 'objection';
|
import { IJSONObject, IRequest } from '@automatisch/types';
|
||||||
import type { RelationMappings } from 'objection';
|
|
||||||
import { AES, enc } from 'crypto-js';
|
import { AES, enc } from 'crypto-js';
|
||||||
import { IRequest } from '@automatisch/types';
|
import type { RelationMappings } from 'objection';
|
||||||
import App from './app';
|
import { ModelOptions, QueryContext } from 'objection';
|
||||||
import AppConfig from './app-config';
|
|
||||||
import AppAuthClient from './app-auth-client';
|
|
||||||
import Base from './base';
|
|
||||||
import User from './user';
|
|
||||||
import Step from './step';
|
|
||||||
import ExtendedQueryBuilder from './query-builder';
|
|
||||||
import appConfig from '../config/app';
|
import appConfig from '../config/app';
|
||||||
import { IJSONObject } from '@automatisch/types';
|
|
||||||
import Telemetry from '../helpers/telemetry';
|
|
||||||
import globalVariable from '../helpers/global-variable';
|
import globalVariable from '../helpers/global-variable';
|
||||||
|
import Telemetry from '../helpers/telemetry';
|
||||||
|
import App from './app';
|
||||||
|
import AppAuthClient from './app-auth-client';
|
||||||
|
import AppConfig from './app-config';
|
||||||
|
import Base from './base';
|
||||||
|
import ExtendedQueryBuilder from './query-builder';
|
||||||
|
import SharedConnection from './shared-connection';
|
||||||
|
import Step from './step';
|
||||||
|
import User from './user';
|
||||||
|
|
||||||
class Connection extends Base {
|
class Connection extends Base {
|
||||||
id!: string;
|
id!: string;
|
||||||
@@ -24,6 +24,9 @@ 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[];
|
||||||
@@ -46,6 +49,7 @@ 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' },
|
||||||
@@ -100,6 +104,14 @@ 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() {
|
||||||
|
45
packages/backend/src/models/shared-connection.ts
Normal file
45
packages/backend/src/models/shared-connection.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
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: ['name', 'key'],
|
||||||
|
|
||||||
|
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 { ModelOptions, QueryContext } from 'objection';
|
import { raw, 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,6 +28,7 @@ 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[];
|
||||||
@@ -69,6 +70,18 @@ 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,
|
||||||
@@ -165,6 +178,40 @@ 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);
|
||||||
}
|
}
|
||||||
|
1
packages/types/index.d.ts
vendored
1
packages/types/index.d.ts
vendored
@@ -23,6 +23,7 @@ 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;
|
||||||
|
@@ -53,6 +53,7 @@ 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);
|
||||||
@@ -105,7 +106,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}
|
{formattedData?.screenName} {shared && 'shared'}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography variant="caption">
|
<Typography variant="caption">
|
||||||
|
@@ -7,6 +7,7 @@ export const GET_APP_CONNECTIONS = gql`
|
|||||||
connections {
|
connections {
|
||||||
id
|
id
|
||||||
key
|
key
|
||||||
|
shared
|
||||||
reconnectable
|
reconnectable
|
||||||
appAuthClientId
|
appAuthClientId
|
||||||
verified
|
verified
|
||||||
|
35
yarn.lock
35
yarn.lock
@@ -7106,6 +7106,11 @@ 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"
|
||||||
@@ -7121,7 +7126,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.1.0:
|
commander@^9.0.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==
|
||||||
@@ -12261,13 +12266,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.4.0:
|
knex@^2.5.1:
|
||||||
version "2.4.0"
|
version "2.5.1"
|
||||||
resolved "https://registry.npmjs.org/knex/-/knex-2.4.0.tgz"
|
resolved "https://registry.yarnpkg.com/knex/-/knex-2.5.1.tgz#a6c6b449866cf4229f070c17411f23871ba52ef9"
|
||||||
integrity sha512-i0GWwqYp1Hs2yvc2rlDO6nzzkLhwdyOZKRdsMTB8ZxOs2IXQyL5rBjSbS1krowCh6V65T4X9CJaKtuIfkaPGSA==
|
integrity sha512-z78DgGKUr4SE/6cm7ku+jHvFT0X97aERh/f0MUKAKgFnwCYBEW4TFBqtHWFYiJFid7fMrtpZ/gxJthvz5mEByA==
|
||||||
dependencies:
|
dependencies:
|
||||||
colorette "2.0.19"
|
colorette "2.0.19"
|
||||||
commander "^9.1.0"
|
commander "^10.0.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"
|
||||||
@@ -12275,7 +12280,7 @@ knex@^2.4.0:
|
|||||||
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.5.0"
|
pg-connection-string "2.6.1"
|
||||||
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"
|
||||||
@@ -13859,12 +13864,13 @@ 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.0.0:
|
objection@^3.1.1:
|
||||||
version "3.0.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.npmjs.org/objection/-/objection-3.0.1.tgz"
|
resolved "https://registry.yarnpkg.com/objection/-/objection-3.1.1.tgz#b744d4ff13c01863d6edec773f1315c964442510"
|
||||||
integrity sha512-rqNnyQE+C55UHjdpTOJEKQHJGZ/BGtBBtgxdUpKG4DQXRUmqxfmgS/MhPWxB9Pw0mLSVLEltr6soD4c0Sddy0Q==
|
integrity sha512-v8dqQrFwZm9gRN3ZF4abF+hL6Jm5EbcUjOxVDan0lheOev0sggGGHBP8jgesZ68I0XXBjDFjGXCjTPZsWDu49A==
|
||||||
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:
|
||||||
@@ -14385,7 +14391,12 @@ 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.5.0, pg-connection-string@^2.5.0:
|
pg-connection-string@2.6.1:
|
||||||
|
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