n8n-nodes-hudu 1.3.0 → 1.3.1

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.
package/README.md CHANGED
@@ -1,4 +1,28 @@
1
1
  # n8n-nodes-hudu
2
+ This community node enables seamless integration with Hudu documentation platform in your n8n workflows, allowing you to automate and manage your IT documentation tasks.
3
+
4
+ ![n8n-nodes-hudu](https://img.shields.io/badge/n8n--nodes--hudu-latest-blue)
5
+ ![License](https://img.shields.io/badge/license-MIT-green)
6
+
7
+ [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-Support-yellow.svg)](https://buymeacoffee.com/msoukhomlinov)
8
+
9
+ ## Recent Changes
10
+
11
+ ### [1.3.1] - 2025-04-21
12
+ - Aligned package general content such as README and documentation
13
+
14
+ ### [1.3.0] - 2025-03-20
15
+ - Fixed missing company_id field in asset delete, archive, and unarchive operations
16
+
17
+ ### [1.2.9] - 2025-01-25
18
+ - Improved Asset operations with enhanced UI, custom asset tags support, and optimised asset creation/update
19
+ - Added streamlined workflow for using asset data between operations
20
+
21
+ ### [1.2.8] - 2025-01-21
22
+ - Fixed asset passwords create/update operations with required fields and enhanced validation
23
+ - Improved field validation and error handling for asset password operations
24
+
25
+ > **IMPORTANT**: When updating between versions, make sure to restart your n8n instance after the update. UI changes and new features are only picked up after a restart. The recommended update process is:
2
26
 
3
27
  This n8n community node enables the integration of Hudu within your n8n workflows.
4
28
 
@@ -8,19 +32,39 @@ This n8n community node enables the integration of Hudu within your n8n workflow
8
32
 
9
33
  This node was built against Hudu v2.34.4. Future versions of Hudu may not be 100% compatible without node updates.
10
34
 
35
+ [Installation](#installation)
36
+ [Credentials](#credentials)
37
+ [Features](#features)
38
+ [Supported Resources & Operations](#supported-resources--operations)
39
+ [Resources](#resources)
40
+ [Contributing](#contributing)
41
+ [Support](#support)
42
+ [License](#license)
43
+
11
44
  ## Installation
12
45
 
13
46
  Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
14
47
 
15
- ## Important Update Note
48
+ ## Credentials
49
+
50
+ To use this node, you need to:
16
51
 
17
- When updating between versions, make sure to restart your n8n instance after the update. UI changes and new features are only picked up after a restart. The recommended update process is:
52
+ 1. Have a Hudu instance
53
+ 2. Generate an API key in your Hudu settings
54
+ 3. Configure the node with:
55
+ - Base URL (e.g., https://your-hudu-instance.com)
56
+ - API Key
18
57
 
19
- 1. Update the node (`npm install n8n-nodes-hudu@latest`)
20
- 2. Restart n8n instance
21
- 3. Clear browser cache if needed
58
+ ## Features
22
59
 
23
- This is particularly important for version 1.2.9 and above where significant UI and functionality changes were introduced.
60
+ - Comprehensive pagination support for all applicable resources
61
+ - Robust error handling and debugging capabilities
62
+ - Advanced filtering options with both API-side and client-side filtering
63
+ - Support for both single and bulk operations
64
+ - Dynamic loading of related resources (companies, layouts, etc.)
65
+ - Date range filtering with preset options
66
+ - Automatic type conversion and validation
67
+ - Debug logging for troubleshooting
24
68
 
25
69
  ## Supported Resources & Operations
26
70
 
@@ -170,27 +214,6 @@ Note: Custom field support has some limitations:
170
214
  - Link to companies
171
215
  - Filter by company and status
172
216
 
173
- ## Credentials
174
-
175
- To use this node, you need to:
176
-
177
- 1. Have a Hudu instance
178
- 2. Generate an API key in your Hudu settings
179
- 3. Configure the node with:
180
- - Base URL (e.g., https://your-hudu-instance.com)
181
- - API Key
182
-
183
- ## Features
184
-
185
- - Comprehensive pagination support for all applicable resources
186
- - Robust error handling and debugging capabilities
187
- - Advanced filtering options with both API-side and client-side filtering
188
- - Support for both single and bulk operations
189
- - Dynamic loading of related resources (companies, layouts, etc.)
190
- - Date range filtering with preset options
191
- - Automatic type conversion and validation
192
- - Debug logging for troubleshooting
193
-
194
217
  ## Resources
195
218
 
196
219
  - [n8n community nodes documentation](https://docs.n8n.io/integrations/community-nodes/)
@@ -215,3 +238,13 @@ Please ensure your PR:
215
238
  - Includes tests if applicable
216
239
 
217
240
  For bug reports or feature requests, please use the GitHub issues section.
241
+
242
+ ## Support
243
+
244
+ If you find this node helpful and would like to support its development:
245
+
246
+ [![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://buymeacoffee.com/msoukhomlinov)
247
+
248
+ ## License
249
+
250
+ [MIT](LICENSE)
@@ -1,235 +1,235 @@
1
- import type { ILoadOptionsFunctions, IDataObject, ResourceMapperFields, FieldType, INodePropertyOptions } from 'n8n-workflow';
2
- import { NodeOperationError } from 'n8n-workflow';
3
- import { handleGetOperation } from '../../utils/operations';
4
- import type { IAssetLayoutFieldEntity } from '../../resources/asset_layout_fields/asset_layout_fields.types';
5
- import { ASSET_LAYOUT_FIELD_TYPES } from '../../utils/constants';
6
- import { debugLog } from '../../utils/debugConfig';
7
- import { huduApiRequest } from '../../utils/requestUtils';
8
- import type { IAssetResponse } from '../../resources/assets/assets.types';
9
-
10
- interface IAssetLayout extends IDataObject {
11
- active: boolean;
12
- name: string;
13
- fields: IAssetLayoutFieldEntity[];
14
- }
15
-
16
- interface IAssetLayoutResponse extends IDataObject {
17
- asset_layout: IAssetLayout;
18
- }
19
-
20
- interface IMappedField {
21
- id: string;
22
- displayName: string;
23
- required: boolean;
24
- defaultMatch: boolean;
25
- type: FieldType | undefined;
26
- display: boolean;
27
- canBeUsedToMatch: boolean;
28
- description: string | undefined;
29
- options?: INodePropertyOptions[];
30
- label: string;
31
- }
32
-
33
- async function getFieldTypeAndOptions(
34
- this: ILoadOptionsFunctions,
35
- huduType: string,
36
- field: IAssetLayoutFieldEntity,
37
- ): Promise<{ type: FieldType | undefined; options?: INodePropertyOptions[] }> {
38
- debugLog('[FieldTypeMapping] Starting type mapping for field:', { huduType, field });
39
-
40
- // Map Hudu field types to n8n types
41
- switch (huduType) {
42
- case ASSET_LAYOUT_FIELD_TYPES.NUMBER:
43
- return { type: 'number' };
44
- case ASSET_LAYOUT_FIELD_TYPES.DATE:
45
- return { type: 'dateTime' };
46
- case ASSET_LAYOUT_FIELD_TYPES.CHECKBOX:
47
- return { type: 'boolean' };
48
- case ASSET_LAYOUT_FIELD_TYPES.LIST_SELECT:
49
- return { type: 'string' };
50
- case ASSET_LAYOUT_FIELD_TYPES.ASSET_TAG:
51
- case ASSET_LAYOUT_FIELD_TYPES.PASSWORD:
52
- case ASSET_LAYOUT_FIELD_TYPES.TEXT:
53
- case ASSET_LAYOUT_FIELD_TYPES.RICH_TEXT:
54
- case ASSET_LAYOUT_FIELD_TYPES.HEADING:
55
- case ASSET_LAYOUT_FIELD_TYPES.WEBSITE:
56
- case ASSET_LAYOUT_FIELD_TYPES.EMBED:
57
- case ASSET_LAYOUT_FIELD_TYPES.EMAIL:
58
- case ASSET_LAYOUT_FIELD_TYPES.PHONE:
59
- case ASSET_LAYOUT_FIELD_TYPES.ADDRESS_DATA:
60
- return { type: 'string' };
61
- default:
62
- return { type: undefined };
63
- }
64
- }
65
-
66
- async function getLayoutFields(
67
- this: ILoadOptionsFunctions,
68
- includeAssetTags = false,
69
- ): Promise<ResourceMapperFields> {
70
- debugLog('[ResourceMapping] Starting getLayoutFields');
71
-
72
- let layoutId: string;
73
- const operation = this.getNodeParameter('operation', 0) as string;
74
-
75
- if (operation === 'update') {
76
- // For update operation, get layout ID from asset details
77
- const assetId = this.getNodeParameter('id', 0) as string;
78
- debugLog('[ResourceMapping] Getting layout ID from asset:', assetId);
79
-
80
- // Use huduApiRequest directly since handleGetAllOperation expects IExecuteFunctions
81
- const response = await huduApiRequest.call(
82
- this,
83
- 'GET',
84
- '/assets',
85
- {},
86
- { id: assetId },
87
- ) as IAssetResponse;
88
-
89
- if (!response || !response.assets || !response.assets.length) {
90
- throw new Error(`Asset with ID ${assetId} not found`);
91
- }
92
-
93
- const asset = response.assets[0] as { asset_layout_id: number };
94
- layoutId = asset.asset_layout_id.toString();
95
- debugLog('[ResourceMapping] Got layout ID from asset:', layoutId);
96
- } else {
97
- // For create operation, get layout ID from UI
98
- layoutId = this.getNodeParameter('asset_layout_id', 0) as string;
99
- debugLog('[ResourceMapping] Got layout ID from UI:', layoutId);
100
- }
101
-
102
- debugLog('[ResourceMapping] Context:', {
103
- node: this.getNode().name,
104
- layoutId,
105
- includeAssetTags,
106
- });
107
-
108
- if (!layoutId) {
109
- debugLog('[ResourceMapping] No layout ID available, returning empty fields');
110
- return {
111
- fields: [],
112
- };
113
- }
114
-
115
- // Fetch the layout details
116
- debugLog('[ResourceMapping] Fetching layout details for ID:', layoutId);
117
- const layout = await handleGetOperation.call(this, '/asset_layouts', layoutId) as IAssetLayoutResponse;
118
- debugLog('[ResourceMapping] Layout response:', layout);
119
-
120
- // Check if layout exists
121
- if (!layout || !layout.asset_layout) {
122
- debugLog('[ResourceMapping] Layout not found or inaccessible');
123
- throw new NodeOperationError(this.getNode(), 'Asset layout not found or inaccessible');
124
- }
125
-
126
- const layoutData = layout.asset_layout;
127
- const fields = layoutData.fields || [];
128
- debugLog('[ResourceMapping] Found fields:', fields);
129
-
130
- // If no fields are found
131
- if (fields.length === 0) {
132
- debugLog('[ResourceMapping] No fields found in layout, returning empty fields');
133
- return {
134
- fields: [],
135
- };
136
- }
137
-
138
- // Map fields to ResourceMapperFields format
139
- debugLog('[ResourceMapping] Starting field mapping process');
140
- const mappedFields = await Promise.all(fields
141
- .filter((field: IAssetLayoutFieldEntity) => {
142
- const isAssetTag = field.field_type === ASSET_LAYOUT_FIELD_TYPES.ASSET_TAG;
143
- const include = !field.is_destroyed && (includeAssetTags ? isAssetTag : !isAssetTag);
144
- debugLog(`[ResourceMapping] Filtering field ${field.label}:`, { include, field });
145
- return include;
146
- })
147
- .map(async (field: IAssetLayoutFieldEntity) => {
148
- debugLog(`[ResourceMapping] Processing field ${field.label} of type ${field.field_type}`);
149
- const { type, options } = await getFieldTypeAndOptions.call(this, field.field_type, field);
150
- debugLog(`[ResourceMapping] Mapped field ${field.label}:`, { field, type, options });
151
-
152
- const mappedField: IMappedField = {
153
- id: field.id.toString(),
154
- displayName: `${field.label} (${field.field_type})${!layoutData.active ? ' [Archived Layout]' : ''}`,
155
- required: field.required || false,
156
- defaultMatch: false,
157
- type,
158
- display: true,
159
- canBeUsedToMatch: true,
160
- description: field.hint || undefined,
161
- label: field.label,
162
- };
163
-
164
- if (options) {
165
- debugLog(`[ResourceMapping] Adding options to field ${field.label}:`, options);
166
- mappedField.options = options;
167
- }
168
-
169
- debugLog(`[ResourceMapping] Final mapped field ${field.label}:`, mappedField);
170
- return mappedField;
171
- }))
172
- .then(fields => fields.sort((a, b) => a.displayName.localeCompare(b.displayName)));
173
-
174
- debugLog('[ResourceMapping] Final mapped fields:', mappedFields);
175
- return {
176
- fields: mappedFields,
177
- };
178
- }
179
-
180
- export async function mapAssetLayoutFieldsForResource(
181
- this: ILoadOptionsFunctions,
182
- ): Promise<ResourceMapperFields> {
183
- try {
184
- const parameterName = this.getNodeParameter('name', 0) as string;
185
- const includeAssetTags = parameterName === 'tagFieldMappings';
186
- return getLayoutFields.call(this, includeAssetTags);
187
- } catch (error) {
188
- debugLog('[ResourceMapping] Error in getAssetLayoutFields:', error);
189
- if (error instanceof NodeOperationError) {
190
- throw error;
191
- }
192
- throw new NodeOperationError(this.getNode(), `Failed to load asset layout fields: ${(error as Error).message}`);
193
- }
194
- }
195
-
196
- export async function getAssetLayoutFields(
197
- this: ILoadOptionsFunctions,
198
- ): Promise<INodePropertyOptions[]> {
199
- try {
200
- const layoutId = this.getNodeParameter('asset_layout_id', 0) as string;
201
-
202
- debugLog('[FieldLoading] Starting getAssetLayoutFields for layout:', layoutId);
203
-
204
- if (!layoutId) {
205
- return [];
206
- }
207
-
208
- // Fetch the layout details
209
- const layout = await handleGetOperation.call(this, '/asset_layouts', layoutId) as IAssetLayoutResponse;
210
-
211
- if (!layout || !layout.asset_layout) {
212
- throw new NodeOperationError(this.getNode(), 'Asset layout not found or inaccessible');
213
- }
214
-
215
- const layoutData = layout.asset_layout;
216
- const fields = layoutData.fields || [];
217
-
218
- // Map fields to INodePropertyOptions format
219
- return fields
220
- .filter((field: IAssetLayoutFieldEntity) => !field.is_destroyed)
221
- .map((field: IAssetLayoutFieldEntity) => ({
222
- name: `${field.label} (${field.field_type})${!layoutData.active ? ' [Archived Layout]' : ''}`,
223
- value: field.id.toString(),
224
- description: field.hint || undefined,
225
- }))
226
- .sort((a, b) => a.name.localeCompare(b.name));
227
-
228
- } catch (error) {
229
- debugLog('[FieldLoading] Error in getAssetLayoutFields:', error);
230
- if (error instanceof NodeOperationError) {
231
- throw error;
232
- }
233
- throw new NodeOperationError(this.getNode(), `Failed to load asset layout fields: ${(error as Error).message}`);
234
- }
1
+ import type { ILoadOptionsFunctions, IDataObject, ResourceMapperFields, FieldType, INodePropertyOptions } from 'n8n-workflow';
2
+ import { NodeOperationError } from 'n8n-workflow';
3
+ import { handleGetOperation } from '../../utils/operations';
4
+ import type { IAssetLayoutFieldEntity } from '../../resources/asset_layout_fields/asset_layout_fields.types';
5
+ import { ASSET_LAYOUT_FIELD_TYPES } from '../../utils/constants';
6
+ import { debugLog } from '../../utils/debugConfig';
7
+ import { huduApiRequest } from '../../utils/requestUtils';
8
+ import type { IAssetResponse } from '../../resources/assets/assets.types';
9
+
10
+ interface IAssetLayout extends IDataObject {
11
+ active: boolean;
12
+ name: string;
13
+ fields: IAssetLayoutFieldEntity[];
14
+ }
15
+
16
+ interface IAssetLayoutResponse extends IDataObject {
17
+ asset_layout: IAssetLayout;
18
+ }
19
+
20
+ interface IMappedField {
21
+ id: string;
22
+ displayName: string;
23
+ required: boolean;
24
+ defaultMatch: boolean;
25
+ type: FieldType | undefined;
26
+ display: boolean;
27
+ canBeUsedToMatch: boolean;
28
+ description: string | undefined;
29
+ options?: INodePropertyOptions[];
30
+ label: string;
31
+ }
32
+
33
+ async function getFieldTypeAndOptions(
34
+ this: ILoadOptionsFunctions,
35
+ huduType: string,
36
+ field: IAssetLayoutFieldEntity,
37
+ ): Promise<{ type: FieldType | undefined; options?: INodePropertyOptions[] }> {
38
+ debugLog('[FieldTypeMapping] Starting type mapping for field:', { huduType, field });
39
+
40
+ // Map Hudu field types to n8n types
41
+ switch (huduType) {
42
+ case ASSET_LAYOUT_FIELD_TYPES.NUMBER:
43
+ return { type: 'number' };
44
+ case ASSET_LAYOUT_FIELD_TYPES.DATE:
45
+ return { type: 'dateTime' };
46
+ case ASSET_LAYOUT_FIELD_TYPES.CHECKBOX:
47
+ return { type: 'boolean' };
48
+ case ASSET_LAYOUT_FIELD_TYPES.LIST_SELECT:
49
+ return { type: 'string' };
50
+ case ASSET_LAYOUT_FIELD_TYPES.ASSET_TAG:
51
+ case ASSET_LAYOUT_FIELD_TYPES.PASSWORD:
52
+ case ASSET_LAYOUT_FIELD_TYPES.TEXT:
53
+ case ASSET_LAYOUT_FIELD_TYPES.RICH_TEXT:
54
+ case ASSET_LAYOUT_FIELD_TYPES.HEADING:
55
+ case ASSET_LAYOUT_FIELD_TYPES.WEBSITE:
56
+ case ASSET_LAYOUT_FIELD_TYPES.EMBED:
57
+ case ASSET_LAYOUT_FIELD_TYPES.EMAIL:
58
+ case ASSET_LAYOUT_FIELD_TYPES.PHONE:
59
+ case ASSET_LAYOUT_FIELD_TYPES.ADDRESS_DATA:
60
+ return { type: 'string' };
61
+ default:
62
+ return { type: undefined };
63
+ }
64
+ }
65
+
66
+ async function getLayoutFields(
67
+ this: ILoadOptionsFunctions,
68
+ includeAssetTags = false,
69
+ ): Promise<ResourceMapperFields> {
70
+ debugLog('[ResourceMapping] Starting getLayoutFields');
71
+
72
+ let layoutId: string;
73
+ const operation = this.getNodeParameter('operation', 0) as string;
74
+
75
+ if (operation === 'update') {
76
+ // For update operation, get layout ID from asset details
77
+ const assetId = this.getNodeParameter('id', 0) as string;
78
+ debugLog('[ResourceMapping] Getting layout ID from asset:', assetId);
79
+
80
+ // Use huduApiRequest directly since handleGetAllOperation expects IExecuteFunctions
81
+ const response = await huduApiRequest.call(
82
+ this,
83
+ 'GET',
84
+ '/assets',
85
+ {},
86
+ { id: assetId },
87
+ ) as IAssetResponse;
88
+
89
+ if (!response || !response.assets || !response.assets.length) {
90
+ throw new Error(`Asset with ID ${assetId} not found`);
91
+ }
92
+
93
+ const asset = response.assets[0] as { asset_layout_id: number };
94
+ layoutId = asset.asset_layout_id.toString();
95
+ debugLog('[ResourceMapping] Got layout ID from asset:', layoutId);
96
+ } else {
97
+ // For create operation, get layout ID from UI
98
+ layoutId = this.getNodeParameter('asset_layout_id', 0) as string;
99
+ debugLog('[ResourceMapping] Got layout ID from UI:', layoutId);
100
+ }
101
+
102
+ debugLog('[ResourceMapping] Context:', {
103
+ node: this.getNode().name,
104
+ layoutId,
105
+ includeAssetTags,
106
+ });
107
+
108
+ if (!layoutId) {
109
+ debugLog('[ResourceMapping] No layout ID available, returning empty fields');
110
+ return {
111
+ fields: [],
112
+ };
113
+ }
114
+
115
+ // Fetch the layout details
116
+ debugLog('[ResourceMapping] Fetching layout details for ID:', layoutId);
117
+ const layout = await handleGetOperation.call(this, '/asset_layouts', layoutId) as IAssetLayoutResponse;
118
+ debugLog('[ResourceMapping] Layout response:', layout);
119
+
120
+ // Check if layout exists
121
+ if (!layout || !layout.asset_layout) {
122
+ debugLog('[ResourceMapping] Layout not found or inaccessible');
123
+ throw new NodeOperationError(this.getNode(), 'Asset layout not found or inaccessible');
124
+ }
125
+
126
+ const layoutData = layout.asset_layout;
127
+ const fields = layoutData.fields || [];
128
+ debugLog('[ResourceMapping] Found fields:', fields);
129
+
130
+ // If no fields are found
131
+ if (fields.length === 0) {
132
+ debugLog('[ResourceMapping] No fields found in layout, returning empty fields');
133
+ return {
134
+ fields: [],
135
+ };
136
+ }
137
+
138
+ // Map fields to ResourceMapperFields format
139
+ debugLog('[ResourceMapping] Starting field mapping process');
140
+ const mappedFields = await Promise.all(fields
141
+ .filter((field: IAssetLayoutFieldEntity) => {
142
+ const isAssetTag = field.field_type === ASSET_LAYOUT_FIELD_TYPES.ASSET_TAG;
143
+ const include = !field.is_destroyed && (includeAssetTags ? isAssetTag : !isAssetTag);
144
+ debugLog(`[ResourceMapping] Filtering field ${field.label}:`, { include, field });
145
+ return include;
146
+ })
147
+ .map(async (field: IAssetLayoutFieldEntity) => {
148
+ debugLog(`[ResourceMapping] Processing field ${field.label} of type ${field.field_type}`);
149
+ const { type, options } = await getFieldTypeAndOptions.call(this, field.field_type, field);
150
+ debugLog(`[ResourceMapping] Mapped field ${field.label}:`, { field, type, options });
151
+
152
+ const mappedField: IMappedField = {
153
+ id: field.id.toString(),
154
+ displayName: `${field.label} (${field.field_type})${!layoutData.active ? ' [Archived Layout]' : ''}`,
155
+ required: field.required || false,
156
+ defaultMatch: false,
157
+ type,
158
+ display: true,
159
+ canBeUsedToMatch: true,
160
+ description: field.hint || undefined,
161
+ label: field.label,
162
+ };
163
+
164
+ if (options) {
165
+ debugLog(`[ResourceMapping] Adding options to field ${field.label}:`, options);
166
+ mappedField.options = options;
167
+ }
168
+
169
+ debugLog(`[ResourceMapping] Final mapped field ${field.label}:`, mappedField);
170
+ return mappedField;
171
+ }))
172
+ .then(fields => fields.sort((a, b) => a.displayName.localeCompare(b.displayName)));
173
+
174
+ debugLog('[ResourceMapping] Final mapped fields:', mappedFields);
175
+ return {
176
+ fields: mappedFields,
177
+ };
178
+ }
179
+
180
+ export async function mapAssetLayoutFieldsForResource(
181
+ this: ILoadOptionsFunctions,
182
+ ): Promise<ResourceMapperFields> {
183
+ try {
184
+ const parameterName = this.getNodeParameter('name', 0) as string;
185
+ const includeAssetTags = parameterName === 'tagFieldMappings';
186
+ return getLayoutFields.call(this, includeAssetTags);
187
+ } catch (error) {
188
+ debugLog('[ResourceMapping] Error in getAssetLayoutFields:', error);
189
+ if (error instanceof NodeOperationError) {
190
+ throw error;
191
+ }
192
+ throw new NodeOperationError(this.getNode(), `Failed to load asset layout fields: ${(error as Error).message}`);
193
+ }
194
+ }
195
+
196
+ export async function getAssetLayoutFields(
197
+ this: ILoadOptionsFunctions,
198
+ ): Promise<INodePropertyOptions[]> {
199
+ try {
200
+ const layoutId = this.getNodeParameter('asset_layout_id', 0) as string;
201
+
202
+ debugLog('[FieldLoading] Starting getAssetLayoutFields for layout:', layoutId);
203
+
204
+ if (!layoutId) {
205
+ return [];
206
+ }
207
+
208
+ // Fetch the layout details
209
+ const layout = await handleGetOperation.call(this, '/asset_layouts', layoutId) as IAssetLayoutResponse;
210
+
211
+ if (!layout || !layout.asset_layout) {
212
+ throw new NodeOperationError(this.getNode(), 'Asset layout not found or inaccessible');
213
+ }
214
+
215
+ const layoutData = layout.asset_layout;
216
+ const fields = layoutData.fields || [];
217
+
218
+ // Map fields to INodePropertyOptions format
219
+ return fields
220
+ .filter((field: IAssetLayoutFieldEntity) => !field.is_destroyed)
221
+ .map((field: IAssetLayoutFieldEntity) => ({
222
+ name: `${field.label} (${field.field_type})${!layoutData.active ? ' [Archived Layout]' : ''}`,
223
+ value: field.id.toString(),
224
+ description: field.hint || undefined,
225
+ }))
226
+ .sort((a, b) => a.name.localeCompare(b.name));
227
+
228
+ } catch (error) {
229
+ debugLog('[FieldLoading] Error in getAssetLayoutFields:', error);
230
+ if (error instanceof NodeOperationError) {
231
+ throw error;
232
+ }
233
+ throw new NodeOperationError(this.getNode(), `Failed to load asset layout fields: ${(error as Error).message}`);
234
+ }
235
235
  }