feat: add shared connection capability

This commit is contained in:
Ali BARIN
2023-09-08 11:32:22 +00:00
parent aefff5c861
commit 099a8ea2cf
19 changed files with 315 additions and 103 deletions

View File

@@ -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",

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,
}) })

View File

@@ -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,
}) })

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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() {

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

View File

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

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ export const GET_APP_CONNECTIONS = gql`
connections { connections {
id id
key key
shared
reconnectable reconnectable
appAuthClientId appAuthClientId
verified verified

View File

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