n8n-nodes-hudu 1.6.1 → 1.6.3

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.
@@ -7,9 +7,11 @@ import {
7
7
  handleDeleteOperation,
8
8
  } from '../../utils/operations';
9
9
  import type { FilterMapping } from '../../utils/types';
10
- import type { FolderOperation, IFolderPostProcessFilters, IFolder, IFolderPathResponse } from './folders.types';
10
+ import type { FolderOperation, IFolderPostProcessFilters, IFolderPathResponse, IFolder } from './folders.types';
11
11
  import { folderFilterMapping } from './folders.types';
12
12
  import { HUDU_API_CONSTANTS } from '../../utils/constants';
13
+ import { buildFolderPath } from '../../utils/folderUtils';
14
+ import type { ICompany } from '../companies/companies.types';
13
15
 
14
16
  export async function handleFolderOperation(
15
17
  this: IExecuteFunctions,
@@ -104,23 +106,64 @@ export async function handleFolderOperation(
104
106
 
105
107
  case 'getPath': {
106
108
  const folderId = this.getNodeParameter('folderId', i) as string;
107
- const folders: IFolder[] = [];
108
- let currentFolderId: string | null = folderId;
109
-
110
- // Recursively get all folders in the path
111
- while (currentFolderId) {
112
- const folderData = await handleGetOperation.call(this, resourceEndpoint, currentFolderId, 'folder') as IDataObject;
113
- const folder = folderData.folder as IFolder;
114
- folders.unshift(folder); // Add to start of array to maintain correct order
115
- currentFolderId = folder.parent_folder_id ? folder.parent_folder_id.toString() : null;
116
- }
117
109
 
118
- // Build the path string
119
- const path = folders.map(folder => folder.name).join(' / ');
110
+ // Separator options
111
+ const separatorOption = this.getNodeParameter('separator', i, '/') as string;
112
+ const customSeparator = this.getNodeParameter('customSeparator', i, '') as string;
113
+ const prependCompanyToFolderPath = this.getNodeParameter(
114
+ 'prependCompanyToFolderPath',
115
+ i,
116
+ false,
117
+ ) as boolean;
118
+
119
+ const { path } = await buildFolderPath(
120
+ this,
121
+ folderId,
122
+ separatorOption,
123
+ customSeparator,
124
+ );
125
+
126
+ let finalPath = path;
127
+
128
+ if (prependCompanyToFolderPath) {
129
+ let companyLabel = 'Central KB';
130
+
131
+ try {
132
+ const folder = (await handleGetOperation.call(
133
+ this,
134
+ resourceEndpoint,
135
+ folderId,
136
+ 'folder',
137
+ )) as IFolder;
138
+
139
+ if (folder && folder.company_id) {
140
+ try {
141
+ const company = (await handleGetOperation.call(
142
+ this,
143
+ '/companies',
144
+ folder.company_id,
145
+ 'company',
146
+ )) as ICompany;
147
+
148
+ if (company && company.name) {
149
+ companyLabel = company.name;
150
+ }
151
+ } catch {
152
+ // If company lookup fails, fall back to default label
153
+ }
154
+ }
155
+ } catch {
156
+ // If folder lookup fails, keep default label and base path
157
+ }
158
+
159
+ const companySeparator =
160
+ separatorOption === 'custom' ? (customSeparator || '/') : separatorOption || '/';
161
+
162
+ finalPath = `${companyLabel}${companySeparator}${path}`;
163
+ }
120
164
 
121
165
  responseData = {
122
- path,
123
- folders,
166
+ path: finalPath,
124
167
  } as IFolderPathResponse;
125
168
  break;
126
169
  }
@@ -34,7 +34,6 @@ export const folderFilterMapping: FilterMapping<IFolderPostProcessFilters> = {
34
34
 
35
35
  export interface IFolderPathResponse extends IDataObject {
36
36
  path: string;
37
- folders: IFolder[];
38
37
  }
39
38
 
40
39
  export type FolderOperation = 'create' | 'get' | 'getAll' | 'update' | 'delete' | 'getPath';
@@ -1,100 +1,100 @@
1
- import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
2
- import { handleCreateOperation, handleDeleteOperation } from '../../utils/operations';
3
- import { huduApiRequest } from '../../utils/requestUtils';
4
- import {
5
- handleMagicDashGetAllOperation,
6
- handleMagicDashGetByIdOperation,
7
- } from '../../utils/operations/magic_dash';
8
- import type { MagicDashOperation } from './magic_dash.types';
9
- import { HUDU_API_CONSTANTS } from '../../utils/constants';
10
-
11
- export async function handleMagicDashOperation(
12
- this: IExecuteFunctions,
13
- operation: MagicDashOperation,
14
- i: number,
15
- ): Promise<IDataObject | IDataObject[]> {
16
- const resourceEndpoint = '/magic_dash';
17
- let responseData: IDataObject | IDataObject[] = {};
18
-
19
- switch (operation) {
20
- case 'getAll': {
21
- const returnAll = this.getNodeParameter('returnAll', i) as boolean;
22
- const filters = this.getNodeParameter('filters', i) as IDataObject;
23
- const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
24
-
25
- return await handleMagicDashGetAllOperation.call(this, filters, returnAll, limit as 25);
26
- }
27
-
28
- case 'get': {
29
- const id = this.getNodeParameter('id', i) as number;
30
- return await handleMagicDashGetByIdOperation.call(this, id);
31
- }
32
-
33
- case 'createOrUpdate': {
34
- const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
35
- const message = this.getNodeParameter('message', i) as string;
36
- const companyName = this.getNodeParameter('companyName', i) as string;
37
- const title = this.getNodeParameter('title', i) as string;
38
- const content = this.getNodeParameter('content', i) as string;
39
-
40
- // Build the request body
41
- const magicDashBody: IDataObject = {
42
- message,
43
- company_name: companyName,
44
- title,
45
- };
46
-
47
- // Add content if it's not empty
48
- if (content) {
49
- magicDashBody.content = content;
50
- }
51
-
52
- // Add any additional fields
53
- for (const key of Object.keys(additionalFields)) {
54
- if (additionalFields[key] !== undefined && additionalFields[key] !== '') {
55
- magicDashBody[key] = additionalFields[key];
56
- }
57
- }
58
-
59
- // Ensure mutually exclusive fields are handled
60
- // content and content_link are mutually exclusive
61
- if (magicDashBody.content && magicDashBody.content_link) {
62
- delete magicDashBody.content_link;
63
- }
64
-
65
- // icon and image_url are mutually exclusive
66
- if (magicDashBody.icon && magicDashBody.image_url) {
67
- delete magicDashBody.image_url;
68
- }
69
-
70
- // API v2.39.6 requires body to be wrapped in magic_dash_item key
71
- const requestBody = { magic_dash_item: magicDashBody };
72
-
73
- responseData = await handleCreateOperation.call(
74
- this,
75
- resourceEndpoint,
76
- requestBody,
77
- );
78
- break;
79
- }
80
-
81
- case 'deleteById': {
82
- // Note: API v2.39.6 supports two DELETE methods:
83
- // 1. DELETE /magic_dash/{id} - implemented here (delete by ID)
84
- // 2. DELETE /magic_dash - not implemented (delete by title + company_name)
85
- const id = this.getNodeParameter('id', i) as number;
86
- responseData = await handleDeleteOperation.call(this, resourceEndpoint, id);
87
- break;
88
- }
89
-
90
- case 'deleteByTitle': {
91
- const title = this.getNodeParameter('title', i) as string;
92
- const companyName = this.getNodeParameter('companyName', i) as string;
93
- const body = { title, company_name: companyName } as IDataObject;
94
- responseData = await huduApiRequest.call(this, 'DELETE', resourceEndpoint, body);
95
- break;
96
- }
97
- }
98
-
99
- return responseData;
100
- }
1
+ import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
2
+ import { handleCreateOperation, handleDeleteOperation } from '../../utils/operations';
3
+ import { huduApiRequest } from '../../utils/requestUtils';
4
+ import {
5
+ handleMagicDashGetAllOperation,
6
+ handleMagicDashGetByIdOperation,
7
+ } from '../../utils/operations/magic_dash';
8
+ import type { MagicDashOperation } from './magic_dash.types';
9
+ import { HUDU_API_CONSTANTS } from '../../utils/constants';
10
+
11
+ export async function handleMagicDashOperation(
12
+ this: IExecuteFunctions,
13
+ operation: MagicDashOperation,
14
+ i: number,
15
+ ): Promise<IDataObject | IDataObject[]> {
16
+ const resourceEndpoint = '/magic_dash';
17
+ let responseData: IDataObject | IDataObject[] = {};
18
+
19
+ switch (operation) {
20
+ case 'getAll': {
21
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
22
+ const filters = this.getNodeParameter('filters', i) as IDataObject;
23
+ const limit = this.getNodeParameter('limit', i, HUDU_API_CONSTANTS.PAGE_SIZE) as number;
24
+
25
+ return await handleMagicDashGetAllOperation.call(this, filters, returnAll, limit as 25);
26
+ }
27
+
28
+ case 'get': {
29
+ const id = this.getNodeParameter('id', i) as number;
30
+ return await handleMagicDashGetByIdOperation.call(this, id);
31
+ }
32
+
33
+ case 'createOrUpdate': {
34
+ const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
35
+ const message = this.getNodeParameter('message', i) as string;
36
+ const companyName = this.getNodeParameter('companyName', i) as string;
37
+ const title = this.getNodeParameter('title', i) as string;
38
+ const content = this.getNodeParameter('content', i) as string;
39
+
40
+ // Build the request body
41
+ const magicDashBody: IDataObject = {
42
+ message,
43
+ company_name: companyName,
44
+ title,
45
+ };
46
+
47
+ // Add content if it's not empty
48
+ if (content) {
49
+ magicDashBody.content = content;
50
+ }
51
+
52
+ // Add any additional fields
53
+ for (const key of Object.keys(additionalFields)) {
54
+ if (additionalFields[key] !== undefined && additionalFields[key] !== '') {
55
+ magicDashBody[key] = additionalFields[key];
56
+ }
57
+ }
58
+
59
+ // Ensure mutually exclusive fields are handled
60
+ // content and content_link are mutually exclusive
61
+ if (magicDashBody.content && magicDashBody.content_link) {
62
+ delete magicDashBody.content_link;
63
+ }
64
+
65
+ // icon and image_url are mutually exclusive
66
+ if (magicDashBody.icon && magicDashBody.image_url) {
67
+ delete magicDashBody.image_url;
68
+ }
69
+
70
+ // API v2.39.6 requires body to be wrapped in magic_dash_item key
71
+ const requestBody = { magic_dash_item: magicDashBody };
72
+
73
+ responseData = await handleCreateOperation.call(
74
+ this,
75
+ resourceEndpoint,
76
+ requestBody,
77
+ );
78
+ break;
79
+ }
80
+
81
+ case 'deleteById': {
82
+ // Note: API v2.39.6 supports two DELETE methods:
83
+ // 1. DELETE /magic_dash/{id} - implemented here (delete by ID)
84
+ // 2. DELETE /magic_dash - not implemented (delete by title + company_name)
85
+ const id = this.getNodeParameter('id', i) as number;
86
+ responseData = await handleDeleteOperation.call(this, resourceEndpoint, id);
87
+ break;
88
+ }
89
+
90
+ case 'deleteByTitle': {
91
+ const title = this.getNodeParameter('title', i) as string;
92
+ const companyName = this.getNodeParameter('companyName', i) as string;
93
+ const body = { title, company_name: companyName } as IDataObject;
94
+ responseData = await huduApiRequest.call(this, 'DELETE', resourceEndpoint, body);
95
+ break;
96
+ }
97
+ }
98
+
99
+ return responseData;
100
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildFolderPath = buildFolderPath;
4
+ const operations_1 = require("./operations");
5
+ async function buildFolderPath(context, folderId, separatorOption, customSeparator) {
6
+ const separator = separatorOption === 'custom' ? (customSeparator || '/') : separatorOption || '/';
7
+ const folders = [];
8
+ const visitedIds = new Set();
9
+ let currentFolderId = folderId;
10
+ let depth = 0;
11
+ const MAX_DEPTH = 100;
12
+ const resourceEndpoint = '/folders';
13
+ while (currentFolderId && depth < MAX_DEPTH) {
14
+ const folder = (await operations_1.handleGetOperation.call(context, resourceEndpoint, currentFolderId, 'folder'));
15
+ if (!folder || typeof folder.id !== 'number') {
16
+ break;
17
+ }
18
+ if (visitedIds.has(folder.id)) {
19
+ break;
20
+ }
21
+ visitedIds.add(folder.id);
22
+ folders.unshift(folder);
23
+ currentFolderId =
24
+ folder.parent_folder_id !== undefined && folder.parent_folder_id !== null
25
+ ? folder.parent_folder_id.toString()
26
+ : null;
27
+ depth += 1;
28
+ }
29
+ const path = folders.map((folder) => folder.name).join(separator);
30
+ return {
31
+ path,
32
+ };
33
+ }
34
+ //# sourceMappingURL=folderUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folderUtils.js","sourceRoot":"","sources":["../../../../../src/nodes/Hudu/utils/folderUtils.ts"],"names":[],"mappings":";;AAIA,0CAkDC;AApDD,6CAAkD;AAE3C,KAAK,UAAU,eAAe,CACnC,OAA0B,EAC1B,QAAgB,EAChB,eAAuB,EACvB,eAAuB;IAEvB,MAAM,SAAS,GACb,eAAe,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,eAAe,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,eAAe,IAAI,GAAG,CAAC;IAEnF,MAAM,OAAO,GAAc,EAAE,CAAC;IAC9B,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,IAAI,eAAe,GAAkB,QAAQ,CAAC;IAC9C,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,SAAS,GAAG,GAAG,CAAC;IACtB,MAAM,gBAAgB,GAAG,UAAU,CAAC;IAGpC,OAAO,eAAe,IAAI,KAAK,GAAG,SAAS,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,CAAC,MAAM,+BAAkB,CAAC,IAAI,CAC3C,OAAO,EACP,gBAAgB,EAChB,eAAe,EACf,QAAQ,CACT,CAAY,CAAC;QAEd,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ,EAAE,CAAC;YAC7C,MAAM;QACR,CAAC;QAED,IAAI,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;YAE9B,MAAM;QACR,CAAC;QAED,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC1B,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAExB,eAAe;YACb,MAAM,CAAC,gBAAgB,KAAK,SAAS,IAAI,MAAM,CAAC,gBAAgB,KAAK,IAAI;gBACvE,CAAC,CAAC,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE;gBACpC,CAAC,CAAC,IAAI,CAAC;QAEX,KAAK,IAAI,CAAC,CAAC;IACb,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAElE,OAAO;QACL,IAAI;KACL,CAAC;AACJ,CAAC"}
@@ -0,0 +1,57 @@
1
+ import type { IExecuteFunctions } from 'n8n-workflow';
2
+ import type { IFolder, IFolderPathResponse } from '../resources/folders/folders.types';
3
+ import { handleGetOperation } from './operations';
4
+
5
+ export async function buildFolderPath(
6
+ context: IExecuteFunctions,
7
+ folderId: string,
8
+ separatorOption: string,
9
+ customSeparator: string,
10
+ ): Promise<IFolderPathResponse> {
11
+ const separator =
12
+ separatorOption === 'custom' ? (customSeparator || '/') : separatorOption || '/';
13
+
14
+ const folders: IFolder[] = [];
15
+ const visitedIds = new Set<number>();
16
+ let currentFolderId: string | null = folderId;
17
+ let depth = 0;
18
+ const MAX_DEPTH = 100;
19
+ const resourceEndpoint = '/folders';
20
+
21
+ // Recursively get all folders in the path
22
+ while (currentFolderId && depth < MAX_DEPTH) {
23
+ const folder = (await handleGetOperation.call(
24
+ context,
25
+ resourceEndpoint,
26
+ currentFolderId,
27
+ 'folder',
28
+ )) as IFolder;
29
+
30
+ if (!folder || typeof folder.id !== 'number') {
31
+ break;
32
+ }
33
+
34
+ if (visitedIds.has(folder.id)) {
35
+ // Prevent potential circular references
36
+ break;
37
+ }
38
+
39
+ visitedIds.add(folder.id);
40
+ folders.unshift(folder); // Add to start of array to maintain correct order
41
+
42
+ currentFolderId =
43
+ folder.parent_folder_id !== undefined && folder.parent_folder_id !== null
44
+ ? folder.parent_folder_id.toString()
45
+ : null;
46
+
47
+ depth += 1;
48
+ }
49
+
50
+ const path = folders.map((folder) => folder.name).join(separator);
51
+
52
+ return {
53
+ path,
54
+ };
55
+ }
56
+
57
+
@@ -1,69 +1,69 @@
1
- import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2
- import { huduApiRequest } from '../requestUtils';
3
- import { HUDU_API_CONSTANTS } from '../constants';
4
-
5
- export async function handleMagicDashGetAllOperation(
6
- this: IExecuteFunctions,
7
- filters: IDataObject = {},
8
- returnAll = false,
9
- limit = HUDU_API_CONSTANTS.PAGE_SIZE,
10
- ): Promise<IDataObject[]> {
11
- let allItems: IDataObject[] = [];
12
- let page = 1;
13
- const pageSize = HUDU_API_CONSTANTS.PAGE_SIZE;
14
-
15
- let hasMorePages = true;
16
- do {
17
- const qs: IDataObject = {
18
- page,
19
- page_size: pageSize,
20
- };
21
-
22
- // Handle filters
23
- if (filters.company_id) {
24
- qs.company_id = Number.parseInt(filters.company_id as string, 10);
25
- }
26
- if (filters.title) {
27
- qs.title = filters.title;
28
- }
29
-
30
- const response = await huduApiRequest.call(
31
- this,
32
- 'GET',
33
- '/magic_dash',
34
- {},
35
- qs,
36
- );
37
-
38
- const items = Array.isArray(response) ? response : [];
39
- allItems.push(...items);
40
-
41
- if (items.length < pageSize || (!returnAll && allItems.length >= limit)) {
42
- hasMorePages = false;
43
- } else {
44
- page++;
45
- }
46
- } while (hasMorePages);
47
-
48
- if (!returnAll) {
49
- allItems = allItems.slice(0, limit);
50
- }
51
-
52
- return allItems;
53
- }
54
-
55
- export async function handleMagicDashGetByIdOperation(
56
- this: IExecuteFunctions,
57
- id: number,
58
- ): Promise<IDataObject> {
59
- // Use the getAll operation to fetch all items, ensuring pagination is handled.
60
- const allItems = await handleMagicDashGetAllOperation.call(this, {}, true);
61
-
62
- const item = allItems.find((item) => item.id === id);
63
-
64
- if (!item) {
65
- throw new Error(`Magic Dash item with ID ${id} not found.`);
66
- }
67
-
68
- return item;
1
+ import type { IExecuteFunctions, IDataObject } from 'n8n-workflow';
2
+ import { huduApiRequest } from '../requestUtils';
3
+ import { HUDU_API_CONSTANTS } from '../constants';
4
+
5
+ export async function handleMagicDashGetAllOperation(
6
+ this: IExecuteFunctions,
7
+ filters: IDataObject = {},
8
+ returnAll = false,
9
+ limit = HUDU_API_CONSTANTS.PAGE_SIZE,
10
+ ): Promise<IDataObject[]> {
11
+ let allItems: IDataObject[] = [];
12
+ let page = 1;
13
+ const pageSize = HUDU_API_CONSTANTS.PAGE_SIZE;
14
+
15
+ let hasMorePages = true;
16
+ do {
17
+ const qs: IDataObject = {
18
+ page,
19
+ page_size: pageSize,
20
+ };
21
+
22
+ // Handle filters
23
+ if (filters.company_id) {
24
+ qs.company_id = Number.parseInt(filters.company_id as string, 10);
25
+ }
26
+ if (filters.title) {
27
+ qs.title = filters.title;
28
+ }
29
+
30
+ const response = await huduApiRequest.call(
31
+ this,
32
+ 'GET',
33
+ '/magic_dash',
34
+ {},
35
+ qs,
36
+ );
37
+
38
+ const items = Array.isArray(response) ? response : [];
39
+ allItems.push(...items);
40
+
41
+ if (items.length < pageSize || (!returnAll && allItems.length >= limit)) {
42
+ hasMorePages = false;
43
+ } else {
44
+ page++;
45
+ }
46
+ } while (hasMorePages);
47
+
48
+ if (!returnAll) {
49
+ allItems = allItems.slice(0, limit);
50
+ }
51
+
52
+ return allItems;
53
+ }
54
+
55
+ export async function handleMagicDashGetByIdOperation(
56
+ this: IExecuteFunctions,
57
+ id: number,
58
+ ): Promise<IDataObject> {
59
+ // Use the getAll operation to fetch all items, ensuring pagination is handled.
60
+ const allItems = await handleMagicDashGetAllOperation.call(this, {}, true);
61
+
62
+ const item = allItems.find((item) => item.id === id);
63
+
64
+ if (!item) {
65
+ throw new Error(`Magic Dash item with ID ${id} not found.`);
66
+ }
67
+
68
+ return item;
69
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-hudu",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "This n8n custom node facilitates integration with Hudu's API.",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",