@memberjunction/server 1.5.1 → 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.
- package/CHANGELOG.json +325 -1
- package/CHANGELOG.md +67 -2
- package/dist/apolloServer/index.d.ts.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/entitySubclasses/EntityBehavior.server.js +1 -1
- package/dist/entitySubclasses/EntityBehavior.server.js.map +1 -1
- package/dist/entitySubclasses/userViewEntity.server.js +2 -2
- package/dist/entitySubclasses/userViewEntity.server.js.map +1 -1
- package/dist/generated/generated.d.ts +557 -43
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +2992 -241
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +16 -6
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +52 -13
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/dist/resolvers/EntityCommunicationsResolver.d.ts +49 -0
- package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -0
- package/dist/resolvers/EntityCommunicationsResolver.js +218 -0
- package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -0
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/UserFavoriteResolver.d.ts +2 -0
- package/dist/resolvers/UserFavoriteResolver.d.ts.map +1 -1
- package/dist/resolvers/UserFavoriteResolver.js +82 -0
- package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
- package/package.json +36 -43
- package/src/entitySubclasses/EntityBehavior.server.ts +1 -1
- package/src/entitySubclasses/userViewEntity.server.ts +2 -2
- package/src/generated/generated.ts +2146 -205
- package/src/generic/ResolverBase.ts +18 -8
- package/src/resolvers/AskSkipResolver.ts +55 -16
- package/src/resolvers/EntityCommunicationsResolver.ts +184 -0
- package/src/resolvers/EntityRecordNameResolver.ts +1 -38
- package/src/resolvers/UserFavoriteResolver.ts +101 -2
- package/.eslintignore +0 -5
- package/.eslintrc +0 -24
|
@@ -435,7 +435,10 @@ export class ResolverBase {
|
|
|
435
435
|
}
|
|
436
436
|
}
|
|
437
437
|
else {
|
|
438
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
}
|
|
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
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
|
-
}
|