import moment from 'moment';

Meteor.methods({
	updateMessage(message) {

		check(message, Match.ObjectIncluding({_id:String}));

		if (!Meteor.userId()) {
			throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'updateMessage' });
		}

		const originalMessage = RocketChat.models.Messages.findOneById(message._id);

		if (!originalMessage || !originalMessage._id) {
			return;
		}

		const hasPermission = RocketChat.authz.hasPermission(Meteor.userId(), 'edit-message', message.rid);
		const editAllowed = RocketChat.settings.get('Message_AllowEditing');
		const editOwn = originalMessage.u && originalMessage.u._id === Meteor.userId();

		if (!hasPermission && (!editAllowed || !editOwn)) {
			throw new Meteor.Error('error-action-not-allowed', 'Message editing not allowed', { method: 'updateMessage', action: 'Message_editing' });
		}

		const blockEditInMinutes = RocketChat.settings.get('Message_AllowEditing_BlockEditInMinutes');
		if (Match.test(blockEditInMinutes, Number) && blockEditInMinutes !== 0) {
			let currentTsDiff;
			let msgTs;

			if (Match.test(originalMessage.ts, Number)) {
				msgTs = moment(originalMessage.ts);
			}
			if (msgTs) {
				currentTsDiff = moment().diff(msgTs, 'minutes');
			}
			if (currentTsDiff > blockEditInMinutes) {
				throw new Meteor.Error('error-message-editing-blocked', 'Message editing is blocked', { method: 'updateMessage' });
			}
		}

		// It is possible to have an empty array as the attachments property, so ensure both things exist
		if (originalMessage.attachments && originalMessage.attachments.length > 0 && originalMessage.attachments[0].description !== undefined) {
			message.attachments = originalMessage.attachments;
			message.attachments[0].description = message.msg;
			message.msg = originalMessage.msg;
		}

		message.u = originalMessage.u;

		return RocketChat.updateMessage(message, Meteor.user());
	}
});


RocketChat.Notifications.onRoom(openedRooms[typeName].rid, 'deleteMessage', onDeleteMessageStream); // eslint-disable-line no-use-before-define

Meteor.defer(() => {
record.ready = true;
Dep.changed();
});

RocketChat.callbacks.add('afterSaveMessage', function(message, room) {
	// skips this callback if the message was edited
	if (!message || message.editedAt) {
		return message;
	}

	// check if room is yet awaiting for response
	if (!(typeof room.t !== 'undefined' && room.t === 'l' && room.waitingResponse)) {
		return message;
	}

	// if the message has a token, it was sent by the visitor, so ignore it
	if (message.token) {
		return message;
	}

	Meteor.defer(() => {
		const now = new Date();
		RocketChat.models.Rooms.setResponseByRoomId(room._id, {
			user: {
				_id: message.u._id,
				username: message.u.username
			},
			responseDate: now,
			responseTime: (now.getTime() - room.ts) / 1000
		});
	});

	return message;
}, RocketChat.callbacks.priority.LOW, 'markRoomResponded');


if (!Meteor.userId() && RocketChat.settings.get('Accounts_AllowAnonymousRead') === false) {

throw new Meteor.Error('error-invalid-user', 'Invalid user', {

keep keep keep keep add keep keep keep keep

import _ from 'underscore';

const RoomManager = new function() {
	const openedRooms = {};
	const msgStream = new Meteor.Streamer('room-messages');
	const onlineUsers = new ReactiveVar({});
	const Dep = new Tracker.Dependency();
	const Cls = class {
		static initClass() {
			/* globals CachedChatRoom CachedChatSubscription */
			this.prototype.openedRooms = openedRooms;
			this.prototype.onlineUsers = onlineUsers;
			this.prototype.computation = Tracker.autorun(() => {
				Object.keys(openedRooms).forEach(typeName => {
					const record = openedRooms[typeName];
					if (record.active !== true || record.ready === true) { return; }
					const ready = CachedChatRoom.ready.get() && RocketChat.mainReady.get();
					if (ready !== true) { return; }
					const user = Meteor.user();

					const type = typeName.substr(0, 1);
					const name = typeName.substr(1);

					const room = Tracker.nonreactive(() => {
						return RocketChat.roomTypes.findRoom(type, name, user);
					});

					if (room == null) {
						record.ready = true;
					} else {
						openedRooms[typeName].rid = room._id;

						RoomHistoryManager.getMoreIfIsEmpty(room._id);
						record.ready = RoomHistoryManager.isLoading(room._id) === false;
						Dep.changed();

						if (openedRooms[typeName].streamActive !== true) {
							openedRooms[typeName].streamActive = true;
							msgStream.on(openedRooms[typeName].rid, msg =>

								RocketChat.promises.run('onClientMessageReceived', msg).then(function(msg) {

									// Should not send message to room if room has not loaded all the current messages
									if (RoomHistoryManager.hasMoreNext(openedRooms[typeName].rid) === false) {

										// Do not load command messages into channel
										if (msg.t !== 'command') {
											const roles = [
												(msg.u && msg.u._id && UserRoles.findOne(msg.u._id, { fields: { roles: 1 }})) || {},
												(msg.u && msg.u._id && RoomRoles.findOne({rid: msg.rid, 'u._id': msg.u._id})) || {}
											].map(e => e.roles);
											msg.roles = _.union.apply(_.union, roles);
											ChatMessage.upsert({ _id: msg._id }, msg);
											msg.room = {
												type,
												name
											};
										}
										msg.name = room.name;
										Meteor.defer(() => RoomManager.updateMentionsMarksOfRoom(typeName));

										RocketChat.callbacks.run('streamMessage', msg);

										return window.fireGlobalEvent('new-message', msg);
									}
								})
							);

							RocketChat.Notifications.onRoom(openedRooms[typeName].rid, 'deleteMessage', onDeleteMessageStream); // eslint-disable-line no-use-before-define
						}
					}
					return Dep.changed();
				});
			});
		}

		getOpenedRoomByRid(rid) {
			return Object.keys(openedRooms).map(typeName => openedRooms[typeName]).find(openedRoom => openedRoom.rid === rid);
		}

		getDomOfRoom(typeName, rid) {
			const room = openedRooms[typeName];
			if ((room == null)) {
				return;
			}

			if ((room.dom == null) && (rid != null)) {
				room.dom = document.createElement('div');
				room.dom.classList.add('room-container');
				const contentAsFunc = content => () => content;

				room.template = Blaze._TemplateWith({ _id: rid }, contentAsFunc(Template.room));
				Blaze.render(room.template, room.dom); //, nextNode, parentView
			}

			return room.dom;
		}

		close(typeName) {
			if (openedRooms[typeName]) {
				if (openedRooms[typeName].rid != null) {
					msgStream.removeAllListeners(openedRooms[typeName].rid);
					RocketChat.Notifications.unRoom(openedRooms[typeName].rid, 'deleteMessage', onDeleteMessageStream); // eslint-disable-line no-use-before-define
				}

				openedRooms[typeName].ready = false;
				openedRooms[typeName].active = false;
				if (openedRooms[typeName].template != null) {
					Blaze.remove(openedRooms[typeName].template);
				}
				delete openedRooms[typeName].dom;
				delete openedRooms[typeName].template;

				const { rid } = openedRooms[typeName];
				delete openedRooms[typeName];

				if (rid != null) {
					return RoomHistoryManager.clear(rid);
				}
			}
		}

		closeOlderRooms() {
			const maxRoomsOpen = 10;
			if (Object.keys(openedRooms).length <= maxRoomsOpen) {
				return;
			}

			const roomsToClose = _.sortBy(_.values(openedRooms), 'lastSeen').reverse().slice(maxRoomsOpen);
			return Array.from(roomsToClose).map((roomToClose) =>
				this.close(roomToClose.typeName));
		}

		closeAllRooms() {
			Object.keys(openedRooms).forEach(key => {
				const openedRoom = openedRooms[key];
				this.close(openedRoom.typeName);
			});
		}

		open(typeName) {
			if ((openedRooms[typeName] == null)) {
				openedRooms[typeName] = {
					typeName,
					active: false,
					ready: false,
					unreadSince: new ReactiveVar(undefined)
				};
			}

			openedRooms[typeName].lastSeen = new Date;

			if (openedRooms[typeName].ready) {
				this.closeOlderRooms();
			}

			if (CachedChatSubscription.ready.get() === true) {

				if (openedRooms[typeName].active !== true) {
					openedRooms[typeName].active = true;
					if (this.computation) {
						this.computation.invalidate();
					}
				}
			}

			return {
				ready() {
					Dep.depend();
					return openedRooms[typeName].ready;
				}
			};
		}

		existsDomOfRoom(typeName) {
			const room = openedRooms[typeName];
			return ((room != null ? room.dom : undefined) != null);
		}

		updateUserStatus(user, status, utcOffset) {
			const onlineUsersValue = onlineUsers.curValue;

			if (status === 'offline') {
				delete onlineUsersValue[user.username];
			} else {
				onlineUsersValue[user.username] = {
					_id: user._id,
					status,
					utcOffset
				};
			}

			return onlineUsers.set(onlineUsersValue);
		}

		updateMentionsMarksOfRoom(typeName) {
			const dom = this.getDomOfRoom(typeName);
			if ((dom == null)) {
				return;
			}

			const ticksBar = $(dom).find('.ticks-bar');
			$(dom).find('.ticks-bar > .tick').remove();

			const scrollTop = $(dom).find('.messages-box > .wrapper').scrollTop() - 50;
			const totalHeight = $(dom).find('.messages-box > .wrapper > ul').height() + 40;

			return $('.messages-box .mention-link-me').each(function(index, item) {
				const topOffset = $(item).offset().top + scrollTop;
				const percent = (100 / totalHeight) * topOffset;
				if ($(item).hasClass('mention-link-all')) {
					return ticksBar.append(`<div class="tick background-attention-color" style="top: ${ percent }%;"></div>`);
				} else {
					return ticksBar.append(`<div class="tick background-primary-action-color" style="top: ${ percent }%;"></div>`);
				}
			});
		}
	};
	Cls.initClass();
	return new Cls;
};

const loadMissedMessages = function(rid) {
	const lastMessage = ChatMessage.findOne({rid}, {sort: {ts: -1}, limit: 1});
	if (lastMessage == null) {
		return;
	}

	return Meteor.call('loadMissedMessages', rid, lastMessage.ts, (err, result) =>
		Array.from(result).map((item) =>
			RocketChat.promises.run('onClientMessageReceived', item).then(function(item) {
				/* globals UserRoles RoomRoles*/
				const roles = [
					(item.u && item.u._id && UserRoles.findOne(item.u._id)) || {},
					(item.u && item.u._id && RoomRoles.findOne({rid: item.rid, 'u._id': item.u._id})) || {}
				].map(({roles}) => roles);
				item.roles = _.union.apply(_, roles);
				return ChatMessage.upsert({_id: item._id}, item);
			}))
	);
};

let connectionWasOnline = true;
Tracker.autorun(function() {
	const { connected } = Meteor.connection.status();

	if (connected === true && connectionWasOnline === false && RoomManager.openedRooms != null) {
		Object.keys(RoomManager.openedRooms).forEach(key => {
			const value = RoomManager.openedRooms[key];
			if (value.rid != null) {
				loadMissedMessages(value.rid);
			}
		});
	}
	return connectionWasOnline = connected;
});

Meteor.startup(() => {

	// Reload rooms after login
	let currentUsername = undefined;
	Tracker.autorun(() => {
		const user = Meteor.user();
		if ((currentUsername === undefined) && ((user != null ? user.username : undefined) != null)) {
			currentUsername = user.username;
			RoomManager.closeAllRooms();
			const roomTypes = RocketChat.roomTypes.roomTypes;
			// Reload only if the current route is a channel route
			const roomType = Object.keys(roomTypes).find(key => roomTypes[key].route && roomTypes[key].route.name === FlowRouter.current().route.name);
			if (roomType) {
				FlowRouter.reload();
			}
		}
	});

	ChatMessage.find().observe({
		removed(record) {
			if (RoomManager.getOpenedRoomByRid(record.rid) != null) {
				const recordBefore = ChatMessage.findOne({ts: {$lt: record.ts}}, {sort: {ts: -1}});
				if (recordBefore != null) {
					ChatMessage.update({_id: recordBefore._id}, {$set: {tick: new Date}});
				}

				const recordAfter = ChatMessage.findOne({ts: {$gt: record.ts}}, {sort: {ts: 1}});
				if (recordAfter != null) {
					return ChatMessage.update({_id: recordAfter._id}, {$set: {tick: new Date}});
				}
			}
		}
	});
});

const onDeleteMessageStream = msg => ChatMessage.remove({_id: msg._id});

Tracker.autorun(function() {
	if (Meteor.userId()) {
		return RocketChat.Notifications.onUser('message', function(msg) {
			msg.u =
			{username: 'rocket.cat'};
			msg.private = true;

			return ChatMessage.upsert({ _id: msg._id }, msg);
		});
	}
});

export { RoomManager };
this.RoomManager = RoomManager;
RocketChat.callbacks.add('afterLogoutCleanUp', () => RoomManager.closeAllRooms(), RocketChat.callbacks.priority.MEDIUM, 'roommanager-after-logout-cleanup');


Meteor.startup(function() {
	Meteor.users.find({}, { fields: { name: 1, username: 1, pictures: 1, status: 1, emails: 1, phone: 1, services: 1, utcOffset: 1 } }).observe({
		added(user) {
			Session.set(`user_${ user.username }_status`, user.status);
			RoomManager.updateUserStatus(user, user.status, user.utcOffset);
		},
		changed(user) {
			Session.set(`user_${ user.username }_status`, user.status);
			RoomManager.updateUserStatus(user, user.status, user.utcOffset);
		},
		removed(user) {
			Session.set(`user_${ user.username }_status`, null);
			RoomManager.updateUserStatus(user, 'offline', null);
		}
	});
});


Package.describe({
	name: 'rocketchat:drupal',
	version: '0.0.1',
	summary: 'RocketChat settings for Drupal oAuth2'
});

Package.onUse(function(api) {
	api.versionsFrom('1.0');
	api.use('ecmascript');
	api.use('service-configuration');
	api.use('rocketchat:lib@0.0.1');
	api.use('rocketchat:custom-oauth');

	// api.use('templating', 'client');

	api.addFiles('common.js');
	api.addFiles('login-button.css', 'client');
	api.addFiles('startup.js', 'server');

	api.use('templating', 'client');
});


This directory and the files immediately inside it are automatically generated
when you change this package's NPM dependencies. Commit the files in this
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
so that others run the same versions of sub-dependencies.

You should NOT check in the node_modules directory that Meteor automatically
creates; if you are using git, the .gitignore file tells git to ignore it.


`DROP INDEX IF EXISTS "${schema}".IDX_${tablePrefixPure}c4d999a5e90784e8caccf5589d`,

import { MigrationInterface, QueryRunner } from 'typeorm';
import * as config from '../../../../config';

export class AddExecutionEntityIndexes1644422880309 implements MigrationInterface {
	name = 'AddExecutionEntityIndexes1644422880309';

	public async up(queryRunner: QueryRunner): Promise<void> {
		let tablePrefix = config.get('database.tablePrefix');
		const tablePrefixPure = tablePrefix;
		const schema = config.get('database.postgresdb.schema');

		if (schema) {
			tablePrefix = schema + '.' + tablePrefix;
		}

		await queryRunner.query(
			`DROP INDEX "${schema}".IDX_${tablePrefixPure}c4d999a5e90784e8caccf5589d`,
		);
		await queryRunner.query(
			`DROP INDEX "${schema}".IDX_${tablePrefixPure}ca4a71b47f28ac6ea88293a8e2`,
		);
		await queryRunner.query(
			`CREATE INDEX "IDX_${tablePrefixPure}33228da131bb1112247cf52a42" ON ${tablePrefix}execution_entity ("stoppedAt") `,
		);
		await queryRunner.query(
			`CREATE INDEX "IDX_${tablePrefixPure}58154df94c686818c99fb754ce" ON ${tablePrefix}execution_entity ("workflowId", "waitTill", "id") `,
		);
		await queryRunner.query(
			`CREATE INDEX "IDX_${tablePrefixPure}4f474ac92be81610439aaad61e" ON ${tablePrefix}execution_entity ("workflowId", "finished", "id") `,
		);
		await queryRunner.query(
			`CREATE INDEX "IDX_${tablePrefixPure}72ffaaab9f04c2c1f1ea86e662" ON ${tablePrefix}execution_entity ("finished", "id") `,
		);
		await queryRunner.query(
			`CREATE INDEX "IDX_${tablePrefixPure}85b981df7b444f905f8bf50747" ON ${tablePrefix}execution_entity ("waitTill", "id") `,
		);
		await queryRunner.query(
			`CREATE INDEX "IDX_${tablePrefixPure}d160d4771aba5a0d78943edbe3" ON ${tablePrefix}execution_entity ("workflowId", "id") `,
		);
	}

	public async down(queryRunner: QueryRunner): Promise<void> {
		let tablePrefix = config.get('database.tablePrefix');
		const tablePrefixPure = tablePrefix;
		const schema = config.get('database.postgresdb.schema');

		if (schema) {
			tablePrefix = schema + '.' + tablePrefix;
		}

		await queryRunner.query(
			`DROP INDEX "${schema}"."IDX_${tablePrefixPure}d160d4771aba5a0d78943edbe3"`,
		);
		await queryRunner.query(
			`DROP INDEX "${schema}"."IDX_${tablePrefixPure}85b981df7b444f905f8bf50747"`,
		);
		await queryRunner.query(
			`DROP INDEX "${schema}"."IDX_${tablePrefixPure}72ffaaab9f04c2c1f1ea86e662"`,
		);
		await queryRunner.query(
			`DROP INDEX "${schema}"."IDX_${tablePrefixPure}4f474ac92be81610439aaad61e"`,
		);
		await queryRunner.query(
			`DROP INDEX "${schema}"."IDX_${tablePrefixPure}58154df94c686818c99fb754ce"`,
		);
		await queryRunner.query(
			`DROP INDEX "${schema}"."IDX_${tablePrefixPure}33228da131bb1112247cf52a42"`,
		);
		await queryRunner.query(
			`CREATE INDEX "IDX_${tablePrefixPure}ca4a71b47f28ac6ea88293a8e2" ON ${tablePrefix}execution_entity ("waitTill") `,
		);
		await queryRunner.query(
			`CREATE INDEX "IDX_${tablePrefixPure}c4d999a5e90784e8caccf5589d" ON ${tablePrefix}execution_entity ("workflowId") `,
		);
	}
}


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

export const sessionOperations: INodeProperties[] = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		default: 'get',
		description: 'Operation to perform',
		options: [
			{
				name: 'Get',
				value: 'get',
			},
			{
				name: 'Get All',
				value: 'getAll',
			},
			{
				name: 'Get Details',
				value: 'getDetails',
			},
		],
		displayOptions: {
			show: {
				resource: [
					'session',
				],
			},
		},
	},
];

export const sessionFields: INodeProperties[] = [
	// ----------------------------------
	//         session: getAll
	// ----------------------------------
	{
		displayName: 'Return All',
		name: 'returnAll',
		type: 'boolean',
		default: false,
		description: 'Return all results.',
		displayOptions: {
			show: {
				resource: [
					'session',
				],
				operation: [
					'getAll',
				],
			},
		},
	},
	{
		displayName: 'Limit',
		name: 'limit',
		type: 'number',
		default: 10,
		description: 'The number of results to return.',
		typeOptions: {
			minValue: 1,
			maxValue: 100,
		},
		displayOptions: {
			show: {
				resource: [
					'session',
				],
				operation: [
					'getAll',
				],
				returnAll: [
					false,
				],
			},
		},
	},
	{
		displayName: 'Additional Fields',
		name: 'additionalFields',
		type: 'collection',
		placeholder: 'Add Field',
		displayOptions: {
			show: {
				resource: [
					'session',
				],
				operation: [
					'getAll',
				],
			},
		},
		default: {},
		options: [
			{
				displayName: 'Time Range',
				name: 'times',
				type: 'fixedCollection',
				placeholder: 'Add Time Range',
				required: true,
				default: {},
				options: [
					{
						displayName: 'Times Properties',
						name: 'timesProperties',
						values: [
							{
								displayName: 'Start Time',
								name: 'fromTime',
								type: 'dateTime',
								description: 'Start of the datetime range for the session.',
								default: '',
							},
							{
								displayName: 'End Time',
								name: 'toTime',
								type: 'dateTime',
								description: 'End of the datetime range for the session.',
								default: '',
							},
						],
					},
				],
			},
			{
				displayName: 'Webinar Key',
				name: 'webinarKey',
				type: 'options',
				typeOptions: {
					loadOptionsMethod: 'getWebinars',
				},
				default: {},
				description: 'Webinar by which to filter the sessions to retrieve.',
			},
		],
	},

	// ----------------------------------
	//      session: shared fields
	// ----------------------------------
	{
		displayName: 'Webinar Key',
		name: 'webinarKey',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getWebinars',
		},
		required: true,
		default: [],
		description: 'Key of the webinar to which the session belongs.',
		displayOptions: {
			show: {
				resource: [
					'session',
				],
				operation: [
					'get',
					'getDetails',
				],
			},
		},
	},
	{
		displayName: 'Session Key',
		name: 'sessionKey',
		type: 'string',
		required: true,
		default: '',
		displayOptions: {
			show: {
				resource: [
					'session',
				],
				operation: [
					'get',
					'getDetails',
				],
			},
		},
	},

	// ----------------------------------
	//        session: getDetails
	// ----------------------------------
	{
		displayName: 'Details',
		name: 'details',
		type: 'options',
		default: 'performance',
		options: [
			{
				name: 'Performance',
				value: 'performance',
				description: 'Performance details for a webinar session.',
			},
			{
				name: 'Polls',
				value: 'polls',
				description: 'Questions and answers for polls from a webinar session.',
			},
			{
				name: 'Questions',
				value: 'questions',
				description: 'Questions and answers for a past webinar session.',
			},
			{
				name: 'Surveys',
				value: 'surveys',
				description: 'Surveys for a past webinar session.',
			},
		],
		displayOptions: {
			show: {
				resource: [
					'session',
				],
				operation: [
					'getDetails',
				],
			},
		},
	},
];


How to host virtual coffee breaks with n8n

Supercharging your conference registration process with n8n

Creating triggers for n8n workflows using polling

5 tasks you can automate with the new Notion API

15 Google apps you can combine and automate to increase productivity

The ultimate guide to automate your video collaboration with Whereby, Mattermost, and n8n

5 workflow automations for Mattermost that we love at n8n

How to automate every step of an incident response workflow

Learn how to use webhooks with Mattermost slash commands

7 no-code workflow automations for Amazon Web Services


export interface IItem {
	cost?: number;
	notes?: string;
	product_key?: string;
	qty?: number;
	tax_rate1?: number;
	tax_rate2?: number;
	tax_name1?: string;
	tax_name2?: string;
}

export interface IInvoice {
	auto_bill?: boolean;
	client_id?: number;
	custom_value1?: number;
	custom_value2?: number;
	email_invoice?: boolean;
	email?: string;
	discount?: number;
	due_date?: string;
	invoice_date?: string;
	invoice_items?: IItem[];
	invoice_number?: string;
	invoice_status_id?: number;
	is_amount_discount?: boolean;
	paid?: number;
	partial?: number;
	partial_due_date?: string;
	po_number?: string;
	private_notes?: string;
	public_notes?: string;
	tax_name1?: string;
	tax_name2?: string;
	tax_rate1?: number;
	tax_rate2?: number;
}


`DROP INDEX IF EXISTS "${schema}".IDX_${tablePrefixPure}ca4a71b47f28ac6ea88293a8e2`,


packages/nodes-base
packages/editor-ui
packages/design-system
*package.json


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

export const leadOperations: INodeProperties[] = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		default: 'create',
		description: 'Operation to perform',
		options: [
			{
				name: 'Create',
				value: 'create',
			},
			{
				name: 'Delete',
				value: 'delete',
			},
			{
				name: 'Get',
				value: 'get',
			},
			{
				name: 'Unsubscribe',
				value: 'unsubscribe',
			},
		],
		displayOptions: {
			show: {
				resource: [
					'lead',
				],
			},
		},
	},
];

export const leadFields: INodeProperties[] = [
	// ----------------------------------
	//        lead: create
	// ----------------------------------
	{
		displayName: 'Campaign ID',
		name: 'campaignId',
		type: 'options',
		required: true,
		default: [],
		typeOptions: {
			loadOptionsMethod: 'getCampaigns',
		},
		description: 'ID of the campaign to create the lead under.',
		displayOptions: {
			show: {
				resource: [
					'lead',
				],
				operation: [
					'create',
				],
			},
		},
	},
	{
		displayName: 'Email',
		name: 'email',
		type: 'string',
		default: '',
		description: 'Email of the lead to create.',
		displayOptions: {
			show: {
				resource: [
					'lead',
				],
				operation: [
					'create',
				],
			},
		},
	},
	{
		displayName: 'Additional Fields',
		name: 'additionalFields',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				resource: [
					'lead',
				],
				operation: [
					'create',
				],
			},
		},
		options: [
			{
				displayName: 'Company Name',
				name: 'companyName',
				type: 'string',
				default: '',
				description: 'Company name of the lead to create.',
			},
			{
				displayName: 'Deduplicate',
				name: 'deduplicate',
				type: 'boolean',
				default: false,
				description: 'Do not insert if this email is already present in another campaign.',
			},
			{
				displayName: 'First Name',
				name: 'firstName',
				type: 'string',
				default: '',
				description: 'First name of the lead to create.',
			},
			{
				displayName: 'Last Name',
				name: 'lastName',
				type: 'string',
				default: '',
				description: 'Last name of the lead to create.',
			},
			{
				displayName: 'Icebreaker',
				name: 'icebreaker',
				type: 'string',
				default: '',
				description: 'Icebreaker of the lead to create.',
			},
			{
				displayName: 'Phone',
				name: 'phone',
				type: 'string',
				default: '',
				description: 'Phone number of the lead to create.',
			},
			{
				displayName: 'Picture URL',
				name: 'picture',
				type: 'string',
				default: '',
				description: 'Picture url of the lead to create.',
			},
			{
				displayName: 'LinkedIn URL',
				name: 'linkedinUrl',
				type: 'string',
				default: '',
				description: 'LinkedIn url of the lead to create.',
			},
		],
	},

	// ----------------------------------
	//        lead: delete
	// ----------------------------------
	{
		displayName: 'Campaign ID',
		name: 'campaignId',
		type: 'options',
		required: true,
		default: [],
		typeOptions: {
			loadOptionsMethod: 'getCampaigns',
		},
		description: 'ID of the campaign to remove the lead from.',
		displayOptions: {
			show: {
				resource: [
					'lead',
				],
				operation: [
					'delete',
				],
			},
		},
	},
	{
		displayName: 'Email',
		name: 'email',
		type: 'string',
		default: '',
		description: 'Email of the lead to delete.',
		displayOptions: {
			show: {
				resource: [
					'lead',
				],
				operation: [
					'delete',
				],
			},
		},
	},

	// ----------------------------------
	//           lead: get
	// ----------------------------------
	{
		displayName: 'Email',
		name: 'email',
		type: 'string',
		default: '',
		description: 'Email of the lead to retrieve.',
		displayOptions: {
			show: {
				resource: [
					'lead',
				],
				operation: [
					'get',
				],
			},
		},
	},

	// ----------------------------------
	//        lead: unsubscribe
	// ----------------------------------
	{
		displayName: 'Campaign ID',
		name: 'campaignId',
		type: 'options',
		required: true,
		default: [],
		typeOptions: {
			loadOptionsMethod: 'getCampaigns',
		},
		description: 'ID of the campaign to unsubscribe the lead from.',
		displayOptions: {
			show: {
				resource: [
					'lead',
				],
				operation: [
					'unsubscribe',
				],
			},
		},
	},
	{
		displayName: 'Email',
		name: 'email',
		type: 'string',
		default: '',
		description: 'Email of the lead to unsubscribe.',
		displayOptions: {
			show: {
				resource: [
					'lead',
				],
				operation: [
					'unsubscribe',
				],
			},
		},
	},
];


`CREATE INDEX "IDX_${tablePrefixPure}33228da131bb1112247cf52a42" ON ${tablePrefix}execution_entity ("stoppedAt") `,

`CREATE INDEX IF NOT EXISTS "IDX_${tablePrefixPure}33228da131bb1112247cf52a42" ON ${tablePrefix}execution_entity ("stoppedAt") `,

import { MigrationInterface, QueryRunner } from 'typeorm';
import * as config from '../../../../config';
import { logMigrationEnd, logMigrationStart } from '../../utils/migrationHelpers';

export class AddExecutionEntityIndexes1644421939510 implements MigrationInterface {
	name = 'AddExecutionEntityIndexes1644421939510';

	public async up(queryRunner: QueryRunner): Promise<void> {
		logMigrationStart(this.name);
		const tablePrefix = config.get('database.tablePrefix');

		await queryRunner.query(`DROP INDEX IF EXISTS 'IDX_${tablePrefix}c4d999a5e90784e8caccf5589d'`);

		await queryRunner.query(`DROP INDEX 'IDX_${tablePrefix}ca4a71b47f28ac6ea88293a8e2'`);

		await queryRunner.query(
			`CREATE INDEX 'IDX_${tablePrefix}06da892aaf92a48e7d3e400003' ON '${tablePrefix}execution_entity' ('workflowId', 'waitTill', 'id') `,
		);
		await queryRunner.query(
			`CREATE INDEX 'IDX_${tablePrefix}78d62b89dc1433192b86dce18a' ON '${tablePrefix}execution_entity' ('workflowId', 'finished', 'id') `,
		);
		await queryRunner.query(
			`CREATE INDEX 'IDX_${tablePrefix}1688846335d274033e15c846a4' ON '${tablePrefix}execution_entity' ('finished', 'id') `,
		);
		await queryRunner.query(
			`CREATE INDEX 'IDX_${tablePrefix}b94b45ce2c73ce46c54f20b5f9' ON '${tablePrefix}execution_entity' ('waitTill', 'id') `,
		);
		await queryRunner.query(
			`CREATE INDEX 'IDX_${tablePrefix}81fc04c8a17de15835713505e4' ON '${tablePrefix}execution_entity' ('workflowId', 'id') `,
		);
		logMigrationEnd(this.name);
	}

	public async down(queryRunner: QueryRunner): Promise<void> {
		const tablePrefix = config.get('database.tablePrefix');

		await queryRunner.query(`DROP INDEX 'IDX_${tablePrefix}81fc04c8a17de15835713505e4'`);
		await queryRunner.query(`DROP INDEX 'IDX_${tablePrefix}b94b45ce2c73ce46c54f20b5f9'`);
		await queryRunner.query(`DROP INDEX 'IDX_${tablePrefix}1688846335d274033e15c846a4'`);
		await queryRunner.query(`DROP INDEX 'IDX_${tablePrefix}78d62b89dc1433192b86dce18a'`);
		await queryRunner.query(`DROP INDEX 'IDX_${tablePrefix}06da892aaf92a48e7d3e400003'`);
		await queryRunner.query(
			`CREATE INDEX 'IDX_${tablePrefix}ca4a71b47f28ac6ea88293a8e2' ON '${tablePrefix}execution_entity' ('waitTill') `,
		);
		await queryRunner.query(
			`CREATE INDEX IF NOT EXISTS 'IDX_${tablePrefix}c4d999a5e90784e8caccf5589d' ON '${tablePrefix}execution_entity' ('workflowId') `,
		);
	}
}


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

export class WooCommerceApi implements ICredentialType {
	name = 'wooCommerceApi';
	displayName = 'WooCommerce API';
	documentationUrl = 'wooCommerce';
	properties: INodeProperties[] = [
		{
			displayName: 'Consumer Key',
			name: 'consumerKey',
			type: 'string',
			default: '',
		},
		{
			displayName: 'Consumer Secret',
			name: 'consumerSecret',
			type: 'string',
			default: '',
		},
		{
			displayName: 'WooCommerce URL',
			name: 'url',
			type: 'string',
			default: '',
			placeholder: 'https://example.com',
		},
		{
			displayName: 'Include Credentials in Query',
			name: 'includeCredentialsInQuery',
			type: 'boolean',
			default: false,
			description: `Occasionally, some servers may not parse the Authorization header correctly (if you see a “Consumer key is missing” error when authenticating over SSL, you have a server issue). In this case, you may provide the consumer key/secret as query string parameters instead.`,
		},
	];
}


// `getNewReleases` requires an ISO 3166-1 alpha-2 country code

export const isoCountryCodes = [
	{
		name: 'Afghanistan',
		alpha2: 'AF',
	},
	{
		name: 'Åland Islands',
		alpha2: 'AX',
	},
	{
		name: 'Albania',
		alpha2: 'AL',
	},
	{
		name: 'Algeria',
		alpha2: 'DZ',
	},
	{
		name: 'American Samoa',
		alpha2: 'AS',
	},
	{
		name: 'Andorra',
		alpha2: 'AD',
	},
	{
		name: 'Angola',
		alpha2: 'AO',
	},
	{
		name: 'Anguilla',
		alpha2: 'AI',
	},
	{
		name: 'Antarctica',
		alpha2: 'AQ',
	},
	{
		name: 'Antigua and Barbuda',
		alpha2: 'AG',
	},
	{
		name: 'Argentina',
		alpha2: 'AR',
	},
	{
		name: 'Armenia',
		alpha2: 'AM',
	},
	{
		name: 'Aruba',
		alpha2: 'AW',
	},
	{
		name: 'Australia',
		alpha2: 'AU',
	},
	{
		name: 'Austria',
		alpha2: 'AT',
	},
	{
		name: 'Azerbaijan',
		alpha2: 'AZ',
	},
	{
		name: 'Bahamas (the)',
		alpha2: 'BS',
	},
	{
		name: 'Bahrain',
		alpha2: 'BH',
	},
	{
		name: 'Bangladesh',
		alpha2: 'BD',
	},
	{
		name: 'Barbados',
		alpha2: 'BB',
	},
	{
		name: 'Belarus',
		alpha2: 'BY',
	},
	{
		name: 'Belgium',
		alpha2: 'BE',
	},
	{
		name: 'Belize',
		alpha2: 'BZ',
	},
	{
		name: 'Benin',
		alpha2: 'BJ',
	},
	{
		name: 'Bermuda',
		alpha2: 'BM',
	},
	{
		name: 'Bhutan',
		alpha2: 'BT',
	},
	{
		name: 'Bolivia (Plurinational State of)',
		alpha2: 'BO',
	},
	{
		name: 'Bonaire, Sint Eustatius and Saba',
		alpha2: 'BQ',
	},
	{
		name: 'Bosnia and Herzegovina',
		alpha2: 'BA',
	},
	{
		name: 'Botswana',
		alpha2: 'BW',
	},
	{
		name: 'Bouvet Island',
		alpha2: 'BV',
	},
	{
		name: 'Brazil',
		alpha2: 'BR',
	},
	{
		name: 'British Indian Ocean Territory (the)',
		alpha2: 'IO',
	},
	{
		name: 'Brunei Darussalam',
		alpha2: 'BN',
	},
	{
		name: 'Bulgaria',
		alpha2: 'BG',
	},
	{
		name: 'Burkina Faso',
		alpha2: 'BF',
	},
	{
		name: 'Burundi',
		alpha2: 'BI',
	},
	{
		name: 'Cabo Verde',
		alpha2: 'CV',
	},
	{
		name: 'Cambodia',
		alpha2: 'KH',
	},
	{
		name: 'Cameroon',
		alpha2: 'CM',
	},
	{
		name: 'Canada',
		alpha2: 'CA',
	},
	{
		name: 'Cayman Islands (the)',
		alpha2: 'KY',
	},
	{
		name: 'Central African Republic (the)',
		alpha2: 'CF',
	},
	{
		name: 'Chad',
		alpha2: 'TD',
	},
	{
		name: 'Chile',
		alpha2: 'CL',
	},
	{
		name: 'China',
		alpha2: 'CN',
	},
	{
		name: 'Christmas Island',
		alpha2: 'CX',
	},
	{
		name: 'Cocos (Keeling) Islands (the)',
		alpha2: 'CC',
	},
	{
		name: 'Colombia',
		alpha2: 'CO',
	},
	{
		name: 'Comoros (the)',
		alpha2: 'KM',
	},
	{
		name: 'Congo (the Democratic Republic of the)',
		alpha2: 'CD',
	},
	{
		name: 'Congo (the)',
		alpha2: 'CG',
	},
	{
		name: 'Cook Islands (the)',
		alpha2: 'CK',
	},
	{
		name: 'Costa Rica',
		alpha2: 'CR',
	},
	{
		name: 'Côte d\'Ivoire',
		alpha2: 'CI',
	},
	{
		name: 'Croatia',
		alpha2: 'HR',
	},
	{
		name: 'Cuba',
		alpha2: 'CU',
	},
	{
		name: 'Curaçao',
		alpha2: 'CW',
	},
	{
		name: 'Cyprus',
		alpha2: 'CY',
	},
	{
		name: 'Czechia',
		alpha2: 'CZ',
	},
	{
		name: 'Denmark',
		alpha2: 'DK',
	},
	{
		name: 'Djibouti',
		alpha2: 'DJ',
	},
	{
		name: 'Dominica',
		alpha2: 'DM',
	},
	{
		name: 'Dominican Republic (the)',
		alpha2: 'DO',
	},
	{
		name: 'Ecuador',
		alpha2: 'EC',
	},
	{
		name: 'Egypt',
		alpha2: 'EG',
	},
	{
		name: 'El Salvador',
		alpha2: 'SV',
	},
	{
		name: 'Equatorial Guinea',
		alpha2: 'GQ',
	},
	{
		name: 'Eritrea',
		alpha2: 'ER',
	},
	{
		name: 'Estonia',
		alpha2: 'EE',
	},
	{
		name: 'Ethiopia',
		alpha2: 'ET',
	},
	{
		name: 'Falkland Islands (the) [Malvinas]',
		alpha2: 'FK',
	},
	{
		name: 'Faroe Islands (the)',
		alpha2: 'FO',
	},
	{
		name: 'Fiji',
		alpha2: 'FJ',
	},
	{
		name: 'Finland',
		alpha2: 'FI',
	},
	{
		name: 'France',
		alpha2: 'FR',
	},
	{
		name: 'French Guiana',
		alpha2: 'GF',
	},
	{
		name: 'French Polynesia',
		alpha2: 'PF',
	},
	{
		name: 'French Southern Territories (the)',
		alpha2: 'TF',
	},
	{
		name: 'Gabon',
		alpha2: 'GA',
	},
	{
		name: 'Gambia (the)',
		alpha2: 'GM',
	},
	{
		name: 'Georgia',
		alpha2: 'GE',
	},
	{
		name: 'Germany',
		alpha2: 'DE',
	},
	{
		name: 'Ghana',
		alpha2: 'GH',
	},
	{
		name: 'Gibraltar',
		alpha2: 'GI',
	},
	{
		name: 'Greece',
		alpha2: 'GR',
	},
	{
		name: 'Greenland',
		alpha2: 'GL',
	},
	{
		name: 'Grenada',
		alpha2: 'GD',
	},
	{
		name: 'Guadeloupe',
		alpha2: 'GP',
	},
	{
		name: 'Guam',
		alpha2: 'GU',
	},
	{
		name: 'Guatemala',
		alpha2: 'GT',
	},
	{
		name: 'Guernsey',
		alpha2: 'GG',
	},
	{
		name: 'Guinea',
		alpha2: 'GN',
	},
	{
		name: 'Guinea-Bissau',
		alpha2: 'GW',
	},
	{
		name: 'Guyana',
		alpha2: 'GY',
	},
	{
		name: 'Haiti',
		alpha2: 'HT',
	},
	{
		name: 'Heard Island and McDonald Islands',
		alpha2: 'HM',
	},
	{
		name: 'Holy See (the)',
		alpha2: 'VA',
	},
	{
		name: 'Honduras',
		alpha2: 'HN',
	},
	{
		name: 'Hong Kong',
		alpha2: 'HK',
	},
	{
		name: 'Hungary',
		alpha2: 'HU',
	},
	{
		name: 'Iceland',
		alpha2: 'IS',
	},
	{
		name: 'India',
		alpha2: 'IN',
	},
	{
		name: 'Indonesia',
		alpha2: 'ID',
	},
	{
		name: 'Iran (Islamic Republic of)',
		alpha2: 'IR',
	},
	{
		name: 'Iraq',
		alpha2: 'IQ',
	},
	{
		name: 'Ireland',
		alpha2: 'IE',
	},
	{
		name: 'Isle of Man',
		alpha2: 'IM',
	},
	{
		name: 'Israel',
		alpha2: 'IL',
	},
	{
		name: 'Italy',
		alpha2: 'IT',
	},
	{
		name: 'Jamaica',
		alpha2: 'JM',
	},
	{
		name: 'Japan',
		alpha2: 'JP',
	},
	{
		name: 'Jersey',
		alpha2: 'JE',
	},
	{
		name: 'Jordan',
		alpha2: 'JO',
	},
	{
		name: 'Kazakhstan',
		alpha2: 'KZ',
	},
	{
		name: 'Kenya',
		alpha2: 'KE',
	},
	{
		name: 'Kiribati',
		alpha2: 'KI',
	},
	{
		name: 'Korea (the Democratic People\'s Republic of)',
		alpha2: 'KP',
	},
	{
		name: 'Korea (the Republic of)',
		alpha2: 'KR',
	},
	{
		name: 'Kuwait',
		alpha2: 'KW',
	},
	{
		name: 'Kyrgyzstan',
		alpha2: 'KG',
	},
	{
		name: 'Lao People\'s Democratic Republic (the)',
		alpha2: 'LA',
	},
	{
		name: 'Latvia',
		alpha2: 'LV',
	},
	{
		name: 'Lebanon',
		alpha2: 'LB',
	},
	{
		name: 'Lesotho',
		alpha2: 'LS',
	},
	{
		name: 'Liberia',
		alpha2: 'LR',
	},
	{
		name: 'Libya',
		alpha2: 'LY',
	},
	{
		name: 'Liechtenstein',
		alpha2: 'LI',
	},
	{
		name: 'Lithuania',
		alpha2: 'LT',
	},
	{
		name: 'Luxembourg',
		alpha2: 'LU',
	},
	{
		name: 'Macao',
		alpha2: 'MO',
	},
	{
		name: 'Macedonia (the former Yugoslav Republic of)',
		alpha2: 'MK',
	},
	{
		name: 'Madagascar',
		alpha2: 'MG',
	},
	{
		name: 'Malawi',
		alpha2: 'MW',
	},
	{
		name: 'Malaysia',
		alpha2: 'MY',
	},
	{
		name: 'Maldives',
		alpha2: 'MV',
	},
	{
		name: 'Mali',
		alpha2: 'ML',
	},
	{
		name: 'Malta',
		alpha2: 'MT',
	},
	{
		name: 'Marshall Islands (the)',
		alpha2: 'MH',
	},
	{
		name: 'Martinique',
		alpha2: 'MQ',
	},
	{
		name: 'Mauritania',
		alpha2: 'MR',
	},
	{
		name: 'Mauritius',
		alpha2: 'MU',
	},
	{
		name: 'Mayotte',
		alpha2: 'YT',
	},
	{
		name: 'Mexico',
		alpha2: 'MX',
	},
	{
		name: 'Micronesia (Federated States of)',
		alpha2: 'FM',
	},
	{
		name: 'Moldova (the Republic of)',
		alpha2: 'MD',
	},
	{
		name: 'Monaco',
		alpha2: 'MC',
	},
	{
		name: 'Mongolia',
		alpha2: 'MN',
	},
	{
		name: 'Montenegro',
		alpha2: 'ME',
	},
	{
		name: 'Montserrat',
		alpha2: 'MS',
	},
	{
		name: 'Morocco',
		alpha2: 'MA',
	},
	{
		name: 'Mozambique',
		alpha2: 'MZ',
	},
	{
		name: 'Myanmar',
		alpha2: 'MM',
	},
	{
		name: 'Namibia',
		alpha2: 'NA',
	},
	{
		name: 'Nauru',
		alpha2: 'NR',
	},
	{
		name: 'Nepal',
		alpha2: 'NP',
	},
	{
		name: 'Netherlands (the)',
		alpha2: 'NL',
	},
	{
		name: 'New Caledonia',
		alpha2: 'NC',
	},
	{
		name: 'New Zealand',
		alpha2: 'NZ',
	},
	{
		name: 'Nicaragua',
		alpha2: 'NI',
	},
	{
		name: 'Niger (the)',
		alpha2: 'NE',
	},
	{
		name: 'Nigeria',
		alpha2: 'NG',
	},
	{
		name: 'Niue',
		alpha2: 'NU',
	},
	{
		name: 'Norfolk Island',
		alpha2: 'NF',
	},
	{
		name: 'Northern Mariana Islands (the)',
		alpha2: 'MP',
	},
	{
		name: 'Norway',
		alpha2: 'NO',
	},
	{
		name: 'Oman',
		alpha2: 'OM',
	},
	{
		name: 'Pakistan',
		alpha2: 'PK',
	},
	{
		name: 'Palau',
		alpha2: 'PW',
	},
	{
		name: 'Palestine, State of',
		alpha2: 'PS',
	},
	{
		name: 'Panama',
		alpha2: 'PA',
	},
	{
		name: 'Papua New Guinea',
		alpha2: 'PG',
	},
	{
		name: 'Paraguay',
		alpha2: 'PY',
	},
	{
		name: 'Peru',
		alpha2: 'PE',
	},
	{
		name: 'Philippines (the)',
		alpha2: 'PH',
	},
	{
		name: 'Pitcairn',
		alpha2: 'PN',
	},
	{
		name: 'Poland',
		alpha2: 'PL',
	},
	{
		name: 'Portugal',
		alpha2: 'PT',
	},
	{
		name: 'Puerto Rico',
		alpha2: 'PR',
	},
	{
		name: 'Qatar',
		alpha2: 'QA',
	},
	{
		name: 'Réunion',
		alpha2: 'RE',
	},
	{
		name: 'Romania',
		alpha2: 'RO',
	},
	{
		name: 'Russian Federation (the)',
		alpha2: 'RU',
	},
	{
		name: 'Rwanda',
		alpha2: 'RW',
	},
	{
		name: 'Saint Barthélemy',
		alpha2: 'BL',
	},
	{
		name: 'Saint Helena, Ascension and Tristan da Cunha',
		alpha2: 'SH',
	},
	{
		name: 'Saint Kitts and Nevis',
		alpha2: 'KN',
	},
	{
		name: 'Saint Lucia',
		alpha2: 'LC',
	},
	{
		name: 'Saint Martin (French part)',
		alpha2: 'MF',
	},
	{
		name: 'Saint Pierre and Miquelon',
		alpha2: 'PM',
	},
	{
		name: 'Saint Vincent and the Grenadines',
		alpha2: 'VC',
	},
	{
		name: 'Samoa',
		alpha2: 'WS',
	},
	{
		name: 'San Marino',
		alpha2: 'SM',
	},
	{
		name: 'Sao Tome and Principe',
		alpha2: 'ST',
	},
	{
		name: 'Saudi Arabia',
		alpha2: 'SA',
	},
	{
		name: 'Senegal',
		alpha2: 'SN',
	},
	{
		name: 'Serbia',
		alpha2: 'RS',
	},
	{
		name: 'Seychelles',
		alpha2: 'SC',
	},
	{
		name: 'Sierra Leone',
		alpha2: 'SL',
	},
	{
		name: 'Singapore',
		alpha2: 'SG',
	},
	{
		name: 'Sint Maarten (Dutch part)',
		alpha2: 'SX',
	},
	{
		name: 'Slovakia',
		alpha2: 'SK',
	},
	{
		name: 'Slovenia',
		alpha2: 'SI',
	},
	{
		name: 'Solomon Islands',
		alpha2: 'SB',
	},
	{
		name: 'Somalia',
		alpha2: 'SO',
	},
	{
		name: 'South Africa',
		alpha2: 'ZA',
	},
	{
		name: 'South Georgia and the South Sandwich Islands',
		alpha2: 'GS',
	},
	{
		name: 'South Sudan',
		alpha2: 'SS',
	},
	{
		name: 'Spain',
		alpha2: 'ES',
	},
	{
		name: 'Sri Lanka',
		alpha2: 'LK',
	},
	{
		name: 'Sudan (the)',
		alpha2: 'SD',
	},
	{
		name: 'Suriname',
		alpha2: 'SR',
	},
	{
		name: 'Svalbard and Jan Mayen',
		alpha2: 'SJ',
	},
	{
		name: 'Swaziland',
		alpha2: 'SZ',
	},
	{
		name: 'Sweden',
		alpha2: 'SE',
	},
	{
		name: 'Switzerland',
		alpha2: 'CH',
	},
	{
		name: 'Syrian Arab Republic',
		alpha2: 'SY',
	},
	{
		name: 'Taiwan (Province of China)',
		alpha2: 'TW',
	},
	{
		name: 'Tajikistan',
		alpha2: 'TJ',
	},
	{
		name: 'Tanzania, United Republic of',
		alpha2: 'TZ',
	},
	{
		name: 'Thailand',
		alpha2: 'TH',
	},
	{
		name: 'Timor-Leste',
		alpha2: 'TL',
	},
	{
		name: 'Togo',
		alpha2: 'TG',
	},
	{
		name: 'Tokelau',
		alpha2: 'TK',
	},
	{
		name: 'Tonga',
		alpha2: 'TO',
	},
	{
		name: 'Trinidad and Tobago',
		alpha2: 'TT',
	},
	{
		name: 'Tunisia',
		alpha2: 'TN',
	},
	{
		name: 'Turkey',
		alpha2: 'TR',
	},
	{
		name: 'Turkmenistan',
		alpha2: 'TM',
	},
	{
		name: 'Turks and Caicos Islands (the)',
		alpha2: 'TC',
	},
	{
		name: 'Tuvalu',
		alpha2: 'TV',
	},
	{
		name: 'Uganda',
		alpha2: 'UG',
	},
	{
		name: 'Ukraine',
		alpha2: 'UA',
	},
	{
		name: 'United Arab Emirates (the)',
		alpha2: 'AE',
	},
	{
		name: 'United Kingdom of Great Britain and Northern Ireland (the)',
		alpha2: 'GB',
	},
	{
		name: 'United States Minor Outlying Islands (the)',
		alpha2: 'UM',
	},
	{
		name: 'United States of America (the)',
		alpha2: 'US',
	},
	{
		name: 'Uruguay',
		alpha2: 'UY',
	},
	{
		name: 'Uzbekistan',
		alpha2: 'UZ',
	},
	{
		name: 'Vanuatu',
		alpha2: 'VU',
	},
	{
		name: 'Venezuela (Bolivarian Republic of)',
		alpha2: 'VE',
	},
	{
		name: 'Viet Nam',
		alpha2: 'VN',
	},
	{
		name: 'Virgin Islands (British)',
		alpha2: 'VG',
	},
	{
		name: 'Virgin Islands (U.S.)',
		alpha2: 'VI',
	},
	{
		name: 'Wallis and Futuna',
		alpha2: 'WF',
	},
	{
		name: 'Western Sahara*',
		alpha2: 'EH',
	},
	{
		name: 'Yemen',
		alpha2: 'YE',
	},
	{
		name: 'Zambia',
		alpha2: 'ZM',
	},
	{
		name: 'Zimbabwe',
		alpha2: 'ZW',
	},
];


import { getAll as execute } from './execute';
import { ticketGetAllDescription as description } from './description';

export {
	description,
	execute,
};


`CREATE INDEX "IDX_${tablePrefixPure}58154df94c686818c99fb754ce" ON ${tablePrefix}execution_entity ("workflowId", "waitTill", "id") `,

`CREATE INDEX IF NOT EXISTS "IDX_${tablePrefixPure}58154df94c686818c99fb754ce" ON ${tablePrefix}execution_entity ("workflowId", "waitTill", "id") `,


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

//https://developers.google.com/youtube/v3/guides/auth/client-side-web-apps#identify-access-scopes
const scopes = [
	'https://www.googleapis.com/auth/youtube',
	'https://www.googleapis.com/auth/youtubepartner',
	'https://www.googleapis.com/auth/youtube.force-ssl',
	'https://www.googleapis.com/auth/youtube.upload',
	'https://www.googleapis.com/auth/youtubepartner-channel-audit',
];

export class YouTubeOAuth2Api implements ICredentialType {
	name = 'youTubeOAuth2Api';
	icon = 'node:n8n-nodes-base.youTube';
	extends = [
		'googleOAuth2Api',
	];
	displayName = 'YouTube OAuth2 API';
	documentationUrl = 'google';
	properties: INodeProperties[] = [
		{
			displayName: 'Scope',
			name: 'scope',
			type: 'hidden',
			default: scopes.join(' '),
		},
	];
}


import {
	UserProperties,
} from '../../Interfaces';

export const userGetByIdDescription: UserProperties = [
	{
		displayName: 'User IDs',
		name: 'userIds',
		type: 'string',
		required: true,
		displayOptions: {
			show: {
				resource: [
					'user',
				],
				operation: [
					'getById',
				],
			},
		},
		default: '',
		description: `User's ID`,
	},
	{
		displayName: 'Additional Fields',
		name: 'additionalFields',
		type: 'collection',
		placeholder: 'Add Field',
		displayOptions: {
			show: {
				resource: [
					'user',
				],
				operation: [
					'getById',
				],
			},
		},
		default: {},
		options: [
			{
				displayName: 'Since',
				name: 'since',
				type: 'dateTime',
				default: '',
				description: 'Only return users that have been modified since the given Unix timestamp (in milliseconds).',
			},
		],
	},
];


`CREATE INDEX "IDX_${tablePrefixPure}4f474ac92be81610439aaad61e" ON ${tablePrefix}execution_entity ("workflowId", "finished", "id") `,

`CREATE INDEX IF NOT EXISTS "IDX_${tablePrefixPure}4f474ac92be81610439aaad61e" ON ${tablePrefix}execution_entity ("workflowId", "finished", "id") `,


import {
	IExecuteFunctions,
} from 'n8n-core';

import {
	IDataObject,
	INodeExecutionData,
} from 'n8n-workflow';

import {
	apiRequest,
} from '../../../transport';

export async function deactive(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
	const userId = this.getNodeParameter('userId', index) as string;
	const qs = {} as IDataObject;
	const requestMethod = 'DELETE';
	const endpoint = `users/${userId}`;
	const body = {} as IDataObject;

	const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);

	return this.helpers.returnJsonArray(responseData);
}


Learn to Automate Your Factory's Incident Reporting: A Step by Step Guide


import {
	ChannelProperties,
} from '../../Interfaces';

export const channelAddUserDescription: ChannelProperties = [
	{
		displayName: 'Channel ID',
		name: 'channelId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getChannels',
		},
		options: [],
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: [
					'addUser',
				],
				resource: [
					'channel',
				],
			},
		},
		description: 'The ID of the channel to invite user to.',
	},
	{
		displayName: 'User ID',
		name: 'userId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getUsers',
		},
		options: [],
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: [
					'addUser',
				],
				resource: [
					'channel',
				],
			},
		},
		description: 'The ID of the user to invite into channel.',
	},
];


`CREATE INDEX "IDX_${tablePrefixPure}72ffaaab9f04c2c1f1ea86e662" ON ${tablePrefix}execution_entity ("finished", "id") `,

`CREATE INDEX IF NOT EXISTS "IDX_${tablePrefixPure}72ffaaab9f04c2c1f1ea86e662" ON ${tablePrefix}execution_entity ("finished", "id") `,


import { IExecuteFunctions } from 'n8n-core';
import {
	INodeExecutionData,
	INodeType,
	INodeTypeDescription,
	NodeOperationError
} from 'n8n-workflow';

import { exec } from 'child_process';

export interface IExecReturnData {
	exitCode: number;
	error?: Error;
	stderr: string;
	stdout: string;
}

/**
 * Promisifiy exec manually to also get the exit code
 *
 * @param {string} command
 * @returns {Promise<IExecReturnData>}
 */
function execPromise(command: string): Promise<IExecReturnData> {
	const returnData: IExecReturnData = {
		error: undefined,
		exitCode: 0,
		stderr: '',
		stdout: '',
	};

	return new Promise((resolve, reject) => {
		exec(command, { cwd: process.cwd() }, (error, stdout, stderr) => {
			returnData.stdout = stdout.trim();
			returnData.stderr = stderr.trim();

			if (error) {
				returnData.error = error;
			}

			resolve(returnData);
		}).on('exit', code => { returnData.exitCode = code || 0; });
	});
}

export class ExecuteCommand implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'Execute Command',
		name: 'executeCommand',
		icon: 'fa:terminal',
		group: ['transform'],
		version: 1,
		description: 'Executes a command on the host',
		defaults: {
			name: 'Execute Command',
			color: '#886644',
		},
		inputs: ['main'],
		outputs: ['main'],
		properties: [
			{
				displayName: 'Execute Once',
				name: 'executeOnce',
				type: 'boolean',
				default: true,
				description: 'If activated it executes only once instead of once for each entry.',
			},
			{
				displayName: 'Command',
				name: 'command',
				typeOptions: {
					rows: 5,
				},
				type: 'string',
				default: '',
				placeholder: 'echo "test"',
				description: 'The command to execute',
			},
		],
	};

	async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {

		let items = this.getInputData();

		let command: string;
		const executeOnce = this.getNodeParameter('executeOnce', 0) as boolean;

		if (executeOnce === true) {
			items = [items[0]];
		}

		const returnItems: INodeExecutionData[] = [];
		for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {

			try{

				command = this.getNodeParameter('command', itemIndex) as string;

				const {
					error,
					exitCode,
					stdout,
					stderr,
				} = await execPromise(command);

				if (error !== undefined) {
					throw new NodeOperationError(this.getNode(), error.message);
				}

				returnItems.push(
					{
						json: {
							exitCode,
							stderr,
							stdout,
						},
					},
				);

			} catch (error) {
				if (this.continueOnFail()) {
					returnItems.push({json:{ error: error.message }});
					continue;
				}
				throw error;
			}
		}

		return this.prepareOutputData(returnItems);
	}
}


import {
	INodeProperties
} from 'n8n-workflow';

export const configOperations: INodeProperties[] = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		displayOptions: {
			show: {
				resource: [
					'config',
				],
			},
		},
		options: [
			{
				name: 'Get',
				value: 'get',
				description: 'Get the configuration',
			},
			{
				name: 'Check Configuration',
				value: 'check',
				description: 'Check the configuration',
			},
		],
		default: 'get',
		description: 'The operation to perform.',
	},
];


import {
	IExecuteFunctions,
} from 'n8n-core';

import {
	IDataObject,
	ILoadOptionsFunctions,
	INodeExecutionData,
	INodeParameters,
	INodePropertyOptions,
	INodeType,
	INodeTypeDescription,
	NodeOperationError,
} from 'n8n-workflow';

import {
	createDatapoint,
	deleteDatapoint,
	getAllDatapoints,
	updateDatapoint
} from './Beeminder.node.functions';

import {
	beeminderApiRequest,
} from './GenericFunctions';

import * as moment from 'moment-timezone';

export class Beeminder implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'Beeminder',
		name: 'beeminder',
		group: ['output'],
		version: 1,
		description: 'Consume Beeminder API',
		subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
		defaults: {
			name: 'Beeminder',
		},
		icon: 'file:beeminder.png',
		inputs: ['main'],
		outputs: ['main'],
		credentials: [
			{
				name: 'beeminderApi',
				required: true,
			},
		],
		properties: [
			{
				displayName: 'Resource',
				name: 'resource',
				type: 'options',
				required: true,
				options: [
					{
						name: 'Datapoint',
						value: 'datapoint',
					},
				],
				default: 'datapoint',
				description: 'The resource to operate on.',
			},
			{
				displayName: 'Operation',
				name: 'operation',
				type: 'options',
				options: [
					{
						name: 'Create',
						value: 'create',
						description: 'Create datapoint for goal.',
					},
					{
						name: 'Delete',
						value: 'delete',
						description: 'Delete a datapoint.',
					},
					{
						name: 'Get All',
						value: 'getAll',
						description: 'Get all datapoints for a goal.',
					},
					{
						name: 'Update',
						value: 'update',
						description: 'Update a datapoint.',
					},
				],
				default: 'create',
				description: 'The operation to perform.',
				required: true,
			},
			{
				displayName: 'Goal Name',
				name: 'goalName',
				type: 'options',
				typeOptions: {
					loadOptionsMethod: 'getGoals',
				},
				displayOptions: {
					show: {
						resource: [
							'datapoint',
						],
					},
				},
				default: '',
				description: 'The name of the goal.',
				required: true,
			},
			{
				displayName: 'Return All',
				name: 'returnAll',
				type: 'boolean',
				displayOptions: {
					show: {
						operation: [
							'getAll',
						],
						resource: [
							'datapoint',
						],
					},
				},
				default: false,
				description: 'If all results should be returned or only up to a given limit.',
			},
			{
				displayName: 'Limit',
				name: 'limit',
				type: 'number',
				displayOptions: {
					show: {
						operation: [
							'getAll',
						],
						resource: [
							'datapoint',
						],
						returnAll: [
							false,
						],
					},
				},
				typeOptions: {
					minValue: 1,
					maxValue: 300,
				},
				default: 30,
				description: 'How many results to return.',
			},
			{
				displayName: 'Value',
				name: 'value',
				type: 'number',
				default: 1,
				placeholder: '',
				description: 'Datapoint value to send.',
				displayOptions: {
					show: {
						resource: [
							'datapoint',
						],
						operation: [
							'create',
						],
					},
				},
				required: true,
			},
			{
				displayName: 'Datapoint ID',
				name: 'datapointId',
				type: 'string',
				default: '',
				description: 'Datapoint id',
				displayOptions: {
					show: {
						operation: [
							'update',
							'delete',
						],
					},
				},
				required: true,
			},
			{
				displayName: 'Additional Fields',
				name: 'additionalFields',
				type: 'collection',
				placeholder: 'Add Field',
				default: {},
				displayOptions: {
					show: {
						resource: [
							'datapoint',
						],
						operation: [
							'create',
						],
					},
				},
				options: [
					{
						displayName: 'Comment',
						name: 'comment',
						type: 'string',
						default: '',
						description: 'Comment',
					},
					{
						displayName: 'Timestamp',
						name: 'timestamp',
						type: 'dateTime',
						default: '',
						placeholder: '',
						description: 'Defaults to "now" if none is passed in, or the existing timestamp if the datapoint is being updated rather than created.',
					},
					{
						displayName: 'Request ID',
						name: 'requestId',
						type: 'string',
						default: '',
						placeholder: '',
						description: 'String to uniquely identify a datapoint.',
					},
				],
			},
			{
				displayName: 'Options',
				name: 'options',
				type: 'collection',
				placeholder: 'Add field',
				default: {},
				displayOptions: {
					show: {
						resource: [
							'datapoint',
						],
						operation: [
							'getAll',
						],
					},
				},
				options: [
					{
						displayName: 'Sort',
						name: 'sort',
						type: 'string',
						default: 'id',
						placeholder: '',
						description: 'Attribute to sort on.',
					},
				],
			},
			{
				displayName: 'Update Fields',
				name: 'updateFields',
				type: 'collection',
				placeholder: 'Add Field',
				default: {},
				displayOptions: {
					show: {
						resource: [
							'datapoint',
						],
						operation: [
							'update',
						],
					},
				},
				options: [
					{
						displayName: 'Value',
						name: 'value',
						type: 'number',
						default: 1,
						placeholder: '',
						description: 'Datapoint value to send.',
					},
					{
						displayName: 'Comment',
						name: 'comment',
						type: 'string',
						default: '',
						description: 'Comment',
					},
					{
						displayName: 'Timestamp',
						name: 'timestamp',
						type: 'dateTime',
						default: '',
						placeholder: '',
						description: 'Defaults to "now" if none is passed in, or the existing timestamp if the datapoint is being updated rather than created.',
					},
				],
			},
		],
	};

	methods = {
		loadOptions: {
			// Get all the available groups to display them to user so that he can
			// select them easily
			async getGoals(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {

				const credentials = await this.getCredentials('beeminderApi');

				if (credentials === undefined) {
					throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
				}

				const endpoint = `/users/${credentials.user}/goals.json`;

				const returnData: INodePropertyOptions[] = [];
				const goals = await beeminderApiRequest.call(this, 'GET', endpoint);
				for (const goal of goals) {
					returnData.push({
						name: goal.slug,
						value: goal.slug,
					});
				}
				return returnData;
			},
		},
	};

	async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {

		const items = this.getInputData();
		const returnData: IDataObject[] = [];
		const length = items.length as unknown as number;
		const timezone = this.getTimezone();

		const resource = this.getNodeParameter('resource', 0) as string;
		const operation = this.getNodeParameter('operation', 0) as string;
		let results;

		for (let i = 0; i < length; i++) {
			try {
				if (resource === 'datapoint') {
					const goalName = this.getNodeParameter('goalName', i) as string;
					if (operation === 'create') {
						const value = this.getNodeParameter('value', i) as number;
						const options = this.getNodeParameter('additionalFields', i) as INodeParameters;
						const data: IDataObject = {
							value,
							goalName,
						};
						Object.assign(data, options);

						if (data.timestamp) {
							data.timestamp = moment.tz(data.timestamp, timezone).unix();
						}
						results = await createDatapoint.call(this, data);
					}
					else if (operation === 'getAll') {
						const returnAll = this.getNodeParameter('returnAll', i) as boolean;
						const options = this.getNodeParameter('options', i) as INodeParameters;
						const data: IDataObject = {
							goalName,
						};
						Object.assign(data, options);

						if (returnAll === false) {
							data.count = this.getNodeParameter('limit', 0) as number;
						}

						results = await getAllDatapoints.call(this, data);
					}
					else if (operation === 'update') {
						const datapointId = this.getNodeParameter('datapointId', i) as string;
						const options = this.getNodeParameter('updateFields', i) as INodeParameters;
						const data: IDataObject = {
							goalName,
							datapointId,
						};
						Object.assign(data, options);
						if (data.timestamp) {
							data.timestamp = moment.tz(data.timestamp, timezone).unix();
						}
						results = await updateDatapoint.call(this, data);
					}
					else if (operation === 'delete') {
						const datapointId = this.getNodeParameter('datapointId', i) as string;
						const data: IDataObject = {
							goalName,
							datapointId,
						};
						results = await deleteDatapoint.call(this, data);
					}
				}
			} catch (error) {
				if (this.continueOnFail()) {
					returnData.push({ error: error.message });
					continue;
				}
				throw error;
			}
			if (Array.isArray(results)) {
				returnData.push.apply(returnData, results as IDataObject[]);
			} else {
				returnData.push(results as IDataObject);
			}
		}

		return [this.helpers.returnJsonArray(returnData)];
	}
}


`CREATE INDEX "IDX_${tablePrefixPure}85b981df7b444f905f8bf50747" ON ${tablePrefix}execution_entity ("waitTill", "id") `,

`CREATE INDEX IF NOT EXISTS "IDX_${tablePrefixPure}85b981df7b444f905f8bf50747" ON ${tablePrefix}execution_entity ("waitTill", "id") `,


import {
	IExecuteFunctions,
} from 'n8n-core';

import {
	IDataObject,
	INodeExecutionData,
} from 'n8n-workflow';

import {
	apiRequest,
} from '../../../transport';

export async function getByEmail(this: IExecuteFunctions, index: number): Promise<INodeExecutionData[]> {
	const email = this.getNodeParameter('email', index) as string;

	const qs = {} as IDataObject;
	const requestMethod = 'GET';
	const endpoint = `users/email/${email}`;
	const body = {} as IDataObject;

	const responseData = await apiRequest.call(this, requestMethod, endpoint, body, qs);

	return this.helpers.returnJsonArray(responseData);
}


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

import {
	getFilters,
} from './GenericFunctions';

export const rowOperations: INodeProperties[] = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		displayOptions: {
			show: {
				resource: [
					'row',
				],
			},
		},
		options: [
			{
				name: 'Create',
				value: 'create',
				description: 'Create a new row',
			},
			{
				name: 'Delete',
				value: 'delete',
				description: 'Delete a row',
			},
			{
				name: 'Get',
				value: 'get',
				description: 'Get a row',
			},
			{
				name: 'Get All',
				value: 'getAll',
				description: 'Get all rows',
			},
			{
				name: 'Update',
				value: 'update',
				description: 'Update a row',
			},
		],
		default: 'create',
	},
];

export const rowFields: INodeProperties[] = [

	/* -------------------------------------------------------------------------- */
	/*                                row:create                                  */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Table Name',
		name: 'tableId',
		type: 'options',
		typeOptions: {
			loadOptionsMethod: 'getTables',
		},
		required: true,
		displayOptions: {
			show: {
				resource: [
					'row',
				],
				operation: [
					'create',
					'delete',
					'get',
					'getAll',
					'update',
				],
			},
		},
		default: '',
	},
	...getFilters(
		['row'],
		['update'],
		{
			includeNoneOption: false,
			filterTypeDisplayName: 'Select Type',
			filterStringDisplayName: 'Select Condition (String)',
			filterFixedCollectionDisplayName: 'Select Conditions',
			mustMatchOptions: [
				{
					name: 'Any Select Condition',
					value: 'anyFilter',
				},
				{
					name: 'All Select Conditions',
					value: 'allFilters',
				},
			],
		}),
	{
		displayName: 'Data to Send',
		name: 'dataToSend',
		type: 'options',
		options: [
			{
				name: 'Auto-map Input Data to Columns',
				value: 'autoMapInputData',
				description: 'Use when node input properties match destination column names',
			},
			{
				name: 'Define Below for Each Column',
				value: 'defineBelow',
				description: 'Set the value for each destination column',
			},
		],
		displayOptions: {
			show: {
				resource: [
					'row',
				],
				operation: [
					'create',
					'update',
				],
			},
		},
		default: 'defineBelow',
		description: '',
	},
	{
		displayName: 'Inputs to Ignore',
		name: 'inputsToIgnore',
		type: 'string',
		displayOptions: {
			show: {
				resource: [
					'row',
				],
				operation: [
					'create',
					'update',
				],
				dataToSend: [
					'autoMapInputData',
				],
			},
		},
		default: '',
		required: false,
		description: 'List of input properties to avoid sending, separated by commas. Leave empty to send all properties.',
		placeholder: 'Enter properties...',
	},
	{
		displayName: 'Fields to Send',
		name: 'fieldsUi',
		placeholder: 'Add Field',
		type: 'fixedCollection',
		typeOptions: {
			multipleValueButtonText: 'Add Field to Send',
			multipleValues: true,
		},
		displayOptions: {
			show: {
				resource: [
					'row',
				],
				operation: [
					'create',
					'update',
				],
				dataToSend: [
					'defineBelow',
				],
			},
		},
		default: {},
		options: [
			{
				displayName: 'Field',
				name: 'fieldValues',
				values: [
					{
						displayName: 'Field Name',
						name: 'fieldId',
						type: 'options',
						typeOptions: {
							loadOptionsDependsOn: [
								'tableId',
							],
							loadOptionsMethod: 'getTableColumns',
						},
						default: '',
					},
					{
						displayName: 'Field Value',
						name: 'fieldValue',
						type: 'string',
						default: '',
					},
				],
			},
		],
	},
	/* -------------------------------------------------------------------------- */
	/*                                row:delete                                  */
	/* -------------------------------------------------------------------------- */
	...getFilters(
		['row'],
		['delete'],
		{
			includeNoneOption: false,
			filterTypeDisplayName: 'Select Type',
			filterStringDisplayName: 'Select Condition (String)',
			filterFixedCollectionDisplayName: 'Select Conditions',
			mustMatchOptions: [
				{
					name: 'Any Select Condition',
					value: 'anyFilter',
				},
				{
					name: 'All Select Conditions',
					value: 'allFilters',
				},
		]}),
	/* -------------------------------------------------------------------------- */
	/*                                row:get                                     */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Select Conditions',
		name: 'filters',
		type: 'fixedCollection',
		typeOptions: {
			multipleValues: true,
		},
		displayOptions: {
			show: {
				resource: [
					'row',
				],
				operation: [
					'get',
				],
			},
		},
		default: '',
		placeholder: 'Add Condition',
		options: [
			{
				displayName: 'Conditions',
				name: 'conditions',
				values: [
					{
						displayName: 'Name',
						name: 'keyName',
						type: 'options',
						typeOptions: {
							loadOptionsDependsOn: [
								'tableId',
							],
							loadOptionsMethod: 'getTableColumns',
						},
						default: '',
						description: '',
					},
					{
						displayName: 'Value',
						name: 'keyValue',
						type: 'string',
						default: '',
					},
				],
			},
		],
	},
	/* -------------------------------------------------------------------------- */
	/*                                  row:getAll                                */
	/* -------------------------------------------------------------------------- */
	{
		displayName: 'Return All',
		name: 'returnAll',
		type: 'boolean',
		noDataExpression: true,
		displayOptions: {
			show: {
				resource: [
					'row',
				],
				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: [
					'row',
				],
				operation: [
					'getAll',
				],
				returnAll: [
					false,
				],
			},
		},
		typeOptions: {
			minValue: 1,
		},
		default: 50,
		description: 'How many results to return',
	},
	...getFilters(['row'], ['getAll'], {}),
];


import {
	IExecuteFunctions,
} from 'n8n-core';

import {
	IDataObject,
	INodeExecutionData,
	INodeType,
	INodeTypeDescription,
} from 'n8n-workflow';

import {
	convertNETDates,
	unleashedApiRequest,
	unleashedApiRequestAllItems,
} from './GenericFunctions';

import {
	salesOrderFields,
	salesOrderOperations,
} from './SalesOrderDescription';

import {
	stockOnHandFields,
	stockOnHandOperations,
} from './StockOnHandDescription';

import * as moment from 'moment';

export class UnleashedSoftware implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'Unleashed Software',
		name: 'unleashedSoftware',
		group: ['transform'],
		subtitle: '={{$parameter["operation"] + ":" + $parameter["resource"]}}',
		icon: 'file:unleashedSoftware.png',
		version: 1,
		description: 'Consume Unleashed Software API',
		defaults: {
			name: 'Unleashed Software',
		},
		inputs: ['main'],
		outputs: ['main'],
		credentials: [
			{
				name: 'unleashedSoftwareApi',
				required: true,
			},
		],
		properties: [
			{
				displayName: 'Resource',
				name: 'resource',
				type: 'options',
				options: [
					{
						name: 'Sales Order',
						value: 'salesOrder',
					},
					{
						name: 'Stock On Hand',
						value: 'stockOnHand',
					},
				],
				default: 'salesOrder',
				description: 'The resource to operate on.',
			},
			...salesOrderOperations,
			...salesOrderFields,

			...stockOnHandOperations,
			...stockOnHandFields,
		],
	};

	async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {

		const items = this.getInputData();
		const returnData: IDataObject[] = [];
		const length = items.length;
		const qs: IDataObject = {};
		let responseData;

		for (let i = 0; i < length; i++) {

			const resource = this.getNodeParameter('resource', 0) as string;
			const operation = this.getNodeParameter('operation', 0) as string;

			//https://apidocs.unleashedsoftware.com/SalesOrders
			if (resource === 'salesOrder') {

				if (operation === 'getAll') {

					const returnAll = this.getNodeParameter('returnAll', i) as boolean;
					const filters = this.getNodeParameter('filters', i) as IDataObject;

					if (filters.startDate) {
						filters.startDate = moment(filters.startDate as string).format('YYYY-MM-DD');
					}

					if (filters.endDate) {
						filters.endDate = moment(filters.endDate as string).format('YYYY-MM-DD');
					}

					if (filters.modifiedSince) {
						filters.modifiedSince = moment(filters.modifiedSince as string).format('YYYY-MM-DD');
					}

					if (filters.orderStatus) {
						filters.orderStatus = (filters.orderStatus as string[]).join(',');
					}

					Object.assign(qs, filters);

					if (returnAll) {
						responseData = await unleashedApiRequestAllItems.call(this, 'Items', 'GET', '/SalesOrders', {}, qs);
					} else {
						const limit = this.getNodeParameter('limit', i) as number;
						qs.pageSize = limit;
						responseData = await unleashedApiRequest.call(this, 'GET', `/SalesOrders`, {}, qs, 1);
						responseData = responseData.Items;
					}

					convertNETDates(responseData);
				}
			}

			//https://apidocs.unleashedsoftware.com/StockOnHand
			if (resource === 'stockOnHand') {

				if (operation === 'getAll') {
					const returnAll = this.getNodeParameter('returnAll', i) as boolean;

					const filters = this.getNodeParameter('filters', i) as IDataObject;

					if (filters.asAtDate) {
						filters.asAtDate = moment(filters.asAtDate as string).format('YYYY-MM-DD');
					}

					if (filters.modifiedSince) {
						filters.modifiedSince = moment(filters.modifiedSince as string).format('YYYY-MM-DD');
					}

					if (filters.orderBy) {
						filters.orderBy = (filters.orderBy as string).trim();
					}

					Object.assign(qs, filters);

					if (returnAll) {
						responseData = await unleashedApiRequestAllItems.call(this, 'Items', 'GET', '/StockOnHand', {}, qs);
					} else {
						const limit = this.getNodeParameter('limit', i) as number;
						qs.pageSize = limit;
						responseData = await unleashedApiRequest.call(this, 'GET', `/StockOnHand`, {}, qs, 1);
						responseData = responseData.Items;
					}

					convertNETDates(responseData);
				}

				if (operation === 'get') {
					const productId = this.getNodeParameter('productId', i) as string;
					responseData = await unleashedApiRequest.call(this, 'GET', `/StockOnHand/${productId}`);
					convertNETDates(responseData);
				}
			}

			if (Array.isArray(responseData)) {
				returnData.push.apply(returnData, responseData as IDataObject[]);
			} else {
				returnData.push(responseData as IDataObject);
			}
		}

		return [this.helpers.returnJsonArray(returnData)];
	}
}


`CREATE INDEX IF NOT EXISTS "IDX_${tablePrefixPure}d160d4771aba5a0d78943edbe3" ON ${tablePrefix}execution_entity ("workflowId", "id") `,

import { MigrationInterface, QueryRunner } from 'typeorm';
import * as config from '../../../../config';

export class AddExecutionEntityIndexes1644422880309 implements MigrationInterface {
	name = 'AddExecutionEntityIndexes1644422880309';

	public async up(queryRunner: QueryRunner): Promise<void> {
		let tablePrefix = config.get('database.tablePrefix');
		const tablePrefixPure = tablePrefix;
		const schema = config.get('database.postgresdb.schema');

		if (schema) {
			tablePrefix = schema + '.' + tablePrefix;
		}

		await queryRunner.query(
			`DROP INDEX "${schema}".IDX_${tablePrefixPure}c4d999a5e90784e8caccf5589d`,
		);
		await queryRunner.query(
			`DROP INDEX "${schema}".IDX_${tablePrefixPure}ca4a71b47f28ac6ea88293a8e2`,
		);
		await queryRunner.query(
			`CREATE INDEX "IDX_${tablePrefixPure}33228da131bb1112247cf52a42" ON ${tablePrefix}execution_entity ("stoppedAt") `,
		);
		await queryRunner.query(
			`CREATE INDEX "IDX_${tablePrefixPure}58154df94c686818c99fb754ce" ON ${tablePrefix}execution_entity ("workflowId", "waitTill", "id") `,
		);
		await queryRunner.query(
			`CREATE INDEX "IDX_${tablePrefixPure}4f474ac92be81610439aaad61e" ON ${tablePrefix}execution_entity ("workflowId", "finished", "id") `,
		);
		await queryRunner.query(
			`CREATE INDEX "IDX_${tablePrefixPure}72ffaaab9f04c2c1f1ea86e662" ON ${tablePrefix}execution_entity ("finished", "id") `,
		);
		await queryRunner.query(
			`CREATE INDEX "IDX_${tablePrefixPure}85b981df7b444f905f8bf50747" ON ${tablePrefix}execution_entity ("waitTill", "id") `,
		);
		await queryRunner.query(
			`CREATE INDEX "IDX_${tablePrefixPure}d160d4771aba5a0d78943edbe3" ON ${tablePrefix}execution_entity ("workflowId", "id") `,
		);
	}

	public async down(queryRunner: QueryRunner): Promise<void> {
		let tablePrefix = config.get('database.tablePrefix');
		const tablePrefixPure = tablePrefix;
		const schema = config.get('database.postgresdb.schema');

		if (schema) {
			tablePrefix = schema + '.' + tablePrefix;
		}

		await queryRunner.query(
			`DROP INDEX "${schema}"."IDX_${tablePrefixPure}d160d4771aba5a0d78943edbe3"`,
		);
		await queryRunner.query(
			`DROP INDEX "${schema}"."IDX_${tablePrefixPure}85b981df7b444f905f8bf50747"`,
		);
		await queryRunner.query(
			`DROP INDEX "${schema}"."IDX_${tablePrefixPure}72ffaaab9f04c2c1f1ea86e662"`,
		);
		await queryRunner.query(
			`DROP INDEX "${schema}"."IDX_${tablePrefixPure}4f474ac92be81610439aaad61e"`,
		);
		await queryRunner.query(
			`DROP INDEX "${schema}"."IDX_${tablePrefixPure}58154df94c686818c99fb754ce"`,
		);
		await queryRunner.query(
			`DROP INDEX "${schema}"."IDX_${tablePrefixPure}33228da131bb1112247cf52a42"`,
		);
		await queryRunner.query(
			`CREATE INDEX "IDX_${tablePrefixPure}ca4a71b47f28ac6ea88293a8e2" ON ${tablePrefix}execution_entity ("waitTill") `,
		);
		await queryRunner.query(
			`CREATE INDEX "IDX_${tablePrefixPure}c4d999a5e90784e8caccf5589d" ON ${tablePrefix}execution_entity ("workflowId") `,
		);
	}
}


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

export const logOperations: INodeProperties[] = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		required: true,
		default: 'getAll',
		displayOptions: {
			show: {
				resource: [
					'log',
				],
			},
		},
		options: [
			{
				name: 'Create',
				value: 'create',
				description: 'Create task log',
			},
			{
				name: 'Execute Responder',
				value: 'executeResponder',
				description: 'Execute a responder on a selected log',
			},
			{
				name: 'Get All',
				value: 'getAll',
				description: 'Get all task logs',
			},
			{
				name: 'Get',
				value: 'get',
				description: 'Get a single log',
			},
		],
	},
];

export const logFields: INodeProperties[] = [
	{
		displayName: 'Task ID',
		name: 'taskId',
		type: 'string',
		required: true,
		default: '',
		displayOptions: {
			show: {
				resource: [
					'log',
				],
				operation: [
					'create',
					'getAll',
				],
			},
		},
		description: 'ID of the task',
	},
	{
		displayName: 'Return All',
		name: 'returnAll',
		type: 'boolean',
		displayOptions: {
			show: {
				operation: [
					'getAll',
				],
				resource: [
					'log',
				],
			},
		},
		default: false,
		description: 'If all results should be returned or only up to a given limit.',
	},
	{
		displayName: 'Limit',
		name: 'limit',
		type: 'number',
		displayOptions: {
			show: {
				operation: [
					'getAll',
				],
				resource: [
					'log',
				],
				returnAll: [
					false,
				],
			},
		},
		typeOptions: {
			minValue: 1,
			maxValue: 500,
		},
		default: 100,
		description: 'How many results to return.',
	},
	// required attributs
	{
		displayName: 'Log ID',
		name: 'id',
		type: 'string',
		default: '',
		required: true,
		displayOptions: {
			show: {
				resource: [
					'log',
				],
				operation: [
					'executeResponder',
					'get',
				],
			},
		},
	},
	{
		displayName: 'Message',
		name: 'message',
		type: 'string',
		required: true,
		default: '',
		displayOptions: {
			show: {
				resource: [
					'log',
				],
				operation: [
					'create',
				],
			},
		},
		description: 'Content of the Log',
	},
	{
		displayName: 'Start Date',
		name: 'startDate',
		type: 'dateTime',
		required: true,
		default: '',
		displayOptions: {
			show: {
				resource: [
					'log',
				],
				operation: [
					'create',
				],
			},
		},
		description: 'Date of the log submission default=now',
	},
	{
		displayName: 'Status',
		name: 'status',
		type: 'options',
		options: [
			{
				name: 'Ok',
				value: 'Ok',
			},
			{
				name: 'Deleted',
				value: 'Deleted',
			},
		],
		default: '',
		required: true,
		displayOptions: {
			show: {
				resource: [
					'log',
				],
				operation: [
					'create',
				],
			},
		},
		description: 'Status of the log (Ok or Deleted) default=Ok',
	},
	// required for responder execution
	{
		displayName: 'Responder ID',
		name: 'responder',
		type: 'options',
		required: true,
		default: '',
		typeOptions: {
			loadOptionsDependsOn: [
				'id',
			],
			loadOptionsMethod: 'loadResponders',
		},
		displayOptions: {
			show: {
				resource: [
					'log',
				],
				operation: [
					'executeResponder',
				],
			},
			hide: {
				id: [
					'',
				],
			},
		},
	},
	// Optional attributs
	{
		displayName: 'Options',
		name: 'options',
		type: 'collection',
		default: {},
		displayOptions: {
			show: {
				resource: [
					'log',
				],
				operation: [
					'create',
				],
			},
		},
		placeholder: 'Add Option',
		options: [
			{
				displayName: 'Attachment',
				name: 'attachmentValues',
				placeholder: 'Add Attachment',
				type: 'fixedCollection',
				typeOptions: {
					multipleValues: false,
				},
				default: {},
				options: [
					{
						displayName: 'Attachment',
						name: 'attachmentValues',
						values: [
							{
								displayName: 'Binary Property',
								name: 'binaryProperty',
								type: 'string',
								default: 'data',
								description: 'Object property name which holds binary data.',
							},
						],
					},
				],
				description: 'File attached to the log',
			},
		],
	},
];


@use "./common/var";
@use "mixins/mixins";
@use "mixins/_button";
@use "mixins/utils";

@include mixins.b(checkbox) {
	color: var.$checkbox-font-color;
	font-size: var.$font-size-base;
	position: relative;
	cursor: pointer;
	display: inline-block;
	white-space: nowrap;
	user-select: none;
	margin-right: 30px;

	@include mixins.when(bordered) {
		padding: var.$checkbox-bordered-padding;
		border-radius: var(--border-radius-base);
		border: var(--border-base);
		box-sizing: border-box;
		line-height: normal;
		height: var.$checkbox-bordered-height;

		&.is-checked {
			border-color: var(--color-primary);
		}

		&.is-disabled {
			border-color: var(--border-color-light);
			cursor: not-allowed;
		}

		& + .el-checkbox.is-bordered {
			margin-left: 10px;
		}

		&.el-checkbox--medium {
			padding: var.$checkbox-bordered-medium-padding;
			border-radius: var.$button-medium-border-radius;
			height: var.$checkbox-bordered-medium-height;

			.el-checkbox__label {
				line-height: 17px;
				font-size: var.$button-medium-font-size;
			}

			.el-checkbox__inner {
				height: var.$checkbox-bordered-medium-input-height;
				width: var.$checkbox-bordered-medium-input-width;
			}
		}

		&.el-checkbox--small {
			padding: var.$checkbox-bordered-small-padding;
			border-radius: var.$button-small-border-radius;
			height: var.$checkbox-bordered-small-height;

			.el-checkbox__label {
				line-height: 15px;
				font-size: var.$button-small-font-size;
			}

			.el-checkbox__inner {
				height: var.$checkbox-bordered-small-input-height;
				width: var.$checkbox-bordered-small-input-width;

				&::after {
					height: 6px;
					width: 2px;
				}
			}
		}

		&.el-checkbox--mini {
			padding: var.$checkbox-bordered-mini-padding;
			border-radius: var.$button-mini-border-radius;
			height: var.$checkbox-bordered-mini-height;

			.el-checkbox__label {
				line-height: 12px;
				font-size: var.$button-mini-font-size;
			}

			.el-checkbox__inner {
				height: var.$checkbox-bordered-mini-input-height;
				width: var.$checkbox-bordered-mini-input-width;
				&::after {
					height: 6px;
					width: 2px;
				}
			}
		}
	}

	@include mixins.e(input) {
		white-space: nowrap;
		cursor: pointer;
		outline: none;
		display: inline-block;
		line-height: 1;
		position: relative;
		vertical-align: middle;

		@include mixins.when(disabled) {
			.el-checkbox__inner {
				background-color: var.$checkbox-disabled-input-fill;
				border-color: var.$checkbox-disabled-border-color;
				cursor: not-allowed;

				&::after {
					cursor: not-allowed;
					border-color: var.$checkbox-disabled-icon-color;
				}

				& + .el-checkbox__label {
					cursor: not-allowed;
				}
			}

			&.is-checked {
				.el-checkbox__inner {
					background-color: var.$checkbox-disabled-checked-input-fill;
					border-color: var.$checkbox-disabled-checked-input-border-color;

					&::after {
						border-color: var.$checkbox-disabled-checked-icon-color;
					}
				}
			}

			&.is-indeterminate {
				.el-checkbox__inner {
					background-color: var.$checkbox-disabled-checked-input-fill;
					border-color: var.$checkbox-disabled-checked-input-border-color;

					&::before {
						background-color: var.$checkbox-disabled-checked-icon-color;
						border-color: var.$checkbox-disabled-checked-icon-color;
					}
				}
			}

			& + span.el-checkbox__label {
				color: var.$disabled-color-base;
				cursor: not-allowed;
			}
		}

		@include mixins.when(checked) {
			.el-checkbox__inner {
				background-color: var.$checkbox-checked-background-color;
				border-color: var.$checkbox-checked-input-border-color;

				&::after {
					transform: rotate(45deg) scaleY(1);
				}
			}
		}
		@include mixins.when(focus) {
			/*focus时 视觉上区分*/
			.el-checkbox__inner {
				border-color: var.$checkbox-input-border-color-hover;
			}
		}
		@include mixins.when(indeterminate) {
			.el-checkbox__inner {
				background-color: var.$checkbox-checked-background-color;
				border-color: var.$checkbox-checked-input-border-color;

				&::before {
					content: '';
					position: absolute;
					display: block;
					background-color: var.$checkbox-checked-icon-color;
					height: 2px;
					transform: scale(0.5);
					left: 0;
					right: 0;
					top: 5px;
				}

				&::after {
					display: none;
				}
			}
		}
	}
	@include mixins.e(inner) {
		display: inline-block;
		position: relative;
		border: var.$checkbox-input-border;
		border-radius: var.$checkbox-border-radius;
		box-sizing: border-box;
		width: var.$checkbox-input-width;
		height: var.$checkbox-input-height;
		background-color: var.$checkbox-background-color;
		z-index: var.$index-normal;
		transition: border-color 0.25s cubic-bezier(0.71, -0.46, 0.29, 1.46),
			background-color 0.25s cubic-bezier(0.71, -0.46, 0.29, 1.46);

		&:hover {
			border-color: var.$checkbox-input-border-color-hover;
		}

		&::after {
			box-sizing: content-box;
			content: '';
			border: 1px solid var.$checkbox-checked-icon-color;
			border-left: 0;
			border-top: 0;
			height: 7px;
			left: 4px;
			position: absolute;
			top: 1px;
			transform: rotate(45deg) scaleY(0);
			width: 3px;
			transition: transform 0.15s ease-in 0.05s;
			transform-origin: center;
		}
	}

	@include mixins.e(original) {
		opacity: 0;
		outline: none;
		position: absolute;
		margin: 0;
		width: 0;
		height: 0;
		z-index: -1;
	}

	@include mixins.e(label) {
		display: inline-block;
		padding-left: 10px;
		line-height: 19px;
		font-size: var.$checkbox-font-size;
	}

	&:last-of-type {
		margin-right: 0;
	}
}

@include mixins.b(checkbox-button) {
	position: relative;
	display: inline-block;

	@include mixins.e(inner) {
		display: inline-block;
		line-height: 1;
		white-space: nowrap;
		vertical-align: middle;
		cursor: pointer;
		background: var.$button-default-background-color;
		border: var(--border-base);
		border-left: 0;
		color: var.$button-default-font-color;
		-webkit-appearance: none;
		text-align: center;
		box-sizing: border-box;
		outline: none;
		margin: 0;
		position: relative;
		transition: var.$all-transition;
		border-radius: 0;
		@include utils.utils-user-select(none);

		@include button.button-size(
			var.$button-padding-vertical,
			var.$button-padding-horizontal,
			var.$button-font-size
		);

		&:hover {
			color: var(--color-primary);
		}

		& [class*='el-icon-'] {
			line-height: 0.9;

			& + span {
				margin-left: 5px;
			}
		}
	}

	@include mixins.e(original) {
		opacity: 0;
		outline: none;
		position: absolute;
		margin: 0;
		z-index: -1;
	}

	&.is-checked {
		& .el-checkbox-button__inner {
			color: var.$checkbox-button-checked-font-color;
			background-color: var.$checkbox-button-checked-background-color;
			border-color: var.$checkbox-button-checked-border-color;
			box-shadow: -1px 0 0 0 var.$color-primary-light-4;
		}
		&:first-child .el-checkbox-button__inner {
			border-left-color: var.$checkbox-button-checked-border-color;
		}
	}

	&.is-disabled {
		& .el-checkbox-button__inner {
			color: var.$button-disabled-font-color;
			cursor: not-allowed;
			background-image: none;
			background-color: var.$button-disabled-background-color;
			border-color: var.$button-disabled-border-color;
			box-shadow: none;
		}
		&:first-child .el-checkbox-button__inner {
			border-left-color: var.$button-disabled-border-color;
		}
	}

	&:first-child {
		.el-checkbox-button__inner {
			border-left: var(--border-base);
			border-radius: var(--border-radius-base) 0 0 var(--border-radius-base);
			box-shadow: none !important;
		}
	}

	&.is-focus {
		& .el-checkbox-button__inner {
			border-color: var.$checkbox-button-checked-border-color;
		}
	}

	&:last-child {
		.el-checkbox-button__inner {
			border-radius: 0 var(--border-radius-base) var(--border-radius-base) 0;
		}
	}
	@include mixins.m(medium) {
		.el-checkbox-button__inner {
			border-radius: 0;
			@include button.button-size(
				var.$button-medium-padding-vertical,
				var.$button-medium-padding-horizontal,
				var.$button-medium-font-size
			);
		}
	}
	@include mixins.m(small) {
		.el-checkbox-button__inner {
			border-radius: 0;
			@include button.button-size(
				var.$button-small-padding-vertical,
				var.$button-small-padding-horizontal,
				var.$button-small-font-size
			);
		}
	}
	@include mixins.m(mini) {
		.el-checkbox-button__inner {
			border-radius: 0;
			@include button.button-size(
				var.$button-mini-padding-vertical,
				var.$button-mini-padding-horizontal,
				var.$button-mini-font-size
			);
		}
	}
}

@include mixins.b(checkbox-group) {
	font-size: 0;
}


<template>
	<div :class="$style.container">
		<banner
			v-show="showValidationWarning"
			theme="danger"
			:message="$locale.baseText('credentialEdit.credentialConfig.pleaseCheckTheErrorsBelow')"
		/>

		<banner
			v-if="authError && !showValidationWarning"
			theme="danger"
			:message="$locale.baseText('credentialEdit.credentialConfig.couldntConnectWithTheseSettings')"
			:details="authError"
			:buttonLabel="$locale.baseText('credentialEdit.credentialConfig.retry')"
			buttonLoadingLabel="Retrying"
			:buttonTitle="$locale.baseText('credentialEdit.credentialConfig.retryCredentialTest')"
			:buttonLoading="isRetesting"
			@click="$emit('retest')"
		/>

		<banner
			v-show="showOAuthSuccessBanner && !showValidationWarning"
			theme="success"
			:message="$locale.baseText('credentialEdit.credentialConfig.accountConnected')"
			:buttonLabel="$locale.baseText('credentialEdit.credentialConfig.reconnect')"
			:buttonTitle="$locale.baseText('credentialEdit.credentialConfig.reconnectOAuth2Credential')"
			@click="$emit('oauth')"
		/>

		<banner
			v-show="testedSuccessfully && !showValidationWarning"
			theme="success"
			:message="$locale.baseText('credentialEdit.credentialConfig.connectionTestedSuccessfully')"
			:buttonLabel="$locale.baseText('credentialEdit.credentialConfig.retry')"
			:buttonLoadingLabel="$locale.baseText('credentialEdit.credentialConfig.retrying')"
			:buttonTitle="$locale.baseText('credentialEdit.credentialConfig.retryCredentialTest')"
			:buttonLoading="isRetesting"
			@click="$emit('retest')"
		/>

		<n8n-info-tip v-if="documentationUrl && credentialProperties.length">
			{{ $locale.baseText('credentialEdit.credentialConfig.needHelpFillingOutTheseFields') }}
			<n8n-link :to="documentationUrl" size="small" :bold="true" @click="onDocumentationUrlClick">
				{{ $locale.baseText('credentialEdit.credentialConfig.openDocs') }}
			</n8n-link>
		</n8n-info-tip>

		<CopyInput
			v-if="isOAuthType && credentialProperties.length"
			:label="$locale.baseText('credentialEdit.credentialConfig.oAuthRedirectUrl')"
			:copyContent="oAuthCallbackUrl"
			:copyButtonText="$locale.baseText('credentialEdit.credentialConfig.clickToCopy')"
			:subtitle="$locale.baseText('credentialEdit.credentialConfig.subtitle', { interpolate: { appName } })"
			:successMessage="$locale.baseText('credentialEdit.credentialConfig.redirectUrlCopiedToClipboard')"
		/>

		<CredentialInputs
			v-if="credentialType"
			:credentialData="credentialData"
			:credentialProperties="credentialProperties"
			:documentationUrl="documentationUrl"
			:showValidationWarnings="showValidationWarning"
			@change="onDataChange"
		/>

		<OauthButton
			v-if="isOAuthType && requiredPropertiesFilled && !isOAuthConnected"
			:isGoogleOAuthType="isGoogleOAuthType"
			@click="$emit('oauth')"
		/>
	</div>
</template>

<script lang="ts">
import { ICredentialType, INodeTypeDescription } from 'n8n-workflow';
import { getAppNameFromCredType } from '../helpers';

import Vue from 'vue';
import Banner from '../Banner.vue';
import CopyInput from '../CopyInput.vue';
import CredentialInputs from './CredentialInputs.vue';
import OauthButton from './OauthButton.vue';
import { restApi } from '@/components/mixins/restApi';
import { addCredentialTranslation } from '@/plugins/i18n';
import mixins from 'vue-typed-mixins';

export default mixins(restApi).extend({
	name: 'CredentialConfig',
	components: {
		Banner,
		CopyInput,
		CredentialInputs,
		OauthButton,
	},
	props: {
		credentialType: {
			type: Object,
		},
		credentialProperties: {
			type: Array,
		},
		parentTypes: {
			type: Array,
		},
		credentialData: {
		},
		showValidationWarning: {
			type: Boolean,
			default: false,
		},
		authError: {
			type: String,
		},
		testedSuccessfully: {
			type: Boolean,
		},
		isOAuthType: {
			type: Boolean,
		},
		isOAuthConnected: {
			type: Boolean,
		},
		isRetesting: {
			type: Boolean,
		},
		requiredPropertiesFilled: {
			type: Boolean,
		},
	},
	async beforeMount() {
		if (this.$store.getters.defaultLocale === 'en') return;

		this.$store.commit('setActiveCredentialType', this.credentialType.name);

		const key = `n8n-nodes-base.credentials.${this.credentialType.name}`;

		if (this.$locale.exists(key)) return;

		const credTranslation = await this.restApi().getCredentialTranslation(this.credentialType.name);

		addCredentialTranslation(
			{ [this.credentialType.name]: credTranslation },
			this.$store.getters.defaultLocale,
		);
	},
	computed: {
		appName(): string {
			if (!this.credentialType) {
				return '';
			}

			const appName = getAppNameFromCredType(
				(this.credentialType as ICredentialType).displayName,
			);

			return appName || this.$locale.baseText('credentialEdit.credentialConfig.theServiceYouReConnectingTo');
		},
		credentialTypeName(): string {
			return (this.credentialType as ICredentialType).name;
		},
		documentationUrl(): string {
			const type = this.credentialType as ICredentialType;

			if (!type || !type.documentationUrl) {
				return '';
			}

			if (type.documentationUrl.startsWith('https://') || type.documentationUrl.startsWith('http://')) {
				return type.documentationUrl;
			}

			return `https://docs.n8n.io/credentials/${type.documentationUrl}/?utm_source=n8n_app&utm_medium=left_nav_menu&utm_campaign=create_new_credentials_modal`;
		},
		isGoogleOAuthType(): boolean {
			return this.credentialTypeName === 'googleOAuth2Api' || this.parentTypes.includes('googleOAuth2Api');
		},
		oAuthCallbackUrl(): string {
			const oauthType =
				this.credentialTypeName === 'oAuth2Api' ||
				this.parentTypes.includes('oAuth2Api')
					? 'oauth2'
					: 'oauth1';
			return this.$store.getters.oauthCallbackUrls[oauthType];
		},
		showOAuthSuccessBanner(): boolean {
			return this.isOAuthType && this.requiredPropertiesFilled && this.isOAuthConnected && !this.authError;
		},
	},
	methods: {
		/**
		 * Get the current version for a node type.
		 */
		async getCurrentNodeVersion(targetNodeType: string) {
			const { allNodeTypes }: { allNodeTypes: INodeTypeDescription[] } = this.$store.getters;
			const found = allNodeTypes.find(nodeType => nodeType.name === targetNodeType);

			return found ? found.version : 1;
		},

		onDataChange (event: { name: string; value: string | number | boolean | Date | null }): void {
			this.$emit('change', event);
		},
		onDocumentationUrlClick (): void {
			this.$telemetry.track('User clicked credential modal docs link', {
				docs_link: this.documentationUrl,
				credential_type: this.credentialTypeName,
				source: 'modal',
				workflow_id: this.$store.getters.workflowId,
			});
		},
	},
	watch: {
		showOAuthSuccessBanner(newValue, oldValue) {
			if (newValue && !oldValue) {
				this.$emit('scrollToTop');
			}
		},
	},
});
</script>

<style lang="scss" module>
.container {
	> * {
		margin-bottom: var(--spacing-l);
	}
}

</style>


const tablePrefix = config.get('database.tablePrefix');

await queryRunner.query(`DROP INDEX IF EXISTS 'IDX_${tablePrefix}c4d999a5e90784e8caccf5589d'`);

await queryRunner.query(`DROP INDEX 'IDX_${tablePrefix}ca4a71b47f28ac6ea88293a8e2'`);

await queryRunner.query(`DROP INDEX IF EXISTS 'IDX_${tablePrefix}ca4a71b47f28ac6ea88293a8e2'`);