@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 ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "@friggframework/eslint-config"
3
+ }
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;
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "pipedrive",
3
+ "productUrl": "https://pipedrive.com",
4
+ "apiDocs": "https://developer.pipedrive.com",
5
+ "logoUrl": "https://friggframework.org/assets/img/pipedrive.jpeg",
6
+ "categories": ["Sales"],
7
+ "description": "Pipedrive"
8
+ }
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
@@ -0,0 +1,3 @@
1
+ module.exports = async () => {
2
+ preset: '@friggframework/test-environment';
3
+ };
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
+ };
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ status: 204,
3
+ };