@memberjunction/server 2.75.0 → 2.76.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.
@@ -1,8 +1,11 @@
1
- import { Arg, Ctx, Field, InputType, Mutation, ObjectType, registerEnumType } from 'type-graphql';
1
+ import { Arg, Ctx, Field, InputType, Mutation, ObjectType, registerEnumType, Resolver, PubSub, PubSubEngine } from 'type-graphql';
2
2
  import { AppContext } from '../types.js';
3
- import { LogError, Metadata, RunView, UserInfo } from '@memberjunction/core';
3
+ import { LogError, Metadata, RunView, UserInfo, CompositeKey } 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 { GetReadWriteDataSource } 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,119 @@ 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) {
110
+ const md = new Metadata();
96
111
  finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, md, context.userPayload.userRecord);
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
- }
121
-
122
- if (input.OriginalSQL != null) {
123
- newQuery.OriginalSQL = input.OriginalSQL;
124
- }
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
+ };
125
129
 
126
- if (input.Feedback != null) {
127
- newQuery.Feedback = input.Feedback;
128
- }
130
+ // Use inherited CreateRecord method which bypasses AI processing
131
+ const connPool = GetReadWriteDataSource(context.dataSources);
132
+ const createdQuery = await this.CreateRecord('Queries', createInput, connPool, context.userPayload, pubSub);
129
133
 
130
- if (input.Status != null) {
131
- newQuery.Status = input.Status;
132
- }
133
-
134
- if (input.QualityRank != null) {
135
- newQuery.QualityRank = input.QualityRank;
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
+ };
136
144
  }
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) queryID: string,
166
+ @Arg('options', () => DeleteOptionsInput, { nullable: true }) options: DeleteOptionsInput | null,
167
+ @Ctx() context: AppContext,
168
+ @PubSub() pubSub: PubSubEngine
169
+ ): Promise<DeleteQueryResultType> {
170
+ try {
171
+ const connPool = GetReadWriteDataSource(context.dataSources);
172
+ const key = new CompositeKey([{FieldName: 'ID', Value: queryID}]);
137
173
 
138
- if (input.ExecutionCostRank != null) {
139
- newQuery.ExecutionCostRank = input.ExecutionCostRank;
140
- }
174
+ // Provide default options if none provided
175
+ const deleteOptions = options || {
176
+ SkipEntityAIActions: false,
177
+ SkipEntityActions: false
178
+ };
141
179
 
142
- if (input.UsesTemplate != null) {
143
- newQuery.UsesTemplate = input.UsesTemplate;
144
- }
145
-
146
- // Save the query
147
- const saveResult = await newQuery.Save();
180
+ // Use inherited DeleteRecord method from ResolverBase
181
+ const deletedQuery = await this.DeleteRecord('Queries', key, deleteOptions, connPool, context.userPayload, pubSub);
148
182
 
149
- if (saveResult) {
183
+ if (deletedQuery) {
150
184
  return {
151
185
  Success: true,
152
- QueryData: JSON.stringify(newQuery.GetAll())
186
+ QueryData: JSON.stringify(deletedQuery)
153
187
  };
154
188
  } else {
155
189
  return {
156
190
  Success: false,
157
- ErrorMessage: `Failed to save query: ${newQuery.LatestResult?.Message || 'Unknown error'}`
191
+ ErrorMessage: 'Failed to delete query using DeleteRecord method'
158
192
  };
159
193
  }
160
194
 
@@ -162,7 +196,7 @@ export class CreateQueryResolver {
162
196
  LogError(err);
163
197
  return {
164
198
  Success: false,
165
- ErrorMessage: `CreateQueryResolver::CreateQuery --- Error creating query: ${err instanceof Error ? err.message : String(err)}`
199
+ ErrorMessage: `QueryResolverExtended::DeleteQuerySystemResolver --- Error deleting query: ${err instanceof Error ? err.message : String(err)}`
166
200
  };
167
201
  }
168
202
  }
@@ -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 {
@@ -38,7 +38,7 @@ export class RunQueryResultType {
38
38
 
39
39
  @Resolver()
40
40
  export class RunQueryResolver {
41
- private async findQuery(QueryID: string, QueryName?: string, CategoryID?: string, CategoryName?: string, refreshMetadataIfNotFound: boolean = false): Promise<QueryInfo | null> {
41
+ private async findQuery(QueryID: string, QueryName?: string, CategoryID?: string, CategoryPath?: string, refreshMetadataIfNotFound: boolean = false): Promise<QueryInfo | null> {
42
42
  const md = new Metadata();
43
43
 
44
44
  // Filter queries based on provided criteria
@@ -50,8 +50,8 @@ export class RunQueryResolver {
50
50
  if (CategoryID) {
51
51
  matches = matches && q.CategoryID?.trim().toLowerCase() === CategoryID.trim().toLowerCase();
52
52
  }
53
- if (CategoryName) {
54
- matches = matches && q.Category?.trim().toLowerCase() === CategoryName.trim().toLowerCase();
53
+ if (CategoryPath) {
54
+ matches = matches && q.Category?.trim().toLowerCase() === CategoryPath.trim().toLowerCase();
55
55
  }
56
56
  return matches;
57
57
  }
@@ -62,7 +62,7 @@ export class RunQueryResolver {
62
62
  if (refreshMetadataIfNotFound) {
63
63
  // If we didn't find the query, refresh metadata and try again
64
64
  await md.Refresh();
65
- return this.findQuery(QueryID, QueryName, CategoryID, CategoryName, false); // change the refresh flag to false so we don't loop infinitely
65
+ return this.findQuery(QueryID, QueryName, CategoryID, CategoryPath, false); // change the refresh flag to false so we don't loop infinitely
66
66
  }
67
67
  else {
68
68
  return null; // No query found and not refreshing metadata
@@ -76,7 +76,7 @@ export class RunQueryResolver {
76
76
  async GetQueryData(@Arg('QueryID', () => String) QueryID: string,
77
77
  @Ctx() context: AppContext,
78
78
  @Arg('CategoryID', () => String, {nullable: true}) CategoryID?: string,
79
- @Arg('CategoryName', () => String, {nullable: true}) CategoryName?: string,
79
+ @Arg('CategoryPath', () => String, {nullable: true}) CategoryPath?: string,
80
80
  @Arg('Parameters', () => GraphQLJSONObject, {nullable: true}) Parameters?: Record<string, any>,
81
81
  @Arg('MaxRows', () => Int, {nullable: true}) MaxRows?: number,
82
82
  @Arg('StartRow', () => Int, {nullable: true}) StartRow?: number): Promise<RunQueryResultType> {
@@ -86,7 +86,7 @@ export class RunQueryResolver {
86
86
  {
87
87
  QueryID: QueryID,
88
88
  CategoryID: CategoryID,
89
- CategoryName: CategoryName,
89
+ CategoryPath: CategoryPath,
90
90
  Parameters: Parameters,
91
91
  MaxRows: MaxRows,
92
92
  StartRow: StartRow
@@ -102,7 +102,7 @@ export class RunQueryResolver {
102
102
  let queryName = result.QueryName;
103
103
  if (!queryName) {
104
104
  try {
105
- const queryInfo = await this.findQuery(QueryID, undefined, CategoryID, CategoryName, true);
105
+ const queryInfo = await this.findQuery(QueryID, undefined, CategoryID, CategoryPath, true);
106
106
  if (queryInfo) {
107
107
  queryName = queryInfo.Name;
108
108
  }
@@ -128,7 +128,7 @@ export class RunQueryResolver {
128
128
  async GetQueryDataByName(@Arg('QueryName', () => String) QueryName: string,
129
129
  @Ctx() context: AppContext,
130
130
  @Arg('CategoryID', () => String, {nullable: true}) CategoryID?: string,
131
- @Arg('CategoryName', () => String, {nullable: true}) CategoryName?: string,
131
+ @Arg('CategoryPath', () => String, {nullable: true}) CategoryPath?: string,
132
132
  @Arg('Parameters', () => GraphQLJSONObject, {nullable: true}) Parameters?: Record<string, any>,
133
133
  @Arg('MaxRows', () => Int, {nullable: true}) MaxRows?: number,
134
134
  @Arg('StartRow', () => Int, {nullable: true}) StartRow?: number): Promise<RunQueryResultType> {
@@ -137,7 +137,7 @@ export class RunQueryResolver {
137
137
  {
138
138
  QueryName: QueryName,
139
139
  CategoryID: CategoryID,
140
- CategoryName: CategoryName,
140
+ CategoryPath: CategoryPath,
141
141
  Parameters: Parameters,
142
142
  MaxRows: MaxRows,
143
143
  StartRow: StartRow
@@ -162,7 +162,7 @@ export class RunQueryResolver {
162
162
  async GetQueryDataSystemUser(@Arg('QueryID', () => String) QueryID: string,
163
163
  @Ctx() context: AppContext,
164
164
  @Arg('CategoryID', () => String, {nullable: true}) CategoryID?: string,
165
- @Arg('CategoryName', () => String, {nullable: true}) CategoryName?: string,
165
+ @Arg('CategoryPath', () => String, {nullable: true}) CategoryPath?: string,
166
166
  @Arg('Parameters', () => GraphQLJSONObject, {nullable: true}) Parameters?: Record<string, any>,
167
167
  @Arg('MaxRows', () => Int, {nullable: true}) MaxRows?: number,
168
168
  @Arg('StartRow', () => Int, {nullable: true}) StartRow?: number): Promise<RunQueryResultType> {
@@ -171,7 +171,7 @@ export class RunQueryResolver {
171
171
  {
172
172
  QueryID: QueryID,
173
173
  CategoryID: CategoryID,
174
- CategoryName: CategoryName,
174
+ CategoryPath: CategoryPath,
175
175
  Parameters: Parameters,
176
176
  MaxRows: MaxRows,
177
177
  StartRow: StartRow
@@ -182,7 +182,7 @@ export class RunQueryResolver {
182
182
  let queryName = result.QueryName;
183
183
  if (!queryName) {
184
184
  try {
185
- const queryInfo = await this.findQuery(QueryID, undefined, CategoryID, CategoryName, true);
185
+ const queryInfo = await this.findQuery(QueryID, undefined, CategoryID, CategoryPath, true);
186
186
  if (queryInfo) {
187
187
  queryName = queryInfo.Name;
188
188
  }
@@ -209,7 +209,7 @@ export class RunQueryResolver {
209
209
  async GetQueryDataByNameSystemUser(@Arg('QueryName', () => String) QueryName: string,
210
210
  @Ctx() context: AppContext,
211
211
  @Arg('CategoryID', () => String, {nullable: true}) CategoryID?: string,
212
- @Arg('CategoryName', () => String, {nullable: true}) CategoryName?: string,
212
+ @Arg('CategoryPath', () => String, {nullable: true}) CategoryPath?: string,
213
213
  @Arg('Parameters', () => GraphQLJSONObject, {nullable: true}) Parameters?: Record<string, any>,
214
214
  @Arg('MaxRows', () => Int, {nullable: true}) MaxRows?: number,
215
215
  @Arg('StartRow', () => Int, {nullable: true}) StartRow?: number): Promise<RunQueryResultType> {
@@ -218,7 +218,7 @@ export class RunQueryResolver {
218
218
  {
219
219
  QueryName: QueryName,
220
220
  CategoryID: CategoryID,
221
- CategoryName: CategoryName,
221
+ CategoryPath: CategoryPath,
222
222
  Parameters: Parameters,
223
223
  MaxRows: MaxRows,
224
224
  StartRow: StartRow