@friggframework/api-module-zoho-crm 1.0.1-canary.332f986.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/.env.example +4 -0
- package/LICENSE.md +16 -0
- package/README.md +269 -0
- package/api.js +169 -0
- package/defaultConfig.json +13 -0
- package/definition.js +51 -0
- package/images/image-1.jpg +0 -0
- package/images/image-10.jpg +0 -0
- package/images/image-11.jpg +0 -0
- package/images/image-12.jpg +0 -0
- package/images/image-2.jpg +0 -0
- package/images/image-3.jpg +0 -0
- package/images/image-5.jpg +0 -0
- package/images/image-6.jpg +0 -0
- package/images/image-7.jpg +0 -0
- package/images/image-9.jpg +0 -0
- package/images/image.jpg +0 -0
- package/index.js +9 -0
- package/jest-setup.js +3 -0
- package/jest-teardown.js +2 -0
- package/jest.config.js +22 -0
- package/package.json +29 -0
- package/tests/api.test.js +195 -0
package/.env.example
ADDED
package/LICENSE.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Left Hook Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
|
6
|
+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
|
7
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
8
|
+
persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or
|
|
11
|
+
substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
14
|
+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
15
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
16
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
# Zoho CRM
|
|
2
|
+
|
|
3
|
+
This is the API Module for Zoho CRM that allows the [Frigg](https://friggframework.org) code to talk to the Zoho CRM API.
|
|
4
|
+
|
|
5
|
+
[Link to the Zoho CRM REST API Postman collection.](https://www.postman.com/zohocrmdevelopers/workspace/zoho-crm-developers/collection/8522016-0a15778a-ccb1-4676-98b7-4cf1fe7fc940?ctx=documentation)
|
|
6
|
+
|
|
7
|
+
Read more on the [Frigg documentation site](https://docs.friggframework.org/api-modules/list/zoho-crm
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## Setup a Zoho CRM developer account
|
|
11
|
+
|
|
12
|
+
In order to test this api module, you will need to populate your local `.env` file with a set of credentials (`ZOHO_CRM_CLIENT_ID` and `ZOHO_CRM_CLIENT_SECRET`).
|
|
13
|
+
|
|
14
|
+
To get those, you will need to sign up for a Zoho CRM developer account and [create a new API client](https://www.zoho.com/crm/developer/docs/api/v6/register-client.html), which is explained below.
|
|
15
|
+
|
|
16
|
+
If you've already done this, skip to the next section.
|
|
17
|
+
|
|
18
|
+
1. Go to https://www.zoho.com/crm/developer/ and click `Sign Up For Free`
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
2. Once you're in, set up your example company. Check the `Load Sample Data` box.
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
3. Go to your account's API Console at https://api-console.zoho.com/.
|
|
27
|
+
|
|
28
|
+
* You may be asked to verify your email address before accessing your API Console
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
* You'll receive an email with a verification link
|
|
32
|
+

|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
4. From your API Console, you are able to create client for your account. For our purposes, select `Server-based Applications`.
|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
5. When filling in the details for your new client, make sure to use `http://localhost:3000/redirect/zoho-crm` in the `Authorized Redirect URIs` field.
|
|
40
|
+

|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
6. After creating the client, you will be sent to the `Client Secret` tab where you can grab your Client ID and Client Secret.
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
## Set up your local `.env` file
|
|
48
|
+
|
|
49
|
+
1. Make a copy of `.env.example` and name it `.env`.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
2. Grab your Client ID and Client Secret from the Zoho CRM API Console and paste them into your local `.env` file. It should look something like this:
|
|
53
|
+
```shell
|
|
54
|
+
ZOHO_CRM_CLIENT_ID=your_client_id
|
|
55
|
+
ZOHO_CRM_CLIENT_SECRET=your_client_secret
|
|
56
|
+
ZOHO_CRM_SCOPE=ZohoCRM.users.ALL,ZohoCRM.org.ALL,ZohoCRM.settings.roles.ALL,ZohoCRM.settings.profiles.ALL
|
|
57
|
+
REDIRECT_URI=http://localhost:3000/redirect
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
## Using the api module from the terminal
|
|
62
|
+
|
|
63
|
+
With your `.env` in place, you can now open a terminal to play around with the available APIs.
|
|
64
|
+
|
|
65
|
+
1. Start a `node` terminal in `packages/zoho-crm`
|
|
66
|
+
|
|
67
|
+
2. Paste the following code into the terminal:
|
|
68
|
+
```js
|
|
69
|
+
require('dotenv').config();
|
|
70
|
+
const {Authenticator} = require('@friggframework/test');
|
|
71
|
+
const {Api} = require('./api.js');
|
|
72
|
+
|
|
73
|
+
api = new Api({
|
|
74
|
+
client_id: process.env.ZOHO_CRM_CLIENT_ID,
|
|
75
|
+
client_secret: process.env.ZOHO_CRM_CLIENT_SECRET,
|
|
76
|
+
scope: process.env.ZOHO_CRM_SCOPE,
|
|
77
|
+
redirect_uri: `${process.env.REDIRECT_URI}/zoho-crm`,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const url = await api.getAuthUri();
|
|
81
|
+
const response = await Authenticator.oauth2(url);
|
|
82
|
+
const baseArr = response.base.split('/');
|
|
83
|
+
response.entityType = baseArr[baseArr.length - 1];
|
|
84
|
+
delete response.base;
|
|
85
|
+
|
|
86
|
+
await api.getTokenFromCode(response.data.code);
|
|
87
|
+
|
|
88
|
+
console.log('api ready!');
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
3. Your browser will open a tab and send you to Zoho CRM to authorize the client. You may need to log in first.
|
|
93
|
+

|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
4. After authorizing, the tokens are returned to your terminal where they are used to create an authenticated instance of the Zoho CRM API module in the `api` variable. From here you can call any of the existing API resources defined in the module.
|
|
97
|
+
* List existing Users:
|
|
98
|
+
```js
|
|
99
|
+
> await api.listUsers()
|
|
100
|
+
{
|
|
101
|
+
users: [
|
|
102
|
+
{
|
|
103
|
+
country: 'HN',
|
|
104
|
+
name_format__s: 'Salutation,First Name,Last Name',
|
|
105
|
+
language: 'en_US',
|
|
106
|
+
microsoft: false,
|
|
107
|
+
'$shift_effective_from': null,
|
|
108
|
+
id: '6238474000000461001',
|
|
109
|
+
state: 'Francisco Morazan',
|
|
110
|
+
fax: null,
|
|
111
|
+
country_locale: 'en_US',
|
|
112
|
+
sandboxDeveloper: false,
|
|
113
|
+
zip: null,
|
|
114
|
+
decimal_separator: 'Period',
|
|
115
|
+
created_time: '2024-04-20T10:35:36-06:00',
|
|
116
|
+
time_format: 'hh:mm a',
|
|
117
|
+
offset: -21600000,
|
|
118
|
+
profile: [Object],
|
|
119
|
+
created_by: [Object],
|
|
120
|
+
zuid: '851289894',
|
|
121
|
+
full_name: 'Armando Alvarado',
|
|
122
|
+
phone: '32415425',
|
|
123
|
+
dob: null,
|
|
124
|
+
sort_order_preference__s: 'First Name,Last Name',
|
|
125
|
+
status: 'active',
|
|
126
|
+
role: [Object],
|
|
127
|
+
customize_info: [Object],
|
|
128
|
+
city: null,
|
|
129
|
+
signature: null,
|
|
130
|
+
locale: 'en_US',
|
|
131
|
+
personal_account: false,
|
|
132
|
+
Source__s: null,
|
|
133
|
+
Isonline: false,
|
|
134
|
+
default_tab_group: '0',
|
|
135
|
+
Modified_By: [Object],
|
|
136
|
+
street: null,
|
|
137
|
+
'$current_shift': null,
|
|
138
|
+
alias: null,
|
|
139
|
+
theme: [Object],
|
|
140
|
+
first_name: 'Armando Alvarado',
|
|
141
|
+
email: 'aaj2006@hotmail.com',
|
|
142
|
+
status_reason__s: null,
|
|
143
|
+
website: null,
|
|
144
|
+
Modified_Time: '2024-04-20T10:37:55-06:00',
|
|
145
|
+
'$next_shift': null,
|
|
146
|
+
mobile: null,
|
|
147
|
+
last_name: null,
|
|
148
|
+
time_zone: 'America/Tegucigalpa',
|
|
149
|
+
number_separator: 'Comma',
|
|
150
|
+
confirm: true,
|
|
151
|
+
date_format: 'MM-dd-yyyy',
|
|
152
|
+
category: 'regular_user'
|
|
153
|
+
}
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
* List existing Roles:
|
|
159
|
+
```js
|
|
160
|
+
> await api.listRoles()
|
|
161
|
+
{
|
|
162
|
+
roles: [
|
|
163
|
+
{
|
|
164
|
+
display_label: 'CEO',
|
|
165
|
+
created_by__s: null,
|
|
166
|
+
modified_by__s: null,
|
|
167
|
+
forecast_manager: null,
|
|
168
|
+
share_with_peers: true,
|
|
169
|
+
modified_time__s: null,
|
|
170
|
+
name: 'CEO',
|
|
171
|
+
description: 'Users with this role have access to the data owned by all other users.',
|
|
172
|
+
reporting_to: null,
|
|
173
|
+
id: '6238474000000026005',
|
|
174
|
+
created_time__s: null
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
display_label: 'Manager',
|
|
178
|
+
created_by__s: null,
|
|
179
|
+
modified_by__s: null,
|
|
180
|
+
forecast_manager: null,
|
|
181
|
+
share_with_peers: false,
|
|
182
|
+
modified_time__s: null,
|
|
183
|
+
name: 'Manager',
|
|
184
|
+
description: 'Users belonging to this role cannot see data for admin users.',
|
|
185
|
+
reporting_to: [Object],
|
|
186
|
+
id: '6238474000000026008',
|
|
187
|
+
created_time__s: null
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Using this API module in a Frigg instance
|
|
194
|
+
1. Run `npm install @friggframework/api-module-zoho-crm`
|
|
195
|
+
|
|
196
|
+
2. Populate your `.env` file with `ZOHO_CRM_CLIENT_ID`, `ZOHO_CRM_CLIENT_SECRET`, and `ZOHO_CRM_SCOPE`.
|
|
197
|
+
|
|
198
|
+
3. Create a subclass of `IntegrationBase` from `@friggframework/core` in `src/integrations` and plug in the Zoho CRM API module. Example:
|
|
199
|
+
```js
|
|
200
|
+
const { IntegrationBase, Options } = require('@friggframework/core');
|
|
201
|
+
const { Definition: ZohoCRMModule } = require('@friggframework/api-module-zoho-crm');
|
|
202
|
+
const _ = require('lodash');
|
|
203
|
+
|
|
204
|
+
class ZohoCRMIntegration extends IntegrationBase {
|
|
205
|
+
static Config = {
|
|
206
|
+
name: 'zoho-crm',
|
|
207
|
+
version: '1.0.0',
|
|
208
|
+
supportedVersions: ['1.0.0'],
|
|
209
|
+
events: ['GET_SOMETHING'],
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
static Options =
|
|
213
|
+
new Options({
|
|
214
|
+
module: ZohoCRMModule,
|
|
215
|
+
integrations: [ZohoCRMModule],
|
|
216
|
+
display: {
|
|
217
|
+
name: 'Zoho CRM',
|
|
218
|
+
description: 'CRM Stuff',
|
|
219
|
+
category: 'CRM',
|
|
220
|
+
detailsUrl: 'https://www.zoho.com/crm/',
|
|
221
|
+
icon: 'https://static.zohocdn.com/crm/images/favicon_cbfca4856ba4bfb37be615b152f95251_.ico',
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
static modules = {
|
|
226
|
+
'zoho-crm': ZohoCRMModule
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* HANDLE EVENTS
|
|
231
|
+
*/
|
|
232
|
+
async receiveNotification(notifier, event, object = null) {
|
|
233
|
+
if (event === 'GET_SOMETHING') {
|
|
234
|
+
return this.target.api.getProjects();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* ALL CUSTOM/OPTIONAL METHODS FOR AN INTEGRATION
|
|
240
|
+
*/
|
|
241
|
+
async getSampleData() {
|
|
242
|
+
const response = await this.target.api.listRoles();
|
|
243
|
+
const data = response.roles.map(role => ({
|
|
244
|
+
'Id': role.id,
|
|
245
|
+
'Name': role.name,
|
|
246
|
+
'Description': role.description,
|
|
247
|
+
}));
|
|
248
|
+
return {data};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = ZohoCRMIntegration;
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
4. Plug your subclass into your app definition's `integrations` array.
|
|
256
|
+

|
|
257
|
+
|
|
258
|
+
5. Zoho CRM should now appear in your list of available integrations
|
|
259
|
+

|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
## Running the tests
|
|
263
|
+
|
|
264
|
+
The API tests verify that the usual CRUD operations against the API resources work as expected. Because of that, you will need to have a valid set of credentials in your local `.env` file.
|
|
265
|
+
|
|
266
|
+
When running `npm run test`, a browser tab will open to ask you for authorization. After you've authorized, the tests will run and produce an output similar to this:
|
|
267
|
+

|
|
268
|
+
|
|
269
|
+
**Note:** There is a 30-second timeout for the authorization request. You may need to try again if your browser does not open fast enough.
|
package/api.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const FormData = require('form-data');
|
|
2
|
+
const {OAuth2Requester, get} = require('@friggframework/core');
|
|
3
|
+
|
|
4
|
+
class Api extends OAuth2Requester {
|
|
5
|
+
constructor(params) {
|
|
6
|
+
super(params);
|
|
7
|
+
// The majority of the properties for OAuth are default loaded by OAuth2Requester.
|
|
8
|
+
// This includes the `client_id`, `client_secret`, `scopes`, and `redirect_uri`.
|
|
9
|
+
this.baseUrl = 'https://www.zohoapis.com/crm/v6';
|
|
10
|
+
this.authorizationUri = encodeURI(
|
|
11
|
+
`https://accounts.zoho.com/oauth/v2/auth?scope=${this.scope}&client_id=${this.client_id}&redirect_uri=${this.redirect_uri}&response_type=code&access_type=offline`
|
|
12
|
+
);
|
|
13
|
+
this.tokenUri = 'https://accounts.zoho.com/oauth/v2/token';
|
|
14
|
+
this.access_token = get(params, 'access_token', null);
|
|
15
|
+
this.refresh_token = get(params, 'refresh_token', null);
|
|
16
|
+
|
|
17
|
+
this.URLs = {
|
|
18
|
+
// Users
|
|
19
|
+
users: '/users',
|
|
20
|
+
user: (userId) => `/users/${userId}`,
|
|
21
|
+
|
|
22
|
+
// Roles
|
|
23
|
+
roles: '/settings/roles',
|
|
24
|
+
role: (roleId) => `/settings/roles/${roleId}`,
|
|
25
|
+
|
|
26
|
+
// Profiles
|
|
27
|
+
profiles: '/settings/profiles',
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getAuthUri() {
|
|
32
|
+
return this.authorizationUri;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getTokenFromCode(code) {
|
|
36
|
+
// I had to override OAuth2Requester.getTokenFromCode method so I could send a form-data,
|
|
37
|
+
// as described in the docs: https://www.zoho.com/crm/developer/docs/api/v6/access-refresh.html
|
|
38
|
+
const formData = new FormData();
|
|
39
|
+
formData.append('grant_type', 'authorization_code');
|
|
40
|
+
formData.append('client_id', this.client_id);
|
|
41
|
+
formData.append('client_secret', this.client_secret);
|
|
42
|
+
formData.append('redirect_uri', this.redirect_uri);
|
|
43
|
+
formData.append('scope', this.scope);
|
|
44
|
+
formData.append('code', code);
|
|
45
|
+
const options = {
|
|
46
|
+
body: formData,
|
|
47
|
+
headers: formData.getHeaders(),
|
|
48
|
+
url: this.tokenUri,
|
|
49
|
+
};
|
|
50
|
+
const response = await this._post(options, false);
|
|
51
|
+
await this.setTokens(response);
|
|
52
|
+
return response;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
addJsonHeaders(options) {
|
|
56
|
+
const jsonHeaders = {
|
|
57
|
+
'content-type': 'application/json',
|
|
58
|
+
Accept: 'application/json',
|
|
59
|
+
};
|
|
60
|
+
options.headers = {
|
|
61
|
+
...jsonHeaders,
|
|
62
|
+
...options.headers,
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async _get(options, stringify) {
|
|
67
|
+
this.addJsonHeaders(options);
|
|
68
|
+
return super._get(options, stringify);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async _post(options, stringify) {
|
|
72
|
+
this.addJsonHeaders(options);
|
|
73
|
+
return super._post(options, stringify);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async _put(options, stringify) {
|
|
77
|
+
this.addJsonHeaders(options);
|
|
78
|
+
return super._put(options, stringify);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async _delete(options) {
|
|
82
|
+
this.addJsonHeaders(options);
|
|
83
|
+
const response = await super._delete(options);
|
|
84
|
+
return await this.parsedBody(response);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ************************** Users **********************************
|
|
88
|
+
// https://www.zoho.com/crm/developer/docs/api/v6/get-users.html
|
|
89
|
+
|
|
90
|
+
async listUsers(queryParams = {}) {
|
|
91
|
+
return this._get({
|
|
92
|
+
url: this.baseUrl + this.URLs.users,
|
|
93
|
+
query: {...queryParams},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async getUser(userId) {
|
|
98
|
+
return this._get({
|
|
99
|
+
url: this.baseUrl + this.URLs.user(userId),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async createUser(body = {}) {
|
|
104
|
+
return this._post({
|
|
105
|
+
url: this.baseUrl + this.URLs.users,
|
|
106
|
+
body: body
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async updateUser(userId, body = {}) {
|
|
111
|
+
return this._put({
|
|
112
|
+
url: this.baseUrl + this.URLs.user(userId),
|
|
113
|
+
body: body,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async deleteUser(userId) {
|
|
118
|
+
return this._delete({
|
|
119
|
+
url: this.baseUrl + this.URLs.user(userId),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ************************** Roles **********************************
|
|
124
|
+
// https://www.zoho.com/crm/developer/docs/api/v6/get-roles.html
|
|
125
|
+
|
|
126
|
+
async listRoles() {
|
|
127
|
+
return this._get({
|
|
128
|
+
url: this.baseUrl + this.URLs.roles
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async getRole(roleId) {
|
|
133
|
+
return this._get({
|
|
134
|
+
url: this.baseUrl + this.URLs.role(roleId)
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async createRole(body = {}) {
|
|
139
|
+
return this._post({
|
|
140
|
+
url: this.baseUrl + this.URLs.roles,
|
|
141
|
+
body: body
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async updateRole(roleId, body = {}) {
|
|
146
|
+
return this._put({
|
|
147
|
+
url: this.baseUrl + this.URLs.role(roleId),
|
|
148
|
+
body: body,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async deleteRole(roleId, queryParams = {}) {
|
|
153
|
+
return this._delete({
|
|
154
|
+
url: this.baseUrl + this.URLs.role(roleId),
|
|
155
|
+
query: {...queryParams},
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ************************** Profiles **********************************
|
|
160
|
+
// https://www.zoho.com/crm/developer/docs/api/v6/get-profiles.html
|
|
161
|
+
|
|
162
|
+
async listProfiles() {
|
|
163
|
+
return this._get({
|
|
164
|
+
url: this.baseUrl + this.URLs.profiles
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = {Api};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zoho-crm",
|
|
3
|
+
"label": "Zoho CRM",
|
|
4
|
+
"productUrl": "https://www.zoho.com/crm/",
|
|
5
|
+
"apiDocs": "https://www.zoho.com/crm/developer/docs/",
|
|
6
|
+
"logoUrl": "https://friggframework.org/assets/img/zoho-crm-512.png",
|
|
7
|
+
"categories": [
|
|
8
|
+
"Sales",
|
|
9
|
+
"Marketing",
|
|
10
|
+
"CRM"
|
|
11
|
+
],
|
|
12
|
+
"description": "Zoho CRM acts as a single repository to bring your sales, marketing, and customer support activities together, and streamline your process, policy, and people in one platform."
|
|
13
|
+
}
|
package/definition.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require('dotenv').config();
|
|
2
|
+
const {Api} = require('./api');
|
|
3
|
+
const {get} = require('@friggframework/core');
|
|
4
|
+
const config = require('./defaultConfig.json')
|
|
5
|
+
|
|
6
|
+
const Definition = {
|
|
7
|
+
API: Api,
|
|
8
|
+
getName: function() {
|
|
9
|
+
return config.name
|
|
10
|
+
},
|
|
11
|
+
moduleName: config.name,
|
|
12
|
+
requiredAuthMethods: {
|
|
13
|
+
getToken: async function(api, params) {
|
|
14
|
+
const code = get(params.data, 'code');
|
|
15
|
+
await api.getTokenFromCode(code);
|
|
16
|
+
},
|
|
17
|
+
apiPropertiesToPersist: {
|
|
18
|
+
credential: ['access_token', 'refresh_token'],
|
|
19
|
+
entity: [],
|
|
20
|
+
},
|
|
21
|
+
getCredentialDetails: async function (api, userId) {
|
|
22
|
+
const response = await api.listUsers({type: 'CurrentUser'});
|
|
23
|
+
const currentUser = response.users[0];
|
|
24
|
+
return {
|
|
25
|
+
identifiers: {externalId: currentUser.id, user: userId},
|
|
26
|
+
details: {},
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
getEntityDetails: async function (api, callbackParams, tokenResponse, userId) {
|
|
30
|
+
const response = await api.listUsers({type: 'CurrentUser'});
|
|
31
|
+
const currentUser = response.users[0];
|
|
32
|
+
return {
|
|
33
|
+
identifiers: {externalId: currentUser.id, user: userId},
|
|
34
|
+
details: {
|
|
35
|
+
name: currentUser.email
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
testAuthRequest: async function(api) {
|
|
40
|
+
return await api.listUsers();
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
env: {
|
|
44
|
+
client_id: process.env.ZOHO_CRM_CLIENT_ID,
|
|
45
|
+
client_secret: process.env.ZOHO_CRM_CLIENT_SECRET,
|
|
46
|
+
scope: process.env.ZOHO_CRM_SCOPE,
|
|
47
|
+
redirect_uri: `${process.env.REDIRECT_URI}/zoho-crm`,
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
module.exports = {Definition};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/images/image.jpg
ADDED
|
Binary file
|
package/index.js
ADDED
package/jest-setup.js
ADDED
package/jest-teardown.js
ADDED
package/jest.config.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* For a detailed explanation regarding each configuration property, visit:
|
|
3
|
+
* https://jestjs.io/docs/configuration
|
|
4
|
+
*/
|
|
5
|
+
module.exports = {
|
|
6
|
+
// preset: '@friggframework/test-environment',
|
|
7
|
+
coverageThreshold: {
|
|
8
|
+
global: {
|
|
9
|
+
statements: 13,
|
|
10
|
+
branches: 0,
|
|
11
|
+
functions: 1,
|
|
12
|
+
lines: 13,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
// A path to a module which exports an async function that is triggered once before all test suites
|
|
16
|
+
globalSetup: './jest-setup.js',
|
|
17
|
+
|
|
18
|
+
// A path to a module which exports an async function that is triggered once after all test suites
|
|
19
|
+
globalTeardown: './jest-teardown.js',
|
|
20
|
+
|
|
21
|
+
testTimeout: 30000,
|
|
22
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@friggframework/api-module-zoho-crm",
|
|
3
|
+
"version": "1.0.1-canary.332f986.0",
|
|
4
|
+
"prettier": "@friggframework/prettier-config",
|
|
5
|
+
"description": "Zoho CRM API module that lets the Frigg Framework interact with Zoho CRM",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"lint:fix": "prettier --write --loglevel error . && eslint . --fix",
|
|
9
|
+
"test": "jest"
|
|
10
|
+
},
|
|
11
|
+
"author": "",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@friggframework/devtools": "^1.1.2",
|
|
15
|
+
"@friggframework/test": "^1.1.2",
|
|
16
|
+
"dotenv": "^16.0.3",
|
|
17
|
+
"eslint": "^8.22.0",
|
|
18
|
+
"jest": "^28.1.3",
|
|
19
|
+
"jest-environment-jsdom": "^28.1.3",
|
|
20
|
+
"prettier": "^2.7.1"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@friggframework/core": "^1.1.2"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"gitHead": "332f9863623e74ea29795607ac3df0120dc1bc3f"
|
|
29
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
const {Authenticator} = require('@friggframework/test');
|
|
2
|
+
const {Api} = require('../api');
|
|
3
|
+
const config = require('../defaultConfig.json');
|
|
4
|
+
const { FetchError } = require('@friggframework/core');
|
|
5
|
+
|
|
6
|
+
const api = new Api({
|
|
7
|
+
client_id: process.env.ZOHO_CRM_CLIENT_ID,
|
|
8
|
+
client_secret: process.env.ZOHO_CRM_CLIENT_SECRET,
|
|
9
|
+
scope: process.env.ZOHO_CRM_SCOPE,
|
|
10
|
+
redirect_uri: `${process.env.REDIRECT_URI}/zoho-crm`,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
const url = api.getAuthUri();
|
|
15
|
+
const response = await Authenticator.oauth2(url);
|
|
16
|
+
const baseArr = response.base.split('/');
|
|
17
|
+
response.entityType = baseArr[baseArr.length - 1];
|
|
18
|
+
delete response.base;
|
|
19
|
+
await api.getTokenFromCode(response.data.code);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe(`${config.label} API tests`, () => {
|
|
23
|
+
let existingRoleId;
|
|
24
|
+
describe('Test Role resource', () => {
|
|
25
|
+
it('should list all Roles', async () => {
|
|
26
|
+
const response = await api.listRoles();
|
|
27
|
+
expect(response).toHaveProperty('roles');
|
|
28
|
+
expect(response.roles).toBeInstanceOf(Array);
|
|
29
|
+
existingRoleId = response.roles[0].id; // needed later, to delete a Role
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
let newRoleId;
|
|
33
|
+
it('should create a new Role', async () => {
|
|
34
|
+
const response = await api.createRole({
|
|
35
|
+
roles: [
|
|
36
|
+
{'name': 'Test Role 1000', 'description': 'Just testing stuff'}
|
|
37
|
+
]
|
|
38
|
+
});
|
|
39
|
+
expect(response).toHaveProperty('roles');
|
|
40
|
+
expect(response.roles[0].code).toBe('SUCCESS');
|
|
41
|
+
expect(response.roles[0].message).toBe('Role added');
|
|
42
|
+
newRoleId = response.roles[0].details.id; // store the id of the newly created Role
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should get the newly created Role by ID', async () => {
|
|
46
|
+
const response = await api.getRole(newRoleId);
|
|
47
|
+
expect(response).toHaveProperty('roles');
|
|
48
|
+
expect(response.roles[0].id).toBe(newRoleId);
|
|
49
|
+
expect(response.roles[0].name).toBe('Test Role 1000');
|
|
50
|
+
expect(response.roles[0].description).toBe('Just testing stuff');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
let updatedName = 'Foo';
|
|
54
|
+
let updatedDescription = 'Bar';
|
|
55
|
+
it('should update the newly created Role by ID', async () => {
|
|
56
|
+
const response = await api.updateRole(
|
|
57
|
+
newRoleId,
|
|
58
|
+
{roles: [{'name': updatedName, 'description': updatedDescription}]},
|
|
59
|
+
);
|
|
60
|
+
expect(response).toHaveProperty('roles');
|
|
61
|
+
expect(response.roles[0].code).toBe('SUCCESS');
|
|
62
|
+
expect(response.roles[0].message).toBe('Role updated');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should receive the updated values when getting the newly created User by ID', async () => {
|
|
66
|
+
const response = await api.getRole(newRoleId);
|
|
67
|
+
expect(response).toHaveProperty('roles');
|
|
68
|
+
expect(response.roles[0].id).toBe(newRoleId);
|
|
69
|
+
expect(response.roles[0].name).toBe(updatedName);
|
|
70
|
+
expect(response.roles[0].description).toBe(updatedDescription);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should delete the newly created Role by ID', async () => {
|
|
74
|
+
// To delete a Role, the api requires that we send it the ID of
|
|
75
|
+
// another Role, to which all users will be transfered after the delete.
|
|
76
|
+
// We rely on one of the existing Roles, whose ID we saved earlier.
|
|
77
|
+
const response = await api.deleteRole(
|
|
78
|
+
newRoleId,
|
|
79
|
+
{'transfer_to_id': existingRoleId}
|
|
80
|
+
);
|
|
81
|
+
expect(response).toHaveProperty('roles');
|
|
82
|
+
expect(response.roles[0].code).toBe('SUCCESS');
|
|
83
|
+
expect(response.roles[0].message).toBe('Role Deleted');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should throw FetchError when trying to create with empty params', () => {
|
|
87
|
+
expect(async () => await api.createRole()).rejects.toThrow(FetchError)
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('Test User resource', () => {
|
|
92
|
+
it('should list all Users', async () => {
|
|
93
|
+
const response = await api.listUsers();
|
|
94
|
+
expect(response).toHaveProperty('users');
|
|
95
|
+
expect(response.users).toBeInstanceOf(Array);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
let newUserId;
|
|
99
|
+
it('should create a new User', async () => {
|
|
100
|
+
// To create a new User in Zoho CRM, we need to specify their
|
|
101
|
+
// Role and Profile by providing the relevant IDs in the request.
|
|
102
|
+
// So we first need to fetch an existing Role and Profile.
|
|
103
|
+
const rolesResponse = await api.listRoles();
|
|
104
|
+
const role = rolesResponse.roles[0];
|
|
105
|
+
const profilesResponse = await api.listProfiles();
|
|
106
|
+
const profile = profilesResponse.profiles[0];
|
|
107
|
+
|
|
108
|
+
const response = await api.createUser({
|
|
109
|
+
users: [{
|
|
110
|
+
first_name: 'Test User 1000',
|
|
111
|
+
email: 'test@friggframework.org',
|
|
112
|
+
role: role.id,
|
|
113
|
+
profile: profile.id,
|
|
114
|
+
}]
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(response).toHaveProperty('users');
|
|
118
|
+
expect(response.users[0].code).toBe('SUCCESS');
|
|
119
|
+
expect(response.users[0].message).toBe('User added');
|
|
120
|
+
newUserId = response.users[0].details.id; // store the id of the newly created User
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should get the newly created User by ID', async () => {
|
|
124
|
+
const response = await api.getUser(newUserId);
|
|
125
|
+
expect(response).toHaveProperty('users');
|
|
126
|
+
expect(response.users[0].id).toBe(newUserId);
|
|
127
|
+
expect(response.users[0].first_name).toBe('Test User 1000');
|
|
128
|
+
expect(response.users[0].email).toBe('test@friggframework.org');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
let updatedFirstName = 'Elon';
|
|
132
|
+
let updatedEmail = 'musk@friggframework.com';
|
|
133
|
+
it('should update the newly created User by ID', async () => {
|
|
134
|
+
const response = await api.updateUser(
|
|
135
|
+
newUserId,
|
|
136
|
+
{users: [{'first_name': updatedFirstName, 'email': updatedEmail}]},
|
|
137
|
+
);
|
|
138
|
+
expect(response).toHaveProperty('users');
|
|
139
|
+
expect(response.users[0].code).toBe('SUCCESS');
|
|
140
|
+
expect(response.users[0].message).toBe('User updated');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should receive the updated values when getting the newly created User by ID', async () => {
|
|
144
|
+
const response = await api.getUser(newUserId);
|
|
145
|
+
expect(response).toHaveProperty('users');
|
|
146
|
+
expect(response.users[0].id).toBe(newUserId);
|
|
147
|
+
expect(response.users[0].first_name).toBe(updatedFirstName);
|
|
148
|
+
expect(response.users[0].email).toBe(updatedEmail);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should delete the newly created User by ID', async () => {
|
|
152
|
+
const response = await api.deleteUser(newUserId);
|
|
153
|
+
expect(response).toHaveProperty('users');
|
|
154
|
+
expect(response.users[0].code).toBe('SUCCESS');
|
|
155
|
+
expect(response.users[0].message).toBe('User deleted');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should throw FetchError when trying to create with empty params', () => {
|
|
159
|
+
expect(async () => await api.createUser()).rejects.toThrow(FetchError)
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('Test Profile resource', () => {
|
|
164
|
+
it('should list all Profiles', async () => {
|
|
165
|
+
const response = await api.listProfiles();
|
|
166
|
+
expect(response).toHaveProperty('profiles');
|
|
167
|
+
expect(response.profiles).toBeInstanceOf(Array);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it.skip('should create a new Profile', async () => {
|
|
171
|
+
// TODO
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it.skip('should get the newly created Profile by ID', async () => {
|
|
175
|
+
// TODO
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it.skip('should update the newly created Profile by ID', async () => {
|
|
179
|
+
// TODO
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it.skip('should receive the updated values when getting the newly created Profile by ID', async () => {
|
|
183
|
+
// TODO
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it.skip('should delete the newly created Profile by ID', async () => {
|
|
187
|
+
// TODO
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it.skip('should throw FetchError when trying to create with empty params', () => {
|
|
191
|
+
// TODO
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
});
|