keep keep keep replace keep keep keep keep keep

"use strict";

const { EOL } = require("os");
const path = require("path");

// mocked modules
const tempWrite = require("temp-write");
const ChildProcessUtilities = require("../src/ChildProcessUtilities");

// file under test
const GitUtilities = require("../src/GitUtilities");

jest.mock("temp-write");
jest.mock("../src/ChildProcessUtilities");

describe("GitUtilities", () => {
  afterEach(() => jest.resetAllMocks());

  describe(".isDetachedHead()", () => {
    const originalGetCurrentBranch = GitUtilities.getCurrentBranch;
    afterEach(() => {
      GitUtilities.getCurrentBranch = originalGetCurrentBranch;
    });

    it("returns true when branchName is HEAD", () => {
      GitUtilities.getCurrentBranch = jest.fn(() => "HEAD");
      expect(GitUtilities.isDetachedHead()).toBe(true);
    });

    it("returns false when branchName is not HEAD", () => {
      GitUtilities.getCurrentBranch = jest.fn(() => "master");
      expect(GitUtilities.isDetachedHead()).toBe(false);
    });

    it("passes opts to getCurrentBranch()", () => {
      GitUtilities.getCurrentBranch = jest.fn();
      const opts = { cwd: "test" };
      GitUtilities.isDetachedHead(opts);
      expect(GitUtilities.getCurrentBranch).lastCalledWith(opts);
    });
  });

  describe(".isInitialized()", () => {
    it("returns true when git command succeeds", () => {
      expect(GitUtilities.isInitialized({ cwd: "test" })).toBe(true);
      expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["rev-parse"], {
        cwd: "test",
        stdio: "ignore",
      });
    });

    it("returns false when git command fails", () => {
      ChildProcessUtilities.execSync.mockImplementation(() => {
        throw new Error("fatal: Not a git repository");
      });
      expect(() => GitUtilities.isInitialized()).not.toThrow();
      expect(GitUtilities.isInitialized()).toBe(false);
    });
  });

  describe(".addFile()", () => {
    it("calls git add with file argument", () => {
      const opts = { cwd: "test" };
      GitUtilities.addFile("foo", opts);
      expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["add", "foo"], opts);
    });
    it("works with absolute path for cwd", () => {
      const cwd = path.resolve("test");
      const file = "foo";
      const opts = { cwd };
      GitUtilities.addFile(file, opts);
      expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["add", "foo"], opts);
    });
    it("works with absolute paths for file and cwd", () => {
      const cwd = path.resolve("test");
      const file = path.resolve(cwd, "foo");
      const opts = { cwd };
      GitUtilities.addFile(file, opts);
      expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["add", "foo"], opts);
    });
    it("uses a POSIX path in the Git command, given a Windows file path", () => {
      const opts = { cwd: "test" };
      GitUtilities.addFile("foo\\bar", opts);
      expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["add", "foo/bar"], opts);
    });
  });

  describe(".commit()", () => {
    it("calls git commit with message", () => {
      const opts = { cwd: "oneline" };
      GitUtilities.commit("foo", opts);

      expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["commit", "-m", "foo"], opts);
      expect(tempWrite.sync).not.toBeCalled();
    });

    it("allows multiline message", () => {
      tempWrite.sync = jest.fn(() => "TEMPFILE");

      const opts = { cwd: "multiline" };
      GitUtilities.commit(`foo${EOL}bar`, opts);

      expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["commit", "-F", "TEMPFILE"], opts);
      expect(tempWrite.sync).lastCalledWith(`foo${EOL}bar`, "lerna-commit.txt");
    });
  });

  describe(".addTag()", () => {
    it("creates annotated git tag", () => {
      const opts = { cwd: "test" };
      GitUtilities.addTag("foo", opts);
      expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["tag", "foo", "-m", "foo"], opts);
    });
  });

  describe(".removeTag()", () => {
    it("deletes specified git tag", () => {
      const opts = { cwd: "test" };
      GitUtilities.removeTag("foo", opts);
      expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["tag", "-d", "foo"], opts);
    });
  });

  describe(".hasTags()", () => {
    it("returns true when one or more git tags exist", () => {
      ChildProcessUtilities.execSync.mockImplementation(() => "v1.0.0");
      const opts = { cwd: "test" };
      expect(GitUtilities.hasTags(opts)).toBe(true);
      expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["tag"], opts);
    });

    it("returns false when no git tags exist", () => {
      ChildProcessUtilities.execSync.mockImplementation(() => "");
      expect(GitUtilities.hasTags()).toBe(false);
    });
  });

  describe(".getLastTaggedCommit()", () => {
    it("returns SHA of closest git tag", () => {
      ChildProcessUtilities.execSync.mockImplementation(() => "deadbeef");
      const opts = { cwd: "test" };
      expect(GitUtilities.getLastTaggedCommit(opts)).toBe("deadbeef");
      expect(ChildProcessUtilities.execSync).lastCalledWith(
        "git",
        ["rev-list", "--tags", "--max-count=1"],
        opts
      );
    });
  });

  describe(".getLastTaggedCommitInBranch()", () => {
    const getLastTagOriginal = GitUtilities.getLastTag;
    afterEach(() => {
      GitUtilities.getLastTag = getLastTagOriginal;
    });

    it("returns SHA of closest git tag in branch", () => {
      GitUtilities.getLastTag = jest.fn(() => "v1.0.0");
      ChildProcessUtilities.execSync.mockImplementation(() => "deadbeef");
      const opts = { cwd: "test" };
      expect(GitUtilities.getLastTaggedCommitInBranch(opts)).toBe("deadbeef");
      expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["rev-list", "-n", "1", "v1.0.0"], opts);
    });
  });

  describe(".getFirstCommit()", () => {
    it("returns SHA of first commit", () => {
      ChildProcessUtilities.execSync.mockImplementation(() => "beefcafe");
      const opts = { cwd: "test" };
      expect(GitUtilities.getFirstCommit(opts)).toBe("beefcafe");
      expect(ChildProcessUtilities.execSync).lastCalledWith(
        "git",
        ["rev-list", "--max-parents=0", "HEAD"],
        opts
      );
    });
  });

  describe(".pushWithTags()", () => {
    const getCurrentBranchOriginal = GitUtilities.getCurrentBranch;
    afterEach(() => {
      GitUtilities.getCurrentBranch = getCurrentBranchOriginal;
    });

    it("pushes current branch and specified tag(s) to origin", () => {
      GitUtilities.getCurrentBranch = jest.fn(() => "master");
      const opts = { cwd: "test" };
      GitUtilities.pushWithTags("origin", ["foo@1.0.1", "foo-bar@1.0.0"], opts);
      expect(ChildProcessUtilities.execSync).toBeCalledWith("git", ["push", "origin", "master"], opts);
      expect(ChildProcessUtilities.execSync).lastCalledWith(
        "git",
        ["push", "origin", "foo@1.0.1", "foo-bar@1.0.0"],
        opts
      );
    });
  });

  describe(".getLastTag()", () => {
    it("returns the closest tag", () => {
      ChildProcessUtilities.execSync.mockImplementation(() => "v1.0.0");
      const opts = { cwd: "test" };
      expect(GitUtilities.getLastTag(opts)).toBe("v1.0.0");
      expect(ChildProcessUtilities.execSync).lastCalledWith(
        "git",
        ["describe", "--tags", "--abbrev=0"],
        opts
      );
    });
  });

  describe(".describeTag()", () => {
    it("returns description of specified tag", () => {
      ChildProcessUtilities.execSync.mockImplementation(() => "foo@1.0.0");
      const opts = { cwd: "test" };
      expect(GitUtilities.describeTag("deadbeef", opts)).toBe("foo@1.0.0");
      expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["describe", "--tags", "deadbeef"], opts);
    });
  });

  describe(".diffSinceIn()", () => {
    it("returns list of files changed since commit at location", () => {
      ChildProcessUtilities.execSync.mockImplementation(() => "files");
      const opts = { cwd: path.resolve(__dirname, "./..") };
      expect(GitUtilities.diffSinceIn("foo@1.0.0", "packages/foo", opts)).toBe("files");
      expect(ChildProcessUtilities.execSync).lastCalledWith(
        "git",
        ["diff", "--name-only", "foo@1.0.0", "--", "packages/foo"],
        opts
      );
    });
  });

  describe(".getWorkspaceRoot()", () => {
    it("calls `git rev-parse --show-toplevel`", () => {
      ChildProcessUtilities.execSync.mockImplementation(() => "master");
      const opts = { cwd: "test" };
      expect(GitUtilities.getWorkspaceRoot(opts)).toBe("master");
      expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["rev-parse", "--show-toplevel"], opts);
    });
  });

  describe(".getCurrentBranch()", () => {
    it("calls `git rev-parse --abbrev-ref HEAD`", () => {
      ChildProcessUtilities.execSync.mockImplementation(() => "master");
      const opts = { cwd: "test" };
      expect(GitUtilities.getCurrentBranch(opts)).toBe("master");
      expect(ChildProcessUtilities.execSync).lastCalledWith(
        "git",
        ["rev-parse", "--abbrev-ref", "HEAD"],
        opts
      );
    });
  });

  describe(".getCurrentSHA()", () => {
    it("returns SHA of current ref", () => {
      ChildProcessUtilities.execSync.mockImplementation(() => "deadcafe");
      const opts = { cwd: "test" };
      expect(GitUtilities.getCurrentSHA(opts)).toBe("deadcafe");
      expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["rev-parse", "HEAD"], opts);
    });
  });

  describe(".checkoutChanges()", () => {
    it("calls git checkout with specified arg", () => {
      const opts = { cwd: "test" };
      GitUtilities.checkoutChanges("packages/*/package.json", opts);
      expect(ChildProcessUtilities.execSync).lastCalledWith(
        "git",
        ["checkout", "--", "packages/*/package.json"],
        opts
      );
    });
  });

  describe(".init()", () => {
    it("calls git init", () => {
      const opts = { cwd: "test" };
      GitUtilities.init(opts);
      expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["init"], opts);
    });
  });

  describe(".hasCommit()", () => {
    it("returns true when git command succeeds", () => {
      const opts = { cwd: "test" };
      expect(GitUtilities.hasCommit(opts)).toBe(true);
      expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["log"], opts);
    });

    it("returns false when git command fails", () => {
      ChildProcessUtilities.execSync.mockImplementation(() => {
        throw new Error("fatal: your current branch 'master' does not have any commits yet");
      });
      expect(GitUtilities.hasCommit()).toBe(false);
    });
  });
});


# OS Specific
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# IDE Specific
nbproject
.~lock.*
.buildpath
.idea
.project
.settings
.vscode
composer.lock
*.sublime-workspace
*.swp
*.swo

# Project Specific
node_modules
*.log
lib
tmp
.changelog
coverage/
.nyc_output/
.eslintcache
*.tgz


"use strict";

const msg = require("@integration/package-1");

module.exports = `package-2 ${msg}`;


expect(ChildProcessUtilities.execSync).lastCalledWith("git", ["commit", "-F", "TEMPFILE"], opts);

keep keep keep keep replace keep keep keep keep keep

["commit", "--no-verify", "-F", "TEMPFILE"],


import Quill, { DeltaOperation } from 'quill';

import AutoFormat, { AutoformatHelperAttribute } from 'quill-autoformat';

keep keep keep keep keep replace keep keep keep

import AutoFormat from 'quill-autoformat';


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

export class TwilioApi implements ICredentialType {
	name = 'twilioApi';
	displayName = 'Twilio API';
	documentationUrl = 'twilio';
	properties = [
		{
			displayName: 'Account SID',
			name: 'accountSid',
			type: 'string' as NodePropertyTypes,
			default: '',
		},
		{
			displayName: 'Auth Token',
			name: 'authToken',
			type: 'string' as NodePropertyTypes,
			default: '',
		},
	];
}


import {
NodeParameterValue,
Workflow,
WorkflowDataProxy,
} from 'n8n-workflow';
import {

<template>

	<expression-edit :dialogVisible="expressionEditDialogVisible" :value="value" :parameter="parameter" :path="path" @closeDialog="closeExpressionEditDialog" @valueChanged="expressionUpdated"></expression-edit>

			<text-edit :dialogVisible="textEditDialogVisible" :value="value" :parameter="parameter" @closeDialog="closeTextEditDialog" @valueChanged="expressionUpdated"></text-edit>

				<prism-editor v-if="!codeEditDialogVisible" :lineNumbers="true" :readonly="true" :code="displayValue" language="js"></prism-editor>

			<el-input v-else v-model="tempValue" ref="inputField" size="small" :type="getStringInputType" :rows="getArgument('rows')" :value="displayValue" :disabled="isReadOnly" @change="valueChanged" @keydown.stop @focus="setFocus" :title="displayTitle" :placeholder="isValueExpression?'':parameter.placeholder">

			</el-input>

			<el-date-picker
				v-model="tempValue"
				ref="inputField"
				type="datetime"
				size="small"
				:value="displayValue"
				:title="displayTitle"
				:disabled="isReadOnly"
				:placeholder="parameter.placeholder?parameter.placeholder:'Select date and time'"
				:picker-options="dateTimePickerOptions"
				@change="valueChanged"
				@focus="setFocus"
				@keydown.stop
			>
			</el-date-picker>

			<el-input-number ref="inputField" size="small" :value="displayValue" :max="getArgument('maxValue')" :min="getArgument('minValue')" :precision="getArgument('numberPrecision')" :step="getArgument('numberStepSize')" :disabled="isReadOnly" @change="valueChanged" @focus="setFocus" @keydown.stop :title="displayTitle" :placeholder="parameter.placeholder"></el-input-number>

		<el-select
			v-else-if="parameter.type === 'options'"
			ref="inputField"
			size="small"
			filterable
			:value="displayValue"
			:loading="remoteParameterOptionsLoading"
			:disabled="isReadOnly || remoteParameterOptionsLoading"
			:title="displayTitle"
			@change="valueChanged"
			@keydown.stop
			@focus="setFocus"
		>
			<el-option
				v-for="option in parameterOptions"
				:value="option.value"
				:key="option.value"
				:label="option.name"
			>

{{ option.name }}

			</el-option>
		</el-select>

		<el-select
			v-else-if="parameter.type === 'multiOptions'"
			ref="inputField"
			size="small"
			filterable
			multiple
			:value="displayValue"
			:loading="remoteParameterOptionsLoading"
			:disabled="isReadOnly || remoteParameterOptionsLoading"
			@change="valueChanged"
			@keydown.stop
			@focus="setFocus"
			:title="displayTitle"
		>
			<el-option v-for="option in parameterOptions" :value="option.value" :key="option.value" :label="option.name" >

{{ option.name }}

			</el-option>
		</el-select>

			<el-color-picker :value="displayValue" :disabled="isReadOnly" @change="valueChanged" size="small" class="color-picker" @focus="setFocus" :title="displayTitle" :show-alpha="getArgument('showAlpha')"></el-color-picker>
			<el-input v-model="tempValue" size="small" type="text" :value="tempValue" :disabled="isReadOnly" @change="valueChanged" @keydown.stop @focus="setFocus" :title="displayTitle" ></el-input>

			<el-switch ref="inputField" :value="displayValue" @change="valueChanged" active-color="#13ce66" :disabled="isValueExpression || isReadOnly"></el-switch>

Edit Expression

			<el-dropdown trigger="click" @command="optionSelected" size="mini">

				<el-dropdown-menu slot="dropdown">
					<el-dropdown-item command="addExpression" v-if="parameter.noDataExpression !== true && !isValueExpression">Add Expression</el-dropdown-item>
					<el-dropdown-item command="removeExpression" v-if="parameter.noDataExpression !== true && isValueExpression">Remove Expression</el-dropdown-item>
					<el-dropdown-item command="resetValue" :disabled="isDefault" divided>Reset Value</el-dropdown-item>
				</el-dropdown-menu>
			</el-dropdown>

		<el-tooltip placement="top" effect="light">

		</el-tooltip>

</template>


#!/bin/sh

if [ -d /root/.n8n ] ; then
  chmod o+rx /root
  chown -R node /root/.n8n
  ln -s /root/.n8n /home/node/
fi

chown -R node /home/node

if [ "$#" -gt 0 ]; then
  # Got started with arguments
  exec su-exec node "$@"
else
  # Got started without arguments
  exec su-exec node n8n
fi


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

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

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

export class GithubTrigger implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'Github Trigger',
		name: 'githubTrigger',
		icon: 'file:github.svg',
		group: ['trigger'],
		version: 1,
		subtitle: '={{$parameter["owner"] + "/" + $parameter["repository"] + ": " + $parameter["events"].join(", ")}}',
		description: 'Starts the workflow when a Github events occurs.',
		defaults: {
			name: 'Github Trigger',
			color: '#000000',
		},
		inputs: [],
		outputs: ['main'],
		credentials: [
			{
				name: 'githubApi',
				required: true,
				displayOptions: {
					show: {
						authentication: [
							'accessToken',
						],
					},
				},
			},
			{
				name: 'githubOAuth2Api',
				required: true,
				displayOptions: {
					show: {
						authentication: [
							'oAuth2',
						],
					},
				},
			},
		],
		webhooks: [
			{
				name: 'default',
				httpMethod: 'POST',
				responseMode: 'onReceived',
				path: 'webhook',
			},
		],
		properties: [
			{
				displayName: 'Authentication',
				name: 'authentication',
				type: 'options',
				options: [
					{
						name: 'Access Token',
						value: 'accessToken',
					},
					{
						name: 'OAuth2',
						value: 'oAuth2',
					},
				],
				default: 'accessToken',
				description: 'The resource to operate on.',
			},
			{
				displayName: 'Repository Owner',
				name: 'owner',
				type: 'string',
				default: '',
				required: true,
				placeholder: 'n8n-io',
				description: 'Owner of the repsitory.',
			},
			{
				displayName: 'Repository Name',
				name: 'repository',
				type: 'string',
				default: '',
				required: true,
				placeholder: 'n8n',
				description: 'The name of the repsitory.',
			},
			{
				displayName: 'Events',
				name: 'events',
				type: 'multiOptions',
				options: [
					{
						name: '*',
						value: '*',
						description: 'Any time any event is triggered (Wildcard Event).',
					},
					{
						name: 'check_run',
						value: 'check_run',
						description: 'Triggered when a check run is created, rerequested, completed, or has a requested_action.',
					},
					{
						name: 'check_suite',
						value: 'check_suite',
						description: 'Triggered when a check suite is completed, requested, or rerequested.',
					},
					{
						name: 'commit_comment',
						value: 'commit_comment',
						description: 'Triggered when a commit comment is created.',
					},
					{
						name: 'content_reference',
						value: 'content_reference',
						description: 'Triggered when the body or comment of an issue or pull request includes a URL that matches a configured content reference domain. Only GitHub Apps can receive this event.',
					},
					{
						name: 'create',
						value: 'create',
						description: 'Represents a created repository, branch, or tag.',
					},
					{
						name: 'delete',
						value: 'delete',
						description: 'Represents a deleted branch or tag.',
					},
					{
						name: 'deploy_key',
						value: 'deploy_key',
						description: 'Triggered when a deploy key is added or removed from a repository.',
					},
					{
						name: 'deployment',
						value: 'deployment',
						description: 'Represents a deployment.',
					},
					{
						name: 'deployment_status',
						value: 'deployment_status',
						description: 'Represents a deployment status.',
					},
					{
						name: 'fork',
						value: 'fork',
						description: 'Triggered when a user forks a repository.',
					},
					{
						name: 'github_app_authorization',
						value: 'github_app_authorization',
						description: 'Triggered when someone revokes their authorization of a GitHub App.',
					},
					{
						name: 'gollum',
						value: 'gollum',
						description: 'Triggered when a Wiki page is created or updated.',
					},
					{
						name: 'installation',
						value: 'installation',
						description: 'Triggered when someone installs (created) , uninstalls (deleted), or accepts new permissions (new_permissions_accepted) for a GitHub App. When a GitHub App owner requests new permissions, the person who installed the GitHub App must accept the new permissions request.',
					},
					{
						name: 'installation_repositories',
						value: 'installation_repositories',
						description: 'Triggered when a repository is added or removed from an installation.',
					},
					{
						name: 'issue_comment',
						value: 'issue_comment',
						description: 'Triggered when an issue comment is created, edited, or deleted.',
					},
					{
						name: 'issues',
						value: 'issues',
						description: 'Triggered when an issue is opened, edited, deleted, transferred, pinned, unpinned, closed, reopened, assigned, unassigned, labeled, unlabeled, locked, unlocked, milestoned, or demilestoned.',
					},
					{
						name: 'label',
						value: 'label',
						description: 'Triggered when a repository\'s label is created, edited, or deleted.',
					},
					{
						name: 'marketplace_purchase',
						value: 'marketplace_purchase',
						description: 'Triggered when someone purchases a GitHub Marketplace plan, cancels their plan, upgrades their plan (effective immediately), downgrades a plan that remains pending until the end of the billing cycle, or cancels a pending plan change.',
					},
					{
						name: 'member',
						value: 'member',
						description: 'Triggered when a user accepts an invitation or is removed as a collaborator to a repository, or has their permissions changed.',
					},
					{
						name: 'membership',
						value: 'membership',
						description: 'Triggered when a user is added or removed from a team. Organization hooks only.',
					},
					{
						name: 'meta',
						value: 'meta',
						description: 'Triggered when the webhook that this event is configured on is deleted.',
					},
					{
						name: 'milestone',
						value: 'milestone',
						description: 'Triggered when a milestone is created, closed, opened, edited, or deleted.',
					},
					{
						name: 'organization',
						value: 'organization',
						description: 'Triggered when an organization is deleted and renamed, and when a user is added, removed, or invited to an organization. Organization hooks only.',
					},
					{
						name: 'org_block',
						value: 'org_block',
						description: 'Triggered when an organization blocks or unblocks a user. Organization hooks only.',
					},
					{
						name: 'page_build',
						value: 'page_build',
						description: 'Triggered on push to a GitHub Pages enabled branch (gh-pages for project pages, master for user and organization pages).',
					},
					{
						name: 'project_card',
						value: 'project_card',
						description: 'Triggered when a project card is created, edited, moved, converted to an issue, or deleted.',
					},
					{
						name: 'project_column',
						value: 'project_column',
						description: 'Triggered when a project column is created, updated, moved, or deleted.',
					},
					{
						name: 'project',
						value: 'project',
						description: 'Triggered when a project is created, updated, closed, reopened, or deleted.',
					},
					{
						name: 'public',
						value: 'public',
						description: 'Triggered when a private repository is open sourced.',
					},
					{
						name: 'pull_request',
						value: 'pull_request',
						description: 'Triggered when a pull request is assigned, unassigned, labeled, unlabeled, opened, edited, closed, reopened, synchronize, ready_for_review, locked, unlocked, a pull request review is requested, or a review request is removed.',
					},
					{
						name: 'pull_request_review',
						value: 'pull_request_review',
						description: 'Triggered when a pull request review is submitted into a non-pending state, the body is edited, or the review is dismissed.',
					},

					{
						name: 'pull_request_review_comment',
						value: 'pull_request_review_comment',
						description: 'Triggered when a comment on a pull request\'s unified diff is created, edited, or deleted (in the Files Changed tab).',
					},
					{
						name: 'push',
						value: 'push',
						description: 'Triggered on a push to a repository branch. Branch pushes and repository tag pushes also trigger webhook push events. This is the default event.',
					},
					{
						name: 'release',
						value: 'release',
						description: 'Triggered when a release is published, unpublished, created, edited, deleted, or prereleased.',
					},
					{
						name: 'repository',
						value: 'repository',
						description: 'Triggered when a repository is created, archived, unarchived, renamed, edited, transferred, made public, or made private. Organization hooks are also triggered when a repository is deleted.',
					},
					{
						name: 'repository_import',
						value: 'repository_import',
						description: 'Triggered when a successful, cancelled, or failed repository import finishes for a GitHub organization or a personal repository.',
					},
					{
						name: 'repository_vulnerability_alert',
						value: 'repository_vulnerability_alert',
						description: 'Triggered when a security alert is created, dismissed, or resolved.',
					},
					{
						name: 'security_advisory',
						value: 'security_advisory',
						description: 'Triggered when a new security advisory is published, updated, or withdrawn.',
					},
					{
						name: 'star',
						value: 'star',
						description: 'Triggered when a star is added or removed from a repository.',
					},
					{
						name: 'status',
						value: 'status',
						description: 'Triggered when the status of a Git commit changes.',
					},
					{
						name: 'team',
						value: 'team',
						description: 'Triggered when an organization\'s team is created,<br/>deleted, edited, added_to_repository, or removed_from_repository. Organization hooks only',
					},
					{
						name: 'team_add',
						value: 'team_add',
						description: 'Triggered when a repository is added to a team.',
					},
					{
						name: 'watch',
						value: 'watch',
						description: 'Triggered when someone stars a repository.',
					},
				],
				required: true,
				default: [],
				description: 'The events to listen to.',
			},
		],
	};

	// @ts-ignore (because of request)
	webhookMethods = {
		default: {
			async checkExists(this: IHookFunctions): Promise<boolean> {
				const webhookData = this.getWorkflowStaticData('node');

				if (webhookData.webhookId === undefined) {
					// No webhook id is set so no webhook can exist
					return false;
				}

				// Webhook got created before so check if it still exists
				const owner = this.getNodeParameter('owner') as string;
				const repository = this.getNodeParameter('repository') as string;
				const endpoint = `/repos/${owner}/${repository}/hooks/${webhookData.webhookId}`;

				try {
					await githubApiRequest.call(this, 'GET', endpoint, {});
				} catch (e) {
					if (e.message.includes('[404]:')) {
						// Webhook does not exist
						delete webhookData.webhookId;
						delete webhookData.webhookEvents;

						return false;
					}

					// Some error occured
					throw e;
				}

				// If it did not error then the webhook exists
				return true;
			},
			async create(this: IHookFunctions): Promise<boolean> {
				const webhookUrl = this.getNodeWebhookUrl('default') as string;

				if (webhookUrl.includes('//localhost')) {
					throw new Error('The Webhook can not work on "localhost". Please, either setup n8n on a custom domain or start with "--tunnel"!');
				}

				const owner = this.getNodeParameter('owner') as string;
				const repository = this.getNodeParameter('repository') as string;
				const events = this.getNodeParameter('events', []);

				const endpoint = `/repos/${owner}/${repository}/hooks`;

				const body = {
					name: 'web',
					config: {
						url: webhookUrl,
						content_type: 'json',
						// secret: '...later...',
						insecure_ssl: '1', // '0' -> not allow inscure ssl | '1' -> allow insercure SSL
					},
					events,
					active: true,
				};

				const webhookData = this.getWorkflowStaticData('node');

				let responseData;
				try {
					responseData = await githubApiRequest.call(this, 'POST', endpoint, body);
				} catch (e) {
					if (e.message.includes('[422]:')) {
						// Webhook exists already

						// Get the data of the already registered webhook
						responseData = await githubApiRequest.call(this, 'GET', endpoint, body);

						for (const webhook of responseData as IDataObject[]) {
							if ((webhook!.config! as IDataObject).url! === webhookUrl) {
								// Webhook got found
								if (JSON.stringify(webhook.events) === JSON.stringify(events)) {
									// Webhook with same events exists already so no need to
									// create it again simply save the webhook-id
									webhookData.webhookId = webhook.id as string;
									webhookData.webhookEvents = webhook.events as string[];
									return true;
								}
							}
						}

						throw new Error('A webhook with the identical URL probably exists already. Please delete it manually on Github!');
					}

					throw e;
				}

				if (responseData.id === undefined || responseData.active !== true) {
					// Required data is missing so was not successful
					throw new Error('Github webhook creation response did not contain the expected data.');
				}

				webhookData.webhookId = responseData.id as string;
				webhookData.webhookEvents = responseData.events as string[];

				return true;
			},
			async delete(this: IHookFunctions): Promise<boolean> {
				const webhookData = this.getWorkflowStaticData('node');

				if (webhookData.webhookId !== undefined) {
					const owner = this.getNodeParameter('owner') as string;
					const repository = this.getNodeParameter('repository') as string;
					const endpoint = `/repos/${owner}/${repository}/hooks/${webhookData.webhookId}`;
					const body = {};

					try {
						await githubApiRequest.call(this, 'DELETE', endpoint, body);
					} catch (e) {
						return false;
					}

					// Remove from the static workflow data so that it is clear
					// that no webhooks are registred anymore
					delete webhookData.webhookId;
					delete webhookData.webhookEvents;
				}

				return true;
			},
		},
	};

	async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
		const bodyData = this.getBodyData();

		// Check if the webhook is only the ping from Github to confirm if it workshook_id
		if (bodyData.hook_id !== undefined && bodyData.action === undefined) {
			// Is only the ping and not an actual webhook call. So return 'OK'
			// but do not start the workflow.

			return {
				webhookResponse: 'OK',
			};
		}

		// Is a regular webhoook call

		// TODO: Add headers & requestPath
		const returnData: IDataObject[] = [];

		returnData.push(
			{
				body: bodyData,
				headers: this.getHeaderData(),
				query: this.getQueryData(),
			},
		);

		return {
			workflowData: [
				this.helpers.returnJsonArray(returnData),
			],
		};
	}
}


<template>

			<el-input placeholder="Type to filter..." v-model="nodeFilter" ref="inputField" size="small" type="text" prefix-icon="el-icon-search" @keydown.native="nodeFilterKeyDown" clearable ></el-input>

			<el-tabs v-model="selectedType" stretch>
				<el-tab-pane label="Regular" name="Regular"></el-tab-pane>
				<el-tab-pane label="Trigger" name="Trigger"></el-tab-pane>
				<el-tab-pane label="All" name="All"></el-tab-pane>
			</el-tabs>

					🙃 no nodes matching your search criteria

				<node-create-item :active="index === activeNodeTypeIndex" :nodeType="nodeType" v-for="(nodeType, index) in filteredNodeTypes" v-bind:key="nodeType.name" @nodeTypeSelected="nodeTypeSelected"></node-create-item>

</template>


} from 'n8n-workflow';
import {
IExecutionResponse,
IVariableItemSelected,


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

export class Start implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'Start',
		name: 'start',
		icon: 'fa:play',
		group: ['input'],
		version: 1,
		description: 'Starts the workflow execution from this node',
		maxNodes: 1,
		defaults: {
			name: 'Start',
			color: '#00e000',
		},
		inputs: [],
		outputs: ['main'],
		properties: [
		],
	};

	execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
		const items = this.getInputData();

		return this.prepareOutputData(items);
	}
}


# n8n on Subfolder with SSL

Starts n8n and deployes it on a subfolder

## Start

To start n8n in a subfolder simply start docker-compose by executing the following
command in the current folder.

**IMPORTANT:** But before you do that change the default users and passwords in the `.env` file!

```
docker-compose up -d
```

To stop it execute:

```
docker-compose stop
```


IVariableItemSelected,
IVariableSelectorOption,
} from '@/Interface';


import Vue from 'vue';
import Router from 'vue-router';
import MainHeader from '@/components/MainHeader.vue';
import MainSidebar from '@/components/MainSidebar.vue';
import NodeView from '@/views/NodeView.vue';

Vue.use(Router);

export default new Router({
	mode: 'history',
	// @ts-ignore
	base: window.BASE_PATH === '/%BASE_PATH%/' ? '/' : window.BASE_PATH,
	routes: [
		{
			path: '/execution/:id',
			name: 'ExecutionById',
			components: {
				default: NodeView,
				header: MainHeader,
				sidebar: MainSidebar,
			},
		},
		{
			path: '/oauth2/callback',
			name: 'oAuth2Callback',
			components: {
			},
		},
		{
			path: '/workflow',
			name: 'NodeViewNew',
			components: {
				default: NodeView,
				header: MainHeader,
				sidebar: MainSidebar,
			},
		},
		{
			path: '/workflow/:name',
			name: 'NodeViewExisting',
			components: {
				default: NodeView,
				header: MainHeader,
				sidebar: MainSidebar,
			},
		},
		{
			path: '/',
			redirect: '/workflow',
		},
	],
});


import {
	OptionsWithUri,
} from 'request';

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

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

import * as _ from 'lodash';

export async function slackApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: object = {}, query: object = {}, headers: {} | undefined = undefined, option: {} = {}): Promise<any> { // tslint:disable-line:no-any
	const authenticationMethod = this.getNodeParameter('authentication', 0, 'accessToken') as string;
	let options: OptionsWithUri = {
		method,
		headers: headers || {
			'Content-Type': 'application/json; charset=utf-8',
		},
		body,
		qs: query,
		uri: `https://slack.com/api${resource}`,
		json: true,
	};
	options = Object.assign({}, options, option);
	if (Object.keys(body).length === 0) {
		delete options.body;
	}
	if (Object.keys(query).length === 0) {
		delete options.qs;
	}
	try {
		let response: any; // tslint:disable-line:no-any

		if (authenticationMethod === 'accessToken') {
			const credentials = this.getCredentials('slackApi');
			if (credentials === undefined) {
				throw new Error('No credentials got returned!');
			}
			options.headers!.Authorization = `Bearer ${credentials.accessToken}`;
			//@ts-ignore
			response = await this.helpers.request(options);
		} else {

			const oAuth2Options: IOAuth2Options = {
				tokenType: 'Bearer',
				property: 'authed_user.access_token',
			};
			//@ts-ignore
			response = await this.helpers.requestOAuth2.call(this, 'slackOAuth2Api', options, oAuth2Options);
		}

		if (response.ok === false) {
			throw new Error('Slack error response: ' + JSON.stringify(response));
		}

		return response;
	} catch (error) {
		if (error.statusCode === 401) {
			// Return a clear error
			throw new Error('The Slack credentials are not valid!');
		}

		if (error.response && error.response.body && error.response.body.message) {
			// Try to return the error prettier
			throw new Error(`Slack error response [${error.statusCode}]: ${error.response.body.message}`);
		}

		// If that data does not exist for some reason return the actual error
		throw error;
	}
}

export async function slackApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
	const returnData: IDataObject[] = [];
	let responseData;
	query.page = 1;
	query.count = 100;
	do {
		responseData = await slackApiRequest.call(this, method, endpoint, body, query);
		query.cursor = encodeURIComponent(_.get(responseData, 'response_metadata.next_cursor'));
		query.page++;
		returnData.push.apply(returnData, responseData[propertyName]);
	} while (
		(responseData.response_metadata !== undefined &&
			responseData.response_metadata.mext_cursor !== undefined &&
			responseData.response_metadata.next_cursor !== '' &&
			responseData.response_metadata.next_cursor !== null) ||
		(responseData.paging !== undefined &&
			responseData.paging.pages !== undefined &&
			responseData.paging.page !== undefined &&
			responseData.paging.page < responseData.paging.pages
		)
	);

	return returnData;
}

export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any
	let result;
	try {
		result = JSON.parse(json!);
	} catch (exception) {
		result = undefined;
	}
	return result;
}


export interface IData {
	document: IDocument;
	encodingType: string;
}

export interface IDocument {
	type: string;
	language?: string;
	content?: string;
	gcsContentUri?: string;
}


import { workflowHelpers } from '@/components/mixins/workflowHelpers';

import { genericHelpers } from '@/components/mixins/genericHelpers';


“Commons Clause” License Condition v1.0

The Software is provided to you by the Licensor under the
License, as defined below, subject to the following condition.

Without limiting other conditions in the License, the grant
of rights under the License will not include, and the License
does not grant to you, the right to Sell the Software.

For purposes of the foregoing, “Sell” means practicing any or
all of the rights granted to you under the License to provide
to third parties, for a fee or other consideration (including
without limitation fees for hosting or consulting/ support
services related to the Software), a product or service whose
value derives, entirely or substantially, from the functionality
of the Software. Any license notice or attribution required by
the License must also include this Commons Clause License
Condition notice.

Software: n8n

License: Apache 2.0 with Commons Clause

Licensor: n8n GmbH

---------------------------------------------------------------------

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2020 n8n GmbH

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


import {
	OptionsWithUri,
} from 'request';

import {
	IExecuteFunctions,
	IExecuteSingleFunctions,
	IHookFunctions,
	ILoadOptionsFunctions,
} from 'n8n-core';

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

export async function profitWellApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
	try {
		const credentials = this.getCredentials('profitWellApi');
		if (credentials === undefined) {
			throw new Error('No credentials got returned!');
		}
		let options: OptionsWithUri = {
			headers: {
				'Authorization': credentials.accessToken,
			},
			method,
			qs,
			body,
			uri: uri || `https://api.profitwell.com/v2${resource}`,
			json: true,
		};

		options = Object.assign({}, options, option);

		return await this.helpers.request!(options);
	} catch (error) {

		if (error.response && error.response.body && error.response.body.message) {
			// Try to return the error prettier
			const errorBody = error.response.body;
			throw new Error(`ProfitWell error response [${error.statusCode}]: ${errorBody.message}`);
		}

		// Expected error data did not get returned so throw the actual error
		throw error;
	}
}

export function simplifyDailyMetrics(responseData: { [key: string]: [{ date: string, value: number | null }] }) {
	const data: IDataObject[] = [];
	const keys = Object.keys(responseData);
	const dates = responseData[keys[0]].map(e => e.date);
	for (const [index, date] of dates.entries()) {
		const element: IDataObject = {
			date,
		};
		for (const key of keys) {
			element[key] = responseData[key][index].value;
		}
		data.push(element);
	}
	return data;
}

export function simplifyMontlyMetrics(responseData: { [key: string]: [{ date: string, value: number | null }] }) {
	const data: IDataObject = {};
	for (const key of Object.keys(responseData)) {
		for (const [index] of responseData[key].entries()) {
			data[key] = responseData[key][index].value;
			data['date'] = responseData[key][index].date;
		}
	}
	return data;
}


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

export class ClickUpApi implements ICredentialType {
	name = 'clickUpApi';
	displayName = 'ClickUp API';
	documentationUrl = 'clickUp';
	properties = [
		{
			displayName: 'Access Token',
			name: 'accessToken',
			type: 'string' as NodePropertyTypes,
			default: '',
		},
	];
}


import mixins from 'vue-typed-mixins';
export default mixins(
workflowHelpers,
)
.extend({


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

export class IterableApi implements ICredentialType {
	name = 'iterableApi';
	displayName = 'Iterable API';
	documentationUrl = 'iterable';
	properties = [
		{
			displayName: 'API Key',
			name: 'apiKey',
			type: 'string' as NodePropertyTypes,
			default: '',
		},
	];
}


export const billAdditionalFieldsOptions = [
	{
		displayName: 'Accounts Payable Account',
		name: 'APAccountRef',
		placeholder: 'Add APA Fields',
		description: 'Accounts Payable account to which the bill will be credited.',
		type: 'fixedCollection',
		default: {},
		options: [
			{
				displayName: 'Details',
				name: 'details',
				values: [
					{
						displayName: 'Name',
						name: 'name',
						type: 'string',
						default: '',
					},
					{
						displayName: 'ID',
						name: 'value',
						type: 'string',
						default: '',
					},
				],
			},
		],
	},
	{
		displayName: 'Balance',
		name: 'Balance',
		description: 'The balance reflecting any payments made against the transaction.',
		type: 'string',
		default: '',
	},
	{
		displayName: 'Due Date',
		name: 'DueDate',
		description: 'Date when the payment of the transaction is due.',
		type: 'dateTime',
		default: '',
	},
	{
		displayName: 'Sales Term',
		name: 'SalesTermRef',
		description: 'Sales term associated with the transaction.',
		placeholder: 'Add Sales Term Fields',
		type: 'fixedCollection',
		default: {},
		options: [
			{
				displayName: 'Details',
				name: 'details',
				values: [
					{
						displayName: 'Name',
						name: 'name',
						type: 'string',
						default: '',
					},
					{
						displayName: 'ID',
						name: 'value',
						type: 'string',
						default: '',
					},
				],
			},
		],
	},
	{
		displayName: 'Total Amount',
		name: 'TotalAmt',
		description: 'Total amount of the transaction.',
		type: 'number',
		default: 0,
	},
	{
		displayName: 'Transaction Date',
		name: 'TxnDate',
		description: 'Date when the transaction occurred.',
		type: 'dateTime',
		default: '',
	},
];


this.editor = new Quill(this.$refs['expression-editor'] as Element, {

readOnly: !!this.resolvedValue || this.isReadOnly,


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

export const subscriberOperations = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		displayOptions: {
			show: {
				resource: [
					'subscriber',
				],
			},
		},
		options: [
			{
				name: 'Add',
				value: 'add',
				description: 'Add a subscriber to a list',
			},
			{
				name: 'Count',
				value: 'count',
				description: 'Count subscribers',
			},
			{
				name: 'Delete',
				value: 'delete',
				description: 'Delete a subscriber from a list',
			},
			{
				name: 'Remove',
				value: 'remove',
				description: 'Unsubscribe user from a list',
			},
			{
				name: 'Status',
				value: 'status',
				description: 'Get the status of subscriber',
			},
		],
		default: 'add',
		description: 'The operation to perform.',
	},
] as INodeProperties[];

export const subscriberFields = [

/* -------------------------------------------------------------------------- */
/*                                subscriber:add                              */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Email',
		name: 'email',
		type: 'string',
		displayOptions: {
			show: {
				resource: [
					'subscriber',
				],
				operation: [
					'add',
				],
			},
		},
		default: '',
		description: 'Email address of the subscriber.',
	},
	{
		displayName: 'List ID',
		name: 'listId',
		type: 'string',
		displayOptions: {
			show: {
				resource: [
					'subscriber',
				],
				operation: [
					'add',
				],
			},
		},
		default: '',
		description: `The list id you want to subscribe a user to.

		This encrypted & hashed id can be found under View all lists section named ID.`,
	},
	{
		displayName: 'Additional Fields',
		name: 'additionalFields',
		type: 'collection',
		placeholder: 'Add Field',
		default: {},
		displayOptions: {
			show: {
				resource: [
					'subscriber',
				],
				operation: [
					'add',
				],
			},
		},
		options: [
			{
				displayName: 'Country',
				name: 'country',
				type: 'string',
				default: '',
				description: `User's 2 letter country code`,
			},
			{
				displayName: 'GDPR',
				name: 'gdpr',
				type: 'boolean',
				default: false,
				description: `If you're signing up EU users in a GDPR compliant manner, set this to "true"`,
			},
			{
				displayName: 'Honeypot',
				name: 'hp',
				type: 'boolean',
				default: false,
				description: `Include this 'honeypot' field to prevent spambots from signing up via this API call. When spambots fills in this field, this API call will exit, preventing them from signing up fake addresses to your form. This parameter is only supported in Sendy 3.0 onwards.`,
			},
			{
				displayName: 'IP Address',
				name: 'ipaddress',
				type: 'string',
				default: '',
				description: `User's IP address`,
			},
			{
				displayName: 'Name',
				name: 'name',
				type: 'string',
				default: '',
				description: `User's name`,
			},
			{
				displayName: 'Referrer',
				name: 'referrer',
				type: 'string',
				default: '',
				description: `The URL where the user signed up from`,
			},
			{
				displayName: 'Silent',
				name: 'silent',
				type: 'boolean',
				default: false,
				description: `Set to "true" if your list is 'Double opt-in' but you want to bypass that and signup the user to the list as 'Single Opt-in instead' (optional)`,
			},
		],
	},
/* -------------------------------------------------------------------------- */
/*                                subscriber:count                            */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'List ID',
		name: 'listId',
		type: 'string',
		displayOptions: {
			show: {
				resource: [
					'subscriber',
				],
				operation: [
					'count',
				],
			},
		},
		default: '',
		description: `The list id you want to subscribe a user to.

		This encrypted & hashed id can be found under View all lists section named ID.`,
	},
/* -------------------------------------------------------------------------- */
/*                                subscriber:delete                           */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Email',
		name: 'email',
		type: 'string',
		displayOptions: {
			show: {
				resource: [
					'subscriber',
				],
				operation: [
					'delete',
				],
			},
		},
		default: '',
		description: 'Email address of the subscriber.',
	},
	{
		displayName: 'List ID',
		name: 'listId',
		type: 'string',
		displayOptions: {
			show: {
				resource: [
					'subscriber',
				],
				operation: [
					'delete',
				],
			},
		},
		default: '',
		description: `The list id you want to subscribe a user to.

		This encrypted & hashed id can be found under View all lists section named ID.`,
	},
/* -------------------------------------------------------------------------- */
/*                                subscriber:remove                           */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Email',
		name: 'email',
		type: 'string',
		displayOptions: {
			show: {
				resource: [
					'subscriber',
				],
				operation: [
					'remove',
				],
			},
		},
		default: '',
		description: 'Email address of the subscriber.',
	},
	{
		displayName: 'List ID',
		name: 'listId',
		type: 'string',
		displayOptions: {
			show: {
				resource: [
					'subscriber',
				],
				operation: [
					'remove',
				],
			},
		},
		default: '',
		description: `The list id you want to subscribe a user to.

		This encrypted & hashed id can be found under View all lists section named ID.`,
	},
/* -------------------------------------------------------------------------- */
/*                                subscriber:status                           */
/* -------------------------------------------------------------------------- */
	{
		displayName: 'Email',
		name: 'email',
		type: 'string',
		displayOptions: {
			show: {
				resource: [
					'subscriber',
				],
				operation: [
					'status',
				],
			},
		},
		default: '',
		description: 'Email address of the subscriber.',
	},
	{
		displayName: 'List ID',
		name: 'listId',
		type: 'string',
		displayOptions: {
			show: {
				resource: [
					'subscriber',
				],
				operation: [
					'status',
				],
			},
		},
		default: '',
		description: `The list id you want to subscribe a user to.

		This encrypted & hashed id can be found under View all lists section named ID.`,
	},
] as INodeProperties[];


import {
	IExecuteFunctions,
	IExecuteSingleFunctions,
	ILoadOptionsFunctions,
} from 'n8n-core';
import { IDataObject } from 'n8n-workflow';
import { OptionsWithUri } from 'request';

export async function cockpitApiRequest(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, method: string, resource: string, body: any = {}, uri?: string, option: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any
	const credentials = this.getCredentials('cockpitApi');

	if (credentials === undefined) {
		throw new Error('No credentials available.');
	}

	let options: OptionsWithUri = {
		headers: {
			Accept: 'application/json',
			'Content-Type': 'application/json',
		},
		method,
		qs: {
			token: credentials!.accessToken,
		},
		body,
		uri: uri || `${credentials!.url}/api${resource}`,
		json: true,
	};

	options = Object.assign({}, options, option);

	if (Object.keys(options.body).length === 0) {
		delete options.body;
	}

	try {
		return await this.helpers.request!(options);
	} catch (error) {
		let errorMessage = error.message;
		if (error.error) {
			errorMessage = error.error.message || error.error.error;
		}

		throw new Error(`Cockpit error [${error.statusCode}]: ` + errorMessage);
	}
}

export function createDataFromParameters(this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, itemIndex: number): IDataObject {
	const dataFieldsAreJson = this.getNodeParameter('jsonDataFields', itemIndex) as boolean;

	if (dataFieldsAreJson) {
		// Parameters are defined as JSON
		return JSON.parse(this.getNodeParameter('dataFieldsJson', itemIndex, '{}') as string);
	}

	// Parameters are defined in UI
	const uiDataFields = this.getNodeParameter('dataFieldsUi', itemIndex, {}) as IDataObject;
	const unpacked: IDataObject = {};

	if (uiDataFields.field === undefined) {
		return unpacked;
	}

	for (const field of uiDataFields!.field as IDataObject[]) {
		unpacked[field!.name as string] = field!.value;
	}

	return unpacked;
}


<prism-editor v-if="!codeEditDialogVisible" :lineNumbers="true" :readonly="true" :code="displayValue" language="js"></prism-editor>

<el-input v-else v-model="tempValue" ref="inputField" size="small" :type="getStringInputType" :rows="getArgument('rows')" :value="displayValue" :disabled="isReadOnly" @change="valueChanged" @keydown.stop @focus="setFocus" :title="displayTitle" :placeholder="isValueExpression?'':parameter.placeholder">

<font-awesome-icon v-if="!isValueExpression && !isReadOnly" slot="suffix" icon="external-link-alt" class="edit-window-button clickable" title="Open Edit Window" @click="displayEditDialog()" />

<div v-else-if="parameter.type === 'dateTime'">

keep keep keep replace keep keep keep keep

<el-input v-else v-model="tempValue" ref="inputField" size="small" :type="getStringInputType" :rows="getArgument('rows')" :value="displayValue" :disabled="!isValueExpression && isReadOnly" @change="valueChanged" @keydown.stop @focus="setFocus" :title="displayTitle" :placeholder="isValueExpression?'':parameter.placeholder">


import {
	OptionsWithUrl,
 } from 'request';

import {
	IExecuteFunctions,
	IExecuteSingleFunctions,
	IHookFunctions,
	ILoadOptionsFunctions,
} from 'n8n-core';

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

export async function mailchimpApiRequest(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, endpoint: string, method: string, body: any = {}, qs: IDataObject = {} ,headers?: object): Promise<any> { // tslint:disable-line:no-any
	const authenticationMethod = this.getNodeParameter('authentication', 0) as string;

	const host = 'api.mailchimp.com/3.0';

	const options: OptionsWithUrl = {
		headers: {
			'Accept': 'application/json',
		},
		method,
		qs,
		body,
		url: ``,
		json: true,
	};

	if (Object.keys(body).length === 0) {
		delete options.body;
	}

	try {
		if (authenticationMethod === 'apiKey') {
			const credentials = this.getCredentials('mailchimpApi');

			if (credentials === undefined) {
				throw new Error('No credentials got returned!');
			}

			options.headers = Object.assign({}, headers, { Authorization: `apikey ${credentials.apiKey}` });

			if (!(credentials.apiKey as string).includes('-')) {
				throw new Error('The API key is not valid!');
			}

			const datacenter = (credentials.apiKey as string).split('-').pop();
			options.url = `https://${datacenter}.${host}${endpoint}`;

			return await this.helpers.request!(options);
		} else {
			const credentials = this.getCredentials('mailchimpOAuth2Api') as IDataObject;

			const { api_endpoint } = await getMetadata.call(this, credentials.oauthTokenData as IDataObject);

			options.url = `${api_endpoint}/3.0${endpoint}`;
			//@ts-ignore
			return await this.helpers.requestOAuth2!.call(this, 'mailchimpOAuth2Api', options, { tokenType: 'Bearer' });
		}
	} catch (error) {
		if (error.respose && error.response.body && error.response.body.detail) {
			throw new Error(`Mailchimp Error response [${error.statusCode}]: ${error.response.body.detail}`);
		}
		throw error;
	}
}

export async function mailchimpApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, endpoint: string, method: string, propertyName: string, body: any = {}, query: IDataObject = {}): Promise<any> { // tslint:disable-line:no-any

	const returnData: IDataObject[] = [];

	let responseData;

	query.offset = 0;
	query.count = 500;

	do {
		responseData = await mailchimpApiRequest.call(this, endpoint, method, body, query);
		returnData.push.apply(returnData, responseData[propertyName]);
		query.offset += query.count;
	} while (
		responseData[propertyName] && responseData[propertyName].length !== 0
	);

	return returnData;
}

export function validateJSON(json: string | undefined): any { // tslint:disable-line:no-any
	let result;
	try {
		result = JSON.parse(json!);
	} catch (exception) {
		result = '';
	}
	return result;
}

function getMetadata(this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions, oauthTokenData: IDataObject) {
	const credentials = this.getCredentials('mailchimpOAuth2Api') as IDataObject;
	const options: OptionsWithUrl = {
		headers: {
			'Accept': 'application/json',
			'Authorization': `OAuth ${oauthTokenData.access_token}`,
		},
		method: 'GET',
		url: credentials.metadataUrl as string,
		json: true,
	};
	return this.helpers.request!(options);
}

export const campaignFieldsMetadata = [
	'*',
	'campaigns.id',
	'campaigns.web_id',
	'campaigns.type',
	'campaigns.create_time',
	'campaigns.archive_url',
	'campaigns.long_archive_url',
	'campaigns.status',
	'campaigns.emails_sent',
	'campaigns.send_time',
	'campaigns.content_type',
	'campaigns.needs_block_refresh',
	'campaigns.resendable',
	'campaigns.recipients',
	'campaigns.recipients.list_id',
	'campaigns.recipients.list_is_active',
	'campaigns.recipients.list_name',
	'campaigns.recipients.segment_text',
	'campaigns.recipients.recipient_count',
	'campaigns.settings',
	'campaigns.settings.subject_line',
	'campaigns.settings.preview_text',
	'campaigns.settings.title',
	'campaigns.settings.from_name',
	'campaigns.settings.reply_to',
	'campaigns.settings.use_conversation',
	'campaigns.settings.to_name',
	'campaigns.settings.folder_id',
	'campaigns.settings.authenticate',
	'campaigns.settings.auto_footer',
	'campaigns.settings.inline_css',
	'campaigns.settings.auto_tweet',
	'campaigns.settings.fb_comments',
	'campaigns.settings.timewarp',
	'campaigns.settings.template_id',
	'campaigns.settings.drag_and_drop',
	'campaigns.tracking',
	'campaigns.tracking.opens',
	'campaigns.tracking.html_clicks',
	'campaigns.tracking.text_clicks',
	'campaigns.tracking.goal_tracking',
	'campaigns.tracking.ecomm360',
	'campaigns.tracking.google_analytics',
	'campaigns.tracking.clicktale',
	'campaigns.report_summary',
	'campaigns.report_summary.opens',
	'campaigns.report_summary.unique_opens',
	'campaigns.report_summary.open_rate',
	'campaigns.report_summary.clicks',
	'campaigns.report_summary.subscriber_clicks',
	'campaigns.report_summary.click_rate',
	'campaigns.report_summary.click_rate.ecommerce',
	'campaigns.report_summary.click_rate.ecommerce.total_orders',
	'campaigns.report_summary.click_rate.ecommerce.total_spent',
	'campaigns.report_summary.click_rate.ecommerce.total_revenue',
	'campaigns.report_summary.delivery_status.enabled',
	'campaigns._links',
];


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

export const userOperations = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		displayOptions: {
			show: {
				resource: [
					'user',
				],
			},
		},
		options: [
			{
				name: 'Get',
				value: 'get',
			},
		],
		default: 'get',
		description: 'Operation to perform',
	},
] as INodeProperties[];

export const userFields = [
	{
		displayName: 'Username',
		name: 'username',
		type: 'string',
		required: true,
		default: '',
		description: 'Reddit ID of the user to retrieve.',
		displayOptions: {
			show: {
				resource: [
					'user',
				],
				operation: [
					'get',
				],
			},
		},
	},
	{
		displayName: 'Details',
		name: 'details',
		type: 'options',
		required: true,
		default: 'about',
		description: 'Details of the user to retrieve.',
		options: [
			{
				name: 'About',
				value: 'about',
			},
			{
				name: 'Comments',
				value: 'comments',
			},
			{
				name: 'Gilded',
				value: 'gilded',
			},
			{
				name: 'Overview',
				value: 'overview',
			},
			{
				name: 'Submitted',
				value: 'submitted',
			},
		],
		displayOptions: {
			show: {
				resource: [
					'user',
				],
				operation: [
					'get',
				],
			},
		},
	},
	{
		displayName: 'Return All',
		name: 'returnAll',
		type: 'boolean',
		default: false,
		description: 'Return all results.',
		displayOptions: {
			show: {
				resource: [
					'user',
				],
				operation: [
					'get',
				],
				details: [
					'overview',
					'submitted',
					'comments',
					'gilded',
				],
			},
		},
	},
	{
		displayName: 'Limit',
		name: 'limit',
		type: 'number',
		default: 100,
		description: 'The number of results to return.',
		typeOptions: {
			minValue: 1,
			maxValue: 100,
		},
		displayOptions: {
			show: {
				resource: [
					'user',
				],
				operation: [
					'get',
				],
				details: [
					'comments',
					'gilded',
					'overview',
					'submitted',
				],
				returnAll: [
					false,
				],
			},
		},
	},
] as INodeProperties[];


keep keep keep keep keep replace replace replace keep keep keep keep


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

import {
	allCurrencies,
} from './currencies';

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

export const dealOperations = [
	{
		displayName: 'Operation',
		name: 'operation',
		type: 'options',
		displayOptions: {
			show: {
				resource: [
					'deal',
				],
			},
		},
		options: [
			{
				name: 'Create',
				value: 'create',
				description: 'Create a deal',
			},
			{
				name: 'Delete',
				value: 'delete',
				description: 'Delete a deal',
			},
			{
				name: 'Get',
				value: 'get',
				description: 'Get data of a deal',
			},
			{
				name: 'Get All',
				value: 'getAll',
				description: 'Get data of all deals',
			},
			{
				name: 'Update',
				value: 'update',
				description: 'Update a deal',
			},
			{
				name: 'Create Note',
				value: 'createNote',
				description: 'Create a deal note',
			},
			{
				name: 'Update deal note',
				value: 'updateNote',
				description: 'Update a deal note',
			},
		],
		default: 'create',
		description: 'The operation to perform.',
	},

] as INodeProperties[];

export const dealFields = [
	// ----------------------------------
	//         deal:create
	// ----------------------------------
	{
		displayName: 'Title',
		name: 'title',
		type: 'string',
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource: [
					'deal',
				],
			},
		},
		description: 'The title of the deal',
	},
	{
		displayName: 'Deal\'s contact ID',
		name: 'contact',
		type: 'number',
		default: 0,
		required: true,
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource: [
					'deal',
				],
			},
		},
		description: 'The ID of the deal\'s contact',
	},
	{
		displayName: 'Deal value',
		name: 'value',
		type: 'number',
		default: 0,
		required: true,
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource: [
					'deal',
				],
			},
		},
		description: 'The value of the deal in cents',
	},
	{
		displayName: 'Currency',
		name: 'currency',
		type: 'options',
		default: 'eur',
		required: true,
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource: [
					'deal',
				],
			},
		},
		options: allCurrencies,
		description: 'The currency of the deal in 3-character ISO format',
	},
	{
		displayName: 'Deal pipeline ID',
		name: 'group',
		type: 'string',
		default: '',
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource: [
					'deal',
				],
			},
		},
		description: 'The pipeline ID of the deal',
	},
	{
		displayName: 'Deal stage ID',
		name: 'stage',
		type: 'string',
		default: '',
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource: [
					'deal',
				],
			},
		},
		description: 'The stage ID of the deal',
	},
	{
		displayName: 'Deal owner ID',
		name: 'owner',
		type: 'string',
		default: '',
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource: [
					'deal',
				],
			},
		},
		description: 'The owner ID of the deal',
	},
	{
		displayName: 'Additional Fields',
		name: 'additionalFields',
		type: 'collection',
		placeholder: 'Add Field',
		displayOptions: {
			show: {
				operation: [
					'create',
				],
				resource: [
					'deal',
				],
			},
		},
		default: {},
		options: [
			{
				displayName: 'Description',
				name: 'description',
				type: 'string',
				default: '',
				description: 'The description of the deal',
			},

			{
				displayName: 'Deal percentage',
				name: 'percent',
				type: 'number',
				default: 0,
				description: 'The percentage of the deal',
			},
			{
				displayName: 'Deal status',
				name: 'status',
				type: 'number',
				default: 0,
				description: 'The status of the deal',
			},
		],
	},

	// ----------------------------------
	//         deal:update
	// ----------------------------------
	{
		displayName: 'Deal ID',
		name: 'dealId',
		type: 'number',
		displayOptions: {
			show: {
				operation: [
					'update',
				],
				resource: [
					'deal',
				],
			},
		},
		default: 0,
		required: true,
		description: 'ID of the deal to update.',
	},
	{
		displayName: 'Update Fields',
		name: 'updateFields',
		type: 'collection',
		description: 'The fields to update.',
		placeholder: 'Add Field',
		displayOptions: {
			show: {
				operation: [
					'update',
				],
				resource: [
					'deal',
				],
			},
		},
		default: {},
		options: [
			{
				displayName: 'Title',
				name: 'title',
				type: 'string',
				default: '',
				description: 'The title of the deal',
			},
			{
				displayName: 'Deal\'s contact ID',
				name: 'contact',
				type: 'number',
				default: 0,
				description: 'The ID of the deal\'s contact',
			},
			{
				displayName: 'Deal value',
				name: 'value',
				type: 'number',
				default: 0,
				description: 'The value of the deal in cents',
			},
			{
				displayName: 'Currency',
				name: 'currency',
				type: 'options',
				options: allCurrencies,
				default: 'eur',
				description: 'The currency of the deal in 3-character ISO format',
			},
			{
				displayName: 'Description',
				name: 'description',
				type: 'string',
				default: '',
				description: 'The description of the deal',
			},
			{
				displayName: 'Deal pipeline ID',
				name: 'group',
				type: 'string',
				default: '',
				description: 'The pipeline ID of the deal',
			},
			{
				displayName: 'Deal stage ID',
				name: 'stage',
				type: 'string',
				default: '',
				description: 'The stage ID of the deal',
			},
			{
				displayName: 'Deal owner ID',
				name: 'owner',
				type: 'string',
				default: '',
				description: 'The owner ID of the deal',
			},
			{
				displayName: 'Deal percentage',
				name: 'percent',
				type: 'number',
				default: 0,
				description: 'The percentage of the deal',
			},
			{
				displayName: 'Deal status',
				name: 'status',
				type: 'number',
				default: 0,
				description: 'The status of the deal',
			},
		],
	},

	// ----------------------------------
	//         deal:delete
	// ----------------------------------
	{
		displayName: 'Deal ID',
		name: 'dealId',
		type: 'number',
		default: 0,
		required: true,
		displayOptions: {
			show: {
				operation: [
					'delete',
				],
				resource: [
					'deal',
				],
			},
		},
		description: 'The ID of the deal to delete.',
	},

	// ----------------------------------
	//         deal:get
	// ----------------------------------
	{
		displayName: 'Deal ID',
		name: 'dealId',
		type: 'number',
		default: 0,
		required: true,
		displayOptions: {
			show: {
				operation: [
					'get',
				],
				resource: [
					'deal',
				],
			},
		},
		description: 'The ID of the deal to get.',
	},

	// ----------------------------------
	//         deal:getAll
	// ----------------------------------
	...activeCampaignDefaultGetAllProperties('deal', 'getAll'),

	// ----------------------------------
	//         dealNote:create
	// ----------------------------------
	{
		displayName: 'Deal ID',
		name: 'dealId',
		type: 'number',
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: [
					'createNote',
				],
				resource: [
					'deal',
				],
			},
		},
		description: 'The ID of the deal note',
	},
	{
		displayName: 'Deal Note',
		name: 'dealNote',
		type: 'string',
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: [
					'createNote',
				],
				resource: [
					'deal',
				],
			},
		},
		description: 'The content of the deal note',
	},

	// ----------------------------------
	//         dealNote:update
	// ----------------------------------
	{
		displayName: 'Deal ID',
		name: 'dealId',
		type: 'number',
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: [
					'updateNote',
				],
				resource: [
					'deal',
				],
			},
		},
		description: 'The ID of the deal note',
	},
	{
		displayName: 'Deal note ID',
		name: 'dealNoteId',
		type: 'number',
		default: '',
		required: true,
		displayOptions: {
			show: {
				operation: [
					'updateNote',
				],
				resource: [
					'deal',
				],
			},
		},
		description: 'The ID of the deal note',
	},
	{
		displayName: 'Deal Note',
		name: 'dealNote',
		type: 'string',
		default: '',
		displayOptions: {
			show: {
				operation: [
					'updateNote',
				],
				resource: [
					'deal',
				],
			},
		},
		description: 'The content of the deal note',
	},

] as INodeProperties[];


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

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

import {
	countriesCodes
} from './CountriesCodes';

import {
	conversationFields,
	conversationOperations,
} from './ConversationDescription';

import {
	customerFields,
	customerOperations,
} from './CustomerDescription';

import {
	ICustomer,
} from './CustomerInterface';

import {
	IConversation,
} from './ConversationInterface';

import {
	helpscoutApiRequest,
	helpscoutApiRequestAllItems,
} from './GenericFunctions';

import {
	mailboxFields,
	mailboxOperations,
} from './MailboxDescription';

import {
	threadFields,
	threadOperations,
} from './ThreadDescription';

import {
	IAttachment,
	IThread,
} from './ThreadInterface';

export class HelpScout implements INodeType {
	description: INodeTypeDescription = {
		displayName: 'HelpScout',
		name: 'helpScout',
		icon: 'file:helpScout.svg',
		group: ['input'],
		version: 1,
		subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
		description: 'Consume Help Scout API.',
		defaults: {
			name: 'HelpScout',
			color: '#1392ee',
		},
		inputs: ['main'],
		outputs: ['main'],
		credentials: [
			{
				name: 'helpScoutOAuth2Api',
				required: true,
			},
		],
		properties: [
			{
				displayName: 'Resource',
				name: 'resource',
				type: 'options',
				options: [
					{
						name: 'Conversation',
						value: 'conversation',
					},
					{
						name: 'Customer',
						value: 'customer',
					},
					{
						name: 'Mailbox',
						value: 'mailbox',
					},
					{
						name: 'Thread',
						value: 'thread',
					},
				],
				default: 'conversation',
				description: 'The resource to operate on.',
			},
			...conversationOperations,
			...conversationFields,
			...customerOperations,
			...customerFields,
			...mailboxOperations,
			...mailboxFields,
			...threadOperations,
			...threadFields,
		],
	};

	methods = {
		loadOptions: {
			// Get all the countries codes to display them to user so that he can
			// select them easily
			async getCountriesCodes(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
				const returnData: INodePropertyOptions[] = [];
				for (const countryCode of countriesCodes) {
					const countryCodeName = `${countryCode.name} - ${countryCode.alpha2}`;
					const countryCodeId = countryCode.alpha2;
					returnData.push({
						name: countryCodeName,
						value: countryCodeId,
					});
				}
				return returnData;
			},
			// Get all the tags to display them to user so that he can
			// select them easily
			async getTags(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
				const returnData: INodePropertyOptions[] = [];
				const tags = await helpscoutApiRequestAllItems.call(this, '_embedded.tags', 'GET', '/v2/tags');
				for (const tag of tags) {
					const tagName = tag.name;
					const tagId = tag.id;
					returnData.push({
						name: tagName,
						value: tagName,
					});
				}
				return returnData;
			},
			// Get all the mailboxes to display them to user so that he can
			// select them easily
			async getMailboxes(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
				const returnData: INodePropertyOptions[] = [];
				const mailboxes = await helpscoutApiRequestAllItems.call(this, '_embedded.mailboxes', 'GET', '/v2/mailboxes');
				for (const mailbox of mailboxes) {
					const mailboxName = mailbox.name;
					const mailboxId = mailbox.id;
					returnData.push({
						name: mailboxName,
						value: mailboxId,
					});
				}
				return returnData;
			},
		},
	};

	async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
		const items = this.getInputData();
		const returnData: IDataObject[] = [];
		const length = items.length as unknown as number;
		const qs: IDataObject = {};
		let responseData;
		const resource = this.getNodeParameter('resource', 0) as string;
		const operation = this.getNodeParameter('operation', 0) as string;
		for (let i = 0; i < length; i++) {
			if (resource === 'conversation') {
				//https://developer.helpscout.com/mailbox-api/endpoints/conversations/create
				if (operation === 'create') {
					const mailboxId = this.getNodeParameter('mailboxId', i) as number;
					const status = this.getNodeParameter('status', i) as string;
					const subject = this.getNodeParameter('subject', i) as string;
					const type = this.getNodeParameter('type', i) as string;
					const resolveData = this.getNodeParameter('resolveData', i) as boolean;
					const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
					const threads = (this.getNodeParameter('threadsUi', i) as IDataObject).threadsValues as IDataObject[];
					const body: IConversation = {
						mailboxId,
						status,
						subject,
						type,
					};
					Object.assign(body, additionalFields);
					if (additionalFields.customerId) {
						body.customer = {
							id: additionalFields.customerId,
						};
						//@ts-ignore
						delete body.customerId;
					}
					if (additionalFields.customerEmail) {
						body.customer = {
							email: additionalFields.customerEmail,
						};
						//@ts-ignore
						delete body.customerEmail;
					}
					if (body.customer === undefined) {
						throw new Error('Either customer email or customer ID must be set');
					}
					if (threads) {
						for (let i = 0; i < threads.length; i++) {
							if (threads[i].type === '' || threads[i].text === '') {
								throw new Error('Chat Threads cannot be empty');
							}
							if (threads[i].type !== 'note') {
								threads[i].customer = body.customer;
							}
						}
						body.threads = threads;
					}
					responseData = await helpscoutApiRequest.call(this, 'POST', '/v2/conversations', body, qs, undefined, { resolveWithFullResponse: true });
					const id = responseData.headers['resource-id'];
					const uri = responseData.headers.location;
					if (resolveData) {
						responseData = await helpscoutApiRequest.call(this, 'GET', '', {}, {}, uri);
					} else {
						responseData = {
							id,
							uri,
						};
					}
				}
				//https://developer.helpscout.com/mailbox-api/endpoints/conversations/delete
				if (operation === 'delete') {
					const conversationId = this.getNodeParameter('conversationId', i) as string;
					responseData = await helpscoutApiRequest.call(this, 'DELETE', `/v2/conversations/${conversationId}`);
					responseData = { success: true };
				}
				//https://developer.helpscout.com/mailbox-api/endpoints/conversations/get
				if (operation === 'get') {
					const conversationId = this.getNodeParameter('conversationId', i) as string;
					responseData = await helpscoutApiRequest.call(this, 'GET', `/v2/conversations/${conversationId}`);
				}
				//https://developer.helpscout.com/mailbox-api/endpoints/conversations/list
				if (operation === 'getAll') {
					const returnAll = this.getNodeParameter('returnAll', i) as boolean;
					const options = this.getNodeParameter('options', i) as IDataObject;
					Object.assign(qs, options);
					if (returnAll) {
						responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.conversations', 'GET', '/v2/conversations', {}, qs);
					} else {
						qs.limit = this.getNodeParameter('limit', i) as number;
						responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.conversations', 'GET', '/v2/conversations', {}, qs);
						responseData = responseData.splice(0, qs.limit);
					}
				}
			}
			if (resource === 'customer') {
				//https://developer.helpscout.com/mailbox-api/endpoints/customers/create
				if (operation === 'create') {
					const resolveData = this.getNodeParameter('resolveData', i) as boolean;
					const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
					const chats = (this.getNodeParameter('chatsUi', i) as IDataObject).chatsValues as IDataObject[];
					const address = (this.getNodeParameter('addressUi', i) as IDataObject).addressValue as IDataObject;
					const emails = (this.getNodeParameter('emailsUi', i) as IDataObject).emailsValues as IDataObject[];
					const phones = (this.getNodeParameter('phonesUi', i) as IDataObject).phonesValues as IDataObject[];
					const socialProfiles = (this.getNodeParameter('socialProfilesUi', i) as IDataObject).socialProfilesValues as IDataObject[];
					const websites = (this.getNodeParameter('websitesUi', i) as IDataObject).websitesValues as IDataObject[];
					let body: ICustomer = {};
					body = Object.assign({}, additionalFields);
					if (body.age) {
						body.age = body.age.toString();
					}
					if (chats) {
						body.chats = chats;
					}
					if (address) {
						body.address = address;
						body.address.lines = [address.line1, address.line2];
					}
					if (emails) {
						body.emails = emails;
					}
					if (phones) {
						body.phones = phones;
					}
					if (socialProfiles) {
						body.socialProfiles = socialProfiles;
					}
					if (websites) {
						body.websites = websites;
					}
					if (Object.keys(body).length === 0) {
						throw new Error('You have to set at least one field');
					}
					responseData = await helpscoutApiRequest.call(this, 'POST', '/v2/customers', body, qs, undefined, { resolveWithFullResponse: true });
					const id = responseData.headers['resource-id'];
					const uri = responseData.headers.location;
					if (resolveData) {
						responseData = await helpscoutApiRequest.call(this, 'GET', '', {}, {}, uri);
					} else {
						responseData = {
							id,
							uri,
						};
					}
				}
				//https://developer.helpscout.com/mailbox-api/endpoints/customer_properties/list
				if (operation === 'properties') {
					responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.customer-properties', 'GET', '/v2/customer-properties', {}, qs);
				}
				//https://developer.helpscout.com/mailbox-api/endpoints/customers/get
				if (operation === 'get') {
					const customerId = this.getNodeParameter('customerId', i) as string;
					responseData = await helpscoutApiRequest.call(this, 'GET', `/v2/customers/${customerId}`);
				}
				//https://developer.helpscout.com/mailbox-api/endpoints/customers/list
				if (operation === 'getAll') {
					const returnAll = this.getNodeParameter('returnAll', i) as boolean;
					const options = this.getNodeParameter('options', i) as IDataObject;
					Object.assign(qs, options);
					if (returnAll) {
						responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.customers', 'GET', '/v2/customers', {}, qs);
					} else {
						qs.limit = this.getNodeParameter('limit', i) as number;
						responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.customers', 'GET', '/v2/customers', {}, qs);
						responseData = responseData.splice(0, qs.limit);
					}
				}
				//https://developer.helpscout.com/mailbox-api/endpoints/customers/overwrite/
				if (operation === 'update') {
					const customerId = this.getNodeParameter('customerId', i) as string;
					const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
					let body: ICustomer = {};
					body = Object.assign({}, updateFields);
					if (body.age) {
						body.age = body.age.toString();
					}
					if (Object.keys(body).length === 0) {
						throw new Error('You have to set at least one field');
					}
					responseData = await helpscoutApiRequest.call(this, 'PUT', `/v2/customers/${customerId}`, body, qs, undefined, { resolveWithFullResponse: true });
					responseData = { success: true };
				}
			}
			if (resource === 'mailbox') {
				//https://developer.helpscout.com/mailbox-api/endpoints/mailboxes/get
				if (operation === 'get') {
					const mailboxId = this.getNodeParameter('mailboxId', i) as string;
					responseData = await helpscoutApiRequest.call(this, 'GET', `/v2/mailboxes/${mailboxId}`, {}, qs);
				}
				//https://developer.helpscout.com/mailbox-api/endpoints/mailboxes/list
				if (operation === 'getAll') {
					const returnAll = this.getNodeParameter('returnAll', i) as boolean;
					if (returnAll) {
						responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.mailboxes', 'GET', '/v2/mailboxes', {}, qs);
					} else {
						qs.limit = this.getNodeParameter('limit', i) as number;
						responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.mailboxes', 'GET', '/v2/mailboxes', {}, qs);
						responseData = responseData.splice(0, qs.limit);
					}
				}
			}
			if (resource === 'thread') {
				//https://developer.helpscout.com/mailbox-api/endpoints/conversations/threads/chat
				if (operation === 'create') {
					const conversationId = this.getNodeParameter('conversationId', i) as string;
					const type = this.getNodeParameter('type', i) as string;
					const text = this.getNodeParameter('text', i) as string;
					const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
					const attachments = this.getNodeParameter('attachmentsUi', i) as IDataObject;
					const body: IThread = {
						text,
						attachments: [],
					};
					Object.assign(body, additionalFields);
					if (additionalFields.customerId) {
						body.customer = {
							id: additionalFields.customerId,
						};
						//@ts-ignore
						delete body.customerId;
					}
					if (additionalFields.customerEmail) {
						body.customer = {
							email: additionalFields.customerEmail,
						};
						//@ts-ignore
						delete body.customerEmail;
					}
					if (body.customer === undefined) {
						throw new Error('Either customer email or customer ID must be set');
					}
					if (attachments) {
						if (attachments.attachmentsValues
							&& (attachments.attachmentsValues as IDataObject[]).length !== 0) {
							body.attachments?.push.apply(body.attachments, attachments.attachmentsValues as IAttachment[]);
						}
						if (attachments.attachmentsBinary
							&& (attachments.attachmentsBinary as IDataObject[]).length !== 0
							&& items[i].binary) {
							const mapFunction = (value: IDataObject): IAttachment => {
								const binaryProperty = (items[i].binary as IBinaryKeyData)[value.property as string];
								if (binaryProperty) {
									return {
										fileName: binaryProperty.fileName || 'unknown',
										data: binaryProperty.data,
										mimeType: binaryProperty.mimeType,
									};
								} else {
									throw new Error(`Binary property ${value.property} does not exist on input`);
								}
							};
							body.attachments?.push.apply(body.attachments, (attachments.attachmentsBinary as IDataObject[]).map(mapFunction) as IAttachment[]);
						}
					}
					responseData = await helpscoutApiRequest.call(this, 'POST', `/v2/conversations/${conversationId}/chats`, body);
					responseData = { success: true };
				}
				//https://developer.helpscout.com/mailbox-api/endpoints/conversations/threads/list
				if (operation === 'getAll') {
					const returnAll = this.getNodeParameter('returnAll', i) as boolean;
					const conversationId = this.getNodeParameter('conversationId', i) as string;
					if (returnAll) {
						responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.threads', 'GET', `/v2/conversations/${conversationId}/threads`);
					} else {
						qs.limit = this.getNodeParameter('limit', i) as number;
						responseData = await helpscoutApiRequestAllItems.call(this, '_embedded.threads', 'GET', `/v2/conversations/${conversationId}/threads`, {}, qs);
						responseData = responseData.splice(0, qs.limit);
					}
				}
			}
			if (Array.isArray(responseData)) {
				returnData.push.apply(returnData, responseData as IDataObject[]);
			} else if (responseData !== undefined) {
				returnData.push(responseData as IDataObject);
			}
		}
		return [this.helpers.returnJsonArray(returnData)];
	}
}


keep keep keep keep add keep keep keep keep

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

export class UrlScanIoApi implements ICredentialType {
	name = 'urlScanIoApi';
	displayName = 'urlscan.io API';
	documentationUrl = 'urlScanIo';
	properties: INodeProperties[] = [
		{
			displayName: 'API Key',
			name: 'apiKey',
			type: 'string',
			default: '',
			required: true,
		},
	];
	authenticate: IAuthenticateGeneric = {
		type: 'generic',
		properties: {
			headers: {
				'API-KEY': '={{$credentials.apiKey}}',
			},
		},
	} as IAuthenticateGeneric;

	test: ICredentialTestRequest = {
		request: {
			baseURL: 'https://urlscan.io',
			url: '/user/quotas',
		},
	};
}