n8n-nodes-mautic-advanced 0.3.7 → 0.3.9

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.
@@ -118,6 +118,13 @@ exports.campaignFields = [
118
118
  default: '',
119
119
  description: 'Date/time when the campaign should be published',
120
120
  },
121
+ {
122
+ displayName: 'Alias',
123
+ name: 'alias',
124
+ type: 'string',
125
+ default: '',
126
+ description: 'Used to generate the URL for the campaign',
127
+ },
121
128
  ],
122
129
  },
123
130
  /* -------------------------------------------------------------------------- */
@@ -136,6 +143,19 @@ exports.campaignFields = [
136
143
  },
137
144
  default: '',
138
145
  },
146
+ {
147
+ displayName: 'Create If Not Found',
148
+ name: 'createIfNotFound',
149
+ type: 'boolean',
150
+ displayOptions: {
151
+ show: {
152
+ resource: ['campaign'],
153
+ operation: ['update'],
154
+ },
155
+ },
156
+ default: false,
157
+ description: 'Create a new campaign if the given ID does not exist (uses PUT instead of PATCH)',
158
+ },
139
159
  {
140
160
  displayName: 'Update Fields',
141
161
  name: 'updateFields',
@@ -184,6 +204,13 @@ exports.campaignFields = [
184
204
  default: '',
185
205
  description: 'Date/time when the campaign should be published',
186
206
  },
207
+ {
208
+ displayName: 'Alias',
209
+ name: 'alias',
210
+ type: 'string',
211
+ default: '',
212
+ description: 'Used to generate the URL for the campaign',
213
+ },
187
214
  ],
188
215
  },
189
216
  /* -------------------------------------------------------------------------- */
@@ -139,6 +139,18 @@ exports.contactOperations = [
139
139
  description: 'Get activity events for all contacts',
140
140
  action: 'Get activity events for all contacts',
141
141
  },
142
+ {
143
+ name: 'Get Owners',
144
+ value: 'getOwners',
145
+ description: 'Get list of available owners',
146
+ action: 'Get list of available owners',
147
+ },
148
+ {
149
+ name: 'Get Fields',
150
+ value: 'getFields',
151
+ description: 'Get list of available contact fields',
152
+ action: 'Get list of available contact fields',
153
+ },
142
154
  ],
143
155
  default: 'create',
144
156
  },
@@ -30,11 +30,19 @@ async function mauticApiRequest(method, endpoint, body = {}, query, uri) {
30
30
  }
31
31
  if (returnData.errors) {
32
32
  // They seem to sometimes return 200 status but still error.
33
- throw new n8n_workflow_1.NodeApiError(this.getNode(), returnData);
33
+ // Preserve the full error object including details for better error handling
34
+ throw new n8n_workflow_1.NodeApiError(this.getNode(), returnData, {
35
+ httpCode: '400',
36
+ description: returnData,
37
+ });
34
38
  }
35
39
  return returnData;
36
40
  }
37
41
  catch (error) {
42
+ // Preserve error details when available for better error handling
43
+ if (error instanceof n8n_workflow_1.NodeApiError) {
44
+ throw error;
45
+ }
38
46
  throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
39
47
  }
40
48
  }
@@ -48,9 +48,11 @@ async function createCampaign(context, itemIndex) {
48
48
  }
49
49
  async function updateCampaign(context, itemIndex) {
50
50
  const campaignId = (0, ApiHelpers_1.getRequiredParam)(context, 'campaignId', itemIndex);
51
+ const createIfNotFound = (0, ApiHelpers_1.getOptionalParam)(context, 'createIfNotFound', itemIndex, false);
51
52
  const updateFields = (0, ApiHelpers_1.getOptionalParam)(context, 'updateFields', itemIndex, {});
52
53
  const body = { ...updateFields };
53
- const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'PATCH', `/campaigns/${campaignId}/edit`, body);
54
+ const method = createIfNotFound ? 'PUT' : 'PATCH';
55
+ const response = await (0, ApiHelpers_1.makeApiRequest)(context, method, `/campaigns/${campaignId}/edit`, body);
54
56
  return response.campaign;
55
57
  }
56
58
  async function cloneCampaign(context, itemIndex) {
@@ -61,7 +63,7 @@ async function cloneCampaign(context, itemIndex) {
61
63
  async function getCampaign(context, itemIndex) {
62
64
  const campaignId = (0, ApiHelpers_1.getRequiredParam)(context, 'campaignId', itemIndex);
63
65
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/campaigns/${campaignId}`);
64
- return response.campaign;
66
+ return (0, DataHelpers_1.convertNumericStrings)(response.campaign);
65
67
  }
66
68
  async function getAllCampaigns(context, itemIndex) {
67
69
  const returnAll = (0, ApiHelpers_1.getOptionalParam)(context, 'returnAll', itemIndex, false);
@@ -72,12 +74,13 @@ async function getAllCampaigns(context, itemIndex) {
72
74
  if (!qs.orderByDir)
73
75
  qs.orderByDir = 'asc';
74
76
  if (returnAll) {
75
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'campaigns', 'GET', '/campaigns', {}, qs);
77
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'campaigns', 'GET', '/campaigns', {}, qs);
78
+ return (0, DataHelpers_1.convertNumericStrings)(result);
76
79
  }
77
80
  else {
78
81
  qs.limit = (0, ApiHelpers_1.getOptionalParam)(context, 'limit', itemIndex, 30);
79
82
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/campaigns', {}, qs);
80
- return response.campaigns;
83
+ return (0, DataHelpers_1.convertNumericStrings)(response.campaigns);
81
84
  }
82
85
  }
83
86
  async function getCampaignContacts(context, itemIndex) {
@@ -86,12 +89,13 @@ async function getCampaignContacts(context, itemIndex) {
86
89
  const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {});
87
90
  const qs = (0, DataHelpers_1.buildQueryFromOptions)(options);
88
91
  if (returnAll) {
89
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'contacts', 'GET', `/campaigns/${campaignId}/contacts`, {}, qs);
92
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'contacts', 'GET', `/campaigns/${campaignId}/contacts`, {}, qs);
93
+ return (0, DataHelpers_1.convertNumericStrings)(result);
90
94
  }
91
95
  else {
92
96
  qs.limit = (0, ApiHelpers_1.getOptionalParam)(context, 'limit', itemIndex, 30);
93
97
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/campaigns/${campaignId}/contacts`, {}, qs);
94
- return response.contacts;
98
+ return (0, DataHelpers_1.convertNumericStrings)(response.contacts);
95
99
  }
96
100
  }
97
101
  async function deleteCampaign(context, itemIndex) {
@@ -131,7 +131,7 @@ async function getCompany(context, itemIndex) {
131
131
  if (simple) {
132
132
  result = result.fields.all;
133
133
  }
134
- return result;
134
+ return (0, DataHelpers_1.convertNumericStrings)(result);
135
135
  }
136
136
  async function getAllCompanies(context, itemIndex) {
137
137
  const returnAll = (0, ApiHelpers_1.getOptionalParam)(context, 'returnAll', itemIndex, false);
@@ -156,7 +156,7 @@ async function getAllCompanies(context, itemIndex) {
156
156
  if (simple) {
157
157
  responseData = responseData.map((item) => item.fields.all);
158
158
  }
159
- return responseData;
159
+ return (0, DataHelpers_1.convertNumericStrings)(responseData);
160
160
  }
161
161
  async function deleteCompany(context, itemIndex) {
162
162
  const simple = (0, ApiHelpers_1.getOptionalParam)(context, 'simple', itemIndex, false);
@@ -72,6 +72,12 @@ async function executeContactOperation(context, operation, i) {
72
72
  case 'getAllActivity':
73
73
  responseData = await getAllContactActivity(context, i);
74
74
  break;
75
+ case 'getOwners':
76
+ responseData = await getContactOwners(context);
77
+ break;
78
+ case 'getFields':
79
+ responseData = await getContactFields(context);
80
+ break;
75
81
  default:
76
82
  throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Operation '${operation}' is not supported for Contact resource.`, { itemIndex: i });
77
83
  }
@@ -99,7 +105,26 @@ async function createContact(context, itemIndex) {
99
105
  body = (0, DataHelpers_1.validateJsonParameter)(context, 'bodyJson', itemIndex);
100
106
  }
101
107
  addContactFields(body, additionalFields);
102
- const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', '/contacts/new', body);
108
+ // Data sanitization: Remove empty string values and validate email format
109
+ const sanitizedBody = {};
110
+ Object.entries(body).forEach(([key, value]) => {
111
+ // Skip empty strings as Mautic sometimes rejects them
112
+ if (value !== '' && value !== null && value !== undefined) {
113
+ sanitizedBody[key] = value;
114
+ }
115
+ });
116
+ // Basic email validation if email is provided
117
+ if (sanitizedBody.email && typeof sanitizedBody.email === 'string') {
118
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
119
+ if (!emailRegex.test(sanitizedBody.email)) {
120
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Invalid email format: ${sanitizedBody.email}`, { itemIndex });
121
+ }
122
+ }
123
+ // Log the sanitized body for debugging (only in development)
124
+ if (process.env.NODE_ENV === 'development') {
125
+ console.log('Mautic Contact Creation - Sanitized Body:', JSON.stringify(sanitizedBody, null, 2));
126
+ }
127
+ const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', '/contacts/new', sanitizedBody);
103
128
  const contactData = [response.contact];
104
129
  return (0, DataHelpers_1.processContactFields)(contactData, options);
105
130
  }
@@ -133,7 +158,8 @@ async function getContact(context, itemIndex) {
133
158
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
134
159
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/contacts/${contactId}`);
135
160
  const contactData = [response.contact];
136
- return (0, DataHelpers_1.processContactFields)(contactData, options, options.fieldsToReturn);
161
+ const processedData = (0, DataHelpers_1.processContactFields)(contactData, options, options.fieldsToReturn);
162
+ return (0, DataHelpers_1.convertNumericStrings)(processedData);
137
163
  }
138
164
  async function getAllContacts(context, itemIndex) {
139
165
  const returnAll = (0, ApiHelpers_1.getOptionalParam)(context, 'returnAll', itemIndex, false);
@@ -171,7 +197,8 @@ async function getAllContacts(context, itemIndex) {
171
197
  responseData = response.contacts ? Object.values(response.contacts) : [];
172
198
  }
173
199
  }
174
- return (0, DataHelpers_1.processContactFields)(responseData, options, options.fieldsToReturn);
200
+ const processedData = (0, DataHelpers_1.processContactFields)(responseData, options, options.fieldsToReturn);
201
+ return (0, DataHelpers_1.convertNumericStrings)(processedData);
175
202
  }
176
203
  async function deleteContact(context, itemIndex) {
177
204
  const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {});
@@ -201,8 +228,16 @@ async function editContactPoints(context, itemIndex) {
201
228
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
202
229
  const action = (0, ApiHelpers_1.getRequiredParam)(context, 'action', itemIndex);
203
230
  const points = (0, ApiHelpers_1.getRequiredParam)(context, 'points', itemIndex);
204
- const body = { points };
205
- const endpoint = action === 'add' ? `/contacts/${contactId}/points/plus` : `/contacts/${contactId}/points/minus`;
231
+ const eventName = (0, ApiHelpers_1.getOptionalParam)(context, 'eventName', itemIndex, '');
232
+ const actionName = (0, ApiHelpers_1.getOptionalParam)(context, 'actionName', itemIndex, '');
233
+ const body = {};
234
+ if (eventName)
235
+ body.eventName = eventName;
236
+ if (actionName)
237
+ body.actionName = actionName;
238
+ const endpoint = action === 'add'
239
+ ? `/contacts/${contactId}/points/plus/${points}`
240
+ : `/contacts/${contactId}/points/minus/${points}`;
206
241
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', endpoint, body);
207
242
  return response.contact;
208
243
  }
@@ -210,8 +245,20 @@ async function editDoNotContactList(context, itemIndex) {
210
245
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
211
246
  const action = (0, ApiHelpers_1.getRequiredParam)(context, 'action', itemIndex);
212
247
  const channel = (0, ApiHelpers_1.getRequiredParam)(context, 'channel', itemIndex);
213
- const body = { action, channel };
214
- const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', `/contacts/${contactId}/dnc/${channel}/${action}`, body);
248
+ const reason = (0, ApiHelpers_1.getOptionalParam)(context, 'reason', itemIndex, 3); // Default to Manual (3)
249
+ const channelId = (0, ApiHelpers_1.getOptionalParam)(context, 'channelId', itemIndex, '');
250
+ const comments = (0, ApiHelpers_1.getOptionalParam)(context, 'comments', itemIndex, '');
251
+ const body = {};
252
+ if (reason !== undefined)
253
+ body.reason = reason;
254
+ if (channelId)
255
+ body.channelId = channelId;
256
+ if (comments)
257
+ body.comments = comments;
258
+ const endpoint = action === 'add'
259
+ ? `/contacts/${contactId}/dnc/${channel}/add`
260
+ : `/contacts/${contactId}/dnc/${channel}/remove`;
261
+ const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', endpoint, body);
215
262
  return response.contact;
216
263
  }
217
264
  async function addUtmTags(context, itemIndex) {
@@ -251,7 +298,8 @@ async function removeUtmTags(context, itemIndex) {
251
298
  }
252
299
  async function getContactDevices(context, itemIndex) {
253
300
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
254
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'devices', 'GET', `/contacts/${contactId}/devices`);
301
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'devices', 'GET', `/contacts/${contactId}/devices`);
302
+ return (0, DataHelpers_1.convertNumericStrings)(result);
255
303
  }
256
304
  async function getContactActivity(context, itemIndex) {
257
305
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
@@ -273,25 +321,30 @@ async function getContactActivity(context, itemIndex) {
273
321
  qs.order = [options.orderBy, options.orderByDir ?? 'asc'];
274
322
  if (options.limit)
275
323
  qs.limit = options.limit;
276
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'events', 'GET', `/contacts/${contactId}/activity`, {}, qs);
324
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'events', 'GET', `/contacts/${contactId}/activity`, {}, qs);
325
+ return (0, DataHelpers_1.convertNumericStrings)(result);
277
326
  }
278
327
  async function getContactNotes(context, itemIndex) {
279
328
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
280
329
  const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {});
281
330
  const qs = options;
282
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'notes', 'GET', `/contacts/${contactId}/notes`, {}, qs);
331
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'notes', 'GET', `/contacts/${contactId}/notes`, {}, qs);
332
+ return (0, DataHelpers_1.convertNumericStrings)(result);
283
333
  }
284
334
  async function getContactCompanies(context, itemIndex) {
285
335
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
286
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'companies', 'GET', `/contacts/${contactId}/companies`);
336
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'companies', 'GET', `/contacts/${contactId}/companies`);
337
+ return (0, DataHelpers_1.convertNumericStrings)(result);
287
338
  }
288
339
  async function getContactCampaigns(context, itemIndex) {
289
340
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
290
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'campaigns', 'GET', `/contacts/${contactId}/campaigns`);
341
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'campaigns', 'GET', `/contacts/${contactId}/campaigns`);
342
+ return (0, DataHelpers_1.convertNumericStrings)(result);
291
343
  }
292
344
  async function getContactSegments(context, itemIndex) {
293
345
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
294
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'segments', 'GET', `/contacts/${contactId}/segments`);
346
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'segments', 'GET', `/contacts/${contactId}/segments`);
347
+ return (0, DataHelpers_1.convertNumericStrings)(result);
295
348
  }
296
349
  async function addContactToSegments(context, itemIndex) {
297
350
  const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
@@ -340,7 +393,8 @@ async function getAllContactActivity(context, itemIndex) {
340
393
  qs.order = [options.orderBy, options.orderByDir ?? 'asc'];
341
394
  if (options.limit)
342
395
  qs.limit = options.limit;
343
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'events', 'GET', `/contacts/activity`, {}, qs);
396
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'events', 'GET', `/contacts/activity`, {}, qs);
397
+ return (0, DataHelpers_1.convertNumericStrings)(result);
344
398
  }
345
399
  function normalizeTagsInput(tagsInput) {
346
400
  // Handle different input formats for tags
@@ -459,3 +513,11 @@ async function getContactsWithDncFilter(context, qs, options, limit) {
459
513
  }
460
514
  return contacts;
461
515
  }
516
+ async function getContactOwners(context) {
517
+ const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/contacts/list/owners');
518
+ return (0, DataHelpers_1.convertNumericStrings)(response);
519
+ }
520
+ async function getContactFields(context) {
521
+ const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/contacts/list/fields');
522
+ return (0, DataHelpers_1.convertNumericStrings)(response);
523
+ }
@@ -142,7 +142,7 @@ async function getField(context, itemIndex) {
142
142
  const fieldId = (0, ApiHelpers_1.getRequiredParam)(context, 'fieldId', itemIndex);
143
143
  const endpoint = `/fields/${fieldObject}/${fieldId}`;
144
144
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', endpoint);
145
- return response.field;
145
+ return (0, DataHelpers_1.convertNumericStrings)(response.field);
146
146
  }
147
147
  async function getAllFields(context, itemIndex) {
148
148
  const fieldObject = (0, ApiHelpers_1.getRequiredParam)(context, 'fieldObject', itemIndex);
@@ -151,12 +151,14 @@ async function getAllFields(context, itemIndex) {
151
151
  const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {});
152
152
  const query = (0, DataHelpers_1.buildQueryFromOptions)(options);
153
153
  if (returnAll) {
154
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'fields', 'GET', `/fields/${fieldObject}`, {}, query);
154
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'fields', 'GET', `/fields/${fieldObject}`, {}, query);
155
+ return (0, DataHelpers_1.convertNumericStrings)(result);
155
156
  }
156
157
  else {
157
158
  query.limit = limit;
158
159
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/fields/${fieldObject}`, {}, query);
159
- return Object.values(response.fields || {});
160
+ const data = Object.values(response.fields || {});
161
+ return (0, DataHelpers_1.convertNumericStrings)(data);
160
162
  }
161
163
  }
162
164
  async function deleteField(context, itemIndex) {
@@ -161,7 +161,7 @@ async function updateTag(context, itemIndex) {
161
161
  async function getTag(context, itemIndex) {
162
162
  const tagId = (0, ApiHelpers_1.getRequiredParam)(context, 'tagId', itemIndex);
163
163
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/tags/${tagId}`);
164
- return response.tag;
164
+ return (0, DataHelpers_1.convertNumericStrings)(response.tag);
165
165
  }
166
166
  async function getAllTags(context, itemIndex) {
167
167
  const returnAll = (0, ApiHelpers_1.getOptionalParam)(context, 'returnAll', itemIndex, false);
@@ -173,12 +173,14 @@ async function getAllTags(context, itemIndex) {
173
173
  qs.orderByDir = 'asc';
174
174
  if (returnAll) {
175
175
  const limit = (0, ApiHelpers_1.getOptionalParam)(context, 'limit', itemIndex, undefined);
176
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'tags', 'GET', '/tags', {}, qs, limit);
176
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'tags', 'GET', '/tags', {}, qs, limit);
177
+ return (0, DataHelpers_1.convertNumericStrings)(result);
177
178
  }
178
179
  else {
179
180
  qs.limit = (0, ApiHelpers_1.getOptionalParam)(context, 'limit', itemIndex, 30);
180
181
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/tags', {}, qs);
181
- return response.tags ? Object.values(response.tags) : [];
182
+ const data = response.tags ? Object.values(response.tags) : [];
183
+ return (0, DataHelpers_1.convertNumericStrings)(data);
182
184
  }
183
185
  }
184
186
  async function deleteTag(context, itemIndex) {
@@ -203,6 +205,7 @@ async function createCategory(context, itemIndex) {
203
205
  }
204
206
  async function updateCategory(context, itemIndex) {
205
207
  const categoryId = (0, ApiHelpers_1.getRequiredParam)(context, 'categoryId', itemIndex);
208
+ const createIfNotFound = (0, ApiHelpers_1.getOptionalParam)(context, 'createIfNotFound', itemIndex, false);
206
209
  const updateFields = (0, ApiHelpers_1.getOptionalParam)(context, 'updateFields', itemIndex, {});
207
210
  const body = {};
208
211
  if (updateFields.title)
@@ -211,13 +214,16 @@ async function updateCategory(context, itemIndex) {
211
214
  body.description = updateFields.description;
212
215
  if (updateFields.color)
213
216
  body.color = updateFields.color;
214
- const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'PATCH', `/categories/${categoryId}/edit`, body);
217
+ if (updateFields.bundle)
218
+ body.bundle = updateFields.bundle;
219
+ const method = createIfNotFound ? 'PUT' : 'PATCH';
220
+ const response = await (0, ApiHelpers_1.makeApiRequest)(context, method, `/categories/${categoryId}/edit`, body);
215
221
  return response.category;
216
222
  }
217
223
  async function getCategory(context, itemIndex) {
218
224
  const categoryId = (0, ApiHelpers_1.getRequiredParam)(context, 'categoryId', itemIndex);
219
225
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/categories/${categoryId}`);
220
- return response.category;
226
+ return (0, DataHelpers_1.convertNumericStrings)(response.category);
221
227
  }
222
228
  async function getAllCategories(context, itemIndex) {
223
229
  const returnAll = (0, ApiHelpers_1.getOptionalParam)(context, 'returnAll', itemIndex, false);
@@ -231,7 +237,8 @@ async function getAllCategories(context, itemIndex) {
231
237
  if (!returnAll) {
232
238
  limit = (0, ApiHelpers_1.getOptionalParam)(context, 'limit', itemIndex, 30);
233
239
  }
234
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'categories', 'GET', '/categories', {}, qs, limit);
240
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'categories', 'GET', '/categories', {}, qs, limit);
241
+ return (0, DataHelpers_1.convertNumericStrings)(result);
235
242
  }
236
243
  async function deleteCategory(context, itemIndex) {
237
244
  const categoryId = (0, ApiHelpers_1.getRequiredParam)(context, 'categoryId', itemIndex);
@@ -111,7 +111,7 @@ async function getNotification(context, itemIndex) {
111
111
  const notificationId = (0, ApiHelpers_1.getRequiredParam)(context, 'notificationId', itemIndex);
112
112
  const endpoint = `/notifications/${notificationId}`;
113
113
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', endpoint);
114
- return response.notification;
114
+ return (0, DataHelpers_1.convertNumericStrings)(response.notification);
115
115
  }
116
116
  async function getAllNotifications(context, itemIndex) {
117
117
  const returnAll = (0, ApiHelpers_1.getOptionalParam)(context, 'returnAll', itemIndex, false);
@@ -119,12 +119,14 @@ async function getAllNotifications(context, itemIndex) {
119
119
  const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {});
120
120
  const query = (0, DataHelpers_1.buildQueryFromOptions)(options);
121
121
  if (returnAll) {
122
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'notifications', 'GET', '/notifications', {}, query);
122
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'notifications', 'GET', '/notifications', {}, query);
123
+ return (0, DataHelpers_1.convertNumericStrings)(result);
123
124
  }
124
125
  else {
125
126
  query.limit = limit;
126
127
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/notifications', {}, query);
127
- return Object.values(response.notifications || {});
128
+ const data = Object.values(response.notifications || {});
129
+ return (0, DataHelpers_1.convertNumericStrings)(data);
128
130
  }
129
131
  }
130
132
  async function deleteNotification(context, itemIndex) {
@@ -62,20 +62,22 @@ async function updateSegment(context, itemIndex) {
62
62
  async function getSegment(context, itemIndex) {
63
63
  const segmentId = (0, ApiHelpers_1.getRequiredParam)(context, 'segmentId', itemIndex);
64
64
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/segments/${segmentId}`);
65
- return response.list;
65
+ return (0, DataHelpers_1.convertNumericStrings)(response.list);
66
66
  }
67
67
  async function getAllSegments(context, itemIndex) {
68
68
  const returnAll = (0, ApiHelpers_1.getOptionalParam)(context, 'returnAll', itemIndex, false);
69
69
  const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {});
70
70
  const qs = (0, DataHelpers_1.buildQueryFromOptions)(options);
71
71
  if (returnAll) {
72
- return await (0, ApiHelpers_1.makePaginatedRequest)(context, 'lists', 'GET', '/segments', {}, qs);
72
+ const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'lists', 'GET', '/segments', {}, qs);
73
+ return (0, DataHelpers_1.convertNumericStrings)(result);
73
74
  }
74
75
  else {
75
76
  const limit = (0, ApiHelpers_1.getOptionalParam)(context, 'limit', itemIndex, 30);
76
77
  qs.limit = limit;
77
78
  const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', '/segments', {}, qs);
78
- return response.lists ? Object.values(response.lists) : [];
79
+ const data = response.lists ? Object.values(response.lists) : [];
80
+ return (0, DataHelpers_1.convertNumericStrings)(data);
79
81
  }
80
82
  }
81
83
  async function deleteSegment(context, itemIndex) {
@@ -39,7 +39,69 @@ function handleApiError(context, error, operation, resource) {
39
39
  throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Permission denied for ${operation} ${resource}. Please check your API credentials.`, { itemIndex: 0 });
40
40
  }
41
41
  if (error.httpCode === '400') {
42
- throw new n8n_workflow_1.NodeOperationError(context.getNode(), `Invalid data provided for ${operation} ${resource}. Please check your input parameters.`, { itemIndex: 0 });
42
+ // Extract detailed validation errors from Mautic API response
43
+ let errorMessage = `Invalid data provided for ${operation} ${resource}.`;
44
+ try {
45
+ const errorData = error.description;
46
+ // Check if we have a direct error message from Mautic API
47
+ if (typeof errorData === 'string' && errorData.trim()) {
48
+ // Parse field-specific error messages like "field_name: error message"
49
+ const fieldErrorMatch = errorData.match(/^([^:]+):\s*(.+)$/);
50
+ if (fieldErrorMatch) {
51
+ const fieldName = fieldErrorMatch[1].trim();
52
+ const errorMsg = fieldErrorMatch[2].trim();
53
+ errorMessage += ` Validation error:\n- ${fieldName}: ${errorMsg}\n`;
54
+ }
55
+ else {
56
+ // If it doesn't match the field:message pattern, show the raw message
57
+ errorMessage += ` Error: ${errorData}\n`;
58
+ }
59
+ }
60
+ // Check for errors array (current Mautic API format)
61
+ else if (errorData?.errors &&
62
+ Array.isArray(errorData.errors) &&
63
+ errorData.errors.length > 0) {
64
+ const validationErrors = [];
65
+ errorData.errors.forEach((err) => {
66
+ if (err.details && typeof err.details === 'object') {
67
+ // Extract field-specific validation messages
68
+ Object.entries(err.details).forEach(([field, messages]) => {
69
+ if (Array.isArray(messages)) {
70
+ messages.forEach((msg) => {
71
+ validationErrors.push(`- ${field}: ${msg}`);
72
+ });
73
+ }
74
+ });
75
+ }
76
+ else if (err.message) {
77
+ // Fallback to error message if no details
78
+ validationErrors.push(`- ${err.message}`);
79
+ }
80
+ });
81
+ if (validationErrors.length > 0) {
82
+ errorMessage += ` Validation errors:\n${validationErrors.join('\n')}\n`;
83
+ }
84
+ }
85
+ // Check for deprecated error format (Mautic < 2.6.0)
86
+ else if (errorData?.error?.details && typeof errorData.error.details === 'object') {
87
+ const validationErrors = [];
88
+ Object.entries(errorData.error.details).forEach(([field, messages]) => {
89
+ if (Array.isArray(messages)) {
90
+ messages.forEach((msg) => {
91
+ validationErrors.push(`- ${field}: ${msg}`);
92
+ });
93
+ }
94
+ });
95
+ if (validationErrors.length > 0) {
96
+ errorMessage += ` Validation errors:\n${validationErrors.join('\n')}\n`;
97
+ }
98
+ }
99
+ }
100
+ catch (parseError) {
101
+ // If we can't parse the error details, just use the generic message
102
+ }
103
+ errorMessage += ' Please check your input parameters.';
104
+ throw new n8n_workflow_1.NodeOperationError(context.getNode(), errorMessage, { itemIndex: 0 });
43
105
  }
44
106
  throw error;
45
107
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createBatchSuccessResponse = exports.processBatchIds = exports.buildQueryFromOptions = exports.validateJsonParameter = exports.processContactFields = exports.wrapSingleItem = exports.processSimpleResponse = void 0;
3
+ exports.convertNumericStrings = exports.createBatchSuccessResponse = exports.processBatchIds = exports.buildQueryFromOptions = exports.validateJsonParameter = exports.processContactFields = exports.wrapSingleItem = exports.processSimpleResponse = void 0;
4
4
  const GenericFunctions_1 = require("../GenericFunctions");
5
5
  // Process simple response data extraction
6
6
  function processSimpleResponse(responseData, simple, dataPath) {
@@ -102,3 +102,33 @@ function createBatchSuccessResponse(operation, ids, customMessage) {
102
102
  };
103
103
  }
104
104
  exports.createBatchSuccessResponse = createBatchSuccessResponse;
105
+ // Convert numeric strings to numbers recursively
106
+ function convertNumericStrings(data) {
107
+ if (data === null || data === undefined) {
108
+ return data;
109
+ }
110
+ if (typeof data === 'string') {
111
+ // Check if string is a valid number (including negative numbers and decimals)
112
+ const numericRegex = /^-?\d+(\.\d+)?$/;
113
+ if (numericRegex.test(data)) {
114
+ const num = parseFloat(data);
115
+ // Only convert if parseFloat doesn't lose precision and result is finite
116
+ if (!isNaN(num) && isFinite(num) && num.toString() === data) {
117
+ return num;
118
+ }
119
+ }
120
+ return data;
121
+ }
122
+ if (Array.isArray(data)) {
123
+ return data.map(convertNumericStrings);
124
+ }
125
+ if (typeof data === 'object') {
126
+ const converted = {};
127
+ for (const [key, value] of Object.entries(data)) {
128
+ converted[key] = convertNumericStrings(value);
129
+ }
130
+ return converted;
131
+ }
132
+ return data;
133
+ }
134
+ exports.convertNumericStrings = convertNumericStrings;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-mautic-advanced",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "Enhanced n8n node for Mautic with comprehensive API coverage including tags, campaigns, categories, and advanced contact management",
5
5
  "keywords": [
6
6
  "n8n",