@friggframework/api-module-pipedrive 0.8.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/.eslintrc.json +3 -0
- package/api.js +117 -0
- package/defaultConfig.json +8 -0
- package/index.js +13 -0
- package/jest.config.js +3 -0
- package/manager.js +189 -0
- package/mocks/activities/createActivity.js +172 -0
- package/mocks/activities/deleteActivity.js +3 -0
- package/mocks/activities/listActivities.js +1538 -0
- package/mocks/activities/updateActivity.js +172 -0
- package/mocks/apiMock.js +28 -0
- package/mocks/deals/listDeals.js +236 -0
- package/models/credential.js +38 -0
- package/models/entity.js +26 -0
- package/package.json +24 -0
- package/test/Api.test.js +157 -0
- package/test/Manager.test.js +138 -0
package/.eslintrc.json
ADDED
package/api.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const OAuth2Base = require('../../base/auth/OAuth2Base');
|
|
2
|
+
|
|
3
|
+
class PipedriveAPI extends OAuth2Base {
|
|
4
|
+
constructor(params) {
|
|
5
|
+
super(params);
|
|
6
|
+
|
|
7
|
+
this.access_token = this.getParam(params, 'access_token', null);
|
|
8
|
+
this.refresh_token = this.getParam(params, 'refresh_token', null);
|
|
9
|
+
this.companyDomain = this.getParam(params, 'companyDomain', null);
|
|
10
|
+
this.baseURL = () => `${this.companyDomain}/api`;
|
|
11
|
+
|
|
12
|
+
this.client_id = process.env.PIPEDRIVE_CLIENT_ID;
|
|
13
|
+
this.client_secret = process.env.PIPEDRIVE_CLIENT_SECRET;
|
|
14
|
+
this.redirect_uri = `${process.env.REDIRECT_URI}/pipedrive`;
|
|
15
|
+
this.scopes = process.env.PIPEDRIVE_SCOPES;
|
|
16
|
+
|
|
17
|
+
this.URLs = {
|
|
18
|
+
activities: '/v1/activities',
|
|
19
|
+
activityFields: '/v1/activityFields',
|
|
20
|
+
activityById: (activityId) => `/v1/activities/${activityId}`,
|
|
21
|
+
getUser: '/v1/users/me',
|
|
22
|
+
users: '/v1/users',
|
|
23
|
+
deals: '/v1/deals',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
this.authorizationUri = encodeURI(
|
|
27
|
+
`https://oauth.pipedrive.com/oauth/authorize?client_id=${this.client_id}&redirect_uri=${this.redirect_uri}&response_type=code&scope=${this.scopes}`
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
this.tokenUri = 'https://oauth.pipedrive.com/oauth/token';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async setTokens(params) {
|
|
34
|
+
await this.setCompanyDomain(params.api_domain);
|
|
35
|
+
return super.setTokens(params);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async setCompanyDomain(companyDomain) {
|
|
39
|
+
this.companyDomain = companyDomain;
|
|
40
|
+
}
|
|
41
|
+
// ************************** Deals **********************************
|
|
42
|
+
async listDeals() {
|
|
43
|
+
const options = {
|
|
44
|
+
url: this.baseURL() + this.URLs.deals,
|
|
45
|
+
};
|
|
46
|
+
const res = await this._get(options);
|
|
47
|
+
return res;
|
|
48
|
+
}
|
|
49
|
+
// ************************** Activities **********************************
|
|
50
|
+
async listActivityFields() {
|
|
51
|
+
const options = {
|
|
52
|
+
url: this.baseURL() + this.URLs.activityFields,
|
|
53
|
+
};
|
|
54
|
+
const res = await this._get(options);
|
|
55
|
+
return res;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async listActivities(params) {
|
|
59
|
+
const options = {
|
|
60
|
+
url: this.baseURL() + this.URLs.activities,
|
|
61
|
+
};
|
|
62
|
+
if (params.query) {
|
|
63
|
+
options.query = params.query;
|
|
64
|
+
}
|
|
65
|
+
const res = await this._get(options);
|
|
66
|
+
return res;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async deleteActivity(activityId) {
|
|
70
|
+
const options = {
|
|
71
|
+
url: this.baseURL() + this.URLs.activityById(activityId),
|
|
72
|
+
};
|
|
73
|
+
const res = await this._delete(options);
|
|
74
|
+
return res;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async updateActivity(activityId, task) {
|
|
78
|
+
const options = {
|
|
79
|
+
url: this.baseURL() + this.URLs.activityById(activityId),
|
|
80
|
+
body: task,
|
|
81
|
+
};
|
|
82
|
+
const res = await this._patch(options);
|
|
83
|
+
return res;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async createActivity(params) {
|
|
87
|
+
const dealId = this.getParam(params, 'dealId', null);
|
|
88
|
+
const subject = this.getParam(params, 'subject');
|
|
89
|
+
const type = this.getParam(params, 'type');
|
|
90
|
+
const options = {
|
|
91
|
+
url: this.baseURL() + this.URLs.activities,
|
|
92
|
+
body: { ...params },
|
|
93
|
+
headers: {
|
|
94
|
+
'Content-Type': 'application/json',
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
const res = await this._post(options);
|
|
98
|
+
return res;
|
|
99
|
+
}
|
|
100
|
+
// ************************** Users **********************************
|
|
101
|
+
async getUser() {
|
|
102
|
+
const options = {
|
|
103
|
+
url: this.baseURL() + this.URLs.getUser,
|
|
104
|
+
};
|
|
105
|
+
const res = await this._get(options);
|
|
106
|
+
return res;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async listUsers() {
|
|
110
|
+
const options = {
|
|
111
|
+
url: this.baseURL() + this.URLs.users,
|
|
112
|
+
};
|
|
113
|
+
const res = await this._get(options);
|
|
114
|
+
return res;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
module.exports = PipedriveAPI;
|
package/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const { Api } = require('./api');
|
|
2
|
+
const { Credential } = require('./models/credential');
|
|
3
|
+
const { Entity } = require('./models/entity');
|
|
4
|
+
const { ModuleManager } = require('./manager');
|
|
5
|
+
const Config = require('./defaultConfig');
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
Api,
|
|
9
|
+
Credential,
|
|
10
|
+
Entity,
|
|
11
|
+
ModuleManager,
|
|
12
|
+
Config,
|
|
13
|
+
};
|
package/jest.config.js
ADDED
package/manager.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const Api = require('./api.js');
|
|
3
|
+
const Entity = require('./models/entity');
|
|
4
|
+
const Credential = require('./models/credential');
|
|
5
|
+
const LHModuleManager = require('../../base/managers/LHModuleManager');
|
|
6
|
+
const ModuleConstants = require('../ModuleConstants');
|
|
7
|
+
const { update } = require('lodash');
|
|
8
|
+
const { flushDebugLog, debug } = require('../../utils/logger');
|
|
9
|
+
const Config = require('./defaultConfig.json');
|
|
10
|
+
|
|
11
|
+
class Manager extends LHModuleManager {
|
|
12
|
+
static Entity = Entity;
|
|
13
|
+
|
|
14
|
+
static Credential = Credential;
|
|
15
|
+
|
|
16
|
+
constructor(params) {
|
|
17
|
+
super(params);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static getName() {
|
|
21
|
+
return Config.name;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static async getInstance(params) {
|
|
25
|
+
const instance = new this(params);
|
|
26
|
+
|
|
27
|
+
const apiParams = { delegate: instance };
|
|
28
|
+
if (params.entityId) {
|
|
29
|
+
instance.entity = await instance.entityMO.get(params.entityId);
|
|
30
|
+
instance.credential = await instance.credentialMO.get(
|
|
31
|
+
instance.entity.credential
|
|
32
|
+
);
|
|
33
|
+
} else if (params.credentialId) {
|
|
34
|
+
instance.credential = await instance.credentialMO.get(
|
|
35
|
+
params.credentialId
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (instance.entity?.credential) {
|
|
39
|
+
apiParams.access_token = instance.credential.accessToken;
|
|
40
|
+
apiParams.refresh_token = instance.credential.refreshToken;
|
|
41
|
+
apiParams.companyDomain = instance.credential.companyDomain;
|
|
42
|
+
}
|
|
43
|
+
instance.api = await new Api(apiParams);
|
|
44
|
+
|
|
45
|
+
return instance;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async getAuthorizationRequirements(params) {
|
|
49
|
+
return {
|
|
50
|
+
url: this.api.authorizationUri,
|
|
51
|
+
type: ModuleConstants.authType.oauth2,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async testAuth() {
|
|
56
|
+
let validAuth = false;
|
|
57
|
+
try {
|
|
58
|
+
if (await this.api.getUser()) validAuth = true;
|
|
59
|
+
} catch (e) {
|
|
60
|
+
await this.markCredentialsInvalid();
|
|
61
|
+
flushDebugLog(e);
|
|
62
|
+
}
|
|
63
|
+
return validAuth;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async processAuthorizationCallback(params) {
|
|
67
|
+
const code = this.getParam(params.data, 'code');
|
|
68
|
+
await this.api.getTokenFromCode(code);
|
|
69
|
+
await this.testAuth();
|
|
70
|
+
|
|
71
|
+
const userProfile = await this.api.getUser();
|
|
72
|
+
await this.findOrCreateEntity({
|
|
73
|
+
companyId: userProfile.data.company_id,
|
|
74
|
+
companyName: userProfile.data.company_name,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
credential_id: this.credential.id,
|
|
79
|
+
entity_id: this.entity.id,
|
|
80
|
+
type: Manager.getName(),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async findOrCreateEntity(params) {
|
|
85
|
+
const companyId = this.getParam(params, 'companyId');
|
|
86
|
+
const companyName = this.getParam(params, 'companyName');
|
|
87
|
+
|
|
88
|
+
const search = await this.entityMO.list({
|
|
89
|
+
user: this.userId,
|
|
90
|
+
externalId: companyId,
|
|
91
|
+
});
|
|
92
|
+
if (search.length === 0) {
|
|
93
|
+
// validate choices!!!
|
|
94
|
+
// create entity
|
|
95
|
+
const createObj = {
|
|
96
|
+
credential: this.credential.id,
|
|
97
|
+
user: this.userId,
|
|
98
|
+
name: companyName,
|
|
99
|
+
externalId: companyId,
|
|
100
|
+
};
|
|
101
|
+
this.entity = await this.entityMO.create(createObj);
|
|
102
|
+
} else if (search.length === 1) {
|
|
103
|
+
this.entity = search[0];
|
|
104
|
+
} else {
|
|
105
|
+
debug(
|
|
106
|
+
'Multiple entities found with the same Company ID:',
|
|
107
|
+
companyId
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
entity_id: this.entity.id,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async deauthorize() {
|
|
117
|
+
this.api = new Api();
|
|
118
|
+
|
|
119
|
+
const entity = await this.entityMO.getByUserId(this.userId);
|
|
120
|
+
if (entity.credential) {
|
|
121
|
+
await this.credentialMO.delete(entity.credential);
|
|
122
|
+
entity.credential = undefined;
|
|
123
|
+
await entity.save();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async receiveNotification(notifier, delegateString, object = null) {
|
|
128
|
+
if (notifier instanceof Api) {
|
|
129
|
+
if (delegateString === this.api.DLGT_TOKEN_UPDATE) {
|
|
130
|
+
const userProfile = await this.api.getUser();
|
|
131
|
+
const pipedriveUserId = userProfile.data.id;
|
|
132
|
+
const updatedToken = {
|
|
133
|
+
user: this.userId,
|
|
134
|
+
accessToken: this.api.access_token,
|
|
135
|
+
refreshToken: this.api.refresh_token,
|
|
136
|
+
accessTokenExpire: this.api.accessTokenExpire,
|
|
137
|
+
externalId: pipedriveUserId,
|
|
138
|
+
companyDomain: object.api_domain,
|
|
139
|
+
auth_is_valid: true,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
if (!this.credential) {
|
|
143
|
+
let credentialSearch = await this.credentialMO.list({
|
|
144
|
+
externalId: pipedriveUserId,
|
|
145
|
+
});
|
|
146
|
+
if (credentialSearch.length === 0) {
|
|
147
|
+
this.credential = await this.credentialMO.create(
|
|
148
|
+
updatedToken
|
|
149
|
+
);
|
|
150
|
+
} else if (credentialSearch.length === 1) {
|
|
151
|
+
if (
|
|
152
|
+
credentialSearch[0].user.toString() ===
|
|
153
|
+
this.userId.toString()
|
|
154
|
+
) {
|
|
155
|
+
this.credential = await this.credentialMO.update(
|
|
156
|
+
credentialSearch[0],
|
|
157
|
+
updatedToken
|
|
158
|
+
);
|
|
159
|
+
} else {
|
|
160
|
+
debug(
|
|
161
|
+
'Somebody else already created a credential with the same User ID:',
|
|
162
|
+
pipedriveUserId
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
// Handling multiple credentials found with an error for the time being
|
|
167
|
+
debug(
|
|
168
|
+
'Multiple credentials found with the same User ID:',
|
|
169
|
+
pipedriveUserId
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
this.credential = await this.credentialMO.update(
|
|
174
|
+
this.credential,
|
|
175
|
+
updatedToken
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (delegateString === this.api.DLGT_TOKEN_DEAUTHORIZED) {
|
|
180
|
+
await this.deauthorize();
|
|
181
|
+
}
|
|
182
|
+
if (delegateString === this.api.DLGT_INVALID_AUTH) {
|
|
183
|
+
await this.markCredentialsInvalid();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = Manager;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
data: {
|
|
3
|
+
type: 'task',
|
|
4
|
+
id: 31,
|
|
5
|
+
attributes: {
|
|
6
|
+
action: 'email',
|
|
7
|
+
autoskipAt: null,
|
|
8
|
+
compiledSequenceTemplateHtml: null,
|
|
9
|
+
completed: false,
|
|
10
|
+
completedAt: null,
|
|
11
|
+
createdAt: '2021-11-06T02:52:48.000Z',
|
|
12
|
+
dueAt: '2021-11-06T02:52:48.000Z',
|
|
13
|
+
note: null,
|
|
14
|
+
opportunityAssociation: null,
|
|
15
|
+
scheduledAt: null,
|
|
16
|
+
state: 'incomplete',
|
|
17
|
+
stateChangedAt: null,
|
|
18
|
+
taskType: 'manual',
|
|
19
|
+
updatedAt: '2021-11-06T02:52:48.000Z',
|
|
20
|
+
},
|
|
21
|
+
relationships: {
|
|
22
|
+
account: {
|
|
23
|
+
data: {
|
|
24
|
+
type: 'account',
|
|
25
|
+
id: 1,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
call: {
|
|
29
|
+
data: null,
|
|
30
|
+
},
|
|
31
|
+
calls: {
|
|
32
|
+
links: {
|
|
33
|
+
related:
|
|
34
|
+
'https://api.pipedrive.io/api/v2/calls?filter%5Btask%5D%5Bid%5D=31',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
completer: {
|
|
38
|
+
data: null,
|
|
39
|
+
},
|
|
40
|
+
creator: {
|
|
41
|
+
data: {
|
|
42
|
+
type: 'user',
|
|
43
|
+
id: 1,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
defaultPluginMapping: {
|
|
47
|
+
data: null,
|
|
48
|
+
},
|
|
49
|
+
mailing: {
|
|
50
|
+
data: null,
|
|
51
|
+
},
|
|
52
|
+
mailings: {
|
|
53
|
+
links: {
|
|
54
|
+
related:
|
|
55
|
+
'https://api.pipedrive.io/api/v2/mailings?filter%5Btask%5D%5Bid%5D=31',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
opportunity: {
|
|
59
|
+
data: null,
|
|
60
|
+
},
|
|
61
|
+
owner: {
|
|
62
|
+
data: {
|
|
63
|
+
type: 'user',
|
|
64
|
+
id: 1,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
prospect: {
|
|
68
|
+
data: null,
|
|
69
|
+
},
|
|
70
|
+
prospectAccount: {
|
|
71
|
+
data: null,
|
|
72
|
+
},
|
|
73
|
+
prospectContacts: {
|
|
74
|
+
data: [],
|
|
75
|
+
links: {
|
|
76
|
+
related:
|
|
77
|
+
'https://api.pipedrive.io/api/v2/emailAddresses?filter%5Btask%5D%5Bid%5D=31',
|
|
78
|
+
},
|
|
79
|
+
meta: {
|
|
80
|
+
count: 0,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
prospectOwner: {
|
|
84
|
+
data: null,
|
|
85
|
+
},
|
|
86
|
+
prospectPhoneNumbers: {
|
|
87
|
+
data: [],
|
|
88
|
+
links: {
|
|
89
|
+
related:
|
|
90
|
+
'https://api.pipedrive.io/api/v2/phoneNumbers?filter%5Btask%5D%5Bid%5D=31',
|
|
91
|
+
},
|
|
92
|
+
meta: {
|
|
93
|
+
count: 0,
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
prospectStage: {
|
|
97
|
+
data: null,
|
|
98
|
+
},
|
|
99
|
+
sequence: {
|
|
100
|
+
data: null,
|
|
101
|
+
},
|
|
102
|
+
sequenceSequenceSteps: {
|
|
103
|
+
data: [],
|
|
104
|
+
links: {
|
|
105
|
+
related:
|
|
106
|
+
'https://api.pipedrive.io/api/v2/sequenceSteps?filter%5Btask%5D%5Bid%5D=31',
|
|
107
|
+
},
|
|
108
|
+
meta: {
|
|
109
|
+
count: 0,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
sequenceState: {
|
|
113
|
+
data: null,
|
|
114
|
+
},
|
|
115
|
+
sequenceStateSequenceStep: {
|
|
116
|
+
data: null,
|
|
117
|
+
},
|
|
118
|
+
sequenceStateSequenceStepOverrides: {
|
|
119
|
+
data: [],
|
|
120
|
+
meta: {
|
|
121
|
+
count: 0,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
sequenceStateStartingTemplate: {
|
|
125
|
+
data: null,
|
|
126
|
+
},
|
|
127
|
+
sequenceStep: {
|
|
128
|
+
data: null,
|
|
129
|
+
},
|
|
130
|
+
sequenceStepOverrideTemplates: {
|
|
131
|
+
data: [],
|
|
132
|
+
links: {
|
|
133
|
+
related:
|
|
134
|
+
'https://api.pipedrive.io/api/v2/templates?filter%5Btask%5D%5Bid%5D=31',
|
|
135
|
+
},
|
|
136
|
+
meta: {
|
|
137
|
+
count: 0,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
sequenceTemplate: {
|
|
141
|
+
data: null,
|
|
142
|
+
},
|
|
143
|
+
sequenceTemplateTemplate: {
|
|
144
|
+
data: null,
|
|
145
|
+
},
|
|
146
|
+
subject: {
|
|
147
|
+
data: {
|
|
148
|
+
type: 'account',
|
|
149
|
+
id: 1,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
taskPriority: {
|
|
153
|
+
data: {
|
|
154
|
+
type: 'taskPriority',
|
|
155
|
+
id: 3,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
taskTheme: {
|
|
159
|
+
data: {
|
|
160
|
+
type: 'taskTheme',
|
|
161
|
+
id: 1,
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
template: {
|
|
165
|
+
data: null,
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
links: {
|
|
169
|
+
self: 'https://api.pipedrive.io/api/v2/tasks/31',
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
};
|