@friggframework/core 2.0.0--canary.395.af305ea.0 → 2.0.0--canary.396.bb6e8dc.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.
@@ -0,0 +1,49 @@
1
+ const { Integration } = require('../integration');
2
+
3
+ class CreateIntegration {
4
+ /**
5
+ * @param {Object} params
6
+ * @param {import('../integration-repository').IntegrationRepository} params.integrationRepository
7
+ * @param {import('../integration-classes').IntegrationClasses} params.integrationClasses
8
+ * @param {import('../../module-plugin/module-service').ModuleService} params.moduleService
9
+ */
10
+ constructor({ integrationRepository, integrationClasses, moduleService }) {
11
+ this.integrationRepository = integrationRepository;
12
+ this.integrationClasses = integrationClasses;
13
+ this.moduleService = moduleService;
14
+ }
15
+
16
+ async execute(entities, userId, config) {
17
+ const integrationRecord = await this.integrationRepository.createIntegration(entities, userId, config);
18
+
19
+ const modules = {};
20
+ for (const [entityId, key] of Object.entries(integrationRecord.entityReference)) {
21
+ const moduleInstance = await this.moduleService.getModuleInstance(
22
+ entityId,
23
+ integrationRecord.user
24
+ );
25
+ modules[key] = moduleInstance;
26
+ }
27
+
28
+ const integration = new Integration({
29
+ id: integrationRecord.id,
30
+ userId: integrationRecord.user,
31
+ entities: integrationRecord.entities,
32
+ config: integrationRecord.config,
33
+ status: integrationRecord.status,
34
+ version: integrationRecord.version,
35
+ messages: integrationRecord.messages,
36
+ entityReference: integrationRecord.entityReference,
37
+ integrationClass: this.integrationClasses[integrationRecord.config.type], // todo: check if this is correct
38
+ modules
39
+ });
40
+
41
+
42
+ // load dynamic user actions and register event handlers
43
+ await integration.initialize();
44
+
45
+ return integration;
46
+ }
47
+ }
48
+
49
+ module.exports = { CreateIntegration };
@@ -0,0 +1,27 @@
1
+ class DeleteIntegrationForUser {
2
+ constructor({ integrationRepository }) {
3
+
4
+ /**
5
+ * @type {import('../integration-repository').IntegrationRepository}
6
+ */
7
+ this.integrationRepository = integrationRepository;
8
+ }
9
+
10
+ async execute(integrationId, userId) {
11
+ const integration = await this.integrationRepository.findIntegrationById(integrationId);
12
+
13
+ if (!integration) {
14
+ throw Boom.notFound(
15
+ `Integration with id of ${integrationId} does not exist`
16
+ );
17
+ }
18
+
19
+ if (integration.user.toString() !== userId.toString()) {
20
+ throw Boom.forbidden('User does not have access to this integration');
21
+ }
22
+
23
+ await this.integrationRepository.deleteIntegrationById(integrationId);
24
+ }
25
+ }
26
+
27
+ module.exports = { DeleteIntegrationForUser };
@@ -0,0 +1,30 @@
1
+ class GetIntegrationForUser {
2
+ constructor({ integrationRepository }) {
3
+
4
+ /**
5
+ * @type {import('../integration-repository').IntegrationRepository}
6
+ */
7
+ this.integrationRepository = integrationRepository;
8
+ }
9
+
10
+ /**
11
+ * @param {string} integrationId
12
+ * @param {string} userId
13
+ * @returns {Promise<Integration>}
14
+ */
15
+ async execute(integrationId, userId) {
16
+ const integration = await this.integrationRepository.findIntegrationById(integrationId);
17
+
18
+ if (!integration) {
19
+ throw Boom.notFound(`Integration with id of ${integrationId} does not exist`);
20
+ }
21
+
22
+ if (integration.user.toString() !== userId.toString()) {
23
+ throw Boom.forbidden('User does not have access to this integration');
24
+ }
25
+
26
+ return integration;
27
+ }
28
+ }
29
+
30
+ module.exports = { GetIntegrationForUser };
@@ -0,0 +1,77 @@
1
+ const { Integration } = require('../integration');
2
+ const { ModuleService } = require('../../module-plugin/module-service');
3
+
4
+
5
+ // todo: remove this use case
6
+ class GetIntegrationInstance {
7
+ constructor({
8
+ integrationRepository,
9
+ integrationClasses,
10
+ moduleService,
11
+ }) {
12
+ this.integrationRepository = integrationRepository;
13
+ this.integrationClasses = integrationClasses;
14
+ this.moduleService = moduleService;
15
+
16
+ // Build type mapping for quick lookup
17
+ this.integrationTypeMap = new Map();
18
+ this.integrationClasses.forEach((IntegrationClass) => {
19
+ this.integrationTypeMap.set(IntegrationClass.getName(), IntegrationClass);
20
+ });
21
+ }
22
+
23
+ // todo: check if this is really all we need to instantiate an integration instance
24
+ async execute(integrationId, userId) {
25
+ // 1. Get integration record from repository
26
+ const integrationRecord = await this.integrationRepository.findIntegrationById(integrationId);
27
+
28
+ if (!integrationRecord) {
29
+ throw new Error(`No integration found by the ID of ${integrationId}`);
30
+ }
31
+
32
+ // 2. Get the correct Integration class by type
33
+ const IntegrationClass = this.integrationTypeMap.get(integrationRecord.config.type);
34
+
35
+ if (!IntegrationClass) {
36
+ throw new Error(`No integration class found for type: ${integrationRecord.config.type}`);
37
+ }
38
+
39
+ if (!integrationRecord.user.equals(userId)) {
40
+ throw new Error(
41
+ `Integration ${integrationId} does not belong to User ${userId}`
42
+ );
43
+ }
44
+
45
+ // 3. Load modules based on entity references
46
+ const modules = {};
47
+ for (const [entityId, key] of Object.entries(integrationRecord.entityReference)) {
48
+ const moduleInstance = await this.moduleService.getModuleInstance(
49
+ entityId,
50
+ integrationRecord.user
51
+ );
52
+ modules[key] = moduleInstance;
53
+ }
54
+
55
+ // 4. Create the Integration domain entity with modules
56
+ const integration = new Integration({
57
+ id: integrationRecord.id,
58
+ userId: integrationRecord.user,
59
+ entities: integrationRecord.entities,
60
+ config: integrationRecord.config,
61
+ status: integrationRecord.status,
62
+ version: integrationRecord.version,
63
+ messages: integrationRecord.messages,
64
+ entityReference: integrationRecord.entityReference,
65
+ integrationClass: IntegrationClass,
66
+ modules
67
+ });
68
+
69
+
70
+ // 6. Complete async initialization (load dynamic actions, register handlers)
71
+ await integration.initialize();
72
+
73
+ return integration;
74
+ }
75
+ }
76
+
77
+ module.exports = { GetIntegrationInstance };
@@ -0,0 +1,19 @@
1
+ class GetIntegrationsForUser {
2
+ constructor({ integrationRepository }) {
3
+
4
+ /**
5
+ * @type {import('../integration-repository').IntegrationRepository}
6
+ */
7
+ this.integrationRepository = integrationRepository;
8
+ }
9
+
10
+ /**
11
+ * @param {string} userId
12
+ * @returns {Promise<Integration[]>}
13
+ */
14
+ async execute(userId) {
15
+ return this.integrationRepository.findIntegrationsByUserId(userId);
16
+ }
17
+ }
18
+
19
+ module.exports = { GetIntegrationsForUser };
@@ -0,0 +1,15 @@
1
+ const { GetIntegrationsForUserId } = require('./get-integrations-for-user');
2
+ const { DeleteIntegrationForUserById } = require('./delete-integration-for-user-by-id');
3
+ const { GetIntegrationById } = require('./get-integration-by-id');
4
+ const { ListCredentials } = require('./list-credentials');
5
+ const { CreateIntegration } = require('./create-integration');
6
+ const { GetIntegrationInstance } = require('./get-integration-instance');
7
+
8
+ module.exports = {
9
+ GetIntegrationsForUserId,
10
+ DeleteIntegrationForUserById,
11
+ GetIntegrationById,
12
+ ListCredentials,
13
+ CreateIntegration,
14
+ GetIntegrationInstance,
15
+ };
@@ -8,7 +8,6 @@ const { OAuth2Requester } = require('./requester/oauth-2');
8
8
  const { Requester } = require('./requester/requester');
9
9
  const { ModuleConstants } = require('./ModuleConstants');
10
10
  const { ModuleFactory } = require('./module-factory');
11
- const { Auther } = require('./auther');
12
11
 
13
12
  module.exports = {
14
13
  Credential,
@@ -21,5 +20,4 @@ module.exports = {
21
20
  Requester,
22
21
  ModuleConstants,
23
22
  ModuleFactory,
24
- Auther
25
23
  };
@@ -1,16 +1,22 @@
1
- const { Entity } = require('./entity');
2
- const { Auther } = require('./auther');
1
+ const { ModuleRepository } = require('./module-repository');
2
+ const { GetModuleInstance } = require('./use-cases/get-module-instance');
3
+ const { Module } = require('./module');
3
4
 
4
5
  class ModuleFactory {
5
6
  constructor(...params) {
6
7
  this.moduleDefinitions = params;
7
8
  this.moduleTypes = this.moduleDefinitions.map((def) => def.moduleName);
9
+ this.moduleRepository = new ModuleRepository();
10
+ this.getModuleInstanceUseCase = new GetModuleInstance({
11
+ moduleRepository: this.moduleRepository,
12
+ moduleDefinitions: this.moduleDefinitions,
13
+ });
8
14
  }
9
15
 
10
16
  async getEntitiesForUser(userId) {
11
17
  let results = [];
12
18
  for (const moduleDefinition of this.moduleDefinitions) {
13
- const moduleInstance = await Auther.getInstance({
19
+ const moduleInstance = new Module({
14
20
  userId,
15
21
  definition: moduleDefinition,
16
22
  });
@@ -29,29 +35,17 @@ class ModuleFactory {
29
35
  }
30
36
 
31
37
  async getModuleInstanceFromEntityId(entityId, userId) {
32
- const entity = await Entity.findById(entityId);
33
- const moduleDefinition = this.moduleDefinitions.find(
34
- (def) =>
35
- entity.toJSON()['__t'] ===
36
- Auther.getEntityModelFromDefinition(def).modelName
37
- );
38
- if (!moduleDefinition) {
39
- throw new Error(
40
- 'Module definition not found for entity type: ' + entity['__t']
41
- );
42
- }
43
- return await Auther.getInstance({
44
- userId,
45
- entityId,
46
- definition: moduleDefinition,
47
- });
38
+ return this.getModuleInstanceUseCase.execute(entityId, userId);
48
39
  }
49
40
 
50
41
  async getInstanceFromTypeName(typeName, userId) {
51
42
  const moduleDefinition = this.moduleDefinitions.find(
52
43
  (def) => def.getName() === typeName
53
44
  );
54
- return await Auther.getInstance({
45
+ if (!moduleDefinition) {
46
+ throw new Error(`Module definition not found for type: ${typeName}`);
47
+ }
48
+ return new Module({
55
49
  userId,
56
50
  definition: moduleDefinition,
57
51
  });
@@ -0,0 +1,14 @@
1
+ const { Entity } = require('./entity');
2
+
3
+ class ModuleRepository {
4
+ async findEntityById(entityId) {
5
+ const entity = await Entity.findById(entityId).populate('credential');
6
+ if (!entity) {
7
+ throw new Error(`Entity ${entityId} not found`);
8
+ }
9
+
10
+ return entity;
11
+ }
12
+ }
13
+
14
+ module.exports = { ModuleRepository };
@@ -0,0 +1,50 @@
1
+ const { Module } = require('./module');
2
+
3
+ class ModuleService {
4
+ /**
5
+ * @param {Object} params - Configuration parameters.
6
+ * @param {import('./module-repository').ModuleRepository} params.moduleRepository - Repository for module data operations.
7
+ * @param {Array<Object>} params.moduleDefinitions - Array of module definitions.
8
+ */
9
+ constructor({ moduleRepository, moduleDefinitions }) {
10
+ this.moduleRepository = moduleRepository;
11
+ this.moduleDefinitions = moduleDefinitions;
12
+ }
13
+
14
+ async getModuleInstance(entityId, userId) {
15
+ const entity = await this.moduleRepository.findEntityById(
16
+ entityId,
17
+ userId
18
+ );
19
+
20
+ if (!entity) {
21
+ throw new Error(`Entity ${entityId} not found`);
22
+ }
23
+
24
+ if (entity.user.toString() !== userId.toString()) {
25
+ throw new Error(
26
+ `Entity ${entityId} does not belong to user ${userId}`
27
+ );
28
+ }
29
+
30
+ const entityType = entity.toJSON().__t;
31
+ const moduleDefinition = this.moduleDefinitions.find((def) => {
32
+ const modelName = Module.getEntityModelFromDefinition(def).modelName;
33
+ return entityType === modelName;
34
+ });
35
+
36
+ if (!moduleDefinition) {
37
+ throw new Error(
38
+ `Module definition not found for entity type: ${entityType}`
39
+ );
40
+ }
41
+
42
+ return new Module({
43
+ userId,
44
+ entity,
45
+ definition: moduleDefinition,
46
+ });
47
+ }
48
+ }
49
+
50
+ module.exports = { ModuleService };
@@ -1,30 +1,3 @@
1
- // Manages authorization and credential persistence
2
- // Instantiation of an API Class
3
- // Expects input object like this:
4
- // const authDef = {
5
- // API: class anAPI{},
6
- // moduleName: 'anAPI', //maybe not required
7
- // requiredAuthMethods: {
8
- // // oauth methods, how to handle these being required/not?
9
- // getToken: async function(params, callbackParams, tokenResponse) {},
10
- // // required for all Auth methods
11
- // getEntityDetails: async function(params) {}, //probably calls api method
12
- // getCredentialDetails: async function(params) {}, // might be same as above
13
- // apiParamsFromCredential: function(params) {},
14
- // testAuth: async function() {}, // basic request to testAuth
15
- // },
16
- // env: {
17
- // client_id: process.env.HUBSPOT_CLIENT_ID,
18
- // client_secret: process.env.HUBSPOT_CLIENT_SECRET,
19
- // scope: process.env.HUBSPOT_SCOPE,
20
- // redirect_uri: `${process.env.REDIRECT_URI}/an-api`,
21
- // }
22
- // };
23
-
24
- //TODO:
25
- // 1. Add definition of expected params to API Class (or could just be credential?)
26
- // 2.
27
-
28
1
  const { Delegate } = require('../core');
29
2
  const { get } = require('../assertions');
30
3
  const _ = require('lodash');
@@ -34,119 +7,44 @@ const { Entity } = require('./entity');
34
7
  const { mongoose } = require('../database/mongoose');
35
8
  const { ModuleConstants } = require('./ModuleConstants');
36
9
 
37
- class Auther extends Delegate {
38
- static validateDefinition(definition) {
39
- if (!definition) {
40
- throw new Error('Auther definition is required');
41
- }
42
- if (!definition.moduleName) {
43
- throw new Error('Auther definition requires moduleName');
44
- }
45
- if (!definition.API) {
46
- throw new Error('Auther definition requires API class');
47
- }
48
- // if (!definition.Credential) {
49
- // throw new Error('Auther definition requires Credential class');
50
- // }
51
- // if (!definition.Entity) {
52
- // throw new Error('Auther definition requires Entity class');
53
- // }
54
- if (!definition.requiredAuthMethods) {
55
- throw new Error('Auther definition requires requiredAuthMethods');
56
- } else {
57
- if (
58
- definition.API.requesterType ===
59
- ModuleConstants.authType.oauth2 &&
60
- !definition.requiredAuthMethods.getToken
61
- ) {
62
- throw new Error(
63
- 'Auther definition requires requiredAuthMethods.getToken'
64
- );
65
- }
66
- if (!definition.requiredAuthMethods.getEntityDetails) {
67
- throw new Error(
68
- 'Auther definition requires requiredAuthMethods.getEntityDetails'
69
- );
70
- }
71
- if (!definition.requiredAuthMethods.getCredentialDetails) {
72
- throw new Error(
73
- 'Auther definition requires requiredAuthMethods.getCredentialDetails'
74
- );
75
- }
76
- if (!definition.requiredAuthMethods.apiPropertiesToPersist) {
77
- throw new Error(
78
- 'Auther definition requires requiredAuthMethods.apiPropertiesToPersist'
79
- );
80
- } else if (definition.Credential) {
81
- for (const prop of definition.requiredAuthMethods
82
- .apiPropertiesToPersist?.credential) {
83
- if (
84
- !definition.Credential.schema.paths.hasOwnProperty(prop)
85
- ) {
86
- throw new Error(
87
- `Auther definition requires Credential schema to have property ${prop}`
88
- );
89
- }
90
- }
91
- }
92
- if (!definition.requiredAuthMethods.testAuthRequest) {
93
- throw new Error(
94
- 'Auther definition requires requiredAuthMethods.testAuth'
95
- );
96
- }
97
- }
98
- }
10
+ class Module extends Delegate {
11
+
12
+ /**
13
+ *
14
+ * @param {Object} params
15
+ * @param {Object} params.definition The definition of the Api Module
16
+ * @param {string} params.userId The user id
17
+ * @param {Object} params.entity The entity record from the database
18
+ */
19
+ constructor({ definition, userId = null, entity: entityObj = null }) {
20
+ super({ definition, userId, entity: entityObj });
21
+
22
+ this.validateDefinition(definition);
23
+
24
+ this.userId = userId;
25
+ this.entity = entityObj.toObject();
26
+ this.credential = entityObj?.credential.toObject();
27
+ this.definition = definition;
28
+ this.getEntityOptions = this.definition.getEntityOptions;
29
+ this.refreshEntityOptions = this.definition.refreshEntityOptions;
30
+ this.name = this.definition.moduleName;
31
+ this.modelName = this.definition.modelName;
32
+ this.apiClass = this.definition.API;
33
+
34
+
35
+ Object.assign(this, this.definition.requiredAuthMethods);
36
+
37
+ this.CredentialModel = this.getCredentialModel();
38
+ this.EntityModel = this.getEntityModel();
99
39
 
100
- constructor(params) {
101
- super(params);
102
- this.userId = get(params, 'userId', null); // Making this non-required
103
- const definition = get(params, 'definition');
104
- Auther.validateDefinition(definition);
105
- Object.assign(this, definition.requiredAuthMethods);
106
- if (definition.getEntityOptions) {
107
- this.getEntityOptions = definition.getEntityOptions;
108
- }
109
- if (definition.refreshEntityOptions) {
110
- this.refreshEntityOptions = definition.refreshEntityOptions;
111
- }
112
- this.name = definition.moduleName;
113
- this.modelName = definition.modelName;
114
- this.apiClass = definition.API;
115
- this.CredentialModel =
116
- definition.Credential || this.getCredentialModel();
117
- this.EntityModel = definition.Entity || this.getEntityModel();
118
- }
119
40
 
120
- static async getInstance(params) {
121
- const instance = new this(params);
122
- if (params.entityId) {
123
- instance.entity = await instance.EntityModel.findById(
124
- params.entityId
125
- );
126
- instance.credential = await instance.CredentialModel.findById(
127
- instance.entity.credential
128
- );
129
- } else if (params.credentialId) {
130
- instance.credential = await instance.CredentialModel.findById(
131
- params.credentialId
132
- );
133
- }
134
- let credential = {};
135
- let entity = {};
136
- if (instance.credential) {
137
- credential = instance.credential.toObject();
138
- }
139
- if (instance.entity) {
140
- entity = instance.entity.toObject();
141
- }
142
41
  const apiParams = {
143
- ...params.definition.env,
144
- delegate: instance,
145
- ...instance.apiParamsFromCredential(credential),
146
- ...instance.apiParamsFromEntity(entity),
42
+ ...this.definition.env,
43
+ delegate: this,
44
+ ...this.apiParamsFromCredential(this.credential),
45
+ ...this.apiParamsFromEntity(this.entity),
147
46
  };
148
- instance.api = new instance.apiClass(apiParams);
149
- return instance;
47
+ this.api = new this.apiClass(apiParams);
150
48
  }
151
49
 
152
50
  static getEntityModelFromDefinition(definition) {
@@ -233,19 +131,10 @@ class Auther extends Delegate {
233
131
  }
234
132
 
235
133
  async getAuthorizationRequirements(params) {
236
- // TODO: How can this be more helpful both to implement and consume
237
- // this function must return a dictionary with the following format
238
- // node only url key is required. Data would be used for Base Authentication
239
- // let returnData = {
240
- // url: "callback url for the data or teh redirect url for login",
241
- // type: one of the types defined in modules/Constants.js
242
- // data: ["required", "fields", "we", "may", "need"]
243
- // }
244
- console.log(this.api);
245
134
  return this.api.getAuthorizationRequirements();
246
135
  }
247
136
 
248
- async testAuth(params) {
137
+ async testAuth() {
249
138
  let validAuth = false;
250
139
  try {
251
140
  if (await this.testAuthRequest(this.api)) validAuth = true;
@@ -308,18 +197,6 @@ class Auther extends Delegate {
308
197
  }
309
198
  }
310
199
 
311
- async getEntityOptions() {
312
- throw new Error(
313
- 'Method getEntityOptions() is not defined in the class'
314
- );
315
- }
316
-
317
- async refreshEntityOptions() {
318
- throw new Error(
319
- 'Method refreshEntityOptions() is not defined in the class'
320
- );
321
- }
322
-
323
200
  async findOrCreateEntity(entityDetails) {
324
201
  const identifiers = get(entityDetails, 'identifiers');
325
202
  const details = get(entityDetails, 'details');
@@ -389,6 +266,63 @@ class Auther extends Delegate {
389
266
  await this.entity.save();
390
267
  }
391
268
  }
269
+
270
+ // todo: check if all these props are still up to date
271
+ validateDefinition(definition) {
272
+ if (!definition) {
273
+ throw new Error('Module definition is required');
274
+ }
275
+ if (!definition.moduleName) {
276
+ throw new Error('Module definition requires moduleName');
277
+ }
278
+ if (!definition.API) {
279
+ throw new Error('Module definition requires API class');
280
+ }
281
+ if (!definition.requiredAuthMethods) {
282
+ throw new Error('Module definition requires requiredAuthMethods');
283
+ } else {
284
+ if (
285
+ definition.API.requesterType ===
286
+ ModuleConstants.authType.oauth2 &&
287
+ !definition.requiredAuthMethods.getToken
288
+ ) {
289
+ throw new Error(
290
+ 'Module definition requires requiredAuthMethods.getToken'
291
+ );
292
+ }
293
+ if (!definition.requiredAuthMethods.getEntityDetails) {
294
+ throw new Error(
295
+ 'Module definition requires requiredAuthMethods.getEntityDetails'
296
+ );
297
+ }
298
+ if (!definition.requiredAuthMethods.getCredentialDetails) {
299
+ throw new Error(
300
+ 'Module definition requires requiredAuthMethods.getCredentialDetails'
301
+ );
302
+ }
303
+ if (!definition.requiredAuthMethods.apiPropertiesToPersist) {
304
+ throw new Error(
305
+ 'Module definition requires requiredAuthMethods.apiPropertiesToPersist'
306
+ );
307
+ } else if (definition.Credential) {
308
+ for (const prop of definition.requiredAuthMethods
309
+ .apiPropertiesToPersist?.credential) {
310
+ if (
311
+ !definition.Credential.schema.paths.hasOwnProperty(prop)
312
+ ) {
313
+ throw new Error(
314
+ `Module definition requires Credential schema to have property ${prop}`
315
+ );
316
+ }
317
+ }
318
+ }
319
+ if (!definition.requiredAuthMethods.testAuthRequest) {
320
+ throw new Error(
321
+ 'Module definition requires requiredAuthMethods.testAuth'
322
+ );
323
+ }
324
+ }
325
+ }
392
326
  }
393
327
 
394
- module.exports = { Auther };
328
+ module.exports = { Module };