@memberjunction/server 1.5.2 → 1.5.3

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 (39) hide show
  1. package/CHANGELOG.json +202 -1
  2. package/CHANGELOG.md +41 -2
  3. package/dist/apolloServer/index.d.ts.map +1 -1
  4. package/dist/context.d.ts.map +1 -1
  5. package/dist/entitySubclasses/EntityBehavior.server.js +1 -1
  6. package/dist/entitySubclasses/EntityBehavior.server.js.map +1 -1
  7. package/dist/entitySubclasses/userViewEntity.server.js +2 -2
  8. package/dist/entitySubclasses/userViewEntity.server.js.map +1 -1
  9. package/dist/generated/generated.d.ts +557 -43
  10. package/dist/generated/generated.d.ts.map +1 -1
  11. package/dist/generated/generated.js +2992 -241
  12. package/dist/generated/generated.js.map +1 -1
  13. package/dist/generic/ResolverBase.d.ts.map +1 -1
  14. package/dist/generic/ResolverBase.js +16 -6
  15. package/dist/generic/ResolverBase.js.map +1 -1
  16. package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
  17. package/dist/resolvers/AskSkipResolver.js +52 -13
  18. package/dist/resolvers/AskSkipResolver.js.map +1 -1
  19. package/dist/resolvers/EntityCommunicationsResolver.d.ts +49 -0
  20. package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -0
  21. package/dist/resolvers/EntityCommunicationsResolver.js +218 -0
  22. package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -0
  23. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  24. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  25. package/dist/resolvers/UserFavoriteResolver.d.ts +2 -0
  26. package/dist/resolvers/UserFavoriteResolver.d.ts.map +1 -1
  27. package/dist/resolvers/UserFavoriteResolver.js +82 -0
  28. package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
  29. package/package.json +36 -43
  30. package/src/entitySubclasses/EntityBehavior.server.ts +1 -1
  31. package/src/entitySubclasses/userViewEntity.server.ts +2 -2
  32. package/src/generated/generated.ts +2146 -205
  33. package/src/generic/ResolverBase.ts +18 -8
  34. package/src/resolvers/AskSkipResolver.ts +55 -16
  35. package/src/resolvers/EntityCommunicationsResolver.ts +184 -0
  36. package/src/resolvers/EntityRecordNameResolver.ts +1 -38
  37. package/src/resolvers/UserFavoriteResolver.ts +101 -2
  38. package/.eslintignore +0 -5
  39. package/.eslintrc +0 -24
@@ -435,7 +435,10 @@ export class ResolverBase {
435
435
  }
436
436
  }
437
437
  else {
438
- throw new Error(`Record not found for ${entityName} with key ${JSON.stringify(cKey)}`);
438
+ // save failed, return null
439
+ throw new GraphQLError(`Record not found for ${entityName} with key ${JSON.stringify(cKey)}`, {
440
+ extensions: { code: 'LOAD_ENTITY_ERROR', entityName },
441
+ });
439
442
  }
440
443
  }
441
444
  else {
@@ -458,15 +461,16 @@ export class ResolverBase {
458
461
  return entityObject.GetAll();
459
462
  }
460
463
  else {
461
- // save failed, return null
462
464
  throw new GraphQLError(entityObject.LatestResult?.Message ?? 'Unknown error', {
463
465
  extensions: { code: 'SAVE_ENTITY_ERROR', entityName },
464
466
  });
465
467
  }
466
468
  }
467
469
  else
468
- return null; // update canceled by the BeforeUpdate event, return null
469
- }
470
+ throw new GraphQLError('Save Canceled by BeforeSave() handler in ResolverBase', {
471
+ extensions: { code: 'SAVE_ENTITY_ERROR', entityName },
472
+ });
473
+ }
470
474
 
471
475
  /**
472
476
  * This routine compares the OldValues property in the input object to the values in the DB that we just loaded. If there are differences, we need to check to see if the client
@@ -566,11 +570,17 @@ export class ResolverBase {
566
570
  await this.AfterDelete(dataSource, key); // fire event
567
571
  return returnValue;
568
572
  }
569
- else
570
- return null; // delete failed, this will cause an exception
573
+ else {
574
+ throw new GraphQLError(entityObject.LatestResult?.Message ?? 'Unknown error', {
575
+ extensions: { code: 'DELETE_ENTITY_ERROR', entityName },
576
+ });
577
+ }
578
+ }
579
+ else {
580
+ throw new GraphQLError('Delete operation canceled by BeforeDelete() handler in ResolverBase', {
581
+ extensions: { code: 'DELETE_ENTITY_ERROR', entityName },
582
+ });
571
583
  }
572
- else
573
- return null; // BeforeDelete canceled the operation, this will cause an exception
574
584
  }
575
585
 
576
586
  // Before/After DELETE Event Hooks for Sub-Classes to Override
@@ -99,7 +99,10 @@ export class AskSkipResolver {
99
99
  const ck = new CompositeKey();
100
100
  ck.KeyValuePairs = compositeKey.KeyValuePairs;
101
101
  dci.RecordID = ck.Values();
102
- await dci.Save();
102
+ let dciSaveResult: boolean = await dci.Save();
103
+ if (!dciSaveResult) {
104
+ LogError(`Error saving DataContextItemEntity for record chat: ${EntityName} ${ck.Values()}`, undefined, dci.LatestResult);
105
+ }
103
106
 
104
107
  await dataContext.Load(dataContext.ID, dataSource, false, true, 10, user); // load again because we added a new data context item
105
108
  await dataContext.SaveItems(user, true); // persist the data becuase the deep loading above with related data is expensive
@@ -108,7 +111,10 @@ export class AskSkipResolver {
108
111
  convoEntity.LinkedEntityID = dci.EntityID;
109
112
  convoEntity.LinkedRecordID = ck.Values();
110
113
  convoEntity.DataContextID = dataContext.ID;
111
- await convoEntity.Save();
114
+ const convoEntitySaveResult: boolean = await convoEntity.Save();
115
+ if (!convoEntitySaveResult) {
116
+ LogError(`Error saving ConversationEntity for record chat: ${EntityName} ${ck.Values()}`, undefined, convoEntity.LatestResult);
117
+ }
112
118
  }
113
119
 
114
120
  const input = this.buildSkipAPIRequest(messages, ConversationId, dataContext, 'chat_with_a_record', false, false);
@@ -158,6 +164,7 @@ export class AskSkipResolver {
158
164
  const md = new Metadata();
159
165
  const convoDetailEntityAI = <ConversationDetailEntity>await md.GetEntityObject('Conversation Details', user);
160
166
  convoDetailEntityAI.NewRecord();
167
+ convoDetailEntityAI.HiddenToUser = false;
161
168
  convoDetailEntityAI.ConversationID = conversationID;
162
169
  const systemMessages = apiResponse.messages.filter((m) => m.role === 'system');
163
170
  const lastSystemMessage = systemMessages[systemMessages.length - 1];
@@ -166,8 +173,10 @@ export class AskSkipResolver {
166
173
  if (await convoDetailEntityAI.Save()) {
167
174
  return convoDetailEntityAI.ID;
168
175
  }
169
- else
176
+ else{
177
+ LogError(`Error saving conversation detail entity for AI message: ${lastSystemMessage?.content}`, undefined, convoDetailEntityAI.LatestResult);
170
178
  return 0;
179
+ }
171
180
  }
172
181
 
173
182
  protected buildSkipAPIRequest(messages: SkipMessage[], conversationId: number, dataContext: DataContext, requestPhase: SkipRequestPhase, includeEntities: boolean, includeQueries: boolean): SkipAPIRequest {
@@ -385,11 +394,16 @@ export class AskSkipResolver {
385
394
  dataContextEntity.NewRecord();
386
395
  dataContextEntity.UserID = user.ID;
387
396
  dataContextEntity.Name = 'Data Context for Skip Conversation';
388
- if (!await dataContextEntity.Save())
397
+ if (!await dataContextEntity.Save()){
398
+ LogError(`Creating a new data context failed`, undefined, dataContextEntity.LatestResult);
389
399
  throw new Error(`Creating a new data context failed`);
400
+ }
390
401
  }
391
402
  else {
392
- await dataContextEntity.Load(DataContextId);
403
+ const dcLoadResult = await dataContextEntity.Load(DataContextId);
404
+ if (!dcLoadResult) {
405
+ throw new Error(`Loading DataContextEntity for DataContextId ${DataContextId} failed`);
406
+ }
393
407
  }
394
408
  convoEntity.DataContextID = dataContextEntity.ID;
395
409
  if (await convoEntity.Save()) {
@@ -397,11 +411,16 @@ export class AskSkipResolver {
397
411
  if (!DataContextId || dataContextEntity.ID <= 0) {
398
412
  // only do this if we created a new data context for this conversation
399
413
  dataContextEntity.Name += ` ${ConversationId}`;
400
- await dataContextEntity.Save();
414
+ const dciSaveResult: boolean = await dataContextEntity.Save();
415
+ if (!dciSaveResult) {
416
+ LogError(`Error saving DataContextEntity for conversation: ${ConversationId}`, undefined, dataContextEntity.LatestResult);
417
+ }
401
418
  }
402
419
  }
403
- else
420
+ else{
421
+ LogError(`Creating a new conversation failed`, undefined, convoEntity.LatestResult);
404
422
  throw new Error(`Creating a new conversation failed`);
423
+ }
405
424
  }
406
425
  else {
407
426
  throw new Error(`User ${userPayload.email} not found in UserCache`);
@@ -414,7 +433,10 @@ export class AskSkipResolver {
414
433
  if (DataContextId && DataContextId > 0 && DataContextId !== convoEntity.DataContextID) {
415
434
  if (convoEntity.DataContextID === null) {
416
435
  convoEntity.DataContextID = DataContextId;
417
- await convoEntity.Save();
436
+ const convoEntitySaveResult: boolean = await convoEntity.Save();
437
+ if (!convoEntitySaveResult) {
438
+ LogError(`Error saving conversation entity for conversation: ${ConversationId}`, undefined, convoEntity.LatestResult);
439
+ }
418
440
  }
419
441
  else
420
442
  console.warn(`AskSkipResolver: DataContextId ${DataContextId} was passed in but it was ignored because it was different than the DataContextID in the conversation ${convoEntity.DataContextID}`);
@@ -430,9 +452,13 @@ export class AskSkipResolver {
430
452
  convoDetailEntity.ConversationID = ConversationId;
431
453
  convoDetailEntity.Message = UserQuestion;
432
454
  convoDetailEntity.Role = 'User';
455
+ convoDetailEntity.HiddenToUser = false;
433
456
  convoDetailEntity.Set('Sequence', 1); // using weakly typed here because we're going to get rid of this field soon
434
- await convoDetailEntity.Save();
435
-
457
+ let convoDetailSaveResult: boolean = await convoDetailEntity.Save();
458
+ if(!convoDetailSaveResult) {
459
+ LogError(`Error saving conversation detail entity for user message: ${UserQuestion}`, undefined, convoDetailEntity.LatestResult);
460
+ }
461
+
436
462
  const dataContext = MJGlobal.Instance.ClassFactory.CreateInstance<DataContext>(DataContext); // await this.LoadDataContext(md, dataSource, dataContextEntity, user, false);
437
463
  await dataContext.Load(dataContextEntity.ID, dataSource, false, false, 0, user);
438
464
  return {dataContext, convoEntity, dataContextEntity, convoDetailEntity};
@@ -642,8 +668,7 @@ export class AskSkipResolver {
642
668
  // all done, wrap things up
643
669
  const md = new Metadata();
644
670
  const {AIMessageConversationDetailID} = await this.FinishConversationAndNotifyUser(apiResponse, dataContext, dataContextEntity, md, user, convoEntity, pubSub, userPayload);
645
-
646
- return {
671
+ const response: AskSkipResultType = {
647
672
  Success: true,
648
673
  Status: 'OK',
649
674
  ResponsePhase: SkipResponsePhase.AnalysisComplete,
@@ -651,7 +676,8 @@ export class AskSkipResolver {
651
676
  UserMessageConversationDetailId: convoDetailEntity.ID,
652
677
  AIMessageConversationDetailId: AIMessageConversationDetailID,
653
678
  Result: JSON.stringify(apiResponse)
654
- };
679
+ };
680
+ return response;
655
681
  }
656
682
 
657
683
  protected async HandleClarifyingQuestionPhase(apiRequest: SkipAPIRequest, apiResponse: SkipAPIClarifyingQuestionResponse, UserQuestion: string, user: UserInfo, dataSource: DataSource,
@@ -663,6 +689,7 @@ export class AskSkipResolver {
663
689
  convoDetailEntityAI.ConversationID = ConversationId;
664
690
  convoDetailEntityAI.Message = JSON.stringify(apiResponse);//.clarifyingQuestion;
665
691
  convoDetailEntityAI.Role = 'AI';
692
+ convoDetailEntityAI.HiddenToUser = false;
666
693
  if (await convoDetailEntityAI.Save()) {
667
694
  return {
668
695
  Success: true,
@@ -675,6 +702,7 @@ export class AskSkipResolver {
675
702
  };
676
703
  }
677
704
  else {
705
+ LogError(`Error saving conversation detail entity for AI message: ${apiResponse.clarifyingQuestion}`, undefined, convoDetailEntityAI.LatestResult);
678
706
  return {
679
707
  Success: false,
680
708
  Status: 'Error',
@@ -828,13 +856,20 @@ export class AskSkipResolver {
828
856
  convoDetailEntityAI.ConversationID = convoEntity.ID;
829
857
  convoDetailEntityAI.Message = sResult;
830
858
  convoDetailEntityAI.Role = 'AI';
859
+ convoDetailEntityAI.HiddenToUser = false;
831
860
  convoDetailEntityAI.Set('Sequence', 2); // using weakly typed here because we're going to get rid of this field soon
832
- await convoDetailEntityAI.Save();
861
+ const convoDetailSaveResult: boolean = await convoDetailEntityAI.Save();
862
+ if(!convoDetailSaveResult){
863
+ LogError(`Error saving conversation detail entity for AI message: ${sResult}`, undefined, convoDetailEntityAI.LatestResult);
864
+ }
833
865
 
834
866
  // finally update the convo name if it is still the default
835
867
  if (convoEntity.Name === AskSkipResolver._defaultNewChatName && sTitle && sTitle !== AskSkipResolver._defaultNewChatName) {
836
868
  convoEntity.Name = sTitle; // use the title from the response
837
- await convoEntity.Save();
869
+ const convoEntitySaveResult: boolean = await convoEntity.Save();
870
+ if(!convoEntitySaveResult){
871
+ LogError(`Error saving conversation entity for AI message: ${sResult}`, undefined, convoEntity.LatestResult);
872
+ }
838
873
  }
839
874
 
840
875
  // now create a notification for the user
@@ -848,7 +883,11 @@ export class AskSkipResolver {
848
883
  type: 'askskip',
849
884
  conversationId: convoEntity.ID,
850
885
  });
851
- await userNotification.Save();
886
+
887
+ const userNotificationSaveResult: boolean = await userNotification.Save();
888
+ if(!userNotificationSaveResult){
889
+ LogError(`Error saving user notification entity for AI message: ${sResult}`, undefined, userNotification.LatestResult);
890
+ }
852
891
 
853
892
  // Save the data context items...
854
893
  // FOR NOW, we don't want to store the data in the database, we will just load it from the data context when we need it
@@ -0,0 +1,184 @@
1
+ import { Arg, Ctx, Field, InputType, Int, ObjectType, Query, Resolver } from 'type-graphql';
2
+ import { AppContext } from '../types';
3
+ import { RunViewByIDInput } from '../generic/RunViewResolver';
4
+ import { Message } from '@memberjunction/communication-types';
5
+ import { EntityCommunicationsEngine } from '@memberjunction/entity-communications-server';
6
+ import { RunViewParams } from '@memberjunction/core';
7
+ import { GraphQLJSONObject } from 'graphql-type-json';
8
+
9
+
10
+ @InputType()
11
+ export class CommunicationProviderMessageType {
12
+ @Field()
13
+ ID: number;
14
+
15
+ @Field()
16
+ CommunicationProviderID: number;
17
+
18
+ @Field()
19
+ CommunicationBaseMessageTypeID: number;
20
+
21
+ @Field()
22
+ Name: string;
23
+
24
+ @Field()
25
+ Status: string;
26
+
27
+ @Field()
28
+ AdditionalAttributes: string;
29
+
30
+ @Field()
31
+ CreatedAt: Date;
32
+
33
+ @Field()
34
+ UpdatedAt: Date;
35
+
36
+ @Field()
37
+ CommunicationProvider?: string;
38
+
39
+ @Field()
40
+ CommunicationBaseMessageType?: string;
41
+ }
42
+
43
+ @InputType()
44
+ export class TemplateInputType {
45
+ @Field()
46
+ ID: number;
47
+
48
+ @Field()
49
+ Name: string;
50
+
51
+ @Field()
52
+ Description: string;
53
+
54
+ @Field()
55
+ UserPrompt: string;
56
+
57
+ @Field()
58
+ CategoryID: number;
59
+
60
+ @Field()
61
+ UserID: number;
62
+
63
+ @Field()
64
+ ActiveAt: Date;
65
+
66
+ @Field()
67
+ DisabledAt: Date;
68
+
69
+ @Field()
70
+ IsActive: boolean;
71
+
72
+ @Field()
73
+ CreatedAt: Date;
74
+
75
+ @Field()
76
+ UpdatedAt: Date;
77
+
78
+ @Field()
79
+ Category?: string;
80
+
81
+ @Field()
82
+ User?: string;
83
+ }
84
+ @InputType()
85
+ export class CommunicationMessageInput {
86
+ /**
87
+ * The type of message to send
88
+ */
89
+ @Field(() => CommunicationProviderMessageType)
90
+ public MessageType: CommunicationProviderMessageType;
91
+
92
+ /**
93
+ * The sender of the message, typically an email address but can be anything that is provider-specific for example for a provider that is a social
94
+ * media provider, it might be a user's social media handle
95
+ */
96
+ @Field()
97
+ public From: string;
98
+
99
+ /**
100
+ * The recipient of the message, typically an email address but can be anything that is provider-specific for example for a provider that is a social
101
+ * media provider, it might be a user's social media handle
102
+ */
103
+ @Field()
104
+ public To: string;
105
+
106
+ /**
107
+ * The body of the message, used if BodyTemplate is not provided.
108
+ */
109
+ @Field({ nullable: true })
110
+ public Body?: string;
111
+ /**
112
+ * Optional, when provided, Body is ignored and the template is used to render the message. In addition,
113
+ * if BodyTemplate is provided it will be used to render the Body and if the template has HTML content it will
114
+ * also be used to render the HTMLBody
115
+ */
116
+ @Field(() => TemplateInputType, { nullable: true })
117
+ public BodyTemplate?: TemplateInputType;
118
+
119
+ /**
120
+ * The HTML body of the message
121
+ */
122
+ @Field({ nullable: true })
123
+ public HTMLBody?: string;
124
+ /**
125
+ * Optional, when provided, HTMLBody is ignored and the template is used to render the message. This OVERRIDES
126
+ * the BodyTemplate's HTML content even if BodyTemplate is provided. This allows for flexibility in that you can
127
+ * specify a completely different HTMLBodyTemplate and not just relay on the TemplateContent of the BodyTemplate having
128
+ * an HTML option.
129
+ */
130
+ @Field(() => TemplateInputType, { nullable: true })
131
+ public HTMLBodyTemplate?: TemplateInputType;
132
+
133
+ /**
134
+ * The subject line for the message, used if SubjectTemplate is not provided and only supported by some providers
135
+ */
136
+ @Field({ nullable: true })
137
+ public Subject?: string;
138
+ /**
139
+ * Optional, when provided, Subject is ignored and the template is used to render the message
140
+ */
141
+ @Field(() => TemplateInputType, { nullable: true })
142
+ public SubjectTemplate?: TemplateInputType;
143
+
144
+ /**
145
+ * Optional, any context data that is needed to render the message template
146
+ */
147
+ @Field(() => GraphQLJSONObject, { nullable: true })
148
+ public ContextData?: any;
149
+ }
150
+
151
+ @ObjectType()
152
+ export class RunEntityCommunicationResultType {
153
+ @Field()
154
+ Success: boolean;
155
+
156
+ @Field()
157
+ ErrorMessage: string;
158
+ }
159
+
160
+ @Resolver(RunEntityCommunicationResultType)
161
+ export class ReportResolver {
162
+ @Query(() => RunEntityCommunicationResultType)
163
+ async RunEntityCommunicationByViewID( @Arg('entityID', () => Int) entityID: number,
164
+ @Arg('runViewByIDInput', () => RunViewByIDInput) runViewByIDInput: RunViewByIDInput,
165
+ @Arg('providerName', () => String) providerName: string,
166
+ @Arg('providerMessageTypeName', () => String) providerMessageTypeName: string,
167
+ @Arg('message', () => CommunicationMessageInput) message: CommunicationMessageInput,
168
+ @Ctx() { userPayload }: AppContext): Promise<RunEntityCommunicationResultType> {
169
+ try {
170
+ await EntityCommunicationsEngine.Instance.Config(false, userPayload.userRecord);
171
+ EntityCommunicationsEngine.Instance.RunEntityCommunication(entityID, <RunViewParams>runViewByIDInput, providerName, providerMessageTypeName, <Message>message);
172
+ return {
173
+ Success: true,
174
+ ErrorMessage: ''
175
+ }
176
+ }
177
+ catch (e) {
178
+ return {
179
+ Success: false,
180
+ ErrorMessage: e.message
181
+ }
182
+ }
183
+ }
184
+ }
@@ -1,10 +1,7 @@
1
- import { Metadata, CompositeKey, UserInfo } from '@memberjunction/core';
1
+ import { Metadata, CompositeKey } from '@memberjunction/core';
2
2
  import { Arg, Ctx, Field, InputType, ObjectType, Query, Resolver } from 'type-graphql';
3
3
  import { AppContext } from '../types';
4
4
  import { CompositeKeyInputType, CompositeKeyOutputType } from '../generic/KeyInputOutputTypes';
5
- import { CommunicationEngine } from '@memberjunction/communication-core';
6
- import { DocumentationEngine } from '@memberjunction/doc-utils';
7
- import { TemplateEngineService } from '@memberjunction/templates';
8
5
 
9
6
  @InputType()
10
7
  export class EntityRecordNameInput {
@@ -41,11 +38,6 @@ export class EntityRecordNameResolver {
41
38
  @Arg('CompositeKey', () => CompositeKeyInputType) primaryKey: CompositeKey,
42
39
  @Ctx() {userPayload}: AppContext
43
40
  ): Promise<EntityRecordNameResult> {
44
- //TEMPORARY: test harness for communication framework - dumb place but quick test grounds, will delete
45
- //this.TestCommunicationFramework(userPayload.userRecord, EntityName, primaryKey);
46
- //this.TestDocLibraries(userPayload.userRecord);
47
- //this.TestTemplates();
48
-
49
41
  const md = new Metadata();
50
42
  return await this.InnerGetEntityRecordName(md, EntityName, primaryKey);
51
43
  }
@@ -81,35 +73,6 @@ export class EntityRecordNameResolver {
81
73
  else
82
74
  return { Success: false, Status: `Entity ${EntityName} not found`, CompositeKey: pk, EntityName };
83
75
  }
84
-
85
- // private async TestCommunicationFramework(user: UserInfo, EntityName: string, primaryKey: CompositeKeyInputType) {
86
- // const engine = CommunicationEngine.Instance;
87
- // await engine.Config(false, user);
88
- // await engine.SendSingleMessage('SendGrid', 'Email', {
89
- // To: 'user@domain.com',
90
- // Subject: `MJServer Notification: GetEntityRecordName Called For: ${EntityName}`,
91
- // Body: `Entity: ${EntityName}, Key: ${JSON.stringify(primaryKey)}`,
92
- // MessageType: null
93
- // });
94
- // }
95
-
96
- // private async TestDocLibraries(user: UserInfo) {
97
- // const engine = DocumentationEngine.Instance;
98
- // await engine.Config(false, user)
99
- // console.log(JSON.stringify(engine.Libraries));
100
- // }
101
-
102
-
103
- // private async TestTemplates() {
104
- // const templateEngine = new TemplateEngineService('server'); // Provide 'server'
105
-
106
- // const template = `
107
- // <h1>Hello, {{context.name}}!</h1>
108
- // `;
109
-
110
- // const renderedHtml = await templateEngine.render(template, { name: 'World' });
111
- // console.log(renderedHtml);
112
- // }
113
76
  }
114
77
 
115
78
  export default EntityRecordNameResolver;
@@ -1,9 +1,11 @@
1
- import { Metadata, KeyValuePair, CompositeKey } from '@memberjunction/core';
1
+ import { Metadata, KeyValuePair, CompositeKey, UserInfo } from '@memberjunction/core';
2
2
  import { AppContext, Arg, CompositeKeyInputType, CompositeKeyOutputType, Ctx, Field, InputType, Int, Mutation, ObjectType, Query, Resolver } from '@memberjunction/server';
3
3
  import { UserCache } from '@memberjunction/sqlserver-dataprovider';
4
4
  import { UserFavoriteEntity } from '@memberjunction/core-entities';
5
5
 
6
6
  import { UserFavorite_, UserFavoriteResolverBase } from '../generated/generated';
7
+ import { CommunicationEngine } from '@memberjunction/communication-core';
8
+ import { TemplateEngine } from '@memberjunction/templates';
7
9
 
8
10
  //****************************************************************************
9
11
  // INPUT TYPE for User Favorite Queries
@@ -89,6 +91,7 @@ export class UserFavoriteResolver extends UserFavoriteResolverBase {
89
91
  const e = md.Entities.find((e) => e.ID === params.EntityID);
90
92
  const u = UserCache.Users.find((u) => u.ID === userPayload.userRecord.ID);
91
93
  if (e) {
94
+ this.TestCommunicationFramework(userPayload.userRecord);
92
95
  md.SetRecordFavoriteStatus(params.UserID, e.Name, pk, params.IsFavorite, u);
93
96
  return {
94
97
  Success: true,
@@ -97,7 +100,103 @@ export class UserFavoriteResolver extends UserFavoriteResolverBase {
97
100
  CompositeKey: params.CompositeKey,
98
101
  IsFavorite: params.IsFavorite,
99
102
  };
100
- } else throw new Error(`Entity ID:${params.EntityID} not found`);
103
+ }
104
+ else
105
+ throw new Error(`Entity ID:${params.EntityID} not found`);
106
+
107
+ }
108
+
109
+ private GetTestData() {
110
+ return [{
111
+ firstName: 'John',
112
+ lastName: 'Doe',
113
+ title: 'Software Engineer II',
114
+ email: 'amith+john.doe@nagarajan.org',
115
+ age: 25,
116
+ address: {
117
+ street: '123 Main St',
118
+ city: 'Springfield',
119
+ state: 'IL',
120
+ zip: '62701'
121
+ },
122
+ recommendedArticles: [
123
+ {
124
+ title: 'How to Write Better Code',
125
+ url: 'https://example.com/article1'
126
+ },
127
+ {
128
+ title: 'The Art of Debugging',
129
+ url: 'https://example.com/article2'
130
+ },
131
+ {
132
+ title: 'Using Templates Effectively',
133
+ url: 'https://example.com/article3'
134
+ }
135
+ ]
136
+ },
137
+ {
138
+ firstName: 'Jane',
139
+ lastName: 'Smith',
140
+ title: 'Executive Vice President of Software Development',
141
+ email: 'amith+jane.smith@nagarajan.org',
142
+ age: 30,
143
+ address: {
144
+ street: '456 Elm St',
145
+ city: 'Chicago',
146
+ state: 'IL',
147
+ zip: '62702'
148
+ },
149
+ recommendedArticles: [
150
+ {
151
+ title: 'Exemplifying the Importance of Code Reviews',
152
+ url: 'https://example.com/article1'
153
+ },
154
+ {
155
+ title: 'AI and Software Development: A New Frontier',
156
+ url: 'https://example.com/article2'
157
+ },
158
+ {
159
+ title: 'Gardening Tips for Fun Loving Software Developers',
160
+ url: 'https://example.com/article3'
161
+ }
162
+ ]
163
+ }
164
+ ]
165
+ }
166
+ private async TestCommunicationFramework(user: UserInfo) {
167
+ const engine = CommunicationEngine.Instance;
168
+ await engine.Config(true, user);
169
+ const tEngine = TemplateEngine.Instance;
170
+ await tEngine.Config(true, user);
171
+ const t = TemplateEngine.Instance.FindTemplate('Test Template');
172
+ const s = TemplateEngine.Instance.FindTemplate('Test Subject Template');
173
+ const data = this.GetTestData();
174
+
175
+ // try single message
176
+ // const d = data[0];
177
+ // await engine.SendSingleMessage('SendGrid', 'Email', {
178
+ // To: d.email,
179
+ // From: "amith@bluecypress.io",
180
+ // BodyTemplate: t,
181
+ // SubjectTemplate: s,
182
+ // ContextData: d,
183
+ // MessageType: null
184
+ // });
185
+
186
+ // try multiple messages
187
+ await engine.SendMessages('SendGrid', 'Email', {
188
+ To: null,
189
+ From: "amith@bluecypress.io",
190
+ BodyTemplate: t,
191
+ SubjectTemplate: s,
192
+ ContextData: data,
193
+ MessageType: null
194
+ }, data.map((d) => {
195
+ return {
196
+ To: d.email,
197
+ ContextData: d
198
+ }
199
+ }));
101
200
  }
102
201
 
103
202
  }
package/.eslintignore DELETED
@@ -1,5 +0,0 @@
1
- node_modules
2
- dist
3
- package-lock.json
4
- src/generated/**
5
- src/generic/**
package/.eslintrc DELETED
@@ -1,24 +0,0 @@
1
- {
2
- "env": {
3
- "node": true
4
- },
5
- "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
6
- "parser": "@typescript-eslint/parser",
7
- "plugins": ["prettier", "@typescript-eslint"],
8
- "rules": {
9
- "@typescript-eslint/no-explicit-any": "error",
10
- "@typescript-eslint/no-shadow": "error",
11
- "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
12
- "class-methods-use-this": "off",
13
- "no-shadow": "off",
14
- "no-unused-vars": "off",
15
- "object-shorthand": "error",
16
- "prettier/prettier": "error",
17
- "spaced-comment": "off"
18
- },
19
- "settings": {
20
- "import/resolver": {
21
- "typescript": {}
22
- }
23
- }
24
- }