@trg-admin/n8n-nodes-zoho-desk 0.1.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 +312 -0
- package/dist/credentials/ZohoCRMDeskOAuth2Api.credentials.d.ts +8 -0
- package/dist/credentials/ZohoCRMDeskOAuth2Api.credentials.js +90 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +18 -0
- package/dist/nodes/ZohoDesk/GenericFunctions.d.ts +6 -0
- package/dist/nodes/ZohoDesk/GenericFunctions.js +66 -0
- package/dist/nodes/ZohoDesk/ZohoDesk.node.d.ts +5 -0
- package/dist/nodes/ZohoDesk/ZohoDesk.node.js +516 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# n8n Zoho Desk Node
|
|
2
|
+
|
|
3
|
+
A comprehensive n8n community node for **Zoho Desk** with automatic OAuth2 token refresh handling, supporting Tickets, Contacts, and Comments.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- **Automatic Token Refresh**: Uses OAuth2 with refresh tokens - no manual token management needed
|
|
8
|
+
- **Multi-Region Support**: Automatically detects and uses the correct Zoho data center (US, EU, IN, AU, CN)
|
|
9
|
+
- **Combined CRM & Desk Access**: Single credential works for both Zoho CRM and Zoho Desk nodes
|
|
10
|
+
- **Comprehensive Operations**:
|
|
11
|
+
- **Tickets**: Create, Get, Get Many, Update
|
|
12
|
+
- **Contacts**: Create, Get, Get Many, Search, Update
|
|
13
|
+
- **Comments**: Add, Get Many
|
|
14
|
+
|
|
15
|
+
## 🔐 Authentication Setup
|
|
16
|
+
|
|
17
|
+
This node uses a **custom OAuth2 credential** (`zohoCRMDeskOAuth2Api`) that properly handles refresh tokens and multi-region support.
|
|
18
|
+
|
|
19
|
+
### Step 1: Create OAuth2 App in Zoho API Console
|
|
20
|
+
|
|
21
|
+
1. Go to [Zoho API Console](https://api-console.zoho.com/)
|
|
22
|
+
2. Click **ADD CLIENT** → **Server-based Applications**
|
|
23
|
+
3. Fill in:
|
|
24
|
+
- **Client Name**: `n8n Integration` (or your preferred name)
|
|
25
|
+
- **Homepage URL**: `https://your-n8n-instance.com`
|
|
26
|
+
- **Authorized Redirect URI**: `https://your-n8n-instance.com/rest/oauth2-credential/callback`
|
|
27
|
+
4. Click **CREATE**
|
|
28
|
+
5. Copy the **Client ID** and **Client Secret** (you'll need these in n8n)
|
|
29
|
+
|
|
30
|
+
### Step 2: Configure Scopes
|
|
31
|
+
|
|
32
|
+
When you create the credential in n8n, the default scope is:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
ZohoCRM.modules.ALL Desk.tickets.ALL Desk.contacts.ALL Desk.search.READ
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Available Zoho Desk Scopes:**
|
|
39
|
+
- `Desk.tickets.ALL` - Full access to tickets
|
|
40
|
+
- `Desk.contacts.ALL` - Full access to contacts
|
|
41
|
+
- `Desk.search.READ` - Search across modules
|
|
42
|
+
- `Desk.settings.ALL` - Access to settings and configurations
|
|
43
|
+
- `Desk.tasks.ALL` - Task management
|
|
44
|
+
- `Desk.basic.READ` - Read-only access to basic information
|
|
45
|
+
|
|
46
|
+
You can customize the scope field in the credential to add or remove permissions as needed.
|
|
47
|
+
|
|
48
|
+
### Step 3: Get Your Organization ID
|
|
49
|
+
|
|
50
|
+
1. Log in to [Zoho Desk](https://desk.zoho.com/)
|
|
51
|
+
2. Go to **Setup** (gear icon) → **Developer Space** → **API**
|
|
52
|
+
3. Copy your **Organization ID** (looks like: `12345678`)
|
|
53
|
+
4. You'll enter this as a parameter in each Zoho Desk node
|
|
54
|
+
|
|
55
|
+
### Step 4: Configure Credential in n8n
|
|
56
|
+
|
|
57
|
+
1. In n8n, go to **Credentials** → **New**
|
|
58
|
+
2. Search for **Zoho CRM & Desk OAuth2 API**
|
|
59
|
+
3. Fill in:
|
|
60
|
+
- **Authorization URL**: Select your region (e.g., `https://accounts.zoho.com/oauth/v2/auth` for US)
|
|
61
|
+
- **Access Token URL**: Select matching region (e.g., `https://accounts.zoho.com/oauth/v2/token` for US)
|
|
62
|
+
- **Client ID**: From Step 1
|
|
63
|
+
- **Client Secret**: From Step 1
|
|
64
|
+
- **Scope**: Default works for most cases, customize if needed
|
|
65
|
+
4. Click **Connect my account**
|
|
66
|
+
5. Authorize access in Zoho
|
|
67
|
+
6. ✅ Credential is ready to use!
|
|
68
|
+
|
|
69
|
+
### Regional Mapping
|
|
70
|
+
|
|
71
|
+
Select the **same region** for both Authorization URL and Access Token URL:
|
|
72
|
+
|
|
73
|
+
| Region | Authorization URL | Access Token URL |
|
|
74
|
+
|--------|-------------------|------------------|
|
|
75
|
+
| **US** | `https://accounts.zoho.com/oauth/v2/auth` | `https://accounts.zoho.com/oauth/v2/token` |
|
|
76
|
+
| **EU** | `https://accounts.zoho.com/oauth/v2/auth` | `https://accounts.zoho.eu/oauth/v2/token` |
|
|
77
|
+
| **India** | `https://accounts.zoho.com/oauth/v2/auth` | `https://accounts.zoho.in/oauth/v2/token` |
|
|
78
|
+
| **Australia** | `https://accounts.zoho.com/oauth/v2/auth` | `https://accounts.zoho.com.au/oauth/v2/token` |
|
|
79
|
+
| **China** | `https://accounts.zoho.com.cn/oauth/v2/auth` | `https://accounts.zoho.com.cn/oauth/v2/token` |
|
|
80
|
+
|
|
81
|
+
**Note**: The API domain (e.g., `https://desk.zoho.com`) is automatically detected from the OAuth response - you don't need to configure it manually.
|
|
82
|
+
|
|
83
|
+
## 📦 Installation
|
|
84
|
+
|
|
85
|
+
### Option 1: Install from npm (when published)
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm install n8n-nodes-zoho -g
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Option 2: Local Development
|
|
92
|
+
|
|
93
|
+
1. Clone this repository:
|
|
94
|
+
```bash
|
|
95
|
+
git clone <your-repo-url>
|
|
96
|
+
cd n8n-nodes-zoho
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
2. Install dependencies and build:
|
|
100
|
+
```bash
|
|
101
|
+
npm install
|
|
102
|
+
npm run build
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
3. Link to your n8n instance:
|
|
106
|
+
```bash
|
|
107
|
+
npm link
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
4. In your n8n installation directory:
|
|
111
|
+
```bash
|
|
112
|
+
npm link n8n-nodes-zoho
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
5. Restart n8n
|
|
116
|
+
|
|
117
|
+
## 🚀 Usage Examples
|
|
118
|
+
|
|
119
|
+
### Create a Ticket
|
|
120
|
+
|
|
121
|
+
**Resource**: Ticket
|
|
122
|
+
**Operation**: Create
|
|
123
|
+
**Parameters**:
|
|
124
|
+
- **Organization ID**: `12345678`
|
|
125
|
+
- **Subject**: `Unable to log in`
|
|
126
|
+
- **Contact Email**: `customer@example.com`
|
|
127
|
+
- **Department ID**: `987654321`
|
|
128
|
+
- **Description**: `Customer reports they cannot access their account`
|
|
129
|
+
- **Additional Fields**:
|
|
130
|
+
- Priority: `High`
|
|
131
|
+
- Status: `Open`
|
|
132
|
+
|
|
133
|
+
### Search for Contacts
|
|
134
|
+
|
|
135
|
+
**Resource**: Contact
|
|
136
|
+
**Operation**: Search
|
|
137
|
+
**Parameters**:
|
|
138
|
+
- **Organization ID**: `12345678`
|
|
139
|
+
- **Search Query**: `john@example.com`
|
|
140
|
+
- **Limit**: `10`
|
|
141
|
+
|
|
142
|
+
### Add Comment to Ticket
|
|
143
|
+
|
|
144
|
+
**Resource**: Comment
|
|
145
|
+
**Operation**: Add
|
|
146
|
+
**Parameters**:
|
|
147
|
+
- **Organization ID**: `12345678`
|
|
148
|
+
- **Ticket ID**: `123456`
|
|
149
|
+
- **Content**: `Thank you for contacting support. We're looking into this issue.`
|
|
150
|
+
- **Is Public**: `✓` (visible to customer)
|
|
151
|
+
- **Content Type**: `Plain Text`
|
|
152
|
+
|
|
153
|
+
## 🔧 How Token Refresh Works
|
|
154
|
+
|
|
155
|
+
This node uses n8n's built-in OAuth2 framework with `requestOAuth2`, which provides automatic token refresh:
|
|
156
|
+
|
|
157
|
+
1. **Initial Authorization**: When you connect the credential, Zoho returns:
|
|
158
|
+
- Access token (valid for ~1 hour)
|
|
159
|
+
- Refresh token (long-lived)
|
|
160
|
+
- Regional API domain
|
|
161
|
+
|
|
162
|
+
2. **Automatic Refresh**: When the access token expires:
|
|
163
|
+
- n8n detects the 401 error
|
|
164
|
+
- Automatically calls Zoho's token endpoint with the refresh token
|
|
165
|
+
- Updates the stored access token
|
|
166
|
+
- Retries the original request
|
|
167
|
+
- **You never see the error or interruption**
|
|
168
|
+
|
|
169
|
+
3. **No Manual Intervention**: Unlike custom API key implementations, you never need to manually refresh tokens or update credentials.
|
|
170
|
+
|
|
171
|
+
## 🤔 Why Not Use the Built-in `zohoOAuth2Api`?
|
|
172
|
+
|
|
173
|
+
The built-in Zoho CRM credential (`zohoOAuth2Api`) has **hardcoded scopes** for CRM only:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
scope: 'ZohoCRM.modules.ALL,ZohoCRM.settings.all,ZohoCRM.users.all'
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The scope property is `type: 'hidden'`, so users cannot modify it to add Desk permissions. This is why we created a custom credential that:
|
|
180
|
+
- Extends the same OAuth2 framework
|
|
181
|
+
- Allows editable scopes
|
|
182
|
+
- Defaults to both CRM and Desk permissions
|
|
183
|
+
- Uses the exact same token refresh mechanism
|
|
184
|
+
|
|
185
|
+
## 🐛 Troubleshooting
|
|
186
|
+
|
|
187
|
+
### "INVALID_OAUTH" or "Authentication failed" errors
|
|
188
|
+
|
|
189
|
+
**Cause**: Credentials may be using the wrong region
|
|
190
|
+
**Fix**: Ensure Authorization URL and Access Token URL match your Zoho account region
|
|
191
|
+
|
|
192
|
+
### "Invalid OrgId" error
|
|
193
|
+
|
|
194
|
+
**Cause**: Organization ID is incorrect or missing
|
|
195
|
+
**Fix**: Double-check your Organization ID in Zoho Desk Setup → Developer Space → API
|
|
196
|
+
|
|
197
|
+
### "Insufficient scope" or 401 Unauthorized
|
|
198
|
+
|
|
199
|
+
**Cause**: OAuth app doesn't have required scopes
|
|
200
|
+
**Fix**: Update the **Scope** field in your credential to include the necessary permissions (e.g., add `Desk.tasks.ALL` if you need task access)
|
|
201
|
+
|
|
202
|
+
### Token refresh not happening automatically
|
|
203
|
+
|
|
204
|
+
**Cause**: Using `httpRequest` instead of `requestOAuth2`
|
|
205
|
+
**Fix**: This should not happen with this node - it's built to use `requestOAuth2`. If you see this, please file an issue.
|
|
206
|
+
|
|
207
|
+
### "Department ID not found" when creating tickets
|
|
208
|
+
|
|
209
|
+
**Cause**: Invalid department ID
|
|
210
|
+
**Fix**: Get valid department IDs from Zoho Desk Setup → Channels & Departments, or use the Zoho Desk API to list departments
|
|
211
|
+
|
|
212
|
+
## 📚 API Documentation
|
|
213
|
+
|
|
214
|
+
- [Zoho Desk API Reference](https://desk.zoho.com/DeskAPIDocument)
|
|
215
|
+
- [Zoho OAuth 2.0 Guide](https://www.zoho.com/accounts/protocol/oauth.html)
|
|
216
|
+
- [n8n Community Nodes Documentation](https://docs.n8n.io/integrations/creating-nodes/)
|
|
217
|
+
|
|
218
|
+
## 🛠️ Development
|
|
219
|
+
|
|
220
|
+
### Build
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
npm run build
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Type Check
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
npm run typecheck
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Validate
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
npm test
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## 📝 Publishing to npm
|
|
239
|
+
|
|
240
|
+
1. Update `package.json`:
|
|
241
|
+
- Bump `version`
|
|
242
|
+
- Update `name` if needed (must be unique on npm)
|
|
243
|
+
- Verify `description`, `author`, `keywords`
|
|
244
|
+
|
|
245
|
+
2. Build and test:
|
|
246
|
+
```bash
|
|
247
|
+
npm run typecheck
|
|
248
|
+
npm run build
|
|
249
|
+
npm test
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
3. Publish:
|
|
253
|
+
```bash
|
|
254
|
+
npm login
|
|
255
|
+
npm publish
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## 📄 License
|
|
259
|
+
|
|
260
|
+
MIT
|
|
261
|
+
|
|
262
|
+
## 🤝 Contributing
|
|
263
|
+
|
|
264
|
+
Contributions are welcome! Please feel free to submit issues or pull requests.
|
|
265
|
+
|
|
266
|
+
## 🙏 Credits
|
|
267
|
+
|
|
268
|
+
Built following n8n's community node patterns and inspired by the official Zoho CRM node implementation.
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
npm login
|
|
272
|
+
npm whoami
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
If your org uses 2FA, npm will ask for OTP during publish.
|
|
276
|
+
|
|
277
|
+
### 4) Publish
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
npm publish
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
For updates, bump version first:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
npm version patch
|
|
287
|
+
npm publish
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### 5) Install in n8n
|
|
291
|
+
|
|
292
|
+
On your n8n host:
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
npm install n8n-nodes-zoho-desk
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Then restart n8n. The new node should appear in the editor.
|
|
299
|
+
|
|
300
|
+
### 6) Configure credentials in n8n
|
|
301
|
+
|
|
302
|
+
In n8n UI:
|
|
303
|
+
1. Create credentials using **Zoho OAuth2 API**.
|
|
304
|
+
2. Add `Zoho Desk` node to a workflow.
|
|
305
|
+
3. Set `Data Center` and `Organization ID`.
|
|
306
|
+
4. Run ticket operations.
|
|
307
|
+
|
|
308
|
+
## Troubleshooting npm publish
|
|
309
|
+
|
|
310
|
+
- **`403 Forbidden` when publishing**: package name already exists or account lacks rights; change name or publish under the correct npm org/scope.
|
|
311
|
+
- **`402 Payment Required`**: scoped package defaults to private; use `--access public` or keep `publishConfig.access = public`.
|
|
312
|
+
- **Missing files after install**: confirm `files` in `package.json` includes your built `dist` output and rerun `npm pack`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
|
2
|
+
export declare class ZohoCRMDeskOAuth2Api implements ICredentialType {
|
|
3
|
+
name: string;
|
|
4
|
+
extends: string[];
|
|
5
|
+
displayName: string;
|
|
6
|
+
documentationUrl: string;
|
|
7
|
+
properties: INodeProperties[];
|
|
8
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ZohoCRMDeskOAuth2Api = void 0;
|
|
4
|
+
class ZohoCRMDeskOAuth2Api {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'zohoCRMDeskOAuth2Api';
|
|
7
|
+
this.extends = ['oAuth2Api'];
|
|
8
|
+
this.displayName = 'Zoho CRM & Desk OAuth2 API';
|
|
9
|
+
this.documentationUrl = 'https://www.zoho.com/desk/developer-guide/';
|
|
10
|
+
this.properties = [
|
|
11
|
+
{
|
|
12
|
+
displayName: 'Grant Type',
|
|
13
|
+
name: 'grantType',
|
|
14
|
+
type: 'hidden',
|
|
15
|
+
default: 'authorizationCode',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
displayName: 'Authorization URL',
|
|
19
|
+
name: 'authUrl',
|
|
20
|
+
type: 'options',
|
|
21
|
+
options: [
|
|
22
|
+
{
|
|
23
|
+
name: 'https://accounts.zoho.com/oauth/v2/auth',
|
|
24
|
+
value: 'https://accounts.zoho.com/oauth/v2/auth',
|
|
25
|
+
description: 'For US, EU, AU, and IN domains',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'https://accounts.zoho.com.cn/oauth/v2/auth',
|
|
29
|
+
value: 'https://accounts.zoho.com.cn/oauth/v2/auth',
|
|
30
|
+
description: 'For China domain',
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
default: 'https://accounts.zoho.com/oauth/v2/auth',
|
|
34
|
+
required: true,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
displayName: 'Access Token URL',
|
|
38
|
+
name: 'accessTokenUrl',
|
|
39
|
+
type: 'options',
|
|
40
|
+
options: [
|
|
41
|
+
{
|
|
42
|
+
name: 'US - https://accounts.zoho.com/oauth/v2/token',
|
|
43
|
+
value: 'https://accounts.zoho.com/oauth/v2/token',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'EU - https://accounts.zoho.eu/oauth/v2/token',
|
|
47
|
+
value: 'https://accounts.zoho.eu/oauth/v2/token',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'IN - https://accounts.zoho.in/oauth/v2/token',
|
|
51
|
+
value: 'https://accounts.zoho.in/oauth/v2/token',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'AU - https://accounts.zoho.com.au/oauth/v2/token',
|
|
55
|
+
value: 'https://accounts.zoho.com.au/oauth/v2/token',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'CN - https://accounts.zoho.com.cn/oauth/v2/token',
|
|
59
|
+
value: 'https://accounts.zoho.com.cn/oauth/v2/token',
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
default: 'https://accounts.zoho.com/oauth/v2/token',
|
|
63
|
+
required: true,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
displayName: 'Scope',
|
|
67
|
+
name: 'scope',
|
|
68
|
+
type: 'string',
|
|
69
|
+
default: 'ZohoCRM.modules.ALL Desk.tickets.ALL Desk.contacts.ALL Desk.search.READ',
|
|
70
|
+
required: true,
|
|
71
|
+
description: 'Space-separated OAuth scopes. Default includes both CRM and Desk permissions. Add or remove scopes as needed (e.g., Desk.settings.ALL, Desk.tasks.ALL).',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
displayName: 'Auth URI Query Parameters',
|
|
75
|
+
name: 'authQueryParameters',
|
|
76
|
+
type: 'hidden',
|
|
77
|
+
default: 'access_type=offline',
|
|
78
|
+
description: 'Requests offline access with refresh token for automatic token renewal',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
displayName: 'Authentication',
|
|
82
|
+
name: 'authentication',
|
|
83
|
+
type: 'hidden',
|
|
84
|
+
default: 'body',
|
|
85
|
+
description: 'Zoho requires client credentials in request body',
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.ZohoCRMDeskOAuth2Api = ZohoCRMDeskOAuth2Api;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./nodes/ZohoDesk/ZohoDesk.node"), exports);
|
|
18
|
+
__exportStar(require("./credentials/ZohoCRMDeskOAuth2Api.credentials"), exports);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IExecuteFunctions, ILoadOptionsFunctions, IHttpRequestMethods, IDataObject } from 'n8n-workflow';
|
|
2
|
+
export declare function zohoDeskApiRequest(this: IExecuteFunctions | ILoadOptionsFunctions, method: IHttpRequestMethods, endpoint: string, body?: IDataObject, qs?: IDataObject, itemIndex?: number): Promise<any>;
|
|
3
|
+
/**
|
|
4
|
+
* Make an API request to paginate through all items
|
|
5
|
+
*/
|
|
6
|
+
export declare function zohoDeskApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, method: IHttpRequestMethods, endpoint: string, body?: IDataObject, qs?: IDataObject, itemIndex?: number): Promise<any>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.zohoDeskApiRequest = zohoDeskApiRequest;
|
|
4
|
+
exports.zohoDeskApiRequestAllItems = zohoDeskApiRequestAllItems;
|
|
5
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
6
|
+
async function zohoDeskApiRequest(method, endpoint, body = {}, qs = {}, itemIndex = 0) {
|
|
7
|
+
// Get credentials with OAuth token data containing regional API domain
|
|
8
|
+
const credentials = (await this.getCredentials('zohoCRMDeskOAuth2Api'));
|
|
9
|
+
const { oauthTokenData } = credentials;
|
|
10
|
+
// Get organization ID from node parameters
|
|
11
|
+
const organizationId = this.getNodeParameter('organizationId', itemIndex);
|
|
12
|
+
// Build request options
|
|
13
|
+
const options = {
|
|
14
|
+
method,
|
|
15
|
+
qs,
|
|
16
|
+
body,
|
|
17
|
+
uri: `${oauthTokenData.api_domain}/api/v1${endpoint}`,
|
|
18
|
+
headers: {
|
|
19
|
+
orgId: organizationId,
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
},
|
|
22
|
+
json: true,
|
|
23
|
+
};
|
|
24
|
+
// Clean up empty parameters
|
|
25
|
+
if (!Object.keys(body).length) {
|
|
26
|
+
delete options.body;
|
|
27
|
+
}
|
|
28
|
+
if (!Object.keys(qs).length) {
|
|
29
|
+
delete options.qs;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
// Use requestOAuth2 for automatic token refresh handling
|
|
33
|
+
const responseData = await this.helpers.requestOAuth2?.call(this, 'zohoCRMDeskOAuth2Api', options);
|
|
34
|
+
if (responseData === undefined) {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
return responseData;
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
throw new n8n_workflow_1.NodeApiError(this.getNode(), error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Make an API request to paginate through all items
|
|
45
|
+
*/
|
|
46
|
+
async function zohoDeskApiRequestAllItems(method, endpoint, body = {}, qs = {}, itemIndex = 0) {
|
|
47
|
+
const returnData = [];
|
|
48
|
+
let responseData;
|
|
49
|
+
qs.limit = 100; // Zoho Desk max limit per page
|
|
50
|
+
qs.from = 0;
|
|
51
|
+
do {
|
|
52
|
+
responseData = await zohoDeskApiRequest.call(this, method, endpoint, body, qs, itemIndex);
|
|
53
|
+
if (responseData.data && Array.isArray(responseData.data)) {
|
|
54
|
+
returnData.push(...responseData.data);
|
|
55
|
+
if (responseData.data.length < 100) {
|
|
56
|
+
// Last page
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
qs.from = qs.from + 100;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
} while (true);
|
|
65
|
+
return returnData;
|
|
66
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
+
export declare class ZohoDesk implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ZohoDesk = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
const GenericFunctions_1 = require("./GenericFunctions");
|
|
6
|
+
class ZohoDesk {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.description = {
|
|
9
|
+
displayName: 'Zoho Desk',
|
|
10
|
+
name: 'zohoDesk',
|
|
11
|
+
icon: 'file:zohodesk.svg',
|
|
12
|
+
group: ['transform'],
|
|
13
|
+
version: 1,
|
|
14
|
+
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
|
15
|
+
description: 'Zoho Desk starter node using n8n built-in zohoOAuth2Api credential for automatic refresh-token handling',
|
|
16
|
+
defaults: {
|
|
17
|
+
name: 'Zoho Desk',
|
|
18
|
+
},
|
|
19
|
+
inputs: ['main'],
|
|
20
|
+
outputs: ['main'],
|
|
21
|
+
credentials: [
|
|
22
|
+
{
|
|
23
|
+
name: 'zohoCRMDeskOAuth2Api',
|
|
24
|
+
required: true,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
properties: [
|
|
28
|
+
{
|
|
29
|
+
displayName: 'Organization ID',
|
|
30
|
+
name: 'organizationId',
|
|
31
|
+
type: 'string',
|
|
32
|
+
default: '',
|
|
33
|
+
required: true,
|
|
34
|
+
description: 'Your Zoho Desk Organization ID. Find this in Setup > Developer Space > API.',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
displayName: 'Resource',
|
|
38
|
+
name: 'resource',
|
|
39
|
+
type: 'options',
|
|
40
|
+
noDataExpression: true,
|
|
41
|
+
default: 'ticket',
|
|
42
|
+
options: [
|
|
43
|
+
{ name: 'Ticket', value: 'ticket' },
|
|
44
|
+
{ name: 'Contact', value: 'contact' },
|
|
45
|
+
{ name: 'Comment', value: 'comment' },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
// Ticket Operations
|
|
49
|
+
{
|
|
50
|
+
displayName: 'Operation',
|
|
51
|
+
name: 'operation',
|
|
52
|
+
type: 'options',
|
|
53
|
+
noDataExpression: true,
|
|
54
|
+
displayOptions: { show: { resource: ['ticket'] } },
|
|
55
|
+
default: 'get',
|
|
56
|
+
options: [
|
|
57
|
+
{ name: 'Create', value: 'create', action: 'Create a ticket' },
|
|
58
|
+
{ name: 'Get', value: 'get', action: 'Get a ticket' },
|
|
59
|
+
{ name: 'Get Many', value: 'getAll', action: 'Get many tickets' },
|
|
60
|
+
{ name: 'Update', value: 'update', action: 'Update a ticket' },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
// Contact Operations
|
|
64
|
+
{
|
|
65
|
+
displayName: 'Operation',
|
|
66
|
+
name: 'operation',
|
|
67
|
+
type: 'options',
|
|
68
|
+
noDataExpression: true,
|
|
69
|
+
displayOptions: { show: { resource: ['contact'] } },
|
|
70
|
+
default: 'get',
|
|
71
|
+
options: [
|
|
72
|
+
{ name: 'Create', value: 'create', action: 'Create a contact' },
|
|
73
|
+
{ name: 'Get', value: 'get', action: 'Get a contact' },
|
|
74
|
+
{ name: 'Get Many', value: 'getAll', action: 'Get many contacts' },
|
|
75
|
+
{ name: 'Search', value: 'search', action: 'Search contacts' },
|
|
76
|
+
{ name: 'Update', value: 'update', action: 'Update a contact' },
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
// Comment Operations
|
|
80
|
+
{
|
|
81
|
+
displayName: 'Operation',
|
|
82
|
+
name: 'operation',
|
|
83
|
+
type: 'options',
|
|
84
|
+
noDataExpression: true,
|
|
85
|
+
displayOptions: { show: { resource: ['comment'] } },
|
|
86
|
+
default: 'add',
|
|
87
|
+
options: [
|
|
88
|
+
{ name: 'Add', value: 'add', action: 'Add a comment to ticket' },
|
|
89
|
+
{ name: 'Get Many', value: 'getAll', action: 'Get all comments from ticket' },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
// ================== Ticket Resource Parameters ==================
|
|
93
|
+
{
|
|
94
|
+
displayName: 'Ticket ID',
|
|
95
|
+
name: 'ticketId',
|
|
96
|
+
type: 'string',
|
|
97
|
+
default: '',
|
|
98
|
+
required: true,
|
|
99
|
+
displayOptions: { show: { resource: ['ticket'], operation: ['get', 'update'] } },
|
|
100
|
+
description: 'The unique ID of the ticket',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
displayName: 'Subject',
|
|
104
|
+
name: 'subject',
|
|
105
|
+
type: 'string',
|
|
106
|
+
default: '',
|
|
107
|
+
required: true,
|
|
108
|
+
displayOptions: { show: { resource: ['ticket'], operation: ['create'] } },
|
|
109
|
+
description: 'Subject/title of the ticket',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
displayName: 'Contact Email',
|
|
113
|
+
name: 'email',
|
|
114
|
+
type: 'string',
|
|
115
|
+
default: '',
|
|
116
|
+
required: true,
|
|
117
|
+
displayOptions: { show: { resource: ['ticket'], operation: ['create'] } },
|
|
118
|
+
description: 'Email address of the contact creating this ticket',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
displayName: 'Department ID',
|
|
122
|
+
name: 'departmentId',
|
|
123
|
+
type: 'string',
|
|
124
|
+
default: '',
|
|
125
|
+
required: true,
|
|
126
|
+
displayOptions: { show: { resource: ['ticket'], operation: ['create'] } },
|
|
127
|
+
description: 'ID of the department to assign the ticket',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
displayName: 'Description',
|
|
131
|
+
name: 'description',
|
|
132
|
+
type: 'string',
|
|
133
|
+
typeOptions: { alwaysOpenEditWindow: true },
|
|
134
|
+
default: '',
|
|
135
|
+
displayOptions: { show: { resource: ['ticket'], operation: ['create', 'update'] } },
|
|
136
|
+
description: 'Description/content of the ticket',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
displayName: 'Additional Fields',
|
|
140
|
+
name: 'additionalFields',
|
|
141
|
+
type: 'collection',
|
|
142
|
+
placeholder: 'Add Field',
|
|
143
|
+
default: {},
|
|
144
|
+
displayOptions: { show: { resource: ['ticket'], operation: ['create', 'update'] } },
|
|
145
|
+
options: [
|
|
146
|
+
{
|
|
147
|
+
displayName: 'Assignee ID',
|
|
148
|
+
name: 'assigneeId',
|
|
149
|
+
type: 'string',
|
|
150
|
+
default: '',
|
|
151
|
+
description: 'Agent ID to assign the ticket to',
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
displayName: 'Contact ID',
|
|
155
|
+
name: 'contactId',
|
|
156
|
+
type: 'string',
|
|
157
|
+
default: '',
|
|
158
|
+
description: 'ID of existing contact (alternative to email)',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
displayName: 'Due Date',
|
|
162
|
+
name: 'dueDate',
|
|
163
|
+
type: 'dateTime',
|
|
164
|
+
default: '',
|
|
165
|
+
description: 'Due date for the ticket (ISO 8601 format)',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
displayName: 'Priority',
|
|
169
|
+
name: 'priority',
|
|
170
|
+
type: 'options',
|
|
171
|
+
options: [
|
|
172
|
+
{ name: 'High', value: 'High' },
|
|
173
|
+
{ name: 'Medium', value: 'Medium' },
|
|
174
|
+
{ name: 'Low', value: 'Low' },
|
|
175
|
+
],
|
|
176
|
+
default: 'Medium',
|
|
177
|
+
description: 'Priority level of the ticket',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
displayName: 'Status',
|
|
181
|
+
name: 'status',
|
|
182
|
+
type: 'string',
|
|
183
|
+
default: '',
|
|
184
|
+
description: 'Status of the ticket (e.g., Open, In Progress, Closed)',
|
|
185
|
+
},
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
displayName: 'Limit',
|
|
190
|
+
name: 'limit',
|
|
191
|
+
type: 'number',
|
|
192
|
+
typeOptions: { minValue: 1, maxValue: 100 },
|
|
193
|
+
default: 20,
|
|
194
|
+
displayOptions: { show: { resource: ['ticket'], operation: ['getAll'] } },
|
|
195
|
+
description: 'Max number of results to return',
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
displayName: 'Return All',
|
|
199
|
+
name: 'returnAll',
|
|
200
|
+
type: 'boolean',
|
|
201
|
+
default: false,
|
|
202
|
+
displayOptions: { show: { resource: ['ticket'], operation: ['getAll'] } },
|
|
203
|
+
description: 'Whether to return all results or limit to specified number',
|
|
204
|
+
},
|
|
205
|
+
// ================== Contact Resource Parameters ==================
|
|
206
|
+
{
|
|
207
|
+
displayName: 'Contact ID',
|
|
208
|
+
name: 'contactId',
|
|
209
|
+
type: 'string',
|
|
210
|
+
default: '',
|
|
211
|
+
required: true,
|
|
212
|
+
displayOptions: { show: { resource: ['contact'], operation: ['get', 'update'] } },
|
|
213
|
+
description: 'The unique ID of the contact',
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
displayName: 'Last Name',
|
|
217
|
+
name: 'lastName',
|
|
218
|
+
type: 'string',
|
|
219
|
+
default: '',
|
|
220
|
+
required: true,
|
|
221
|
+
displayOptions: { show: { resource: ['contact'], operation: ['create'] } },
|
|
222
|
+
description: 'Last name of the contact',
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
displayName: 'Email',
|
|
226
|
+
name: 'email',
|
|
227
|
+
type: 'string',
|
|
228
|
+
default: '',
|
|
229
|
+
required: true,
|
|
230
|
+
displayOptions: { show: { resource: ['contact'], operation: ['create'] } },
|
|
231
|
+
description: 'Email address of the contact',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
displayName: 'Additional Fields',
|
|
235
|
+
name: 'additionalFields',
|
|
236
|
+
type: 'collection',
|
|
237
|
+
placeholder: 'Add Field',
|
|
238
|
+
default: {},
|
|
239
|
+
displayOptions: { show: { resource: ['contact'], operation: ['create', 'update'] } },
|
|
240
|
+
options: [
|
|
241
|
+
{
|
|
242
|
+
displayName: 'First Name',
|
|
243
|
+
name: 'firstName',
|
|
244
|
+
type: 'string',
|
|
245
|
+
default: '',
|
|
246
|
+
description: 'First name of the contact',
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
displayName: 'Phone',
|
|
250
|
+
name: 'phone',
|
|
251
|
+
type: 'string',
|
|
252
|
+
default: '',
|
|
253
|
+
description: 'Phone number of the contact',
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
displayName: 'Mobile',
|
|
257
|
+
name: 'mobile',
|
|
258
|
+
type: 'string',
|
|
259
|
+
default: '',
|
|
260
|
+
description: 'Mobile number of the contact',
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
displayName: 'Account ID',
|
|
264
|
+
name: 'accountId',
|
|
265
|
+
type: 'string',
|
|
266
|
+
default: '',
|
|
267
|
+
description: 'ID of the account to associate with',
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
displayName: 'Description',
|
|
271
|
+
name: 'description',
|
|
272
|
+
type: 'string',
|
|
273
|
+
default: '',
|
|
274
|
+
description: 'Additional description about the contact',
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
displayName: 'Search Query',
|
|
280
|
+
name: 'searchQuery',
|
|
281
|
+
type: 'string',
|
|
282
|
+
default: '',
|
|
283
|
+
required: true,
|
|
284
|
+
displayOptions: { show: { resource: ['contact'], operation: ['search'] } },
|
|
285
|
+
description: 'Search query string (e.g., email, name, phone)',
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
displayName: 'Limit',
|
|
289
|
+
name: 'limit',
|
|
290
|
+
type: 'number',
|
|
291
|
+
typeOptions: { minValue: 1, maxValue: 100 },
|
|
292
|
+
default: 20,
|
|
293
|
+
displayOptions: { show: { resource: ['contact'], operation: ['getAll', 'search'] } },
|
|
294
|
+
description: 'Max number of results to return',
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
displayName: 'Return All',
|
|
298
|
+
name: 'returnAll',
|
|
299
|
+
type: 'boolean',
|
|
300
|
+
default: false,
|
|
301
|
+
displayOptions: { show: { resource: ['contact'], operation: ['getAll'] } },
|
|
302
|
+
description: 'Whether to return all results or limit to specified number',
|
|
303
|
+
},
|
|
304
|
+
// ================== Comment Resource Parameters ==================
|
|
305
|
+
{
|
|
306
|
+
displayName: 'Ticket ID',
|
|
307
|
+
name: 'ticketId',
|
|
308
|
+
type: 'string',
|
|
309
|
+
default: '',
|
|
310
|
+
required: true,
|
|
311
|
+
displayOptions: { show: { resource: ['comment'], operation: ['add', 'getAll'] } },
|
|
312
|
+
description: 'ID of the ticket to add comment to or get comments from',
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
displayName: 'Content',
|
|
316
|
+
name: 'content',
|
|
317
|
+
type: 'string',
|
|
318
|
+
typeOptions: { alwaysOpenEditWindow: true },
|
|
319
|
+
default: '',
|
|
320
|
+
required: true,
|
|
321
|
+
displayOptions: { show: { resource: ['comment'], operation: ['add'] } },
|
|
322
|
+
description: 'Content of the comment',
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
displayName: 'Is Public',
|
|
326
|
+
name: 'isPublic',
|
|
327
|
+
type: 'boolean',
|
|
328
|
+
default: true,
|
|
329
|
+
displayOptions: { show: { resource: ['comment'], operation: ['add'] } },
|
|
330
|
+
description: 'Whether the comment is public (visible to customer) or private (internal note)',
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
displayName: 'Content Type',
|
|
334
|
+
name: 'contentType',
|
|
335
|
+
type: 'options',
|
|
336
|
+
options: [
|
|
337
|
+
{ name: 'Plain Text', value: 'plainText' },
|
|
338
|
+
{ name: 'HTML', value: 'html' },
|
|
339
|
+
],
|
|
340
|
+
default: 'plainText',
|
|
341
|
+
displayOptions: { show: { resource: ['comment'], operation: ['add'] } },
|
|
342
|
+
description: 'Format of the comment content',
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
displayName: 'Limit',
|
|
346
|
+
name: 'limit',
|
|
347
|
+
type: 'number',
|
|
348
|
+
typeOptions: { minValue: 1, maxValue: 100 },
|
|
349
|
+
default: 20,
|
|
350
|
+
displayOptions: { show: { resource: ['comment'], operation: ['getAll'] } },
|
|
351
|
+
description: 'Max number of results to return',
|
|
352
|
+
},
|
|
353
|
+
],
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
async execute() {
|
|
357
|
+
const items = this.getInputData();
|
|
358
|
+
const returnData = [];
|
|
359
|
+
for (let i = 0; i < items.length; i++) {
|
|
360
|
+
const resource = this.getNodeParameter('resource', i);
|
|
361
|
+
const operation = this.getNodeParameter('operation', i);
|
|
362
|
+
try {
|
|
363
|
+
// ==================== TICKET OPERATIONS ====================
|
|
364
|
+
if (resource === 'ticket') {
|
|
365
|
+
if (operation === 'get') {
|
|
366
|
+
const ticketId = this.getNodeParameter('ticketId', i);
|
|
367
|
+
const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'GET', `/tickets/${ticketId}`, {}, {}, i);
|
|
368
|
+
returnData.push({ json: responseData });
|
|
369
|
+
}
|
|
370
|
+
if (operation === 'getAll') {
|
|
371
|
+
const returnAll = this.getNodeParameter('returnAll', i, false);
|
|
372
|
+
if (returnAll) {
|
|
373
|
+
const responseData = await GenericFunctions_1.zohoDeskApiRequestAllItems.call(this, 'GET', '/tickets', {}, {}, i);
|
|
374
|
+
for (const row of responseData) {
|
|
375
|
+
returnData.push({ json: row });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
const limit = this.getNodeParameter('limit', i);
|
|
380
|
+
const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'GET', '/tickets', {}, { limit }, i);
|
|
381
|
+
const data = Array.isArray(responseData.data)
|
|
382
|
+
? responseData.data
|
|
383
|
+
: [responseData];
|
|
384
|
+
for (const row of data) {
|
|
385
|
+
returnData.push({ json: row });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (operation === 'create') {
|
|
390
|
+
const subject = this.getNodeParameter('subject', i);
|
|
391
|
+
const email = this.getNodeParameter('email', i);
|
|
392
|
+
const departmentId = this.getNodeParameter('departmentId', i);
|
|
393
|
+
const description = this.getNodeParameter('description', i, '');
|
|
394
|
+
const additionalFields = this.getNodeParameter('additionalFields', i, {});
|
|
395
|
+
const body = {
|
|
396
|
+
subject,
|
|
397
|
+
email,
|
|
398
|
+
departmentId,
|
|
399
|
+
...additionalFields,
|
|
400
|
+
};
|
|
401
|
+
if (description) {
|
|
402
|
+
body.description = description;
|
|
403
|
+
}
|
|
404
|
+
const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'POST', '/tickets', body, {}, i);
|
|
405
|
+
returnData.push({ json: responseData });
|
|
406
|
+
}
|
|
407
|
+
if (operation === 'update') {
|
|
408
|
+
const ticketId = this.getNodeParameter('ticketId', i);
|
|
409
|
+
const description = this.getNodeParameter('description', i, '');
|
|
410
|
+
const additionalFields = this.getNodeParameter('additionalFields', i, {});
|
|
411
|
+
const body = {
|
|
412
|
+
...additionalFields,
|
|
413
|
+
};
|
|
414
|
+
if (description) {
|
|
415
|
+
body.description = description;
|
|
416
|
+
}
|
|
417
|
+
const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'PATCH', `/tickets/${ticketId}`, body, {}, i);
|
|
418
|
+
returnData.push({ json: responseData });
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
// ==================== CONTACT OPERATIONS ====================
|
|
422
|
+
if (resource === 'contact') {
|
|
423
|
+
if (operation === 'get') {
|
|
424
|
+
const contactId = this.getNodeParameter('contactId', i);
|
|
425
|
+
const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'GET', `/contacts/${contactId}`, {}, {}, i);
|
|
426
|
+
returnData.push({ json: responseData });
|
|
427
|
+
}
|
|
428
|
+
if (operation === 'getAll') {
|
|
429
|
+
const returnAll = this.getNodeParameter('returnAll', i, false);
|
|
430
|
+
if (returnAll) {
|
|
431
|
+
const responseData = await GenericFunctions_1.zohoDeskApiRequestAllItems.call(this, 'GET', '/contacts', {}, {}, i);
|
|
432
|
+
for (const row of responseData) {
|
|
433
|
+
returnData.push({ json: row });
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
const limit = this.getNodeParameter('limit', i);
|
|
438
|
+
const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'GET', '/contacts', {}, { limit }, i);
|
|
439
|
+
const data = Array.isArray(responseData.data)
|
|
440
|
+
? responseData.data
|
|
441
|
+
: [responseData];
|
|
442
|
+
for (const row of data) {
|
|
443
|
+
returnData.push({ json: row });
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (operation === 'create') {
|
|
448
|
+
const lastName = this.getNodeParameter('lastName', i);
|
|
449
|
+
const email = this.getNodeParameter('email', i);
|
|
450
|
+
const additionalFields = this.getNodeParameter('additionalFields', i, {});
|
|
451
|
+
const body = {
|
|
452
|
+
lastName,
|
|
453
|
+
email,
|
|
454
|
+
...additionalFields,
|
|
455
|
+
};
|
|
456
|
+
const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'POST', '/contacts', body, {}, i);
|
|
457
|
+
returnData.push({ json: responseData });
|
|
458
|
+
}
|
|
459
|
+
if (operation === 'update') {
|
|
460
|
+
const contactId = this.getNodeParameter('contactId', i);
|
|
461
|
+
const additionalFields = this.getNodeParameter('additionalFields', i, {});
|
|
462
|
+
const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'PATCH', `/contacts/${contactId}`, additionalFields, {}, i);
|
|
463
|
+
returnData.push({ json: responseData });
|
|
464
|
+
}
|
|
465
|
+
if (operation === 'search') {
|
|
466
|
+
const searchQuery = this.getNodeParameter('searchQuery', i);
|
|
467
|
+
const limit = this.getNodeParameter('limit', i);
|
|
468
|
+
const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'GET', '/contacts/search', {}, { limit, searchStr: searchQuery }, i);
|
|
469
|
+
const data = Array.isArray(responseData.data)
|
|
470
|
+
? responseData.data
|
|
471
|
+
: [responseData];
|
|
472
|
+
for (const row of data) {
|
|
473
|
+
returnData.push({ json: row });
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// ==================== COMMENT OPERATIONS ====================
|
|
478
|
+
if (resource === 'comment') {
|
|
479
|
+
if (operation === 'add') {
|
|
480
|
+
const ticketId = this.getNodeParameter('ticketId', i);
|
|
481
|
+
const content = this.getNodeParameter('content', i);
|
|
482
|
+
const isPublic = this.getNodeParameter('isPublic', i);
|
|
483
|
+
const contentType = this.getNodeParameter('contentType', i);
|
|
484
|
+
const body = {
|
|
485
|
+
content,
|
|
486
|
+
isPublic,
|
|
487
|
+
contentType,
|
|
488
|
+
};
|
|
489
|
+
const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'POST', `/tickets/${ticketId}/comments`, body, {}, i);
|
|
490
|
+
returnData.push({ json: responseData });
|
|
491
|
+
}
|
|
492
|
+
if (operation === 'getAll') {
|
|
493
|
+
const ticketId = this.getNodeParameter('ticketId', i);
|
|
494
|
+
const limit = this.getNodeParameter('limit', i);
|
|
495
|
+
const responseData = await GenericFunctions_1.zohoDeskApiRequest.call(this, 'GET', `/tickets/${ticketId}/comments`, {}, { limit }, i);
|
|
496
|
+
const data = Array.isArray(responseData.data)
|
|
497
|
+
? responseData.data
|
|
498
|
+
: [responseData];
|
|
499
|
+
for (const row of data) {
|
|
500
|
+
returnData.push({ json: row });
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
catch (error) {
|
|
506
|
+
if (this.continueOnFail()) {
|
|
507
|
+
returnData.push({ json: { error: error.message }, pairedItem: i });
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), error, { itemIndex: i });
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return [returnData];
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
exports.ZohoDesk = ZohoDesk;
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@trg-admin/n8n-nodes-zoho-desk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Community n8n node starter for Zoho Desk using built-in Zoho OAuth2 auth behavior",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"lint": "echo 'No linter configured'",
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"test": "node scripts.validate.js",
|
|
13
|
+
"prepublishOnly": "npm run typecheck && npm run build"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"typescript": "^5.9.3"
|
|
20
|
+
},
|
|
21
|
+
"n8n": {
|
|
22
|
+
"n8nNodesApiVersion": 1,
|
|
23
|
+
"nodes": [
|
|
24
|
+
"dist/nodes/ZohoDesk/ZohoDesk.node.js"
|
|
25
|
+
],
|
|
26
|
+
"credentials": [
|
|
27
|
+
"dist/credentials/ZohoCRMDeskOAuth2Api.credentials.js"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"n8n-workflow": "*"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
}
|
|
36
|
+
}
|