@friggframework/core 0.2.31-v1-alpha-package-update.0 → 1.0.1-v1-alpha-package-update.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.
Files changed (193) hide show
  1. package/assertions/CHANGELOG.md +87 -0
  2. package/assertions/LICENSE.md +9 -0
  3. package/assertions/README.md +3 -0
  4. package/assertions/bump.txt +1 -0
  5. package/assertions/get.js +139 -0
  6. package/assertions/index.js +19 -0
  7. package/assertions/is-equal.js +17 -0
  8. package/associations/LICENSE.md +9 -0
  9. package/associations/README.md +3 -0
  10. package/associations/association.js +78 -0
  11. package/associations/bump3.txt +0 -0
  12. package/associations/jest.config.js +5 -0
  13. package/associations/model.js +54 -0
  14. package/bump.txt +0 -0
  15. package/core/.eslintrc.json +3 -0
  16. package/{Delegate.js → core/Delegate.js} +1 -1
  17. package/core/LICENSE.md +9 -0
  18. package/{Worker.js → core/Worker.js} +2 -2
  19. package/core/bump3.txt +0 -0
  20. package/{create-handler.js → core/create-handler.js} +2 -2
  21. package/core/index.js +6 -0
  22. package/core/jest.config.js +5 -0
  23. package/database/.eslintrc.json +3 -0
  24. package/database/CHANGELOG.md +97 -0
  25. package/database/LICENSE.md +9 -0
  26. package/database/README.md +3 -0
  27. package/database/bump3.txt +0 -0
  28. package/database/index.js +17 -0
  29. package/database/jest.config.js +5 -0
  30. package/database/models/IndividualUser.js +76 -0
  31. package/database/models/OrganizationUser.js +29 -0
  32. package/database/models/State.js +9 -0
  33. package/database/models/Token.js +70 -0
  34. package/database/models/UserModel.js +7 -0
  35. package/database/mongo.js +45 -0
  36. package/database/mongoose.js +5 -0
  37. package/encrypt/.eslintrc.json +3 -0
  38. package/encrypt/CHANGELOG.md +65 -0
  39. package/encrypt/Cryptor.js +236 -0
  40. package/encrypt/Cryptor.test.js +32 -0
  41. package/encrypt/LICENSE.md +9 -0
  42. package/encrypt/README.md +3 -0
  43. package/encrypt/aes.js +27 -0
  44. package/encrypt/bump3.txt +0 -0
  45. package/encrypt/encrypt.js +124 -0
  46. package/encrypt/encrypt.test.js +1068 -0
  47. package/encrypt/index.js +3 -0
  48. package/encrypt/jest.config.js +5 -0
  49. package/encrypt/test-encrypt.js +107 -0
  50. package/errors/.eslintrc.json +3 -0
  51. package/errors/CHANGELOG.md +44 -0
  52. package/errors/LICENSE.md +9 -0
  53. package/errors/README.md +3 -0
  54. package/errors/base-error.js +23 -0
  55. package/errors/base-error.test.js +32 -0
  56. package/errors/bump.txt +1 -0
  57. package/errors/bump3.txt +0 -0
  58. package/errors/fetch-error.js +72 -0
  59. package/errors/fetch-error.test.js +79 -0
  60. package/errors/halt-error.js +10 -0
  61. package/errors/halt-error.test.js +11 -0
  62. package/errors/index.js +15 -0
  63. package/errors/jest.config.js +5 -0
  64. package/errors/validation-errors.js +23 -0
  65. package/errors/validation-errors.test.js +120 -0
  66. package/eslint-config/.eslintrc.json +3 -0
  67. package/eslint-config/CHANGELOG.md +17 -0
  68. package/eslint-config/LICENSE.md +9 -0
  69. package/eslint-config/README.md +3 -0
  70. package/eslint-config/bump3.txt +0 -0
  71. package/eslint-config/index.js +38 -0
  72. package/index.js +29 -5
  73. package/integrations/.eslintrc.json +3 -0
  74. package/integrations/CHANGELOG.md +244 -0
  75. package/integrations/LICENSE.md +9 -0
  76. package/integrations/README.md +3 -0
  77. package/integrations/bump3.txt +0 -0
  78. package/integrations/create-frigg-backend.js +31 -0
  79. package/integrations/index.js +19 -0
  80. package/integrations/integration-base.js +162 -0
  81. package/integrations/integration-factory.js +166 -0
  82. package/integrations/integration-mapping.js +43 -0
  83. package/integrations/integration-model.js +42 -0
  84. package/integrations/integration-router.js +367 -0
  85. package/integrations/integration-user.js +144 -0
  86. package/integrations/jest-setup.js +2 -0
  87. package/integrations/jest-teardown.js +2 -0
  88. package/integrations/jest.config.js +12 -0
  89. package/integrations/options.js +54 -0
  90. package/integrations/test/integration-base.test.js +143 -0
  91. package/lambda/README.md +3 -0
  92. package/lambda/TimeoutCatcher.js +43 -0
  93. package/lambda/TimeoutCatcher.test.js +68 -0
  94. package/lambda/bump3.txt +0 -0
  95. package/lambda/index.js +3 -0
  96. package/lambda/jest.config.js +3 -0
  97. package/logs/.eslintrc.json +3 -0
  98. package/logs/CHANGELOG.md +57 -0
  99. package/logs/LICENSE.md +9 -0
  100. package/logs/README.md +3 -0
  101. package/logs/bump3.txt +0 -0
  102. package/logs/index.js +7 -0
  103. package/logs/jest.config.js +5 -0
  104. package/logs/logger.js +69 -0
  105. package/logs/logger.test.js +76 -0
  106. package/migrations/README.md +3 -0
  107. package/migrations/bump3.txt +0 -0
  108. package/migrations/index.js +9 -0
  109. package/migrations/jest.config.js +3 -0
  110. package/migrations/manager.js +33 -0
  111. package/migrations/migrator.js +170 -0
  112. package/migrations/options.js +28 -0
  113. package/module-plugin/.eslintrc.json +3 -0
  114. package/module-plugin/CHANGELOG.md +224 -0
  115. package/module-plugin/LICENSE.md +9 -0
  116. package/module-plugin/ModuleConstants.js +10 -0
  117. package/module-plugin/README.md +3 -0
  118. package/module-plugin/auther.js +342 -0
  119. package/module-plugin/bump3.txt +0 -0
  120. package/module-plugin/credential.js +22 -0
  121. package/module-plugin/entity-manager.js +70 -0
  122. package/module-plugin/entity.js +46 -0
  123. package/module-plugin/index.js +25 -0
  124. package/module-plugin/jest-setup.js +3 -0
  125. package/module-plugin/jest-teardown.js +2 -0
  126. package/module-plugin/jest.config.js +20 -0
  127. package/module-plugin/manager.js +169 -0
  128. package/module-plugin/module-factory.js +60 -0
  129. package/module-plugin/requester/api-key.js +36 -0
  130. package/module-plugin/requester/basic.js +43 -0
  131. package/module-plugin/requester/oauth-2.js +219 -0
  132. package/module-plugin/requester/requester.js +150 -0
  133. package/module-plugin/requester/requester.test.js +28 -0
  134. package/module-plugin/test/auther.test.js +97 -0
  135. package/module-plugin/test/mock-api/api.js +29 -0
  136. package/module-plugin/test/mock-api/definition.js +48 -0
  137. package/module-plugin/test/mock-api/mocks/hubspot.js +43 -0
  138. package/package.json +37 -12
  139. package/prettier-config/.eslintrc.json +3 -0
  140. package/prettier-config/CHANGELOG.md +17 -0
  141. package/prettier-config/LICENSE.md +9 -0
  142. package/prettier-config/README.md +3 -0
  143. package/prettier-config/bump3.txt +0 -0
  144. package/prettier-config/index.js +6 -0
  145. package/syncs/README.md +3 -0
  146. package/syncs/bump3.txt +0 -0
  147. package/syncs/jest.config.js +5 -0
  148. package/syncs/manager.js +466 -0
  149. package/syncs/model.js +62 -0
  150. package/syncs/sync.js +114 -0
  151. package/test-environment/.eslintrc.json +3 -0
  152. package/test-environment/Authenticator.js +73 -0
  153. package/test-environment/CHANGELOG.md +46 -0
  154. package/test-environment/LICENSE.md +9 -0
  155. package/test-environment/README.md +3 -0
  156. package/test-environment/auther-definition-method-tester.js +45 -0
  157. package/test-environment/auther-definition-tester.js +104 -0
  158. package/test-environment/bump3.txt +0 -0
  159. package/test-environment/index.js +24 -0
  160. package/test-environment/integration-validator.js +2 -0
  161. package/test-environment/jest-global-setup.js +6 -0
  162. package/test-environment/jest-global-teardown.js +3 -0
  163. package/test-environment/jest-preset.js +14 -0
  164. package/test-environment/mock-api-readme.md +102 -0
  165. package/test-environment/mock-api.js +284 -0
  166. package/test-environment/mock-integration.js +82 -0
  167. package/test-environment/mongodb.js +22 -0
  168. package/test-environment/override-environment.js +11 -0
  169. package/types/CHANGELOG.md +49 -0
  170. package/types/README.md +24 -0
  171. package/types/assertions/index.d.ts +83 -0
  172. package/types/associations/index.d.ts +74 -0
  173. package/types/bump3.txt +0 -0
  174. package/types/core/index.d.ts +54 -0
  175. package/types/database/index.d.ts +3 -0
  176. package/types/encrypt/index.d.ts +5 -0
  177. package/types/errors/index.d.ts +66 -0
  178. package/types/eslint-config/index.d.ts +41 -0
  179. package/types/index.d.ts +14 -0
  180. package/types/integrations/index.d.ts +191 -0
  181. package/types/lambda/index.d.ts +31 -0
  182. package/types/logs/index.d.ts +5 -0
  183. package/types/module-plugin/index.d.ts +293 -0
  184. package/types/prettier-config/index.d.ts +6 -0
  185. package/types/syncs/index.d.ts +128 -0
  186. package/types/test-environment/index.d.ts +17 -0
  187. package/types/tsconfig.json +103 -0
  188. /package/{.eslintrc.json → assertions/.eslintrc.json} +0 -0
  189. /package/{bump3.txt → assertions/bump3.txt} +0 -0
  190. /package/{jest.config.js → assertions/jest.config.js} +0 -0
  191. /package/{CHANGELOG.md → core/CHANGELOG.md} +0 -0
  192. /package/{README.md → core/README.md} +0 -0
  193. /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 };