// Check if node-name is unique else find one that is

if (nodeTypeData.webhooks && nodeTypeData.webhooks.length) {

// Automatically deselect all nodes and select the current one and also active

keep keep keep keep replace keep keep keep keep keep

import {
	IContextObject,
	INodeCredentialDescription,
	INode,
	INodeExecutionData,
	INodeIssues,
	INodeIssueObjectProperty,
	INodeParameters,
	INodeProperties,
	INodePropertyCollection,
	INodeType,
	IParameterDependencies,
	IRunExecutionData,
	IWebhookData,
	IWorkflowExecuteAdditionalData,
	NodeParameterValue,
	WebhookHttpMethod,
} from './Interfaces';

import {
	Workflow
} from './Workflow';

import { get } from 'lodash';

/**
 * Gets special parameters which should be added to nodeTypes depending
 * on their type or configuration
 *
 * @export
 * @param {INodeType} nodeType
 * @returns
 */
export function getSpecialNodeParameters(nodeType: INodeType) {
	if (nodeType.description.polling === true) {
		return [
			{
				displayName: 'Poll Times',
				name: 'pollTimes',
				type: 'fixedCollection',
				typeOptions: {
					multipleValues: true,
					multipleValueButtonText: 'Add Poll Time',
				},
				default: {},
				description: 'Time at which polling should occur.',
				placeholder: 'Add Poll Time',
				options: [
					{
						name: 'item',
						displayName: 'Item',
						values: [
							{
								displayName: 'Mode',
								name: 'mode',
								type: 'options',
								options: [
									{
										name: 'Every Minute',
										value: 'everyMinute',
									},
									{
										name: 'Every Hour',
										value: 'everyHour',
									},
									{
										name: 'Every Day',
										value: 'everyDay',
									},
									{
										name: 'Every Week',
										value: 'everyWeek',
									},
									{
										name: 'Every Month',
										value: 'everyMonth',
									},
									{
										name: 'Every X',
										value: 'everyX',
									},
									{
										name: 'Custom',
										value: 'custom',
									},
								],
								default: 'everyDay',
								description: 'How often to trigger.',
							},
							{
								displayName: 'Hour',
								name: 'hour',
								type: 'number',
								typeOptions: {
									minValue: 0,
									maxValue: 23,
								},
								displayOptions: {
									hide: {
										mode: [
											'custom',
											'everyHour',
											'everyMinute',
											'everyX',
										],
									},
								},
								default: 14,
								description: 'The hour of the day to trigger (24h format).',
							},
							{
								displayName: 'Minute',
								name: 'minute',
								type: 'number',
								typeOptions: {
									minValue: 0,
									maxValue: 59,
								},
								displayOptions: {
									hide: {
										mode: [
											'custom',
											'everyMinute',
											'everyX',
										],
									},
								},
								default: 0,
								description: 'The minute of the day to trigger.',
							},
							{
								displayName: 'Day of Month',
								name: 'dayOfMonth',
								type: 'number',
								displayOptions: {
									show: {
										mode: [
											'everyMonth',
										],
									},
								},
								typeOptions: {
									minValue: 1,
									maxValue: 31,
								},
								default: 1,
								description: 'The day of the month to trigger.',
							},
							{
								displayName: 'Weekday',
								name: 'weekday',
								type: 'options',
								displayOptions: {
									show: {
										mode: [
											'everyWeek',
										],
									},
								},
								options: [
									{
										name: 'Monday',
										value: '1',
									},
									{
										name: 'Tuesday',
										value: '2',
									},
									{
										name: 'Wednesday',
										value: '3',
									},
									{
										name: 'Thursday',
										value: '4',
									},
									{
										name: 'Friday',
										value: '5',
									},
									{
										name: 'Saturday',
										value: '6',
									},
									{
										name: 'Sunday',
										value: '0',
									},
								],
								default: '1',
								description: 'The weekday to trigger.',
							},
							{
								displayName: 'Cron Expression',
								name: 'cronExpression',
								type: 'string',
								displayOptions: {
									show: {
										mode: [
											'custom',
										],
									},
								},
								default: '* * * * * *',
								description: 'Use custom cron expression. Values and ranges as follows:

Seconds: 0-59

Minutes: 0 - 59

Hours: 0 - 23

Day of Month: 1 - 31

Months: 0 - 11 (Jan - Dec)

Day of Week: 0 - 6 (Sun - Sat)

',
							},
							{
								displayName: 'Value',
								name: 'value',
								type: 'number',
								typeOptions: {
									minValue: 0,
									maxValue: 1000,
								},
								displayOptions: {
									show: {
										mode: [
											'everyX',
										],
									},
								},
								default: 2,
								description: 'All how many X minutes/hours it should trigger.',
							},
							{
								displayName: 'Unit',
								name: 'unit',
								type: 'options',
								displayOptions: {
									show: {
										mode: [
											'everyX',
										],
									},
								},
								options: [
									{
										name: 'Minutes',
										value: 'minutes'
									},
									{
										name: 'Hours',
										value: 'hours'
									},
								],
								default: 'hours',
								description: 'If it should trigger all X minutes or hours.',
							},
						],
					},
				],
			},
		];
	}

	return [];
}

/**
 * Returns if the parameter should be displayed or not
 *
 * @export
 * @param {INodeParameters} nodeValues The data on the node which decides if the parameter
 *                                    should be displayed
 * @param {(INodeProperties | INodeCredentialDescription)} parameter The parameter to check if it should be displayed
 * @param {INodeParameters} [nodeValuesRoot] The root node-parameter-data
 * @returns
 */
export function displayParameter(nodeValues: INodeParameters, parameter: INodeProperties | INodeCredentialDescription, nodeValuesRoot?: INodeParameters) {
	if (!parameter.displayOptions) {
		return true;
	}

	nodeValuesRoot = nodeValuesRoot || nodeValues;

	let value;
	const values: any[] = []; // tslint:disable-line:no-any
	if (parameter.displayOptions.show) {
		// All the defined rules have to match to display parameter
		for (const propertyName of Object.keys(parameter.displayOptions.show)) {
			if (propertyName.charAt(0) === '/') {
				// Get the value from the root of the node
				value = get(nodeValuesRoot, propertyName.slice(1));
			} else {
				// Get the value from current level
				value = get(nodeValues, propertyName);
			}

			values.length = 0;
			if (!Array.isArray(value)) {
				values.push(value);
			} else {
				values.push.apply(values, value);
			}

			if (values.length === 0 || !parameter.displayOptions.show[propertyName].some(v => values.includes(v))) {
				return false;
			}
		}
	}

	if (parameter.displayOptions.hide) {
		// Any of the defined hide rules have to match to hide the paramter
		for (const propertyName of Object.keys(parameter.displayOptions.hide)) {
			if (propertyName.charAt(0) === '/') {
				// Get the value from the root of the node
				value = get(nodeValuesRoot, propertyName.slice(1));
			} else {
				// Get the value from current level
				value = get(nodeValues, propertyName);
			}

			values.length = 0;
			if (!Array.isArray(value)) {
				values.push(value);
			} else {
				values.push.apply(values, value);
			}

			if (values.length !== 0 && parameter.displayOptions.hide[propertyName].some(v => values.includes(v))) {
				return false;
			}
		}
	}

	return true;
}

/**
 * Returns if the given parameter should be displayed or not considering the path
 * to the properties
 *
 * @export
 * @param {INodeParameters} nodeValues The data on the node which decides if the parameter
 *                                    should be displayed
 * @param {(INodeProperties | INodeCredentialDescription)} parameter The parameter to check if it should be displayed
 * @param {string} path The path to the property
 * @returns
 */
export function displayParameterPath(nodeValues: INodeParameters, parameter: INodeProperties | INodeCredentialDescription, path: string) {
	let resolvedNodeValues = nodeValues;
	if (path !== '') {
		resolvedNodeValues = get(
			nodeValues,
			path,
		) as INodeParameters;
	}

	// Get the root parameter data
	let nodeValuesRoot = nodeValues;
	if (path && path.split('.').indexOf('parameters') === 0) {
		nodeValuesRoot = get(
			nodeValues,
			'parameters',
		) as INodeParameters;
	}

	return displayParameter(resolvedNodeValues, parameter, nodeValuesRoot);
}

/**
 * Returns the context data
 *
 * @export
 * @param {IRunExecutionData} runExecutionData The run execution data
 * @param {string} type The data type. "node"/"flow"
 * @param {INode} [node] If type "node" is set the node to return the context of has to be supplied
 * @returns {IContextObject}
 */
export function getContext(runExecutionData: IRunExecutionData, type: string, node?: INode): IContextObject {
	if (runExecutionData.executionData === undefined) {
		// TODO: Should not happen leave it for test now
		throw new Error('The "executionData" is not initialized!');
	}

	let key: string;
	if (type === 'flow') {
		key = 'flow';
	} else if (type === 'node') {
		if (node === undefined) {
			throw new Error(`The request data of context type "node" the node parameter has to be set!`);
		}
		key = `node:${node.name}`;
	} else {
		throw new Error(`The context type "${type}" is not know. Only "flow" and node" are supported!`);
	}

	if (runExecutionData.executionData.contextData[key] === undefined) {
		runExecutionData.executionData.contextData[key] = {};
	}

	return runExecutionData.executionData.contextData[key];
}

/**
 * Returns which parameters are dependent on which
 *
 * @export
 * @param {INodeProperties[]} nodePropertiesArray
 * @returns {IParameterDependencies}
 */
export function getParamterDependencies(nodePropertiesArray: INodeProperties[]): IParameterDependencies {
	const dependencies: IParameterDependencies = {};

	let displayRule: string;
	let parameterName: string;
	for (const nodeProperties of nodePropertiesArray) {
		if (dependencies[nodeProperties.name] === undefined) {
			dependencies[nodeProperties.name] = [];
		}
		if (nodeProperties.displayOptions === undefined) {
			// Does not have any dependencies
			continue;
		}

		for (displayRule of Object.keys(nodeProperties.displayOptions)) {
			// @ts-ignore
			for (parameterName of Object.keys(nodeProperties.displayOptions[displayRule])) {
				if (!dependencies[nodeProperties.name].includes(parameterName)) {
					dependencies[nodeProperties.name].push(parameterName);
				}
			}
		}
	}

	return dependencies;
}

/**
 * Returns in which order the parameters should be resolved
 * to have the paramters available they are depent on
 *
 * @export
 * @param {INodeProperties[]} nodePropertiesArray
 * @param {IParameterDependencies} parameterDependencies
 * @returns {number[]}
 */
export function getParamterResolveOrder(nodePropertiesArray: INodeProperties[], parameterDependencies: IParameterDependencies): number[] {
	const executionOrder: number[] = [];
	const indexToResolve = Array.from({ length: nodePropertiesArray.length }, (v, k) => k);
	const resolvedParamters: string[] = [];

	let index: number;
	let property: INodeProperties;

	let lastIndexLength = indexToResolve.length;
	let lastIndexReduction = -1;

	let itterations = 0 ;

	while (indexToResolve.length !== 0) {
		itterations += 1;

		index = indexToResolve.shift() as number;
		property = nodePropertiesArray[index];

		if (parameterDependencies[property.name].length === 0) {
			// Does not have any dependencies so simply add
			executionOrder.push(index);
			resolvedParamters.push(property.name);
			continue;
		}

		// Parameter has dependencies
		for (const dependency of parameterDependencies[property.name]) {
			if (!resolvedParamters.includes(dependency)) {
				if (dependency.charAt(0) === '/') {
					// Assume that root level depenencies are resolved
					continue;
				}
				// Dependencies for that paramter are still missing so
				// try to add again later
				indexToResolve.push(index);
				continue;
			}
		}

		// All dependencies got found so add
		executionOrder.push(index);
		resolvedParamters.push(property.name);

		if (indexToResolve.length < lastIndexLength) {
			lastIndexReduction = itterations;
		}

		if (itterations > lastIndexReduction + nodePropertiesArray.length) {
			throw new Error('Could not resolve parameter depenencies. Max itterations got reached!');
		}
		lastIndexLength = indexToResolve.length;
	}

	return executionOrder;
}

/**
 * Returns the node parameter values. Depending on the settings it either just returns the none
 * default values or it applies all the default values.
 *
 * @export
 * @param {INodeProperties[]} nodePropertiesArray The properties which exist and their settings
 * @param {INodeParameters} nodeValues The node parameter data
 * @param {boolean} returnDefaults If default values get added or only none default values returned
 * @param {boolean} returnNoneDisplayed If also values which should not be displayed should be returned
 * @param {boolean} [onlySimpleTypes=false] If only simple types should be resolved
 * @param {boolean} [dataIsResolved=false] If nodeValues are already fully resolved (so that all default values got added already)
 * @param {INodeParameters} [nodeValuesRoot] The root node-parameter-data
 * @returns {(INodeParameters | null)}
 */
export function getNodeParameters(nodePropertiesArray: INodeProperties[], nodeValues: INodeParameters, returnDefaults: boolean, returnNoneDisplayed: boolean, onlySimpleTypes = false, dataIsResolved = false, nodeValuesRoot?: INodeParameters, parentType?: string, parameterDependencies?: IParameterDependencies): INodeParameters | null {
	if (parameterDependencies === undefined) {
		parameterDependencies = getParamterDependencies(nodePropertiesArray);
	}

	// Get the parameter names which get used multiple times as for this
	// ones we have to always check which ones get displayed and which ones not
	const duplicateParameterNames: string[] = [];
	const parameterNames: string[] = [];
	for (const nodeProperties of nodePropertiesArray) {
		if (parameterNames.includes(nodeProperties.name)) {
			if (!duplicateParameterNames.includes(nodeProperties.name)) {
				duplicateParameterNames.push(nodeProperties.name);
			}
		} else {
			parameterNames.push(nodeProperties.name);
		}
	}

	const nodeParameters: INodeParameters = {};
	const nodeParametersFull: INodeParameters = {};

	let nodeValuesDisplayCheck = nodeParametersFull;
	if (dataIsResolved !== true && returnNoneDisplayed === false) {
		nodeValuesDisplayCheck = getNodeParameters(nodePropertiesArray, nodeValues, true, true, true, true, nodeValuesRoot, parentType, parameterDependencies) as INodeParameters;
	}

	nodeValuesRoot = nodeValuesRoot || nodeValuesDisplayCheck;

	// Go through the parameters in order of their dependencies
	const parameterItterationOrderIndex = getParamterResolveOrder(nodePropertiesArray, parameterDependencies);

	for (const parameterIndex of parameterItterationOrderIndex) {
		const nodeProperties = nodePropertiesArray[parameterIndex];
		if (nodeValues[nodeProperties.name] === undefined && (returnDefaults === false || parentType === 'collection')) {
			// The value is not defined so go to the next
			continue;
		}

		if (returnNoneDisplayed === false && !displayParameter(nodeValuesDisplayCheck, nodeProperties, nodeValuesRoot)) {
			if (returnNoneDisplayed === false) {
				continue;
			}
			if (returnDefaults === false) {
				continue;
			}
		}

		if (!['collection', 'fixedCollection'].includes(nodeProperties.type)) {
			// Is a simple property so can be set as it is

			if (duplicateParameterNames.includes(nodeProperties.name)) {
				if (!displayParameter(nodeValuesDisplayCheck, nodeProperties, nodeValuesRoot)) {
					continue;
				}
			}

			if (returnDefaults === true) {
				// Set also when it has the default value
				if (['boolean', 'number', 'options'].includes(nodeProperties.type)) {
					// Boolean, numbers and options are special as false and 0 are valid values
					// and should not be replaced with default value
					nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name] !== undefined ? nodeValues[nodeProperties.name] : nodeProperties.default;
				} else {
					nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name] || nodeProperties.default;
				}
				nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
			} else if (nodeValues[nodeProperties.name] !== nodeProperties.default || (nodeValues[nodeProperties.name] !== undefined && parentType === 'collection')) {
				// Set only if it is different to the default value
				nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name];
				nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
				continue;
			}
		}

		if (onlySimpleTypes === true) {
			// It is only supposed to resolve the simple types. So continue.
			continue;
		}

		// Is a complex property so check lower levels
		let tempValue: INodeParameters | null;
		if (nodeProperties.type === 'collection') {
			// Is collection

			if (nodeProperties.typeOptions !== undefined && nodeProperties.typeOptions.multipleValues === true) {
				// Multiple can be set so will be an array

				// Return directly the values like they are
				if (nodeValues[nodeProperties.name] !== undefined) {
					nodeParameters[nodeProperties.name] = nodeValues[nodeProperties.name];
				} else if (returnDefaults === true) {
					// Does not have values defined but defaults should be returned which is in the
					// case of a collection with multipleValues always an empty array
					nodeParameters[nodeProperties.name] = [];
				}
				nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
			} else {
				if (nodeValues[nodeProperties.name] !== undefined) {
					// Has values defined so get them
					const tempNodeParameters = getNodeParameters(nodeProperties.options as INodeProperties[], nodeValues[nodeProperties.name] as INodeParameters, returnDefaults, returnNoneDisplayed, false, false, nodeValuesRoot, nodeProperties.type);

					if (tempNodeParameters !== null) {
						nodeParameters[nodeProperties.name] = tempNodeParameters;
						nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
					}
				} else if (returnDefaults === true) {
					// Does not have values defined but defaults should be returned
					nodeParameters[nodeProperties.name] = JSON.parse(JSON.stringify(nodeProperties.default));
					nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
				}
			}
		} else if (nodeProperties.type === 'fixedCollection') {
			// Is fixedCollection

			const collectionValues: INodeParameters = {};
			let tempNodeParameters: INodeParameters;
			let tempNodePropertiesArray: INodeProperties[];
			let nodePropertyOptions: INodePropertyCollection | undefined;

			let propertyValues = nodeValues[nodeProperties.name];
			if (returnDefaults === true) {
				if (propertyValues === undefined) {
					propertyValues = JSON.parse(JSON.stringify(nodeProperties.default));
				}
			}

			// Itterate over all collections
			for (const itemName of Object.keys(propertyValues)) {
				if (nodeProperties.typeOptions !== undefined && nodeProperties.typeOptions.multipleValues === true) {
					// Multiple can be set so will be an array

					const tempArrayValue: INodeParameters[] = [];
					// Itterate over all items as it contains multiple ones
					for (const nodeValue of (propertyValues as INodeParameters)[itemName] as INodeParameters[]) {
						nodePropertyOptions = nodeProperties!.options!.find((nodePropertyOptions) => nodePropertyOptions.name === itemName) as INodePropertyCollection;

						if (nodePropertyOptions === undefined) {
							throw new Error(`Could not find property option "${itemName}" for "${nodeProperties.name}"`);
						}

						tempNodePropertiesArray = (nodePropertyOptions as INodePropertyCollection).values!;
						tempValue = getNodeParameters(tempNodePropertiesArray, nodeValue as INodeParameters, returnDefaults, returnNoneDisplayed, false, false, nodeValuesRoot, nodeProperties.type);
						if (tempValue !== null) {
							tempArrayValue.push(tempValue);
						}
					}
					collectionValues[itemName] = tempArrayValue;
				} else {
					// Only one can be set so is an object of objects
					tempNodeParameters = {};

					// Get the options of the current item
					const nodePropertyOptions = nodeProperties!.options!.find((data) => data.name === itemName);

					if (nodePropertyOptions !== undefined) {
						tempNodePropertiesArray = (nodePropertyOptions as INodePropertyCollection).values!;
						tempValue = getNodeParameters(tempNodePropertiesArray, (nodeValues[nodeProperties.name] as INodeParameters)[itemName] as INodeParameters, returnDefaults, returnNoneDisplayed, false, false, nodeValuesRoot, nodeProperties.type);
						if (tempValue !== null) {
							Object.assign(tempNodeParameters, tempValue);
						}
					}

					if (Object.keys(tempNodeParameters).length !== 0) {
						collectionValues[itemName] = tempNodeParameters;
					}
				}
			}

			if (Object.keys(collectionValues).length !== 0 || returnDefaults === true) {
				// Set only if value got found

				if (returnDefaults === true) {
					// Set also when it has the default value
					if (collectionValues === undefined) {
						nodeParameters[nodeProperties.name] = JSON.parse(JSON.stringify(nodeProperties.default));
					} else {
						nodeParameters[nodeProperties.name] = collectionValues;
					}
					nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
				} else if (collectionValues !== nodeProperties.default) {
					// Set only if values got found and it is not the default
					nodeParameters[nodeProperties.name] = collectionValues;
					nodeParametersFull[nodeProperties.name] = nodeParameters[nodeProperties.name];
				}
			}
		}
	}

	return nodeParameters;
}

/**
 * Brings the output data in a format that can be returned from a node
 *
 * @export
 * @param {INodeExecutionData[]} outputData
 * @param {number} [outputIndex=0]
 * @returns {Promise<INodeExecutionData[][]>}
 */
export async function prepareOutputData(outputData: INodeExecutionData[], outputIndex = 0): Promise<INodeExecutionData[][]> {
	// TODO: Check if node has output with that index
	const returnData = [];

	for (let i = 0; i < outputIndex; i++) {
		returnData.push([]);
	}

	returnData.push(outputData);

	return returnData;
}

/**
 * Returns all the webhooks which should be created for the give node
 *
 * @export
 *
 * @param {INode} node
 * @returns {IWebhookData[]}
 */
export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData: IWorkflowExecuteAdditionalData): IWebhookData[] {
	if (node.disabled === true) {
		// Node is disabled so webhooks will also not be enabled
		return [];
	}

	const nodeType = workflow.nodeTypes.getByName(node.type) as INodeType;

	if (nodeType.description.webhooks === undefined) {
		// Node does not have any webhooks so return
		return [];
	}

	const workflowId = workflow.id || '__UNSAVED__';

	const returnData: IWebhookData[] = [];
	for (const webhookDescription of nodeType.description.webhooks) {
		let nodeWebhookPath = workflow.getSimpleParameterValue(node, webhookDescription['path']);
		if (nodeWebhookPath === undefined) {
			// TODO: Use a proper logger
			console.error(`No webhook path could be found for node "${node.name}" in workflow "${workflowId}".`);
			continue;
		}

		nodeWebhookPath = nodeWebhookPath.toString();

		if (nodeWebhookPath.charAt(0) === '/') {
			nodeWebhookPath = nodeWebhookPath.slice(1);
		}

		const isFullPath: boolean = workflow.getSimpleParameterValue(node, webhookDescription['isFullPath'], false) as boolean;
		const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath, isFullPath);

		const httpMethod = workflow.getSimpleParameterValue(node, webhookDescription['httpMethod'], 'GET');

		if (httpMethod === undefined) {
			// TODO: Use a proper logger
			console.error(`The webhook "${path}" for node "${node.name}" in workflow "${workflowId}" could not be added because the httpMethod is not defined.`);
			continue;
		}

		returnData.push({
			httpMethod: httpMethod.toString() as WebhookHttpMethod,
			node: node.name,
			path,
			webhookDescription,
			workflowId,
			workflowExecuteAdditionalData: additionalData,
		});
	}

	return returnData;
}

export function getNodeWebhooksBasic(workflow: Workflow, node: INode): IWebhookData[] {
	if (node.disabled === true) {
		// Node is disabled so webhooks will also not be enabled
		return [];
	}

	const nodeType = workflow.nodeTypes.getByName(node.type) as INodeType;

	if (nodeType.description.webhooks === undefined) {
		// Node does not have any webhooks so return
		return [];
	}

	const workflowId = workflow.id || '__UNSAVED__';

	const returnData: IWebhookData[] = [];
	for (const webhookDescription of nodeType.description.webhooks) {
		let nodeWebhookPath = workflow.getSimpleParameterValue(node, webhookDescription['path']);
		if (nodeWebhookPath === undefined) {
			// TODO: Use a proper logger
			console.error(`No webhook path could be found for node "${node.name}" in workflow "${workflowId}".`);
			continue;
		}

		nodeWebhookPath = nodeWebhookPath.toString();

		if (nodeWebhookPath.charAt(0) === '/') {
			nodeWebhookPath = nodeWebhookPath.slice(1);
		}

		const isFullPath: boolean = workflow.getSimpleParameterValue(node, webhookDescription['isFullPath'], false) as boolean;

		const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath, isFullPath);

		const httpMethod = workflow.getSimpleParameterValue(node, webhookDescription['httpMethod']);

		if (httpMethod === undefined) {
			// TODO: Use a proper logger
			console.error(`The webhook "${path}" for node "${node.name}" in workflow "${workflowId}" could not be added because the httpMethod is not defined.`);
			continue;
		}

		//@ts-ignore
		returnData.push({
			httpMethod: httpMethod.toString() as WebhookHttpMethod,
			node: node.name,
			path,
			webhookDescription,
			workflowId,
		});
	}

	return returnData;
}

/**
 * Returns the webhook path
 *
 * @export
 * @param {string} workflowId
 * @param {string} nodeTypeName
 * @param {string} path
 * @returns {string}
 */
export function getNodeWebhookPath(workflowId: string, node: INode, path: string, isFullPath?: boolean): string {
	let webhookPath = '';
	if (node.webhookPath === undefined) {
		webhookPath = `${workflowId}/${encodeURIComponent(node.name.toLowerCase())}/${path}`;
	} else {
		if (isFullPath === true) {
			return path;
		}
		webhookPath = `${node.webhookPath}/${path}`;
	}
	return webhookPath;
}

/**
 * Returns the webhook URL
 *
 * @export
 * @param {string} baseUrl
 * @param {string} workflowId
 * @param {string} nodeTypeName
 * @param {string} path
 * @param {boolean} isFullPath
 * @returns {string}
 */
export function getNodeWebhookUrl(baseUrl: string, workflowId: string, node: INode, path: string, isFullPath?: boolean): string {
	return `${baseUrl}/${getNodeWebhookPath(workflowId, node, path, isFullPath)}`;
}

/**
 * Returns all the parameter-issues of the node
 *
 * @export
 * @param {INodeProperties[]} nodePropertiesArray The properties of the node
 * @param {INode} node The data of the node
 * @returns {(INodeIssues | null)}
 */
export function getNodeParametersIssues(nodePropertiesArray: INodeProperties[], node: INode): INodeIssues | null {
	const foundIssues: INodeIssues = {};
	let propertyIssues: INodeIssues;

	if (node.disabled === true) {
		// Ignore issues on disabled nodes
		return null;
	}

	for (const nodeProperty of nodePropertiesArray) {
		propertyIssues = getParameterIssues(nodeProperty, node.parameters, '');
		mergeIssues(foundIssues, propertyIssues);
	}

	if (Object.keys(foundIssues).length === 0) {
		return null;
	}

	return foundIssues;
}

/**
 * Returns the issues of the node as string
 *
 * @export
 * @param {INodeIssues} issues The issues of the node
 * @param {INode} node The node
 * @returns {string[]}
 */
export function nodeIssuesToString(issues: INodeIssues, node?: INode): string[] {
	const nodeIssues = [];

	if (issues.execution !== undefined) {
		nodeIssues.push(`Execution Error.`);
	}

	const objectProperties = [
		'parameters',
		'credentials',
	];

	let issueText: string, parameterName: string;
	for (const propertyName of objectProperties) {
		if (issues[propertyName] !== undefined) {
			for (parameterName of Object.keys(issues[propertyName] as object)) {
				for (issueText of (issues[propertyName] as INodeIssueObjectProperty)[parameterName]) {
					nodeIssues.push(issueText);
				}
			}
		}
	}

	if (issues.typeUnknown !== undefined) {
		if (node !== undefined) {
			nodeIssues.push(`Node Type "${node.type}" is not known.`);
		} else {
			nodeIssues.push(`Node Type is not known.`);
		}
	}

	return nodeIssues;
}

/**
 * Adds an issue if the parameter is not defined
 *
 * @export
 * @param {INodeIssues} foundIssues The already found issues
 * @param {INodeProperties} nodeProperties The properties of the node
 * @param {NodeParameterValue} value The value of the parameter
 */
export function addToIssuesIfMissing(foundIssues: INodeIssues, nodeProperties: INodeProperties, value: NodeParameterValue) {
	// TODO: Check what it really has when undefined
	if ((nodeProperties.type === 'string' && (value === '' || value === undefined)) ||
		(nodeProperties.type === 'multiOptions' && Array.isArray(value) && value.length === 0) ||
		(nodeProperties.type === 'dateTime' && value === undefined)) {
		// Parameter is requried but empty
		if (foundIssues.parameters === undefined) {
			foundIssues.parameters = {};
		}
		if (foundIssues.parameters[nodeProperties.name] === undefined) {
			foundIssues.parameters[nodeProperties.name] = [];
		}

		foundIssues.parameters[nodeProperties.name].push(`Parameter "${nodeProperties.displayName}" is required.`);
	}
}

/**
 * Returns the parameter value
 *
 * @export
 * @param {INodeParameters} nodeValues The values of the node
 * @param {string} parameterName The name of the parameter to return the value of
 * @param {string} path The path to the properties
 * @returns
 */
export function getParameterValueByPath(nodeValues: INodeParameters, parameterName: string, path: string) {
	return get(
		nodeValues,
		path ? path + '.' + parameterName : parameterName,
	);
}

/**
 * Returns all the issues with the given node-values
 *
 * @export
 * @param {INodeProperties} nodeProperties The properties of the node
 * @param {INodeParameters} nodeValues The values of the node
 * @param {string} path The path to the properties
 * @returns {INodeIssues}
 */
export function getParameterIssues(nodeProperties: INodeProperties, nodeValues: INodeParameters, path: string): INodeIssues {
	const foundIssues: INodeIssues = {};
	let value;

	if (nodeProperties.required === true) {
		if (displayParameterPath(nodeValues, nodeProperties, path)) {
			value = getParameterValueByPath(nodeValues, nodeProperties.name, path);

			if (nodeProperties.typeOptions !== undefined && nodeProperties.typeOptions.multipleValues !== undefined) {
				// Multiple can be set so will be an array
				if (Array.isArray(value)) {
					for (const singleValue of value as NodeParameterValue[]) {
						addToIssuesIfMissing(foundIssues, nodeProperties, singleValue as NodeParameterValue);
					}
				}
			} else {
				// Only one can be set so will be a single value
				addToIssuesIfMissing(foundIssues, nodeProperties, value as NodeParameterValue);
			}
		}
	}

	// Check if there are any child parameters
	if (nodeProperties.options === undefined) {
		// There are none so nothing else to check
		return foundIssues;
	}

	// Check the child parameters

	// Important:
	// Checks the child properties only if the property is defined on current level.
	// That means that the required flag works only for the current level only. If
	// it is set on a lower level it means that the property is only required in case
	// the parent property got set.

	let basePath = path ? `${path}.` : '';

	const checkChildNodeProperties: Array<{
		basePath: string;
		data: INodeProperties;
	}> = [];

	// Collect all the properties to check
	if (nodeProperties.type === 'collection') {
		for (const option of nodeProperties.options) {
			checkChildNodeProperties.push({
				basePath,
				data: option as INodeProperties,
			});
		}
	} else if (nodeProperties.type === 'fixedCollection') {
		basePath = basePath ? `${basePath}.` : '' + nodeProperties.name + '.';

		let propertyOptions: INodePropertyCollection;
		for (propertyOptions of nodeProperties.options as INodePropertyCollection[]) {
			// Check if the option got set and if not skip it
			value = getParameterValueByPath(nodeValues, propertyOptions.name, basePath.slice(0, -1));
			if (value === undefined) {
				continue;
			}

			if (nodeProperties.typeOptions !== undefined && nodeProperties.typeOptions.multipleValues !== undefined) {
				// Multiple can be set so will be an array of objects
				if (Array.isArray(value)) {
					for (let i = 0; i < (value as INodeParameters[]).length; i++) {
						for (const option of propertyOptions.values) {
							checkChildNodeProperties.push({
								basePath: `${basePath}${propertyOptions.name}[${i}]`,
								data: option as INodeProperties,
							});
						}
					}
				}
			} else {
				// Only one can be set so will be an object
				for (const option of propertyOptions.values) {
					checkChildNodeProperties.push({
						basePath: basePath + propertyOptions.name,
						data: option as INodeProperties,
					});
				}
			}
		}
	} else {
		// For all other types there is nothing to check so return
		return foundIssues;
	}

	let propertyIssues;

	for (const optionData of checkChildNodeProperties) {
		propertyIssues = getParameterIssues(optionData.data as INodeProperties, nodeValues, optionData.basePath);
		mergeIssues(foundIssues, propertyIssues);
	}

	return foundIssues;
}

/**
 * Merges multiple NodeIssues together
 *
 * @export
 * @param {INodeIssues} destination The issues to merge into
 * @param {(INodeIssues | null)} source The issues to merge
 * @returns
 */
export function mergeIssues(destination: INodeIssues, source: INodeIssues | null) {
	if (source === null) {
		// Nothing to merge
		return;
	}

	if (source.execution === true) {
		destination.execution = true;
	}

	const objectProperties = [
		'parameters',
		'credentials',
	];

	let destinationProperty: INodeIssueObjectProperty;
	for (const propertyName of objectProperties) {
		if (source[propertyName] !== undefined) {
			if (destination[propertyName] === undefined) {
				destination[propertyName] = {};
			}

			let parameterName: string;
			for (parameterName of Object.keys(source[propertyName] as INodeIssueObjectProperty)) {
				destinationProperty = destination[propertyName] as INodeIssueObjectProperty;
				if (destinationProperty[parameterName] === undefined) {
					destinationProperty[parameterName] = [];
				}
				destinationProperty[parameterName].push.apply(destinationProperty[parameterName], (source[propertyName] as INodeIssueObjectProperty)[parameterName]);
			}
		}
	}

	if (source.typeUnknown === true) {
		destination.typeUnknown = true;
	}
}


import { INodeProperties } from "n8n-workflow";

export const taskOperations = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		displayOptions: {
			show: {
				resource: [
					'task',
				],
			},
		},
		options: [
			{
				name: 'Create',
				value: 'create',
				description: 'Create a new task',
			},
			{
				name: 'Delete',
				value: 'delete',
				description: 'Delete a task',
			},
			{
				name: 'Get',
				value: 'get',
				description: 'Get data of a task',
			},
			{
				name: 'Get All',
				value: 'getAll',
				description: 'Get data of all tasks',
			},
		],
		default: 'create',
		description: 'The operation to perform.',
	},
] as INodeProperties[];

export const taskFields = [
/* -------------------------------------------------------------------------- */
/*                                 task:create                               */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Additional Fields',
		name: 'additionalFields',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource: [
					'task',
				],
			},
		},
		options: [
			{
				displayName: 'Client',
				name: 'client',
				type: 'options',
				typeOptions: {
					loadOptionsMethod: 'getClients',
				},
				default: '',
			},
			{
				displayName: 'Custom Value 1',
				name: 'customValue1',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Custom Value 2',
				name: 'customValue2',
				type: 'string',
				default: '',
			},
			{
				displayName: 'Description',
				name: 'description',
				type: 'string',
				typeOptions: {
					alwaysOpenEditWindow: true,
				},
				default: '',
			},
			{
				displayName: 'Project',
				name: 'project',
				type: 'options',
				typeOptions: {
					loadOptionsMethod: 'getProjects',
				},
				default: '',
			},
		],
	},
	{
		displayName: 'Time Logs',
		name: 'timeLogsUi',
		placeholder: 'Add Time Log',
		type: 'fixedCollection',
		typeOptions: {
			multipleValues: true,
		},
		displayOptions: {
			show: {
				resource: [
					'task',
				],
				operation: [
					'create',
				],
			},
		},
		default: {},
		options: [
			{
				name: 'timeLogsValues',
				displayName: 'Time Log',
				values: [
					{
						displayName: 'Start Date',
						name: 'startDate',
						type: 'dateTime',
						default: '',
					},
					{
						displayName: 'End Date',
						name: 'endDate',
						type: 'dateTime',
						default: '',
					},
					{
						displayName: 'Duration (Hours)',
						name: 'duration',
						type: 'number',
						typeOptions: {
							minValue: 0,
						},
						default: 0,
					},
				],
			},
		],
	},
/* -------------------------------------------------------------------------- */
/*                                 task:delete                                */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Task ID',
		name: 'taskId',
		type: 'string',
		required: true,
		displayOptions: {
			show: {
				resource: [
					'task',
				],
				operation: [
					'delete',
				],
			},
		},
	},
/* -------------------------------------------------------------------------- */
/*                                  task:get                                  */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Task ID',
		name: 'taskId',
		type: 'string',
		required: true,
		displayOptions: {
			show: {
				resource: [
					'task',
				],
				operation: [
					'get',
				],
			},
		},
	},
	{
		displayName: 'Options',
		name: 'options',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				operation: [
					'get',
				],
				resource: [
					'task',
				],
			},
		},
		options: [
			{
				displayName: 'Include',
				name: 'include',
				type: 'options',
				options: [
					{
						name: 'Client',
						value: 'client',
					},
				],
				default: 'client',
			},
		],
	},
/* -------------------------------------------------------------------------- */
/*                                  task:getAll                              */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Return All',
		name: 'returnAll',
		type: 'boolean',
		displayOptions: {
			show: {
				resource: [
					'task',
				],
				operation: [
					'getAll',
				],
			},
		},
		default: false,
		description: 'If all results should be returned or only up to a given limit.',
	},
	{
		displayName: 'Limit',
		name: 'limit',
		type: 'number',
		displayOptions: {
			show: {
				resource: [
					'task',
				],
				operation: [
					'getAll',
				],
				returnAll: [
					false,
				],
			},
		},
		typeOptions: {
			minValue: 1,
			maxValue: 60,
		},
		default: 50,
		description: 'How many results to return.',
	},
	{
		displayName: 'Options',
		name: 'options',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				operation: [
					'getAll',
				],
				resource: [
					'task',
				],
			},
		},
		options: [
			{
				displayName: 'Include',
				name: 'include',
				type: 'options',
				options: [
					{
						name: 'Client',
						value: 'client',
					},
				],
				default: 'client',
			},
		],
	},

] as INodeProperties[];


<template>

		<el-dialog class="n8n-about" :visible="dialogVisible" append-to-body width="50%" title="About n8n" :before-close="closeDialog">

				<el-row>
					<el-col :span="8" class="info-name">
						n8n Version:
					</el-col>
					<el-col :span="16">
						{{versionCli}}
					</el-col>
				</el-row>
				<el-row>
					<el-col :span="8" class="info-name">
						Source Code:
					</el-col>
					<el-col :span="16">
						https://github.com/n8n-io/n8n
					</el-col>
				</el-row>
				<el-row>
					<el-col :span="8" class="info-name">
						License:
					</el-col>
					<el-col :span="16">
						Apache 2.0 with Commons Clause
					</el-col>
				</el-row>

					<el-button type="success" @click="closeDialog">
						Close
					</el-button>

		</el-dialog>

</template>


import {
	INodeProperties,
 } from 'n8n-workflow';

export const orderOperations = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		displayOptions: {
			show: {
				resource: [
					'order',
				],
			},
		},
		options: [
			{
				name: 'Create',
				value: 'create',
				description: 'Create an order',
			},
			{
				name: 'Delete',
				value: 'delete',
				description: 'Delete an order',
			},
			{
				name: 'Get',
				value: 'get',
				description: 'Get an order',
			},
			{
				name: 'Get All',
				value: 'getAll',
				description: 'Get all orders',
			},
			{
				name: 'Update',
				value: 'update',
				description: 'Update an order',
			},
		],
		default: 'create',
		description: 'The operation to perform.',
	},
] as INodeProperties[];

export const orderFields = [

/* -------------------------------------------------------------------------- */
/*                                order:create                                */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Additional Fields',
		name: 'additionalFields',
		type: 'collection',
		placeholder: 'Add Field',
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource: [
					'order',
				],
			},
		},
		default: {},
		options: [
			{
				displayName: 'Billing Address',
				name: 'billingAddressUi',
				placeholder: 'Add Billing Address',
				type: 'fixedCollection',
				default: '',
				typeOptions: {
					multipleValues: false,
				},
				description: 'Billing address',
				options: [
					{
						name: 'billingAddressValues',
						displayName: 'Billing Address',
						values: [
							{
								displayName: 'First Name',
								name: 'firstName',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Last Name',
								name: 'lastName',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Company',
								name: 'company',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Country',
								name: 'country',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Address Line 1',
								name: 'address1',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Address Line 2',
								name: 'address2',
								type: 'string',
								default: '',
							},
							{
								displayName: 'City',
								name: 'city',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Province',
								name: 'province',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Zip Code',
								name: 'zip',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Phone',
								name: 'phone',
								type: 'string',
								default: '',
							},
						],
					},
				],
			},
			{
				displayName: 'Discount Codes',
				name: 'discountCodesUi',
				placeholder: 'Add Discount Code',
				type: 'fixedCollection',
				default: '',
				typeOptions: {
					multipleValues: true,
				},
				options: [
					{
						name: 'discountCodesValues',
						displayName: 'Discount Code',
						values: [
							{
								displayName: 'Amount',
								name: 'amount',
								type: 'string',
								default: '',
								description: `The amount that's deducted from the order total.`,
							},
							{
								displayName: 'Code',
								name: 'code',
								type: 'string',
								default: '',
								description: 'When the associated discount application is of type code',
							},
							{
								displayName: 'Type',
								name: 'type',
								type: 'options',
								options: [
									{
										name: 'Fixed Amount',
										value: 'fixedAmount',
										description: `Applies amount as a unit of the store's currency.`,
									},
									{
										name: 'Percentage',
										value: 'percentage',
										description: `Applies a discount of amount as a percentage of the order total.`,
									},
									{
										name: 'Shipping',
										value: 'shipping',
										description: `Applies a free shipping discount on orders that have a shipping rate less than or equal to amount.`,
									},
								],
								default: 'fixedAmount',
								description: 'When the associated discount application is of type code',
							},
						],
					},
				],
			},
			{
				displayName: 'Email',
				name: 'email',
				type: 'string',
				default: '',
				description: `The customer's email address.`,
			},
			{
				displayName: 'Fulfillment Status',
				name: 'fulfillmentStatus',
				type: 'options',
				options: [
					{
						name: 'Fulfilled',
						value: 'fulfilled',
						description: 'Every line item in the order has been fulfilled.',
					},
					{
						name: 'Null',
						value: 'null',
						description: 'None of the line items in the order have been fulfilled.',
					},
					{
						name: 'Partial',
						value: 'partial',
						description: 'At least one line item in the order has been fulfilled.',
					},
					{
						name: 'Restocked',
						value: 'restocked',
						description: 'Every line item in the order has been restocked and the order canceled.',
					},
				],
				default: '',
				description: `The order's status in terms of fulfilled line items`,
			},
			{
				displayName: 'Inventory Behaviour',
				name: 'inventoryBehaviour',
				type: 'options',
				options: [
					{
						name: 'Bypass',
						value: 'bypass',
						description: 'Do not claim inventory.',
					},
					{
						name: 'Decrement Ignoring Policy',
						value: 'decrementIgnoringPolicy',
						description: `Ignore the product's inventory policy and claim inventory.`,
					},
					{
						name: 'Decrement Obeying Policy',
						value: 'decrementObeyingPolicy',
						description: `Follow the product's inventory policy and claim inventory, if possible.`,
					},
				],
				default: 'bypass',
				description: `The behaviour to use when updating inventory.`,
			},
			{
				displayName: 'Location ID',
				name: 'locationId',
				type: 'options',
				typeOptions: {
					loadOptionsMethod: 'getLocations',
				},
				default: '',
				description: 'The ID of the physical location where the order was processed.',
			},
			{
				displayName: 'Note',
				name: 'note',
				type: 'string',
				typeOptions: {
					alwaysOpenEditWindow: true,
				},
				default: '',
				description: 'An optional note that a shop owner can attach to the order.',
			},
			{
				displayName: 'Send Fulfillment Receipt',
				name: 'sendFulfillmentReceipt',
				type: 'boolean',
				default: false,
				description: 'Whether to send a shipping confirmation to the customer.',
			},
			{
				displayName: 'Send Receipt',
				name: 'sendReceipt',
				type: 'boolean',
				default: false,
				description: 'Whether to send an order confirmation to the customer.',
			},
			{
				displayName: 'Shipping Address',
				name: 'shippingAddressUi',
				placeholder: 'Add Shipping',
				type: 'fixedCollection',
				default: '',
				typeOptions: {
					multipleValues: false,
				},
				description: 'Shipping Address',
				options: [
					{
						name: 'shippingAddressValues',
						displayName: 'shipping Address',
						values: [
							{
								displayName: 'First Name',
								name: 'firstName',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Last Name',
								name: 'lastName',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Company',
								name: 'company',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Country',
								name: 'country',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Address Line 1',
								name: 'address1',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Address Line 2',
								name: 'address2',
								type: 'string',
								default: '',
							},
							{
								displayName: 'City',
								name: 'city',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Province',
								name: 'province',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Zip Code',
								name: 'zip',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Phone',
								name: 'phone',
								type: 'string',
								default: '',
							},
						],
					},
				],
			},
			{
				displayName: 'Source Name',
				name: 'sourceName',
				type: 'string',
				default: '',
				description: 'Where the order originated. Can be set only during order creation, and is not writeable afterwards',
			},
			{
				displayName: 'Tags',
				name: 'tags',
				type: 'string',
				default: '',
				description: 'Tags attached to the order, formatted as a string of comma-separated values.',
			},
			{
				displayName: 'Test',
				name: 'test',
				type: 'boolean',
				default: '',
				description: 'Whether this is a test order.',
			},
		],
	},
	{
		displayName: 'Line Items',
		name: 'limeItemsUi',
		placeholder: 'Add Line Item',
		type: 'fixedCollection',
		typeOptions: {
			multipleValues: true,
		},
		displayOptions: {
			show: {
				resource: [
					'order',
				],
				operation: [
					'create',
				],
			},
		},
		default: {},
		options: [
			{
				displayName: 'Line Item',
				name: 'lineItemValues',
				values: [
					{
						displayName: 'Product ID',
						name: 'productId',
						type: 'options',
						typeOptions: {
							loadOptionsMethod: 'getProducts',
						},
						default: '',
						description: 'The ID of the product that the line item belongs to',
					},
					{
						displayName: 'Variant ID',
						name: 'variantId',
						type: 'string',
						default: '',
						description: 'The ID of the product variant.',
					},
					{
						displayName: 'Title',
						name: 'title',
						type: 'string',
						default: '',
						description: 'The title of the product.',
					},
					{
						displayName: 'Grams',
						name: 'grams',
						type: 'string',
						default: '',
						description: 'The weight of the item in grams.',
					},
					{
						displayName: 'Quantity',
						name: 'quantity',
						type: 'number',
						typeOptions: {
							minValue: 1,
						},
						default: 1,
						description: 'The number of items that were purchased.',
					},
					{
						displayName: 'Price',
						name: 'price',
						type: 'string',
						default: '',
					},
				],
			},
		],
	},
/* -------------------------------------------------------------------------- */
/*                                order:delete                                */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Order ID',
		name: 'orderId',
		type: 'string',
		default: '',
		displayOptions: {
			show: {
				resource: [
					'order',
				],
				operation: [
					'delete',
				],
			},
		},
		required: true,
	},
/* -------------------------------------------------------------------------- */
/*                                order:get                                   */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Order ID',
		name: 'orderId',
		type: 'string',
		default: '',
		displayOptions: {
			show: {
				resource: [
					'order',
				],
				operation: [
					'get',
				],
			},
		},
		required: true,
	},
	{
		displayName: 'Options',
		name: 'options',
		type: 'collection',
		placeholder: 'Add Field',
		displayOptions: {
			show: {
				operation: [
					'get',
				],
				resource: [
					'order',
				],
			},
		},
		default: {},
		options: [
			{
				displayName: 'Fields',
				name: 'fields',
				type: 'string',
				default: '',
				description: 'Fields the order will return, formatted as a string of comma-separated values. By default all the fields are returned',
			},
		],
	},
/* -------------------------------------------------------------------------- */
/*                                order:getAll                                */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Return All',
		name: 'returnAll',
		type: 'boolean',
		displayOptions: {
			show: {
				resource: [
					'order',
				],
				operation: [
					'getAll',
				],
			},
		},
		default: false,
		description: 'If all results should be returned or only up to a given limit.',
	},
	{
		displayName: 'Limit',
		name: 'limit',
		type: 'number',
		displayOptions: {
			show: {
				resource: [
					'order',
				],
				operation: [
					'getAll',
				],
				returnAll: [
					false,
				],
			},
		},
		typeOptions: {
			minValue: 1,
			maxValue: 250,
		},
		default: 50,
		description: 'How many results to return.',
	},
	{
		displayName: 'Options',
		name: 'options',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				operation: [
					'getAll',
				],
				resource: [
					'order',
				],
			},
		},
		options: [
			{
				displayName: 'Attribution App ID',
				name: 'attributionAppId',
				type: 'string',
				default: '',
				description: 'Show orders attributed to a certain app, specified by the app ID. Set as current to show orders for the app currently consuming the API.',
			},
			{
				displayName: 'Created At Min',
				name: 'createdAtMin',
				type: 'dateTime',
				default: '',
				description: 'Show orders created at or after date ',
			},
			{
				displayName: 'Created At Max',
				name: 'createdAtMax',
				type: 'dateTime',
				default: '',
				description: 'Show orders created at or before date',
			},
			{
				displayName: 'Financial Status',
				name: 'financialStatus',
				type: 'options',
				options: [
					{
						name: 'Any',
						value: 'any',
						description: 'Show orders of any financial status.',
					},
					{
						name: 'Authorized',
						value: 'authorized',
						description: 'Show only authorized orders',
					},
					{
						name: 'Paid',
						value: 'paid',
						description: 'Show only paid orders',
					},
					{
						name: 'Partially Paid',
						value: 'partiallyPaid',
						description: 'Show only partially paid orders',
					},
					{
						name: 'Partially Refunded',
						value: 'partiallyRefunded',
						description: 'Show only partially refunded orders',
					},
					{
						name: 'Pending',
						value: 'pending',
						description: 'Show only pending orders',
					},
					{
						name: 'Refunded',
						value: 'refunded',
						description: 'Show only refunded orders',
					},
					{
						name: 'Voided',
						value: 'voided',
						description: 'Show only voided orders',
					},
					{
						name: 'Unpaid',
						value: 'unpaid',
						description: 'Show authorized and partially paid orders.',
					},
				],
				default: 'any',
				description: 'Filter orders by their financial status.',
			},
			{
				displayName: 'Fulfillment Status',
				name: 'fulfillmentStatus',
				type: 'options',
				options: [
					{
						name: 'Any',
						value: 'any',
						description: 'Show orders of any fulfillment status.',
					},
					{
						name: 'Partial',
						value: 'partial',
						description: 'Show partially shipped orders.',
					},
					{
						name: 'Shipped',
						value: 'shipped',
						description: 'Show orders that have been shipped. Returns orders with fulfillment_status of fulfilled.',
					},
					{
						name: 'Unshipped',
						value: 'unshipped',
						description: 'Show orders that have not yet been shipped. Returns orders with fulfillment_status of null.',
					},
					{
						name: 'Unfulfilled',
						value: 'unfulfilled',
						description: 'Returns orders with fulfillment_status of null or partial.',
					},
				],
				default: 'any',
				description: 'Filter orders by their fulfillment status.',
			},
			{
				displayName: 'Fields',
				name: 'fields',
				type: 'string',
				default: '',
				description: 'Fields the orders will return, formatted as a string of comma-separated values. By default all the fields are returned',
			},
			{
				displayName: 'IDs',
				name: 'ids',
				type: 'string',
				default: '',
				description: 'Retrieve only orders specified by a comma-separated list of order IDs.',
			},
			{
				displayName: 'Processed At Max',
				name: 'processedAtMax',
				type: 'dateTime',
				default: '',
				description: 'Show orders imported at or before date',
			},
			{
				displayName: 'Processed At Min',
				name: 'processedAtMin',
				type: 'dateTime',
				default: '',
				description: 'Show orders imported at or after date',
			},
			{
				displayName: 'Status',
				name: 'status',
				type: 'options',
				options: [
					{
						name: 'Any',
						value: 'any',
						description: 'Show orders of any status, including archived orders.',
					},
					{
						name: 'Cancelled',
						value: 'Cancelled',
						description: 'Show only canceled orders.',
					},
					{
						name: 'Closed',
						value: 'closed',
						description: 'Show only closed orders.',
					},
					{
						name: 'Open',
						value: 'open',
						description: 'Show only open orders.',
					},
				],
				default: 'open',
				description: 'Filter orders by their status.',
			},
			{
				displayName: 'Since ID',
				name: 'sinceId',
				type: 'string',
				default: '',
				description: 'Show orders after the specified ID.',
			},
			{
				displayName: 'Updated At Max',
				name: 'updatedAtMax',
				type: 'dateTime',
				default: '',
				description: 'Show orders last updated at or after date',
			},
			{
				displayName: 'Updated At Min',
				name: 'updatedAtMin',
				type: 'dateTime',
				default: '',
				description: 'Show orders last updated at or before date',
			},
		],
	},
/* -------------------------------------------------------------------------- */
/*                                order:update                                */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Order ID',
		name: 'orderId',
		type: 'string',
		default: '',
		displayOptions: {
			show: {
				resource: [
					'order',
				],
				operation: [
					'update',
				],
			},
		},
		required: true,
	},
	{
		displayName: 'Update Fields',
		name: 'updateFields',
		type: 'collection',
		placeholder: 'Add Field',
		displayOptions: {
			show: {
				operation: [
					'update',
				],
				resource: [
					'order',
				],
			},
		},
		default: {},
		options: [
			{
				displayName: 'Email',
				name: 'email',
				type: 'string',
				default: '',
				description: `The customer's email address.`,
			},
			{
				displayName: 'Location ID',
				name: 'locationId',
				type: 'options',
				typeOptions: {
					loadOptionsMethod: 'getLocations',
				},
				default: '',
				description: 'The ID of the physical location where the order was processed.',
			},
			{
				displayName: 'Note',
				name: 'note',
				type: 'string',
				typeOptions: {
					alwaysOpenEditWindow: true,
				},
				default: '',
				description: 'An optional note that a shop owner can attach to the order.',
			},
			{
				displayName: 'Shipping Address',
				name: 'shippingAddressUi',
				placeholder: 'Add Shipping',
				type: 'fixedCollection',
				default: '',
				typeOptions: {
					multipleValues: false,
				},
				description: 'Shipping Address',
				options: [
					{
						name: 'shippingAddressValues',
						displayName: 'shipping Address',
						values: [
							{
								displayName: 'First Name',
								name: 'firstName',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Last Name',
								name: 'lastName',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Company',
								name: 'company',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Country',
								name: 'country',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Address Line 1',
								name: 'address1',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Address Line 2',
								name: 'address2',
								type: 'string',
								default: '',
							},
							{
								displayName: 'City',
								name: 'city',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Province',
								name: 'province',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Zip Code',
								name: 'zip',
								type: 'string',
								default: '',
							},
							{
								displayName: 'Phone',
								name: 'phone',
								type: 'string',
								default: '',
							},
						],
					},
				],
			},
			{
				displayName: 'Source Name',
				name: 'sourceName',
				type: 'string',
				default: '',
				description: 'Where the order originated. Can be set only during order creation, and is not writeable afterwards',
			},
			{
				displayName: 'Tags',
				name: 'tags',
				type: 'string',
				default: '',
				description: 'Tags attached to the order, formatted as a string of comma-separated values.',
			},
		],
	},
] as INodeProperties[];


alwaysOutputData?: boolean;
continueOnFail?: boolean;
parameters: INodeParameters;
credentials?: INodeCredentials;
webhookPath?: string;
}

keep keep keep keep replace keep keep keep


#!/bin/bash
set -e;

if [ -n "${POSTGRES_NON_ROOT_USER:-}" ] && [ -n "${POSTGRES_NON_ROOT_PASSWORD:-}" ]; then
	psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
		CREATE USER ${POSTGRES_NON_ROOT_USER} WITH PASSWORD '${POSTGRES_NON_ROOT_PASSWORD}';
		GRANT ALL PRIVILEGES ON DATABASE ${POSTGRES_DB} TO ${POSTGRES_NON_ROOT_USER};
	EOSQL
else
	echo "SETUP INFO: No Environment variables given!"
fi


import {
	IDataObject,
	INodeExecutionData,
	ICredentialDataDecryptedObject
} from 'n8n-workflow';
import {
	IMongoCredentialsType,
	IMongoParametricCredentials,
	IMongoCredentials
} from './mongo.node.types';

/**
 * Standard way of building the MongoDB connection string, unless overridden with a provided string
 *
 * @param {ICredentialDataDecryptedObject} credentials MongoDB credentials to use, unless conn string is overridden
 */
function buildParameterizedConnString(
	credentials: IMongoParametricCredentials
): string {
	if (credentials.port) {
		return `mongodb://${credentials.user}:${credentials.password}@${credentials.host}:${credentials.port}`;
	} else {
		return `mongodb+srv://${credentials.user}:${credentials.password}@${credentials.host}`;
	}
}

/**
 * Build mongoDb connection string and resolve database name.
 * If a connection string override value is provided, that will be used in place of individual args
 *
 * @param {ICredentialDataDecryptedObject} credentials raw/input MongoDB credentials to use
 */
function buildMongoConnectionParams(
	credentials: IMongoCredentialsType
): IMongoCredentials {
	const sanitizedDbName =
		credentials.database && credentials.database.trim().length > 0
			? credentials.database.trim()
			: '';
	if (credentials.configurationType === 'connectionString') {
		if (
			credentials.connectionString &&
			credentials.connectionString.trim().length > 0
		) {
			return {
				connectionString: credentials.connectionString.trim(),
				database: sanitizedDbName
			};
		} else {
			throw new Error(
				'Cannot override credentials: valid MongoDB connection string not provided '
			);
		}
	} else {
		return {
			connectionString: buildParameterizedConnString(credentials),
			database: sanitizedDbName
		};
	}
}

/**
 * Verify credentials. If ok, build mongoDb connection string and resolve database name.
 *
 * @param {ICredentialDataDecryptedObject} credentials raw/input MongoDB credentials to use
 */
export function validateAndResolveMongoCredentials(
	credentials?: ICredentialDataDecryptedObject
): IMongoCredentials {
	if (credentials === undefined) {
		throw new Error('No credentials got returned!');
	} else {
		return buildMongoConnectionParams(
			credentials as unknown as IMongoCredentialsType,
		);
	}
}

/**
 * Returns of copy of the items which only contains the json data and
 * of that only the define properties
 *
 * @param {INodeExecutionData[]} items The items to copy
 * @param {string[]} properties The properties it should include
 * @returns
 */
export function getItemCopy(
	items: INodeExecutionData[],
	properties: string[]
): IDataObject[] {
	// Prepare the data to insert and copy it to be returned
	let newItem: IDataObject;
	return items.map(item => {
		newItem = {};
		for (const property of properties) {
			if (item.json[property] === undefined) {
				newItem[property] = null;
			} else {
				newItem[property] = JSON.parse(JSON.stringify(item.json[property]));
			}
		}
		return newItem;
	});
}


import {
	IHookFunctions,
	IWebhookFunctions,
} from 'n8n-core';

import {
	IDataObject,
	ILoadOptionsFunctions,
	INodeType,
	INodeTypeDescription,
	INodePropertyOptions,
	IWebhookResponseData,
} from 'n8n-workflow';

import {
	bitbucketApiRequest,
	bitbucketApiRequestAllItems,
} from './GenericFunctions';

export class BitbucketTrigger implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'Bitbucket Trigger',
		name: 'bitbucketTrigger',
		icon: 'file:bitbucket.png',
		group: ['trigger'],
		version: 1,
		description: 'Handle Bitbucket events via webhooks',
		defaults: {
			name: 'Bitbucket Trigger',
			color: '#0052cc',
		},
		inputs: [],
		outputs: ['main'],
		credentials: [
			{
				name: 'bitbucketApi',
				required: true,
			}
		],
		webhooks: [
			{
				name: 'default',
				httpMethod: 'POST',
				responseMode: 'onReceived',
				path: 'webhook',
			},
		],
		properties: [
			{
				displayName: 'Resource',
				name: 'resource',
				type: 'options',
				required: true,
				options: [
					{
						name: 'User',
						value: 'user',
					},
					{
						name: 'Team',
						value: 'team',
					},
					{
						name: 'Repository',
						value: 'repository',
					},
				],
				default: 'user',
				description: 'The resource to operate on.',
			},
			{
				displayName: 'Events',
				name: 'events',
				type: 'multiOptions',
				displayOptions: {
					show: {
						resource: [
							'user'
						]
					}
				},
				typeOptions: {
					loadOptionsMethod: 'getUsersEvents',
				},
				options: [],
				required: true,
				default: [],
				description: 'The events to listen to.',
			},
			{
				displayName: 'Team',
				name: 'team',
				type: 'options',
				displayOptions: {
					show: {
						resource: [
							'team'
						]
					}
				},
				typeOptions: {
					loadOptionsMethod: 'getTeams',
				},
				required: true,
				default: '',
				description: 'The team of which to listen to the events.',
			},
			{
				displayName: 'Events',
				name: 'events',
				type: 'multiOptions',
				displayOptions: {
					show: {
						resource: [
							'team'
						]
					}
				},
				typeOptions: {
					loadOptionsMethod: 'getTeamEvents',
				},
				options: [],
				required: true,
				default: [],
				description: 'The events to listen to.',
			},
			{
				displayName: 'Repository',
				name: 'repository',
				type: 'options',
				displayOptions: {
					show: {
						resource: [
							'repository'
						]
					}
				},
				typeOptions: {
					loadOptionsMethod: 'getRepositories',
				},
				required: true,
				default: '',
				description: 'The repository of which to listen to the events.',
			},
			{
				displayName: 'Events',
				name: 'events',
				type: 'multiOptions',
				displayOptions: {
					show: {
						resource: [
							'repository'
						]
					}
				},
				typeOptions: {
					loadOptionsMethod: 'getRepositoriesEvents',
				},
				options: [],
				required: true,
				default: [],
				description: 'The events to listen to.',
			},
		],

	};

	methods = {
		loadOptions: {
			// Get all the events to display them to user so that he can
			// select them easily
			async getUsersEvents(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
				const returnData: INodePropertyOptions[] = [];
				const events = await bitbucketApiRequestAllItems.call(this, 'values', 'GET', '/hook_events/user');
				for (const event of events) {
					const eventName = event.event;
					const eventId = event.event;
					const eventDescription = event.description;
					returnData.push({
						name: eventName,
						value: eventId,
						description: eventDescription,
					});
				}
				return returnData;
			},
			// Get all the events to display them to user so that he can
			// select them easily
			async getTeamEvents(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
				const returnData: INodePropertyOptions[] = [];
				const events = await bitbucketApiRequestAllItems.call(this, 'values', 'GET', '/hook_events/team');
				for (const event of events) {
					const eventName = event.event;
					const eventId = event.event;
					const eventDescription = event.description;
					returnData.push({
						name: eventName,
						value: eventId,
						description: eventDescription,
					});
				}
				return returnData;
			},
			// Get all the events to display them to user so that he can
			// select them easily
			async getRepositoriesEvents(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
				const returnData: INodePropertyOptions[] = [];
				const events = await bitbucketApiRequestAllItems.call(this, 'values', 'GET', '/hook_events/repository');
				for (const event of events) {
					const eventName = event.event;
					const eventId = event.event;
					const eventDescription = event.description;
					returnData.push({
						name: eventName,
						value: eventId,
						description: eventDescription,
					});
				}
				return returnData;
			},
			// Get all the repositories to display them to user so that he can
			// select them easily
			async getRepositories(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
				const credentials = this.getCredentials('bitbucketApi');
				const returnData: INodePropertyOptions[] = [];
				const repositories = await bitbucketApiRequestAllItems.call(this, 'values', 'GET', `/repositories/${credentials!.username}`);
				for (const repository of repositories) {
					const repositoryName = repository.slug;
					const repositoryId = repository.slug;
					const repositoryDescription = repository.description;
					returnData.push({
						name: repositoryName,
						value: repositoryId,
						description: repositoryDescription,
					});
				}
				return returnData;
			},
			// Get all the teams to display them to user so that he can
			// select them easily
			async getTeams(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
				const returnData: INodePropertyOptions[] = [];
				const qs: IDataObject = {
					role: 'member',
				};
				const teams = await bitbucketApiRequestAllItems.call(this, 'values', 'GET', '/teams', {}, qs);
				for (const team of teams) {
					const teamName = team.display_name;
					const teamId = team.username;
					returnData.push({
						name: teamName,
						value: teamId,
					});
				}
				return returnData;
			},
		},
	};
	// @ts-ignore
	webhookMethods = {
		default: {
			async checkExists(this: IHookFunctions): Promise<boolean> {
				let endpoint = '';
				const credentials = this.getCredentials('bitbucketApi');
				const resource = this.getNodeParameter('resource', 0) as string;
				const webhookData = this.getWorkflowStaticData('node');
				if (webhookData.webhookId === undefined) {
					return false;
				}
				if (resource === 'user') {
					endpoint = `/users/${credentials!.username}/hooks/${webhookData.webhookId}`;
				}
				if (resource === 'team') {
					const team = this.getNodeParameter('team', 0) as string;
					endpoint = `/teams/${team}/hooks/${webhookData.webhookId}`;
				}
				if (resource === 'repository') {
					const repository = this.getNodeParameter('repository', 0) as string;
					endpoint = `/repositories/${credentials!.username}/${repository}/hooks/${webhookData.webhookId}`;
				}
				try {
					await bitbucketApiRequest.call(this, 'GET', endpoint);
				} catch (e) {
					return false;
				}
				return true;
			},
			async create(this: IHookFunctions): Promise<boolean> {
				let responseData;
				let endpoint = '';
				const webhookUrl = this.getNodeWebhookUrl('default');
				const webhookData = this.getWorkflowStaticData('node');
				const events = this.getNodeParameter('events') as string[];
				const resource = this.getNodeParameter('resource', 0) as string;
				const credentials = this.getCredentials('bitbucketApi');

				if (resource === 'user') {
					endpoint = `/users/${credentials!.username}/hooks`;
				}
				if (resource === 'team') {
					const team = this.getNodeParameter('team', 0) as string;
					endpoint = `/teams/${team}/hooks`;
				}
				if (resource === 'repository') {
					const repository = this.getNodeParameter('repository', 0) as string;
					endpoint = `/repositories/${credentials!.username}/${repository}/hooks`;
				}
				const body: IDataObject = {
					description: 'n8n webhook',
					url: webhookUrl,
					active: true,
					events,
				};
				responseData = await bitbucketApiRequest.call(this, 'POST', endpoint, body);
				webhookData.webhookId = responseData.uuid.replace('{', '').replace('}', '');
				return true;
			},
			async delete(this: IHookFunctions): Promise<boolean> {
				let endpoint = '';
				const webhookData = this.getWorkflowStaticData('node');
				const credentials = this.getCredentials('bitbucketApi');
				const resource = this.getNodeParameter('resource', 0) as string;
				if (resource === 'user') {
					endpoint = `/users/${credentials!.username}/hooks/${webhookData.webhookId}`;
				}
				if (resource === 'team') {
					const team = this.getNodeParameter('team', 0) as string;
					endpoint = `/teams/${team}/hooks/${webhookData.webhookId}`;
				}
				if (resource === 'repository') {
					const repository = this.getNodeParameter('repository', 0) as string;
					endpoint = `/repositories/${credentials!.username}/${repository}/hooks/${webhookData.webhookId}`;
				}
				try {
					await bitbucketApiRequest.call(this, 'DELETE', endpoint);
				} catch(error) {
					return false;
				}
				delete webhookData.webhookId;
				return true;
			},
		},
	};

	async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
		const req = this.getRequestObject();
		const headerData = this.getHeaderData() as IDataObject;
		const webhookData = this.getWorkflowStaticData('node');
		if (headerData['x-hook-uuid'] !== webhookData.webhookId) {
			return {};
		}
		return {
			workflowData: [
				this.helpers.returnJsonArray(req.body)
			],
		};
	}
}


keep keep keep keep keep replace keep keep keep keep keep


import {
	IExecuteFunctions,
} from 'n8n-core';
import {
	IDataObject,
	INodeExecutionData,
	INodeType,
	INodeTypeDescription,
} from 'n8n-workflow';
import {
	clearbitApiRequest,
} from './GenericFunctions';
import {
	companyFields,
	companyOperations,
} from './CompanyDescription';
import {
	personOperations,
	personFields,
} from './PersonDescription';

export class Clearbit implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'Clearbit',
		name: 'clearbit',
		icon: 'file:clearbit.png',
		group: ['output'],
		version: 1,
		subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}',
		description: 'Consume Clearbit API',
		defaults: {
			name: 'Clearbit',
			color: '#219ef9',
		},
		inputs: ['main'],
		outputs: ['main'],
		credentials: [
			{
				name: 'clearbitApi',
				required: true,
			}
		],
		properties: [
			{
				displayName: 'Resource',
				name: 'resource',
				type: 'options',
				options: [
					{
						name: 'Company',
						value: 'company',
						description: 'The Company API allows you to look up a company by their domain',
					},
					{
						name: 'Person',
						value: 'person',
						description: `The Person API lets you retrieve social information associated with an email address,<br/>
						such as a person’s name, location and Twitter handle.`
					},
				],
				default: 'company',
				description: 'Resource to consume.',
			},
			...companyOperations,
			...companyFields,
			...personOperations,
			...personFields,
		],
	};

	async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
		const items = this.getInputData();
		const returnData: IDataObject[] = [];
		const length = items.length as unknown as number;
		const qs: IDataObject = {};
		let responseData;
		const resource = this.getNodeParameter('resource', 0) as string;
		const operation = this.getNodeParameter('operation', 0) as string;
		for (let i = 0; i < length; i++) {
			if (resource === 'person') {
				if (operation === 'enrich') {
					const email = this.getNodeParameter('email', i) as string;
					const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
					qs.email = email;
					if (additionalFields.givenName) {
						qs.given_name = additionalFields.givenName as string;
					}
					if (additionalFields.familyName) {
						qs.family_name = additionalFields.familyName as string;
					}
					if (additionalFields.ipAddress) {
						qs.ip_address = additionalFields.ipAddress as string;
					}
					if (additionalFields.location) {
						qs.location = additionalFields.location as string;
					}
					if (additionalFields.company) {
						qs.company = additionalFields.company as string;
					}
					if (additionalFields.companyDomain) {
						qs.company_domain = additionalFields.companyDomain as string;
					}
					if (additionalFields.linkedIn) {
						qs.linkedin = additionalFields.linkedIn as string;
					}
					if (additionalFields.twitter) {
						qs.twitter = additionalFields.twitter as string;
					}
					if (additionalFields.facebook) {
						qs.facebook = additionalFields.facebook as string;
					}
					responseData = await clearbitApiRequest.call(this, 'GET', resource, '/v2/people/find', {}, qs);
				}
			}
			if (resource === 'company') {
				if (operation === 'enrich') {
					const domain = this.getNodeParameter('domain', i) as string;
					const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
					qs.domain = domain;
					if (additionalFields.companyName) {
						qs.company_name = additionalFields.companyName as string;
					}
					if (additionalFields.linkedin) {
						qs.linkedin = additionalFields.linkedin as string;
					}
					if (additionalFields.twitter) {
						qs.twitter = additionalFields.twitter as string;
					}
					if (additionalFields.facebook) {
						qs.facebook = additionalFields.facebook as string;
					}
					responseData = await clearbitApiRequest.call(this, 'GET', resource, '/v2/companies/find', {}, qs);
				}
				if (operation === 'autocomplete') {
					const name = this.getNodeParameter('name', i) as string;
					qs.query = name;
					responseData = await clearbitApiRequest.call(this, 'GET', 'autocomplete', '/v1/companies/suggest', {}, qs);
				}
			}
			if (Array.isArray(responseData)) {
				returnData.push.apply(returnData, responseData as IDataObject[]);
			} else {
				returnData.push(responseData as IDataObject);
			}
		}
		return [this.helpers.returnJsonArray(returnData)];
	}
}


import {
	IExecuteFunctions,
} from 'n8n-core';
import {
	INodeTypeDescription,
	INodeType,
	INodeExecutionData,
	IDataObject,
} from 'n8n-workflow';
import {
	driftApiRequest,
} from './GenericFunctions';
import {
	contactFields,
	contactOperations,
} from './ContactDescription';
import {
	IContact,
} from './ContactInterface';

export class Drift implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'Drift',
		name: 'drift',
		icon: 'file:drift.png',
		group: ['output'],
		version: 1,
		subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
		description: 'Consume Drift API',
		defaults: {
			name: 'Drift ',
			color: '#404040',
		},
		inputs: ['main'],
		outputs: ['main'],
		credentials: [
			{
				name: 'driftApi',
				required: true,
			},
		],
		properties: [
			{
				displayName: 'Resource',
				name: 'resource',
				type: 'options',
				options: [
					{
						name: 'Contact',
						value: 'contact',
					},
				],
				default: 'contact',
				description: 'Resource to consume.',
			},
			...contactOperations,
			...contactFields,
		],
	};

	async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
		const items = this.getInputData();
		const returnData: IDataObject[] = [];
		const length = items.length as unknown as number;
		let responseData;
		const qs: IDataObject = {};
		const resource = this.getNodeParameter('resource', 0) as string;
		const operation = this.getNodeParameter('operation', 0) as string;
		for (let i = 0; i < length; i++) {
			if (resource === 'contact') {
				//https://devdocs.drift.com/docs/creating-a-contact
				if (operation === 'create') {
					const email = this.getNodeParameter('email', i) as string;
					const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
					const body: IContact = {
						email,
					};
					if (additionalFields.name) {
						body.name = additionalFields.name as string;
					}
					if (additionalFields.phone) {
						body.phone = additionalFields.phone as string;
					}
					responseData = await driftApiRequest.call(this, 'POST', '/contacts', { attributes: body });
					responseData = responseData.data;
				}
				//https://devdocs.drift.com/docs/updating-a-contact
				if (operation === 'update') {
					const contactId = this.getNodeParameter('contactId', i) as string;
					const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
					const body: IContact = {};
					if (updateFields.name) {
						body.name = updateFields.name as string;
					}
					if (updateFields.phone) {
						body.phone = updateFields.phone as string;
					}
					if (updateFields.email) {
						body.email = updateFields.email as string;
					}
					responseData = await driftApiRequest.call(this, 'PATCH', `/contacts/${contactId}`, { attributes: body });
					responseData = responseData.data;
				}
				//https://devdocs.drift.com/docs/retrieving-contact
				if (operation === 'get') {
					const contactId = this.getNodeParameter('contactId', i) as string;
					responseData = await driftApiRequest.call(this, 'GET', `/contacts/${contactId}`);
					responseData = responseData.data;
				}
				//https://devdocs.drift.com/docs/listing-custom-attributes
				if (operation === 'getCustomAttributes') {
					responseData = await driftApiRequest.call(this, 'GET', '/contacts/attributes');
					responseData = responseData.data.properties;
				}
				//https://devdocs.drift.com/docs/removing-a-contact
				if (operation === 'delete') {
					const contactId = this.getNodeParameter('contactId', i) as string;
					responseData = await driftApiRequest.call(this, 'DELETE', `/contacts/${contactId}`);
					responseData = { success: true };
				}
			}
			if (Array.isArray(responseData)) {
				returnData.push.apply(returnData, responseData as IDataObject[]);
			} else {
				returnData.push(responseData as IDataObject);
			}
		}
		return [this.helpers.returnJsonArray(returnData)];
	}
}


import { IDataObject } from "n8n-workflow";

export interface IUserCompany {
	company_id?: string;
}

export interface IAvatar {
	type?: string;
	image_url?: string;
}

export interface IUser {
	user_id?: string;
	id?: string;
	email?: string;
	phone?: string;
	name?: string;
	custom_attributes?: IDataObject;
	companies?: IUserCompany[];
	last_request_at?: number;
	signed_up_at?: string;
	unsubscribed_from_emails?: boolean;
	update_last_request_at?: boolean;
	last_seen_user_agent?: boolean;
	session_count?: number;
	avatar?: IAvatar;
	utm_source?: string;
	utm_medium?: string;
	utm_campaign?: string;
	utm_term?: string;
	utm_content?: string;
}


last 1 chrome version
last 1 firefox version
last 1 safari version


import * as React from 'react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import cn from 'classnames'
export function SidebarNavLink({
  route: { href, pathname, title, selected },
  onClick,
}) {
  const router = useRouter()
  const onlyHashChange = pathname === router.pathname

  return (

      {
        // NOTE: use just anchor element for triggering `hashchange` event
        onlyHashChange ? (

            {title}

        ) : (
          <Link href={href} as={pathname}>
            {title}
          </Link>
        )
      }

  )
}
SidebarNavLink.displayName = 'SidebarNavLink'


import fetch from 'isomorphic-unfetch'

export default async function (...args) {
  const res = await fetch(...args)
  return await res.json()
}


"build": "rescripts build",
"test": "rescripts test",
"eject": "rescripts eject"
},
"dependencies": {
"axios": "0.19.2",
"graphql": "^15.3.0",
"graphql-request": "^3.1.0",
"react": "^16.8.6",

keep keep keep keep keep replace keep keep keep


import React from 'react'
import { render } from '@testing-library/react'

import { QueryClient, QueryClientProvider, QueryCache, useQuery } from '../..'
import { dehydrate, useHydrate, Hydrate } from '../'
import { sleep } from '../../react/tests/utils'

describe('React hydration', () => {
  const fetchData: (value: string) => Promise<string> = value =>
    new Promise(res => setTimeout(() => res(value), 10))
  const dataQuery: (key: string) => Promise<string> = key => fetchData(key)
  let stringifiedState: string

  beforeAll(async () => {
    const queryCache = new QueryCache()
    const queryClient = new QueryClient({ queryCache })
    await queryClient.prefetchQuery('string', () => dataQuery('string'))
    const dehydrated = dehydrate(queryClient)
    stringifiedState = JSON.stringify(dehydrated)
    queryClient.clear()
  })

  describe('useHydrate', () => {
    test('should hydrate queries to the cache on context', async () => {
      const dehydratedState = JSON.parse(stringifiedState)
      const queryCache = new QueryCache()
      const queryClient = new QueryClient({ queryCache })

      function Page() {
        useHydrate(dehydratedState)
        const { data } = useQuery('string', () => dataQuery('string'))
        return (

{data}

        )
      }

      const rendered = render(
        <QueryClientProvider client={queryClient}>
          <Page />
        </QueryClientProvider>
      )

      await sleep(10)
      rendered.getByText('string')
      queryClient.clear()
    })
  })

  describe('ReactQueryCacheProvider with hydration support', () => {
    test('should hydrate new queries if queries change', async () => {
      const dehydratedState = JSON.parse(stringifiedState)
      const queryCache = new QueryCache()
      const queryClient = new QueryClient({ queryCache })

      function Page({ queryKey }: { queryKey: string }) {
        const { data } = useQuery(queryKey, () => dataQuery(queryKey))
        return (

{data}

        )
      }

      const rendered = render(
        <QueryClientProvider client={queryClient}>
          <Hydrate state={dehydratedState}>
            <Page queryKey={'string'} />
          </Hydrate>
        </QueryClientProvider>
      )

      await sleep(10)
      rendered.getByText('string')

      const intermediateCache = new QueryCache()
      const intermediateClient = new QueryClient({
        queryCache: intermediateCache,
      })
      await intermediateClient.prefetchQuery('string', () =>
        dataQuery('should change')
      )
      await intermediateClient.prefetchQuery('added string', () =>
        dataQuery('added string')
      )
      const dehydrated = dehydrate(intermediateClient)
      intermediateClient.clear()

      rendered.rerender(
        <QueryClientProvider client={queryClient}>
          <Hydrate state={dehydrated}>
            <Page queryKey={'string'} />
            <Page queryKey={'added string'} />
          </Hydrate>
        </QueryClientProvider>
      )

      // Existing query data should be overwritten if older,
      // so this should have changed
      await sleep(10)
      rendered.getByText('should change')
      // New query data should be available immediately
      rendered.getByText('added string')

      queryClient.clear()
    })

    test('should hydrate queries to new cache if cache changes', async () => {
      const dehydratedState = JSON.parse(stringifiedState)
      const queryCache = new QueryCache()
      const queryClient = new QueryClient({ queryCache })

      function Page() {
        const { data } = useQuery('string', () => dataQuery('string'))
        return (

{data}

        )
      }

      const rendered = render(
        <QueryClientProvider client={queryClient}>
          <Hydrate state={dehydratedState}>
            <Page />
          </Hydrate>
        </QueryClientProvider>
      )

      await sleep(10)
      rendered.getByText('string')

      const newClientQueryCache = new QueryCache()
      const newClientQueryClient = new QueryClient({
        queryCache: newClientQueryCache,
      })

      rendered.rerender(
        <QueryClientProvider client={newClientQueryClient}>
          <Hydrate state={dehydratedState}>
            <Page />
          </Hydrate>
        </QueryClientProvider>
      )

      await sleep(10)
      rendered.getByText('string')

      queryClient.clear()
      newClientQueryClient.clear()
    })
  })
})


import React from "react";

import Spinner from "./Spinner";

export default function Button({ children, timeoutMs = 3000, onClick }) {
  const [startTransition, isPending] = React.useTransition({
    timeoutMs: timeoutMs
  });

  const handleClick = e => {
    startTransition(() => {
      onClick(e);
    });
  };

  return (
    <>
      <button onClick={handleClick} disabled={isPending}>
        {children} {isPending ? <Spinner /> : null}
      </button>
    </>
  );
}


---
id: caching
title: Caching Examples
---

> Please thoroughly read the [Important Defaults](./important-defaults) before reading this guide

## Basic Example

This caching example illustrates the story and lifecycle of:

- Query Instances with and without cache data
- Background Refetching
- Inactive Queries
- Garbage Collection

Let's assume we are using the default `cacheTime` of **5 minutes** and the default `staleTime` of `0`.

- A new instance of `useQuery('todos', fetchTodos)` mounts.
  - Since no other queries have been made with this query + variable combination, this query will show a hard loading state and make a network request to fetch the data.
  - It will then cache the data using `'todos'` and `fetchTodos` as the unique identifiers for that cache.
  - The hook will mark itself as stale after the configured `staleTime` (defaults to `0`, or immediately).
- A second instance of `useQuery('todos', fetchTodos)` mounts elsewhere.
  - Because this exact data exist in the cache from the first instance of this query, that data is immediately returned from the cache.
- A background refetch is triggered for both queries (but only one request), since a new instance appeared on screen.
  - Both instances are updated with the new data if the fetch is successful
- Both instances of the `useQuery('todos', fetchTodos)` query are unmounted and no longer in use.
  - Since there are no more active instances to this query, a cache timeout is set using `cacheTime` to delete and garbage collect the query (defaults to **5 minutes**).
- Before the cache timeout has completed another instance of `useQuery('todos', fetchTodos)` mounts. The query immediately returns the available cached value while the `fetchTodos` function is being run in the background to populate the query with a fresh value.
- The final instance of `useQuery('todos', fetchTodos)` unmounts.
- No more instances of `useQuery('todos', fetchTodos)` appear within **5 minutes**.
  - This query and its data are deleted and garbage collected.


.next
.now
.env
.env.*
node_modules
*.log
.DS_Store
.vercel


const path = require("path");
const resolveFrom = require("resolve-from");

const fixLinkedDependencies = (config) => {
  config.resolve = {
    ...config.resolve,
    alias: {
      ...config.resolve.alias,
      react$: resolveFrom(path.resolve("node_modules"), "react"),
      "react-dom$": resolveFrom(path.resolve("node_modules"), "react-dom"),
    },
  };
  return config;
};

const includeSrcDirectory = (config) => {
  config.resolve = {
    ...config.resolve,
    modules: [path.resolve("src"), ...config.resolve.modules],
  };
  return config;
};

const allowOutsideSrc = (config) => {
  config.resolve.plugins = config.resolve.plugins.filter(
    (p) => p.constructor.name !== "ModuleScopePlugin"
  );
  return config;
};

module.exports = [
  ["use-babel-config", ".babelrc"],
  ["use-eslint-config", ".eslintrc"],
  fixLinkedDependencies,
  allowOutsideSrc,
  // includeSrcDirectory,
];


"build": "rescripts build",
"test": "rescripts test",
"eject": "rescripts eject"
},
"dependencies": {
"axios": "0.19.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-query": "^3.5.0",
"react-scripts": "3.0.1",

keep keep keep keep keep replace keep keep keep keep

new

>0.2%
not dead
not ie <= 11
not op_mini all


import { useState } from 'react'
import cn from 'classnames'
import { Search } from './Search'
export const Sidebar = ({ active, children, fixed }) => {
  const [searching, setSearching] = useState(false)
  return (

        <Search />

{children}

  )
}