@memberjunction/server 2.75.0 → 2.77.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 (127) hide show
  1. package/README.md +61 -2
  2. package/dist/apolloServer/index.d.ts.map +1 -1
  3. package/dist/apolloServer/index.js +1 -3
  4. package/dist/apolloServer/index.js.map +1 -1
  5. package/dist/auth/exampleNewUserSubClass.d.ts +1 -1
  6. package/dist/auth/exampleNewUserSubClass.d.ts.map +1 -1
  7. package/dist/auth/exampleNewUserSubClass.js +1 -1
  8. package/dist/auth/exampleNewUserSubClass.js.map +1 -1
  9. package/dist/auth/newUsers.js.map +1 -1
  10. package/dist/context.d.ts +5 -0
  11. package/dist/context.d.ts.map +1 -1
  12. package/dist/context.js +34 -3
  13. package/dist/context.js.map +1 -1
  14. package/dist/entitySubclasses/entityPermissions.server.d.ts +2 -2
  15. package/dist/entitySubclasses/entityPermissions.server.d.ts.map +1 -1
  16. package/dist/entitySubclasses/entityPermissions.server.js +2 -2
  17. package/dist/entitySubclasses/entityPermissions.server.js.map +1 -1
  18. package/dist/generated/generated.d.ts +1787 -1628
  19. package/dist/generated/generated.d.ts.map +1 -1
  20. package/dist/generated/generated.js +5871 -4367
  21. package/dist/generated/generated.js.map +1 -1
  22. package/dist/generic/ResolverBase.d.ts +20 -21
  23. package/dist/generic/ResolverBase.d.ts.map +1 -1
  24. package/dist/generic/ResolverBase.js +75 -59
  25. package/dist/generic/ResolverBase.js.map +1 -1
  26. package/dist/generic/RunViewResolver.d.ts +8 -8
  27. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  28. package/dist/generic/RunViewResolver.js +50 -48
  29. package/dist/generic/RunViewResolver.js.map +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/resolvers/AskSkipResolver.d.ts +6 -6
  35. package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
  36. package/dist/resolvers/AskSkipResolver.js +33 -21
  37. package/dist/resolvers/AskSkipResolver.js.map +1 -1
  38. package/dist/resolvers/CreateQueryResolver.d.ts +12 -3
  39. package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
  40. package/dist/resolvers/CreateQueryResolver.js +121 -65
  41. package/dist/resolvers/CreateQueryResolver.js.map +1 -1
  42. package/dist/resolvers/EntityResolver.d.ts +1 -2
  43. package/dist/resolvers/EntityResolver.d.ts.map +1 -1
  44. package/dist/resolvers/EntityResolver.js +17 -9
  45. package/dist/resolvers/EntityResolver.js.map +1 -1
  46. package/dist/resolvers/FileCategoryResolver.d.ts +1 -1
  47. package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -1
  48. package/dist/resolvers/FileCategoryResolver.js +9 -9
  49. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  50. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  51. package/dist/resolvers/FileResolver.js +3 -1
  52. package/dist/resolvers/FileResolver.js.map +1 -1
  53. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  54. package/dist/resolvers/GetDataResolver.js +3 -0
  55. package/dist/resolvers/GetDataResolver.js.map +1 -1
  56. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  57. package/dist/resolvers/MergeRecordsResolver.js +2 -1
  58. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  59. package/dist/resolvers/QueryResolver.d.ts +4 -4
  60. package/dist/resolvers/QueryResolver.js +18 -18
  61. package/dist/resolvers/ReportResolver.js.map +1 -1
  62. package/dist/resolvers/SyncDataResolver.d.ts +8 -8
  63. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  64. package/dist/resolvers/SyncDataResolver.js +19 -19
  65. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  66. package/dist/resolvers/SyncRolesUsersResolver.d.ts +10 -10
  67. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  68. package/dist/resolvers/SyncRolesUsersResolver.js +19 -19
  69. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  70. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  71. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  72. package/dist/resolvers/UserFavoriteResolver.d.ts +2 -2
  73. package/dist/resolvers/UserFavoriteResolver.d.ts.map +1 -1
  74. package/dist/resolvers/UserFavoriteResolver.js +7 -4
  75. package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
  76. package/dist/resolvers/UserResolver.d.ts +3 -3
  77. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  78. package/dist/resolvers/UserResolver.js +10 -6
  79. package/dist/resolvers/UserResolver.js.map +1 -1
  80. package/dist/resolvers/UserViewResolver.d.ts +2 -2
  81. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  82. package/dist/resolvers/UserViewResolver.js +11 -6
  83. package/dist/resolvers/UserViewResolver.js.map +1 -1
  84. package/dist/scheduler/LearningCycleScheduler.d.ts.map +1 -1
  85. package/dist/scheduler/LearningCycleScheduler.js +7 -1
  86. package/dist/scheduler/LearningCycleScheduler.js.map +1 -1
  87. package/dist/types.d.ts +7 -4
  88. package/dist/types.d.ts.map +1 -1
  89. package/dist/types.js +4 -0
  90. package/dist/types.js.map +1 -1
  91. package/dist/util.d.ts +8 -1
  92. package/dist/util.d.ts.map +1 -1
  93. package/dist/util.js +28 -0
  94. package/dist/util.js.map +1 -1
  95. package/package.json +34 -34
  96. package/src/apolloServer/index.ts +3 -3
  97. package/src/auth/exampleNewUserSubClass.ts +3 -2
  98. package/src/auth/newUsers.ts +1 -1
  99. package/src/context.ts +49 -9
  100. package/src/entitySubclasses/entityPermissions.server.ts +3 -3
  101. package/src/generated/generated.ts +5506 -4368
  102. package/src/generic/ResolverBase.ts +103 -86
  103. package/src/generic/RunViewResolver.ts +55 -54
  104. package/src/index.ts +1 -1
  105. package/src/resolvers/AskSkipResolver.ts +44 -23
  106. package/src/resolvers/CreateQueryResolver.ts +104 -62
  107. package/src/resolvers/EntityResolver.ts +18 -9
  108. package/src/resolvers/FileCategoryResolver.ts +12 -9
  109. package/src/resolvers/FileResolver.ts +4 -2
  110. package/src/resolvers/GetDataResolver.ts +3 -0
  111. package/src/resolvers/MergeRecordsResolver.ts +2 -1
  112. package/src/resolvers/QueryResolver.ts +14 -14
  113. package/src/resolvers/ReportResolver.ts +1 -1
  114. package/src/resolvers/SyncDataResolver.ts +21 -21
  115. package/src/resolvers/SyncRolesUsersResolver.ts +24 -21
  116. package/src/resolvers/TransactionGroupResolver.ts +1 -1
  117. package/src/resolvers/UserFavoriteResolver.ts +7 -5
  118. package/src/resolvers/UserResolver.ts +10 -6
  119. package/src/resolvers/UserViewResolver.ts +13 -7
  120. package/src/scheduler/LearningCycleScheduler.ts +10 -4
  121. package/src/types.ts +14 -4
  122. package/src/util.ts +45 -2
  123. package/dist/apolloServer/TransactionPlugin.d.ts +0 -4
  124. package/dist/apolloServer/TransactionPlugin.d.ts.map +0 -1
  125. package/dist/apolloServer/TransactionPlugin.js +0 -46
  126. package/dist/apolloServer/TransactionPlugin.js.map +0 -1
  127. package/src/apolloServer/TransactionPlugin.ts +0 -53
package/src/index.ts CHANGED
@@ -93,7 +93,7 @@ export * from './resolvers/GetDataResolver.js';
93
93
  export * from './resolvers/GetDataContextDataResolver.js';
94
94
  export * from './resolvers/TransactionGroupResolver.js';
95
95
  export * from './resolvers/CreateQueryResolver.js';
96
- export { GetReadOnlyDataSource, GetReadWriteDataSource } from './util.js';
96
+ export { GetReadOnlyDataSource, GetReadWriteDataSource, GetReadWriteProvider, GetReadOnlyProvider } from './util.js';
97
97
 
98
98
  export * from './generated/generated.js';
99
99
 
@@ -1,5 +1,5 @@
1
1
  import { Arg, Ctx, Field, Mutation, ObjectType, PubSub, PubSubEngine, Query, Resolver } from 'type-graphql';
2
- import { LogError, LogStatus, Metadata, RunView, UserInfo, CompositeKey, EntityFieldInfo, EntityInfo, EntityRelationshipInfo } from '@memberjunction/core';
2
+ import { LogError, LogStatus, Metadata, RunView, UserInfo, CompositeKey, EntityFieldInfo, EntityInfo, EntityRelationshipInfo, EntitySaveOptions, EntityDeleteOptions } from '@memberjunction/core';
3
3
  import { AppContext, UserPayload, MJ_SERVER_EVENT_CODE } from '../types.js';
4
4
  import { BehaviorSubject } from 'rxjs';
5
5
  import { take } from 'rxjs/operators';
@@ -390,6 +390,7 @@ export class AskSkipResolver {
390
390
  const ck = new CompositeKey();
391
391
  ck.KeyValuePairs = compositeKey.KeyValuePairs;
392
392
  dci.RecordID = ck.Values();
393
+
393
394
  let dciSaveResult: boolean = await dci.Save();
394
395
  if (!dciSaveResult) {
395
396
  LogError(`Error saving DataContextItemEntity for record chat: ${EntityName} ${ck.Values()}`, undefined, dci.LatestResult);
@@ -415,7 +416,7 @@ export class AskSkipResolver {
415
416
  conversationDetailID: convoDetailEntity.ID,
416
417
  });
417
418
 
418
- return this.handleSimpleSkipChatPostRequest(input, convoEntity, convoDetailEntity, true, user);
419
+ return this.handleSimpleSkipChatPostRequest(input, convoEntity, convoDetailEntity, true, user, userPayload);
419
420
  }
420
421
 
421
422
  /**
@@ -536,7 +537,7 @@ export class AskSkipResolver {
536
537
  }
537
538
  else {
538
539
  // Make the API request
539
- const response = await this.handleSimpleSkipLearningPostRequest(input, user, learningCycleId, agentID);
540
+ const response = await this.handleSimpleSkipLearningPostRequest(input, user, learningCycleId, agentID, userPayload);
540
541
 
541
542
  // Update learning cycle to completed
542
543
  const endTime = new Date();
@@ -594,7 +595,8 @@ export class AskSkipResolver {
594
595
  input: SkipAPILearningCycleRequest,
595
596
  user: UserInfo,
596
597
  learningCycleId: string,
597
- agentID: string
598
+ agentID: string,
599
+ userPayload: UserPayload
598
600
  ): Promise<SkipAPILearningCycleResponse> {
599
601
  const skipConfigInfo = configInfo.askSkip;
600
602
  LogStatus(` >>> HandleSimpleSkipLearningPostRequest Sending request to Skip API: ${skipConfigInfo.learningCycleURL}`);
@@ -608,7 +610,7 @@ export class AskSkipResolver {
608
610
 
609
611
  // Process any note changes, if any
610
612
  if (apiResponse.noteChanges && apiResponse.noteChanges.length > 0) {
611
- await this.processLearningCycleNoteChanges(apiResponse.noteChanges, agentID, user);
613
+ await this.processLearningCycleNoteChanges(apiResponse.noteChanges, agentID, user, userPayload);
612
614
  }
613
615
 
614
616
  // Not yet implemented
@@ -653,7 +655,8 @@ export class AskSkipResolver {
653
655
  convoEntity: ConversationEntity = null,
654
656
  convoDetailEntity: ConversationDetailEntity = null,
655
657
  createAIMessageConversationDetail: boolean = false,
656
- user: UserInfo = null
658
+ user: UserInfo = null,
659
+ userPayload: UserPayload = null
657
660
  ): Promise<AskSkipResultType> {
658
661
  const skipConfigInfo = configInfo.askSkip;
659
662
  LogStatus(` >>> HandleSimpleSkipChatPostRequest Sending request to Skip API: ${skipConfigInfo.chatURL}`);
@@ -665,7 +668,7 @@ export class AskSkipResolver {
665
668
  // the last object in the response array is the final response from the Skip API
666
669
  const apiResponse = <SkipAPIResponse>response[response.length - 1].value;
667
670
  const AIMessageConversationDetailID = createAIMessageConversationDetail && convoEntity
668
- ? await this.CreateAIMessageConversationDetail(apiResponse, convoEntity.ID, user)
671
+ ? await this.CreateAIMessageConversationDetail(apiResponse, convoEntity.ID, user, userPayload)
669
672
  : '';
670
673
  // const apiResponse = <SkipAPIResponse>response.data;
671
674
  LogStatus(` Skip API response: ${apiResponse.responsePhase}`);
@@ -681,7 +684,7 @@ export class AskSkipResolver {
681
684
  } else {
682
685
  // Set conversation status to Available on failure so user can try again (if conversation exists)
683
686
  if (convoEntity) {
684
- await this.setConversationStatus(convoEntity, 'Available');
687
+ await this.setConversationStatus(convoEntity, 'Available', userPayload);
685
688
  }
686
689
 
687
690
  return {
@@ -697,7 +700,7 @@ export class AskSkipResolver {
697
700
  } catch (error) {
698
701
  // Set conversation status to Available on error so user can try again (if conversation exists)
699
702
  if (convoEntity) {
700
- await this.setConversationStatus(convoEntity, 'Available');
703
+ await this.setConversationStatus(convoEntity, 'Available', userPayload);
701
704
  }
702
705
 
703
706
  // Log the error for debugging
@@ -720,7 +723,8 @@ export class AskSkipResolver {
720
723
  protected async processLearningCycleNoteChanges(
721
724
  noteChanges: SkipLearningCycleNoteChange[],
722
725
  agentID: string,
723
- user: UserInfo
726
+ user: UserInfo,
727
+ userPayload: UserPayload
724
728
  ): Promise<void> {
725
729
  const md = new Metadata();
726
730
 
@@ -739,9 +743,9 @@ export class AskSkipResolver {
739
743
  await Promise.all(validNoteChanges.map(async (change) => {
740
744
  try {
741
745
  if (change.changeType === 'add' || change.changeType === 'update') {
742
- await this.processAddOrUpdateSkipNote(change, agentID, user);
746
+ await this.processAddOrUpdateSkipNote(change, agentID, user, userPayload);
743
747
  } else if (change.changeType === 'delete') {
744
- await this.processDeleteSkipNote(change, user);
748
+ await this.processDeleteSkipNote(change, user, userPayload);
745
749
  }
746
750
  } catch (e) {
747
751
  LogError(`Error processing note change: ${e}`);
@@ -758,7 +762,7 @@ export class AskSkipResolver {
758
762
  * @param user User context for the operation
759
763
  * @returns Whether the operation was successful
760
764
  */
761
- protected async processAddOrUpdateSkipNote(change: SkipLearningCycleNoteChange, agentID: string, user: UserInfo): Promise<boolean> {
765
+ protected async processAddOrUpdateSkipNote(change: SkipLearningCycleNoteChange, agentID: string, user: UserInfo, userPayload: UserPayload): Promise<boolean> {
762
766
  try {
763
767
  // Get the note entity object
764
768
  const md = new Metadata();
@@ -805,7 +809,7 @@ export class AskSkipResolver {
805
809
  * @param user User context for the operation
806
810
  * @returns Whether the deletion was successful
807
811
  */
808
- protected async processDeleteSkipNote(change: SkipLearningCycleNoteChange, user: UserInfo): Promise<boolean> {
812
+ protected async processDeleteSkipNote(change: SkipLearningCycleNoteChange, user: UserInfo, userPayload: UserPayload): Promise<boolean> {
809
813
  // Get the note entity object
810
814
  const md = new Metadata();
811
815
  const noteEntity = await md.GetEntityObject<AIAgentNoteEntity>('AI Agent Notes', user);
@@ -843,7 +847,7 @@ cycle.`);
843
847
  * @param user User context for the operation
844
848
  * @returns ID of the created conversation detail, or empty string if creation failed
845
849
  */
846
- protected async CreateAIMessageConversationDetail(apiResponse: SkipAPIResponse, conversationID: string, user: UserInfo): Promise<string> {
850
+ protected async CreateAIMessageConversationDetail(apiResponse: SkipAPIResponse, conversationID: string, user: UserInfo, userPayload: UserPayload): Promise<string> {
847
851
  const md = new Metadata();
848
852
  const convoDetailEntityAI = <ConversationDetailEntity>await md.GetEntityObject('Conversation Details', user);
849
853
  convoDetailEntityAI.NewRecord();
@@ -853,6 +857,7 @@ cycle.`);
853
857
  const lastSystemMessage = systemMessages[systemMessages.length - 1];
854
858
  convoDetailEntityAI.Message = lastSystemMessage?.content;
855
859
  convoDetailEntityAI.Role = 'AI';
860
+
856
861
  if (await convoDetailEntityAI.Save()) {
857
862
  return convoDetailEntityAI.ID;
858
863
  } else {
@@ -1287,7 +1292,7 @@ cycle.`);
1287
1292
  await dataContext.Load(DataContextId, dataSource, true, false, 0, user);
1288
1293
  const input = <SkipAPIRunScriptRequest>await this.buildSkipChatAPIRequest([], '', dataContext, 'run_existing_script', false, false, false, false, user, dataSource, false, false);
1289
1294
  input.scriptText = ScriptText;
1290
- return this.handleSimpleSkipChatPostRequest(input);
1295
+ return this.handleSimpleSkipChatPostRequest(input, undefined, undefined, undefined, userPayload.userRecord, userPayload);
1291
1296
  }
1292
1297
 
1293
1298
  /**
@@ -1366,7 +1371,7 @@ cycle.`);
1366
1371
  );
1367
1372
 
1368
1373
  // Set the conversation status to 'Processing' when a request is initiated
1369
- await this.setConversationStatus(convoEntity, 'Processing');
1374
+ await this.setConversationStatus(convoEntity, 'Processing', userPayload);
1370
1375
 
1371
1376
  // now load up the messages. We will load up ALL of the messages for this conversation, and then pass them to the Skip API
1372
1377
  const messages: SkipMessage[] = await this.LoadConversationDetailsIntoSkipMessages(
@@ -1455,7 +1460,19 @@ cycle.`);
1455
1460
  updatedAt: f.__mj_UpdatedAt,
1456
1461
  };
1457
1462
  }),
1458
- };
1463
+ params: q.Parameters.map((p) => {
1464
+ return {
1465
+ id: p.ID,
1466
+ name: p.Name,
1467
+ description: p.Description,
1468
+ type: p.Type,
1469
+ isRequired: p.IsRequired,
1470
+ defaultValue: p.DefaultValue,
1471
+ createdAt: p.__mj_CreatedAt,
1472
+ updatedAt: p.__mj_UpdatedAt,
1473
+ };
1474
+ })
1475
+ }
1459
1476
  });
1460
1477
  }
1461
1478
 
@@ -1986,6 +2003,7 @@ cycle.`);
1986
2003
  convoDetailEntity.Message = UserQuestion;
1987
2004
  convoDetailEntity.Role = 'User';
1988
2005
  convoDetailEntity.HiddenToUser = false;
2006
+
1989
2007
  let convoDetailSaveResult: boolean = await convoDetailEntity.Save();
1990
2008
  if (!convoDetailSaveResult) {
1991
2009
  LogError(`Error saving conversation detail entity for user message: ${UserQuestion}`, undefined, convoDetailEntity.LatestResult);
@@ -2162,7 +2180,7 @@ cycle.`);
2162
2180
 
2163
2181
  if (conversationDetailCount > 10) {
2164
2182
  // Set status of conversation to Available since we still want to allow the user to ask questions
2165
- await this.setConversationStatus(convoEntity, 'Available');
2183
+ await this.setConversationStatus(convoEntity, 'Available', userPayload);
2166
2184
 
2167
2185
  // At this point it is likely that we are stuck in a loop, so we stop here
2168
2186
  pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
@@ -2222,7 +2240,7 @@ cycle.`);
2222
2240
  );
2223
2241
  } catch (error) {
2224
2242
  // Set conversation status to Available on error so user can try again
2225
- await this.setConversationStatus(convoEntity, 'Available');
2243
+ await this.setConversationStatus(convoEntity, 'Available', userPayload);
2226
2244
 
2227
2245
  // Log the error for debugging
2228
2246
  LogError(`Error in HandleSkipChatRequest sendPostRequest: ${error}`);
@@ -2305,7 +2323,7 @@ cycle.`);
2305
2323
  }
2306
2324
  } else {
2307
2325
  // Set status of conversation to Available since we still want to allow the user to ask questions
2308
- await this.setConversationStatus(convoEntity, 'Available');
2326
+ await this.setConversationStatus(convoEntity, 'Available', userPayload);
2309
2327
 
2310
2328
  pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
2311
2329
  message: JSON.stringify({
@@ -2477,7 +2495,7 @@ cycle.`);
2477
2495
  convoDetailEntityAI.CompletionTime = endTime.getTime() - startTime.getTime();
2478
2496
 
2479
2497
  // Set conversation status back to Available since we need user input for the clarifying question
2480
- await this.setConversationStatus(convoEntity, 'Available');
2498
+ await this.setConversationStatus(convoEntity, 'Available', userPayload);
2481
2499
 
2482
2500
  if (await convoDetailEntityAI.Save()) {
2483
2501
  return {
@@ -2765,6 +2783,7 @@ cycle.`);
2765
2783
  artifactVersionEntity.ConversationArtifactID = artifactId;
2766
2784
  artifactVersionEntity.Version = newVersion;
2767
2785
  artifactVersionEntity.Configuration = sResult; // store the full response here
2786
+
2768
2787
  if (await artifactVersionEntity.Save()) {
2769
2788
  // success saving the new version, set the artifactVersionId
2770
2789
  artifactVersionId = artifactVersionEntity.ID;
@@ -2792,6 +2811,7 @@ cycle.`);
2792
2811
  convoDetailEntityAI.ArtifactVersionID = artifactVersionId;
2793
2812
  }
2794
2813
  }
2814
+
2795
2815
  const convoDetailSaveResult: boolean = await convoDetailEntityAI.Save();
2796
2816
  if (!convoDetailSaveResult) {
2797
2817
  LogError(`Error saving conversation detail entity for AI message: ${sResult}`, undefined, convoDetailEntityAI.LatestResult);
@@ -2872,9 +2892,10 @@ cycle.`);
2872
2892
  };
2873
2893
  }
2874
2894
 
2875
- private async setConversationStatus(convoEntity: ConversationEntity, status: 'Processing' | 'Available'): Promise<boolean> {
2895
+ private async setConversationStatus(convoEntity: ConversationEntity, status: 'Processing' | 'Available', userPayload: UserPayload): Promise<boolean> {
2876
2896
  if (convoEntity.Status !== status) {
2877
2897
  convoEntity.Status = status;
2898
+
2878
2899
  const convoSaveResult = await convoEntity.Save();
2879
2900
  if (!convoSaveResult) {
2880
2901
  LogError(`Error updating conversation status to '${status}'`, undefined, convoEntity.LatestResult);
@@ -1,8 +1,11 @@
1
- import { Arg, Ctx, Field, InputType, Mutation, ObjectType, registerEnumType } from 'type-graphql';
2
- import { AppContext } from '../types.js';
3
- import { LogError, Metadata, RunView, UserInfo } from '@memberjunction/core';
1
+ import { Arg, Ctx, Field, InputType, Mutation, ObjectType, registerEnumType, Resolver, PubSub, PubSubEngine } from 'type-graphql';
2
+ import { AppContext, UserPayload } from '../types.js';
3
+ import { LogError, Metadata, RunView, UserInfo, CompositeKey, EntitySaveOptions } from '@memberjunction/core';
4
4
  import { RequireSystemUser } from '../directives/RequireSystemUser.js';
5
- import { QueryEntity, QueryCategoryEntity } from '@memberjunction/core-entities';
5
+ import { QueryCategoryEntity } from '@memberjunction/core-entities';
6
+ import { QueryResolver } from '../generated/generated.js';
7
+ import { GetReadWriteProvider } from '../util.js';
8
+ import { DeleteOptionsInput } from '../generic/DeleteOptionsInput.js';
6
9
 
7
10
  /**
8
11
  * Query status enumeration for GraphQL
@@ -20,7 +23,7 @@ registerEnumType(QueryStatus, {
20
23
  });
21
24
 
22
25
  @InputType()
23
- export class CreateQueryInputType {
26
+ export class CreateQuerySystemUserInput {
24
27
  @Field(() => String)
25
28
  Name!: string;
26
29
 
@@ -73,88 +76,127 @@ export class CreateQueryResultType {
73
76
  QueryData?: string;
74
77
  }
75
78
 
76
- export class CreateQueryResolver {
79
+ @ObjectType()
80
+ export class DeleteQueryResultType {
81
+ @Field(() => Boolean)
82
+ Success!: boolean;
83
+
84
+ @Field(() => String, { nullable: true })
85
+ ErrorMessage?: string;
86
+
87
+ @Field(() => String, { nullable: true })
88
+ QueryData?: string;
89
+ }
90
+
91
+ @Resolver()
92
+ export class QueryResolverExtended extends QueryResolver {
77
93
  /**
78
94
  * Creates a new query with the provided attributes. This mutation is restricted to system users only.
79
- * @param input - CreateQueryInputType containing all the query attributes
95
+ * @param input - CreateQuerySystemUserInput containing all the query attributes
80
96
  * @param context - Application context containing user information
81
97
  * @returns CreateQueryResultType with success status and query data
82
98
  */
83
99
  @RequireSystemUser()
84
100
  @Mutation(() => CreateQueryResultType)
85
- async CreateQuery(
86
- @Arg('input', () => CreateQueryInputType) input: CreateQueryInputType,
87
- @Ctx() context: AppContext
101
+ async CreateQuerySystemUser(
102
+ @Arg('input', () => CreateQuerySystemUserInput) input: CreateQuerySystemUserInput,
103
+ @Ctx() context: AppContext,
104
+ @PubSub() pubSub: PubSubEngine
88
105
  ): Promise<CreateQueryResultType> {
89
106
  try {
90
- const md = new Metadata();
91
- const newQuery = await md.GetEntityObject<QueryEntity>("Queries", context.userPayload.userRecord);
92
-
93
107
  // Handle CategoryPath if provided
94
108
  let finalCategoryID = input.CategoryID;
95
109
  if (input.CategoryPath) {
96
- finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, md, context.userPayload.userRecord);
110
+ const md = new Metadata();
111
+ finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, md, context.userPayload.userRecord, context.userPayload);
97
112
  }
98
113
 
99
- // Populate the query fields from input
100
- newQuery.Name = input.Name;
101
-
102
- if (finalCategoryID != null) {
103
- newQuery.CategoryID = finalCategoryID;
104
- }
105
-
106
- if (input.UserQuestion != null) {
107
- newQuery.UserQuestion = input.UserQuestion;
108
- }
109
-
110
- if (input.Description != null) {
111
- newQuery.Description = input.Description;
112
- }
113
-
114
- if (input.SQL != null) {
115
- newQuery.SQL = input.SQL;
116
- }
117
-
118
- if (input.TechnicalDescription != null) {
119
- newQuery.TechnicalDescription = input.TechnicalDescription;
120
- }
114
+ // Create input for the inherited CreateRecord method
115
+ const createInput = {
116
+ Name: input.Name,
117
+ CategoryID: finalCategoryID,
118
+ UserQuestion: input.UserQuestion,
119
+ Description: input.Description,
120
+ SQL: input.SQL,
121
+ TechnicalDescription: input.TechnicalDescription,
122
+ OriginalSQL: input.OriginalSQL,
123
+ Feedback: input.Feedback,
124
+ Status: input.Status || 'Approved',
125
+ QualityRank: input.QualityRank || 0,
126
+ ExecutionCostRank: input.ExecutionCostRank,
127
+ UsesTemplate: input.UsesTemplate || false
128
+ };
121
129
 
122
- if (input.OriginalSQL != null) {
123
- newQuery.OriginalSQL = input.OriginalSQL;
124
- }
130
+ // Use inherited CreateRecord method which bypasses AI processing
131
+ const provider = GetReadWriteProvider(context.providers);
132
+ const createdQuery = await this.CreateRecord('Queries', createInput, provider, context.userPayload, pubSub);
125
133
 
126
- if (input.Feedback != null) {
127
- newQuery.Feedback = input.Feedback;
128
- }
129
-
130
- if (input.Status != null) {
131
- newQuery.Status = input.Status;
134
+ if (createdQuery) {
135
+ return {
136
+ Success: true,
137
+ QueryData: JSON.stringify(createdQuery)
138
+ };
139
+ } else {
140
+ return {
141
+ Success: false,
142
+ ErrorMessage: 'Failed to create query using CreateRecord method'
143
+ };
132
144
  }
133
-
134
- if (input.QualityRank != null) {
135
- newQuery.QualityRank = input.QualityRank;
145
+
146
+ } catch (err) {
147
+ LogError(err);
148
+ return {
149
+ Success: false,
150
+ ErrorMessage: `QueryResolverExtended::CreateQuerySystemUser --- Error creating query: ${err instanceof Error ? err.message : String(err)}`
151
+ };
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Deletes a query by ID. This mutation is restricted to system users only.
157
+ * @param ID - The ID of the query to delete
158
+ * @param options - Delete options controlling action execution
159
+ * @param context - Application context containing user information
160
+ * @returns DeleteQueryResultType with success status and deleted query data
161
+ */
162
+ @RequireSystemUser()
163
+ @Mutation(() => DeleteQueryResultType)
164
+ async DeleteQuerySystemResolver(
165
+ @Arg('ID', () => String) ID: string,
166
+ @Arg('options', () => DeleteOptionsInput, { nullable: true }) options: DeleteOptionsInput | null,
167
+ @Ctx() context: AppContext,
168
+ @PubSub() pubSub: PubSubEngine
169
+ ): Promise<DeleteQueryResultType> {
170
+ try {
171
+ // Validate ID is not null/undefined/empty
172
+ if (!ID || ID.trim() === '') {
173
+ return {
174
+ Success: false,
175
+ ErrorMessage: 'QueryResolverExtended::DeleteQuerySystemResolver --- Invalid query ID: ID cannot be null or empty'
176
+ };
136
177
  }
178
+
179
+ const provider = GetReadWriteProvider(context.providers);
180
+ const key = new CompositeKey([{FieldName: 'ID', Value: ID}]);
137
181
 
138
- if (input.ExecutionCostRank != null) {
139
- newQuery.ExecutionCostRank = input.ExecutionCostRank;
140
- }
182
+ // Provide default options if none provided
183
+ const deleteOptions = options || {
184
+ SkipEntityAIActions: false,
185
+ SkipEntityActions: false
186
+ };
141
187
 
142
- if (input.UsesTemplate != null) {
143
- newQuery.UsesTemplate = input.UsesTemplate;
144
- }
145
-
146
- // Save the query
147
- const saveResult = await newQuery.Save();
188
+ // Use inherited DeleteRecord method from ResolverBase
189
+ const deletedQuery = await this.DeleteRecord('Queries', key, deleteOptions, provider, context.userPayload, pubSub);
148
190
 
149
- if (saveResult) {
191
+ if (deletedQuery) {
150
192
  return {
151
193
  Success: true,
152
- QueryData: JSON.stringify(newQuery.GetAll())
194
+ QueryData: JSON.stringify(deletedQuery)
153
195
  };
154
196
  } else {
155
197
  return {
156
198
  Success: false,
157
- ErrorMessage: `Failed to save query: ${newQuery.LatestResult?.Message || 'Unknown error'}`
199
+ ErrorMessage: 'Failed to delete query using DeleteRecord method'
158
200
  };
159
201
  }
160
202
 
@@ -162,7 +204,7 @@ export class CreateQueryResolver {
162
204
  LogError(err);
163
205
  return {
164
206
  Success: false,
165
- ErrorMessage: `CreateQueryResolver::CreateQuery --- Error creating query: ${err instanceof Error ? err.message : String(err)}`
207
+ ErrorMessage: `QueryResolverExtended::DeleteQuerySystemResolver --- Error deleting query: ${err instanceof Error ? err.message : String(err)}`
166
208
  };
167
209
  }
168
210
  }
@@ -175,7 +217,7 @@ export class CreateQueryResolver {
175
217
  * @param contextUser - User context for operations
176
218
  * @returns The ID of the final category in the path
177
219
  */
178
- private async findOrCreateCategoryPath(categoryPath: string, md: Metadata, contextUser: UserInfo): Promise<string> {
220
+ private async findOrCreateCategoryPath(categoryPath: string, md: Metadata, contextUser: UserInfo, userPayload: UserPayload): Promise<string> {
179
221
  if (!categoryPath || categoryPath.trim() === '') {
180
222
  throw new Error('CategoryPath cannot be empty');
181
223
  }
@@ -1,19 +1,21 @@
1
- import { EntityPermissionType } from '@memberjunction/core';
1
+ import { EntityPermissionType, IRunViewProvider } from '@memberjunction/core';
2
2
  import { AppContext } from '../types.js';
3
3
  import { Arg, Ctx, Query, Resolver, InputType, Field } from 'type-graphql';
4
4
  import { Entity_, EntityResolverBase } from '../generated/generated.js';
5
5
  import sql from 'mssql';
6
+ import { GetReadOnlyProvider } from '../util.js';
6
7
 
7
8
  @Resolver(Entity_)
8
9
  export class EntityResolver extends EntityResolverBase {
9
10
  @Query(() => [Entity_])
10
11
  async EntitiesBySchemas(
11
- @Ctx() { dataSource, userPayload }: AppContext,
12
+ @Ctx() { providers, userPayload }: AppContext,
12
13
  @Arg('IncludeSchemas', () => [String], { nullable: true }) IncludeSchemas?: string[],
13
14
  @Arg('ExcludeSchemas', () => [String], { nullable: true }) ExcludeSchemas?: string[]
14
15
  ) {
15
16
  this.CheckUserReadPermissions('Entities', userPayload);
16
- const rlsWhere = this.getRowLevelSecurityWhereClause('Entities', userPayload, EntityPermissionType.Read, ' WHERE');
17
+ const provider = GetReadOnlyProvider(providers, { allowFallbackToReadWrite: true });
18
+ const rlsWhere = this.getRowLevelSecurityWhereClause(provider, 'Entities', userPayload, EntityPermissionType.Read, ' WHERE');
17
19
  const includeSchemaSQL =
18
20
  IncludeSchemas && IncludeSchemas.length > 0 ? `SchemaName IN (${IncludeSchemas.map((s) => `'${s}'`).join(',')})` : '';
19
21
  const excludeSchemaSQL =
@@ -25,14 +27,21 @@ export class EntityResolver extends EntityResolverBase {
25
27
  else schemaSQL = excludeSchemaSQL;
26
28
  }
27
29
  let totalWhere = '';
28
- if (schemaSQL) totalWhere = ` WHERE ${schemaSQL}`;
30
+ if (schemaSQL) totalWhere = `${schemaSQL}`;
29
31
  if (rlsWhere) {
30
32
  if (totalWhere) totalWhere = `${totalWhere} AND ${rlsWhere}`;
31
- else totalWhere = ` WHERE ${rlsWhere}`;
33
+ else totalWhere = ` ${rlsWhere}`;
34
+ }
35
+ const rv = provider as any as IRunViewProvider;
36
+ const result = await rv.RunView({
37
+ EntityName: 'Entities',
38
+ ExtraFilter: totalWhere,
39
+ }, userPayload.userRecord);
40
+ if (result && result.Success) {
41
+ return result.Results;
42
+ }
43
+ else {
44
+ throw new Error(`Failed to fetch entities: ${result?.ErrorMessage || 'Unknown error'}`);
32
45
  }
33
- const sSQL = `SELECT * FROM [${this.MJCoreSchema}].vwEntities${totalWhere}`;
34
- const request = new sql.Request(dataSource);
35
- const result = await request.query(sSQL);
36
- return result.recordset;
37
46
  }
38
47
  }
@@ -1,20 +1,24 @@
1
- import { CompositeKey, EntityPermissionType, Metadata, RunView } from '@memberjunction/core';
1
+ import { CompositeKey, EntityDeleteOptions, EntityPermissionType, EntitySaveOptions, Metadata, RunView } from '@memberjunction/core';
2
2
  import { FileCategoryEntity, FileEntity } from '@memberjunction/core-entities';
3
3
  import { AppContext, Arg, Ctx, DeleteOptionsInput, Int, Mutation } from '@memberjunction/server';
4
4
  import { mj_core_schema } from '../config.js';
5
5
  import { FileCategoryResolver as FileCategoryResolverBase, FileCategory_ } from '../generated/generated.js';
6
6
  import sql from 'mssql';
7
+ import { SQLServerDataProvider } from '@memberjunction/sqlserver-dataprovider';
8
+ import { GetReadWriteProvider } from '../util.js';
7
9
 
8
10
  export class FileResolver extends FileCategoryResolverBase {
9
11
  @Mutation(() => FileCategory_)
10
12
  async DeleteFileCategory(
11
13
  @Arg('ID', () => String) ID: string,
12
14
  @Arg('options___', () => DeleteOptionsInput) options: DeleteOptionsInput,
13
- @Ctx() { dataSource, userPayload }: AppContext
15
+ @Ctx() { providers, userPayload }: AppContext
14
16
  ) {
15
17
  const key = new CompositeKey();
16
18
  key.LoadFromSingleKeyValuePair('ID', ID);
17
- if (!(await this.BeforeDelete(dataSource, key))) {
19
+ const provider = GetReadWriteProvider(providers);
20
+
21
+ if (!(await this.BeforeDelete(provider, key))) {
18
22
  return null;
19
23
  }
20
24
 
@@ -30,8 +34,7 @@ export class FileResolver extends FileCategoryResolverBase {
30
34
  const returnValue = fileCategoryEntity.GetAll();
31
35
 
32
36
  // Any files using the deleted category fall back to its parent
33
- const transaction = new sql.Transaction(dataSource);
34
- await transaction.begin();
37
+ await provider.BeginTransaction();
35
38
  try {
36
39
  // SHOULD USE BaseEntity for each of these records to ensure object model
37
40
  // is used everywhere - new code below. The below is SLOWER than a single
@@ -63,14 +66,14 @@ export class FileResolver extends FileCategoryResolverBase {
63
66
  await fileEntity.Save();
64
67
  }
65
68
  }
66
- await fileCategoryEntity.Delete();
67
- await transaction.commit();
69
+ await fileCategoryEntity.Delete(options);
70
+ await provider.CommitTransaction();
68
71
  } catch (error) {
69
- await transaction.rollback();
72
+ await provider.RollbackTransaction();
70
73
  throw error;
71
74
  }
72
75
 
73
- await this.AfterDelete(dataSource, key); // fire event
76
+ await this.AfterDelete(provider, key); // fire event
74
77
  return returnValue;
75
78
  }
76
79
  }
@@ -1,4 +1,4 @@
1
- import { EntityPermissionType, Metadata, FieldValueCollection } from '@memberjunction/core';
1
+ import { EntityPermissionType, Metadata, FieldValueCollection, EntitySaveOptions } from '@memberjunction/core';
2
2
  import { FileEntity, FileStorageProviderEntity } from '@memberjunction/core-entities';
3
3
  import {
4
4
  AppContext,
@@ -19,6 +19,7 @@ import {
19
19
  import { createDownloadUrl, createUploadUrl, deleteObject, moveObject } from '@memberjunction/storage';
20
20
  import { CreateFileInput, FileResolver as FileResolverBase, File_, UpdateFileInput } from '../generated/generated.js';
21
21
  import { FieldMapper } from '@memberjunction/graphql-dataprovider';
22
+ import { GetReadOnlyProvider } from '../util.js';
22
23
 
23
24
  @InputType()
24
25
  export class CreateUploadURLInput {
@@ -57,8 +58,9 @@ export class FileResolver extends FileResolverBase {
57
58
  fileEntity.CheckPermissions(EntityPermissionType.Create, true);
58
59
 
59
60
  // Check to see if there's already an object with that name
61
+ const provider = GetReadOnlyProvider(context.providers, {allowFallbackToReadWrite: true})
60
62
  const [sameName] = await this.findBy(
61
- context.dataSource,
63
+ provider,
62
64
  'Files',
63
65
  { Name: input.Name, ProviderID: input.ProviderID },
64
66
  context.userPayload.userRecord
@@ -264,6 +264,9 @@ export function isTokenValid(token: string) {
264
264
  export function recordTokenUse(token: string, usePayload: any) {
265
265
  const t = __accessTokens.find((t) => t.Token === token);
266
266
  if (t) {
267
+ if (!t.TokenUses) {
268
+ t.TokenUses = [];
269
+ }
267
270
  t.TokenUses.push({ Token: token, UsedAt: new Date(), UsePayload: usePayload });
268
271
  }
269
272
  else {
@@ -163,7 +163,8 @@ export class RecordMergeResolver {
163
163
  ) {
164
164
  try {
165
165
  const md = new Metadata();
166
- const result = await md.MergeRecords(request, userPayload.userRecord);
166
+ const options = {};
167
+ const result = await md.MergeRecords(request, userPayload.userRecord, options);
167
168
  return result;
168
169
  } catch (e) {
169
170
  LogError(e);