@memberjunction/server 2.78.0 → 2.80.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.
- package/dist/generated/generated.d.ts +48 -8
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +317 -70
- package/dist/generated/generated.js.map +1 -1
- package/dist/resolvers/CreateQueryResolver.d.ts +71 -0
- package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/CreateQueryResolver.js +500 -27
- package/dist/resolvers/CreateQueryResolver.js.map +1 -1
- package/package.json +34 -34
- package/src/generated/generated.ts +205 -53
- package/src/resolvers/CreateQueryResolver.ts +409 -41
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Arg, Ctx, Field, InputType, Mutation, ObjectType, registerEnumType, Resolver, PubSub, PubSubEngine } from 'type-graphql';
|
|
2
|
-
import { AppContext
|
|
3
|
-
import { LogError, Metadata, RunView, UserInfo, CompositeKey
|
|
2
|
+
import { AppContext } from '../types.js';
|
|
3
|
+
import { LogError, Metadata, RunView, UserInfo, CompositeKey } from '@memberjunction/core';
|
|
4
4
|
import { RequireSystemUser } from '../directives/RequireSystemUser.js';
|
|
5
|
-
import { QueryCategoryEntity } from '@memberjunction/core-entities';
|
|
5
|
+
import { QueryCategoryEntity, QueryPermissionEntity } from '@memberjunction/core-entities';
|
|
6
6
|
import { QueryResolver } from '../generated/generated.js';
|
|
7
7
|
import { GetReadWriteProvider } from '../util.js';
|
|
8
8
|
import { DeleteOptionsInput } from '../generic/DeleteOptionsInput.js';
|
|
9
|
+
import { QueryEntityExtended } from '@memberjunction/core-entities-server';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Query status enumeration for GraphQL
|
|
@@ -22,6 +23,12 @@ registerEnumType(QueryStatus, {
|
|
|
22
23
|
description: "Status of a query: Pending, Approved, Rejected, or Expired"
|
|
23
24
|
});
|
|
24
25
|
|
|
26
|
+
@InputType()
|
|
27
|
+
export class QueryPermissionInputType {
|
|
28
|
+
@Field(() => String)
|
|
29
|
+
RoleID!: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
25
32
|
@InputType()
|
|
26
33
|
export class CreateQuerySystemUserInput {
|
|
27
34
|
@Field(() => String)
|
|
@@ -62,6 +69,147 @@ export class CreateQuerySystemUserInput {
|
|
|
62
69
|
|
|
63
70
|
@Field(() => Boolean, { nullable: true })
|
|
64
71
|
UsesTemplate?: boolean;
|
|
72
|
+
|
|
73
|
+
@Field(() => [QueryPermissionInputType], { nullable: true })
|
|
74
|
+
Permissions?: QueryPermissionInputType[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@InputType()
|
|
78
|
+
export class UpdateQuerySystemUserInput {
|
|
79
|
+
@Field(() => String)
|
|
80
|
+
ID!: string;
|
|
81
|
+
|
|
82
|
+
@Field(() => String, { nullable: true })
|
|
83
|
+
Name?: string;
|
|
84
|
+
|
|
85
|
+
@Field(() => String, { nullable: true })
|
|
86
|
+
CategoryID?: string;
|
|
87
|
+
|
|
88
|
+
@Field(() => String, { nullable: true })
|
|
89
|
+
CategoryPath?: string;
|
|
90
|
+
|
|
91
|
+
@Field(() => String, { nullable: true })
|
|
92
|
+
UserQuestion?: string;
|
|
93
|
+
|
|
94
|
+
@Field(() => String, { nullable: true })
|
|
95
|
+
Description?: string;
|
|
96
|
+
|
|
97
|
+
@Field(() => String, { nullable: true })
|
|
98
|
+
SQL?: string;
|
|
99
|
+
|
|
100
|
+
@Field(() => String, { nullable: true })
|
|
101
|
+
TechnicalDescription?: string;
|
|
102
|
+
|
|
103
|
+
@Field(() => String, { nullable: true })
|
|
104
|
+
OriginalSQL?: string;
|
|
105
|
+
|
|
106
|
+
@Field(() => String, { nullable: true })
|
|
107
|
+
Feedback?: string;
|
|
108
|
+
|
|
109
|
+
@Field(() => QueryStatus, { nullable: true })
|
|
110
|
+
Status?: QueryStatus;
|
|
111
|
+
|
|
112
|
+
@Field(() => Number, { nullable: true })
|
|
113
|
+
QualityRank?: number;
|
|
114
|
+
|
|
115
|
+
@Field(() => Number, { nullable: true })
|
|
116
|
+
ExecutionCostRank?: number;
|
|
117
|
+
|
|
118
|
+
@Field(() => Boolean, { nullable: true })
|
|
119
|
+
UsesTemplate?: boolean;
|
|
120
|
+
|
|
121
|
+
@Field(() => [QueryPermissionInputType], { nullable: true })
|
|
122
|
+
Permissions?: QueryPermissionInputType[];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@ObjectType()
|
|
126
|
+
export class QueryFieldType {
|
|
127
|
+
@Field(() => String)
|
|
128
|
+
ID!: string;
|
|
129
|
+
|
|
130
|
+
@Field(() => String)
|
|
131
|
+
QueryID!: string;
|
|
132
|
+
|
|
133
|
+
@Field(() => String)
|
|
134
|
+
Name!: string;
|
|
135
|
+
|
|
136
|
+
@Field(() => String, { nullable: true })
|
|
137
|
+
Description?: string;
|
|
138
|
+
|
|
139
|
+
@Field(() => String, { nullable: true })
|
|
140
|
+
Type?: string;
|
|
141
|
+
|
|
142
|
+
@Field(() => Number)
|
|
143
|
+
Sequence!: number;
|
|
144
|
+
|
|
145
|
+
@Field(() => String, { nullable: true })
|
|
146
|
+
SQLBaseType?: string;
|
|
147
|
+
|
|
148
|
+
@Field(() => String, { nullable: true })
|
|
149
|
+
SQLFullType?: string;
|
|
150
|
+
|
|
151
|
+
@Field(() => Boolean)
|
|
152
|
+
IsComputed!: boolean;
|
|
153
|
+
|
|
154
|
+
@Field(() => String, { nullable: true })
|
|
155
|
+
ComputationDescription?: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@ObjectType()
|
|
159
|
+
export class QueryParameterType {
|
|
160
|
+
@Field(() => String)
|
|
161
|
+
ID!: string;
|
|
162
|
+
|
|
163
|
+
@Field(() => String)
|
|
164
|
+
QueryID!: string;
|
|
165
|
+
|
|
166
|
+
@Field(() => String)
|
|
167
|
+
Name!: string;
|
|
168
|
+
|
|
169
|
+
@Field(() => String)
|
|
170
|
+
Type!: string;
|
|
171
|
+
|
|
172
|
+
@Field(() => String, { nullable: true })
|
|
173
|
+
DefaultValue?: string;
|
|
174
|
+
|
|
175
|
+
@Field(() => String, { nullable: true })
|
|
176
|
+
Comments?: string;
|
|
177
|
+
|
|
178
|
+
@Field(() => Boolean)
|
|
179
|
+
IsRequired!: boolean;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@ObjectType()
|
|
183
|
+
export class QueryEntityType {
|
|
184
|
+
@Field(() => String)
|
|
185
|
+
ID!: string;
|
|
186
|
+
|
|
187
|
+
@Field(() => String)
|
|
188
|
+
QueryID!: string;
|
|
189
|
+
|
|
190
|
+
@Field(() => String)
|
|
191
|
+
EntityID!: string;
|
|
192
|
+
|
|
193
|
+
@Field(() => String, { nullable: true })
|
|
194
|
+
EntityName?: string;
|
|
195
|
+
|
|
196
|
+
@Field(() => Number)
|
|
197
|
+
Sequence!: number;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@ObjectType()
|
|
201
|
+
export class QueryPermissionType {
|
|
202
|
+
@Field(() => String)
|
|
203
|
+
ID!: string;
|
|
204
|
+
|
|
205
|
+
@Field(() => String)
|
|
206
|
+
QueryID!: string;
|
|
207
|
+
|
|
208
|
+
@Field(() => String)
|
|
209
|
+
RoleID!: string;
|
|
210
|
+
|
|
211
|
+
@Field(() => String, { nullable: true })
|
|
212
|
+
RoleName?: string;
|
|
65
213
|
}
|
|
66
214
|
|
|
67
215
|
@ObjectType()
|
|
@@ -74,6 +222,42 @@ export class CreateQueryResultType {
|
|
|
74
222
|
|
|
75
223
|
@Field(() => String, { nullable: true })
|
|
76
224
|
QueryData?: string;
|
|
225
|
+
|
|
226
|
+
@Field(() => [QueryFieldType], { nullable: true })
|
|
227
|
+
Fields?: QueryFieldType[];
|
|
228
|
+
|
|
229
|
+
@Field(() => [QueryParameterType], { nullable: true })
|
|
230
|
+
Parameters?: QueryParameterType[];
|
|
231
|
+
|
|
232
|
+
@Field(() => [QueryEntityType], { nullable: true })
|
|
233
|
+
Entities?: QueryEntityType[];
|
|
234
|
+
|
|
235
|
+
@Field(() => [QueryPermissionType], { nullable: true })
|
|
236
|
+
Permissions?: QueryPermissionType[];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
@ObjectType()
|
|
240
|
+
export class UpdateQueryResultType {
|
|
241
|
+
@Field(() => Boolean)
|
|
242
|
+
Success!: boolean;
|
|
243
|
+
|
|
244
|
+
@Field(() => String, { nullable: true })
|
|
245
|
+
ErrorMessage?: string;
|
|
246
|
+
|
|
247
|
+
@Field(() => String, { nullable: true })
|
|
248
|
+
QueryData?: string;
|
|
249
|
+
|
|
250
|
+
@Field(() => [QueryFieldType], { nullable: true })
|
|
251
|
+
Fields?: QueryFieldType[];
|
|
252
|
+
|
|
253
|
+
@Field(() => [QueryParameterType], { nullable: true })
|
|
254
|
+
Parameters?: QueryParameterType[];
|
|
255
|
+
|
|
256
|
+
@Field(() => [QueryEntityType], { nullable: true })
|
|
257
|
+
Entities?: QueryEntityType[];
|
|
258
|
+
|
|
259
|
+
@Field(() => [QueryPermissionType], { nullable: true })
|
|
260
|
+
Permissions?: QueryPermissionType[];
|
|
77
261
|
}
|
|
78
262
|
|
|
79
263
|
@ObjectType()
|
|
@@ -108,46 +292,222 @@ export class QueryResolverExtended extends QueryResolver {
|
|
|
108
292
|
let finalCategoryID = input.CategoryID;
|
|
109
293
|
if (input.CategoryPath) {
|
|
110
294
|
const md = new Metadata();
|
|
111
|
-
finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, md, context.userPayload.userRecord
|
|
295
|
+
finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, md, context.userPayload.userRecord);
|
|
112
296
|
}
|
|
113
|
-
|
|
114
|
-
//
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
OriginalSQL: input.OriginalSQL,
|
|
123
|
-
Feedback: input.Feedback,
|
|
297
|
+
|
|
298
|
+
// Use QueryEntityExtended which handles AI processing
|
|
299
|
+
const provider = GetReadWriteProvider(context.providers);
|
|
300
|
+
const record = await provider.GetEntityObject<QueryEntityExtended>("Queries", context.userPayload.userRecord);
|
|
301
|
+
|
|
302
|
+
// Set the fields from input, handling CategoryPath resolution
|
|
303
|
+
const fieldsToSet = {
|
|
304
|
+
...input,
|
|
305
|
+
CategoryID: finalCategoryID || input.CategoryID,
|
|
124
306
|
Status: input.Status || 'Approved',
|
|
125
307
|
QualityRank: input.QualityRank || 0,
|
|
126
|
-
ExecutionCostRank: input.ExecutionCostRank,
|
|
127
308
|
UsesTemplate: input.UsesTemplate || false
|
|
128
309
|
};
|
|
310
|
+
// Remove Permissions from the fields to set since we handle them separately
|
|
311
|
+
delete (fieldsToSet as any).Permissions;
|
|
129
312
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (
|
|
313
|
+
record.SetMany(fieldsToSet, true);
|
|
314
|
+
this.ListenForEntityMessages(record, pubSub, context.userPayload.userRecord);
|
|
315
|
+
|
|
316
|
+
// Pass the transactionScopeId from the user payload to the save operation
|
|
317
|
+
if (await record.Save()) {
|
|
318
|
+
// save worked, fire the AfterCreate event and then return all the data
|
|
319
|
+
await this.AfterCreate(provider, input); // fire event
|
|
320
|
+
const queryID = record.ID;
|
|
321
|
+
|
|
322
|
+
if (input.Permissions && input.Permissions.length > 0) {
|
|
323
|
+
await this.createPermissions(input.Permissions, queryID, context.userPayload.userRecord);
|
|
324
|
+
await record.RefreshRelatedMetadata(true); // force DB update since we just created new permissions
|
|
325
|
+
}
|
|
326
|
+
|
|
135
327
|
return {
|
|
136
328
|
Success: true,
|
|
137
|
-
QueryData: JSON.stringify(
|
|
329
|
+
QueryData: JSON.stringify(record.GetAll()),
|
|
330
|
+
Fields: record.QueryFields,
|
|
331
|
+
Parameters: record.QueryParameters,
|
|
332
|
+
Entities: record.QueryEntities,
|
|
333
|
+
Permissions: record.QueryPermissions
|
|
138
334
|
};
|
|
139
|
-
}
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
140
337
|
return {
|
|
141
338
|
Success: false,
|
|
142
339
|
ErrorMessage: 'Failed to create query using CreateRecord method'
|
|
143
340
|
};
|
|
144
341
|
}
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
LogError(err);
|
|
345
|
+
return {
|
|
346
|
+
Success: false,
|
|
347
|
+
ErrorMessage: `QueryResolverExtended::CreateQuerySystemUser --- Error creating query: ${err instanceof Error ? err.message : String(err)}`
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
protected async createPermissions(permissions: QueryPermissionInputType[], queryID: string, contextUser: UserInfo): Promise<QueryPermissionType[]> {
|
|
353
|
+
// Create permissions if provided
|
|
354
|
+
const createdPermissions: QueryPermissionType[] = [];
|
|
355
|
+
if (permissions && permissions.length > 0) {
|
|
356
|
+
const md = new Metadata();
|
|
357
|
+
for (const perm of permissions) {
|
|
358
|
+
const permissionEntity = await md.GetEntityObject<QueryPermissionEntity>('Query Permissions', contextUser);
|
|
359
|
+
if (permissionEntity) {
|
|
360
|
+
permissionEntity.QueryID = queryID;
|
|
361
|
+
permissionEntity.RoleID = perm.RoleID;
|
|
362
|
+
|
|
363
|
+
const saveResult = await permissionEntity.Save();
|
|
364
|
+
if (saveResult) {
|
|
365
|
+
createdPermissions.push({
|
|
366
|
+
ID: permissionEntity.ID,
|
|
367
|
+
QueryID: permissionEntity.QueryID,
|
|
368
|
+
RoleID: permissionEntity.RoleID,
|
|
369
|
+
RoleName: permissionEntity.Role // The view includes the Role name
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return createdPermissions;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Updates an existing query with the provided attributes. This mutation is restricted to system users only.
|
|
380
|
+
* @param input - UpdateQuerySystemUserInput containing the query ID and fields to update
|
|
381
|
+
* @param context - Application context containing user information
|
|
382
|
+
* @returns UpdateQueryResultType with success status and updated query data including related entities
|
|
383
|
+
*/
|
|
384
|
+
@RequireSystemUser()
|
|
385
|
+
@Mutation(() => UpdateQueryResultType)
|
|
386
|
+
async UpdateQuerySystemUser(
|
|
387
|
+
@Arg('input', () => UpdateQuerySystemUserInput) input: UpdateQuerySystemUserInput,
|
|
388
|
+
@Ctx() context: AppContext,
|
|
389
|
+
@PubSub() pubSub: PubSubEngine
|
|
390
|
+
): Promise<UpdateQueryResultType> {
|
|
391
|
+
try {
|
|
392
|
+
// Load the existing query using QueryEntityExtended
|
|
393
|
+
const provider = GetReadWriteProvider(context.providers);
|
|
394
|
+
const queryEntity = await provider.GetEntityObject<QueryEntityExtended>('Queries', context.userPayload.userRecord);
|
|
395
|
+
if (!queryEntity || !await queryEntity.Load(input.ID)) {
|
|
396
|
+
return {
|
|
397
|
+
Success: false,
|
|
398
|
+
ErrorMessage: `Query with ID ${input.ID} not found`
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Handle CategoryPath if provided
|
|
403
|
+
let finalCategoryID = input.CategoryID;
|
|
404
|
+
if (input.CategoryPath) {
|
|
405
|
+
const md = new Metadata();
|
|
406
|
+
finalCategoryID = await this.findOrCreateCategoryPath(input.CategoryPath, md, context.userPayload.userRecord);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Update fields that were provided
|
|
410
|
+
if (input.Name !== undefined) queryEntity.Name = input.Name;
|
|
411
|
+
if (finalCategoryID !== undefined) queryEntity.CategoryID = finalCategoryID;
|
|
412
|
+
if (input.UserQuestion !== undefined) queryEntity.UserQuestion = input.UserQuestion;
|
|
413
|
+
if (input.Description !== undefined) queryEntity.Description = input.Description;
|
|
414
|
+
if (input.SQL !== undefined) queryEntity.SQL = input.SQL;
|
|
415
|
+
if (input.TechnicalDescription !== undefined) queryEntity.TechnicalDescription = input.TechnicalDescription;
|
|
416
|
+
if (input.OriginalSQL !== undefined) queryEntity.OriginalSQL = input.OriginalSQL;
|
|
417
|
+
if (input.Feedback !== undefined) queryEntity.Feedback = input.Feedback;
|
|
418
|
+
if (input.Status !== undefined) queryEntity.Status = input.Status;
|
|
419
|
+
if (input.QualityRank !== undefined) queryEntity.QualityRank = input.QualityRank;
|
|
420
|
+
if (input.ExecutionCostRank !== undefined) queryEntity.ExecutionCostRank = input.ExecutionCostRank;
|
|
421
|
+
if (input.UsesTemplate !== undefined) queryEntity.UsesTemplate = input.UsesTemplate;
|
|
422
|
+
|
|
423
|
+
// Save the updated query
|
|
424
|
+
const saveResult = await queryEntity.Save();
|
|
425
|
+
if (!saveResult) {
|
|
426
|
+
return {
|
|
427
|
+
Success: false,
|
|
428
|
+
ErrorMessage: `Failed to update query: ${queryEntity.LatestResult?.Message || 'Unknown error'}`
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const queryID = queryEntity.ID;
|
|
433
|
+
|
|
434
|
+
// Handle permissions update if provided
|
|
435
|
+
if (input.Permissions !== undefined) {
|
|
436
|
+
// Delete existing permissions
|
|
437
|
+
const rv = new RunView();
|
|
438
|
+
const existingPermissions = await rv.RunView<QueryPermissionEntity>({
|
|
439
|
+
EntityName: 'Query Permissions',
|
|
440
|
+
ExtraFilter: `QueryID='${queryID}'`,
|
|
441
|
+
ResultType: 'entity_object'
|
|
442
|
+
}, context.userPayload.userRecord);
|
|
443
|
+
|
|
444
|
+
if (existingPermissions.Success && existingPermissions.Results) {
|
|
445
|
+
for (const perm of existingPermissions.Results) {
|
|
446
|
+
await perm.Delete();
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Create new permissions
|
|
451
|
+
await this.createPermissions(input.Permissions, queryID, context.userPayload.userRecord);
|
|
452
|
+
|
|
453
|
+
// Refresh the metadata to get updated permissions
|
|
454
|
+
await queryEntity.RefreshRelatedMetadata(true);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Use the properties from QueryEntityExtended instead of manual loading
|
|
458
|
+
const fields: QueryFieldType[] = queryEntity.QueryFields.map(f => ({
|
|
459
|
+
ID: f.ID,
|
|
460
|
+
QueryID: f.QueryID,
|
|
461
|
+
Name: f.Name,
|
|
462
|
+
Description: f.Description || undefined,
|
|
463
|
+
Type: f.SQLBaseType || undefined,
|
|
464
|
+
Sequence: f.Sequence,
|
|
465
|
+
SQLBaseType: f.SQLBaseType || undefined,
|
|
466
|
+
SQLFullType: f.SQLFullType || undefined,
|
|
467
|
+
IsComputed: f.IsComputed,
|
|
468
|
+
ComputationEnabled: true, // Default to true as it's not in QueryFieldInfo
|
|
469
|
+
ComputationDescription: f.ComputationDescription || undefined
|
|
470
|
+
}));
|
|
471
|
+
|
|
472
|
+
const parameters: QueryParameterType[] = queryEntity.QueryParameters.map(p => ({
|
|
473
|
+
ID: p.ID,
|
|
474
|
+
QueryID: p.QueryID,
|
|
475
|
+
Name: p.Name,
|
|
476
|
+
Type: p.Type,
|
|
477
|
+
DefaultValue: p.DefaultValue || undefined,
|
|
478
|
+
Comments: '', // Not available in QueryParameterInfo
|
|
479
|
+
IsRequired: p.IsRequired
|
|
480
|
+
}));
|
|
481
|
+
|
|
482
|
+
const entities: QueryEntityType[] = queryEntity.QueryEntities.map(e => ({
|
|
483
|
+
ID: e.ID,
|
|
484
|
+
QueryID: e.QueryID,
|
|
485
|
+
EntityID: e.EntityID,
|
|
486
|
+
EntityName: e.Entity || undefined, // Property is called Entity, not EntityName
|
|
487
|
+
Sequence: e.Sequence || 0
|
|
488
|
+
}));
|
|
489
|
+
|
|
490
|
+
const permissions: QueryPermissionType[] = queryEntity.QueryPermissions.map(p => ({
|
|
491
|
+
ID: p.ID,
|
|
492
|
+
QueryID: p.QueryID,
|
|
493
|
+
RoleID: p.RoleID,
|
|
494
|
+
RoleName: p.Role || undefined // Property is called Role, not RoleName
|
|
495
|
+
}));
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
Success: true,
|
|
499
|
+
QueryData: JSON.stringify(queryEntity.GetAll()),
|
|
500
|
+
Fields: fields,
|
|
501
|
+
Parameters: parameters,
|
|
502
|
+
Entities: entities,
|
|
503
|
+
Permissions: permissions
|
|
504
|
+
};
|
|
145
505
|
|
|
146
506
|
} catch (err) {
|
|
147
507
|
LogError(err);
|
|
148
508
|
return {
|
|
149
509
|
Success: false,
|
|
150
|
-
ErrorMessage: `QueryResolverExtended::
|
|
510
|
+
ErrorMessage: `QueryResolverExtended::UpdateQuerySystemUser --- Error updating query: ${err instanceof Error ? err.message : String(err)}`
|
|
151
511
|
};
|
|
152
512
|
}
|
|
153
513
|
}
|
|
@@ -217,7 +577,7 @@ export class QueryResolverExtended extends QueryResolver {
|
|
|
217
577
|
* @param contextUser - User context for operations
|
|
218
578
|
* @returns The ID of the final category in the path
|
|
219
579
|
*/
|
|
220
|
-
private async findOrCreateCategoryPath(categoryPath: string, md: Metadata, contextUser: UserInfo
|
|
580
|
+
private async findOrCreateCategoryPath(categoryPath: string, md: Metadata, contextUser: UserInfo): Promise<string> {
|
|
221
581
|
if (!categoryPath || categoryPath.trim() === '') {
|
|
222
582
|
throw new Error('CategoryPath cannot be empty');
|
|
223
583
|
}
|
|
@@ -240,23 +600,31 @@ export class QueryResolverExtended extends QueryResolver {
|
|
|
240
600
|
currentCategoryID = existingCategory.ID;
|
|
241
601
|
currentParentID = existingCategory.ID;
|
|
242
602
|
} else {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
603
|
+
try {
|
|
604
|
+
// Create new category
|
|
605
|
+
const newCategory = await md.GetEntityObject<QueryCategoryEntity>("Query Categories", contextUser);
|
|
606
|
+
if (!newCategory) {
|
|
607
|
+
throw new Error(`Failed to create entity object for Query Categories`);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
newCategory.Name = categoryName;
|
|
611
|
+
newCategory.ParentID = currentParentID;
|
|
612
|
+
newCategory.UserID = contextUser.ID;
|
|
613
|
+
newCategory.Description = `Auto-created category from path: ${categoryPath}`;
|
|
614
|
+
|
|
615
|
+
const saveResult = await newCategory.Save();
|
|
616
|
+
if (!saveResult) {
|
|
617
|
+
throw new Error(`Failed to create category '${categoryName}': ${newCategory.LatestResult?.Message || 'Unknown error'}`);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
currentCategoryID = newCategory.ID;
|
|
621
|
+
currentParentID = newCategory.ID;
|
|
622
|
+
|
|
623
|
+
// Refresh metadata after each category creation to ensure it's available for subsequent lookups
|
|
624
|
+
await md.Refresh();
|
|
625
|
+
} catch (error) {
|
|
626
|
+
throw new Error(`Failed to create category '${categoryName}': ${error instanceof Error ? error.message : String(error)}`);
|
|
253
627
|
}
|
|
254
|
-
|
|
255
|
-
currentCategoryID = newCategory.ID;
|
|
256
|
-
currentParentID = newCategory.ID;
|
|
257
|
-
|
|
258
|
-
// Refresh metadata after each category creation to ensure it's available for subsequent lookups
|
|
259
|
-
await md.Refresh();
|
|
260
628
|
}
|
|
261
629
|
}
|
|
262
630
|
|