n8n-nodes-mautic-advanced 0.3.8 → 0.4.0
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 +335 -335
- package/dist/nodes/MauticAdvanced/CampaignDescription.js +27 -0
- package/dist/nodes/MauticAdvanced/ContactDescription.js +12 -0
- package/dist/nodes/MauticAdvanced/{mauticadvanced.svg → MauticAdvanced.svg} +9 -9
- package/dist/nodes/MauticAdvanced/SegmentDescription.js +35 -1
- package/dist/nodes/MauticAdvanced/operations/CampaignOperations.js +10 -6
- package/dist/nodes/MauticAdvanced/operations/CompanyOperations.js +2 -2
- package/dist/nodes/MauticAdvanced/operations/ContactOperations.js +56 -13
- package/dist/nodes/MauticAdvanced/operations/FieldOperations.js +5 -3
- package/dist/nodes/MauticAdvanced/operations/MiscellaneousOperations.js +13 -6
- package/dist/nodes/MauticAdvanced/operations/NotificationOperations.js +5 -3
- package/dist/nodes/MauticAdvanced/operations/SegmentOperations.js +5 -3
- package/dist/nodes/MauticAdvanced/utils/ApiHelpers.js +3 -1
- package/dist/nodes/MauticAdvanced/utils/DataHelpers.js +31 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,335 +1,335 @@
|
|
|
1
|
-
# n8n Mautic Advanced Node
|
|
2
|
-
|
|
3
|
-
[](https://buymeacoffee.com/msoukhomlinov)
|
|
4
|
-
|
|
5
|
-
Enhanced n8n node for Mautic with comprehensive API coverage including tags, campaigns, categories, notifications, and advanced contact management.
|
|
6
|
-
|
|
7
|
-
## 📋 Table of Contents
|
|
8
|
-
|
|
9
|
-
- [What Makes This "Advanced"?](#what-makes-this-advanced)
|
|
10
|
-
- [Features](#features)
|
|
11
|
-
- [Supported Resources and Operations](#supported-resources-and-operations)
|
|
12
|
-
- [Installation](#installation)
|
|
13
|
-
- [Authentication](#authentication)
|
|
14
|
-
- [Advanced Features](#advanced-features)
|
|
15
|
-
- [Usage Examples](#usage-examples)
|
|
16
|
-
- [Development](#development)
|
|
17
|
-
- [Troubleshooting](#troubleshooting)
|
|
18
|
-
- [Changelog](#changelog)
|
|
19
|
-
- [Support](#support)
|
|
20
|
-
- [License](#license)
|
|
21
|
-
|
|
22
|
-
## What Makes This "Advanced"?
|
|
23
|
-
|
|
24
|
-
This enhanced version extends the standard n8n Mautic node with:
|
|
25
|
-
|
|
26
|
-
- **🏷️ Complete Tag Management**: Full CRUD operations for tags (missing in the standard node)
|
|
27
|
-
- **📊 Campaign Operations**: Create, clone, update, and manage campaigns
|
|
28
|
-
- **📁 Category Management**: Handle categories with bundle and color support
|
|
29
|
-
- **🔔 Notification Management**: Full CRUD operations for notifications with scheduling and language support
|
|
30
|
-
- **🔗 Advanced Relationship Management**: Sophisticated contact-to-campaign and contact-to-company associations
|
|
31
|
-
- **📧 Enhanced Email Operations**: Segment-based email sending capabilities
|
|
32
|
-
- **👥 Extended Contact Operations**: UTM tag management, activity tracking, device information, and notes
|
|
33
|
-
- **🏢 Complete Company Management**: Full company lifecycle with custom fields and address support
|
|
34
|
-
- **🔍 Advanced Filtering**: Where filters, DNC filtering, and field selection
|
|
35
|
-
- **📅 Smart Date Handling**: Automatic date formatting for Mautic API compatibility
|
|
36
|
-
|
|
37
|
-
## Features
|
|
38
|
-
|
|
39
|
-
### 🚀 Core Features
|
|
40
|
-
- **Comprehensive API Coverage**: All major Mautic API endpoints supported
|
|
41
|
-
- **Advanced Filtering**: Where filters with nested conditions (andX/orX)
|
|
42
|
-
- **DNC Management**: Filter contacts by Do Not Contact status
|
|
43
|
-
- **Field Selection**: Choose which fields to return for Contact operations
|
|
44
|
-
- **Pagination Support**: Automatic handling of large datasets
|
|
45
|
-
- **Custom Fields**: Full support for custom field management
|
|
46
|
-
- **Error Handling**: Robust error handling and validation
|
|
47
|
-
|
|
48
|
-
### 🔐 Authentication
|
|
49
|
-
- **API Credentials**: Simple API key authentication
|
|
50
|
-
- **OAuth2**: Full OAuth2 flow support for secure authentication
|
|
51
|
-
|
|
52
|
-
### 📊 Data Management
|
|
53
|
-
- **RAW Data Options**: Control data output format
|
|
54
|
-
- **System Fields**: Built-in support for system fields
|
|
55
|
-
- **Date Formatting**: Automatic UTC date formatting
|
|
56
|
-
- **Deduplication**: Prevents duplicate records in paginated results
|
|
57
|
-
|
|
58
|
-
## Supported Resources and Operations
|
|
59
|
-
|
|
60
|
-
### 🏢 Companies
|
|
61
|
-
- **Create** a new company with full address and custom field support
|
|
62
|
-
- **Get** a company by ID
|
|
63
|
-
- **Get Many** companies with filtering and pagination
|
|
64
|
-
- **Update** company details
|
|
65
|
-
- **Delete** a company
|
|
66
|
-
|
|
67
|
-
### 👥 Contacts (Enhanced)
|
|
68
|
-
- **Create** a new contact with extensive field options
|
|
69
|
-
- **Get** a contact by ID with field selection
|
|
70
|
-
- **Get Many** contacts with advanced filtering and DNC options
|
|
71
|
-
- **Update** contact details
|
|
72
|
-
- **Delete** a contact
|
|
73
|
-
- **Delete Batch** multiple contacts in one operation
|
|
74
|
-
- **Send Email** to a contact
|
|
75
|
-
- **Edit Contact Points** (add/subtract points)
|
|
76
|
-
- **Edit Do Not Contact List** (add/remove from DNC)
|
|
77
|
-
- **Add/Remove UTM Tags** for tracking
|
|
78
|
-
- **Get Notes** associated with a contact
|
|
79
|
-
- **Get Activity** history for a contact
|
|
80
|
-
- **Get Companies** associated with a contact
|
|
81
|
-
- **Get Devices** used by a contact
|
|
82
|
-
|
|
83
|
-
### 🏷️ Tags
|
|
84
|
-
- **Create** a new tag with description
|
|
85
|
-
- **Get** a tag by ID
|
|
86
|
-
- **Get Many** tags with search capabilities
|
|
87
|
-
- **Update** tag name and description
|
|
88
|
-
- **Delete** a tag
|
|
89
|
-
|
|
90
|
-
### 📊 Campaigns
|
|
91
|
-
- **Create** a new campaign
|
|
92
|
-
- **Get** a campaign by ID
|
|
93
|
-
- **Get All** campaigns
|
|
94
|
-
- **Update** campaign details
|
|
95
|
-
- **Delete** a campaign
|
|
96
|
-
- **Clone** an existing campaign
|
|
97
|
-
- **Get Contacts** in a campaign
|
|
98
|
-
|
|
99
|
-
### 📁 Categories
|
|
100
|
-
- **Create** a new category with bundle and color settings
|
|
101
|
-
- **Get** a category by ID
|
|
102
|
-
- **Get Many** categories
|
|
103
|
-
- **Update** category details
|
|
104
|
-
- **Delete** a category
|
|
105
|
-
|
|
106
|
-
### 🔔 Notifications
|
|
107
|
-
- **Create** a new notification with scheduling and language support
|
|
108
|
-
- **Get** a notification by ID
|
|
109
|
-
- **Get Many** notifications with filtering
|
|
110
|
-
- **Update** notification details
|
|
111
|
-
- **Delete** a notification
|
|
112
|
-
|
|
113
|
-
### 📋 Segments
|
|
114
|
-
- **Create** a new segment
|
|
115
|
-
- **Get** a segment by ID
|
|
116
|
-
- **Get Many** segments with filtering
|
|
117
|
-
- **Update** segment details
|
|
118
|
-
- **Delete** a segment
|
|
119
|
-
|
|
120
|
-
### 🔗 Relationship Management
|
|
121
|
-
- **Campaign Contact**: Add/remove contacts to/from campaigns
|
|
122
|
-
- **Company Contact**: Add/remove contacts to/from companies
|
|
123
|
-
- **Contact Segment**: Add/remove contacts to/from segments
|
|
124
|
-
|
|
125
|
-
### 📧 Email Operations
|
|
126
|
-
- **Segment Email**: Send emails to segments
|
|
127
|
-
|
|
128
|
-
## Installation
|
|
129
|
-
|
|
130
|
-
### Method 1: npm (Recommended)
|
|
131
|
-
```bash
|
|
132
|
-
npm install n8n-nodes-mautic-advanced
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Method 2: Manual Installation
|
|
136
|
-
1. Clone this repository:
|
|
137
|
-
```bash
|
|
138
|
-
git clone https://github.com/msoukhomlinov/n8n-nodes-mautic-advanced.git
|
|
139
|
-
cd n8n-nodes-mautic-advanced
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
2. Install dependencies:
|
|
143
|
-
```bash
|
|
144
|
-
npm install
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
3. Build the node:
|
|
148
|
-
```bash
|
|
149
|
-
npm run build
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
4. Link to your n8n installation:
|
|
153
|
-
```bash
|
|
154
|
-
npm link
|
|
155
|
-
cd /path/to/your/n8n/installation
|
|
156
|
-
npm link n8n-nodes-mautic-advanced
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
## Authentication
|
|
160
|
-
|
|
161
|
-
### API Credentials
|
|
162
|
-
1. Go to your Mautic instance
|
|
163
|
-
2. Navigate to **Settings** → **API Credentials**
|
|
164
|
-
3. Create a new API credential
|
|
165
|
-
4. Copy the **Public Key** and **Secret Key**
|
|
166
|
-
5. In n8n, add a new Mautic Advanced credential
|
|
167
|
-
6. Select **Credentials** authentication method
|
|
168
|
-
7. Enter your Mautic URL, Public Key, and Secret Key
|
|
169
|
-
|
|
170
|
-
### OAuth2
|
|
171
|
-
1. In n8n, add a new Mautic Advanced credential
|
|
172
|
-
2. Select **OAuth2** authentication method
|
|
173
|
-
3. Enter your Mautic URL
|
|
174
|
-
4. Follow the OAuth2 authorization flow
|
|
175
|
-
|
|
176
|
-
## Advanced Features
|
|
177
|
-
|
|
178
|
-
### Where Filters
|
|
179
|
-
Advanced filtering for Contact > Get Many operations:
|
|
180
|
-
- **Nested Conditions**: Support for andX/orX logical operators
|
|
181
|
-
- **Multiple Expressions**: eq, neq, lt, lte, gt, gte, between, in, isNull, isNotNull
|
|
182
|
-
- **Custom Fields**: Filter by any custom or system field
|
|
183
|
-
- **Date Filtering**: Automatic date formatting for Mautic API
|
|
184
|
-
|
|
185
|
-
### DNC Filtering
|
|
186
|
-
Filter contacts by Do Not Contact status:
|
|
187
|
-
- **Email DNC Only**: Contacts with email DNC enabled
|
|
188
|
-
- **SMS DNC Only**: Contacts with SMS DNC enabled
|
|
189
|
-
- **Any DNC Only**: Contacts with any DNC enabled
|
|
190
|
-
|
|
191
|
-
### Field Selection
|
|
192
|
-
Choose which fields to return for Contact operations:
|
|
193
|
-
- **System Fields**: date_added, date_modified, id, owner_id
|
|
194
|
-
- **Custom Fields**: Any custom field defined in Mautic
|
|
195
|
-
- **All Fields**: Return complete contact data
|
|
196
|
-
|
|
197
|
-
### Date Formatting
|
|
198
|
-
Automatic date formatting for known date fields:
|
|
199
|
-
- **Format**: YYYY-MM-DD HH:mm:ss UTC
|
|
200
|
-
- **Compatibility**: Ensures Mautic API compatibility
|
|
201
|
-
- **Fields**: date_added, date_modified, lastActive, etc.
|
|
202
|
-
|
|
203
|
-
## Usage Examples
|
|
204
|
-
|
|
205
|
-
### Create a Contact with Tags
|
|
206
|
-
```javascript
|
|
207
|
-
// Contact Create operation
|
|
208
|
-
{
|
|
209
|
-
"email": "john.doe@example.com",
|
|
210
|
-
"firstName": "John",
|
|
211
|
-
"lastName": "Doe",
|
|
212
|
-
"additionalFields": {
|
|
213
|
-
"tags": ["customer", "vip"],
|
|
214
|
-
"company": "Example Corp",
|
|
215
|
-
"phone": "+1234567890"
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
### Filter Contacts with Where Conditions
|
|
221
|
-
```javascript
|
|
222
|
-
// Contact Get Many with Where filter
|
|
223
|
-
{
|
|
224
|
-
"where": {
|
|
225
|
-
"conditions": [
|
|
226
|
-
{
|
|
227
|
-
"col": "email",
|
|
228
|
-
"expr": "neq",
|
|
229
|
-
"val": ""
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
"col": "date_added",
|
|
233
|
-
"expr": "gte",
|
|
234
|
-
"val": "2024-01-01"
|
|
235
|
-
}
|
|
236
|
-
]
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
### Send Email to Segment
|
|
242
|
-
```javascript
|
|
243
|
-
// Segment Email operation
|
|
244
|
-
{
|
|
245
|
-
"segmentId": "123",
|
|
246
|
-
"emailId": "456",
|
|
247
|
-
"options": {
|
|
248
|
-
"sendToNewOnly": true
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
## Development
|
|
254
|
-
|
|
255
|
-
### Prerequisites
|
|
256
|
-
- Node.js 16+
|
|
257
|
-
- npm or yarn
|
|
258
|
-
- n8n development environment
|
|
259
|
-
|
|
260
|
-
### Commands
|
|
261
|
-
```bash
|
|
262
|
-
# Install dependencies
|
|
263
|
-
npm install
|
|
264
|
-
|
|
265
|
-
# Build the node
|
|
266
|
-
npm run build
|
|
267
|
-
|
|
268
|
-
# Watch for changes (development)
|
|
269
|
-
npm run dev
|
|
270
|
-
|
|
271
|
-
# Check for linting errors
|
|
272
|
-
npm run lint
|
|
273
|
-
|
|
274
|
-
# Fix linting errors
|
|
275
|
-
npm run lint:fix
|
|
276
|
-
|
|
277
|
-
# Format code
|
|
278
|
-
npm run format
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
### Project Structure
|
|
282
|
-
```
|
|
283
|
-
├── credentials/ # Authentication credentials
|
|
284
|
-
├── nodes/ # Node implementations
|
|
285
|
-
│ └── MauticAdvanced/ # Main node files
|
|
286
|
-
├── dist/ # Compiled output
|
|
287
|
-
├── package.json # Project configuration
|
|
288
|
-
└── README.md # This file
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
## Troubleshooting
|
|
292
|
-
|
|
293
|
-
### Common Issues
|
|
294
|
-
|
|
295
|
-
#### "Could not get parameter 'options'" Error
|
|
296
|
-
**Cause**: Missing Options parameter in node definition
|
|
297
|
-
**Solution**: Update to latest version (0.3.2+) which includes all required Options parameters
|
|
298
|
-
|
|
299
|
-
#### Authentication Errors
|
|
300
|
-
**Cause**: Incorrect credentials or URL
|
|
301
|
-
**Solution**:
|
|
302
|
-
- Verify Mautic URL format (https://your-mautic.com)
|
|
303
|
-
- Check API credentials are active
|
|
304
|
-
- Ensure proper permissions for API access
|
|
305
|
-
|
|
306
|
-
#### Date Filter Issues
|
|
307
|
-
**Cause**: Incorrect date format
|
|
308
|
-
**Solution**: Use YYYY-MM-DD format for date filters
|
|
309
|
-
|
|
310
|
-
#### Pagination Problems
|
|
311
|
-
**Cause**: Large datasets causing timeouts
|
|
312
|
-
**Solution**: Use "Return All" option or set appropriate limits
|
|
313
|
-
|
|
314
|
-
### Getting Help
|
|
315
|
-
1. Check the [Changelog](CHANGELOG.md) for recent fixes
|
|
316
|
-
2. Search existing [Issues](https://github.com/msoukhomlinov/n8n-nodes-mautic-advanced/issues)
|
|
317
|
-
3. Create a new issue with detailed information
|
|
318
|
-
|
|
319
|
-
## Support
|
|
320
|
-
|
|
321
|
-
If you find this node helpful and want to support its ongoing development, you can buy me a coffee:
|
|
322
|
-
|
|
323
|
-
[](https://buymeacoffee.com/msoukhomlinov)
|
|
324
|
-
|
|
325
|
-
Your support helps maintain this project and develop new features.
|
|
326
|
-
|
|
327
|
-
## License
|
|
328
|
-
|
|
329
|
-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
330
|
-
|
|
331
|
-
## Credits
|
|
332
|
-
|
|
333
|
-
- Built with [n8n](https://n8n.io/) workflow automation platform
|
|
334
|
-
- Uses [change-case](https://github.com/blakeembrey/change-case) for string manipulation
|
|
335
|
-
- Icons and design inspired by n8n community standards
|
|
1
|
+
# n8n Mautic Advanced Node
|
|
2
|
+
|
|
3
|
+
[](https://buymeacoffee.com/msoukhomlinov)
|
|
4
|
+
|
|
5
|
+
Enhanced n8n node for Mautic with comprehensive API coverage including tags, campaigns, categories, notifications, and advanced contact management.
|
|
6
|
+
|
|
7
|
+
## 📋 Table of Contents
|
|
8
|
+
|
|
9
|
+
- [What Makes This "Advanced"?](#what-makes-this-advanced)
|
|
10
|
+
- [Features](#features)
|
|
11
|
+
- [Supported Resources and Operations](#supported-resources-and-operations)
|
|
12
|
+
- [Installation](#installation)
|
|
13
|
+
- [Authentication](#authentication)
|
|
14
|
+
- [Advanced Features](#advanced-features)
|
|
15
|
+
- [Usage Examples](#usage-examples)
|
|
16
|
+
- [Development](#development)
|
|
17
|
+
- [Troubleshooting](#troubleshooting)
|
|
18
|
+
- [Changelog](#changelog)
|
|
19
|
+
- [Support](#support)
|
|
20
|
+
- [License](#license)
|
|
21
|
+
|
|
22
|
+
## What Makes This "Advanced"?
|
|
23
|
+
|
|
24
|
+
This enhanced version extends the standard n8n Mautic node with:
|
|
25
|
+
|
|
26
|
+
- **🏷️ Complete Tag Management**: Full CRUD operations for tags (missing in the standard node)
|
|
27
|
+
- **📊 Campaign Operations**: Create, clone, update, and manage campaigns
|
|
28
|
+
- **📁 Category Management**: Handle categories with bundle and color support
|
|
29
|
+
- **🔔 Notification Management**: Full CRUD operations for notifications with scheduling and language support
|
|
30
|
+
- **🔗 Advanced Relationship Management**: Sophisticated contact-to-campaign and contact-to-company associations
|
|
31
|
+
- **📧 Enhanced Email Operations**: Segment-based email sending capabilities
|
|
32
|
+
- **👥 Extended Contact Operations**: UTM tag management, activity tracking, device information, and notes
|
|
33
|
+
- **🏢 Complete Company Management**: Full company lifecycle with custom fields and address support
|
|
34
|
+
- **🔍 Advanced Filtering**: Where filters, DNC filtering, and field selection
|
|
35
|
+
- **📅 Smart Date Handling**: Automatic date formatting for Mautic API compatibility
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
### 🚀 Core Features
|
|
40
|
+
- **Comprehensive API Coverage**: All major Mautic API endpoints supported
|
|
41
|
+
- **Advanced Filtering**: Where filters with nested conditions (andX/orX)
|
|
42
|
+
- **DNC Management**: Filter contacts by Do Not Contact status
|
|
43
|
+
- **Field Selection**: Choose which fields to return for Contact operations
|
|
44
|
+
- **Pagination Support**: Automatic handling of large datasets
|
|
45
|
+
- **Custom Fields**: Full support for custom field management
|
|
46
|
+
- **Error Handling**: Robust error handling and validation
|
|
47
|
+
|
|
48
|
+
### 🔐 Authentication
|
|
49
|
+
- **API Credentials**: Simple API key authentication
|
|
50
|
+
- **OAuth2**: Full OAuth2 flow support for secure authentication
|
|
51
|
+
|
|
52
|
+
### 📊 Data Management
|
|
53
|
+
- **RAW Data Options**: Control data output format
|
|
54
|
+
- **System Fields**: Built-in support for system fields
|
|
55
|
+
- **Date Formatting**: Automatic UTC date formatting
|
|
56
|
+
- **Deduplication**: Prevents duplicate records in paginated results
|
|
57
|
+
|
|
58
|
+
## Supported Resources and Operations
|
|
59
|
+
|
|
60
|
+
### 🏢 Companies
|
|
61
|
+
- **Create** a new company with full address and custom field support
|
|
62
|
+
- **Get** a company by ID
|
|
63
|
+
- **Get Many** companies with filtering and pagination
|
|
64
|
+
- **Update** company details
|
|
65
|
+
- **Delete** a company
|
|
66
|
+
|
|
67
|
+
### 👥 Contacts (Enhanced)
|
|
68
|
+
- **Create** a new contact with extensive field options
|
|
69
|
+
- **Get** a contact by ID with field selection
|
|
70
|
+
- **Get Many** contacts with advanced filtering and DNC options
|
|
71
|
+
- **Update** contact details
|
|
72
|
+
- **Delete** a contact
|
|
73
|
+
- **Delete Batch** multiple contacts in one operation
|
|
74
|
+
- **Send Email** to a contact
|
|
75
|
+
- **Edit Contact Points** (add/subtract points)
|
|
76
|
+
- **Edit Do Not Contact List** (add/remove from DNC)
|
|
77
|
+
- **Add/Remove UTM Tags** for tracking
|
|
78
|
+
- **Get Notes** associated with a contact
|
|
79
|
+
- **Get Activity** history for a contact
|
|
80
|
+
- **Get Companies** associated with a contact
|
|
81
|
+
- **Get Devices** used by a contact
|
|
82
|
+
|
|
83
|
+
### 🏷️ Tags
|
|
84
|
+
- **Create** a new tag with description
|
|
85
|
+
- **Get** a tag by ID
|
|
86
|
+
- **Get Many** tags with search capabilities
|
|
87
|
+
- **Update** tag name and description
|
|
88
|
+
- **Delete** a tag
|
|
89
|
+
|
|
90
|
+
### 📊 Campaigns
|
|
91
|
+
- **Create** a new campaign
|
|
92
|
+
- **Get** a campaign by ID
|
|
93
|
+
- **Get All** campaigns
|
|
94
|
+
- **Update** campaign details
|
|
95
|
+
- **Delete** a campaign
|
|
96
|
+
- **Clone** an existing campaign
|
|
97
|
+
- **Get Contacts** in a campaign
|
|
98
|
+
|
|
99
|
+
### 📁 Categories
|
|
100
|
+
- **Create** a new category with bundle and color settings
|
|
101
|
+
- **Get** a category by ID
|
|
102
|
+
- **Get Many** categories
|
|
103
|
+
- **Update** category details
|
|
104
|
+
- **Delete** a category
|
|
105
|
+
|
|
106
|
+
### 🔔 Notifications
|
|
107
|
+
- **Create** a new notification with scheduling and language support
|
|
108
|
+
- **Get** a notification by ID
|
|
109
|
+
- **Get Many** notifications with filtering
|
|
110
|
+
- **Update** notification details
|
|
111
|
+
- **Delete** a notification
|
|
112
|
+
|
|
113
|
+
### 📋 Segments
|
|
114
|
+
- **Create** a new segment
|
|
115
|
+
- **Get** a segment by ID
|
|
116
|
+
- **Get Many** segments with filtering
|
|
117
|
+
- **Update** segment details
|
|
118
|
+
- **Delete** a segment
|
|
119
|
+
|
|
120
|
+
### 🔗 Relationship Management
|
|
121
|
+
- **Campaign Contact**: Add/remove contacts to/from campaigns
|
|
122
|
+
- **Company Contact**: Add/remove contacts to/from companies
|
|
123
|
+
- **Contact Segment**: Add/remove contacts to/from segments
|
|
124
|
+
|
|
125
|
+
### 📧 Email Operations
|
|
126
|
+
- **Segment Email**: Send emails to segments
|
|
127
|
+
|
|
128
|
+
## Installation
|
|
129
|
+
|
|
130
|
+
### Method 1: npm (Recommended)
|
|
131
|
+
```bash
|
|
132
|
+
npm install n8n-nodes-mautic-advanced
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Method 2: Manual Installation
|
|
136
|
+
1. Clone this repository:
|
|
137
|
+
```bash
|
|
138
|
+
git clone https://github.com/msoukhomlinov/n8n-nodes-mautic-advanced.git
|
|
139
|
+
cd n8n-nodes-mautic-advanced
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
2. Install dependencies:
|
|
143
|
+
```bash
|
|
144
|
+
npm install
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
3. Build the node:
|
|
148
|
+
```bash
|
|
149
|
+
npm run build
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
4. Link to your n8n installation:
|
|
153
|
+
```bash
|
|
154
|
+
npm link
|
|
155
|
+
cd /path/to/your/n8n/installation
|
|
156
|
+
npm link n8n-nodes-mautic-advanced
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Authentication
|
|
160
|
+
|
|
161
|
+
### API Credentials
|
|
162
|
+
1. Go to your Mautic instance
|
|
163
|
+
2. Navigate to **Settings** → **API Credentials**
|
|
164
|
+
3. Create a new API credential
|
|
165
|
+
4. Copy the **Public Key** and **Secret Key**
|
|
166
|
+
5. In n8n, add a new Mautic Advanced credential
|
|
167
|
+
6. Select **Credentials** authentication method
|
|
168
|
+
7. Enter your Mautic URL, Public Key, and Secret Key
|
|
169
|
+
|
|
170
|
+
### OAuth2
|
|
171
|
+
1. In n8n, add a new Mautic Advanced credential
|
|
172
|
+
2. Select **OAuth2** authentication method
|
|
173
|
+
3. Enter your Mautic URL
|
|
174
|
+
4. Follow the OAuth2 authorization flow
|
|
175
|
+
|
|
176
|
+
## Advanced Features
|
|
177
|
+
|
|
178
|
+
### Where Filters
|
|
179
|
+
Advanced filtering for Contact > Get Many operations:
|
|
180
|
+
- **Nested Conditions**: Support for andX/orX logical operators
|
|
181
|
+
- **Multiple Expressions**: eq, neq, lt, lte, gt, gte, between, in, isNull, isNotNull
|
|
182
|
+
- **Custom Fields**: Filter by any custom or system field
|
|
183
|
+
- **Date Filtering**: Automatic date formatting for Mautic API
|
|
184
|
+
|
|
185
|
+
### DNC Filtering
|
|
186
|
+
Filter contacts by Do Not Contact status:
|
|
187
|
+
- **Email DNC Only**: Contacts with email DNC enabled
|
|
188
|
+
- **SMS DNC Only**: Contacts with SMS DNC enabled
|
|
189
|
+
- **Any DNC Only**: Contacts with any DNC enabled
|
|
190
|
+
|
|
191
|
+
### Field Selection
|
|
192
|
+
Choose which fields to return for Contact operations:
|
|
193
|
+
- **System Fields**: date_added, date_modified, id, owner_id
|
|
194
|
+
- **Custom Fields**: Any custom field defined in Mautic
|
|
195
|
+
- **All Fields**: Return complete contact data
|
|
196
|
+
|
|
197
|
+
### Date Formatting
|
|
198
|
+
Automatic date formatting for known date fields:
|
|
199
|
+
- **Format**: YYYY-MM-DD HH:mm:ss UTC
|
|
200
|
+
- **Compatibility**: Ensures Mautic API compatibility
|
|
201
|
+
- **Fields**: date_added, date_modified, lastActive, etc.
|
|
202
|
+
|
|
203
|
+
## Usage Examples
|
|
204
|
+
|
|
205
|
+
### Create a Contact with Tags
|
|
206
|
+
```javascript
|
|
207
|
+
// Contact Create operation
|
|
208
|
+
{
|
|
209
|
+
"email": "john.doe@example.com",
|
|
210
|
+
"firstName": "John",
|
|
211
|
+
"lastName": "Doe",
|
|
212
|
+
"additionalFields": {
|
|
213
|
+
"tags": ["customer", "vip"],
|
|
214
|
+
"company": "Example Corp",
|
|
215
|
+
"phone": "+1234567890"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Filter Contacts with Where Conditions
|
|
221
|
+
```javascript
|
|
222
|
+
// Contact Get Many with Where filter
|
|
223
|
+
{
|
|
224
|
+
"where": {
|
|
225
|
+
"conditions": [
|
|
226
|
+
{
|
|
227
|
+
"col": "email",
|
|
228
|
+
"expr": "neq",
|
|
229
|
+
"val": ""
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"col": "date_added",
|
|
233
|
+
"expr": "gte",
|
|
234
|
+
"val": "2024-01-01"
|
|
235
|
+
}
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Send Email to Segment
|
|
242
|
+
```javascript
|
|
243
|
+
// Segment Email operation
|
|
244
|
+
{
|
|
245
|
+
"segmentId": "123",
|
|
246
|
+
"emailId": "456",
|
|
247
|
+
"options": {
|
|
248
|
+
"sendToNewOnly": true
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Development
|
|
254
|
+
|
|
255
|
+
### Prerequisites
|
|
256
|
+
- Node.js 16+
|
|
257
|
+
- npm or yarn
|
|
258
|
+
- n8n development environment
|
|
259
|
+
|
|
260
|
+
### Commands
|
|
261
|
+
```bash
|
|
262
|
+
# Install dependencies
|
|
263
|
+
npm install
|
|
264
|
+
|
|
265
|
+
# Build the node
|
|
266
|
+
npm run build
|
|
267
|
+
|
|
268
|
+
# Watch for changes (development)
|
|
269
|
+
npm run dev
|
|
270
|
+
|
|
271
|
+
# Check for linting errors
|
|
272
|
+
npm run lint
|
|
273
|
+
|
|
274
|
+
# Fix linting errors
|
|
275
|
+
npm run lint:fix
|
|
276
|
+
|
|
277
|
+
# Format code
|
|
278
|
+
npm run format
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Project Structure
|
|
282
|
+
```
|
|
283
|
+
├── credentials/ # Authentication credentials
|
|
284
|
+
├── nodes/ # Node implementations
|
|
285
|
+
│ └── MauticAdvanced/ # Main node files
|
|
286
|
+
├── dist/ # Compiled output
|
|
287
|
+
├── package.json # Project configuration
|
|
288
|
+
└── README.md # This file
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Troubleshooting
|
|
292
|
+
|
|
293
|
+
### Common Issues
|
|
294
|
+
|
|
295
|
+
#### "Could not get parameter 'options'" Error
|
|
296
|
+
**Cause**: Missing Options parameter in node definition
|
|
297
|
+
**Solution**: Update to latest version (0.3.2+) which includes all required Options parameters
|
|
298
|
+
|
|
299
|
+
#### Authentication Errors
|
|
300
|
+
**Cause**: Incorrect credentials or URL
|
|
301
|
+
**Solution**:
|
|
302
|
+
- Verify Mautic URL format (https://your-mautic.com)
|
|
303
|
+
- Check API credentials are active
|
|
304
|
+
- Ensure proper permissions for API access
|
|
305
|
+
|
|
306
|
+
#### Date Filter Issues
|
|
307
|
+
**Cause**: Incorrect date format
|
|
308
|
+
**Solution**: Use YYYY-MM-DD format for date filters
|
|
309
|
+
|
|
310
|
+
#### Pagination Problems
|
|
311
|
+
**Cause**: Large datasets causing timeouts
|
|
312
|
+
**Solution**: Use "Return All" option or set appropriate limits
|
|
313
|
+
|
|
314
|
+
### Getting Help
|
|
315
|
+
1. Check the [Changelog](CHANGELOG.md) for recent fixes
|
|
316
|
+
2. Search existing [Issues](https://github.com/msoukhomlinov/n8n-nodes-mautic-advanced/issues)
|
|
317
|
+
3. Create a new issue with detailed information
|
|
318
|
+
|
|
319
|
+
## Support
|
|
320
|
+
|
|
321
|
+
If you find this node helpful and want to support its ongoing development, you can buy me a coffee:
|
|
322
|
+
|
|
323
|
+
[](https://buymeacoffee.com/msoukhomlinov)
|
|
324
|
+
|
|
325
|
+
Your support helps maintain this project and develop new features.
|
|
326
|
+
|
|
327
|
+
## License
|
|
328
|
+
|
|
329
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
330
|
+
|
|
331
|
+
## Credits
|
|
332
|
+
|
|
333
|
+
- Built with [n8n](https://n8n.io/) workflow automation platform
|
|
334
|
+
- Uses [change-case](https://github.com/blakeembrey/change-case) for string manipulation
|
|
335
|
+
- Icons and design inspired by n8n community standards
|
|
@@ -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
|
},
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="349.779" height="349.779">
|
|
2
|
-
<path
|
|
3
|
-
fill="#FFF"
|
|
4
|
-
d="M174.89 349.779C78.612 349.779 0 271.462 0 174.89S78.612 0 174.89 0c23.26 0 45.931 4.417 67.129 13.543 5.889 2.65 8.833 9.422 6.478 15.605-2.649 5.888-9.421 8.833-15.604 6.477-18.549-7.655-37.98-11.482-58.002-11.482-83.323 0-151.041 67.718-151.041 151.041s67.717 151.041 151.04 151.041 151.041-67.718 151.041-151.041c0-17.96-2.944-35.332-9.127-51.819-2.355-6.183.883-12.955 7.066-15.31s12.954.883 15.31 7.066c7.066 19.138 10.6 39.453 10.6 60.063-.001 95.983-78.318 174.595-174.89 174.595"
|
|
5
|
-
/>
|
|
6
|
-
<path
|
|
7
|
-
fill="#FDB933"
|
|
8
|
-
d="m251.44 156.93-27.086 28.264 15.015 63.302h34.153zm-11.187-83.618 9.421 9.422-74.784 79.201-63.891-65.658-36.803 152.219h34.154l20.315-84.5 46.225 50.347 98.927-107.76 9.422 9.716 9.421-53.292z"
|
|
9
|
-
/>
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="349.779" height="349.779">
|
|
2
|
+
<path
|
|
3
|
+
fill="#FFF"
|
|
4
|
+
d="M174.89 349.779C78.612 349.779 0 271.462 0 174.89S78.612 0 174.89 0c23.26 0 45.931 4.417 67.129 13.543 5.889 2.65 8.833 9.422 6.478 15.605-2.649 5.888-9.421 8.833-15.604 6.477-18.549-7.655-37.98-11.482-58.002-11.482-83.323 0-151.041 67.718-151.041 151.041s67.717 151.041 151.04 151.041 151.041-67.718 151.041-151.041c0-17.96-2.944-35.332-9.127-51.819-2.355-6.183.883-12.955 7.066-15.31s12.954.883 15.31 7.066c7.066 19.138 10.6 39.453 10.6 60.063-.001 95.983-78.318 174.595-174.89 174.595"
|
|
5
|
+
/>
|
|
6
|
+
<path
|
|
7
|
+
fill="#FDB933"
|
|
8
|
+
d="m251.44 156.93-27.086 28.264 15.015 63.302h34.153zm-11.187-83.618 9.421 9.422-74.784 79.201-63.891-65.658-36.803 152.219h34.154l20.315-84.5 46.225 50.347 98.927-107.76 9.422 9.716 9.421-53.292z"
|
|
9
|
+
/>
|
|
10
10
|
</svg>
|
|
@@ -162,7 +162,26 @@ exports.segmentFields = [
|
|
|
162
162
|
{
|
|
163
163
|
displayName: 'Type',
|
|
164
164
|
name: 'type',
|
|
165
|
-
type: '
|
|
165
|
+
type: 'options',
|
|
166
|
+
options: [
|
|
167
|
+
{ name: 'Boolean', value: 'boolean' },
|
|
168
|
+
{ name: 'Date', value: 'date' },
|
|
169
|
+
{ name: 'DateTime', value: 'datetime' },
|
|
170
|
+
{ name: 'Email', value: 'email' },
|
|
171
|
+
{ name: 'Country', value: 'country' },
|
|
172
|
+
{ name: 'Locale', value: 'locale' },
|
|
173
|
+
{ name: 'Lookup', value: 'lookup' },
|
|
174
|
+
{ name: 'Number', value: 'number' },
|
|
175
|
+
{ name: 'Tel', value: 'tel' },
|
|
176
|
+
{ name: 'Region', value: 'region' },
|
|
177
|
+
{ name: 'Select', value: 'select' },
|
|
178
|
+
{ name: 'Multiselect', value: 'multiselect' },
|
|
179
|
+
{ name: 'Text', value: 'text' },
|
|
180
|
+
{ name: 'Textarea', value: 'textarea' },
|
|
181
|
+
{ name: 'Time', value: 'time' },
|
|
182
|
+
{ name: 'Timezone', value: 'timezone' },
|
|
183
|
+
{ name: 'URL', value: 'url' },
|
|
184
|
+
],
|
|
166
185
|
default: 'email',
|
|
167
186
|
},
|
|
168
187
|
{
|
|
@@ -178,6 +197,10 @@ exports.segmentFields = [
|
|
|
178
197
|
options: [
|
|
179
198
|
{ name: '=', value: '=' },
|
|
180
199
|
{ name: '!=', value: '!=' },
|
|
200
|
+
{ name: '>', value: 'gt' },
|
|
201
|
+
{ name: '>=', value: 'gte' },
|
|
202
|
+
{ name: '<', value: 'lt' },
|
|
203
|
+
{ name: '<=', value: 'lte' },
|
|
181
204
|
{ name: 'Empty', value: 'empty' },
|
|
182
205
|
{ name: 'Not Empty', value: '!empty' },
|
|
183
206
|
{ name: 'Like', value: 'like' },
|
|
@@ -187,9 +210,20 @@ exports.segmentFields = [
|
|
|
187
210
|
{ name: 'Starts With', value: 'startsWith' },
|
|
188
211
|
{ name: 'Ends With', value: 'endsWith' },
|
|
189
212
|
{ name: 'Contains', value: 'contains' },
|
|
213
|
+
{ name: 'In', value: 'in' },
|
|
214
|
+
{ name: 'Not In', value: '!in' },
|
|
215
|
+
{ name: 'Between', value: 'between' },
|
|
216
|
+
{ name: 'Not Between', value: '!between' },
|
|
190
217
|
],
|
|
191
218
|
default: 'like',
|
|
192
219
|
},
|
|
220
|
+
{
|
|
221
|
+
displayName: 'Display',
|
|
222
|
+
name: 'display',
|
|
223
|
+
type: 'string',
|
|
224
|
+
default: '',
|
|
225
|
+
description: 'Optional display name for the filter',
|
|
226
|
+
},
|
|
193
227
|
],
|
|
194
228
|
},
|
|
195
229
|
],
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -152,7 +158,8 @@ async function getContact(context, itemIndex) {
|
|
|
152
158
|
const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
|
|
153
159
|
const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'GET', `/contacts/${contactId}`);
|
|
154
160
|
const contactData = [response.contact];
|
|
155
|
-
|
|
161
|
+
const processedData = (0, DataHelpers_1.processContactFields)(contactData, options, options.fieldsToReturn);
|
|
162
|
+
return (0, DataHelpers_1.convertNumericStrings)(processedData);
|
|
156
163
|
}
|
|
157
164
|
async function getAllContacts(context, itemIndex) {
|
|
158
165
|
const returnAll = (0, ApiHelpers_1.getOptionalParam)(context, 'returnAll', itemIndex, false);
|
|
@@ -190,7 +197,8 @@ async function getAllContacts(context, itemIndex) {
|
|
|
190
197
|
responseData = response.contacts ? Object.values(response.contacts) : [];
|
|
191
198
|
}
|
|
192
199
|
}
|
|
193
|
-
|
|
200
|
+
const processedData = (0, DataHelpers_1.processContactFields)(responseData, options, options.fieldsToReturn);
|
|
201
|
+
return (0, DataHelpers_1.convertNumericStrings)(processedData);
|
|
194
202
|
}
|
|
195
203
|
async function deleteContact(context, itemIndex) {
|
|
196
204
|
const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {});
|
|
@@ -220,8 +228,16 @@ async function editContactPoints(context, itemIndex) {
|
|
|
220
228
|
const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
|
|
221
229
|
const action = (0, ApiHelpers_1.getRequiredParam)(context, 'action', itemIndex);
|
|
222
230
|
const points = (0, ApiHelpers_1.getRequiredParam)(context, 'points', itemIndex);
|
|
223
|
-
const
|
|
224
|
-
const
|
|
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}`;
|
|
225
241
|
const response = await (0, ApiHelpers_1.makeApiRequest)(context, 'POST', endpoint, body);
|
|
226
242
|
return response.contact;
|
|
227
243
|
}
|
|
@@ -229,8 +245,20 @@ async function editDoNotContactList(context, itemIndex) {
|
|
|
229
245
|
const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
|
|
230
246
|
const action = (0, ApiHelpers_1.getRequiredParam)(context, 'action', itemIndex);
|
|
231
247
|
const channel = (0, ApiHelpers_1.getRequiredParam)(context, 'channel', itemIndex);
|
|
232
|
-
const
|
|
233
|
-
const
|
|
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);
|
|
234
262
|
return response.contact;
|
|
235
263
|
}
|
|
236
264
|
async function addUtmTags(context, itemIndex) {
|
|
@@ -270,7 +298,8 @@ async function removeUtmTags(context, itemIndex) {
|
|
|
270
298
|
}
|
|
271
299
|
async function getContactDevices(context, itemIndex) {
|
|
272
300
|
const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
|
|
273
|
-
|
|
301
|
+
const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'devices', 'GET', `/contacts/${contactId}/devices`);
|
|
302
|
+
return (0, DataHelpers_1.convertNumericStrings)(result);
|
|
274
303
|
}
|
|
275
304
|
async function getContactActivity(context, itemIndex) {
|
|
276
305
|
const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
|
|
@@ -292,25 +321,30 @@ async function getContactActivity(context, itemIndex) {
|
|
|
292
321
|
qs.order = [options.orderBy, options.orderByDir ?? 'asc'];
|
|
293
322
|
if (options.limit)
|
|
294
323
|
qs.limit = options.limit;
|
|
295
|
-
|
|
324
|
+
const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'events', 'GET', `/contacts/${contactId}/activity`, {}, qs);
|
|
325
|
+
return (0, DataHelpers_1.convertNumericStrings)(result);
|
|
296
326
|
}
|
|
297
327
|
async function getContactNotes(context, itemIndex) {
|
|
298
328
|
const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
|
|
299
329
|
const options = (0, ApiHelpers_1.getOptionalParam)(context, 'options', itemIndex, {});
|
|
300
330
|
const qs = options;
|
|
301
|
-
|
|
331
|
+
const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'notes', 'GET', `/contacts/${contactId}/notes`, {}, qs);
|
|
332
|
+
return (0, DataHelpers_1.convertNumericStrings)(result);
|
|
302
333
|
}
|
|
303
334
|
async function getContactCompanies(context, itemIndex) {
|
|
304
335
|
const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
|
|
305
|
-
|
|
336
|
+
const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'companies', 'GET', `/contacts/${contactId}/companies`);
|
|
337
|
+
return (0, DataHelpers_1.convertNumericStrings)(result);
|
|
306
338
|
}
|
|
307
339
|
async function getContactCampaigns(context, itemIndex) {
|
|
308
340
|
const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
|
|
309
|
-
|
|
341
|
+
const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'campaigns', 'GET', `/contacts/${contactId}/campaigns`);
|
|
342
|
+
return (0, DataHelpers_1.convertNumericStrings)(result);
|
|
310
343
|
}
|
|
311
344
|
async function getContactSegments(context, itemIndex) {
|
|
312
345
|
const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
|
|
313
|
-
|
|
346
|
+
const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'segments', 'GET', `/contacts/${contactId}/segments`);
|
|
347
|
+
return (0, DataHelpers_1.convertNumericStrings)(result);
|
|
314
348
|
}
|
|
315
349
|
async function addContactToSegments(context, itemIndex) {
|
|
316
350
|
const contactId = (0, ApiHelpers_1.getRequiredParam)(context, 'contactId', itemIndex);
|
|
@@ -359,7 +393,8 @@ async function getAllContactActivity(context, itemIndex) {
|
|
|
359
393
|
qs.order = [options.orderBy, options.orderByDir ?? 'asc'];
|
|
360
394
|
if (options.limit)
|
|
361
395
|
qs.limit = options.limit;
|
|
362
|
-
|
|
396
|
+
const result = await (0, ApiHelpers_1.makePaginatedRequest)(context, 'events', 'GET', `/contacts/activity`, {}, qs);
|
|
397
|
+
return (0, DataHelpers_1.convertNumericStrings)(result);
|
|
363
398
|
}
|
|
364
399
|
function normalizeTagsInput(tagsInput) {
|
|
365
400
|
// Handle different input formats for tags
|
|
@@ -478,3 +513,11 @@ async function getContactsWithDncFilter(context, qs, options, limit) {
|
|
|
478
513
|
}
|
|
479
514
|
return contacts;
|
|
480
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -58,7 +58,9 @@ function handleApiError(context, error, operation, resource) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
// Check for errors array (current Mautic API format)
|
|
61
|
-
else if (errorData?.errors &&
|
|
61
|
+
else if (errorData?.errors &&
|
|
62
|
+
Array.isArray(errorData.errors) &&
|
|
63
|
+
errorData.errors.length > 0) {
|
|
62
64
|
const validationErrors = [];
|
|
63
65
|
errorData.errors.forEach((err) => {
|
|
64
66
|
if (err.details && typeof err.details === 'object') {
|
|
@@ -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