mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
refactor: Use string ids on Credentials, Workflows, Tags, and Executions DB entities (#5041)
This commit is contained in:
parent
8bee04cd2a
commit
ee28213538
|
@ -55,11 +55,9 @@ export class ActiveExecutions {
|
||||||
fullExecutionData.retryOf = executionData.retryOf.toString();
|
fullExecutionData.retryOf = executionData.retryOf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
const workflowId = executionData.workflowData.id;
|
||||||
executionData.workflowData.id !== undefined &&
|
if (workflowId !== undefined && WorkflowHelpers.isWorkflowIdValid(workflowId)) {
|
||||||
WorkflowHelpers.isWorkflowIdValid(executionData.workflowData.id.toString())
|
fullExecutionData.workflowId = workflowId;
|
||||||
) {
|
|
||||||
fullExecutionData.workflowId = executionData.workflowData.id.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const execution = ResponseHelper.flattenExecutionData(fullExecutionData);
|
const execution = ResponseHelper.flattenExecutionData(fullExecutionData);
|
||||||
|
|
|
@ -42,7 +42,6 @@ import {
|
||||||
IActivationError,
|
IActivationError,
|
||||||
IQueuedWorkflowActivations,
|
IQueuedWorkflowActivations,
|
||||||
IResponseCallbackData,
|
IResponseCallbackData,
|
||||||
IWebhookDb,
|
|
||||||
IWorkflowDb,
|
IWorkflowDb,
|
||||||
IWorkflowExecutionDataProcess,
|
IWorkflowExecutionDataProcess,
|
||||||
} from '@/Interfaces';
|
} from '@/Interfaces';
|
||||||
|
@ -53,7 +52,8 @@ import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData'
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { User } from '@db/entities/User';
|
import { User } from '@db/entities/User';
|
||||||
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
|
import type { WebhookEntity } from '@db/entities/WebhookEntity';
|
||||||
import * as ActiveExecutions from '@/ActiveExecutions';
|
import * as ActiveExecutions from '@/ActiveExecutions';
|
||||||
import { createErrorExecution } from '@/GenericHelpers';
|
import { createErrorExecution } from '@/GenericHelpers';
|
||||||
import { WORKFLOW_REACTIVATE_INITIAL_TIMEOUT, WORKFLOW_REACTIVATE_MAX_TIMEOUT } from '@/constants';
|
import { WORKFLOW_REACTIVATE_INITIAL_TIMEOUT, WORKFLOW_REACTIVATE_MAX_TIMEOUT } from '@/constants';
|
||||||
|
@ -114,7 +114,7 @@ export class ActiveWorkflowRunner {
|
||||||
workflowId: workflowData.id,
|
workflowId: workflowData.id,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await this.add(workflowData.id.toString(), 'init', workflowData);
|
await this.add(workflowData.id, 'init', workflowData);
|
||||||
Logger.verbose(`Successfully started workflow "${workflowData.name}"`, {
|
Logger.verbose(`Successfully started workflow "${workflowData.name}"`, {
|
||||||
workflowName: workflowData.name,
|
workflowName: workflowData.name,
|
||||||
workflowId: workflowData.id,
|
workflowId: workflowData.id,
|
||||||
|
@ -165,10 +165,7 @@ export class ActiveWorkflowRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeWorkflows = await this.getActiveWorkflows();
|
const activeWorkflows = await this.getActiveWorkflows();
|
||||||
activeWorkflowIds = [
|
activeWorkflowIds = [...activeWorkflowIds, ...activeWorkflows.map((workflow) => workflow.id)];
|
||||||
...activeWorkflowIds,
|
|
||||||
...activeWorkflows.map((workflow) => workflow.id.toString()),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Make sure IDs are unique
|
// Make sure IDs are unique
|
||||||
activeWorkflowIds = Array.from(new Set(activeWorkflowIds));
|
activeWorkflowIds = Array.from(new Set(activeWorkflowIds));
|
||||||
|
@ -206,10 +203,10 @@ export class ActiveWorkflowRunner {
|
||||||
path = path.slice(0, -1);
|
path = path.slice(0, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let webhook = (await Db.collections.Webhook.findOne({
|
let webhook = await Db.collections.Webhook.findOne({
|
||||||
webhookPath: path,
|
webhookPath: path,
|
||||||
method: httpMethod,
|
method: httpMethod,
|
||||||
})) as IWebhookDb;
|
});
|
||||||
let webhookId: string | undefined;
|
let webhookId: string | undefined;
|
||||||
|
|
||||||
// check if path is dynamic
|
// check if path is dynamic
|
||||||
|
@ -280,7 +277,7 @@ export class ActiveWorkflowRunner {
|
||||||
|
|
||||||
const nodeTypes = NodeTypes();
|
const nodeTypes = NodeTypes();
|
||||||
const workflow = new Workflow({
|
const workflow = new Workflow({
|
||||||
id: webhook.workflowId.toString(),
|
id: webhook.workflowId,
|
||||||
name: workflowData.name,
|
name: workflowData.name,
|
||||||
nodes: workflowData.nodes,
|
nodes: workflowData.nodes,
|
||||||
connections: workflowData.connections,
|
connections: workflowData.connections,
|
||||||
|
@ -419,12 +416,12 @@ export class ActiveWorkflowRunner {
|
||||||
|
|
||||||
path = webhookData.path;
|
path = webhookData.path;
|
||||||
|
|
||||||
const webhook = {
|
const webhook: WebhookEntity = {
|
||||||
workflowId: webhookData.workflowId,
|
workflowId: webhookData.workflowId,
|
||||||
webhookPath: path,
|
webhookPath: path,
|
||||||
node: node.name,
|
node: node.name,
|
||||||
method: webhookData.httpMethod,
|
method: webhookData.httpMethod,
|
||||||
} as IWebhookDb;
|
};
|
||||||
|
|
||||||
if (webhook.webhookPath.startsWith('/')) {
|
if (webhook.webhookPath.startsWith('/')) {
|
||||||
webhook.webhookPath = webhook.webhookPath.slice(1);
|
webhook.webhookPath = webhook.webhookPath.slice(1);
|
||||||
|
@ -549,11 +546,9 @@ export class ActiveWorkflowRunner {
|
||||||
|
|
||||||
await WorkflowHelpers.saveStaticData(workflow);
|
await WorkflowHelpers.saveStaticData(workflow);
|
||||||
|
|
||||||
const webhook = {
|
await Db.collections.Webhook.delete({
|
||||||
workflowId: workflowData.id,
|
workflowId: workflowData.id,
|
||||||
} as IWebhookDb;
|
});
|
||||||
|
|
||||||
await Db.collections.Webhook.delete(webhook);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -713,15 +708,15 @@ export class ActiveWorkflowRunner {
|
||||||
`The trigger node "${node.name}" of workflow "${workflowData.name}" failed with the error: "${error.message}". Will try to reactivate.`,
|
`The trigger node "${node.name}" of workflow "${workflowData.name}" failed with the error: "${error.message}". Will try to reactivate.`,
|
||||||
{
|
{
|
||||||
nodeName: node.name,
|
nodeName: node.name,
|
||||||
workflowId: workflowData.id.toString(),
|
workflowId: workflowData.id,
|
||||||
workflowName: workflowData.name,
|
workflowName: workflowData.name,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Remove the workflow as "active"
|
// Remove the workflow as "active"
|
||||||
|
|
||||||
await this.activeWorkflows?.remove(workflowData.id.toString());
|
await this.activeWorkflows?.remove(workflowData.id);
|
||||||
this.activationErrors[workflowData.id.toString()] = {
|
this.activationErrors[workflowData.id] = {
|
||||||
time: new Date().getTime(),
|
time: new Date().getTime(),
|
||||||
error: {
|
error: {
|
||||||
message: error.message,
|
message: error.message,
|
||||||
|
@ -897,7 +892,7 @@ export class ActiveWorkflowRunner {
|
||||||
activationMode: WorkflowActivateMode,
|
activationMode: WorkflowActivateMode,
|
||||||
workflowData: IWorkflowDb,
|
workflowData: IWorkflowDb,
|
||||||
): void {
|
): void {
|
||||||
const workflowId = workflowData.id.toString();
|
const workflowId = workflowData.id;
|
||||||
const workflowName = workflowData.name;
|
const workflowName = workflowData.name;
|
||||||
|
|
||||||
const retryFunction = async () => {
|
const retryFunction = async () => {
|
||||||
|
|
|
@ -279,7 +279,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||||
const credential = userId
|
const credential = userId
|
||||||
? await Db.collections.SharedCredentials.findOneOrFail({
|
? await Db.collections.SharedCredentials.findOneOrFail({
|
||||||
relations: ['credentials'],
|
relations: ['credentials'],
|
||||||
where: { credentials: { id: nodeCredential.id, type }, user: { id: userId } },
|
where: { credentials: { id: nodeCredential.id, type }, userId },
|
||||||
}).then((shared) => shared.credentials)
|
}).then((shared) => shared.credentials)
|
||||||
: await Db.collections.Credentials.findOneOrFail({ id: nodeCredential.id, type });
|
: await Db.collections.Credentials.findOneOrFail({ id: nodeCredential.id, type });
|
||||||
|
|
||||||
|
@ -290,7 +290,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Credentials(
|
return new Credentials(
|
||||||
{ id: credential.id.toString(), name: credential.name },
|
{ id: credential.id, name: credential.name },
|
||||||
credential.type,
|
credential.type,
|
||||||
credential.nodesAccess,
|
credential.nodesAccess,
|
||||||
credential.data,
|
credential.data,
|
||||||
|
@ -581,7 +581,7 @@ export class CredentialsHelper extends ICredentialsHelper {
|
||||||
position: [0, 0],
|
position: [0, 0],
|
||||||
credentials: {
|
credentials: {
|
||||||
[credentialType]: {
|
[credentialType]: {
|
||||||
id: credentialsDecrypted.id.toString(),
|
id: credentialsDecrypted.id,
|
||||||
name: credentialsDecrypted.name,
|
name: credentialsDecrypted.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -762,8 +762,7 @@ export async function getCredentialForUser(
|
||||||
export async function getCredentialWithoutUser(
|
export async function getCredentialWithoutUser(
|
||||||
credentialId: string,
|
credentialId: string,
|
||||||
): Promise<ICredentialsDb | undefined> {
|
): Promise<ICredentialsDb | undefined> {
|
||||||
const credential = await Db.collections.Credentials.findOne(credentialId);
|
return Db.collections.Credentials.findOne(credentialId);
|
||||||
return credential;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createCredentialsFromCredentialsEntity(
|
export function createCredentialsFromCredentialsEntity(
|
||||||
|
@ -774,5 +773,5 @@ export function createCredentialsFromCredentialsEntity(
|
||||||
if (encrypt) {
|
if (encrypt) {
|
||||||
return new Credentials({ id: null, name }, type, nodesAccess);
|
return new Credentials({ id: null, name }, type, nodesAccess);
|
||||||
}
|
}
|
||||||
return new Credentials({ id: id.toString(), name }, type, nodesAccess, data);
|
return new Credentials({ id, name }, type, nodesAccess, data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import type {
|
||||||
ITaskData,
|
ITaskData,
|
||||||
ITelemetrySettings,
|
ITelemetrySettings,
|
||||||
ITelemetryTrackProperties,
|
ITelemetryTrackProperties,
|
||||||
IWorkflowBase as IWorkflowBaseWorkflow,
|
IWorkflowBase,
|
||||||
CredentialLoadingDetails,
|
CredentialLoadingDetails,
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowActivateMode,
|
WorkflowActivateMode,
|
||||||
|
@ -38,6 +38,7 @@ import type { SharedCredentials } from '@db/entities/SharedCredentials';
|
||||||
import type { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
import type { SharedWorkflow } from '@db/entities/SharedWorkflow';
|
||||||
import type { TagEntity } from '@db/entities/TagEntity';
|
import type { TagEntity } from '@db/entities/TagEntity';
|
||||||
import type { User } from '@db/entities/User';
|
import type { User } from '@db/entities/User';
|
||||||
|
import type { WebhookEntity } from '@db/entities/WebhookEntity';
|
||||||
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
|
||||||
import type { WorkflowStatistics } from '@db/entities/WorkflowStatistics';
|
import type { WorkflowStatistics } from '@db/entities/WorkflowStatistics';
|
||||||
|
|
||||||
|
@ -71,7 +72,7 @@ export interface IDatabaseCollections {
|
||||||
Credentials: Repository<ICredentialsDb>;
|
Credentials: Repository<ICredentialsDb>;
|
||||||
Execution: Repository<IExecutionFlattedDb>;
|
Execution: Repository<IExecutionFlattedDb>;
|
||||||
Workflow: Repository<WorkflowEntity>;
|
Workflow: Repository<WorkflowEntity>;
|
||||||
Webhook: Repository<IWebhookDb>;
|
Webhook: Repository<WebhookEntity>;
|
||||||
Tag: Repository<TagEntity>;
|
Tag: Repository<TagEntity>;
|
||||||
Role: Repository<Role>;
|
Role: Repository<Role>;
|
||||||
User: Repository<User>;
|
User: Repository<User>;
|
||||||
|
@ -83,28 +84,12 @@ export interface IDatabaseCollections {
|
||||||
WorkflowStatistics: Repository<WorkflowStatistics>;
|
WorkflowStatistics: Repository<WorkflowStatistics>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWebhookDb {
|
|
||||||
workflowId: number | string;
|
|
||||||
webhookPath: string;
|
|
||||||
method: string;
|
|
||||||
node: string;
|
|
||||||
webhookId?: string;
|
|
||||||
pathLength?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// tags
|
// tags
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
export interface ITagDb {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ITagToImport {
|
export interface ITagToImport {
|
||||||
id: string | number;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
updatedAt?: string;
|
updatedAt?: string;
|
||||||
|
@ -114,20 +99,16 @@ export type UsageCount = {
|
||||||
usageCount: number;
|
usageCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ITagWithCountDb = ITagDb & UsageCount;
|
export type ITagWithCountDb = TagEntity & UsageCount;
|
||||||
|
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
// workflows
|
// workflows
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
export interface IWorkflowBase extends IWorkflowBaseWorkflow {
|
|
||||||
id?: number | string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Almost identical to editor-ui.Interfaces.ts
|
// Almost identical to editor-ui.Interfaces.ts
|
||||||
export interface IWorkflowDb extends IWorkflowBase {
|
export interface IWorkflowDb extends IWorkflowBase {
|
||||||
id: number | string;
|
id: string;
|
||||||
tags?: ITagDb[];
|
tags?: TagEntity[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkflowToImport extends IWorkflowBase {
|
export interface IWorkflowToImport extends IWorkflowBase {
|
||||||
|
@ -148,35 +129,27 @@ export interface ICredentialsBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICredentialsDb extends ICredentialsBase, ICredentialsEncrypted {
|
export interface ICredentialsDb extends ICredentialsBase, ICredentialsEncrypted {
|
||||||
id: number | string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
shared?: SharedCredentials[];
|
shared?: SharedCredentials[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICredentialsResponse extends ICredentialsDb {
|
export type ICredentialsDecryptedDb = ICredentialsBase & ICredentialsDecrypted;
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICredentialsDecryptedDb extends ICredentialsBase, ICredentialsDecrypted {
|
export type ICredentialsDecryptedResponse = ICredentialsDecryptedDb;
|
||||||
id: number | string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICredentialsDecryptedResponse extends ICredentialsDecryptedDb {
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DatabaseType = 'mariadb' | 'postgresdb' | 'mysqldb' | 'sqlite';
|
export type DatabaseType = 'mariadb' | 'postgresdb' | 'mysqldb' | 'sqlite';
|
||||||
export type SaveExecutionDataType = 'all' | 'none';
|
export type SaveExecutionDataType = 'all' | 'none';
|
||||||
|
|
||||||
export interface IExecutionBase {
|
export interface IExecutionBase {
|
||||||
id?: number | string;
|
id?: string;
|
||||||
mode: WorkflowExecuteMode;
|
mode: WorkflowExecuteMode;
|
||||||
startedAt: Date;
|
startedAt: Date;
|
||||||
stoppedAt?: Date; // empty value means execution is still running
|
stoppedAt?: Date; // empty value means execution is still running
|
||||||
workflowId?: string; // To be able to filter executions easily //
|
workflowId?: string; // To be able to filter executions easily //
|
||||||
finished: boolean;
|
finished: boolean;
|
||||||
retryOf?: number | string; // If it is a retry, the id of the execution it is a retry of.
|
retryOf?: string; // If it is a retry, the id of the execution it is a retry of.
|
||||||
retrySuccessId?: number | string; // If it failed and a retry did succeed. The id of the successful retry.
|
retrySuccessId?: string; // If it failed and a retry did succeed. The id of the successful retry.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Data in regular format with references
|
// Data in regular format with references
|
||||||
|
@ -208,7 +181,7 @@ export interface IExecutionFlatted extends IExecutionBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IExecutionFlattedDb extends IExecutionBase {
|
export interface IExecutionFlattedDb extends IExecutionBase {
|
||||||
id: number | string;
|
id: string;
|
||||||
data: string;
|
data: string;
|
||||||
waitTill?: Date | null;
|
waitTill?: Date | null;
|
||||||
workflowData: Omit<IWorkflowBase, 'pinData'>;
|
workflowData: Omit<IWorkflowBase, 'pinData'>;
|
||||||
|
@ -220,14 +193,14 @@ export interface IExecutionFlattedResponse extends IExecutionFlatted {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IExecutionResponseApi {
|
export interface IExecutionResponseApi {
|
||||||
id: number | string;
|
id: string;
|
||||||
mode: WorkflowExecuteMode;
|
mode: WorkflowExecuteMode;
|
||||||
startedAt: Date;
|
startedAt: Date;
|
||||||
stoppedAt?: Date;
|
stoppedAt?: Date;
|
||||||
workflowId?: string;
|
workflowId?: string;
|
||||||
finished: boolean;
|
finished: boolean;
|
||||||
retryOf?: number | string;
|
retryOf?: string;
|
||||||
retrySuccessId?: number | string;
|
retrySuccessId?: string;
|
||||||
data?: object;
|
data?: object;
|
||||||
waitTill?: Date | null;
|
waitTill?: Date | null;
|
||||||
workflowData: IWorkflowBase;
|
workflowData: IWorkflowBase;
|
||||||
|
@ -685,7 +658,7 @@ export interface IWorkflowExecutionDataProcess {
|
||||||
executionData?: IRunExecutionData;
|
executionData?: IRunExecutionData;
|
||||||
runData?: IRunData;
|
runData?: IRunData;
|
||||||
pinData?: IPinData;
|
pinData?: IPinData;
|
||||||
retryOf?: number | string;
|
retryOf?: string;
|
||||||
sessionId?: string;
|
sessionId?: string;
|
||||||
startNodes?: string[];
|
startNodes?: string[];
|
||||||
workflowData: IWorkflowBase;
|
workflowData: IWorkflowBase;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
INodeTypes,
|
INodeTypes,
|
||||||
IRun,
|
IRun,
|
||||||
ITelemetryTrackProperties,
|
ITelemetryTrackProperties,
|
||||||
|
IWorkflowBase,
|
||||||
TelemetryHelpers,
|
TelemetryHelpers,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { get as pslGet } from 'psl';
|
import { get as pslGet } from 'psl';
|
||||||
|
@ -12,7 +13,6 @@ import {
|
||||||
IDiagnosticInfo,
|
IDiagnosticInfo,
|
||||||
IInternalHooksClass,
|
IInternalHooksClass,
|
||||||
ITelemetryUserDeletionData,
|
ITelemetryUserDeletionData,
|
||||||
IWorkflowBase,
|
|
||||||
IWorkflowDb,
|
IWorkflowDb,
|
||||||
IExecutionTrackProperties,
|
IExecutionTrackProperties,
|
||||||
} from '@/Interfaces';
|
} from '@/Interfaces';
|
||||||
|
@ -114,7 +114,7 @@ export class InternalHooksClass implements IInternalHooksClass {
|
||||||
|
|
||||||
let userRole: 'owner' | 'sharee' | undefined = undefined;
|
let userRole: 'owner' | 'sharee' | undefined = undefined;
|
||||||
if (userId && workflow.id) {
|
if (userId && workflow.id) {
|
||||||
const role = await RoleService.getUserRoleForWorkflow(userId, workflow.id.toString());
|
const role = await RoleService.getUserRoleForWorkflow(userId, workflow.id);
|
||||||
if (role) {
|
if (role) {
|
||||||
userRole = role.name === 'owner' ? 'owner' : 'sharee';
|
userRole = role.name === 'owner' ? 'owner' : 'sharee';
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ export class InternalHooksClass implements IInternalHooksClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
const properties: IExecutionTrackProperties = {
|
const properties: IExecutionTrackProperties = {
|
||||||
workflow_id: workflow.id.toString(),
|
workflow_id: workflow.id,
|
||||||
is_manual: false,
|
is_manual: false,
|
||||||
version_cli: this.versionCli,
|
version_cli: this.versionCli,
|
||||||
success: false,
|
success: false,
|
||||||
|
@ -208,7 +208,7 @@ export class InternalHooksClass implements IInternalHooksClass {
|
||||||
|
|
||||||
let userRole: 'owner' | 'sharee' | undefined = undefined;
|
let userRole: 'owner' | 'sharee' | undefined = undefined;
|
||||||
if (userId) {
|
if (userId) {
|
||||||
const role = await RoleService.getUserRoleForWorkflow(userId, workflow.id.toString());
|
const role = await RoleService.getUserRoleForWorkflow(userId, workflow.id);
|
||||||
if (role) {
|
if (role) {
|
||||||
userRole = role.name === 'owner' ? 'owner' : 'sharee';
|
userRole = role.name === 'owner' ? 'owner' : 'sharee';
|
||||||
}
|
}
|
||||||
|
@ -216,7 +216,7 @@ export class InternalHooksClass implements IInternalHooksClass {
|
||||||
|
|
||||||
const manualExecEventProperties: ITelemetryTrackProperties = {
|
const manualExecEventProperties: ITelemetryTrackProperties = {
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
workflow_id: workflow.id.toString(),
|
workflow_id: workflow.id,
|
||||||
status: properties.success ? 'success' : 'failed',
|
status: properties.success ? 'success' : 'failed',
|
||||||
error_message: properties.error_message as string,
|
error_message: properties.error_message as string,
|
||||||
error_node_type: properties.error_node_type,
|
error_node_type: properties.error_node_type,
|
||||||
|
@ -512,14 +512,14 @@ export class InternalHooksClass implements IInternalHooksClass {
|
||||||
*/
|
*/
|
||||||
async onFirstProductionWorkflowSuccess(data: {
|
async onFirstProductionWorkflowSuccess(data: {
|
||||||
user_id: string;
|
user_id: string;
|
||||||
workflow_id: string | number;
|
workflow_id: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
return this.telemetry.track('Workflow first prod success', data, { withPostHog: true });
|
return this.telemetry.track('Workflow first prod success', data, { withPostHog: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
async onFirstWorkflowDataLoad(data: {
|
async onFirstWorkflowDataLoad(data: {
|
||||||
user_id: string;
|
user_id: string;
|
||||||
workflow_id: string | number;
|
workflow_id: string;
|
||||||
node_type: string;
|
node_type: string;
|
||||||
node_id: string;
|
node_id: string;
|
||||||
credential_type?: string;
|
credential_type?: string;
|
||||||
|
|
16
packages/cli/src/PublicApi/types.d.ts
vendored
16
packages/cli/src/PublicApi/types.d.ts
vendored
|
@ -37,7 +37,7 @@ export type PaginatatedRequest = AuthenticatedRequest<
|
||||||
limit?: number;
|
limit?: number;
|
||||||
cursor?: string;
|
cursor?: string;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
lastId?: number;
|
lastId?: string;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
export declare namespace ExecutionRequest {
|
export declare namespace ExecutionRequest {
|
||||||
|
@ -51,12 +51,12 @@ export declare namespace ExecutionRequest {
|
||||||
cursor?: string;
|
cursor?: string;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
includeData?: boolean;
|
includeData?: boolean;
|
||||||
workflowId?: number;
|
workflowId?: string;
|
||||||
lastId?: number;
|
lastId?: string;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type Get = AuthenticatedRequest<{ id: number }, {}, {}, { includeData?: boolean }>;
|
type Get = AuthenticatedRequest<{ id: string }, {}, {}, { includeData?: boolean }>;
|
||||||
type Delete = Get;
|
type Delete = Get;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,9 +81,9 @@ export declare namespace WorkflowRequest {
|
||||||
>;
|
>;
|
||||||
|
|
||||||
type Create = AuthenticatedRequest<{}, {}, WorkflowEntity, {}>;
|
type Create = AuthenticatedRequest<{}, {}, WorkflowEntity, {}>;
|
||||||
type Get = AuthenticatedRequest<{ id: number }, {}, {}, {}>;
|
type Get = AuthenticatedRequest<{ id: string }, {}, {}, {}>;
|
||||||
type Delete = Get;
|
type Delete = Get;
|
||||||
type Update = AuthenticatedRequest<{ id: number }, {}, WorkflowEntity, {}>;
|
type Update = AuthenticatedRequest<{ id: string }, {}, WorkflowEntity, {}>;
|
||||||
type Activate = Get;
|
type Activate = Get;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,11 +140,11 @@ type PaginationBase = { limit: number };
|
||||||
|
|
||||||
type PaginationOffsetDecoded = PaginationBase & { offset: number };
|
type PaginationOffsetDecoded = PaginationBase & { offset: number };
|
||||||
|
|
||||||
type PaginationCursorDecoded = PaginationBase & { lastId: number };
|
type PaginationCursorDecoded = PaginationBase & { lastId: string };
|
||||||
|
|
||||||
type OffsetPagination = PaginationBase & { offset: number; numberOfTotalRecords: number };
|
type OffsetPagination = PaginationBase & { offset: number; numberOfTotalRecords: number };
|
||||||
|
|
||||||
type CursorPagination = PaginationBase & { lastId: number; numberOfNextRecords: number };
|
type CursorPagination = PaginationBase & { lastId: string; numberOfNextRecords: number };
|
||||||
export interface IRequired {
|
export interface IRequired {
|
||||||
required?: string[];
|
required?: string[];
|
||||||
not?: { required?: string[] };
|
not?: { required?: string[] };
|
||||||
|
|
|
@ -39,7 +39,7 @@ export = {
|
||||||
const savedCredential = await saveCredential(newCredential, req.user, encryptedData);
|
const savedCredential = await saveCredential(newCredential, req.user, encryptedData);
|
||||||
|
|
||||||
// LoggerProxy.verbose('New credential created', {
|
// LoggerProxy.verbose('New credential created', {
|
||||||
// credentialId: newCredential.id,
|
// credentialsId: newCredential.id,
|
||||||
// ownerId: req.user.id,
|
// ownerId: req.user.id,
|
||||||
// });
|
// });
|
||||||
|
|
||||||
|
@ -77,8 +77,6 @@ export = {
|
||||||
}
|
}
|
||||||
|
|
||||||
await removeCredential(credential);
|
await removeCredential(credential);
|
||||||
credential.id = Number(credentialId);
|
|
||||||
|
|
||||||
return res.json(sanitizeCredentials(credential));
|
return res.json(sanitizeCredentials(credential));
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
/* eslint-disable no-restricted-syntax */
|
import type { FindConditions } from 'typeorm';
|
||||||
/* eslint-disable no-underscore-dangle */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
||||||
import { FindOneOptions } from 'typeorm';
|
|
||||||
import { UserSettings, Credentials } from 'n8n-core';
|
import { UserSettings, Credentials } from 'n8n-core';
|
||||||
import { IDataObject, INodeProperties, INodePropertyOptions } from 'n8n-workflow';
|
import { IDataObject, INodeProperties, INodePropertyOptions } from 'n8n-workflow';
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
|
@ -14,29 +10,17 @@ import { ExternalHooks } from '@/ExternalHooks';
|
||||||
import { IDependency, IJsonSchema } from '../../../types';
|
import { IDependency, IJsonSchema } from '../../../types';
|
||||||
import { CredentialRequest } from '@/requests';
|
import { CredentialRequest } from '@/requests';
|
||||||
|
|
||||||
export async function getCredentials(
|
export async function getCredentials(credentialId: string): Promise<ICredentialsDb | undefined> {
|
||||||
credentialId: number | string,
|
|
||||||
): Promise<ICredentialsDb | undefined> {
|
|
||||||
return Db.collections.Credentials.findOne(credentialId);
|
return Db.collections.Credentials.findOne(credentialId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSharedCredentials(
|
export async function getSharedCredentials(
|
||||||
userId: string,
|
userId: string,
|
||||||
credentialId: number | string,
|
credentialId: string,
|
||||||
relations?: string[],
|
relations?: string[],
|
||||||
): Promise<SharedCredentials | undefined> {
|
): Promise<SharedCredentials | undefined> {
|
||||||
const options: FindOneOptions = {
|
const where: FindConditions<SharedCredentials> = { userId, credentialsId: credentialId };
|
||||||
where: {
|
return Db.collections.SharedCredentials.findOne({ where, relations });
|
||||||
user: { id: userId },
|
|
||||||
credentials: { id: credentialId },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (relations) {
|
|
||||||
options.relations = relations;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Db.collections.SharedCredentials.findOne(options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createCredential(
|
export async function createCredential(
|
||||||
|
|
|
@ -12,7 +12,7 @@ delete:
|
||||||
description: The credential ID that needs to be deleted
|
description: The credential ID that needs to be deleted
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: number
|
type: string
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Operation successful.
|
description: Operation successful.
|
||||||
|
|
|
@ -5,9 +5,9 @@ required:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: number
|
type: string
|
||||||
readOnly: true
|
readOnly: true
|
||||||
example: 42
|
example: '42'
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
example: Joe's Github Credentials
|
example: Joe's Github Credentials
|
||||||
|
|
|
@ -36,7 +36,7 @@ export = {
|
||||||
return res.status(404).json({ message: 'Not Found' });
|
return res.status(404).json({ message: 'Not Found' });
|
||||||
}
|
}
|
||||||
|
|
||||||
await BinaryDataManager.getInstance().deleteBinaryDataByExecutionId(execution.id.toString());
|
await BinaryDataManager.getInstance().deleteBinaryDataByExecutionId(execution.id);
|
||||||
|
|
||||||
await deleteExecution(execution);
|
await deleteExecution(execution);
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ export = {
|
||||||
// get running workflows so we exclude them from the result
|
// get running workflows so we exclude them from the result
|
||||||
const runningExecutionsIds = ActiveExecutions.getInstance()
|
const runningExecutionsIds = ActiveExecutions.getInstance()
|
||||||
.getActiveExecutions()
|
.getActiveExecutions()
|
||||||
.map(({ id }) => Number(id));
|
.map(({ id }) => id);
|
||||||
|
|
||||||
const filters = {
|
const filters = {
|
||||||
status,
|
status,
|
||||||
|
@ -110,7 +110,7 @@ export = {
|
||||||
|
|
||||||
const executions = await getExecutions(filters);
|
const executions = await getExecutions(filters);
|
||||||
|
|
||||||
const newLastId = !executions.length ? 0 : (executions.slice(-1)[0].id as number);
|
const newLastId = !executions.length ? '0' : executions.slice(-1)[0].id;
|
||||||
|
|
||||||
filters.lastId = newLastId;
|
filters.lastId = newLastId;
|
||||||
|
|
||||||
|
|
|
@ -60,14 +60,14 @@ function getExecutionSelectableProperties(includeData?: boolean): Array<keyof IE
|
||||||
export async function getExecutions(params: {
|
export async function getExecutions(params: {
|
||||||
limit: number;
|
limit: number;
|
||||||
includeData?: boolean;
|
includeData?: boolean;
|
||||||
lastId?: number;
|
lastId?: string;
|
||||||
workflowIds?: number[];
|
workflowIds?: string[];
|
||||||
status?: ExecutionStatus;
|
status?: ExecutionStatus;
|
||||||
excludedExecutionsIds?: number[];
|
excludedExecutionsIds?: string[];
|
||||||
}): Promise<IExecutionResponseApi[]> {
|
}): Promise<IExecutionResponseApi[]> {
|
||||||
type WhereClause = Record<
|
type WhereClause = Record<
|
||||||
string,
|
string,
|
||||||
string | boolean | FindOperator<number | Partial<ExecutionEntity>>
|
string | boolean | FindOperator<string | Partial<ExecutionEntity>>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
let where: WhereClause = {};
|
let where: WhereClause = {};
|
||||||
|
@ -103,10 +103,10 @@ export async function getExecutions(params: {
|
||||||
|
|
||||||
export async function getExecutionsCount(data: {
|
export async function getExecutionsCount(data: {
|
||||||
limit: number;
|
limit: number;
|
||||||
lastId?: number;
|
lastId?: string;
|
||||||
workflowIds?: number[];
|
workflowIds?: string[];
|
||||||
status?: ExecutionStatus;
|
status?: ExecutionStatus;
|
||||||
excludedWorkflowIds?: number[];
|
excludedWorkflowIds?: string[];
|
||||||
}): Promise<number> {
|
}): Promise<number> {
|
||||||
const executions = await Db.collections.Execution.count({
|
const executions = await Db.collections.Execution.count({
|
||||||
where: {
|
where: {
|
||||||
|
@ -122,15 +122,15 @@ export async function getExecutionsCount(data: {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getExecutionInWorkflows(
|
export async function getExecutionInWorkflows(
|
||||||
id: number,
|
id: string,
|
||||||
workflows: string[],
|
workflowIds: string[],
|
||||||
includeData?: boolean,
|
includeData?: boolean,
|
||||||
): Promise<IExecutionResponseApi | undefined> {
|
): Promise<IExecutionResponseApi | undefined> {
|
||||||
const execution = await Db.collections.Execution.findOne({
|
const execution = await Db.collections.Execution.findOne({
|
||||||
select: getExecutionSelectableProperties(includeData),
|
select: getExecutionSelectableProperties(includeData),
|
||||||
where: {
|
where: {
|
||||||
id,
|
id,
|
||||||
workflowId: In(workflows),
|
workflowId: In(workflowIds),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,8 @@ get:
|
||||||
description: Workflow to filter the executions by.
|
description: Workflow to filter the executions by.
|
||||||
required: false
|
required: false
|
||||||
schema:
|
schema:
|
||||||
type: number
|
type: string
|
||||||
example: 1000
|
example: '1000'
|
||||||
- $ref: '../../../../shared/spec/parameters/limit.yml'
|
- $ref: '../../../../shared/spec/parameters/limit.yml'
|
||||||
- $ref: '../../../../shared/spec/parameters/cursor.yml'
|
- $ref: '../../../../shared/spec/parameters/cursor.yml'
|
||||||
responses:
|
responses:
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: number
|
type: string
|
||||||
example: 1000
|
example: '1000'
|
||||||
data:
|
data:
|
||||||
type: object
|
type: object
|
||||||
finished:
|
finished:
|
||||||
|
@ -17,7 +17,7 @@ properties:
|
||||||
retrySuccessId:
|
retrySuccessId:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
example: 2
|
example: '2'
|
||||||
startedAt:
|
startedAt:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
@ -26,7 +26,7 @@ properties:
|
||||||
format: date-time
|
format: date-time
|
||||||
workflowId:
|
workflowId:
|
||||||
type: string
|
type: string
|
||||||
example: 1000
|
example: '1000'
|
||||||
waitTill:
|
waitTill:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
|
|
|
@ -3,4 +3,4 @@ in: path
|
||||||
description: The ID of the execution.
|
description: The ID of the execution.
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: number
|
type: string
|
||||||
|
|
|
@ -3,4 +3,4 @@ in: path
|
||||||
description: The ID of the workflow.
|
description: The ID of the workflow.
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: number
|
type: string
|
||||||
|
|
|
@ -2,7 +2,7 @@ type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
example: 12
|
example: '12'
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
example: Production
|
example: Production
|
||||||
|
|
|
@ -7,9 +7,9 @@ required:
|
||||||
- settings
|
- settings
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: number
|
type: string
|
||||||
readOnly: true
|
readOnly: true
|
||||||
example: 1
|
example: '1'
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
example: Workflow 1
|
example: Workflow 1
|
||||||
|
|
|
@ -17,7 +17,7 @@ properties:
|
||||||
maxLength: 3600
|
maxLength: 3600
|
||||||
errorWorkflow:
|
errorWorkflow:
|
||||||
type: string
|
type: string
|
||||||
example: 10
|
example: '10'
|
||||||
description: The ID of the workflow that contains the error trigger node.
|
description: The ID of the workflow that contains the error trigger node.
|
||||||
timezone:
|
timezone:
|
||||||
type: string
|
type: string
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
|
||||||
import { FindManyOptions, In, ObjectLiteral } from 'typeorm';
|
import { FindConditions, FindManyOptions, In } from 'typeorm';
|
||||||
|
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import * as ActiveWorkflowRunner from '@/ActiveWorkflowRunner';
|
import * as ActiveWorkflowRunner from '@/ActiveWorkflowRunner';
|
||||||
|
@ -60,7 +60,7 @@ export = {
|
||||||
async (req: WorkflowRequest.Get, res: express.Response): Promise<express.Response> => {
|
async (req: WorkflowRequest.Get, res: express.Response): Promise<express.Response> => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
const sharedWorkflow = await getSharedWorkflow(req.user, id.toString());
|
const sharedWorkflow = await getSharedWorkflow(req.user, id);
|
||||||
|
|
||||||
if (!sharedWorkflow) {
|
if (!sharedWorkflow) {
|
||||||
// user trying to access a workflow he does not own
|
// user trying to access a workflow he does not own
|
||||||
|
@ -70,13 +70,13 @@ export = {
|
||||||
|
|
||||||
if (sharedWorkflow.workflow.active) {
|
if (sharedWorkflow.workflow.active) {
|
||||||
// deactivate before deleting
|
// deactivate before deleting
|
||||||
await ActiveWorkflowRunner.getInstance().remove(id.toString());
|
await ActiveWorkflowRunner.getInstance().remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Db.collections.Workflow.delete(id);
|
await Db.collections.Workflow.delete(id);
|
||||||
|
|
||||||
void InternalHooksManager.getInstance().onWorkflowDeleted(req.user.id, id.toString(), true);
|
void InternalHooksManager.getInstance().onWorkflowDeleted(req.user.id, id, true);
|
||||||
await ExternalHooks().run('workflow.afterDelete', [id.toString()]);
|
await ExternalHooks().run('workflow.afterDelete', [id]);
|
||||||
|
|
||||||
return res.json(sharedWorkflow.workflow);
|
return res.json(sharedWorkflow.workflow);
|
||||||
},
|
},
|
||||||
|
@ -86,7 +86,7 @@ export = {
|
||||||
async (req: WorkflowRequest.Get, res: express.Response): Promise<express.Response> => {
|
async (req: WorkflowRequest.Get, res: express.Response): Promise<express.Response> => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
const sharedWorkflow = await getSharedWorkflow(req.user, id.toString());
|
const sharedWorkflow = await getSharedWorkflow(req.user, id);
|
||||||
|
|
||||||
if (!sharedWorkflow) {
|
if (!sharedWorkflow) {
|
||||||
// user trying to access a workflow he does not own
|
// user trying to access a workflow he does not own
|
||||||
|
@ -111,26 +111,27 @@ export = {
|
||||||
let workflows: WorkflowEntity[];
|
let workflows: WorkflowEntity[];
|
||||||
let count: number;
|
let count: number;
|
||||||
|
|
||||||
const query: FindManyOptions<WorkflowEntity> & { where: ObjectLiteral } = {
|
const where: FindConditions<WorkflowEntity> = {
|
||||||
|
...(active !== undefined && { active }),
|
||||||
|
};
|
||||||
|
const query: FindManyOptions<WorkflowEntity> = {
|
||||||
skip: offset,
|
skip: offset,
|
||||||
take: limit,
|
take: limit,
|
||||||
where: {
|
where,
|
||||||
...(active !== undefined && { active }),
|
|
||||||
},
|
|
||||||
...(!config.getEnv('workflowTagsDisabled') && { relations: ['tags'] }),
|
...(!config.getEnv('workflowTagsDisabled') && { relations: ['tags'] }),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isInstanceOwner(req.user)) {
|
if (isInstanceOwner(req.user)) {
|
||||||
if (tags) {
|
if (tags) {
|
||||||
const workflowIds = await getWorkflowIdsViaTags(parseTagNames(tags));
|
const workflowIds = await getWorkflowIdsViaTags(parseTagNames(tags));
|
||||||
Object.assign(query.where, { id: In(workflowIds) });
|
Object.assign(where, { id: In(workflowIds) });
|
||||||
}
|
}
|
||||||
|
|
||||||
workflows = await getWorkflows(query);
|
workflows = await getWorkflows(query);
|
||||||
|
|
||||||
count = await getWorkflowsCount(query);
|
count = await getWorkflowsCount(query);
|
||||||
} else {
|
} else {
|
||||||
const options: { workflowIds?: number[] } = {};
|
const options: { workflowIds?: string[] } = {};
|
||||||
|
|
||||||
if (tags) {
|
if (tags) {
|
||||||
options.workflowIds = await getWorkflowIdsViaTags(parseTagNames(tags));
|
options.workflowIds = await getWorkflowIdsViaTags(parseTagNames(tags));
|
||||||
|
@ -147,7 +148,7 @@ export = {
|
||||||
|
|
||||||
const workflowsIds = sharedWorkflows.map((shareWorkflow) => shareWorkflow.workflowId);
|
const workflowsIds = sharedWorkflows.map((shareWorkflow) => shareWorkflow.workflowId);
|
||||||
|
|
||||||
Object.assign(query.where, { id: In(workflowsIds) });
|
Object.assign(where, { id: In(workflowsIds) });
|
||||||
|
|
||||||
workflows = await getWorkflows(query);
|
workflows = await getWorkflows(query);
|
||||||
|
|
||||||
|
@ -176,7 +177,7 @@ export = {
|
||||||
const updateData = new WorkflowEntity();
|
const updateData = new WorkflowEntity();
|
||||||
Object.assign(updateData, req.body);
|
Object.assign(updateData, req.body);
|
||||||
|
|
||||||
const sharedWorkflow = await getSharedWorkflow(req.user, id.toString());
|
const sharedWorkflow = await getSharedWorkflow(req.user, id);
|
||||||
|
|
||||||
if (!sharedWorkflow) {
|
if (!sharedWorkflow) {
|
||||||
// user trying to access a workflow he does not own
|
// user trying to access a workflow he does not own
|
||||||
|
@ -196,7 +197,7 @@ export = {
|
||||||
if (sharedWorkflow.workflow.active) {
|
if (sharedWorkflow.workflow.active) {
|
||||||
// When workflow gets saved always remove it as the triggers could have been
|
// When workflow gets saved always remove it as the triggers could have been
|
||||||
// changed and so the changes would not take effect
|
// changed and so the changes would not take effect
|
||||||
await workflowRunner.remove(id.toString());
|
await workflowRunner.remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -209,7 +210,7 @@ export = {
|
||||||
|
|
||||||
if (sharedWorkflow.workflow.active) {
|
if (sharedWorkflow.workflow.active) {
|
||||||
try {
|
try {
|
||||||
await workflowRunner.add(sharedWorkflow.workflowId.toString(), 'update');
|
await workflowRunner.add(sharedWorkflow.workflowId, 'update');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
return res.status(400).json({ message: error.message });
|
return res.status(400).json({ message: error.message });
|
||||||
|
@ -230,7 +231,7 @@ export = {
|
||||||
async (req: WorkflowRequest.Activate, res: express.Response): Promise<express.Response> => {
|
async (req: WorkflowRequest.Activate, res: express.Response): Promise<express.Response> => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
const sharedWorkflow = await getSharedWorkflow(req.user, id.toString());
|
const sharedWorkflow = await getSharedWorkflow(req.user, id);
|
||||||
|
|
||||||
if (!sharedWorkflow) {
|
if (!sharedWorkflow) {
|
||||||
// user trying to access a workflow he does not own
|
// user trying to access a workflow he does not own
|
||||||
|
@ -240,10 +241,7 @@ export = {
|
||||||
|
|
||||||
if (!sharedWorkflow.workflow.active) {
|
if (!sharedWorkflow.workflow.active) {
|
||||||
try {
|
try {
|
||||||
await ActiveWorkflowRunner.getInstance().add(
|
await ActiveWorkflowRunner.getInstance().add(sharedWorkflow.workflowId, 'activate');
|
||||||
sharedWorkflow.workflowId.toString(),
|
|
||||||
'activate',
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
return res.status(400).json({ message: error.message });
|
return res.status(400).json({ message: error.message });
|
||||||
|
@ -267,7 +265,7 @@ export = {
|
||||||
async (req: WorkflowRequest.Activate, res: express.Response): Promise<express.Response> => {
|
async (req: WorkflowRequest.Activate, res: express.Response): Promise<express.Response> => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
const sharedWorkflow = await getSharedWorkflow(req.user, id.toString());
|
const sharedWorkflow = await getSharedWorkflow(req.user, id);
|
||||||
|
|
||||||
if (!sharedWorkflow) {
|
if (!sharedWorkflow) {
|
||||||
// user trying to access a workflow he does not own
|
// user trying to access a workflow he does not own
|
||||||
|
@ -278,7 +276,7 @@ export = {
|
||||||
const workflowRunner = ActiveWorkflowRunner.getInstance();
|
const workflowRunner = ActiveWorkflowRunner.getInstance();
|
||||||
|
|
||||||
if (sharedWorkflow.workflow.active) {
|
if (sharedWorkflow.workflow.active) {
|
||||||
await workflowRunner.remove(sharedWorkflow.workflowId.toString());
|
await workflowRunner.remove(sharedWorkflow.workflowId);
|
||||||
|
|
||||||
await setWorkflowAsInactive(sharedWorkflow.workflow);
|
await setWorkflowAsInactive(sharedWorkflow.workflow);
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ export async function getSharedWorkflowIds(user: User): Promise<string[]> {
|
||||||
where: { user },
|
where: { user },
|
||||||
});
|
});
|
||||||
|
|
||||||
return sharedWorkflows.map(({ workflowId }) => workflowId.toString());
|
return sharedWorkflows.map(({ workflowId }) => workflowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSharedWorkflow(
|
export async function getSharedWorkflow(
|
||||||
|
@ -30,7 +30,7 @@ export async function getSharedWorkflow(
|
||||||
return Db.collections.SharedWorkflow.findOne({
|
return Db.collections.SharedWorkflow.findOne({
|
||||||
where: {
|
where: {
|
||||||
...(!isInstanceOwner(user) && { user }),
|
...(!isInstanceOwner(user) && { user }),
|
||||||
...(workflowId && { workflow: { id: workflowId } }),
|
...(workflowId && { workflowId }),
|
||||||
},
|
},
|
||||||
relations: [...insertIf(!config.getEnv('workflowTagsDisabled'), ['workflow.tags']), 'workflow'],
|
relations: [...insertIf(!config.getEnv('workflowTagsDisabled'), ['workflow.tags']), 'workflow'],
|
||||||
});
|
});
|
||||||
|
@ -40,19 +40,19 @@ export async function getSharedWorkflows(
|
||||||
user: User,
|
user: User,
|
||||||
options: {
|
options: {
|
||||||
relations?: string[];
|
relations?: string[];
|
||||||
workflowIds?: number[];
|
workflowIds?: string[];
|
||||||
},
|
},
|
||||||
): Promise<SharedWorkflow[]> {
|
): Promise<SharedWorkflow[]> {
|
||||||
return Db.collections.SharedWorkflow.find({
|
return Db.collections.SharedWorkflow.find({
|
||||||
where: {
|
where: {
|
||||||
...(!isInstanceOwner(user) && { user }),
|
...(!isInstanceOwner(user) && { user }),
|
||||||
...(options.workflowIds && { workflow: { id: In(options.workflowIds) } }),
|
...(options.workflowIds && { workflowId: In(options.workflowIds) }),
|
||||||
},
|
},
|
||||||
...(options.relations && { relations: options.relations }),
|
...(options.relations && { relations: options.relations }),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getWorkflowById(id: number): Promise<WorkflowEntity | undefined> {
|
export async function getWorkflowById(id: string): Promise<WorkflowEntity | undefined> {
|
||||||
return Db.collections.Workflow.findOne({
|
return Db.collections.Workflow.findOne({
|
||||||
where: { id },
|
where: { id },
|
||||||
});
|
});
|
||||||
|
@ -62,7 +62,7 @@ export async function getWorkflowById(id: number): Promise<WorkflowEntity | unde
|
||||||
* Returns the workflow IDs that have certain tags.
|
* Returns the workflow IDs that have certain tags.
|
||||||
* Intersection! e.g. workflow needs to have all provided tags.
|
* Intersection! e.g. workflow needs to have all provided tags.
|
||||||
*/
|
*/
|
||||||
export async function getWorkflowIdsViaTags(tags: string[]): Promise<number[]> {
|
export async function getWorkflowIdsViaTags(tags: string[]): Promise<string[]> {
|
||||||
const dbTags = await Db.collections.Tag.find({
|
const dbTags = await Db.collections.Tag.find({
|
||||||
where: { name: In(tags) },
|
where: { name: In(tags) },
|
||||||
relations: ['workflows'],
|
relations: ['workflows'],
|
||||||
|
@ -118,7 +118,7 @@ export async function getWorkflowsCount(options: FindManyOptions<WorkflowEntity>
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateWorkflow(
|
export async function updateWorkflow(
|
||||||
workflowId: number,
|
workflowId: string,
|
||||||
updateData: WorkflowEntity,
|
updateData: WorkflowEntity,
|
||||||
): Promise<UpdateResult> {
|
): Promise<UpdateResult> {
|
||||||
return Db.collections.Workflow.update(workflowId, updateData);
|
return Db.collections.Workflow.update(workflowId, updateData);
|
||||||
|
|
|
@ -225,7 +225,7 @@ export function flattenExecutionData(fullExecutionData: IExecutionDb): IExecutio
|
||||||
};
|
};
|
||||||
|
|
||||||
if (fullExecutionData.id !== undefined) {
|
if (fullExecutionData.id !== undefined) {
|
||||||
returnData.id = fullExecutionData.id.toString();
|
returnData.id = fullExecutionData.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fullExecutionData.retryOf !== undefined) {
|
if (fullExecutionData.retryOf !== undefined) {
|
||||||
|
@ -246,7 +246,7 @@ export function flattenExecutionData(fullExecutionData: IExecutionDb): IExecutio
|
||||||
*/
|
*/
|
||||||
export function unflattenExecutionData(fullExecutionData: IExecutionFlattedDb): IExecutionResponse {
|
export function unflattenExecutionData(fullExecutionData: IExecutionFlattedDb): IExecutionResponse {
|
||||||
const returnData: IExecutionResponse = {
|
const returnData: IExecutionResponse = {
|
||||||
id: fullExecutionData.id.toString(),
|
id: fullExecutionData.id,
|
||||||
workflowData: fullExecutionData.workflowData as IWorkflowDb,
|
workflowData: fullExecutionData.workflowData as IWorkflowDb,
|
||||||
data: parse(fullExecutionData.data),
|
data: parse(fullExecutionData.data),
|
||||||
mode: fullExecutionData.mode,
|
mode: fullExecutionData.mode,
|
||||||
|
|
|
@ -983,8 +983,7 @@ class App {
|
||||||
`/${this.restEndpoint}/active`,
|
`/${this.restEndpoint}/active`,
|
||||||
ResponseHelper.send(async (req: WorkflowRequest.GetAllActive) => {
|
ResponseHelper.send(async (req: WorkflowRequest.GetAllActive) => {
|
||||||
const activeWorkflows = await this.activeWorkflowRunner.getActiveWorkflows(req.user);
|
const activeWorkflows = await this.activeWorkflowRunner.getActiveWorkflows(req.user);
|
||||||
|
return activeWorkflows.map(({ id }) => id);
|
||||||
return activeWorkflows.map(({ id }) => id.toString());
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1365,22 +1364,21 @@ class App {
|
||||||
for (const data of executingWorkflows) {
|
for (const data of executingWorkflows) {
|
||||||
if (
|
if (
|
||||||
(filter.workflowId !== undefined && filter.workflowId !== data.workflowId) ||
|
(filter.workflowId !== undefined && filter.workflowId !== data.workflowId) ||
|
||||||
(data.workflowId !== undefined &&
|
(data.workflowId !== undefined && !sharedWorkflowIds.includes(data.workflowId))
|
||||||
!sharedWorkflowIds.includes(data.workflowId.toString()))
|
|
||||||
) {
|
) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
returnData.push({
|
returnData.push({
|
||||||
id: data.id.toString(),
|
id: data.id,
|
||||||
workflowId: data.workflowId === undefined ? '' : data.workflowId.toString(),
|
workflowId: data.workflowId === undefined ? '' : data.workflowId,
|
||||||
mode: data.mode,
|
mode: data.mode,
|
||||||
retryOf: data.retryOf,
|
retryOf: data.retryOf,
|
||||||
startedAt: new Date(data.startedAt),
|
startedAt: new Date(data.startedAt),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
returnData.sort((a, b) => parseInt(b.id, 10) - parseInt(a.id, 10));
|
returnData.sort((a, b) => Number(b.id) - Number(a.id));
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
},
|
},
|
||||||
|
@ -1435,7 +1433,7 @@ class App {
|
||||||
const queue = await Queue.getInstance();
|
const queue = await Queue.getInstance();
|
||||||
const currentJobs = await queue.getJobs(['active', 'waiting']);
|
const currentJobs = await queue.getJobs(['active', 'waiting']);
|
||||||
|
|
||||||
const job = currentJobs.find((job) => job.data.executionId.toString() === req.params.id);
|
const job = currentJobs.find((job) => job.data.executionId === req.params.id);
|
||||||
|
|
||||||
if (!job) {
|
if (!job) {
|
||||||
throw new Error(`Could not stop "${req.params.id}" as it is no longer in queue.`);
|
throw new Error(`Could not stop "${req.params.id}" as it is no longer in queue.`);
|
||||||
|
|
|
@ -18,7 +18,7 @@ export function sortByRequestOrder(
|
||||||
{ requestOrder }: { requestOrder: string[] },
|
{ requestOrder }: { requestOrder: string[] },
|
||||||
) {
|
) {
|
||||||
const tagMap = tags.reduce<Record<string, TagEntity>>((acc, tag) => {
|
const tagMap = tags.reduce<Record<string, TagEntity>>((acc, tag) => {
|
||||||
acc[tag.id.toString()] = tag;
|
acc[tag.id] = tag;
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ export async function getTagsWithCountDb(tablePrefix: string): Promise<ITagWithC
|
||||||
.getRawMany()
|
.getRawMany()
|
||||||
.then((tagsWithCount) => {
|
.then((tagsWithCount) => {
|
||||||
tagsWithCount.forEach((tag) => {
|
tagsWithCount.forEach((tag) => {
|
||||||
|
// NOTE: since this code doesn't use the DB entities, we need to stringify the IDs manually
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
||||||
tag.id = tag.id.toString();
|
tag.id = tag.id.toString();
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||||
|
@ -102,7 +103,7 @@ const findOrCreateTag = async (
|
||||||
// Assume tag is identical if createdAt date is the same to preserve a changed tag name
|
// Assume tag is identical if createdAt date is the same to preserve a changed tag name
|
||||||
const identicalMatch = tagsEntities.find(
|
const identicalMatch = tagsEntities.find(
|
||||||
(existingTag) =>
|
(existingTag) =>
|
||||||
existingTag.id.toString() === importTag.id.toString() &&
|
existingTag.id === importTag.id &&
|
||||||
existingTag.createdAt &&
|
existingTag.createdAt &&
|
||||||
importTag.createdAt &&
|
importTag.createdAt &&
|
||||||
existingTag.createdAt.getTime() === new Date(importTag.createdAt).getTime(),
|
existingTag.createdAt.getTime() === new Date(importTag.createdAt).getTime(),
|
||||||
|
|
|
@ -213,7 +213,7 @@ export class TestWebhooks {
|
||||||
|
|
||||||
// Remove test-webhooks automatically if they do not get called (after 120 seconds)
|
// Remove test-webhooks automatically if they do not get called (after 120 seconds)
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
this.cancelTestWebhook(workflowData.id.toString());
|
this.cancelTestWebhook(workflowData.id);
|
||||||
}, 120000);
|
}, 120000);
|
||||||
|
|
||||||
let key: string;
|
let key: string;
|
||||||
|
@ -260,7 +260,7 @@ export class TestWebhooks {
|
||||||
for (const webhookKey of Object.keys(this.testWebhookData)) {
|
for (const webhookKey of Object.keys(this.testWebhookData)) {
|
||||||
const webhookData = this.testWebhookData[webhookKey];
|
const webhookData = this.testWebhookData[webhookKey];
|
||||||
|
|
||||||
if (webhookData.workflowData.id.toString() !== workflowId) {
|
if (webhookData.workflowData.id !== workflowId) {
|
||||||
// eslint-disable-next-line no-continue
|
// eslint-disable-next-line no-continue
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ export class PermissionChecker {
|
||||||
if (workflow.id && isSharingEnabled()) {
|
if (workflow.id && isSharingEnabled()) {
|
||||||
const workflowSharings = await Db.collections.SharedWorkflow.find({
|
const workflowSharings = await Db.collections.SharedWorkflow.find({
|
||||||
relations: ['workflow'],
|
relations: ['workflow'],
|
||||||
where: { workflow: { id: Number(workflow.id) } },
|
where: { workflowId: workflow.id },
|
||||||
});
|
});
|
||||||
workflowUserIds = workflowSharings.map((s) => s.userId);
|
workflowUserIds = workflowSharings.map((s) => s.userId);
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ export class PermissionChecker {
|
||||||
where: credentialsWhere,
|
where: credentialsWhere,
|
||||||
});
|
});
|
||||||
|
|
||||||
const accessibleCredIds = credentialSharings.map((s) => s.credentialsId.toString());
|
const accessibleCredIds = credentialSharings.map((s) => s.credentialsId);
|
||||||
|
|
||||||
const inaccessibleCredIds = workflowCredIds.filter((id) => !accessibleCredIds.includes(id));
|
const inaccessibleCredIds = workflowCredIds.filter((id) => !accessibleCredIds.includes(id));
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,11 @@ import { getLicense } from '@/License';
|
||||||
import { WhereClause } from '@/Interfaces';
|
import { WhereClause } from '@/Interfaces';
|
||||||
import { RoleService } from '@/role/role.service';
|
import { RoleService } from '@/role/role.service';
|
||||||
|
|
||||||
export async function getWorkflowOwner(workflowId: string | number): Promise<User> {
|
export async function getWorkflowOwner(workflowId: string): Promise<User> {
|
||||||
const workflowOwnerRole = await RoleService.get({ name: 'owner', scope: 'workflow' });
|
const workflowOwnerRole = await RoleService.get({ name: 'owner', scope: 'workflow' });
|
||||||
|
|
||||||
const sharedWorkflow = await Db.collections.SharedWorkflow.findOneOrFail({
|
const sharedWorkflow = await Db.collections.SharedWorkflow.findOneOrFail({
|
||||||
where: { workflow: { id: workflowId }, role: workflowOwnerRole },
|
where: { workflowId, role: workflowOwnerRole },
|
||||||
relations: ['user', 'user.globalRole'],
|
relations: ['user', 'user.globalRole'],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -426,13 +426,13 @@ export function usersNamespace(this: N8nApp): void {
|
||||||
|
|
||||||
await Db.transaction(async (transactionManager) => {
|
await Db.transaction(async (transactionManager) => {
|
||||||
// Get all workflow ids belonging to user to delete
|
// Get all workflow ids belonging to user to delete
|
||||||
const sharedWorkflows = await transactionManager.getRepository(SharedWorkflow).find({
|
const sharedWorkflowIds = await transactionManager
|
||||||
where: { user: userToDelete, role: workflowOwnerRole },
|
.getRepository(SharedWorkflow)
|
||||||
});
|
.find({
|
||||||
|
select: ['workflowId'],
|
||||||
const sharedWorkflowIds = sharedWorkflows.map((sharedWorkflow) =>
|
where: { userId: userToDelete.id, role: workflowOwnerRole },
|
||||||
sharedWorkflow.workflowId.toString(),
|
})
|
||||||
);
|
.then((sharedWorkflows) => sharedWorkflows.map(({ workflowId }) => workflowId));
|
||||||
|
|
||||||
// Prevents issues with unique key constraints since user being assigned
|
// Prevents issues with unique key constraints since user being assigned
|
||||||
// workflows and credentials might be a sharee
|
// workflows and credentials might be a sharee
|
||||||
|
@ -451,21 +451,21 @@ export function usersNamespace(this: N8nApp): void {
|
||||||
// Now do the same for creds
|
// Now do the same for creds
|
||||||
|
|
||||||
// Get all workflow ids belonging to user to delete
|
// Get all workflow ids belonging to user to delete
|
||||||
const sharedCredentials = await transactionManager.getRepository(SharedCredentials).find({
|
const sharedCredentialIds = await transactionManager
|
||||||
where: { user: userToDelete, role: credentialOwnerRole },
|
.getRepository(SharedCredentials)
|
||||||
});
|
.find({
|
||||||
|
select: ['credentialsId'],
|
||||||
const sharedCredentialIds = sharedCredentials.map((sharedCredential) =>
|
where: { user: userToDelete, role: credentialOwnerRole },
|
||||||
sharedCredential.credentialsId.toString(),
|
})
|
||||||
);
|
.then((sharedCredentials) =>
|
||||||
|
sharedCredentials.map(({ credentialsId }) => credentialsId),
|
||||||
|
);
|
||||||
|
|
||||||
// Prevents issues with unique key constraints since user being assigned
|
// Prevents issues with unique key constraints since user being assigned
|
||||||
// workflows and credentials might be a sharee
|
// workflows and credentials might be a sharee
|
||||||
await transactionManager.delete(SharedCredentials, {
|
await transactionManager.delete(SharedCredentials, {
|
||||||
user: transferee,
|
user: transferee,
|
||||||
credentials: In(
|
credentialsId: In(sharedCredentialIds),
|
||||||
sharedCredentialIds.map((sharedCredentialId) => ({ id: sharedCredentialId })),
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Transfer ownership of owned credentials
|
// Transfer ownership of owned credentials
|
||||||
|
@ -500,7 +500,7 @@ export function usersNamespace(this: N8nApp): void {
|
||||||
ownedSharedWorkflows.map(async ({ workflow }) => {
|
ownedSharedWorkflows.map(async ({ workflow }) => {
|
||||||
if (workflow.active) {
|
if (workflow.active) {
|
||||||
// deactivate before deleting
|
// deactivate before deleting
|
||||||
await this.activeWorkflowRunner.remove(workflow.id.toString());
|
await this.activeWorkflowRunner.remove(workflow.id);
|
||||||
}
|
}
|
||||||
return workflow;
|
return workflow;
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -77,7 +77,7 @@ export class WaitTrackerClass {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const executionIds = executions.map((execution) => execution.id.toString()).join(', ');
|
const executionIds = executions.map((execution) => execution.id).join(', ');
|
||||||
Logger.debug(
|
Logger.debug(
|
||||||
`Wait tracker found ${executions.length} executions. Setting timer for IDs: ${executionIds}`,
|
`Wait tracker found ${executions.length} executions. Setting timer for IDs: ${executionIds}`,
|
||||||
);
|
);
|
||||||
|
@ -85,7 +85,7 @@ export class WaitTrackerClass {
|
||||||
// Add timers for each waiting execution that they get started at the correct time
|
// Add timers for each waiting execution that they get started at the correct time
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const execution of executions) {
|
for (const execution of executions) {
|
||||||
const executionId = execution.id.toString();
|
const executionId = execution.id;
|
||||||
if (this.waitingExecutions[executionId] === undefined) {
|
if (this.waitingExecutions[executionId] === undefined) {
|
||||||
const triggerTime = execution.waitTill!.getTime() - new Date().getTime();
|
const triggerTime = execution.waitTill!.getTime() - new Date().getTime();
|
||||||
this.waitingExecutions[executionId] = {
|
this.waitingExecutions[executionId] = {
|
||||||
|
@ -161,7 +161,7 @@ export class WaitTrackerClass {
|
||||||
if (!fullExecutionData.workflowData.id) {
|
if (!fullExecutionData.workflowData.id) {
|
||||||
throw new Error('Only saved workflows can be resumed.');
|
throw new Error('Only saved workflows can be resumed.');
|
||||||
}
|
}
|
||||||
const user = await getWorkflowOwner(fullExecutionData.workflowData.id.toString());
|
const user = await getWorkflowOwner(fullExecutionData.workflowData.id);
|
||||||
|
|
||||||
const data: IWorkflowExecutionDataProcess = {
|
const data: IWorkflowExecutionDataProcess = {
|
||||||
executionMode: fullExecutionData.mode,
|
executionMode: fullExecutionData.mode,
|
||||||
|
|
|
@ -167,7 +167,7 @@ export async function executeWebhook(
|
||||||
user = (workflowData as WorkflowEntity).shared[0].user;
|
user = (workflowData as WorkflowEntity).shared[0].user;
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
user = await getWorkflowOwner(workflowData.id.toString());
|
user = await getWorkflowOwner(workflowData.id);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new ResponseHelper.NotFoundError('Cannot find workflow');
|
throw new ResponseHelper.NotFoundError('Cannot find workflow');
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,13 +27,13 @@ import {
|
||||||
IRun,
|
IRun,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
ITaskData,
|
ITaskData,
|
||||||
|
IWorkflowBase,
|
||||||
IWorkflowExecuteAdditionalData,
|
IWorkflowExecuteAdditionalData,
|
||||||
IWorkflowExecuteHooks,
|
IWorkflowExecuteHooks,
|
||||||
IWorkflowHooksOptionalParameters,
|
IWorkflowHooksOptionalParameters,
|
||||||
IWorkflowSettings,
|
IWorkflowSettings,
|
||||||
ErrorReporterProxy as ErrorReporter,
|
ErrorReporterProxy as ErrorReporter,
|
||||||
LoggerProxy as Logger,
|
LoggerProxy as Logger,
|
||||||
SubworkflowOperationError,
|
|
||||||
Workflow,
|
Workflow,
|
||||||
WorkflowExecuteMode,
|
WorkflowExecuteMode,
|
||||||
WorkflowHooks,
|
WorkflowHooks,
|
||||||
|
@ -51,7 +51,6 @@ import {
|
||||||
IExecutionFlattedDb,
|
IExecutionFlattedDb,
|
||||||
IExecutionResponse,
|
IExecutionResponse,
|
||||||
IPushDataExecutionFinished,
|
IPushDataExecutionFinished,
|
||||||
IWorkflowBase,
|
|
||||||
IWorkflowExecuteProcess,
|
IWorkflowExecuteProcess,
|
||||||
IWorkflowExecutionDataProcess,
|
IWorkflowExecutionDataProcess,
|
||||||
IWorkflowErrorData,
|
IWorkflowErrorData,
|
||||||
|
@ -62,7 +61,7 @@ import * as Push from '@/Push';
|
||||||
import * as ResponseHelper from '@/ResponseHelper';
|
import * as ResponseHelper from '@/ResponseHelper';
|
||||||
import * as WebhookHelpers from '@/WebhookHelpers';
|
import * as WebhookHelpers from '@/WebhookHelpers';
|
||||||
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
||||||
import { getUserById, getWorkflowOwner, whereClause } from '@/UserManagement/UserManagementHelper';
|
import { getWorkflowOwner } from '@/UserManagement/UserManagementHelper';
|
||||||
import { findSubworkflowStart } from '@/utils';
|
import { findSubworkflowStart } from '@/utils';
|
||||||
import { PermissionChecker } from './UserManagement/PermissionChecker';
|
import { PermissionChecker } from './UserManagement/PermissionChecker';
|
||||||
import { WorkflowsService } from './workflows/workflows.services';
|
import { WorkflowsService } from './workflows/workflows.services';
|
||||||
|
@ -94,6 +93,7 @@ export function executeErrorWorkflow(
|
||||||
|
|
||||||
if (fullRunData.data.resultData.error !== undefined) {
|
if (fullRunData.data.resultData.error !== undefined) {
|
||||||
let workflowErrorData: IWorkflowErrorData;
|
let workflowErrorData: IWorkflowErrorData;
|
||||||
|
const workflowId = workflowData.id;
|
||||||
|
|
||||||
if (executionId) {
|
if (executionId) {
|
||||||
// The error did happen in an execution
|
// The error did happen in an execution
|
||||||
|
@ -107,7 +107,7 @@ export function executeErrorWorkflow(
|
||||||
retryOf,
|
retryOf,
|
||||||
},
|
},
|
||||||
workflow: {
|
workflow: {
|
||||||
id: workflowData.id !== undefined ? workflowData.id.toString() : undefined,
|
id: workflowId,
|
||||||
name: workflowData.name,
|
name: workflowData.name,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -119,7 +119,7 @@ export function executeErrorWorkflow(
|
||||||
mode,
|
mode,
|
||||||
},
|
},
|
||||||
workflow: {
|
workflow: {
|
||||||
id: workflowData.id !== undefined ? workflowData.id.toString() : undefined,
|
id: workflowId,
|
||||||
name: workflowData.name,
|
name: workflowData.name,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -128,30 +128,28 @@ export function executeErrorWorkflow(
|
||||||
// Run the error workflow
|
// Run the error workflow
|
||||||
// To avoid an infinite loop do not run the error workflow again if the error-workflow itself failed and it is its own error-workflow.
|
// To avoid an infinite loop do not run the error workflow again if the error-workflow itself failed and it is its own error-workflow.
|
||||||
if (
|
if (
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
workflowData.settings?.errorWorkflow &&
|
||||||
workflowData.settings !== undefined &&
|
|
||||||
workflowData.settings.errorWorkflow &&
|
|
||||||
!(
|
!(
|
||||||
mode === 'error' &&
|
mode === 'error' &&
|
||||||
workflowData.id &&
|
workflowId &&
|
||||||
workflowData.settings.errorWorkflow.toString() === workflowData.id.toString()
|
workflowData.settings.errorWorkflow.toString() === workflowId
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Logger.verbose('Start external error workflow', {
|
Logger.verbose('Start external error workflow', {
|
||||||
executionId,
|
executionId,
|
||||||
errorWorkflowId: workflowData.settings.errorWorkflow.toString(),
|
errorWorkflowId: workflowData.settings.errorWorkflow.toString(),
|
||||||
workflowId: workflowData.id,
|
workflowId,
|
||||||
});
|
});
|
||||||
// If a specific error workflow is set run only that one
|
// If a specific error workflow is set run only that one
|
||||||
|
|
||||||
// First, do permission checks.
|
// First, do permission checks.
|
||||||
if (!workflowData.id) {
|
if (!workflowId) {
|
||||||
// Manual executions do not trigger error workflows
|
// Manual executions do not trigger error workflows
|
||||||
// So this if should never happen. It was added to
|
// So this if should never happen. It was added to
|
||||||
// make sure there are no possible security gaps
|
// make sure there are no possible security gaps
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getWorkflowOwner(workflowData.id)
|
getWorkflowOwner(workflowId)
|
||||||
.then((user) => {
|
.then((user) => {
|
||||||
void WorkflowHelpers.executeErrorWorkflow(
|
void WorkflowHelpers.executeErrorWorkflow(
|
||||||
workflowData.settings!.errorWorkflow as string,
|
workflowData.settings!.errorWorkflow as string,
|
||||||
|
@ -166,7 +164,7 @@ export function executeErrorWorkflow(
|
||||||
{
|
{
|
||||||
executionId,
|
executionId,
|
||||||
errorWorkflowId: workflowData.settings!.errorWorkflow!.toString(),
|
errorWorkflowId: workflowData.settings!.errorWorkflow!.toString(),
|
||||||
workflowId: workflowData.id,
|
workflowId,
|
||||||
error,
|
error,
|
||||||
workflowErrorData,
|
workflowErrorData,
|
||||||
},
|
},
|
||||||
|
@ -174,16 +172,12 @@ export function executeErrorWorkflow(
|
||||||
});
|
});
|
||||||
} else if (
|
} else if (
|
||||||
mode !== 'error' &&
|
mode !== 'error' &&
|
||||||
workflowData.id !== undefined &&
|
workflowId !== undefined &&
|
||||||
workflowData.nodes.some((node) => node.type === ERROR_TRIGGER_TYPE)
|
workflowData.nodes.some((node) => node.type === ERROR_TRIGGER_TYPE)
|
||||||
) {
|
) {
|
||||||
Logger.verbose('Start internal error workflow', { executionId, workflowId: workflowData.id });
|
Logger.verbose('Start internal error workflow', { executionId, workflowId });
|
||||||
void getWorkflowOwner(workflowData.id).then((user) => {
|
void getWorkflowOwner(workflowId).then((user) => {
|
||||||
void WorkflowHelpers.executeErrorWorkflow(
|
void WorkflowHelpers.executeErrorWorkflow(workflowId, workflowErrorData, user);
|
||||||
workflowData.id!.toString(),
|
|
||||||
workflowErrorData,
|
|
||||||
user,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -219,7 +213,7 @@ async function pruneExecutionData(this: WorkflowHooks): Promise<void> {
|
||||||
}, timeout * 1000);
|
}, timeout * 1000);
|
||||||
// Mark binary data for deletion for all executions
|
// Mark binary data for deletion for all executions
|
||||||
await BinaryDataManager.getInstance().markDataForDeletionByExecutionIds(
|
await BinaryDataManager.getInstance().markDataForDeletionByExecutionIds(
|
||||||
executions.map(({ id }) => id.toString()),
|
executions.map(({ id }) => id),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ErrorReporter.error(error);
|
ErrorReporter.error(error);
|
||||||
|
@ -505,7 +499,7 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
!isManualMode &&
|
!isManualMode &&
|
||||||
WorkflowHelpers.isWorkflowIdValid(this.workflowData.id as string) &&
|
WorkflowHelpers.isWorkflowIdValid(this.workflowData.id) &&
|
||||||
newStaticData
|
newStaticData
|
||||||
) {
|
) {
|
||||||
// Workflow is saved so update in database
|
// Workflow is saved so update in database
|
||||||
|
@ -593,17 +587,15 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks {
|
||||||
fullExecutionData.retryOf = this.retryOf.toString();
|
fullExecutionData.retryOf = this.retryOf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
const workflowId = this.workflowData.id;
|
||||||
this.workflowData.id !== undefined &&
|
if (WorkflowHelpers.isWorkflowIdValid(workflowId)) {
|
||||||
WorkflowHelpers.isWorkflowIdValid(this.workflowData.id.toString())
|
fullExecutionData.workflowId = workflowId;
|
||||||
) {
|
|
||||||
fullExecutionData.workflowId = this.workflowData.id.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Leave log message before flatten as that operation increased memory usage a lot and the chance of a crash is highest here
|
// Leave log message before flatten as that operation increased memory usage a lot and the chance of a crash is highest here
|
||||||
Logger.debug(`Save execution data to database for execution ID ${this.executionId}`, {
|
Logger.debug(`Save execution data to database for execution ID ${this.executionId}`, {
|
||||||
executionId: this.executionId,
|
executionId: this.executionId,
|
||||||
workflowId: this.workflowData.id,
|
workflowId,
|
||||||
finished: fullExecutionData.finished,
|
finished: fullExecutionData.finished,
|
||||||
stoppedAt: fullExecutionData.stoppedAt,
|
stoppedAt: fullExecutionData.stoppedAt,
|
||||||
});
|
});
|
||||||
|
@ -685,7 +677,7 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks {
|
||||||
newStaticData: IDataObject,
|
newStaticData: IDataObject,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (WorkflowHelpers.isWorkflowIdValid(this.workflowData.id as string) && newStaticData) {
|
if (WorkflowHelpers.isWorkflowIdValid(this.workflowData.id) && newStaticData) {
|
||||||
// Workflow is saved so update in database
|
// Workflow is saved so update in database
|
||||||
try {
|
try {
|
||||||
await WorkflowHelpers.saveStaticDataById(
|
await WorkflowHelpers.saveStaticDataById(
|
||||||
|
@ -726,11 +718,9 @@ function hookFunctionsSaveWorker(): IWorkflowExecuteHooks {
|
||||||
fullExecutionData.retryOf = this.retryOf.toString();
|
fullExecutionData.retryOf = this.retryOf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
const workflowId = this.workflowData.id;
|
||||||
this.workflowData.id !== undefined &&
|
if (WorkflowHelpers.isWorkflowIdValid(workflowId)) {
|
||||||
WorkflowHelpers.isWorkflowIdValid(this.workflowData.id.toString())
|
fullExecutionData.workflowId = workflowId;
|
||||||
) {
|
|
||||||
fullExecutionData.workflowId = this.workflowData.id.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const executionData = ResponseHelper.flattenExecutionData(fullExecutionData);
|
const executionData = ResponseHelper.flattenExecutionData(fullExecutionData);
|
||||||
|
@ -844,12 +834,7 @@ export async function getWorkflowData(
|
||||||
|
|
||||||
const relations = config.getEnv('workflowTagsDisabled') ? [] : ['tags'];
|
const relations = config.getEnv('workflowTagsDisabled') ? [] : ['tags'];
|
||||||
|
|
||||||
workflowData = await WorkflowsService.get(
|
workflowData = await WorkflowsService.get({ id: workflowInfo.id }, { relations });
|
||||||
{ id: parseInt(workflowInfo.id, 10) },
|
|
||||||
{
|
|
||||||
relations,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (workflowData === undefined) {
|
if (workflowData === undefined) {
|
||||||
throw new Error(`The workflow with the id "${workflowInfo.id}" does not exist.`);
|
throw new Error(`The workflow with the id "${workflowInfo.id}" does not exist.`);
|
||||||
|
@ -1004,7 +989,7 @@ async function executeWorkflow(
|
||||||
workflowData,
|
workflowData,
|
||||||
};
|
};
|
||||||
if (workflowData.id) {
|
if (workflowData.id) {
|
||||||
fullExecutionData.workflowId = workflowData.id as string;
|
fullExecutionData.workflowId = workflowData.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const executionData = ResponseHelper.flattenExecutionData(fullExecutionData);
|
const executionData = ResponseHelper.flattenExecutionData(fullExecutionData);
|
||||||
|
|
|
@ -71,16 +71,8 @@ export function getDataLastExecutedNodeData(inputData: IRun): ITaskData | undefi
|
||||||
*
|
*
|
||||||
* @param {(string | null | undefined)} id The id to check
|
* @param {(string | null | undefined)} id The id to check
|
||||||
*/
|
*/
|
||||||
export function isWorkflowIdValid(id: string | null | undefined | number): boolean {
|
export function isWorkflowIdValid(id: string | null | undefined): boolean {
|
||||||
if (typeof id === 'string') {
|
return !(typeof id === 'string' && isNaN(parseInt(id, 10)));
|
||||||
id = parseInt(id, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-globals
|
|
||||||
if (isNaN(id as number)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -97,7 +89,7 @@ export async function executeErrorWorkflow(
|
||||||
// Wrap everything in try/catch to make sure that no errors bubble up and all get caught here
|
// Wrap everything in try/catch to make sure that no errors bubble up and all get caught here
|
||||||
try {
|
try {
|
||||||
let workflowData;
|
let workflowData;
|
||||||
if (workflowId.toString() !== workflowErrorData.workflow.id?.toString()) {
|
if (workflowId !== workflowErrorData.workflow.id) {
|
||||||
// To make this code easier to understand, we split it in 2 parts:
|
// To make this code easier to understand, we split it in 2 parts:
|
||||||
// 1) Fetch the owner of the errored workflows and then
|
// 1) Fetch the owner of the errored workflows and then
|
||||||
// 2) if now instance owner, then check if the user has access to the
|
// 2) if now instance owner, then check if the user has access to the
|
||||||
|
@ -106,13 +98,10 @@ export async function executeErrorWorkflow(
|
||||||
const user = await getWorkflowOwner(workflowErrorData.workflow.id!);
|
const user = await getWorkflowOwner(workflowErrorData.workflow.id!);
|
||||||
|
|
||||||
if (user.globalRole.name === 'owner') {
|
if (user.globalRole.name === 'owner') {
|
||||||
workflowData = await Db.collections.Workflow.findOne({ id: Number(workflowId) });
|
workflowData = await Db.collections.Workflow.findOne({ id: workflowId });
|
||||||
} else {
|
} else {
|
||||||
const sharedWorkflowData = await Db.collections.SharedWorkflow.findOne({
|
const sharedWorkflowData = await Db.collections.SharedWorkflow.findOne({
|
||||||
where: {
|
where: { workflowId, userId: user.id },
|
||||||
workflow: { id: workflowId },
|
|
||||||
user,
|
|
||||||
},
|
|
||||||
relations: ['workflow'],
|
relations: ['workflow'],
|
||||||
});
|
});
|
||||||
if (sharedWorkflowData) {
|
if (sharedWorkflowData) {
|
||||||
|
@ -120,7 +109,7 @@ export async function executeErrorWorkflow(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
workflowData = await Db.collections.Workflow.findOne({ id: Number(workflowId) });
|
workflowData = await Db.collections.Workflow.findOne({ id: workflowId });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workflowData === undefined) {
|
if (workflowData === undefined) {
|
||||||
|
@ -250,11 +239,11 @@ export async function saveStaticData(workflow: Workflow): Promise<void> {
|
||||||
/**
|
/**
|
||||||
* Saves the given static data on workflow
|
* Saves the given static data on workflow
|
||||||
*
|
*
|
||||||
* @param {(string | number)} workflowId The id of the workflow to save data on
|
* @param {(string)} workflowId The id of the workflow to save data on
|
||||||
* @param {IDataObject} newStaticData The static data to save
|
* @param {IDataObject} newStaticData The static data to save
|
||||||
*/
|
*/
|
||||||
export async function saveStaticDataById(
|
export async function saveStaticDataById(
|
||||||
workflowId: string | number,
|
workflowId: string,
|
||||||
newStaticData: IDataObject,
|
newStaticData: IDataObject,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await Db.collections.Workflow.update(workflowId, {
|
await Db.collections.Workflow.update(workflowId, {
|
||||||
|
@ -265,10 +254,10 @@ export async function saveStaticDataById(
|
||||||
/**
|
/**
|
||||||
* Returns the static data of workflow
|
* Returns the static data of workflow
|
||||||
*
|
*
|
||||||
* @param {(string | number)} workflowId The id of the workflow to get static data of
|
* @param {(string)} workflowId The id of the workflow to get static data of
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
export async function getStaticDataById(workflowId: string | number) {
|
export async function getStaticDataById(workflowId: string) {
|
||||||
const workflowData = await Db.collections.Workflow.findOne(workflowId, {
|
const workflowData = await Db.collections.Workflow.findOne(workflowId, {
|
||||||
select: ['staticData'],
|
select: ['staticData'],
|
||||||
});
|
});
|
||||||
|
@ -329,7 +318,7 @@ export async function replaceInvalidCredentials(workflow: WorkflowEntity): Promi
|
||||||
// if credential name-type combination is unique, use it
|
// if credential name-type combination is unique, use it
|
||||||
if (credentials?.length === 1) {
|
if (credentials?.length === 1) {
|
||||||
credentialsByName[nodeCredentialType][name] = {
|
credentialsByName[nodeCredentialType][name] = {
|
||||||
id: credentials[0].id.toString(),
|
id: credentials[0].id,
|
||||||
name: credentials[0].name,
|
name: credentials[0].name,
|
||||||
};
|
};
|
||||||
node.credentials[nodeCredentialType] = credentialsByName[nodeCredentialType][name];
|
node.credentials[nodeCredentialType] = credentialsByName[nodeCredentialType][name];
|
||||||
|
@ -364,7 +353,7 @@ export async function replaceInvalidCredentials(workflow: WorkflowEntity): Promi
|
||||||
});
|
});
|
||||||
if (credentials) {
|
if (credentials) {
|
||||||
credentialsById[nodeCredentialType][nodeCredentials.id] = {
|
credentialsById[nodeCredentialType][nodeCredentials.id] = {
|
||||||
id: credentials.id.toString(),
|
id: credentials.id,
|
||||||
name: credentials.name,
|
name: credentials.name,
|
||||||
};
|
};
|
||||||
node.credentials[nodeCredentialType] =
|
node.credentials[nodeCredentialType] =
|
||||||
|
@ -380,7 +369,7 @@ export async function replaceInvalidCredentials(workflow: WorkflowEntity): Promi
|
||||||
if (credsByName?.length === 1) {
|
if (credsByName?.length === 1) {
|
||||||
// add found credential to cache
|
// add found credential to cache
|
||||||
credentialsById[nodeCredentialType][credsByName[0].id] = {
|
credentialsById[nodeCredentialType][credsByName[0].id] = {
|
||||||
id: credsByName[0].id.toString(),
|
id: credsByName[0].id,
|
||||||
name: credsByName[0].name,
|
name: credsByName[0].name,
|
||||||
};
|
};
|
||||||
node.credentials[nodeCredentialType] =
|
node.credentials[nodeCredentialType] =
|
||||||
|
@ -410,9 +399,10 @@ export async function getSharedWorkflowIds(user: User, roles?: string[]): Promis
|
||||||
const sharedWorkflows = await Db.collections.SharedWorkflow.find({
|
const sharedWorkflows = await Db.collections.SharedWorkflow.find({
|
||||||
relations: ['workflow', 'role'],
|
relations: ['workflow', 'role'],
|
||||||
where: whereClause({ user, entityType: 'workflow', roles }),
|
where: whereClause({ user, entityType: 'workflow', roles }),
|
||||||
|
select: ['workflowId'],
|
||||||
});
|
});
|
||||||
|
|
||||||
return sharedWorkflows.map(({ workflowId }) => workflowId.toString());
|
return sharedWorkflows.map(({ workflowId }) => workflowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -428,9 +418,12 @@ export async function isBelowOnboardingThreshold(user: User): Promise<boolean> {
|
||||||
scope: 'workflow',
|
scope: 'workflow',
|
||||||
});
|
});
|
||||||
const ownedWorkflowsIds = await Db.collections.SharedWorkflow.find({
|
const ownedWorkflowsIds = await Db.collections.SharedWorkflow.find({
|
||||||
user,
|
where: {
|
||||||
role: workflowOwnerRole,
|
user,
|
||||||
}).then((ownedWorkflows) => ownedWorkflows.map((wf) => wf.workflowId));
|
role: workflowOwnerRole,
|
||||||
|
},
|
||||||
|
select: ['workflowId'],
|
||||||
|
}).then((ownedWorkflows) => ownedWorkflows.map(({ workflowId }) => workflowId));
|
||||||
|
|
||||||
if (ownedWorkflowsIds.length > 15) {
|
if (ownedWorkflowsIds.length > 15) {
|
||||||
belowThreshold = false;
|
belowThreshold = false;
|
||||||
|
@ -538,7 +531,7 @@ export function validateWorkflowCredentialUsage(
|
||||||
* - It's a new node which indicates tampering and therefore must fail saving
|
* - It's a new node which indicates tampering and therefore must fail saving
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const allowedCredentialIds = credentialsUserHasAccessTo.map((cred) => cred.id.toString());
|
const allowedCredentialIds = credentialsUserHasAccessTo.map((cred) => cred.id);
|
||||||
|
|
||||||
const nodesWithCredentialsUserDoesNotHaveAccessTo = getNodesWithInaccessibleCreds(
|
const nodesWithCredentialsUserDoesNotHaveAccessTo = getNodesWithInaccessibleCreds(
|
||||||
newWorkflowVersion,
|
newWorkflowVersion,
|
||||||
|
|
|
@ -196,10 +196,9 @@ export class WorkflowRunner {
|
||||||
restartExecutionId?: string,
|
restartExecutionId?: string,
|
||||||
responsePromise?: IDeferredPromise<IExecuteResponsePromiseData>,
|
responsePromise?: IDeferredPromise<IExecuteResponsePromiseData>,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if (loadStaticData === true && data.workflowData.id) {
|
const workflowId = data.workflowData.id;
|
||||||
data.workflowData.staticData = await WorkflowHelpers.getStaticDataById(
|
if (loadStaticData === true && workflowId) {
|
||||||
data.workflowData.id as string,
|
data.workflowData.staticData = await WorkflowHelpers.getStaticDataById(workflowId);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeTypes = NodeTypes();
|
const nodeTypes = NodeTypes();
|
||||||
|
@ -218,7 +217,7 @@ export class WorkflowRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflow = new Workflow({
|
const workflow = new Workflow({
|
||||||
id: data.workflowData.id as string | undefined,
|
id: workflowId,
|
||||||
name: data.workflowData.name,
|
name: data.workflowData.name,
|
||||||
nodes: data.workflowData.nodes,
|
nodes: data.workflowData.nodes,
|
||||||
connections: data.workflowData.connections,
|
connections: data.workflowData.connections,
|
||||||
|
@ -596,13 +595,12 @@ export class WorkflowRunner {
|
||||||
restartExecutionId?: string,
|
restartExecutionId?: string,
|
||||||
responsePromise?: IDeferredPromise<IExecuteResponsePromiseData>,
|
responsePromise?: IDeferredPromise<IExecuteResponsePromiseData>,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
|
const workflowId = data.workflowData.id;
|
||||||
let startedAt = new Date();
|
let startedAt = new Date();
|
||||||
const subprocess = fork(pathJoin(__dirname, 'WorkflowRunnerProcess.js'));
|
const subprocess = fork(pathJoin(__dirname, 'WorkflowRunnerProcess.js'));
|
||||||
|
|
||||||
if (loadStaticData === true && data.workflowData.id) {
|
if (loadStaticData === true && workflowId) {
|
||||||
data.workflowData.staticData = await WorkflowHelpers.getStaticDataById(
|
data.workflowData.staticData = await WorkflowHelpers.getStaticDataById(workflowId);
|
||||||
data.workflowData.id as string,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register the active execution
|
// Register the active execution
|
||||||
|
|
|
@ -135,7 +135,7 @@ class WorkflowRunnerProcess {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.workflow = new Workflow({
|
this.workflow = new Workflow({
|
||||||
id: this.data.workflowData.id as string | undefined,
|
id: this.data.workflowData.id,
|
||||||
name: this.data.workflowData.name,
|
name: this.data.workflowData.name,
|
||||||
nodes: this.data.workflowData.nodes,
|
nodes: this.data.workflowData.nodes,
|
||||||
connections: this.data.workflowData.connections,
|
connections: this.data.workflowData.connections,
|
||||||
|
|
|
@ -63,7 +63,7 @@ tagsController.post(
|
||||||
|
|
||||||
// Updates a tag
|
// Updates a tag
|
||||||
tagsController.patch(
|
tagsController.patch(
|
||||||
'/:id',
|
'/:id(\\d+)',
|
||||||
workflowsEnabledMiddleware,
|
workflowsEnabledMiddleware,
|
||||||
ResponseHelper.send(async (req: express.Request): Promise<TagEntity | void> => {
|
ResponseHelper.send(async (req: express.Request): Promise<TagEntity | void> => {
|
||||||
const { name } = req.body;
|
const { name } = req.body;
|
||||||
|
@ -86,7 +86,7 @@ tagsController.patch(
|
||||||
);
|
);
|
||||||
|
|
||||||
tagsController.delete(
|
tagsController.delete(
|
||||||
'/:id',
|
'/:id(\\d+)',
|
||||||
workflowsEnabledMiddleware,
|
workflowsEnabledMiddleware,
|
||||||
ResponseHelper.send(async (req: TagsRequest.Delete): Promise<boolean> => {
|
ResponseHelper.send(async (req: TagsRequest.Delete): Promise<boolean> => {
|
||||||
if (
|
if (
|
||||||
|
@ -98,7 +98,7 @@ tagsController.delete(
|
||||||
'Only owners can remove tags',
|
'Only owners can remove tags',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const id = Number(req.params.id);
|
const id = req.params.id;
|
||||||
|
|
||||||
await externalHooks.run('tag.beforeDelete', [id]);
|
await externalHooks.run('tag.beforeDelete', [id]);
|
||||||
|
|
||||||
|
|
6
packages/cli/src/commands/Interfaces.d.ts
vendored
6
packages/cli/src/commands/Interfaces.d.ts
vendored
|
@ -13,7 +13,7 @@ interface IResult {
|
||||||
executions: IExecutionResult[];
|
executions: IExecutionResult[];
|
||||||
}
|
}
|
||||||
interface IExecutionResult {
|
interface IExecutionResult {
|
||||||
workflowId: string | number;
|
workflowId: string;
|
||||||
workflowName: string;
|
workflowName: string;
|
||||||
executionTime: number; // Given in seconds with decimals for milliseconds
|
executionTime: number; // Given in seconds with decimals for milliseconds
|
||||||
finished: boolean;
|
finished: boolean;
|
||||||
|
@ -26,12 +26,12 @@ interface IExecutionResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IExecutionError {
|
interface IExecutionError {
|
||||||
workflowId: string | number;
|
workflowId: string;
|
||||||
error: string;
|
error: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IWorkflowExecutionProgress {
|
interface IWorkflowExecutionProgress {
|
||||||
workflowId: string | number;
|
workflowId: string;
|
||||||
status: ExecutionStatus;
|
status: ExecutionStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import { Command, flags } from '@oclif/command';
|
import { Command, flags } from '@oclif/command';
|
||||||
import { BinaryDataManager, UserSettings, PLACEHOLDER_EMPTY_WORKFLOW_ID } from 'n8n-core';
|
import { BinaryDataManager, UserSettings, PLACEHOLDER_EMPTY_WORKFLOW_ID } from 'n8n-core';
|
||||||
import { LoggerProxy } from 'n8n-workflow';
|
import { LoggerProxy, IWorkflowBase } from 'n8n-workflow';
|
||||||
|
|
||||||
import * as ActiveExecutions from '@/ActiveExecutions';
|
import * as ActiveExecutions from '@/ActiveExecutions';
|
||||||
import { CredentialsOverwrites } from '@/CredentialsOverwrites';
|
import { CredentialsOverwrites } from '@/CredentialsOverwrites';
|
||||||
|
@ -17,7 +17,7 @@ import { NodeTypes } from '@/NodeTypes';
|
||||||
import { InternalHooksManager } from '@/InternalHooksManager';
|
import { InternalHooksManager } from '@/InternalHooksManager';
|
||||||
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
import * as WorkflowHelpers from '@/WorkflowHelpers';
|
||||||
import { WorkflowRunner } from '@/WorkflowRunner';
|
import { WorkflowRunner } from '@/WorkflowRunner';
|
||||||
import { IWorkflowBase, IWorkflowExecutionDataProcess } from '@/Interfaces';
|
import { IWorkflowExecutionDataProcess } from '@/Interfaces';
|
||||||
import { getLogger } from '@/Logger';
|
import { getLogger } from '@/Logger';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import { getInstanceOwner } from '@/UserManagement/UserManagementHelper';
|
import { getInstanceOwner } from '@/UserManagement/UserManagementHelper';
|
||||||
|
@ -96,8 +96,7 @@ export class Execute extends Command {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
workflowId = workflowData.id ?? PLACEHOLDER_EMPTY_WORKFLOW_ID;
|
||||||
workflowId = workflowData.id ? workflowData.id.toString() : PLACEHOLDER_EMPTY_WORKFLOW_ID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait till the database is ready
|
// Wait till the database is ready
|
||||||
|
|
|
@ -36,6 +36,8 @@ import { User } from '@db/entities/User';
|
||||||
import { getInstanceOwner } from '@/UserManagement/UserManagementHelper';
|
import { getInstanceOwner } from '@/UserManagement/UserManagementHelper';
|
||||||
import { findCliWorkflowStart } from '@/utils';
|
import { findCliWorkflowStart } from '@/utils';
|
||||||
|
|
||||||
|
const re = /\d+/;
|
||||||
|
|
||||||
export class ExecuteBatch extends Command {
|
export class ExecuteBatch extends Command {
|
||||||
static description = '\nExecutes multiple workflows once';
|
static description = '\nExecutes multiple workflows once';
|
||||||
|
|
||||||
|
@ -199,8 +201,8 @@ export class ExecuteBatch extends Command {
|
||||||
ExecuteBatch.debug = flags.debug;
|
ExecuteBatch.debug = flags.debug;
|
||||||
ExecuteBatch.concurrency = flags.concurrency || 1;
|
ExecuteBatch.concurrency = flags.concurrency || 1;
|
||||||
|
|
||||||
const ids: number[] = [];
|
const ids: string[] = [];
|
||||||
const skipIds: number[] = [];
|
const skipIds: string[] = [];
|
||||||
|
|
||||||
if (flags.snapshot !== undefined) {
|
if (flags.snapshot !== undefined) {
|
||||||
if (fs.existsSync(flags.snapshot)) {
|
if (fs.existsSync(flags.snapshot)) {
|
||||||
|
@ -241,13 +243,10 @@ export class ExecuteBatch extends Command {
|
||||||
if (flags.ids !== undefined) {
|
if (flags.ids !== undefined) {
|
||||||
if (fs.existsSync(flags.ids)) {
|
if (fs.existsSync(flags.ids)) {
|
||||||
const contents = fs.readFileSync(flags.ids, { encoding: 'utf-8' });
|
const contents = fs.readFileSync(flags.ids, { encoding: 'utf-8' });
|
||||||
ids.push(...contents.split(',').map((id) => parseInt(id.trim(), 10)));
|
ids.push(...contents.split(',').filter((id) => re.exec(id)));
|
||||||
} else {
|
} else {
|
||||||
const paramIds = flags.ids.split(',');
|
const paramIds = flags.ids.split(',');
|
||||||
const re = /\d+/;
|
const matchedIds = paramIds.filter((id) => re.exec(id));
|
||||||
const matchedIds = paramIds
|
|
||||||
.filter((id) => re.exec(id))
|
|
||||||
.map((id) => parseInt(id.trim(), 10));
|
|
||||||
|
|
||||||
if (matchedIds.length === 0) {
|
if (matchedIds.length === 0) {
|
||||||
console.log(
|
console.log(
|
||||||
|
@ -263,7 +262,7 @@ export class ExecuteBatch extends Command {
|
||||||
if (flags.skipList !== undefined) {
|
if (flags.skipList !== undefined) {
|
||||||
if (fs.existsSync(flags.skipList)) {
|
if (fs.existsSync(flags.skipList)) {
|
||||||
const contents = fs.readFileSync(flags.skipList, { encoding: 'utf-8' });
|
const contents = fs.readFileSync(flags.skipList, { encoding: 'utf-8' });
|
||||||
skipIds.push(...contents.split(',').map((id) => parseInt(id.trim(), 10)));
|
skipIds.push(...contents.split(',').filter((id) => re.exec(id)));
|
||||||
} else {
|
} else {
|
||||||
console.log('Skip list file not found. Exiting.');
|
console.log('Skip list file not found. Exiting.');
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -128,7 +128,7 @@ export class ExportCredentialsCommand extends Command {
|
||||||
|
|
||||||
for (let i = 0; i < credentials.length; i++) {
|
for (let i = 0; i < credentials.length; i++) {
|
||||||
const { name, type, nodesAccess, data } = credentials[i];
|
const { name, type, nodesAccess, data } = credentials[i];
|
||||||
const id = credentials[i].id as string;
|
const id = credentials[i].id;
|
||||||
const credential = new Credentials({ id, name }, type, nodesAccess, data);
|
const credential = new Credentials({ id, name }, type, nodesAccess, data);
|
||||||
const plainData = credential.getData(encryptionKey);
|
const plainData = credential.getData(encryptionKey);
|
||||||
(credentials[i] as ICredentialsDecryptedDb).data = plainData;
|
(credentials[i] as ICredentialsDecryptedDb).data = plainData;
|
||||||
|
|
|
@ -263,7 +263,7 @@ export class ImportWorkflowsCommand extends Command {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (matchingCredentials.length === 1) {
|
if (matchingCredentials.length === 1) {
|
||||||
nodeCredentials.id = matchingCredentials[0].id.toString();
|
nodeCredentials.id = matchingCredentials[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
|
|
@ -35,13 +35,9 @@ EECredentialsController.get(
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||||
return allCredentials
|
return allCredentials.map((credential: CredentialsEntity & CredentialWithSharings) =>
|
||||||
.map((credential: CredentialsEntity & CredentialWithSharings) =>
|
EECredentials.addOwnerAndSharings(credential),
|
||||||
EECredentials.addOwnerAndSharings(credential),
|
);
|
||||||
)
|
|
||||||
.map(
|
|
||||||
(credential): CredentialWithSharings => ({ ...credential, id: credential.id.toString() }),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LoggerProxy.error('Request to list credentials failed', error as Error);
|
LoggerProxy.error('Request to list credentials failed', error as Error);
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -53,16 +49,12 @@ EECredentialsController.get(
|
||||||
* GET /credentials/:id
|
* GET /credentials/:id
|
||||||
*/
|
*/
|
||||||
EECredentialsController.get(
|
EECredentialsController.get(
|
||||||
'/:id',
|
'/:id(\\d+)',
|
||||||
(req, res, next) => (req.params.id === 'new' ? next('router') : next()), // skip ee router and use free one for naming
|
(req, res, next) => (req.params.id === 'new' ? next('router') : next()), // skip ee router and use free one for naming
|
||||||
ResponseHelper.send(async (req: CredentialRequest.Get) => {
|
ResponseHelper.send(async (req: CredentialRequest.Get) => {
|
||||||
const { id: credentialId } = req.params;
|
const { id: credentialId } = req.params;
|
||||||
const includeDecryptedData = req.query.includeData === 'true';
|
const includeDecryptedData = req.query.includeData === 'true';
|
||||||
|
|
||||||
if (Number.isNaN(Number(credentialId))) {
|
|
||||||
throw new ResponseHelper.BadRequestError('Credential ID must be a number.');
|
|
||||||
}
|
|
||||||
|
|
||||||
let credential = (await EECredentials.get(
|
let credential = (await EECredentials.get(
|
||||||
{ id: credentialId },
|
{ id: credentialId },
|
||||||
{ relations: ['shared', 'shared.role', 'shared.user'] },
|
{ relations: ['shared', 'shared.role', 'shared.user'] },
|
||||||
|
@ -82,19 +74,12 @@ EECredentialsController.get(
|
||||||
|
|
||||||
credential = EECredentials.addOwnerAndSharings(credential);
|
credential = EECredentials.addOwnerAndSharings(credential);
|
||||||
|
|
||||||
// @ts-ignore @TODO_TECH_DEBT: Stringify `id` with entity field transformer
|
|
||||||
credential.id = credential.id.toString();
|
|
||||||
|
|
||||||
if (!includeDecryptedData || !userSharing || userSharing.role.name !== 'owner') {
|
if (!includeDecryptedData || !userSharing || userSharing.role.name !== 'owner') {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
const { data: _, ...rest } = credential;
|
||||||
const { id, data: _, ...rest } = credential;
|
return { ...rest };
|
||||||
|
|
||||||
// @TODO_TECH_DEBT: Stringify `id` with entity field transformer
|
|
||||||
return { id: id.toString(), ...rest };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
const { data: _, ...rest } = credential;
|
||||||
const { id, data: _, ...rest } = credential;
|
|
||||||
|
|
||||||
const key = await EECredentials.getEncryptionKey();
|
const key = await EECredentials.getEncryptionKey();
|
||||||
const decryptedData = EECredentials.redact(
|
const decryptedData = EECredentials.redact(
|
||||||
|
@ -102,8 +87,7 @@ EECredentialsController.get(
|
||||||
credential,
|
credential,
|
||||||
);
|
);
|
||||||
|
|
||||||
// @TODO_TECH_DEBT: Stringify `id` with entity field transformer
|
return { data: decryptedData, ...rest };
|
||||||
return { id: id.toString(), data: decryptedData, ...rest };
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -119,9 +103,10 @@ EECredentialsController.post(
|
||||||
|
|
||||||
const encryptionKey = await EECredentials.getEncryptionKey();
|
const encryptionKey = await EECredentials.getEncryptionKey();
|
||||||
|
|
||||||
const { ownsCredential } = await EECredentials.isOwned(req.user, credentials.id.toString());
|
const credentialId = credentials.id;
|
||||||
|
const { ownsCredential } = await EECredentials.isOwned(req.user, credentialId);
|
||||||
|
|
||||||
const sharing = await EECredentials.getSharing(req.user, credentials.id);
|
const sharing = await EECredentials.getSharing(req.user, credentialId);
|
||||||
if (!ownsCredential) {
|
if (!ownsCredential) {
|
||||||
if (!sharing) {
|
if (!sharing) {
|
||||||
throw new ResponseHelper.UnauthorizedError('Forbidden');
|
throw new ResponseHelper.UnauthorizedError('Forbidden');
|
||||||
|
@ -161,7 +146,6 @@ EECredentialsController.put(
|
||||||
}
|
}
|
||||||
|
|
||||||
const { ownsCredential, credential } = await EECredentials.isOwned(req.user, credentialId);
|
const { ownsCredential, credential } = await EECredentials.isOwned(req.user, credentialId);
|
||||||
|
|
||||||
if (!ownsCredential || !credential) {
|
if (!ownsCredential || !credential) {
|
||||||
throw new ResponseHelper.UnauthorizedError('Forbidden');
|
throw new ResponseHelper.UnauthorizedError('Forbidden');
|
||||||
}
|
}
|
||||||
|
@ -191,7 +175,7 @@ EECredentialsController.put(
|
||||||
|
|
||||||
void InternalHooksManager.getInstance().onUserSharedCredentials({
|
void InternalHooksManager.getInstance().onUserSharedCredentials({
|
||||||
credential_type: credential.type,
|
credential_type: credential.type,
|
||||||
credential_id: credential.id.toString(),
|
credential_id: credential.id,
|
||||||
user_id_sharer: req.user.id,
|
user_id_sharer: req.user.id,
|
||||||
user_ids_sharees_added: newShareeIds,
|
user_ids_sharees_added: newShareeIds,
|
||||||
sharees_removed: amountRemoved,
|
sharees_removed: amountRemoved,
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { getLogger } from '@/Logger';
|
||||||
import { EECredentialsController } from './credentials.controller.ee';
|
import { EECredentialsController } from './credentials.controller.ee';
|
||||||
import { CredentialsService } from './credentials.service';
|
import { CredentialsService } from './credentials.service';
|
||||||
|
|
||||||
import type { ICredentialsResponse } from '@/Interfaces';
|
import type { ICredentialsDb } from '@/Interfaces';
|
||||||
import type { CredentialRequest } from '@/requests';
|
import type { CredentialRequest } from '@/requests';
|
||||||
|
|
||||||
export const credentialsController = express.Router();
|
export const credentialsController = express.Router();
|
||||||
|
@ -35,14 +35,8 @@ credentialsController.use('/', EECredentialsController);
|
||||||
*/
|
*/
|
||||||
credentialsController.get(
|
credentialsController.get(
|
||||||
'/',
|
'/',
|
||||||
ResponseHelper.send(async (req: CredentialRequest.GetAll): Promise<ICredentialsResponse[]> => {
|
ResponseHelper.send(async (req: CredentialRequest.GetAll): Promise<ICredentialsDb[]> => {
|
||||||
const credentials = await CredentialsService.getAll(req.user, { roles: ['owner'] });
|
return CredentialsService.getAll(req.user, { roles: ['owner'] });
|
||||||
|
|
||||||
return credentials.map((credential) => {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
credential.id = credential.id.toString();
|
|
||||||
return credential as ICredentialsResponse;
|
|
||||||
});
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -69,15 +63,11 @@ credentialsController.get(
|
||||||
* GET /credentials/:id
|
* GET /credentials/:id
|
||||||
*/
|
*/
|
||||||
credentialsController.get(
|
credentialsController.get(
|
||||||
'/:id',
|
'/:id(\\d+)',
|
||||||
ResponseHelper.send(async (req: CredentialRequest.Get) => {
|
ResponseHelper.send(async (req: CredentialRequest.Get) => {
|
||||||
const { id: credentialId } = req.params;
|
const { id: credentialId } = req.params;
|
||||||
const includeDecryptedData = req.query.includeData === 'true';
|
const includeDecryptedData = req.query.includeData === 'true';
|
||||||
|
|
||||||
if (Number.isNaN(Number(credentialId))) {
|
|
||||||
throw new ResponseHelper.BadRequestError('Credential ID must be a number.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const sharing = await CredentialsService.getSharing(req.user, credentialId, ['credentials']);
|
const sharing = await CredentialsService.getSharing(req.user, credentialId, ['credentials']);
|
||||||
|
|
||||||
if (!sharing) {
|
if (!sharing) {
|
||||||
|
@ -88,11 +78,10 @@ credentialsController.get(
|
||||||
|
|
||||||
const { credentials: credential } = sharing;
|
const { credentials: credential } = sharing;
|
||||||
|
|
||||||
const { id, data: _, ...rest } = credential;
|
const { data: _, ...rest } = credential;
|
||||||
|
|
||||||
if (!includeDecryptedData) {
|
if (!includeDecryptedData) {
|
||||||
// @TODO_TECH_DEBT: Stringify `id` with entity field transformer
|
return { ...rest };
|
||||||
return { id: id.toString(), ...rest };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = await CredentialsService.getEncryptionKey();
|
const key = await CredentialsService.getEncryptionKey();
|
||||||
|
@ -101,8 +90,7 @@ credentialsController.get(
|
||||||
credential,
|
credential,
|
||||||
);
|
);
|
||||||
|
|
||||||
// @TODO_TECH_DEBT: Stringify `id` with entity field transformer
|
return { data: decryptedData, ...rest };
|
||||||
return { id: id.toString(), data: decryptedData, ...rest };
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -139,15 +127,15 @@ credentialsController.post(
|
||||||
|
|
||||||
const key = await CredentialsService.getEncryptionKey();
|
const key = await CredentialsService.getEncryptionKey();
|
||||||
const encryptedData = CredentialsService.createEncryptedData(key, null, newCredential);
|
const encryptedData = CredentialsService.createEncryptedData(key, null, newCredential);
|
||||||
const { id, ...rest } = await CredentialsService.save(newCredential, encryptedData, req.user);
|
const credential = await CredentialsService.save(newCredential, encryptedData, req.user);
|
||||||
|
|
||||||
void InternalHooksManager.getInstance().onUserCreatedCredentials({
|
void InternalHooksManager.getInstance().onUserCreatedCredentials({
|
||||||
credential_type: rest.type,
|
credential_type: credential.type,
|
||||||
credential_id: id.toString(),
|
credential_id: credential.id,
|
||||||
public_api: false,
|
public_api: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { id: id.toString(), ...rest };
|
return credential;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -155,8 +143,8 @@ credentialsController.post(
|
||||||
* PATCH /credentials/:id
|
* PATCH /credentials/:id
|
||||||
*/
|
*/
|
||||||
credentialsController.patch(
|
credentialsController.patch(
|
||||||
'/:id',
|
'/:id(\\d+)',
|
||||||
ResponseHelper.send(async (req: CredentialRequest.Update): Promise<ICredentialsResponse> => {
|
ResponseHelper.send(async (req: CredentialRequest.Update): Promise<ICredentialsDb> => {
|
||||||
const { id: credentialId } = req.params;
|
const { id: credentialId } = req.params;
|
||||||
|
|
||||||
const sharing = await CredentialsService.getSharing(req.user, credentialId);
|
const sharing = await CredentialsService.getSharing(req.user, credentialId);
|
||||||
|
@ -194,14 +182,11 @@ credentialsController.patch(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the encrypted data as it is not needed in the frontend
|
// Remove the encrypted data as it is not needed in the frontend
|
||||||
const { id, data: _, ...rest } = responseData;
|
const { data: _, ...rest } = responseData;
|
||||||
|
|
||||||
LoggerProxy.verbose('Credential updated', { credentialId });
|
LoggerProxy.verbose('Credential updated', { credentialId });
|
||||||
|
|
||||||
return {
|
return { ...rest };
|
||||||
id: id.toString(),
|
|
||||||
...rest,
|
|
||||||
};
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -209,7 +194,7 @@ credentialsController.patch(
|
||||||
* DELETE /credentials/:id
|
* DELETE /credentials/:id
|
||||||
*/
|
*/
|
||||||
credentialsController.delete(
|
credentialsController.delete(
|
||||||
'/:id',
|
'/:id(\\d+)',
|
||||||
ResponseHelper.send(async (req: CredentialRequest.Delete) => {
|
ResponseHelper.send(async (req: CredentialRequest.Delete) => {
|
||||||
const { id: credentialId } = req.params;
|
const { id: credentialId } = req.params;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
import { DeleteResult, EntityManager, FindOneOptions, In, Not, ObjectLiteral } from 'typeorm';
|
import { DeleteResult, EntityManager, FindConditions, In, Not } from 'typeorm';
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import { RoleService } from '@/role/role.service';
|
import { RoleService } from '@/role/role.service';
|
||||||
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
import { CredentialsEntity } from '@db/entities/CredentialsEntity';
|
||||||
|
@ -30,35 +30,31 @@ export class EECredentialsService extends CredentialsService {
|
||||||
*/
|
*/
|
||||||
static async getSharing(
|
static async getSharing(
|
||||||
user: User,
|
user: User,
|
||||||
credentialId: number | string,
|
credentialId: string,
|
||||||
relations: string[] = ['credentials'],
|
relations: string[] = ['credentials'],
|
||||||
{ allowGlobalOwner } = { allowGlobalOwner: true },
|
{ allowGlobalOwner } = { allowGlobalOwner: true },
|
||||||
): Promise<SharedCredentials | undefined> {
|
): Promise<SharedCredentials | undefined> {
|
||||||
const options: FindOneOptions<SharedCredentials> & { where: ObjectLiteral } = {
|
const where: FindConditions<SharedCredentials> = { credentialsId: credentialId };
|
||||||
where: {
|
|
||||||
credentials: { id: credentialId },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Omit user from where if the requesting user is the global
|
// Omit user from where if the requesting user is the global
|
||||||
// owner. This allows the global owner to view and delete
|
// owner. This allows the global owner to view and delete
|
||||||
// credentials they don't own.
|
// credentials they don't own.
|
||||||
if (!allowGlobalOwner || user.globalRole.name !== 'owner') {
|
if (!allowGlobalOwner || user.globalRole.name !== 'owner') {
|
||||||
options.where.user = { id: user.id };
|
where.user = { id: user.id };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relations?.length) {
|
return Db.collections.SharedCredentials.findOne({
|
||||||
options.relations = relations;
|
where,
|
||||||
}
|
relations,
|
||||||
|
});
|
||||||
return Db.collections.SharedCredentials.findOne(options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getSharings(
|
static async getSharings(
|
||||||
transaction: EntityManager,
|
transaction: EntityManager,
|
||||||
credentialId: string,
|
credentialId: string,
|
||||||
): Promise<SharedCredentials[]> {
|
): Promise<SharedCredentials[]> {
|
||||||
const credential = await transaction.findOne(CredentialsEntity, credentialId, {
|
const credential = await transaction.findOne(CredentialsEntity, {
|
||||||
|
where: { id: credentialId },
|
||||||
relations: ['shared'],
|
relations: ['shared'],
|
||||||
});
|
});
|
||||||
return credential?.shared ?? [];
|
return credential?.shared ?? [];
|
||||||
|
@ -69,10 +65,11 @@ export class EECredentialsService extends CredentialsService {
|
||||||
credentialId: string,
|
credentialId: string,
|
||||||
userIds: string[],
|
userIds: string[],
|
||||||
): Promise<DeleteResult> {
|
): Promise<DeleteResult> {
|
||||||
return transaction.delete(SharedCredentials, {
|
const conditions: FindConditions<SharedCredentials> = {
|
||||||
credentials: { id: credentialId },
|
credentialsId: credentialId,
|
||||||
user: { id: Not(In(userIds)) },
|
userId: Not(In(userIds)),
|
||||||
});
|
};
|
||||||
|
return transaction.delete(SharedCredentials, conditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async share(
|
static async share(
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
LoggerProxy,
|
LoggerProxy,
|
||||||
NodeHelpers,
|
NodeHelpers,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { FindManyOptions, FindOneOptions, In } from 'typeorm';
|
import { FindConditions, FindManyOptions, In } from 'typeorm';
|
||||||
|
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import * as ResponseHelper from '@/ResponseHelper';
|
import * as ResponseHelper from '@/ResponseHelper';
|
||||||
|
@ -59,22 +59,13 @@ export class CredentialsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if member, return credentials owned by or shared with member
|
// if member, return credentials owned by or shared with member
|
||||||
|
const userSharings = await Db.collections.SharedCredentials.find({
|
||||||
const whereConditions: FindManyOptions = {
|
|
||||||
where: {
|
where: {
|
||||||
user,
|
userId: user.id,
|
||||||
|
...(options?.roles?.length ? { role: { name: In(options.roles) } } : {}),
|
||||||
},
|
},
|
||||||
};
|
relations: options?.roles?.length ? ['role'] : [],
|
||||||
|
});
|
||||||
if (options?.roles?.length) {
|
|
||||||
whereConditions.where = {
|
|
||||||
...whereConditions.where,
|
|
||||||
role: { name: In(options.roles) },
|
|
||||||
} as FindManyOptions;
|
|
||||||
whereConditions.relations = ['role'];
|
|
||||||
}
|
|
||||||
|
|
||||||
const userSharings = await Db.collections.SharedCredentials.find(whereConditions);
|
|
||||||
|
|
||||||
return Db.collections.Credentials.find({
|
return Db.collections.Credentials.find({
|
||||||
select: SELECT_FIELDS,
|
select: SELECT_FIELDS,
|
||||||
|
@ -94,35 +85,26 @@ export class CredentialsService {
|
||||||
*/
|
*/
|
||||||
static async getSharing(
|
static async getSharing(
|
||||||
user: User,
|
user: User,
|
||||||
credentialId: number | string,
|
credentialId: string,
|
||||||
relations: string[] = ['credentials'],
|
relations: string[] = ['credentials'],
|
||||||
{ allowGlobalOwner } = { allowGlobalOwner: true },
|
{ allowGlobalOwner } = { allowGlobalOwner: true },
|
||||||
): Promise<SharedCredentials | undefined> {
|
): Promise<SharedCredentials | undefined> {
|
||||||
const options: FindOneOptions = {
|
const where: FindConditions<SharedCredentials> = { credentialsId: credentialId };
|
||||||
where: {
|
|
||||||
credentials: { id: credentialId },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Omit user from where if the requesting user is the global
|
// Omit user from where if the requesting user is the global
|
||||||
// owner. This allows the global owner to view and delete
|
// owner. This allows the global owner to view and delete
|
||||||
// credentials they don't own.
|
// credentials they don't own.
|
||||||
if (!allowGlobalOwner || user.globalRole.name !== 'owner') {
|
if (!allowGlobalOwner || user.globalRole.name !== 'owner') {
|
||||||
options.where = {
|
Object.assign(where, {
|
||||||
...options.where,
|
userId: user.id,
|
||||||
user: { id: user.id },
|
|
||||||
role: { name: 'owner' },
|
role: { name: 'owner' },
|
||||||
} as FindOneOptions;
|
});
|
||||||
if (!relations.includes('role')) {
|
if (!relations.includes('role')) {
|
||||||
relations.push('role');
|
relations.push('role');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relations?.length) {
|
return Db.collections.SharedCredentials.findOne({ where, relations });
|
||||||
options.relations = relations;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Db.collections.SharedCredentials.findOne(options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async prepareCreateData(
|
static async prepareCreateData(
|
||||||
|
@ -132,7 +114,7 @@ export class CredentialsService {
|
||||||
const { id, ...rest } = data;
|
const { id, ...rest } = data;
|
||||||
|
|
||||||
// This saves us a merge but requires some type casting. These
|
// This saves us a merge but requires some type casting. These
|
||||||
// types are compatiable for this case.
|
// types are compatible for this case.
|
||||||
const newCredentials = Db.collections.Credentials.create(
|
const newCredentials = Db.collections.Credentials.create(
|
||||||
rest as ICredentialsDb,
|
rest as ICredentialsDb,
|
||||||
) as CredentialsEntity;
|
) as CredentialsEntity;
|
||||||
|
@ -182,11 +164,11 @@ export class CredentialsService {
|
||||||
|
|
||||||
static createEncryptedData(
|
static createEncryptedData(
|
||||||
encryptionKey: string,
|
encryptionKey: string,
|
||||||
credentialsId: string | null,
|
credentialId: string | null,
|
||||||
data: CredentialsEntity,
|
data: CredentialsEntity,
|
||||||
): ICredentialsDb {
|
): ICredentialsDb {
|
||||||
const credentials = new Credentials(
|
const credentials = new Credentials(
|
||||||
{ id: credentialsId, name: data.name },
|
{ id: credentialId, name: data.name },
|
||||||
data.type,
|
data.type,
|
||||||
data.nodesAccess,
|
data.nodesAccess,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import type { ICredentialNodeAccess } from 'n8n-workflow';
|
import type { ICredentialNodeAccess } from 'n8n-workflow';
|
||||||
import { Column, Entity, Index, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Entity, Generated, Index, OneToMany, PrimaryColumn } from 'typeorm';
|
||||||
import { IsArray, IsObject, IsString, Length } from 'class-validator';
|
import { IsArray, IsObject, IsString, Length } from 'class-validator';
|
||||||
import type { SharedCredentials } from './SharedCredentials';
|
import type { SharedCredentials } from './SharedCredentials';
|
||||||
import { AbstractEntity, jsonColumnType } from './AbstractEntity';
|
import { AbstractEntity, jsonColumnType } from './AbstractEntity';
|
||||||
import type { ICredentialsDb } from '@/Interfaces';
|
import type { ICredentialsDb } from '@/Interfaces';
|
||||||
|
import { idStringifier } from '../utils/transformers';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class CredentialsEntity extends AbstractEntity implements ICredentialsDb {
|
export class CredentialsEntity extends AbstractEntity implements ICredentialsDb {
|
||||||
@PrimaryGeneratedColumn()
|
@Generated()
|
||||||
id: number;
|
@PrimaryColumn({ transformer: idStringifier })
|
||||||
|
id: string;
|
||||||
|
|
||||||
@Column({ length: 128 })
|
@Column({ length: 128 })
|
||||||
@IsString({ message: 'Credential `name` must be of type string.' })
|
@IsString({ message: 'Credential `name` must be of type string.' })
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import type { WorkflowExecuteMode } from 'n8n-workflow';
|
import type { WorkflowExecuteMode } from 'n8n-workflow';
|
||||||
import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
|
import { Column, Entity, Generated, Index, PrimaryColumn } from 'typeorm';
|
||||||
import { datetimeColumnType, jsonColumnType } from './AbstractEntity';
|
import { datetimeColumnType, jsonColumnType } from './AbstractEntity';
|
||||||
import type { IExecutionFlattedDb, IWorkflowDb } from '@/Interfaces';
|
import type { IExecutionFlattedDb, IWorkflowDb } from '@/Interfaces';
|
||||||
|
import { idStringifier } from '../utils/transformers';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
@Index(['workflowId', 'id'])
|
@Index(['workflowId', 'id'])
|
||||||
|
@ -10,8 +11,9 @@ import type { IExecutionFlattedDb, IWorkflowDb } from '@/Interfaces';
|
||||||
@Index(['workflowId', 'finished', 'id'])
|
@Index(['workflowId', 'finished', 'id'])
|
||||||
@Index(['workflowId', 'waitTill', 'id'])
|
@Index(['workflowId', 'waitTill', 'id'])
|
||||||
export class ExecutionEntity implements IExecutionFlattedDb {
|
export class ExecutionEntity implements IExecutionFlattedDb {
|
||||||
@PrimaryGeneratedColumn()
|
@Generated()
|
||||||
id: number;
|
@PrimaryColumn({ transformer: idStringifier })
|
||||||
|
id: string;
|
||||||
|
|
||||||
@Column('text')
|
@Column('text')
|
||||||
data: string;
|
data: string;
|
||||||
|
@ -38,7 +40,7 @@ export class ExecutionEntity implements IExecutionFlattedDb {
|
||||||
@Column(jsonColumnType)
|
@Column(jsonColumnType)
|
||||||
workflowData: IWorkflowDb;
|
workflowData: IWorkflowDb;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true, transformer: idStringifier })
|
||||||
workflowId: string;
|
workflowId: string;
|
||||||
|
|
||||||
@Column({ type: datetimeColumnType, nullable: true })
|
@Column({ type: datetimeColumnType, nullable: true })
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type { CredentialsEntity } from './CredentialsEntity';
|
||||||
import type { User } from './User';
|
import type { User } from './User';
|
||||||
import type { Role } from './Role';
|
import type { Role } from './Role';
|
||||||
import { AbstractEntity } from './AbstractEntity';
|
import { AbstractEntity } from './AbstractEntity';
|
||||||
|
import { idStringifier } from '../utils/transformers';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class SharedCredentials extends AbstractEntity {
|
export class SharedCredentials extends AbstractEntity {
|
||||||
|
@ -22,7 +23,7 @@ export class SharedCredentials extends AbstractEntity {
|
||||||
})
|
})
|
||||||
credentials: CredentialsEntity;
|
credentials: CredentialsEntity;
|
||||||
|
|
||||||
@PrimaryColumn()
|
@PrimaryColumn({ transformer: idStringifier })
|
||||||
@RelationId((sharedCredential: SharedCredentials) => sharedCredential.credentials)
|
@RelationId((sharedCredential: SharedCredentials) => sharedCredential.credentials)
|
||||||
credentialsId: number;
|
credentialsId: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type { WorkflowEntity } from './WorkflowEntity';
|
||||||
import type { User } from './User';
|
import type { User } from './User';
|
||||||
import type { Role } from './Role';
|
import type { Role } from './Role';
|
||||||
import { AbstractEntity } from './AbstractEntity';
|
import { AbstractEntity } from './AbstractEntity';
|
||||||
|
import { idStringifier } from '../utils/transformers';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class SharedWorkflow extends AbstractEntity {
|
export class SharedWorkflow extends AbstractEntity {
|
||||||
|
@ -22,7 +23,7 @@ export class SharedWorkflow extends AbstractEntity {
|
||||||
})
|
})
|
||||||
workflow: WorkflowEntity;
|
workflow: WorkflowEntity;
|
||||||
|
|
||||||
@PrimaryColumn()
|
@PrimaryColumn({ transformer: idStringifier })
|
||||||
@RelationId((sharedWorkflow: SharedWorkflow) => sharedWorkflow.workflow)
|
@RelationId((sharedWorkflow: SharedWorkflow) => sharedWorkflow.workflow)
|
||||||
workflowId: number;
|
workflowId: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
import { Column, Entity, Generated, Index, ManyToMany, PrimaryColumn } from 'typeorm';
|
import { Column, Entity, Generated, Index, ManyToMany, PrimaryColumn } from 'typeorm';
|
||||||
import { IsString, Length } from 'class-validator';
|
import { IsString, Length } from 'class-validator';
|
||||||
|
|
||||||
import type { ITagDb } from '@/Interfaces';
|
|
||||||
import { idStringifier } from '../utils/transformers';
|
import { idStringifier } from '../utils/transformers';
|
||||||
import type { WorkflowEntity } from './WorkflowEntity';
|
import type { WorkflowEntity } from './WorkflowEntity';
|
||||||
import { AbstractEntity } from './AbstractEntity';
|
import { AbstractEntity } from './AbstractEntity';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class TagEntity extends AbstractEntity implements ITagDb {
|
export class TagEntity extends AbstractEntity {
|
||||||
@Generated()
|
@Generated()
|
||||||
@PrimaryColumn({
|
@PrimaryColumn({ transformer: idStringifier })
|
||||||
transformer: idStringifier,
|
id: string;
|
||||||
})
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@Column({ length: 24 })
|
@Column({ length: 24 })
|
||||||
@Index({ unique: true })
|
@Index({ unique: true })
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { Column, Entity, Index, PrimaryColumn } from 'typeorm';
|
import { Column, Entity, Index, PrimaryColumn } from 'typeorm';
|
||||||
|
|
||||||
import { IWebhookDb } from '@/Interfaces';
|
import { idStringifier } from '../utils/transformers';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
@Index(['webhookId', 'method', 'pathLength'])
|
@Index(['webhookId', 'method', 'pathLength'])
|
||||||
export class WebhookEntity implements IWebhookDb {
|
export class WebhookEntity {
|
||||||
@Column()
|
@Column({ transformer: idStringifier })
|
||||||
workflowId: number;
|
workflowId: string;
|
||||||
|
|
||||||
@PrimaryColumn()
|
@PrimaryColumn()
|
||||||
webhookPath: string;
|
webhookPath: string;
|
||||||
|
@ -18,8 +18,8 @@ export class WebhookEntity implements IWebhookDb {
|
||||||
node: string;
|
node: string;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
webhookId: string;
|
webhookId?: string;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
pathLength: number;
|
pathLength?: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,26 +12,28 @@ import type {
|
||||||
import {
|
import {
|
||||||
Column,
|
Column,
|
||||||
Entity,
|
Entity,
|
||||||
|
Generated,
|
||||||
Index,
|
Index,
|
||||||
JoinColumn,
|
JoinColumn,
|
||||||
JoinTable,
|
JoinTable,
|
||||||
ManyToMany,
|
ManyToMany,
|
||||||
OneToMany,
|
OneToMany,
|
||||||
PrimaryGeneratedColumn,
|
PrimaryColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
import type { TagEntity } from './TagEntity';
|
import type { TagEntity } from './TagEntity';
|
||||||
import type { SharedWorkflow } from './SharedWorkflow';
|
import type { SharedWorkflow } from './SharedWorkflow';
|
||||||
import type { WorkflowStatistics } from './WorkflowStatistics';
|
import type { WorkflowStatistics } from './WorkflowStatistics';
|
||||||
import { objectRetriever, sqlite } from '../utils/transformers';
|
import { idStringifier, objectRetriever, sqlite } from '../utils/transformers';
|
||||||
import { AbstractEntity, jsonColumnType } from './AbstractEntity';
|
import { AbstractEntity, jsonColumnType } from './AbstractEntity';
|
||||||
import type { IWorkflowDb } from '@/Interfaces';
|
import type { IWorkflowDb } from '@/Interfaces';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class WorkflowEntity extends AbstractEntity implements IWorkflowDb {
|
export class WorkflowEntity extends AbstractEntity implements IWorkflowDb {
|
||||||
@PrimaryGeneratedColumn()
|
@Generated()
|
||||||
id: number;
|
@PrimaryColumn({ transformer: idStringifier })
|
||||||
|
id: string;
|
||||||
|
|
||||||
// TODO: Add XSS check
|
// TODO: Add XSS check
|
||||||
@Index({ unique: true })
|
@Index({ unique: true })
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Column, Entity, RelationId, ManyToOne, PrimaryColumn } from 'typeorm';
|
import { Column, Entity, RelationId, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||||
|
import { idStringifier } from '../utils/transformers';
|
||||||
import { datetimeColumnType } from './AbstractEntity';
|
import { datetimeColumnType } from './AbstractEntity';
|
||||||
import type { WorkflowEntity } from './WorkflowEntity';
|
import type { WorkflowEntity } from './WorkflowEntity';
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ export class WorkflowStatistics {
|
||||||
})
|
})
|
||||||
workflow: WorkflowEntity;
|
workflow: WorkflowEntity;
|
||||||
|
|
||||||
|
@PrimaryColumn({ transformer: idStringifier })
|
||||||
@RelationId((workflowStatistics: WorkflowStatistics) => workflowStatistics.workflow)
|
@RelationId((workflowStatistics: WorkflowStatistics) => workflowStatistics.workflow)
|
||||||
@PrimaryColumn()
|
workflowId: string;
|
||||||
workflowId: number;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ export class UpdateWorkflowCredentials1630451444017 implements MigrationInterfac
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
(credentials) => credentials.name === name && credentials.type === type,
|
(credentials) => credentials.name === name && credentials.type === type,
|
||||||
);
|
);
|
||||||
node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name };
|
node.credentials[type] = { id: matchingCredentials?.id || null, name };
|
||||||
credentialsUpdated = true;
|
credentialsUpdated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ export class UpdateWorkflowCredentials1630451444017 implements MigrationInterfac
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
(credentials) => credentials.name === name && credentials.type === type,
|
(credentials) => credentials.name === name && credentials.type === type,
|
||||||
);
|
);
|
||||||
node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name };
|
node.credentials[type] = { id: matchingCredentials?.id || null, name };
|
||||||
credentialsUpdated = true;
|
credentialsUpdated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ export class UpdateWorkflowCredentials1630451444017 implements MigrationInterfac
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
(credentials) => credentials.name === name && credentials.type === type,
|
(credentials) => credentials.name === name && credentials.type === type,
|
||||||
);
|
);
|
||||||
node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name };
|
node.credentials[type] = { id: matchingCredentials?.id || null, name };
|
||||||
credentialsUpdated = true;
|
credentialsUpdated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ export class UpdateWorkflowCredentials1630419189837 implements MigrationInterfac
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
(credentials) => credentials.name === name && credentials.type === type,
|
(credentials) => credentials.name === name && credentials.type === type,
|
||||||
);
|
);
|
||||||
node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name };
|
node.credentials[type] = { id: matchingCredentials?.id || null, name };
|
||||||
credentialsUpdated = true;
|
credentialsUpdated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ export class UpdateWorkflowCredentials1630419189837 implements MigrationInterfac
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
(credentials) => credentials.name === name && credentials.type === type,
|
(credentials) => credentials.name === name && credentials.type === type,
|
||||||
);
|
);
|
||||||
node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name };
|
node.credentials[type] = { id: matchingCredentials?.id || null, name };
|
||||||
credentialsUpdated = true;
|
credentialsUpdated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ export class UpdateWorkflowCredentials1630419189837 implements MigrationInterfac
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
(credentials) => credentials.name === name && credentials.type === type,
|
(credentials) => credentials.name === name && credentials.type === type,
|
||||||
);
|
);
|
||||||
node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name };
|
node.credentials[type] = { id: matchingCredentials?.id || null, name };
|
||||||
credentialsUpdated = true;
|
credentialsUpdated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ export class UpdateWorkflowCredentials1630330987096 implements MigrationInterfac
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
(credentials) => credentials.name === name && credentials.type === type,
|
(credentials) => credentials.name === name && credentials.type === type,
|
||||||
);
|
);
|
||||||
node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name };
|
node.credentials[type] = { id: matchingCredentials?.id || null, name };
|
||||||
credentialsUpdated = true;
|
credentialsUpdated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ export class UpdateWorkflowCredentials1630330987096 implements MigrationInterfac
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
(credentials) => credentials.name === name && credentials.type === type,
|
(credentials) => credentials.name === name && credentials.type === type,
|
||||||
);
|
);
|
||||||
node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name };
|
node.credentials[type] = { id: matchingCredentials?.id || null, name };
|
||||||
credentialsUpdated = true;
|
credentialsUpdated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,7 +126,7 @@ export class UpdateWorkflowCredentials1630330987096 implements MigrationInterfac
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
(credentials) => credentials.name === name && credentials.type === type,
|
(credentials) => credentials.name === name && credentials.type === type,
|
||||||
);
|
);
|
||||||
node.credentials[type] = { id: matchingCredentials?.id.toString() || null, name };
|
node.credentials[type] = { id: matchingCredentials?.id || null, name };
|
||||||
credentialsUpdated = true;
|
credentialsUpdated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { jsonParse } from 'n8n-workflow';
|
import { jsonParse } from 'n8n-workflow';
|
||||||
import { ValueTransformer } from 'typeorm';
|
import type { ValueTransformer, FindOperator } from 'typeorm';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
|
|
||||||
export const idStringifier = {
|
export const idStringifier = {
|
||||||
from: (value: number): string | number => (typeof value === 'number' ? value.toString() : value),
|
from: (value?: number): string | undefined => value?.toString(),
|
||||||
to: (value: string): number | string => (typeof value === 'string' ? Number(value) : value),
|
to: (
|
||||||
|
value: string | FindOperator<unknown> | undefined,
|
||||||
|
): number | FindOperator<unknown> | undefined =>
|
||||||
|
typeof value === 'string' ? Number(value) : value,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const lowerCaser = {
|
export const lowerCaser = {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { INode, IRun, IWorkflowBase, LoggerProxy } from 'n8n-workflow';
|
import type { INode, IRun, IWorkflowBase } from 'n8n-workflow';
|
||||||
import * as Db from '@/Db';
|
import * as Db from '@/Db';
|
||||||
import { InternalHooksManager } from '@/InternalHooksManager';
|
import { InternalHooksManager } from '@/InternalHooksManager';
|
||||||
import { StatisticsNames } from '@/databases/entities/WorkflowStatistics';
|
import { StatisticsNames } from '@/databases/entities/WorkflowStatistics';
|
||||||
|
@ -22,14 +22,8 @@ export async function workflowExecutionCompleted(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the workflow id
|
// Get the workflow id
|
||||||
let workflowId: number;
|
const workflowId = workflowData.id;
|
||||||
try {
|
if (workflowId === undefined) return;
|
||||||
workflowId = parseInt(workflowData.id as string, 10);
|
|
||||||
if (isNaN(workflowId)) throw new Error('not a number');
|
|
||||||
} catch (error) {
|
|
||||||
LoggerProxy.error(`Error "${error as string}" when casting workflow ID to a number`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try insertion and if it fails due to key conflicts then update the existing entry instead
|
// Try insertion and if it fails due to key conflicts then update the existing entry instead
|
||||||
try {
|
try {
|
||||||
|
@ -62,19 +56,9 @@ export async function workflowExecutionCompleted(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function nodeFetchedData(workflowId: string, node: INode): Promise<void> {
|
export async function nodeFetchedData(workflowId: string, node: INode): Promise<void> {
|
||||||
// Get the workflow id
|
|
||||||
let id: number;
|
|
||||||
try {
|
|
||||||
id = parseInt(workflowId, 10);
|
|
||||||
if (isNaN(id)) throw new Error('not a number');
|
|
||||||
} catch (error) {
|
|
||||||
LoggerProxy.error(`Error ${error as string} when casting workflow ID to a number`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update only if necessary
|
// Update only if necessary
|
||||||
const response = await Db.collections.Workflow.update(
|
const response = await Db.collections.Workflow.update(
|
||||||
{ id, dataLoaded: false },
|
{ id: workflowId, dataLoaded: false },
|
||||||
{ dataLoaded: true },
|
{ dataLoaded: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -85,7 +69,7 @@ export async function nodeFetchedData(workflowId: string, node: INode): Promise<
|
||||||
const owner = await getWorkflowOwner(workflowId);
|
const owner = await getWorkflowOwner(workflowId);
|
||||||
let metrics = {
|
let metrics = {
|
||||||
user_id: owner.id,
|
user_id: owner.id,
|
||||||
workflow_id: id,
|
workflow_id: workflowId,
|
||||||
node_type: node.type,
|
node_type: node.type,
|
||||||
node_id: node.id,
|
node_id: node.id,
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,7 +36,7 @@ EEExecutionsController.get(
|
||||||
* GET /executions/:id
|
* GET /executions/:id
|
||||||
*/
|
*/
|
||||||
EEExecutionsController.get(
|
EEExecutionsController.get(
|
||||||
'/:id',
|
'/:id(\\d+)',
|
||||||
ResponseHelper.send(
|
ResponseHelper.send(
|
||||||
async (
|
async (
|
||||||
req: ExecutionRequest.Get,
|
req: ExecutionRequest.Get,
|
||||||
|
|
|
@ -41,7 +41,7 @@ executionsController.get(
|
||||||
* GET /executions/:id
|
* GET /executions/:id
|
||||||
*/
|
*/
|
||||||
executionsController.get(
|
executionsController.get(
|
||||||
'/:id',
|
'/:id(\\d+)',
|
||||||
ResponseHelper.send(
|
ResponseHelper.send(
|
||||||
async (
|
async (
|
||||||
req: ExecutionRequest.Get,
|
req: ExecutionRequest.Get,
|
||||||
|
|
|
@ -3,7 +3,15 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
import { validate as jsonSchemaValidate } from 'jsonschema';
|
import { validate as jsonSchemaValidate } from 'jsonschema';
|
||||||
import { BinaryDataManager } from 'n8n-core';
|
import { BinaryDataManager } from 'n8n-core';
|
||||||
import { deepCopy, IDataObject, LoggerProxy, JsonObject, jsonParse, Workflow } from 'n8n-workflow';
|
import {
|
||||||
|
deepCopy,
|
||||||
|
IDataObject,
|
||||||
|
IWorkflowBase,
|
||||||
|
LoggerProxy,
|
||||||
|
JsonObject,
|
||||||
|
jsonParse,
|
||||||
|
Workflow,
|
||||||
|
} from 'n8n-workflow';
|
||||||
import { FindOperator, In, IsNull, LessThanOrEqual, Not, Raw } from 'typeorm';
|
import { FindOperator, In, IsNull, LessThanOrEqual, Not, Raw } from 'typeorm';
|
||||||
import * as ActiveExecutions from '@/ActiveExecutions';
|
import * as ActiveExecutions from '@/ActiveExecutions';
|
||||||
import config from '@/config';
|
import config from '@/config';
|
||||||
|
@ -12,7 +20,6 @@ import {
|
||||||
IExecutionFlattedResponse,
|
IExecutionFlattedResponse,
|
||||||
IExecutionResponse,
|
IExecutionResponse,
|
||||||
IExecutionsListResponse,
|
IExecutionsListResponse,
|
||||||
IWorkflowBase,
|
|
||||||
IWorkflowExecutionDataProcess,
|
IWorkflowExecutionDataProcess,
|
||||||
} from '@/Interfaces';
|
} from '@/Interfaces';
|
||||||
import { NodeTypes } from '@/NodeTypes';
|
import { NodeTypes } from '@/NodeTypes';
|
||||||
|
@ -31,7 +38,7 @@ interface IGetExecutionsQueryFilter {
|
||||||
mode?: string;
|
mode?: string;
|
||||||
retryOf?: string;
|
retryOf?: string;
|
||||||
retrySuccessId?: string;
|
retrySuccessId?: string;
|
||||||
workflowId?: number | string;
|
workflowId?: string;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
waitTill?: FindOperator<any> | boolean;
|
waitTill?: FindOperator<any> | boolean;
|
||||||
}
|
}
|
||||||
|
@ -247,7 +254,7 @@ export class ExecutionsService {
|
||||||
|
|
||||||
const formattedExecutions = executions.map((execution) => {
|
const formattedExecutions = executions.map((execution) => {
|
||||||
return {
|
return {
|
||||||
id: execution.id.toString(),
|
id: execution.id,
|
||||||
finished: execution.finished,
|
finished: execution.finished,
|
||||||
mode: execution.mode,
|
mode: execution.mode,
|
||||||
retryOf: execution.retryOf?.toString(),
|
retryOf: execution.retryOf?.toString(),
|
||||||
|
@ -255,7 +262,7 @@ export class ExecutionsService {
|
||||||
waitTill: execution.waitTill as Date | undefined,
|
waitTill: execution.waitTill as Date | undefined,
|
||||||
startedAt: execution.startedAt,
|
startedAt: execution.startedAt,
|
||||||
stoppedAt: execution.stoppedAt,
|
stoppedAt: execution.stoppedAt,
|
||||||
workflowId: execution.workflowData?.id?.toString() ?? '',
|
workflowId: execution.workflowData?.id ?? '',
|
||||||
workflowName: execution.workflowData?.name,
|
workflowName: execution.workflowData?.name,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
@ -293,13 +300,8 @@ export class ExecutionsService {
|
||||||
return ResponseHelper.unflattenExecutionData(execution);
|
return ResponseHelper.unflattenExecutionData(execution);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id, ...rest } = execution;
|
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return {
|
return execution;
|
||||||
id: id.toString(),
|
|
||||||
...rest,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async retryExecution(req: ExecutionRequest.Retry): Promise<boolean> {
|
static async retryExecution(req: ExecutionRequest.Retry): Promise<boolean> {
|
||||||
|
@ -460,7 +462,7 @@ export class ExecutionsService {
|
||||||
};
|
};
|
||||||
|
|
||||||
let query = Db.collections.Execution.createQueryBuilder()
|
let query = Db.collections.Execution.createQueryBuilder()
|
||||||
.select()
|
.select('id')
|
||||||
.where({
|
.where({
|
||||||
...filters,
|
...filters,
|
||||||
workflowId: In(sharedWorkflowIds),
|
workflowId: In(sharedWorkflowIds),
|
||||||
|
@ -474,7 +476,7 @@ export class ExecutionsService {
|
||||||
|
|
||||||
if (!executions.length) return;
|
if (!executions.length) return;
|
||||||
|
|
||||||
const idsToDelete = executions.map(({ id }) => id.toString());
|
const idsToDelete = executions.map(({ id }) => id);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
idsToDelete.map(async (id) => binaryDataManager.deleteBinaryDataByExecutionId(id)),
|
idsToDelete.map(async (id) => binaryDataManager.deleteBinaryDataByExecutionId(id)),
|
||||||
|
@ -503,7 +505,7 @@ export class ExecutionsService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const idsToDelete = executions.map(({ id }) => id.toString());
|
const idsToDelete = executions.map(({ id }) => id);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
idsToDelete.map(async (id) => binaryDataManager.deleteBinaryDataByExecutionId(id)),
|
idsToDelete.map(async (id) => binaryDataManager.deleteBinaryDataByExecutionId(id)),
|
||||||
|
|
|
@ -13,10 +13,7 @@ export class RoleService {
|
||||||
|
|
||||||
static async getUserRoleForWorkflow(userId: string, workflowId: string) {
|
static async getUserRoleForWorkflow(userId: string, workflowId: string) {
|
||||||
const shared = await Db.collections.SharedWorkflow.findOne({
|
const shared = await Db.collections.SharedWorkflow.findOne({
|
||||||
where: {
|
where: { workflowId, userId },
|
||||||
workflow: { id: workflowId },
|
|
||||||
user: { id: userId },
|
|
||||||
},
|
|
||||||
relations: ['role'],
|
relations: ['role'],
|
||||||
});
|
});
|
||||||
return shared?.role;
|
return shared?.role;
|
||||||
|
|
|
@ -92,7 +92,7 @@ EEWorkflowController.get(
|
||||||
relations.push('tags');
|
relations.push('tags');
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflow = await EEWorkflows.get({ id: parseInt(workflowId, 10) }, { relations });
|
const workflow = await EEWorkflows.get({ id: workflowId }, { relations });
|
||||||
|
|
||||||
if (!workflow) {
|
if (!workflow) {
|
||||||
throw new ResponseHelper.NotFoundError(`Workflow with ID "${workflowId}" does not exist`);
|
throw new ResponseHelper.NotFoundError(`Workflow with ID "${workflowId}" does not exist`);
|
||||||
|
@ -108,7 +108,7 @@ EEWorkflowController.get(
|
||||||
|
|
||||||
EEWorkflows.addOwnerAndSharings(workflow);
|
EEWorkflows.addOwnerAndSharings(workflow);
|
||||||
await EEWorkflows.addCredentialsToWorkflow(workflow, req.user);
|
await EEWorkflows.addCredentialsToWorkflow(workflow, req.user);
|
||||||
return EEWorkflows.entityToResponse(workflow);
|
return workflow;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -189,7 +189,7 @@ EEWorkflowController.post(
|
||||||
await ExternalHooks().run('workflow.afterCreate', [savedWorkflow]);
|
await ExternalHooks().run('workflow.afterCreate', [savedWorkflow]);
|
||||||
void InternalHooksManager.getInstance().onWorkflowCreated(req.user.id, newWorkflow, false);
|
void InternalHooksManager.getInstance().onWorkflowCreated(req.user.id, newWorkflow, false);
|
||||||
|
|
||||||
return EEWorkflows.entityToResponse(savedWorkflow);
|
return savedWorkflow;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -205,7 +205,7 @@ EEWorkflowController.get(
|
||||||
return workflows.map((workflow) => {
|
return workflows.map((workflow) => {
|
||||||
EEWorkflows.addOwnerAndSharings(workflow);
|
EEWorkflows.addOwnerAndSharings(workflow);
|
||||||
workflow.nodes = [];
|
workflow.nodes = [];
|
||||||
return EEWorkflows.entityToResponse(workflow);
|
return workflow;
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -230,7 +230,7 @@ EEWorkflowController.patch(
|
||||||
forceSave,
|
forceSave,
|
||||||
);
|
);
|
||||||
|
|
||||||
return EEWorkflows.entityToResponse(updatedWorkflow);
|
return updatedWorkflow;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -244,11 +244,7 @@ EEWorkflowController.post(
|
||||||
Object.assign(workflow, req.body.workflowData);
|
Object.assign(workflow, req.body.workflowData);
|
||||||
|
|
||||||
if (workflow.id !== undefined) {
|
if (workflow.id !== undefined) {
|
||||||
const safeWorkflow = await EEWorkflows.preventTampering(
|
const safeWorkflow = await EEWorkflows.preventTampering(workflow, workflow.id, req.user);
|
||||||
workflow,
|
|
||||||
workflow.id.toString(),
|
|
||||||
req.user,
|
|
||||||
);
|
|
||||||
req.body.workflowData.nodes = safeWorkflow.nodes;
|
req.body.workflowData.nodes = safeWorkflow.nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ workflowsController.post(
|
||||||
await ExternalHooks().run('workflow.afterCreate', [savedWorkflow]);
|
await ExternalHooks().run('workflow.afterCreate', [savedWorkflow]);
|
||||||
void InternalHooksManager.getInstance().onWorkflowCreated(req.user.id, newWorkflow, false);
|
void InternalHooksManager.getInstance().onWorkflowCreated(req.user.id, newWorkflow, false);
|
||||||
|
|
||||||
return WorkflowsService.entityToResponse(savedWorkflow);
|
return savedWorkflow;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -116,8 +116,7 @@ workflowsController.post(
|
||||||
workflowsController.get(
|
workflowsController.get(
|
||||||
'/',
|
'/',
|
||||||
ResponseHelper.send(async (req: WorkflowRequest.GetAll) => {
|
ResponseHelper.send(async (req: WorkflowRequest.GetAll) => {
|
||||||
const workflows = await WorkflowsService.getMany(req.user, req.query.filter);
|
return WorkflowsService.getMany(req.user, req.query.filter);
|
||||||
return workflows.map((workflow) => WorkflowsService.entityToResponse(workflow));
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -218,7 +217,7 @@ workflowsController.get(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return WorkflowsService.entityToResponse(shared.workflow);
|
return shared.workflow;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -244,7 +243,7 @@ workflowsController.patch(
|
||||||
['owner'],
|
['owner'],
|
||||||
);
|
);
|
||||||
|
|
||||||
return WorkflowsService.entityToResponse(updatedWorkflow);
|
return updatedWorkflow;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,8 @@ export class EEWorkflowsService extends WorkflowsService {
|
||||||
transaction: EntityManager,
|
transaction: EntityManager,
|
||||||
workflowId: string,
|
workflowId: string,
|
||||||
): Promise<SharedWorkflow[]> {
|
): Promise<SharedWorkflow[]> {
|
||||||
const workflow = await transaction.findOne(WorkflowEntity, workflowId, {
|
const workflow = await transaction.findOne(WorkflowEntity, {
|
||||||
|
where: { id: workflowId },
|
||||||
relations: ['shared'],
|
relations: ['shared'],
|
||||||
});
|
});
|
||||||
return workflow?.shared ?? [];
|
return workflow?.shared ?? [];
|
||||||
|
@ -54,8 +55,8 @@ export class EEWorkflowsService extends WorkflowsService {
|
||||||
userIds: string[],
|
userIds: string[],
|
||||||
): Promise<DeleteResult> {
|
): Promise<DeleteResult> {
|
||||||
return transaction.delete(SharedWorkflow, {
|
return transaction.delete(SharedWorkflow, {
|
||||||
workflow: { id: workflowId },
|
workflowId,
|
||||||
user: { id: Not(In(userIds)) },
|
userId: Not(In(userIds)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +114,7 @@ export class EEWorkflowsService extends WorkflowsService {
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
workflow.usedCredentials = [];
|
workflow.usedCredentials = [];
|
||||||
const userCredentials = await EECredentials.getAll(currentUser, { disableGlobalRole: true });
|
const userCredentials = await EECredentials.getAll(currentUser, { disableGlobalRole: true });
|
||||||
const credentialIdsUsedByWorkflow = new Set<number>();
|
const credentialIdsUsedByWorkflow = new Set<string>();
|
||||||
workflow.nodes.forEach((node) => {
|
workflow.nodes.forEach((node) => {
|
||||||
if (!node.credentials) {
|
if (!node.credentials) {
|
||||||
return;
|
return;
|
||||||
|
@ -123,8 +124,7 @@ export class EEWorkflowsService extends WorkflowsService {
|
||||||
if (!credential?.id) {
|
if (!credential?.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const credentialId = parseInt(credential.id, 10);
|
credentialIdsUsedByWorkflow.add(credential.id);
|
||||||
credentialIdsUsedByWorkflow.add(credentialId);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const workflowCredentials = await EECredentials.getMany({
|
const workflowCredentials = await EECredentials.getMany({
|
||||||
|
@ -133,11 +133,11 @@ export class EEWorkflowsService extends WorkflowsService {
|
||||||
},
|
},
|
||||||
relations: ['shared', 'shared.user', 'shared.role'],
|
relations: ['shared', 'shared.user', 'shared.role'],
|
||||||
});
|
});
|
||||||
const userCredentialIds = userCredentials.map((credential) => credential.id.toString());
|
const userCredentialIds = userCredentials.map((credential) => credential.id);
|
||||||
workflowCredentials.forEach((credential) => {
|
workflowCredentials.forEach((credential) => {
|
||||||
const credentialId = credential.id.toString();
|
const credentialId = credential.id;
|
||||||
const workflowCredential: CredentialUsedByWorkflow = {
|
const workflowCredential: CredentialUsedByWorkflow = {
|
||||||
id: credential.id.toString(),
|
id: credentialId,
|
||||||
name: credential.name,
|
name: credential.name,
|
||||||
type: credential.type,
|
type: credential.type,
|
||||||
currentUserHasAccess: userCredentialIds.includes(credentialId),
|
currentUserHasAccess: userCredentialIds.includes(credentialId),
|
||||||
|
@ -190,23 +190,24 @@ export class EEWorkflowsService extends WorkflowsService {
|
||||||
relations: ['shared', 'shared.user', 'shared.role'],
|
relations: ['shared', 'shared.user', 'shared.role'],
|
||||||
});
|
});
|
||||||
const userCredentials = await EECredentials.getAll(currentUser, { disableGlobalRole: true });
|
const userCredentials = await EECredentials.getAll(currentUser, { disableGlobalRole: true });
|
||||||
const userCredentialIds = userCredentials.map((credential) => credential.id.toString());
|
const userCredentialIds = userCredentials.map((credential) => credential.id);
|
||||||
const credentialsMap: Record<string, CredentialUsedByWorkflow> = {};
|
const credentialsMap: Record<string, CredentialUsedByWorkflow> = {};
|
||||||
usedWorkflowsCredentials.forEach((credential) => {
|
usedWorkflowsCredentials.forEach((credential) => {
|
||||||
credentialsMap[credential.id.toString()] = {
|
const credentialId = credential.id;
|
||||||
id: credential.id.toString(),
|
credentialsMap[credentialId] = {
|
||||||
|
id: credentialId,
|
||||||
name: credential.name,
|
name: credential.name,
|
||||||
type: credential.type,
|
type: credential.type,
|
||||||
currentUserHasAccess: userCredentialIds.includes(credential.id.toString()),
|
currentUserHasAccess: userCredentialIds.includes(credentialId),
|
||||||
sharedWith: [],
|
sharedWith: [],
|
||||||
ownedBy: null,
|
ownedBy: null,
|
||||||
};
|
};
|
||||||
credential.shared?.forEach(({ user, role }) => {
|
credential.shared?.forEach(({ user, role }) => {
|
||||||
const { id, email, firstName, lastName } = user;
|
const { id, email, firstName, lastName } = user;
|
||||||
if (role.name === 'owner') {
|
if (role.name === 'owner') {
|
||||||
credentialsMap[credential.id.toString()].ownedBy = { id, email, firstName, lastName };
|
credentialsMap[credentialId].ownedBy = { id, email, firstName, lastName };
|
||||||
} else {
|
} else {
|
||||||
credentialsMap[credential.id.toString()].sharedWith?.push({
|
credentialsMap[credentialId].sharedWith?.push({
|
||||||
id,
|
id,
|
||||||
email,
|
email,
|
||||||
firstName,
|
firstName,
|
||||||
|
@ -230,10 +231,9 @@ export class EEWorkflowsService extends WorkflowsService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Object.keys(node.credentials).forEach((credentialType) => {
|
Object.keys(node.credentials).forEach((credentialType) => {
|
||||||
const credentialId = parseInt(node.credentials?.[credentialType].id ?? '', 10);
|
const credentialId = node.credentials?.[credentialType].id;
|
||||||
const matchedCredential = allowedCredentials.find(
|
if (credentialId === undefined) return;
|
||||||
(credential) => credential.id === credentialId,
|
const matchedCredential = allowedCredentials.find(({ id }) => id === credentialId);
|
||||||
);
|
|
||||||
if (!matchedCredential) {
|
if (!matchedCredential) {
|
||||||
throw new Error('The workflow contains credentials that you do not have access to');
|
throw new Error('The workflow contains credentials that you do not have access to');
|
||||||
}
|
}
|
||||||
|
@ -242,7 +242,7 @@ export class EEWorkflowsService extends WorkflowsService {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async preventTampering(workflow: WorkflowEntity, workflowId: string, user: User) {
|
static async preventTampering(workflow: WorkflowEntity, workflowId: string, user: User) {
|
||||||
const previousVersion = await EEWorkflowsService.get({ id: parseInt(workflowId, 10) });
|
const previousVersion = await EEWorkflowsService.get({ id: workflowId });
|
||||||
|
|
||||||
if (!previousVersion) {
|
if (!previousVersion) {
|
||||||
throw new ResponseHelper.NotFoundError('Workflow not found');
|
throw new ResponseHelper.NotFoundError('Workflow not found');
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { validate as jsonSchemaValidate } from 'jsonschema';
|
import { validate as jsonSchemaValidate } from 'jsonschema';
|
||||||
import { INode, IPinData, JsonObject, jsonParse, LoggerProxy, Workflow } from 'n8n-workflow';
|
import { INode, IPinData, JsonObject, jsonParse, LoggerProxy, Workflow } from 'n8n-workflow';
|
||||||
import { FindManyOptions, FindOneOptions, In, ObjectLiteral } from 'typeorm';
|
import { FindConditions, In } from 'typeorm';
|
||||||
import pick from 'lodash.pick';
|
import pick from 'lodash.pick';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import * as ActiveWorkflowRunner from '@/ActiveWorkflowRunner';
|
import * as ActiveWorkflowRunner from '@/ActiveWorkflowRunner';
|
||||||
|
@ -16,7 +16,7 @@ import { validateEntity } from '@/GenericHelpers';
|
||||||
import { ExternalHooks } from '@/ExternalHooks';
|
import { ExternalHooks } from '@/ExternalHooks';
|
||||||
import * as TagHelpers from '@/TagHelpers';
|
import * as TagHelpers from '@/TagHelpers';
|
||||||
import { WorkflowRequest } from '@/requests';
|
import { WorkflowRequest } from '@/requests';
|
||||||
import { IWorkflowDb, IWorkflowExecutionDataProcess, IWorkflowResponse } from '@/Interfaces';
|
import type { IWorkflowDb, IWorkflowExecutionDataProcess } from '@/Interfaces';
|
||||||
import { NodeTypes } from '@/NodeTypes';
|
import { NodeTypes } from '@/NodeTypes';
|
||||||
import { WorkflowRunner } from '@/WorkflowRunner';
|
import { WorkflowRunner } from '@/WorkflowRunner';
|
||||||
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
|
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
|
||||||
|
@ -25,7 +25,7 @@ import { getSharedWorkflowIds } from '@/WorkflowHelpers';
|
||||||
import { isSharingEnabled, whereClause } from '@/UserManagement/UserManagementHelper';
|
import { isSharingEnabled, whereClause } from '@/UserManagement/UserManagementHelper';
|
||||||
|
|
||||||
export interface IGetWorkflowsQueryFilter {
|
export interface IGetWorkflowsQueryFilter {
|
||||||
id?: number | string;
|
id?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
}
|
}
|
||||||
|
@ -45,28 +45,20 @@ const allowedWorkflowsQueryFilterFields = Object.keys(schemaGetWorkflowsQueryFil
|
||||||
export class WorkflowsService {
|
export class WorkflowsService {
|
||||||
static async getSharing(
|
static async getSharing(
|
||||||
user: User,
|
user: User,
|
||||||
workflowId: number | string,
|
workflowId: string,
|
||||||
relations: string[] = ['workflow'],
|
relations: string[] = ['workflow'],
|
||||||
{ allowGlobalOwner } = { allowGlobalOwner: true },
|
{ allowGlobalOwner } = { allowGlobalOwner: true },
|
||||||
): Promise<SharedWorkflow | undefined> {
|
): Promise<SharedWorkflow | undefined> {
|
||||||
const options: FindOneOptions<SharedWorkflow> & { where: ObjectLiteral } = {
|
const where: FindConditions<SharedWorkflow> = { workflowId };
|
||||||
where: {
|
|
||||||
workflow: { id: workflowId },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Omit user from where if the requesting user is the global
|
// Omit user from where if the requesting user is the global
|
||||||
// owner. This allows the global owner to view and delete
|
// owner. This allows the global owner to view and delete
|
||||||
// workflows they don't own.
|
// workflows they don't own.
|
||||||
if (!allowGlobalOwner || user.globalRole.name !== 'owner') {
|
if (!allowGlobalOwner || user.globalRole.name !== 'owner') {
|
||||||
options.where.user = { id: user.id };
|
where.userId = user.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relations?.length) {
|
return Db.collections.SharedWorkflow.findOne({ where, relations });
|
||||||
options.relations = relations;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Db.collections.SharedWorkflow.findOne(options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -120,11 +112,6 @@ export class WorkflowsService {
|
||||||
return getSharedWorkflowIds(user, roles);
|
return getSharedWorkflowIds(user, roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
static entityToResponse(entity: WorkflowEntity): IWorkflowResponse {
|
|
||||||
const { id, ...rest } = entity;
|
|
||||||
return { ...rest, id: id.toString() };
|
|
||||||
}
|
|
||||||
|
|
||||||
static async getMany(user: User, rawFilter: string): Promise<WorkflowEntity[]> {
|
static async getMany(user: User, rawFilter: string): Promise<WorkflowEntity[]> {
|
||||||
const sharedWorkflowIds = await this.getWorkflowIdsForUser(user, ['owner']);
|
const sharedWorkflowIds = await this.getWorkflowIdsForUser(user, ['owner']);
|
||||||
if (sharedWorkflowIds.length === 0) {
|
if (sharedWorkflowIds.length === 0) {
|
||||||
|
@ -181,16 +168,14 @@ export class WorkflowsService {
|
||||||
relations.push('shared', 'shared.user', 'shared.role');
|
relations.push('shared', 'shared.user', 'shared.role');
|
||||||
}
|
}
|
||||||
|
|
||||||
const query: FindManyOptions<WorkflowEntity> = {
|
return Db.collections.Workflow.find({
|
||||||
select: isSharingEnabled() ? [...fields, 'versionId'] : fields,
|
select: isSharingEnabled() ? [...fields, 'versionId'] : fields,
|
||||||
relations,
|
relations,
|
||||||
where: {
|
where: {
|
||||||
id: In(sharedWorkflowIds),
|
id: In(sharedWorkflowIds),
|
||||||
...filter,
|
...filter,
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
return Db.collections.Workflow.find(query);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async update(
|
static async update(
|
||||||
|
@ -310,17 +295,11 @@ export class WorkflowsService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: FindManyOptions<WorkflowEntity> = {
|
const relations = config.getEnv('workflowTagsDisabled') ? [] : ['tags'];
|
||||||
relations: ['tags'],
|
|
||||||
};
|
|
||||||
|
|
||||||
if (config.getEnv('workflowTagsDisabled')) {
|
|
||||||
delete options.relations;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We sadly get nothing back from "update". Neither if it updated a record
|
// We sadly get nothing back from "update". Neither if it updated a record
|
||||||
// nor the new value. So query now the hopefully updated entry.
|
// nor the new value. So query now the hopefully updated entry.
|
||||||
const updatedWorkflow = await Db.collections.Workflow.findOne(workflowId, options);
|
const updatedWorkflow = await Db.collections.Workflow.findOne(workflowId, { relations });
|
||||||
|
|
||||||
if (updatedWorkflow === undefined) {
|
if (updatedWorkflow === undefined) {
|
||||||
throw new ResponseHelper.BadRequestError(
|
throw new ResponseHelper.BadRequestError(
|
||||||
|
|
|
@ -351,7 +351,7 @@ test('GET /credentials/:id should return 404 if cred not found', async () => {
|
||||||
expect(response.statusCode).toBe(404);
|
expect(response.statusCode).toBe(404);
|
||||||
|
|
||||||
const responseAbc = await authAgent(ownerShell).get('/credentials/abc');
|
const responseAbc = await authAgent(ownerShell).get('/credentials/abc');
|
||||||
expect(responseAbc.statusCode).toBe(400);
|
expect(responseAbc.statusCode).toBe(404);
|
||||||
|
|
||||||
// because EE router has precedence, check if forwards this route
|
// because EE router has precedence, check if forwards this route
|
||||||
const responseNew = await authAgent(ownerShell).get('/credentials/new');
|
const responseNew = await authAgent(ownerShell).get('/credentials/new');
|
||||||
|
|
|
@ -81,7 +81,7 @@ test('GET /credentials should return all creds for owner', async () => {
|
||||||
response.body.data.forEach((credential: CredentialsEntity) => {
|
response.body.data.forEach((credential: CredentialsEntity) => {
|
||||||
validateMainCredentialData(credential);
|
validateMainCredentialData(credential);
|
||||||
expect(credential.data).toBeUndefined();
|
expect(credential.data).toBeUndefined();
|
||||||
expect(savedCredentialsIds.includes(Number(credential.id))).toBe(true);
|
expect(savedCredentialsIds).toContain(credential.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ test('GET /credentials should return only own creds for member', async () => {
|
||||||
|
|
||||||
validateMainCredentialData(member1Credential);
|
validateMainCredentialData(member1Credential);
|
||||||
expect(member1Credential.data).toBeUndefined();
|
expect(member1Credential.data).toBeUndefined();
|
||||||
expect(member1Credential.id).toBe(savedCredential1.id.toString());
|
expect(member1Credential.id).toBe(savedCredential1.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /credentials should create cred', async () => {
|
test('POST /credentials should create cred', async () => {
|
||||||
|
@ -573,16 +573,11 @@ test('GET /credentials/:id should fail with missing encryption key', async () =>
|
||||||
|
|
||||||
test('GET /credentials/:id should return 404 if cred not found', async () => {
|
test('GET /credentials/:id should return 404 if cred not found', async () => {
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
||||||
|
|
||||||
const response = await authAgent(ownerShell).get('/credentials/789');
|
const response = await authAgent(ownerShell).get('/credentials/789');
|
||||||
expect(response.statusCode).toBe(404);
|
expect(response.statusCode).toBe(404);
|
||||||
});
|
|
||||||
|
|
||||||
test('GET /credentials/:id should return 400 if id is not a number', async () => {
|
|
||||||
const ownerShell = await testDb.createUserShell(globalOwnerRole);
|
|
||||||
|
|
||||||
const responseAbc = await authAgent(ownerShell).get('/credentials/abc');
|
const responseAbc = await authAgent(ownerShell).get('/credentials/abc');
|
||||||
expect(responseAbc.statusCode).toBe(400);
|
expect(responseAbc.statusCode).toBe(404);
|
||||||
});
|
});
|
||||||
|
|
||||||
function validateMainCredentialData(credential: CredentialsEntity) {
|
function validateMainCredentialData(credential: CredentialsEntity) {
|
||||||
|
|
|
@ -463,7 +463,7 @@ test('GET /executions should retrieve all executions of specific workflow', asyn
|
||||||
await testDb.createManyExecutions(2, workflow2, testDb.createSuccessfulExecution);
|
await testDb.createManyExecutions(2, workflow2, testDb.createSuccessfulExecution);
|
||||||
|
|
||||||
const response = await authOwnerAgent.get(`/executions`).query({
|
const response = await authOwnerAgent.get(`/executions`).query({
|
||||||
workflowId: workflow.id.toString(),
|
workflowId: workflow.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
|
@ -490,7 +490,7 @@ test('GET /executions should retrieve all executions of specific workflow', asyn
|
||||||
expect(retryOf).toBeNull();
|
expect(retryOf).toBeNull();
|
||||||
expect(startedAt).not.toBeNull();
|
expect(startedAt).not.toBeNull();
|
||||||
expect(stoppedAt).not.toBeNull();
|
expect(stoppedAt).not.toBeNull();
|
||||||
expect(workflowId).toBe(workflow.id.toString());
|
expect(workflowId).toBe(workflow.id);
|
||||||
expect(waitTill).toBeNull();
|
expect(waitTill).toBeNull();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -195,7 +195,7 @@ test('GET /workflows should return all owned workflows with pagination', async (
|
||||||
}
|
}
|
||||||
|
|
||||||
// check that we really received a different result
|
// check that we really received a different result
|
||||||
expect(response.body.data[0].id).toBeLessThan(response2.body.data[0].id);
|
expect(Number(response.body.data[0].id)).toBeLessThan(Number(response2.body.data[0].id));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('GET /workflows should return all owned workflows filtered by tag', async () => {
|
test('GET /workflows should return all owned workflows filtered by tag', async () => {
|
||||||
|
@ -690,7 +690,7 @@ test('POST /workflows/:id/activate should set workflow as active', async () => {
|
||||||
expect(sharedWorkflow?.workflow.active).toBe(true);
|
expect(sharedWorkflow?.workflow.active).toBe(true);
|
||||||
|
|
||||||
// check whether the workflow is on the active workflow runner
|
// check whether the workflow is on the active workflow runner
|
||||||
expect(await workflowRunner.isActive(workflow.id.toString())).toBe(true);
|
expect(await workflowRunner.isActive(workflow.id)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /workflows/:id/activate should set non-owned workflow as active when owner', async () => {
|
test('POST /workflows/:id/activate should set non-owned workflow as active when owner', async () => {
|
||||||
|
@ -744,7 +744,7 @@ test('POST /workflows/:id/activate should set non-owned workflow as active when
|
||||||
expect(sharedWorkflow?.workflow.active).toBe(true);
|
expect(sharedWorkflow?.workflow.active).toBe(true);
|
||||||
|
|
||||||
// check whether the workflow is on the active workflow runner
|
// check whether the workflow is on the active workflow runner
|
||||||
expect(await workflowRunner.isActive(workflow.id.toString())).toBe(true);
|
expect(await workflowRunner.isActive(workflow.id)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /workflows/:id/deactivate should fail due to missing API Key', async () => {
|
test('POST /workflows/:id/deactivate should fail due to missing API Key', async () => {
|
||||||
|
@ -835,7 +835,7 @@ test('POST /workflows/:id/deactivate should deactivate workflow', async () => {
|
||||||
// check whether the workflow is deactivated in the database
|
// check whether the workflow is deactivated in the database
|
||||||
expect(sharedWorkflow?.workflow.active).toBe(false);
|
expect(sharedWorkflow?.workflow.active).toBe(false);
|
||||||
|
|
||||||
expect(await workflowRunner.isActive(workflow.id.toString())).toBe(false);
|
expect(await workflowRunner.isActive(workflow.id)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /workflows/:id/deactivate should deactivate non-owned workflow when owner', async () => {
|
test('POST /workflows/:id/deactivate should deactivate non-owned workflow when owner', async () => {
|
||||||
|
@ -888,7 +888,7 @@ test('POST /workflows/:id/deactivate should deactivate non-owned workflow when o
|
||||||
|
|
||||||
expect(sharedWorkflow?.workflow.active).toBe(false);
|
expect(sharedWorkflow?.workflow.active).toBe(false);
|
||||||
|
|
||||||
expect(await workflowRunner.isActive(workflow.id.toString())).toBe(false);
|
expect(await workflowRunner.isActive(workflow.id)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('POST /workflows should fail due to missing API Key', async () => {
|
test('POST /workflows should fail due to missing API Key', async () => {
|
||||||
|
|
|
@ -474,7 +474,7 @@ export async function createExecution(
|
||||||
finished: finished ?? true,
|
finished: finished ?? true,
|
||||||
mode: mode ?? 'manual',
|
mode: mode ?? 'manual',
|
||||||
startedAt: startedAt ?? new Date(),
|
startedAt: startedAt ?? new Date(),
|
||||||
...(workflow !== undefined && { workflowData: workflow, workflowId: workflow.id.toString() }),
|
...(workflow !== undefined && { workflowData: workflow, workflowId: workflow.id }),
|
||||||
stoppedAt: stoppedAt ?? new Date(),
|
stoppedAt: stoppedAt ?? new Date(),
|
||||||
waitTill: waitTill ?? null,
|
waitTill: waitTill ?? null,
|
||||||
});
|
});
|
||||||
|
|
|
@ -180,7 +180,7 @@ describe('GET /workflows', () => {
|
||||||
position: [0, 0],
|
position: [0, 0],
|
||||||
credentials: {
|
credentials: {
|
||||||
actionNetworkApi: {
|
actionNetworkApi: {
|
||||||
id: savedCredential.id.toString(),
|
id: savedCredential.id,
|
||||||
name: savedCredential.name,
|
name: savedCredential.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -220,7 +220,7 @@ describe('GET /workflows', () => {
|
||||||
const [usedCredential] = fetchedWorkflow.usedCredentials;
|
const [usedCredential] = fetchedWorkflow.usedCredentials;
|
||||||
|
|
||||||
expect(usedCredential).toMatchObject({
|
expect(usedCredential).toMatchObject({
|
||||||
id: savedCredential.id.toString(),
|
id: savedCredential.id,
|
||||||
name: savedCredential.name,
|
name: savedCredential.name,
|
||||||
type: savedCredential.type,
|
type: savedCredential.type,
|
||||||
currentUserHasAccess: true,
|
currentUserHasAccess: true,
|
||||||
|
@ -313,7 +313,7 @@ describe('GET /workflows/:id', () => {
|
||||||
|
|
||||||
const workflowPayload = makeWorkflow({
|
const workflowPayload = makeWorkflow({
|
||||||
withPinData: false,
|
withPinData: false,
|
||||||
withCredential: { id: savedCredential.id.toString(), name: savedCredential.name },
|
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
||||||
});
|
});
|
||||||
const workflow = await createWorkflow(workflowPayload, owner);
|
const workflow = await createWorkflow(workflowPayload, owner);
|
||||||
|
|
||||||
|
@ -322,7 +322,7 @@ describe('GET /workflows/:id', () => {
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response.body.data.usedCredentials).toMatchObject([
|
expect(response.body.data.usedCredentials).toMatchObject([
|
||||||
{
|
{
|
||||||
id: savedCredential.id.toString(),
|
id: savedCredential.id,
|
||||||
name: savedCredential.name,
|
name: savedCredential.name,
|
||||||
currentUserHasAccess: true,
|
currentUserHasAccess: true,
|
||||||
},
|
},
|
||||||
|
@ -338,7 +338,7 @@ describe('GET /workflows/:id', () => {
|
||||||
|
|
||||||
const workflowPayload = makeWorkflow({
|
const workflowPayload = makeWorkflow({
|
||||||
withPinData: false,
|
withPinData: false,
|
||||||
withCredential: { id: savedCredential.id.toString(), name: savedCredential.name },
|
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
||||||
});
|
});
|
||||||
const workflow = await createWorkflow(workflowPayload, owner);
|
const workflow = await createWorkflow(workflowPayload, owner);
|
||||||
|
|
||||||
|
@ -347,7 +347,7 @@ describe('GET /workflows/:id', () => {
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response.body.data.usedCredentials).toMatchObject([
|
expect(response.body.data.usedCredentials).toMatchObject([
|
||||||
{
|
{
|
||||||
id: savedCredential.id.toString(),
|
id: savedCredential.id,
|
||||||
name: savedCredential.name,
|
name: savedCredential.name,
|
||||||
currentUserHasAccess: false, // although owner can see, he does not have access
|
currentUserHasAccess: false, // although owner can see, he does not have access
|
||||||
},
|
},
|
||||||
|
@ -363,7 +363,7 @@ describe('GET /workflows/:id', () => {
|
||||||
|
|
||||||
const workflowPayload = makeWorkflow({
|
const workflowPayload = makeWorkflow({
|
||||||
withPinData: false,
|
withPinData: false,
|
||||||
withCredential: { id: savedCredential.id.toString(), name: savedCredential.name },
|
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
||||||
});
|
});
|
||||||
const workflow = await createWorkflow(workflowPayload, member1);
|
const workflow = await createWorkflow(workflowPayload, member1);
|
||||||
await testDb.shareWorkflowWithUsers(workflow, [member2]);
|
await testDb.shareWorkflowWithUsers(workflow, [member2]);
|
||||||
|
@ -372,7 +372,7 @@ describe('GET /workflows/:id', () => {
|
||||||
expect(responseMember1.statusCode).toBe(200);
|
expect(responseMember1.statusCode).toBe(200);
|
||||||
expect(responseMember1.body.data.usedCredentials).toMatchObject([
|
expect(responseMember1.body.data.usedCredentials).toMatchObject([
|
||||||
{
|
{
|
||||||
id: savedCredential.id.toString(),
|
id: savedCredential.id,
|
||||||
name: savedCredential.name,
|
name: savedCredential.name,
|
||||||
currentUserHasAccess: true, // one user has access
|
currentUserHasAccess: true, // one user has access
|
||||||
},
|
},
|
||||||
|
@ -383,7 +383,7 @@ describe('GET /workflows/:id', () => {
|
||||||
expect(responseMember2.statusCode).toBe(200);
|
expect(responseMember2.statusCode).toBe(200);
|
||||||
expect(responseMember2.body.data.usedCredentials).toMatchObject([
|
expect(responseMember2.body.data.usedCredentials).toMatchObject([
|
||||||
{
|
{
|
||||||
id: savedCredential.id.toString(),
|
id: savedCredential.id,
|
||||||
name: savedCredential.name,
|
name: savedCredential.name,
|
||||||
currentUserHasAccess: false, // the other one doesn't
|
currentUserHasAccess: false, // the other one doesn't
|
||||||
},
|
},
|
||||||
|
@ -400,7 +400,7 @@ describe('GET /workflows/:id', () => {
|
||||||
|
|
||||||
const workflowPayload = makeWorkflow({
|
const workflowPayload = makeWorkflow({
|
||||||
withPinData: false,
|
withPinData: false,
|
||||||
withCredential: { id: savedCredential.id.toString(), name: savedCredential.name },
|
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
||||||
});
|
});
|
||||||
const workflow = await createWorkflow(workflowPayload, member1);
|
const workflow = await createWorkflow(workflowPayload, member1);
|
||||||
await testDb.shareWorkflowWithUsers(workflow, [member2]);
|
await testDb.shareWorkflowWithUsers(workflow, [member2]);
|
||||||
|
@ -409,7 +409,7 @@ describe('GET /workflows/:id', () => {
|
||||||
expect(responseMember1.statusCode).toBe(200);
|
expect(responseMember1.statusCode).toBe(200);
|
||||||
expect(responseMember1.body.data.usedCredentials).toMatchObject([
|
expect(responseMember1.body.data.usedCredentials).toMatchObject([
|
||||||
{
|
{
|
||||||
id: savedCredential.id.toString(),
|
id: savedCredential.id,
|
||||||
name: savedCredential.name,
|
name: savedCredential.name,
|
||||||
currentUserHasAccess: true,
|
currentUserHasAccess: true,
|
||||||
},
|
},
|
||||||
|
@ -420,7 +420,7 @@ describe('GET /workflows/:id', () => {
|
||||||
expect(responseMember2.statusCode).toBe(200);
|
expect(responseMember2.statusCode).toBe(200);
|
||||||
expect(responseMember2.body.data.usedCredentials).toMatchObject([
|
expect(responseMember2.body.data.usedCredentials).toMatchObject([
|
||||||
{
|
{
|
||||||
id: savedCredential.id.toString(),
|
id: savedCredential.id,
|
||||||
name: savedCredential.name,
|
name: savedCredential.name,
|
||||||
currentUserHasAccess: true,
|
currentUserHasAccess: true,
|
||||||
},
|
},
|
||||||
|
@ -446,7 +446,7 @@ describe('POST /workflows', () => {
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
const workflow = makeWorkflow({
|
const workflow = makeWorkflow({
|
||||||
withPinData: false,
|
withPinData: false,
|
||||||
withCredential: { id: savedCredential.id.toString(), name: savedCredential.name },
|
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await authAgent(owner).post('/workflows').send(workflow);
|
const response = await authAgent(owner).post('/workflows').send(workflow);
|
||||||
|
@ -462,7 +462,7 @@ describe('POST /workflows', () => {
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
|
||||||
const workflow = makeWorkflow({
|
const workflow = makeWorkflow({
|
||||||
withPinData: false,
|
withPinData: false,
|
||||||
withCredential: { id: savedCredential.id.toString(), name: savedCredential.name },
|
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await authAgent(member).post('/workflows').send(workflow);
|
const response = await authAgent(member).post('/workflows').send(workflow);
|
||||||
|
@ -481,7 +481,7 @@ describe('POST /workflows', () => {
|
||||||
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member });
|
||||||
const workflow = makeWorkflow({
|
const workflow = makeWorkflow({
|
||||||
withPinData: false,
|
withPinData: false,
|
||||||
withCredential: { id: savedCredential.id.toString(), name: savedCredential.name },
|
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await authAgent(owner).post('/workflows').send(workflow);
|
const response = await authAgent(owner).post('/workflows').send(workflow);
|
||||||
|
@ -498,7 +498,7 @@ describe('POST /workflows', () => {
|
||||||
|
|
||||||
const workflow = makeWorkflow({
|
const workflow = makeWorkflow({
|
||||||
withPinData: false,
|
withPinData: false,
|
||||||
withCredential: { id: savedCredential.id.toString(), name: savedCredential.name },
|
withCredential: { id: savedCredential.id, name: savedCredential.name },
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await authAgent(member2).post('/workflows').send(workflow);
|
const response = await authAgent(member2).post('/workflows').send(workflow);
|
||||||
|
@ -525,7 +525,7 @@ describe('PATCH /workflows/:id - validate credential permissions to user', () =>
|
||||||
typeVersion: 1,
|
typeVersion: 1,
|
||||||
credentials: {
|
credentials: {
|
||||||
default: {
|
default: {
|
||||||
id: savedCredential.id.toString(),
|
id: savedCredential.id,
|
||||||
name: savedCredential.name,
|
name: savedCredential.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -563,7 +563,7 @@ describe('PATCH /workflows/:id - validate credential permissions to user', () =>
|
||||||
typeVersion: 1,
|
typeVersion: 1,
|
||||||
credentials: {
|
credentials: {
|
||||||
default: {
|
default: {
|
||||||
id: savedCredential.id.toString(),
|
id: savedCredential.id,
|
||||||
name: savedCredential.name,
|
name: savedCredential.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -588,7 +588,7 @@ describe('PATCH /workflows/:id - validate credential permissions to user', () =>
|
||||||
typeVersion: 1,
|
typeVersion: 1,
|
||||||
credentials: {
|
credentials: {
|
||||||
default: {
|
default: {
|
||||||
id: savedCredential.id.toString(),
|
id: savedCredential.id,
|
||||||
name: savedCredential.name,
|
name: savedCredential.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -619,7 +619,7 @@ describe('PATCH /workflows/:id - validate credential permissions to user', () =>
|
||||||
typeVersion: 1,
|
typeVersion: 1,
|
||||||
credentials: {
|
credentials: {
|
||||||
default: {
|
default: {
|
||||||
id: savedCredential.id.toString(),
|
id: savedCredential.id,
|
||||||
name: savedCredential.name,
|
name: savedCredential.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -653,7 +653,7 @@ describe('PATCH /workflows/:id - validate credential permissions to user', () =>
|
||||||
typeVersion: 1,
|
typeVersion: 1,
|
||||||
credentials: {
|
credentials: {
|
||||||
default: {
|
default: {
|
||||||
id: savedCredential.id.toString(),
|
id: savedCredential.id,
|
||||||
name: savedCredential.name,
|
name: savedCredential.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -682,7 +682,7 @@ describe('PATCH /workflows/:id - validate credential permissions to user', () =>
|
||||||
typeVersion: 1,
|
typeVersion: 1,
|
||||||
credentials: {
|
credentials: {
|
||||||
default: {
|
default: {
|
||||||
id: savedCredential.id.toString(),
|
id: savedCredential.id,
|
||||||
name: savedCredential.name,
|
name: savedCredential.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -721,7 +721,7 @@ describe('PATCH /workflows/:id - validate credential permissions to user', () =>
|
||||||
typeVersion: 1,
|
typeVersion: 1,
|
||||||
credentials: {
|
credentials: {
|
||||||
default: {
|
default: {
|
||||||
id: savedCredential.id.toString(),
|
id: savedCredential.id,
|
||||||
name: savedCredential.name,
|
name: savedCredential.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,13 +22,13 @@ jest.mock('@/Db', () => {
|
||||||
collections: {
|
collections: {
|
||||||
Workflow: {
|
Workflow: {
|
||||||
update: jest.fn(({ id, dataLoaded }, updateArgs) => {
|
update: jest.fn(({ id, dataLoaded }, updateArgs) => {
|
||||||
if (id === 1) return { affected: 1 };
|
if (id === '1') return { affected: 1 };
|
||||||
return { affected: 0 };
|
return { affected: 0 };
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
WorkflowStatistics: {
|
WorkflowStatistics: {
|
||||||
insert: jest.fn(({ count, name, workflowId }) => {
|
insert: jest.fn(({ count, name, workflowId }) => {
|
||||||
if (workflowId === -1) throw new Error('test error');
|
if (workflowId === '-1') throw new Error('test error');
|
||||||
return null;
|
return null;
|
||||||
}),
|
}),
|
||||||
update: jest.fn((...args) => {}),
|
update: jest.fn((...args) => {}),
|
||||||
|
@ -104,7 +104,7 @@ describe('Events', () => {
|
||||||
expect(mockedFirstProductionWorkflowSuccess).toBeCalledTimes(1);
|
expect(mockedFirstProductionWorkflowSuccess).toBeCalledTimes(1);
|
||||||
expect(mockedFirstProductionWorkflowSuccess).toHaveBeenNthCalledWith(1, {
|
expect(mockedFirstProductionWorkflowSuccess).toHaveBeenNthCalledWith(1, {
|
||||||
user_id: FAKE_USER_ID,
|
user_id: FAKE_USER_ID,
|
||||||
workflow_id: parseInt(workflow.id, 10),
|
workflow_id: workflow.id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -180,7 +180,7 @@ describe('Events', () => {
|
||||||
expect(mockedFirstWorkflowDataLoad).toBeCalledTimes(1);
|
expect(mockedFirstWorkflowDataLoad).toBeCalledTimes(1);
|
||||||
expect(mockedFirstWorkflowDataLoad).toHaveBeenNthCalledWith(1, {
|
expect(mockedFirstWorkflowDataLoad).toHaveBeenNthCalledWith(1, {
|
||||||
user_id: FAKE_USER_ID,
|
user_id: FAKE_USER_ID,
|
||||||
workflow_id: parseInt(workflowId, 10),
|
workflow_id: workflowId,
|
||||||
node_type: node.type,
|
node_type: node.type,
|
||||||
node_id: node.id,
|
node_id: node.id,
|
||||||
});
|
});
|
||||||
|
@ -207,7 +207,7 @@ describe('Events', () => {
|
||||||
expect(mockedFirstWorkflowDataLoad).toBeCalledTimes(1);
|
expect(mockedFirstWorkflowDataLoad).toBeCalledTimes(1);
|
||||||
expect(mockedFirstWorkflowDataLoad).toHaveBeenNthCalledWith(1, {
|
expect(mockedFirstWorkflowDataLoad).toHaveBeenNthCalledWith(1, {
|
||||||
user_id: FAKE_USER_ID,
|
user_id: FAKE_USER_ID,
|
||||||
workflow_id: parseInt(workflowId, 10),
|
workflow_id: workflowId,
|
||||||
node_type: node.type,
|
node_type: node.type,
|
||||||
node_id: node.id,
|
node_id: node.id,
|
||||||
credential_type: 'testCredentials',
|
credential_type: 'testCredentials',
|
||||||
|
|
|
@ -129,7 +129,7 @@ describe('PermissionChecker.check()', () => {
|
||||||
position: [0, 0],
|
position: [0, 0],
|
||||||
credentials: {
|
credentials: {
|
||||||
actionNetworkApi: {
|
actionNetworkApi: {
|
||||||
id: ownerCred.id.toString(),
|
id: ownerCred.id,
|
||||||
name: ownerCred.name,
|
name: ownerCred.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -143,7 +143,7 @@ describe('PermissionChecker.check()', () => {
|
||||||
position: [0, 0],
|
position: [0, 0],
|
||||||
credentials: {
|
credentials: {
|
||||||
actionNetworkApi: {
|
actionNetworkApi: {
|
||||||
id: memberCred.id.toString(),
|
id: memberCred.id,
|
||||||
name: memberCred.name,
|
name: memberCred.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -160,7 +160,7 @@ describe('PermissionChecker.check()', () => {
|
||||||
const memberCred = await saveCredential(randomCred(), { user: member });
|
const memberCred = await saveCredential(randomCred(), { user: member });
|
||||||
|
|
||||||
const workflowDetails = {
|
const workflowDetails = {
|
||||||
id: randomPositiveDigit(),
|
id: randomPositiveDigit().toString(),
|
||||||
name: 'test',
|
name: 'test',
|
||||||
active: false,
|
active: false,
|
||||||
connections: {},
|
connections: {},
|
||||||
|
@ -175,7 +175,7 @@ describe('PermissionChecker.check()', () => {
|
||||||
position: [0, 0] as [number, number],
|
position: [0, 0] as [number, number],
|
||||||
credentials: {
|
credentials: {
|
||||||
actionNetworkApi: {
|
actionNetworkApi: {
|
||||||
id: memberCred.id.toString(),
|
id: memberCred.id,
|
||||||
name: memberCred.name,
|
name: memberCred.name,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -205,7 +205,7 @@ describe('PermissionChecker.check()', () => {
|
||||||
role: workflowOwnerRole,
|
role: workflowOwnerRole,
|
||||||
});
|
});
|
||||||
|
|
||||||
const workflow = new Workflow({ ...workflowDetails, id: workflowDetails.id.toString() });
|
const workflow = new Workflow(workflowDetails);
|
||||||
|
|
||||||
expect(PermissionChecker.check(workflow, member.id)).rejects.toThrow();
|
expect(PermissionChecker.check(workflow, member.id)).rejects.toThrow();
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,7 @@ async function mockFind({
|
||||||
id,
|
id,
|
||||||
type,
|
type,
|
||||||
}: {
|
}: {
|
||||||
id: string | number;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
}): Promise<IWorkflowCredentials | null> {
|
}): Promise<IWorkflowCredentials | null> {
|
||||||
// Simple statement that maps a return value based on the `id` parameter
|
// Simple statement that maps a return value based on the `id` parameter
|
||||||
|
|
|
@ -148,7 +148,7 @@ describe('WorkflowHelpers', () => {
|
||||||
|
|
||||||
function generateCredentialEntity(credentialId: string) {
|
function generateCredentialEntity(credentialId: string) {
|
||||||
const credentialEntity = new CredentialsEntity();
|
const credentialEntity = new CredentialsEntity();
|
||||||
credentialEntity.id = parseInt(credentialId, 10);
|
credentialEntity.id = credentialId;
|
||||||
return credentialEntity;
|
return credentialEntity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -217,8 +217,8 @@ export interface IRestApi {
|
||||||
getPastExecutions(
|
getPastExecutions(
|
||||||
filter: object,
|
filter: object,
|
||||||
limit: number,
|
limit: number,
|
||||||
lastId?: string | number,
|
lastId?: string,
|
||||||
firstId?: string | number,
|
firstId?: string,
|
||||||
): Promise<IExecutionsListResponse>;
|
): Promise<IExecutionsListResponse>;
|
||||||
stopCurrentExecution(executionId: string): Promise<IExecutionsStopData>;
|
stopCurrentExecution(executionId: string): Promise<IExecutionsStopData>;
|
||||||
makeRestApiRequest(method: string, endpoint: string, data?: any): Promise<any>;
|
makeRestApiRequest(method: string, endpoint: string, data?: any): Promise<any>;
|
||||||
|
@ -276,7 +276,7 @@ export interface IVariableSelectorOption {
|
||||||
|
|
||||||
// Simple version of n8n-workflow.Workflow
|
// Simple version of n8n-workflow.Workflow
|
||||||
export interface IWorkflowData {
|
export interface IWorkflowData {
|
||||||
id?: string | number;
|
id?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
nodes: INode[];
|
nodes: INode[];
|
||||||
|
@ -288,7 +288,7 @@ export interface IWorkflowData {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkflowDataUpdate {
|
export interface IWorkflowDataUpdate {
|
||||||
id?: string | number;
|
id?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
nodes?: INode[];
|
nodes?: INode[];
|
||||||
connections?: IConnections;
|
connections?: IConnections;
|
||||||
|
@ -391,7 +391,7 @@ export interface ICredentialsDecryptedResponse extends ICredentialsBase, ICreden
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IExecutionBase {
|
export interface IExecutionBase {
|
||||||
id?: number | string;
|
id?: string;
|
||||||
finished: boolean;
|
finished: boolean;
|
||||||
mode: WorkflowExecuteMode;
|
mode: WorkflowExecuteMode;
|
||||||
retryOf?: string;
|
retryOf?: string;
|
||||||
|
|
|
@ -153,7 +153,7 @@ export default mixins(restApi).extend({
|
||||||
},
|
},
|
||||||
credentialData: {},
|
credentialData: {},
|
||||||
credentialId: {
|
credentialId: {
|
||||||
type: [String, Number],
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
showValidationWarning: {
|
showValidationWarning: {
|
||||||
|
|
|
@ -171,7 +171,7 @@ export default mixins(showMessage, nodeHelpers).extend({
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
activeId: {
|
activeId: {
|
||||||
type: [String, Number],
|
type: [String],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
mode: {
|
mode: {
|
||||||
|
|
|
@ -742,7 +742,7 @@ export default mixins(externalHooks, genericHelpers, restApi, showMessage).exten
|
||||||
this.isDataLoading = true;
|
this.isDataLoading = true;
|
||||||
|
|
||||||
const filter = this.workflowFilterPast;
|
const filter = this.workflowFilterPast;
|
||||||
let lastId: string | number | undefined;
|
let lastId: string | undefined;
|
||||||
|
|
||||||
if (this.finishedExecutions.length !== 0) {
|
if (this.finishedExecutions.length !== 0) {
|
||||||
const lastItem = this.finishedExecutions.slice(-1)[0];
|
const lastItem = this.finishedExecutions.slice(-1)[0];
|
||||||
|
|
|
@ -219,7 +219,7 @@ export default mixins(
|
||||||
}
|
}
|
||||||
this.loadingMore = true;
|
this.loadingMore = true;
|
||||||
|
|
||||||
let lastId: string | number | undefined;
|
let lastId: string | undefined;
|
||||||
if (this.executions.length !== 0) {
|
if (this.executions.length !== 0) {
|
||||||
const lastItem = this.executions.slice(-1)[0];
|
const lastItem = this.executions.slice(-1)[0];
|
||||||
lastId = lastItem.id;
|
lastId = lastItem.id;
|
||||||
|
|
|
@ -434,10 +434,6 @@ export default mixins(workflowHelpers, titleChange).extend({
|
||||||
case WORKFLOW_MENU_ACTIONS.DOWNLOAD: {
|
case WORKFLOW_MENU_ACTIONS.DOWNLOAD: {
|
||||||
const workflowData = await this.getWorkflowDataToSave();
|
const workflowData = await this.getWorkflowDataToSave();
|
||||||
const { tags, ...data } = workflowData;
|
const { tags, ...data } = workflowData;
|
||||||
if (data.id && typeof data.id === 'string') {
|
|
||||||
data.id = parseInt(data.id, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
const exportData: IWorkflowToShare = {
|
const exportData: IWorkflowToShare = {
|
||||||
...data,
|
...data,
|
||||||
meta: {
|
meta: {
|
||||||
|
|
|
@ -179,8 +179,8 @@ export const restApi = Vue.extend({
|
||||||
getPastExecutions: (
|
getPastExecutions: (
|
||||||
filter: object,
|
filter: object,
|
||||||
limit: number,
|
limit: number,
|
||||||
lastId?: string | number,
|
lastId?: string,
|
||||||
firstId?: string | number,
|
firstId?: string,
|
||||||
): Promise<IExecutionsListResponse> => {
|
): Promise<IExecutionsListResponse> => {
|
||||||
let sendData = {};
|
let sendData = {};
|
||||||
if (filter) {
|
if (filter) {
|
||||||
|
|
|
@ -133,7 +133,7 @@ export interface ICredentialNodeAccess {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICredentialsDecrypted {
|
export interface ICredentialsDecrypted {
|
||||||
id: string | number;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
nodesAccess: ICredentialNodeAccess[];
|
nodesAccess: ICredentialNodeAccess[];
|
||||||
|
@ -143,7 +143,7 @@ export interface ICredentialsDecrypted {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICredentialsEncrypted {
|
export interface ICredentialsEncrypted {
|
||||||
id?: string | number;
|
id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
nodesAccess: ICredentialNodeAccess[];
|
nodesAccess: ICredentialNodeAccess[];
|
||||||
|
@ -1430,7 +1430,7 @@ export interface IWorkflowDataProxyData {
|
||||||
export type IWorkflowDataProxyAdditionalKeys = IDataObject;
|
export type IWorkflowDataProxyAdditionalKeys = IDataObject;
|
||||||
|
|
||||||
export interface IWorkflowMetadata {
|
export interface IWorkflowMetadata {
|
||||||
id?: number | string;
|
id?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
}
|
}
|
||||||
|
@ -1571,7 +1571,7 @@ export interface IWaitingForExecutionSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IWorkflowBase {
|
export interface IWorkflowBase {
|
||||||
id?: number | string | any;
|
id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
|
|
Loading…
Reference in a new issue