@friggframework/core 0.2.31-v1-alpha-package-update.0 → 1.0.1-v1-alpha-package-update.1
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/assertions/CHANGELOG.md +87 -0
- package/assertions/LICENSE.md +9 -0
- package/assertions/README.md +3 -0
- package/assertions/bump.txt +1 -0
- package/assertions/get.js +139 -0
- package/assertions/index.js +19 -0
- package/assertions/is-equal.js +17 -0
- package/associations/LICENSE.md +9 -0
- package/associations/README.md +3 -0
- package/associations/association.js +78 -0
- package/associations/bump3.txt +0 -0
- package/associations/jest.config.js +5 -0
- package/associations/model.js +54 -0
- package/bump.txt +1 -0
- package/core/.eslintrc.json +3 -0
- package/{Delegate.js → core/Delegate.js} +1 -1
- package/core/LICENSE.md +9 -0
- package/{Worker.js → core/Worker.js} +2 -2
- package/core/bump3.txt +0 -0
- package/{create-handler.js → core/create-handler.js} +2 -2
- package/core/index.js +6 -0
- package/core/jest.config.js +5 -0
- package/database/.eslintrc.json +3 -0
- package/database/CHANGELOG.md +97 -0
- package/database/LICENSE.md +9 -0
- package/database/README.md +3 -0
- package/database/bump3.txt +0 -0
- package/database/index.js +17 -0
- package/database/jest.config.js +5 -0
- package/database/models/IndividualUser.js +76 -0
- package/database/models/OrganizationUser.js +29 -0
- package/database/models/State.js +9 -0
- package/database/models/Token.js +70 -0
- package/database/models/UserModel.js +7 -0
- package/database/mongo.js +45 -0
- package/database/mongoose.js +5 -0
- package/encrypt/.eslintrc.json +3 -0
- package/encrypt/CHANGELOG.md +65 -0
- package/encrypt/Cryptor.js +236 -0
- package/encrypt/Cryptor.test.js +32 -0
- package/encrypt/LICENSE.md +9 -0
- package/encrypt/README.md +3 -0
- package/encrypt/aes.js +27 -0
- package/encrypt/bump3.txt +0 -0
- package/encrypt/encrypt.js +124 -0
- package/encrypt/encrypt.test.js +1068 -0
- package/encrypt/index.js +3 -0
- package/encrypt/jest.config.js +5 -0
- package/encrypt/test-encrypt.js +107 -0
- package/errors/.eslintrc.json +3 -0
- package/errors/CHANGELOG.md +44 -0
- package/errors/LICENSE.md +9 -0
- package/errors/README.md +3 -0
- package/errors/base-error.js +23 -0
- package/errors/base-error.test.js +32 -0
- package/errors/bump.txt +1 -0
- package/errors/bump3.txt +0 -0
- package/errors/fetch-error.js +72 -0
- package/errors/fetch-error.test.js +79 -0
- package/errors/halt-error.js +10 -0
- package/errors/halt-error.test.js +11 -0
- package/errors/index.js +15 -0
- package/errors/jest.config.js +5 -0
- package/errors/validation-errors.js +23 -0
- package/errors/validation-errors.test.js +120 -0
- package/eslint-config/.eslintrc.json +3 -0
- package/eslint-config/CHANGELOG.md +17 -0
- package/eslint-config/LICENSE.md +9 -0
- package/eslint-config/README.md +3 -0
- package/eslint-config/bump3.txt +0 -0
- package/eslint-config/index.js +38 -0
- package/index.js +29 -5
- package/integrations/.eslintrc.json +3 -0
- package/integrations/CHANGELOG.md +244 -0
- package/integrations/LICENSE.md +9 -0
- package/integrations/README.md +3 -0
- package/integrations/bump3.txt +0 -0
- package/integrations/create-frigg-backend.js +31 -0
- package/integrations/index.js +19 -0
- package/integrations/integration-base.js +162 -0
- package/integrations/integration-factory.js +166 -0
- package/integrations/integration-mapping.js +43 -0
- package/integrations/integration-model.js +42 -0
- package/integrations/integration-router.js +367 -0
- package/integrations/integration-user.js +144 -0
- package/integrations/jest-setup.js +2 -0
- package/integrations/jest-teardown.js +2 -0
- package/integrations/jest.config.js +12 -0
- package/integrations/options.js +54 -0
- package/integrations/test/integration-base.test.js +143 -0
- package/lambda/README.md +3 -0
- package/lambda/TimeoutCatcher.js +43 -0
- package/lambda/TimeoutCatcher.test.js +68 -0
- package/lambda/bump3.txt +0 -0
- package/lambda/index.js +3 -0
- package/lambda/jest.config.js +3 -0
- package/logs/.eslintrc.json +3 -0
- package/logs/CHANGELOG.md +57 -0
- package/logs/LICENSE.md +9 -0
- package/logs/README.md +3 -0
- package/logs/bump3.txt +0 -0
- package/logs/index.js +7 -0
- package/logs/jest.config.js +5 -0
- package/logs/logger.js +69 -0
- package/logs/logger.test.js +76 -0
- package/migrations/README.md +3 -0
- package/migrations/bump3.txt +0 -0
- package/migrations/index.js +9 -0
- package/migrations/jest.config.js +3 -0
- package/migrations/manager.js +33 -0
- package/migrations/migrator.js +170 -0
- package/migrations/options.js +28 -0
- package/module-plugin/.eslintrc.json +3 -0
- package/module-plugin/CHANGELOG.md +224 -0
- package/module-plugin/LICENSE.md +9 -0
- package/module-plugin/ModuleConstants.js +10 -0
- package/module-plugin/README.md +3 -0
- package/module-plugin/auther.js +342 -0
- package/module-plugin/bump3.txt +0 -0
- package/module-plugin/credential.js +22 -0
- package/module-plugin/entity-manager.js +70 -0
- package/module-plugin/entity.js +46 -0
- package/module-plugin/index.js +25 -0
- package/module-plugin/jest-setup.js +3 -0
- package/module-plugin/jest-teardown.js +2 -0
- package/module-plugin/jest.config.js +20 -0
- package/module-plugin/manager.js +169 -0
- package/module-plugin/module-factory.js +60 -0
- package/module-plugin/requester/api-key.js +36 -0
- package/module-plugin/requester/basic.js +43 -0
- package/module-plugin/requester/oauth-2.js +219 -0
- package/module-plugin/requester/requester.js +150 -0
- package/module-plugin/requester/requester.test.js +28 -0
- package/module-plugin/test/auther.test.js +97 -0
- package/module-plugin/test/mock-api/api.js +29 -0
- package/module-plugin/test/mock-api/definition.js +48 -0
- package/module-plugin/test/mock-api/mocks/hubspot.js +43 -0
- package/package.json +37 -12
- package/prettier-config/.eslintrc.json +3 -0
- package/prettier-config/CHANGELOG.md +17 -0
- package/prettier-config/LICENSE.md +9 -0
- package/prettier-config/README.md +3 -0
- package/prettier-config/bump3.txt +0 -0
- package/prettier-config/index.js +6 -0
- package/syncs/README.md +3 -0
- package/syncs/bump3.txt +0 -0
- package/syncs/jest.config.js +5 -0
- package/syncs/manager.js +466 -0
- package/syncs/model.js +62 -0
- package/syncs/sync.js +114 -0
- package/test-environment/.eslintrc.json +3 -0
- package/test-environment/Authenticator.js +73 -0
- package/test-environment/CHANGELOG.md +46 -0
- package/test-environment/LICENSE.md +9 -0
- package/test-environment/README.md +3 -0
- package/test-environment/auther-definition-method-tester.js +45 -0
- package/test-environment/auther-definition-tester.js +104 -0
- package/test-environment/bump3.txt +0 -0
- package/test-environment/index.js +24 -0
- package/test-environment/integration-validator.js +2 -0
- package/test-environment/jest-global-setup.js +6 -0
- package/test-environment/jest-global-teardown.js +3 -0
- package/test-environment/jest-preset.js +14 -0
- package/test-environment/mock-api-readme.md +102 -0
- package/test-environment/mock-api.js +284 -0
- package/test-environment/mock-integration.js +82 -0
- package/test-environment/mongodb.js +22 -0
- package/test-environment/override-environment.js +11 -0
- package/types/CHANGELOG.md +49 -0
- package/types/README.md +24 -0
- package/types/assertions/index.d.ts +83 -0
- package/types/associations/index.d.ts +74 -0
- package/types/bump3.txt +0 -0
- package/types/core/index.d.ts +54 -0
- package/types/database/index.d.ts +3 -0
- package/types/encrypt/index.d.ts +5 -0
- package/types/errors/index.d.ts +66 -0
- package/types/eslint-config/index.d.ts +41 -0
- package/types/index.d.ts +14 -0
- package/types/integrations/index.d.ts +191 -0
- package/types/lambda/index.d.ts +31 -0
- package/types/logs/index.d.ts +5 -0
- package/types/module-plugin/index.d.ts +293 -0
- package/types/prettier-config/index.d.ts +6 -0
- package/types/syncs/index.d.ts +128 -0
- package/types/test-environment/index.d.ts +17 -0
- package/types/tsconfig.json +103 -0
- /package/{.eslintrc.json → assertions/.eslintrc.json} +0 -0
- /package/{bump3.txt → assertions/bump3.txt} +0 -0
- /package/{jest.config.js → assertions/jest.config.js} +0 -0
- /package/{CHANGELOG.md → core/CHANGELOG.md} +0 -0
- /package/{README.md → core/README.md} +0 -0
- /package/{load-installed-modules.js → core/load-installed-modules.js} +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
const { ModuleFactory, Credential, Entity } = require('../module-plugin');
|
|
2
|
+
const {IntegrationModel} = require("./integration-model");
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IntegrationFactory {
|
|
7
|
+
constructor(integrationClasses = []) {
|
|
8
|
+
this.integrationClasses = integrationClasses;
|
|
9
|
+
this.moduleFactory = new ModuleFactory(...this.getModules());
|
|
10
|
+
this.integrationTypes = this.integrationClasses.map(IntegrationClass => IntegrationClass.getName());
|
|
11
|
+
this.getIntegrationConfigs = this.integrationClasses.map(IntegrationClass => IntegrationClass.Config);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async getIntegrationOptions() {
|
|
15
|
+
const options = this.integrationClasses.map(IntegrationClass => IntegrationClass.Options);
|
|
16
|
+
return {
|
|
17
|
+
entities: {
|
|
18
|
+
primary: this.getPrimaryName(),
|
|
19
|
+
options: options.map(val => val.get()),
|
|
20
|
+
authorized: [],
|
|
21
|
+
},
|
|
22
|
+
integrations: [],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getModules() {
|
|
27
|
+
return [... new Set(this.integrationClasses.map(integration =>
|
|
28
|
+
Object.values(integration.modules)
|
|
29
|
+
).flat())];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getPrimaryName() {
|
|
33
|
+
function findMostFrequentElement(array) {
|
|
34
|
+
const frequencyMap = _.countBy(array);
|
|
35
|
+
return _.maxBy(_.keys(frequencyMap), (element) => frequencyMap[element]);
|
|
36
|
+
}
|
|
37
|
+
const allModulesNames = _.flatten(this.integrationClasses.map(integration =>
|
|
38
|
+
Object.values(integration.modules).map(module => module.getName())
|
|
39
|
+
));
|
|
40
|
+
return findMostFrequentElement(allModulesNames);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getIntegrationClassDefByType(type) {
|
|
44
|
+
const integrationClassIndex = this.integrationTypes.indexOf(type);
|
|
45
|
+
return this.integrationClasses[integrationClassIndex];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async getInstanceFromIntegrationId(params) {
|
|
49
|
+
const integrationRecord = await IntegrationHelper.getIntegrationById(params.integrationId);
|
|
50
|
+
let {userId} = params;
|
|
51
|
+
if (!integrationRecord) {
|
|
52
|
+
throw new Error(`No integration found by the ID of ${params.integrationId}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!userId) {
|
|
56
|
+
userId = integrationRecord.user._id.toString();
|
|
57
|
+
} else if (userId !== integrationRecord.user._id.toString()) {
|
|
58
|
+
throw new Error(`Integration ${params.integrationId} does not belong to User ${userId}, ${integrationRecord.user.id.toString()}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const integrationClassDef = this.getIntegrationClassDefByType(integrationRecord.config.type);
|
|
62
|
+
const instance = new integrationClassDef({
|
|
63
|
+
userId,
|
|
64
|
+
integrationId: params.integrationId,
|
|
65
|
+
});
|
|
66
|
+
instance.record = integrationRecord;
|
|
67
|
+
instance.delegateTypes.push(...integrationClassDef.Config.events);
|
|
68
|
+
instance.primary = await this.moduleFactory.getModuleInstanceFromEntityId(
|
|
69
|
+
instance.record.entities[0],
|
|
70
|
+
instance.record.user
|
|
71
|
+
);
|
|
72
|
+
instance.target = await this.moduleFactory.getModuleInstanceFromEntityId(
|
|
73
|
+
instance.record.entities[1],
|
|
74
|
+
instance.record.user
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
await instance.getAndSetUserActions();
|
|
79
|
+
instance.delegateTypes.push(...Object.keys(instance.userActions));
|
|
80
|
+
} catch(e) {
|
|
81
|
+
instance.userActions = {};
|
|
82
|
+
instance.record.status = 'ERROR';
|
|
83
|
+
instance.record.messages.errors.push(e);
|
|
84
|
+
await instance.record.save();
|
|
85
|
+
}
|
|
86
|
+
return instance;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async createIntegration(entities, userId, config) {
|
|
90
|
+
// verify entity ids belong to the user
|
|
91
|
+
// for (const id of entities) {
|
|
92
|
+
// const entity = await Entity.findById(id);
|
|
93
|
+
// if (!entity) {
|
|
94
|
+
// throw new Error(`Entity with ID ${id} does not exist.`);
|
|
95
|
+
// }
|
|
96
|
+
// if (entity.user.toString() !== userId.toString()) {
|
|
97
|
+
// throw new Error('one or more the entities do not belong to the user');
|
|
98
|
+
// }
|
|
99
|
+
// }
|
|
100
|
+
|
|
101
|
+
// build integration
|
|
102
|
+
const integrationRecord = await IntegrationModel.create({
|
|
103
|
+
entities: entities,
|
|
104
|
+
user: userId,
|
|
105
|
+
config,
|
|
106
|
+
version: '0.0.0',
|
|
107
|
+
});
|
|
108
|
+
return await this.getInstanceFromIntegrationId({integrationId: integrationRecord.id, userId});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const IntegrationHelper = {
|
|
113
|
+
getFormattedIntegration: async function(integrationRecord) {
|
|
114
|
+
const integrationObj = {
|
|
115
|
+
id: integrationRecord.id,
|
|
116
|
+
status: integrationRecord.status,
|
|
117
|
+
config: integrationRecord.config,
|
|
118
|
+
entities: [],
|
|
119
|
+
version: integrationRecord.version,
|
|
120
|
+
messages: integrationRecord.messages,
|
|
121
|
+
};
|
|
122
|
+
for (const entityId of integrationRecord.entities) {
|
|
123
|
+
// Only return non-internal fields. Leverages "select" and "options" to non-excepted fields and a pure object.
|
|
124
|
+
const entity = await Entity.findById(
|
|
125
|
+
entityId,
|
|
126
|
+
'-createdAt -updatedAt -user -credentials -credential -_id -__t -__v',
|
|
127
|
+
{ lean: true }
|
|
128
|
+
);
|
|
129
|
+
integrationObj.entities.push({
|
|
130
|
+
id: entityId,
|
|
131
|
+
...entity,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return integrationObj;
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
getIntegrationsForUserId: async function(userId) {
|
|
138
|
+
const integrationList = await IntegrationModel.find({ user: userId });
|
|
139
|
+
return await Promise.all(integrationList.map(async (integrationRecord) =>
|
|
140
|
+
await IntegrationHelper.getFormattedIntegration(integrationRecord)
|
|
141
|
+
));
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
deleteIntegrationForUserById: async function(userId, integrationId) {
|
|
145
|
+
const integrationList = await IntegrationModel.find({
|
|
146
|
+
user: userId,
|
|
147
|
+
_id: integrationId,
|
|
148
|
+
});
|
|
149
|
+
if (integrationList.length !== 1) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`Integration with id of ${integrationId} does not exist for this user`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
await IntegrationModel.deleteOne({ _id: integrationId });
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
getIntegrationById: async function(id) {
|
|
158
|
+
return IntegrationModel.findById(id);
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
listCredentials: async function(options) {
|
|
162
|
+
return Credential.find(options);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = { IntegrationFactory, IntegrationHelper };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const { mongoose } = require('../database/mongoose');
|
|
2
|
+
const { Encrypt } = require('../encrypt');
|
|
3
|
+
|
|
4
|
+
const schema = new mongoose.Schema(
|
|
5
|
+
{
|
|
6
|
+
integration: {
|
|
7
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
8
|
+
ref: 'Integration',
|
|
9
|
+
required: true,
|
|
10
|
+
},
|
|
11
|
+
sourceId: { type: String }, // Used for lookups
|
|
12
|
+
mapping: {}
|
|
13
|
+
},
|
|
14
|
+
{ timestamps: true }
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
schema.plugin(Encrypt);
|
|
18
|
+
|
|
19
|
+
schema.static({
|
|
20
|
+
findBy: async function (integrationId, sourceId) {
|
|
21
|
+
const mappings = await this.find({ integration: integrationId, sourceId });
|
|
22
|
+
if (mappings.length === 0) {
|
|
23
|
+
return null;
|
|
24
|
+
} else if (mappings.length === 1) {
|
|
25
|
+
return mappings[0].mapping;
|
|
26
|
+
} else {
|
|
27
|
+
throw new Error('multiple integration mappings with same sourceId');
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
upsert: async function (integrationId, sourceId, mapping) {
|
|
31
|
+
return this.findOneAndUpdate(
|
|
32
|
+
{ integration: integrationId, sourceId },
|
|
33
|
+
{ mapping },
|
|
34
|
+
{ new: true, upsert: true, setDefaultsOnInsert: true }
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
schema.index({ integration: 1, sourceId: 1 });
|
|
40
|
+
|
|
41
|
+
const IntegrationMapping =
|
|
42
|
+
mongoose.models.IntegrationMapping || mongoose.model('IntegrationMapping', schema);
|
|
43
|
+
module.exports = { IntegrationMapping };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const { mongoose } = require('../database/mongoose');
|
|
2
|
+
|
|
3
|
+
const schema = new mongoose.Schema(
|
|
4
|
+
{
|
|
5
|
+
entities: [
|
|
6
|
+
{
|
|
7
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
8
|
+
ref: 'Entity',
|
|
9
|
+
required: true,
|
|
10
|
+
},
|
|
11
|
+
],
|
|
12
|
+
user: {
|
|
13
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
14
|
+
ref: 'User',
|
|
15
|
+
required: false,
|
|
16
|
+
},
|
|
17
|
+
status: {
|
|
18
|
+
type: String,
|
|
19
|
+
enum: [
|
|
20
|
+
'ENABLED',
|
|
21
|
+
'NEEDS_CONFIG',
|
|
22
|
+
'PROCESSING',
|
|
23
|
+
'DISABLED',
|
|
24
|
+
'ERROR',
|
|
25
|
+
],
|
|
26
|
+
default: 'ENABLED',
|
|
27
|
+
},
|
|
28
|
+
config: {},
|
|
29
|
+
version: { type: String },
|
|
30
|
+
messages: {
|
|
31
|
+
errors: [],
|
|
32
|
+
warnings: [],
|
|
33
|
+
info: [],
|
|
34
|
+
logs: [],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{ timestamps: true }
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const Integration =
|
|
41
|
+
mongoose.models.Integration || mongoose.model('Integration', schema);
|
|
42
|
+
module.exports = { IntegrationModel: Integration };
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const { get } = require('../assertions');
|
|
3
|
+
const Boom = require('@hapi/boom');
|
|
4
|
+
const catchAsyncError = require('express-async-handler');
|
|
5
|
+
const { debug } = require('../logs');
|
|
6
|
+
function createIntegrationRouter(params) {
|
|
7
|
+
const router = get(params, 'router', express());
|
|
8
|
+
const factory = get(params, 'factory');
|
|
9
|
+
const getUserId = get(params, 'getUserId', (req) => null);
|
|
10
|
+
const requireLoggedInUser = get(params, 'requireLoggedInUser', (req, res, next) => next());
|
|
11
|
+
|
|
12
|
+
router.all('/api/entities*', requireLoggedInUser);
|
|
13
|
+
router.all('/api/authorize', requireLoggedInUser);
|
|
14
|
+
router.all('/api/integrations*', requireLoggedInUser);
|
|
15
|
+
|
|
16
|
+
setIntegrationRoutes(router, factory, getUserId);
|
|
17
|
+
setEntityRoutes(router, factory, getUserId);
|
|
18
|
+
return router;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function checkRequiredParams(params, requiredKeys) {
|
|
22
|
+
const missingKeys = [];
|
|
23
|
+
const returnDict = {};
|
|
24
|
+
for (const key of requiredKeys) {
|
|
25
|
+
const val = get(params, key, null);
|
|
26
|
+
if (val) {
|
|
27
|
+
returnDict[key] = val;
|
|
28
|
+
} else {
|
|
29
|
+
missingKeys.push(key);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (missingKeys.length > 0) {
|
|
34
|
+
throw Boom.badRequest(
|
|
35
|
+
`Missing Parameter${
|
|
36
|
+
missingKeys.length === 1 ? '' : 's'
|
|
37
|
+
}: ${missingKeys.join(', ')} ${
|
|
38
|
+
missingKeys.length === 1 ? 'is' : 'are'
|
|
39
|
+
} required.`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return returnDict;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function setIntegrationRoutes(router, factory, getUserId) {
|
|
46
|
+
const {moduleFactory, integrationFactory, IntegrationHelper} = factory;
|
|
47
|
+
router.route('/api/integrations').get(
|
|
48
|
+
catchAsyncError(async (req, res) => {
|
|
49
|
+
const results = await integrationFactory.getIntegrationOptions();
|
|
50
|
+
results.entities.authorized = await moduleFactory.getEntitiesForUser(
|
|
51
|
+
getUserId(req)
|
|
52
|
+
);
|
|
53
|
+
results.integrations = await IntegrationHelper.getIntegrationsForUserId(
|
|
54
|
+
getUserId(req)
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
for (const integrationRecord of results.integrations) {
|
|
58
|
+
const integration = await integrationFactory.getInstanceFromIntegrationId({
|
|
59
|
+
integrationId: integrationRecord.id,
|
|
60
|
+
userId: getUserId(req),
|
|
61
|
+
});
|
|
62
|
+
integrationRecord.userActions = integration.userActions;
|
|
63
|
+
}
|
|
64
|
+
res.json(results);
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
router.route('/api/integrations').post(
|
|
69
|
+
catchAsyncError(async (req, res) => {
|
|
70
|
+
const params = checkRequiredParams(req.body, [
|
|
71
|
+
'entities',
|
|
72
|
+
'config',
|
|
73
|
+
]);
|
|
74
|
+
// throw if not value
|
|
75
|
+
get(params.config, 'type');
|
|
76
|
+
|
|
77
|
+
// create integration
|
|
78
|
+
const integration =
|
|
79
|
+
await integrationFactory.createIntegration(
|
|
80
|
+
params.entities,
|
|
81
|
+
getUserId(req),
|
|
82
|
+
params.config,
|
|
83
|
+
moduleFactory
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// post integration initialization
|
|
87
|
+
debug(
|
|
88
|
+
`Calling onCreate on the ${integration?.constructor?.Config?.name} Integration with no arguments`
|
|
89
|
+
);
|
|
90
|
+
await integration.onCreate();
|
|
91
|
+
|
|
92
|
+
// filtered set for results
|
|
93
|
+
const response = await IntegrationHelper.getFormattedIntegration(
|
|
94
|
+
integration.record
|
|
95
|
+
);
|
|
96
|
+
res.status(201);
|
|
97
|
+
res.json(response);
|
|
98
|
+
})
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
router.route('/api/integrations/:integrationId').patch(
|
|
102
|
+
catchAsyncError(async (req, res) => {
|
|
103
|
+
const params = checkRequiredParams(req.body, ['config']);
|
|
104
|
+
|
|
105
|
+
const integration =
|
|
106
|
+
await integrationFactory.getInstanceFromIntegrationId({
|
|
107
|
+
integrationId: req.params.integrationId,
|
|
108
|
+
userId: getUserId(req),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
debug(
|
|
112
|
+
`Calling onUpdate on the ${integration?.constructor?.Config?.name} Integration arguments: `,
|
|
113
|
+
params
|
|
114
|
+
);
|
|
115
|
+
await integration.onUpdate(params);
|
|
116
|
+
|
|
117
|
+
const response = await IntegrationHelper.getFormattedIntegration(
|
|
118
|
+
integration.record
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
res.json(response);
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
router.route('/api/integrations/:integrationId').delete(
|
|
126
|
+
catchAsyncError(async (req, res) => {
|
|
127
|
+
const params = checkRequiredParams(req.params, [
|
|
128
|
+
'integrationId',
|
|
129
|
+
]);
|
|
130
|
+
const integration =
|
|
131
|
+
await integrationFactory.getInstanceFromIntegrationId({
|
|
132
|
+
userId: getUserId(req),
|
|
133
|
+
integrationId: params.integrationId,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
debug(
|
|
137
|
+
`Calling onUpdate on the ${integration?.constructor?.Config?.name} Integration with no arguments`
|
|
138
|
+
);
|
|
139
|
+
await integration.onDelete();
|
|
140
|
+
await IntegrationHelper.deleteIntegrationForUserById(
|
|
141
|
+
getUserId(req),
|
|
142
|
+
params.integrationId
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
res.status(201);
|
|
146
|
+
res.json({});
|
|
147
|
+
})
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
router.route('/api/integrations/:integrationId/config/options').get(
|
|
151
|
+
catchAsyncError(async (req, res) => {
|
|
152
|
+
const params = checkRequiredParams(req.params, [
|
|
153
|
+
'integrationId',
|
|
154
|
+
]);
|
|
155
|
+
const integration =
|
|
156
|
+
await integrationFactory.getInstanceFromIntegrationId(params);
|
|
157
|
+
const results = await integration.getConfigOptions();
|
|
158
|
+
// We could perhaps augment router with dynamic options? Haven't decided yet, but here may be the place
|
|
159
|
+
res.json(results);
|
|
160
|
+
})
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
router.route('/api/integrations/:integrationId/actions/:actionId/options').get(
|
|
164
|
+
catchAsyncError(async (req, res) => {
|
|
165
|
+
const params = checkRequiredParams(req.params, [
|
|
166
|
+
'integrationId',
|
|
167
|
+
'actionId'
|
|
168
|
+
]);
|
|
169
|
+
const integration =
|
|
170
|
+
await integrationFactory.getInstanceFromIntegrationId(params);
|
|
171
|
+
const results = await integration.getActionOptions(
|
|
172
|
+
params.actionId
|
|
173
|
+
);
|
|
174
|
+
// We could perhaps augment router with dynamic options? Haven't decided yet, but here may be the place
|
|
175
|
+
res.json(results);
|
|
176
|
+
})
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
router.route('/api/integrations/:integrationId/actions/:actionId').post(
|
|
180
|
+
catchAsyncError(async (req, res) => {
|
|
181
|
+
const params = checkRequiredParams(req.params, [
|
|
182
|
+
'integrationId',
|
|
183
|
+
'actionId'
|
|
184
|
+
]);
|
|
185
|
+
const integration =
|
|
186
|
+
await integrationFactory.getInstanceFromIntegrationId(params);
|
|
187
|
+
const results = await integration.notify(
|
|
188
|
+
params.actionId,
|
|
189
|
+
req.body
|
|
190
|
+
);
|
|
191
|
+
// We could perhaps augment router with dynamic options? Haven't decided yet, but here may be the place
|
|
192
|
+
res.json(results);
|
|
193
|
+
})
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
router.route('/api/integrations/:integrationId').get(
|
|
197
|
+
catchAsyncError(async (req, res) => {
|
|
198
|
+
const params = checkRequiredParams(req.params, [
|
|
199
|
+
'integrationId',
|
|
200
|
+
]);
|
|
201
|
+
const integration = await IntegrationHelper.getIntegrationById(
|
|
202
|
+
params.integrationId
|
|
203
|
+
);
|
|
204
|
+
// We could perhaps augment router with dynamic options? Haven't decided yet, but here may be the place
|
|
205
|
+
|
|
206
|
+
res.json({
|
|
207
|
+
id: integration.id,
|
|
208
|
+
entities: integration.entities,
|
|
209
|
+
status: integration.status,
|
|
210
|
+
config: integration.config,
|
|
211
|
+
});
|
|
212
|
+
})
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
router.route('/api/integrations/:integrationId/test-auth').get(
|
|
216
|
+
catchAsyncError(async (req, res) => {
|
|
217
|
+
const params = checkRequiredParams(req.params, [
|
|
218
|
+
'integrationId',
|
|
219
|
+
]);
|
|
220
|
+
const instance = await integrationFactory.getInstanceFromIntegrationId({
|
|
221
|
+
userId: getUserId(req),
|
|
222
|
+
integrationId: params.integrationId,
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
if (!instance) {
|
|
226
|
+
throw Boom.notFound();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const start = Date.now();
|
|
230
|
+
await instance.testAuth();
|
|
231
|
+
const errors = instance.record.messages?.errors?.filter(
|
|
232
|
+
({ timestamp }) => timestamp >= start
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
if (errors?.length) {
|
|
236
|
+
res.status(400);
|
|
237
|
+
res.json({ errors });
|
|
238
|
+
} else {
|
|
239
|
+
res.json({ status: 'ok' });
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function setEntityRoutes(router, factory, getUserId) {
|
|
246
|
+
const {moduleFactory, IntegrationHelper} = factory;
|
|
247
|
+
const getModuleInstance = async (req, entityType) => {
|
|
248
|
+
if (!moduleFactory.checkIsValidType(entityType)) {
|
|
249
|
+
throw Boom.badRequest(
|
|
250
|
+
`Error: Invalid entity type of ${entityType}, options are ${moduleFactory.moduleTypes.join(
|
|
251
|
+
', '
|
|
252
|
+
)}`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
return await moduleFactory.getInstanceFromTypeName(entityType, getUserId(req));
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
router.route('/api/authorize').get(
|
|
259
|
+
catchAsyncError(async (req, res) => {
|
|
260
|
+
const params = checkRequiredParams(req.query, [
|
|
261
|
+
'entityType',
|
|
262
|
+
]);
|
|
263
|
+
const module = await getModuleInstance(req, params.entityType);
|
|
264
|
+
const areRequirementsValid =
|
|
265
|
+
module.validateAuthorizationRequirements();
|
|
266
|
+
if (!areRequirementsValid) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
`Error: EntityManager of type ${params.entityType} requires a valid url`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
res.json(await module.getAuthorizationRequirements());
|
|
272
|
+
})
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
router.route('/api/authorize').post(
|
|
276
|
+
catchAsyncError(async (req, res) => {
|
|
277
|
+
const params = checkRequiredParams(req.body, [
|
|
278
|
+
'entityType',
|
|
279
|
+
'data',
|
|
280
|
+
]);
|
|
281
|
+
console.log('post authorize', params);
|
|
282
|
+
const module = await getModuleInstance(req, params.entityType);
|
|
283
|
+
console.log('post authorize module', module);
|
|
284
|
+
const results = await module.processAuthorizationCallback({
|
|
285
|
+
userId: getUserId(req),
|
|
286
|
+
data: params.data,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
res.json(results);
|
|
290
|
+
})
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
router.route('/api/entity/options/:credentialId').get(
|
|
294
|
+
catchAsyncError(async (req, res) => {
|
|
295
|
+
// TODO May want to pass along the user ID as well so credential ID's can't be fished???
|
|
296
|
+
// TODO **flagging this for review** -MW
|
|
297
|
+
const credential = await IntegrationHelper.getCredentialById(
|
|
298
|
+
req.params.credentialId
|
|
299
|
+
);
|
|
300
|
+
if (credential.user._id.toString() !== getUserId(req)) {
|
|
301
|
+
throw Boom.forbidden('Credential does not belong to user');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const params = checkRequiredParams(req.query, [
|
|
305
|
+
'entityType',
|
|
306
|
+
]);
|
|
307
|
+
const module = await getModuleInstance(req, params.entityType);
|
|
308
|
+
|
|
309
|
+
res.json(await module.getEntityOptions());
|
|
310
|
+
})
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
router.route('/api/entity').post(
|
|
314
|
+
catchAsyncError(async (req, res) => {
|
|
315
|
+
const params = checkRequiredParams(req.body, [
|
|
316
|
+
'entityType',
|
|
317
|
+
'data',
|
|
318
|
+
]);
|
|
319
|
+
checkRequiredParams(req.body.data, ['credential_id']);
|
|
320
|
+
|
|
321
|
+
// May want to pass along the user ID as well so credential ID's can't be fished???
|
|
322
|
+
const credential = await IntegrationHelper.getCredentialById(
|
|
323
|
+
params.data.credential_id
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
if (!credential) {
|
|
327
|
+
throw Boom.badRequest('Invalid credential ID');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const module = await getModuleInstance(req, params.entityType);
|
|
331
|
+
res.json(await module.findOrCreateEntity(params.data));
|
|
332
|
+
})
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
router.route('/api/entities/:entityId/test-auth').get(
|
|
336
|
+
catchAsyncError(async (req, res) => {
|
|
337
|
+
const params = checkRequiredParams(req.params, ['entityId']);
|
|
338
|
+
const module = await moduleFactory.getModuleInstanceFromEntityId(
|
|
339
|
+
params.entityId,
|
|
340
|
+
getUserId(req)
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
if (!module) {
|
|
344
|
+
throw Boom.notFound();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const testAuthResponse = await module.testAuth();
|
|
348
|
+
|
|
349
|
+
if (!testAuthResponse) {
|
|
350
|
+
res.status(400);
|
|
351
|
+
res.json({
|
|
352
|
+
errors: [
|
|
353
|
+
{
|
|
354
|
+
title: 'Authentication Error',
|
|
355
|
+
message: `There was an error with your ${module.constructor.getName()} Entity. Please reconnect/re-authenticate, or reach out to Support for assistance.`,
|
|
356
|
+
timestamp: Date.now(),
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
});
|
|
360
|
+
} else {
|
|
361
|
+
res.json({status: 'ok'});
|
|
362
|
+
}
|
|
363
|
+
})
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
module.exports = { createIntegrationRouter, checkRequiredParams };
|