n8n-nodes-clientify 0.2.14 → 0.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
- import { ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
1
+ import { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from "n8n-workflow";
2
2
  export declare class ClientifyApi implements ICredentialType {
3
3
  name: string;
4
4
  displayName: string;
5
5
  documentationUrl: string;
6
- authenticate: ICredentialType['authenticate'];
6
+ authenticate: IAuthenticateGeneric;
7
7
  test: ICredentialTestRequest;
8
8
  properties: INodeProperties[];
9
9
  }
@@ -3,46 +3,44 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ClientifyApi = void 0;
4
4
  class ClientifyApi {
5
5
  constructor() {
6
- this.name = 'clientifyApi';
7
- this.displayName = 'Clientify API';
8
- this.documentationUrl = 'https://newapi.clientify.com/';
6
+ this.name = "clientifyApi";
7
+ this.displayName = "Clientify API";
8
+ this.documentationUrl = "https://newapi.clientify.com/";
9
9
  this.authenticate = {
10
- type: 'generic',
10
+ type: "generic",
11
11
  properties: {
12
12
  headers: {
13
- Authorization: '=Token {{$credentials.apiKey}}',
13
+ Authorization: "=Token {{$credentials.apiKey}}",
14
14
  },
15
15
  },
16
16
  };
17
17
  this.test = {
18
18
  request: {
19
- // NOTE: n8n’s automated verification expects a literal baseURL here (not an expression).
20
- // Runtime requests still use the user-provided `baseUrl` credential value.
21
- baseURL: 'https://api-plus.clientify.com/v2',
22
- url: '/me/?fields=id,email',
23
- method: 'GET',
19
+ baseURL: "={{$credentials.baseUrl}}",
20
+ url: "/me/?fields=id,email",
21
+ method: "GET",
24
22
  },
25
23
  };
26
24
  this.properties = [
27
25
  {
28
- displayName: 'API Key',
29
- name: 'apiKey',
30
- type: 'string',
26
+ displayName: "API Key",
27
+ name: "apiKey",
28
+ type: "string",
31
29
  typeOptions: {
32
30
  password: true,
33
31
  },
34
- default: '',
32
+ default: "",
35
33
  required: true,
36
- placeholder: 'Enter your Clientify API key',
37
- description: 'Clientify API key (used as \"Authorization: Token <apiKey>\")',
34
+ placeholder: "Enter your Clientify API key",
35
+ description: 'Clientify API key (used as "Authorization: Token <apiKey>")',
38
36
  },
39
37
  {
40
- displayName: 'Base URL',
41
- name: 'baseUrl',
42
- type: 'string',
43
- default: 'https://api-plus.clientify.com/v2',
44
- placeholder: 'https://api-plus.clientify.com/v2',
45
- description: 'Optional override for the Clientify API base URL',
38
+ displayName: "Base URL",
39
+ name: "baseUrl",
40
+ type: "string",
41
+ default: "https://api-plus.clientify.com/v2",
42
+ placeholder: "https://api-plus.clientify.com/v2",
43
+ description: "Optional override for the Clientify API base URL",
46
44
  },
47
45
  ];
48
46
  }
@@ -1,4 +1,4 @@
1
- import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
1
+ import { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from "n8n-workflow";
2
2
  export declare class ClientifyApi implements INodeType {
3
3
  description: INodeTypeDescription;
4
4
  execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
@@ -6,35 +6,45 @@ const ClientifyApiCatalog_1 = require("./ClientifyApiCatalog");
6
6
  class ClientifyApi {
7
7
  constructor() {
8
8
  this.description = {
9
- displayName: 'Clientify',
10
- name: 'clientifyApi',
11
- icon: 'file:clientify.svg',
12
- group: ['transform'],
9
+ displayName: "Clientify",
10
+ name: "clientifyApi",
11
+ icon: "file:clientify.svg",
12
+ group: ["transform"],
13
13
  version: 1,
14
- subtitle: '={{$parameter["operation"]}}',
15
- description: 'Clientify CRM (direct REST API)',
14
+ subtitle: '={{$parameter["resource"] + " · " + $parameter["operation"]}}',
15
+ description: "Clientify CRM (direct REST API)",
16
16
  defaults: {
17
- name: 'Clientify',
17
+ name: "Clientify",
18
18
  },
19
- inputs: ['main'],
20
- outputs: ['main'],
19
+ inputs: ["main"],
20
+ outputs: ["main"],
21
21
  credentials: [
22
22
  {
23
- name: 'clientifyApi',
23
+ name: "clientifyApi",
24
24
  required: true,
25
25
  },
26
26
  ],
27
27
  properties: (() => {
28
28
  const props = [
29
29
  {
30
- displayName: 'Action',
31
- name: 'operation',
32
- type: 'options',
30
+ displayName: "Resource",
31
+ name: "resource",
32
+ type: "options",
33
+ options: ClientifyApiCatalog_1.resourceOptions,
34
+ default: "auto",
35
+ required: true,
36
+ noDataExpression: true,
37
+ description: "Choose a resource first, then pick an action",
38
+ },
39
+ {
40
+ displayName: "Action",
41
+ name: "operation",
42
+ type: "options",
33
43
  options: ClientifyApiCatalog_1.operationOptions,
34
- default: 'GetCurrentUser',
44
+ default: "GetCurrentUser",
35
45
  required: true,
36
46
  noDataExpression: true,
37
- description: 'Select the Clientify action to execute (mirrors the AppMixer connector action list)',
47
+ description: "Select the Clientify action to execute (mirrors the AppMixer connector action list)",
38
48
  },
39
49
  ...ClientifyApiCatalog_1.operationFields,
40
50
  ];
@@ -45,11 +55,11 @@ class ClientifyApi {
45
55
  async execute() {
46
56
  const items = this.getInputData();
47
57
  const returnData = [];
48
- const credentials = await this.getCredentials('clientifyApi');
49
- const apiKey = credentials.apiKey;
50
- const baseUrl = credentials.baseUrl || 'https://api-plus.clientify.com/v2';
58
+ const credentials = await this.getCredentials("clientifyApi");
59
+ const baseUrl = credentials.baseUrl || "https://api-plus.clientify.com/v2";
51
60
  for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
52
- const operation = this.getNodeParameter('operation', itemIndex);
61
+ const selectedResource = this.getNodeParameter("resource", itemIndex, "auto");
62
+ const operation = this.getNodeParameter("operation", itemIndex);
53
63
  const def = ClientifyApiCatalog_1.operationDefinitions[operation];
54
64
  if (!def) {
55
65
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation "${operation}". Ensure the operation catalog is present in this node package build output.`);
@@ -65,17 +75,18 @@ class ClientifyApi {
65
75
  continue;
66
76
  }
67
77
  // Apply per-operation defaults if user left the field empty.
68
- if ((value === '' || value === null || value === undefined) && fieldName in def.fieldDefaults) {
78
+ if ((value === "" || value === null || value === undefined) &&
79
+ fieldName in def.fieldDefaults) {
69
80
  value = def.fieldDefaults[fieldName];
70
81
  }
71
82
  // Avoid accidentally sending empty optional values (n8n defaults).
72
83
  const isRequired = def.requiredFieldNames.includes(fieldName);
73
84
  if (!isRequired) {
74
- if (value === '' || value === null || value === undefined)
85
+ if (value === "" || value === null || value === undefined)
75
86
  continue;
76
- if (typeof value === 'number' && value === 0)
87
+ if (typeof value === "number" && value === 0)
77
88
  continue;
78
- if (typeof value === 'boolean' && value === false)
89
+ if (typeof value === "boolean" && value === false)
79
90
  continue;
80
91
  }
81
92
  input[fieldName] = value;
@@ -83,35 +94,39 @@ class ClientifyApi {
83
94
  // Validate required fields are present (avoid confusing API errors).
84
95
  for (const fieldName of def.requiredFieldNames) {
85
96
  const value = input[fieldName];
86
- if (value === undefined || value === null || value === '') {
97
+ if (value === undefined || value === null || value === "") {
87
98
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `${fieldName} is required`);
88
99
  }
89
- if (typeof value === 'number' && value <= 0) {
100
+ if (typeof value === "number" && value <= 0) {
90
101
  throw new n8n_workflow_1.NodeOperationError(this.getNode(), `${fieldName} must be a positive number`);
91
102
  }
92
103
  }
93
104
  const url = (0, ClientifyApiCatalog_1.renderPathTemplate)(def.pathTemplate, input);
94
105
  const rest = (0, ClientifyApiCatalog_1.omitKeys)(input, def.pathParamNames);
95
- const isQueryMethod = def.method === 'GET' || def.method === 'DELETE';
96
- const qs = isQueryMethod ? Object.assign(Object.assign({}, (def.fixedQuery || {})), rest) : undefined;
97
- const body = !isQueryMethod && Object.keys(rest).length > 0 ? rest : undefined;
98
- const result = await this.helpers.httpRequest({
106
+ const isQueryMethod = def.method === "GET" || def.method === "DELETE";
107
+ const qs = isQueryMethod
108
+ ? Object.assign(Object.assign({}, (def.fixedQuery || {})), rest)
109
+ : undefined;
110
+ const body = !isQueryMethod && Object.keys(rest).length > 0
111
+ ? rest
112
+ : undefined;
113
+ const result = await this.helpers.httpRequestWithAuthentication.call(this, "clientifyApi", {
99
114
  method: def.method,
100
115
  url: `${baseUrl}${url}`,
101
116
  qs,
102
117
  body,
103
118
  json: true,
104
- headers: {
105
- Authorization: `Token ${apiKey}`,
106
- },
107
119
  });
108
- const normalized = result === undefined || result === null || result === ''
120
+ const normalized = result === undefined || result === null || result === ""
109
121
  ? { ok: true }
110
- : typeof result === 'object'
122
+ : typeof result === "object"
111
123
  ? result
112
124
  : { data: result };
113
125
  returnData.push({
114
126
  json: Object.assign(Object.assign({}, normalized), { _meta: {
127
+ resource: selectedResource === "auto"
128
+ ? (0, ClientifyApiCatalog_1.getResourceForOperation)(operation)
129
+ : selectedResource,
115
130
  operation,
116
131
  method: def.method,
117
132
  path: url,
@@ -1,9 +1,21 @@
1
- import { INodeProperties } from 'n8n-workflow';
2
- import { operationDefinitions } from './operations.generated';
1
+ import { INodeProperties } from "n8n-workflow";
2
+ import { operationDefinitions } from "./operations.generated";
3
+ type ClientifyResource = "auto" | "companies" | "contacts" | "tasks" | "users" | "misc";
4
+ export declare function getResourceForOperation(operation: string): Exclude<ClientifyResource, "auto">;
5
+ export declare const resourceOptions: {
6
+ name: string;
7
+ value: string;
8
+ description: string;
9
+ }[];
3
10
  export declare const operationOptions: {
4
11
  name: string;
5
12
  value: string;
6
13
  description: string;
14
+ displayOptions: {
15
+ show: {
16
+ resource: string[];
17
+ };
18
+ };
7
19
  }[];
8
20
  export declare const operationFields: INodeProperties[];
9
21
  export declare function renderPathTemplate(pathTemplate: string, input: Record<string, unknown>): string;
@@ -1,28 +1,75 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.operationDefinitions = exports.operationFields = exports.operationOptions = void 0;
3
+ exports.operationDefinitions = exports.operationFields = exports.operationOptions = exports.resourceOptions = void 0;
4
+ exports.getResourceForOperation = getResourceForOperation;
4
5
  exports.renderPathTemplate = renderPathTemplate;
5
6
  exports.omitKeys = omitKeys;
6
7
  const operations_generated_1 = require("./operations.generated");
7
8
  Object.defineProperty(exports, "operationDefinitions", { enumerable: true, get: function () { return operations_generated_1.operationDefinitions; } });
8
9
  function mapInspectorTypeToN8nType(type) {
9
- if (type === 'number')
10
- return 'number';
11
- if (type === 'toggle')
12
- return 'boolean';
13
- return 'string';
10
+ if (type === "number")
11
+ return "number";
12
+ if (type === "toggle")
13
+ return "boolean";
14
+ return "string";
14
15
  }
16
+ function inferResourceFromPath(pathTemplate) {
17
+ if (pathTemplate.startsWith("/companies/"))
18
+ return "companies";
19
+ if (pathTemplate.startsWith("/contacts/"))
20
+ return "contacts";
21
+ if (pathTemplate.startsWith("/tasks/"))
22
+ return "tasks";
23
+ if (pathTemplate.startsWith("/users/"))
24
+ return "users";
25
+ return "misc";
26
+ }
27
+ const resourceLabels = {
28
+ companies: "Companies",
29
+ contacts: "Contacts",
30
+ tasks: "Tasks",
31
+ users: "Users",
32
+ misc: "Misc",
33
+ };
34
+ const operationResourceMap = Object.fromEntries(Object.values(operations_generated_1.operationDefinitions).map((def) => [
35
+ def.operation,
36
+ inferResourceFromPath(def.pathTemplate),
37
+ ]));
38
+ function getResourceForOperation(operation) {
39
+ var _a;
40
+ return (_a = operationResourceMap[operation]) !== null && _a !== void 0 ? _a : "misc";
41
+ }
42
+ exports.resourceOptions = [
43
+ {
44
+ name: "Auto (Legacy Workflows)",
45
+ value: "auto",
46
+ description: "Shows all actions and keeps legacy operation-only workflows compatible",
47
+ },
48
+ ...Array.from(new Set(Object.values(operationResourceMap)))
49
+ .sort((a, b) => resourceLabels[a].localeCompare(resourceLabels[b]))
50
+ .map((resource) => ({
51
+ name: resourceLabels[resource],
52
+ value: resource,
53
+ description: `Actions related to ${resourceLabels[resource].toLowerCase()}`,
54
+ })),
55
+ ];
15
56
  exports.operationOptions = Object.values(operations_generated_1.operationDefinitions)
16
57
  .sort((a, b) => a.label.localeCompare(b.label))
17
58
  .map((def) => ({
18
59
  name: def.label,
19
60
  value: def.operation,
20
61
  description: def.description,
62
+ displayOptions: {
63
+ show: {
64
+ resource: [getResourceForOperation(def.operation), "auto"],
65
+ },
66
+ },
21
67
  }));
22
68
  exports.operationFields = (() => {
23
69
  var _a;
24
70
  const fields = [];
25
71
  for (const def of Object.values(operations_generated_1.operationDefinitions)) {
72
+ const resource = getResourceForOperation(def.operation);
26
73
  const inspectorInputs = def.inspectorInputs || {};
27
74
  const sortedFieldEntries = Object.entries(inspectorInputs).sort((a, b) => {
28
75
  var _a, _b, _c, _d;
@@ -33,16 +80,17 @@ exports.operationFields = (() => {
33
80
  for (const [fieldName, inputDef] of sortedFieldEntries) {
34
81
  const isRequired = def.requiredFieldNames.includes(fieldName);
35
82
  const n8nType = mapInspectorTypeToN8nType(inputDef === null || inputDef === void 0 ? void 0 : inputDef.type);
36
- const defaultValue = (_a = def.fieldDefaults[fieldName]) !== null && _a !== void 0 ? _a : (n8nType === 'number' ? 0 : n8nType === 'boolean' ? false : '');
83
+ const defaultValue = (_a = def.fieldDefaults[fieldName]) !== null && _a !== void 0 ? _a : (n8nType === "number" ? 0 : n8nType === "boolean" ? false : "");
37
84
  fields.push({
38
85
  displayName: (inputDef === null || inputDef === void 0 ? void 0 : inputDef.label) || fieldName,
39
86
  name: fieldName,
40
87
  type: n8nType,
41
88
  default: defaultValue,
42
89
  required: isRequired,
43
- description: (inputDef === null || inputDef === void 0 ? void 0 : inputDef.tooltip) || '',
90
+ description: (inputDef === null || inputDef === void 0 ? void 0 : inputDef.tooltip) || "",
44
91
  displayOptions: {
45
92
  show: {
93
+ resource: [resource, "auto"],
46
94
  operation: [def.operation],
47
95
  },
48
96
  },
@@ -56,7 +104,7 @@ function renderPathTemplate(pathTemplate, input) {
56
104
  const key = String(expr).trim();
57
105
  const value = input[key];
58
106
  if (value === undefined || value === null)
59
- return '';
107
+ return "";
60
108
  return encodeURIComponent(String(value));
61
109
  });
62
110
  }