@memberjunction/server 1.8.1 → 2.1.1
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 +130 -1
- package/CHANGELOG.md +29 -2
- package/package.json +31 -40
- package/src/apolloServer/index.ts +0 -1
- package/src/auth/newUsers.ts +4 -3
- package/src/context.ts +19 -27
- package/src/entitySubclasses/entityPermissions.server.ts +3 -3
- package/src/entitySubclasses/userViewEntity.server.ts +1 -1
- package/src/generated/generated.ts +9482 -8398
- package/src/generic/ResolverBase.ts +299 -172
- package/src/generic/RunViewResolver.ts +204 -14
- package/src/index.ts +18 -19
- package/src/resolvers/AskSkipResolver.ts +35 -35
- package/src/resolvers/ColorResolver.ts +3 -14
- package/src/resolvers/DatasetResolver.ts +4 -4
- package/src/resolvers/EntityCommunicationsResolver.ts +37 -15
- package/src/resolvers/FileCategoryResolver.ts +2 -2
- package/src/resolvers/FileResolver.ts +6 -4
- package/src/resolvers/MergeRecordsResolver.ts +3 -2
- package/src/resolvers/PotentialDuplicateRecordResolver.ts +48 -40
- package/src/resolvers/QueryResolver.ts +2 -2
- package/src/resolvers/ReportResolver.ts +6 -6
- package/src/resolvers/UserFavoriteResolver.ts +8 -8
- package/src/resolvers/UserResolver.ts +3 -1
- package/src/resolvers/UserViewResolver.ts +1 -1
- package/src/types.ts +21 -0
- package/tsconfig.json +3 -4
- package/dist/apolloServer/TransactionPlugin.d.ts +0 -4
- package/dist/apolloServer/TransactionPlugin.d.ts.map +0 -1
- package/dist/apolloServer/TransactionPlugin.js +0 -49
- package/dist/apolloServer/TransactionPlugin.js.map +0 -1
- package/dist/apolloServer/index.d.ts +0 -11
- package/dist/apolloServer/index.d.ts.map +0 -1
- package/dist/apolloServer/index.js +0 -27
- package/dist/apolloServer/index.js.map +0 -1
- package/dist/auth/exampleNewUserSubClass.d.ts +0 -6
- package/dist/auth/exampleNewUserSubClass.d.ts.map +0 -1
- package/dist/auth/exampleNewUserSubClass.js +0 -54
- package/dist/auth/exampleNewUserSubClass.js.map +0 -1
- package/dist/auth/index.d.ts +0 -30
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/index.js +0 -129
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/newUsers.d.ts +0 -5
- package/dist/auth/newUsers.d.ts.map +0 -1
- package/dist/auth/newUsers.js +0 -66
- package/dist/auth/newUsers.js.map +0 -1
- package/dist/auth/tokenExpiredError.d.ts +0 -5
- package/dist/auth/tokenExpiredError.d.ts.map +0 -1
- package/dist/auth/tokenExpiredError.js +0 -16
- package/dist/auth/tokenExpiredError.js.map +0 -1
- package/dist/cache.d.ts +0 -3
- package/dist/cache.d.ts.map +0 -1
- package/dist/cache.js +0 -11
- package/dist/cache.js.map +0 -1
- package/dist/config.d.ts +0 -196
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -72
- package/dist/config.js.map +0 -1
- package/dist/context.d.ts +0 -17
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -114
- package/dist/context.js.map +0 -1
- package/dist/directives/Public.d.ts +0 -4
- package/dist/directives/Public.d.ts.map +0 -1
- package/dist/directives/Public.js +0 -34
- package/dist/directives/Public.js.map +0 -1
- package/dist/directives/index.d.ts +0 -2
- package/dist/directives/index.d.ts.map +0 -1
- package/dist/directives/index.js +0 -18
- package/dist/directives/index.js.map +0 -1
- package/dist/entitySubclasses/DuplicateRunEntity.server.d.ts +0 -6
- package/dist/entitySubclasses/DuplicateRunEntity.server.d.ts.map +0 -1
- package/dist/entitySubclasses/DuplicateRunEntity.server.js +0 -37
- package/dist/entitySubclasses/DuplicateRunEntity.server.js.map +0 -1
- package/dist/entitySubclasses/EntityBehavior.server.d.ts +0 -29
- package/dist/entitySubclasses/EntityBehavior.server.d.ts.map +0 -1
- package/dist/entitySubclasses/EntityBehavior.server.js +0 -213
- package/dist/entitySubclasses/EntityBehavior.server.js.map +0 -1
- package/dist/entitySubclasses/entityPermissions.server.d.ts +0 -23
- package/dist/entitySubclasses/entityPermissions.server.d.ts.map +0 -1
- package/dist/entitySubclasses/entityPermissions.server.js +0 -99
- package/dist/entitySubclasses/entityPermissions.server.js.map +0 -1
- package/dist/entitySubclasses/userViewEntity.server.d.ts +0 -13
- package/dist/entitySubclasses/userViewEntity.server.d.ts.map +0 -1
- package/dist/entitySubclasses/userViewEntity.server.js +0 -164
- package/dist/entitySubclasses/userViewEntity.server.js.map +0 -1
- package/dist/generated/generated.d.ts +0 -6482
- package/dist/generated/generated.d.ts.map +0 -1
- package/dist/generated/generated.js +0 -35073
- package/dist/generated/generated.js.map +0 -1
- package/dist/generic/DeleteOptionsInput.d.ts +0 -5
- package/dist/generic/DeleteOptionsInput.d.ts.map +0 -1
- package/dist/generic/DeleteOptionsInput.js +0 -28
- package/dist/generic/DeleteOptionsInput.js.map +0 -1
- package/dist/generic/KeyInputOutputTypes.d.ts +0 -16
- package/dist/generic/KeyInputOutputTypes.d.ts.map +0 -1
- package/dist/generic/KeyInputOutputTypes.js +0 -62
- package/dist/generic/KeyInputOutputTypes.js.map +0 -1
- package/dist/generic/KeyValuePairInput.d.ts +0 -5
- package/dist/generic/KeyValuePairInput.d.ts.map +0 -1
- package/dist/generic/KeyValuePairInput.js +0 -28
- package/dist/generic/KeyValuePairInput.js.map +0 -1
- package/dist/generic/PushStatusResolver.d.ts +0 -14
- package/dist/generic/PushStatusResolver.d.ts.map +0 -1
- package/dist/generic/PushStatusResolver.js +0 -58
- package/dist/generic/PushStatusResolver.js.map +0 -1
- package/dist/generic/ResolverBase.d.ts +0 -37
- package/dist/generic/ResolverBase.d.ts.map +0 -1
- package/dist/generic/ResolverBase.js +0 -468
- package/dist/generic/ResolverBase.js.map +0 -1
- package/dist/generic/RunViewResolver.d.ts +0 -88
- package/dist/generic/RunViewResolver.d.ts.map +0 -1
- package/dist/generic/RunViewResolver.js +0 -443
- package/dist/generic/RunViewResolver.js.map +0 -1
- package/dist/index.d.ts +0 -28
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -145
- package/dist/index.js.map +0 -1
- package/dist/orm.d.ts +0 -4
- package/dist/orm.d.ts.map +0 -1
- package/dist/orm.js +0 -34
- package/dist/orm.js.map +0 -1
- package/dist/resolvers/AskSkipResolver.d.ts +0 -54
- package/dist/resolvers/AskSkipResolver.d.ts.map +0 -1
- package/dist/resolvers/AskSkipResolver.js +0 -800
- package/dist/resolvers/AskSkipResolver.js.map +0 -1
- package/dist/resolvers/ColorResolver.d.ts +0 -22
- package/dist/resolvers/ColorResolver.d.ts.map +0 -1
- package/dist/resolvers/ColorResolver.js +0 -94
- package/dist/resolvers/ColorResolver.js.map +0 -1
- package/dist/resolvers/DatasetResolver.d.ts +0 -42
- package/dist/resolvers/DatasetResolver.d.ts.map +0 -1
- package/dist/resolvers/DatasetResolver.js +0 -168
- package/dist/resolvers/DatasetResolver.js.map +0 -1
- package/dist/resolvers/EntityCommunicationsResolver.d.ts +0 -49
- package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +0 -1
- package/dist/resolvers/EntityCommunicationsResolver.js +0 -228
- package/dist/resolvers/EntityCommunicationsResolver.js.map +0 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +0 -21
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +0 -1
- package/dist/resolvers/EntityRecordNameResolver.js +0 -113
- package/dist/resolvers/EntityRecordNameResolver.js.map +0 -1
- package/dist/resolvers/EntityResolver.d.ts +0 -6
- package/dist/resolvers/EntityResolver.d.ts.map +0 -1
- package/dist/resolvers/EntityResolver.js +0 -60
- package/dist/resolvers/EntityResolver.js.map +0 -1
- package/dist/resolvers/FileCategoryResolver.d.ts +0 -6
- package/dist/resolvers/FileCategoryResolver.d.ts.map +0 -1
- package/dist/resolvers/FileCategoryResolver.js +0 -65
- package/dist/resolvers/FileCategoryResolver.js.map +0 -1
- package/dist/resolvers/FileResolver.d.ts +0 -24
- package/dist/resolvers/FileResolver.d.ts.map +0 -1
- package/dist/resolvers/FileResolver.js +0 -162
- package/dist/resolvers/FileResolver.js.map +0 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts +0 -59
- package/dist/resolvers/MergeRecordsResolver.d.ts.map +0 -1
- package/dist/resolvers/MergeRecordsResolver.js +0 -256
- package/dist/resolvers/MergeRecordsResolver.js.map +0 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts +0 -29
- package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts.map +0 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.js +0 -127
- package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +0 -1
- package/dist/resolvers/QueryResolver.d.ts +0 -13
- package/dist/resolvers/QueryResolver.d.ts.map +0 -1
- package/dist/resolvers/QueryResolver.js +0 -74
- package/dist/resolvers/QueryResolver.js.map +0 -1
- package/dist/resolvers/ReportResolver.d.ts +0 -20
- package/dist/resolvers/ReportResolver.d.ts.map +0 -1
- package/dist/resolvers/ReportResolver.js +0 -175
- package/dist/resolvers/ReportResolver.js.map +0 -1
- package/dist/resolvers/UserFavoriteResolver.d.ts +0 -42
- package/dist/resolvers/UserFavoriteResolver.d.ts.map +0 -1
- package/dist/resolvers/UserFavoriteResolver.js +0 -221
- package/dist/resolvers/UserFavoriteResolver.js.map +0 -1
- package/dist/resolvers/UserResolver.d.ts +0 -10
- package/dist/resolvers/UserResolver.d.ts.map +0 -1
- package/dist/resolvers/UserResolver.js +0 -72
- package/dist/resolvers/UserResolver.js.map +0 -1
- package/dist/resolvers/UserViewResolver.d.ts +0 -13
- package/dist/resolvers/UserViewResolver.d.ts.map +0 -1
- package/dist/resolvers/UserViewResolver.js +0 -102
- package/dist/resolvers/UserViewResolver.js.map +0 -1
- package/dist/types.d.ts +0 -17
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/util.d.ts +0 -4
- package/dist/util.d.ts.map +0 -1
- package/dist/util.js +0 -89
- package/dist/util.js.map +0 -1
- package/src/entitySubclasses/EntityBehavior.server.ts +0 -241
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { BaseEntity, CompositeKey, EntityFieldTSType, EntityPermissionType, Metadata, RunView, UserInfo } from '@memberjunction/core';
|
|
1
|
+
import { BaseEntity, CompositeKey, EntityFieldTSType, EntityPermissionType, LogError, Metadata, RunView, RunViewParams, RunViewResult, UserInfo } from '@memberjunction/core';
|
|
2
2
|
import { AuditLogEntity, UserViewEntity } from '@memberjunction/core-entities';
|
|
3
3
|
import { UserCache } from '@memberjunction/sqlserver-dataprovider';
|
|
4
4
|
import { PubSubEngine } from 'type-graphql';
|
|
5
5
|
import { GraphQLError } from 'graphql';
|
|
6
6
|
import { DataSource } from 'typeorm';
|
|
7
7
|
|
|
8
|
-
import { UserPayload } from '../types';
|
|
8
|
+
import { RunViewGenericParams, UserPayload } from '../types';
|
|
9
9
|
import { RunDynamicViewInput, RunViewByIDInput, RunViewByNameInput } from './RunViewResolver';
|
|
10
10
|
import { DeleteOptionsInput } from './DeleteOptionsInput';
|
|
11
11
|
import { MJGlobal } from '@memberjunction/global';
|
|
12
12
|
import { PUSH_STATUS_UPDATES_TOPIC } from './PushStatusResolver';
|
|
13
|
-
|
|
13
|
+
import { FieldMapper } from '@memberjunction/graphql-dataprovider';
|
|
14
|
+
|
|
14
15
|
export class ResolverBase {
|
|
15
|
-
|
|
16
16
|
protected MapFieldNamesToCodeNames(entityName: string, dataObject: any) {
|
|
17
17
|
// for the given entity name provided, check to see if there are any fields
|
|
18
18
|
// where the code name is different from the field name, and for just those
|
|
@@ -22,21 +22,19 @@ export class ResolverBase {
|
|
|
22
22
|
if (dataObject) {
|
|
23
23
|
const md = new Metadata();
|
|
24
24
|
const entityInfo = md.Entities.find((e) => e.Name === entityName);
|
|
25
|
-
if (!entityInfo)
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
25
|
+
if (!entityInfo) throw new Error(`Entity ${entityName} not found in metadata`);
|
|
26
|
+
// const fields = entityInfo.Fields.filter((f) => f.Name !== f.CodeName || f.Name.startsWith('__mj_'));
|
|
27
|
+
const mapper = new FieldMapper();
|
|
28
|
+
entityInfo.Fields.forEach((f) => {
|
|
29
29
|
if (dataObject.hasOwnProperty(f.Name)) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
dataObject[f.CodeName] = dataObject[f.Name];
|
|
30
|
+
// GraphQL doesn't allow us to pass back fields with __ so we are mapping our special field cases that start with __mj_ to _mj__ for transport - they are converted back on the other side automatically
|
|
31
|
+
const mappedFieldName = mapper.MapFieldName(f.CodeName)
|
|
32
|
+
if (mappedFieldName !== f.Name) {
|
|
33
|
+
dataObject[mappedFieldName] = dataObject[f.Name];
|
|
34
|
+
delete dataObject[f.Name];
|
|
36
35
|
}
|
|
37
|
-
delete dataObject[f.Name];
|
|
38
36
|
}
|
|
39
|
-
});
|
|
37
|
+
});
|
|
40
38
|
}
|
|
41
39
|
return dataObject;
|
|
42
40
|
}
|
|
@@ -46,7 +44,7 @@ export class ResolverBase {
|
|
|
46
44
|
if (dataObjectArray && dataObjectArray.length > 0) {
|
|
47
45
|
dataObjectArray.forEach((element) => {
|
|
48
46
|
this.MapFieldNamesToCodeNames(entityName, element);
|
|
49
|
-
});
|
|
47
|
+
});
|
|
50
48
|
}
|
|
51
49
|
return dataObjectArray;
|
|
52
50
|
}
|
|
@@ -75,7 +73,9 @@ export class ResolverBase {
|
|
|
75
73
|
|
|
76
74
|
async RunViewByNameGeneric(viewInput: RunViewByNameInput, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
|
|
77
75
|
try {
|
|
78
|
-
const viewInfo: UserViewEntity = this.safeFirstArrayElement(
|
|
76
|
+
const viewInfo: UserViewEntity = this.safeFirstArrayElement(
|
|
77
|
+
await this.findBy(dataSource, 'User Views', { Name: viewInput.ViewName })
|
|
78
|
+
);
|
|
79
79
|
return this.RunViewGenericInternal(
|
|
80
80
|
viewInfo,
|
|
81
81
|
dataSource,
|
|
@@ -134,9 +134,9 @@ export class ResolverBase {
|
|
|
134
134
|
if (!entity) throw new Error(`Entity ${viewInput.EntityName} not found in metadata`);
|
|
135
135
|
|
|
136
136
|
const viewInfo: UserViewEntity = {
|
|
137
|
-
ID:
|
|
137
|
+
ID: "",
|
|
138
138
|
Entity: viewInput.EntityName,
|
|
139
|
-
EntityID: entity.ID
|
|
139
|
+
EntityID: entity.ID,
|
|
140
140
|
EntityBaseView: entity.BaseView as string,
|
|
141
141
|
} as UserViewEntity; // only providing a few bits of data here, but it's enough to get the view to run
|
|
142
142
|
|
|
@@ -164,6 +164,71 @@ export class ResolverBase {
|
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
async RunViewsGeneric(viewInputs: (RunViewByNameInput & RunViewByIDInput & RunDynamicViewInput)[], dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
|
|
168
|
+
let md: Metadata | null = null;
|
|
169
|
+
let params: RunViewGenericParams[] = [];
|
|
170
|
+
for(const viewInput of viewInputs) {
|
|
171
|
+
try {
|
|
172
|
+
let viewInfo: UserViewEntity | null = null;
|
|
173
|
+
|
|
174
|
+
if(viewInput.ViewName) {
|
|
175
|
+
viewInfo = this.safeFirstArrayElement(
|
|
176
|
+
await this.findBy(dataSource, 'User Views', { Name: viewInput.ViewName })
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
else if(viewInput.ViewID) {
|
|
180
|
+
viewInfo = this.safeFirstArrayElement(await this.findBy(dataSource, 'User Views', { ID: viewInput.ViewID }));
|
|
181
|
+
}
|
|
182
|
+
else if(viewInput.EntityName) {
|
|
183
|
+
md = md || new Metadata();
|
|
184
|
+
const entity = md.Entities.find((e) => e.Name === viewInput.EntityName);
|
|
185
|
+
if (!entity) {
|
|
186
|
+
throw new Error(`Entity ${viewInput.EntityName} not found in metadata`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// only providing a few bits of data here, but it's enough to get the view to run
|
|
190
|
+
viewInfo = {
|
|
191
|
+
ID: "",
|
|
192
|
+
Entity: viewInput.EntityName,
|
|
193
|
+
EntityID: entity.ID,
|
|
194
|
+
EntityBaseView: entity.BaseView,
|
|
195
|
+
} as UserViewEntity;
|
|
196
|
+
}
|
|
197
|
+
else{
|
|
198
|
+
throw new Error("Unable to determine input type");
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
params.push({
|
|
202
|
+
viewInfo: viewInfo,
|
|
203
|
+
dataSource: dataSource,
|
|
204
|
+
extraFilter: viewInput.ExtraFilter,
|
|
205
|
+
orderBy: viewInput.OrderBy,
|
|
206
|
+
userSearchString: viewInput.UserSearchString,
|
|
207
|
+
excludeUserViewRunID: viewInput.ExcludeUserViewRunID,
|
|
208
|
+
overrideExcludeFilter: viewInput.OverrideExcludeFilter,
|
|
209
|
+
saveViewResults: viewInput.EntityName ? false : viewInput.SaveViewResults,
|
|
210
|
+
fields: viewInput.Fields,
|
|
211
|
+
ignoreMaxRows: viewInput.IgnoreMaxRows,
|
|
212
|
+
excludeDataFromAllPriorViewRuns: viewInput.EntityName ? false : viewInput.ExcludeDataFromAllPriorViewRuns,
|
|
213
|
+
forceAuditLog: viewInput.ForceAuditLog,
|
|
214
|
+
auditLogDescription: viewInput.AuditLogDescription,
|
|
215
|
+
resultType: viewInput.ResultType,
|
|
216
|
+
userPayload,
|
|
217
|
+
pubSub
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
} catch (err) {
|
|
221
|
+
LogError(err);
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let results: RunViewResult[] = await this.RunViewsGenericInternal(params);
|
|
227
|
+
return results;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
167
232
|
protected CheckUserReadPermissions(entityName: string, userPayload: UserPayload | null) {
|
|
168
233
|
const md = new Metadata();
|
|
169
234
|
const entityInfo = md.Entities.find((e) => e.Name === entityName);
|
|
@@ -185,7 +250,7 @@ export class ResolverBase {
|
|
|
185
250
|
extraFilter: string,
|
|
186
251
|
orderBy: string,
|
|
187
252
|
userSearchString: string,
|
|
188
|
-
excludeUserViewRunID:
|
|
253
|
+
excludeUserViewRunID: string | undefined,
|
|
189
254
|
overrideExcludeFilter: string | undefined,
|
|
190
255
|
saveViewResults: boolean | undefined,
|
|
191
256
|
fields: string[] | undefined,
|
|
@@ -241,22 +306,90 @@ export class ResolverBase {
|
|
|
241
306
|
user
|
|
242
307
|
);
|
|
243
308
|
// go through the result and convert all fields that start with __mj_*** to _mj__*** for GraphQL transport
|
|
309
|
+
const mapper = new FieldMapper();
|
|
244
310
|
if (result && result.Success) {
|
|
245
311
|
for (const r of result.Results) {
|
|
246
|
-
|
|
247
|
-
keys.forEach((k) => {
|
|
248
|
-
if (k.trim().toLowerCase().startsWith('__mj_')) {
|
|
249
|
-
r[`_mj__${k.substring(5)}`] = r[k];
|
|
250
|
-
delete r[k];
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
|
|
312
|
+
mapper.MapFields(r);
|
|
254
313
|
}
|
|
255
314
|
}
|
|
256
315
|
return result;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
|
|
316
|
+
} else return null;
|
|
317
|
+
} catch (err) {
|
|
318
|
+
console.log(err);
|
|
319
|
+
throw err;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
protected async RunViewsGenericInternal(params: RunViewGenericParams[]): Promise<RunViewResult[]> {
|
|
324
|
+
try {
|
|
325
|
+
let md: Metadata | null = null;
|
|
326
|
+
const rv = new RunView();
|
|
327
|
+
let RunViewParams: RunViewParams[] = [];
|
|
328
|
+
let contextUser: UserInfo | null = null;
|
|
329
|
+
for(const param of params){
|
|
330
|
+
if (param.viewInfo && param.userPayload) {
|
|
331
|
+
md = md || new Metadata();
|
|
332
|
+
const user: UserInfo = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === param.userPayload?.email.toLowerCase().trim());
|
|
333
|
+
if (!user) {
|
|
334
|
+
throw new Error(`User ${param.userPayload?.email} not found in metadata`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
contextUser = contextUser || user;
|
|
338
|
+
|
|
339
|
+
const entityInfo = md.Entities.find((e) => e.Name === param.viewInfo.Entity);
|
|
340
|
+
if (!entityInfo){
|
|
341
|
+
throw new Error(`Entity ${param.viewInfo.Entity} not found in metadata`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// figure out the result type from the input string (if provided)
|
|
346
|
+
let rt: 'simple' | 'entity_object' | 'count_only' = 'simple';
|
|
347
|
+
switch (param.resultType?.trim().toLowerCase()) {
|
|
348
|
+
case 'count_only':
|
|
349
|
+
rt = 'count_only';
|
|
350
|
+
break;
|
|
351
|
+
// use simple as the default AND for entity_object
|
|
352
|
+
// becuase on teh server we don't really pass back
|
|
353
|
+
// a true entity_object anyway, just passing back
|
|
354
|
+
// the simple object anyway
|
|
355
|
+
case 'entity_object':
|
|
356
|
+
default:
|
|
357
|
+
rt = 'simple';
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
RunViewParams.push({
|
|
362
|
+
ViewID: param.viewInfo.ID,
|
|
363
|
+
ViewName: param.viewInfo.Name,
|
|
364
|
+
EntityName: param.viewInfo.Entity,
|
|
365
|
+
ExtraFilter: param.extraFilter,
|
|
366
|
+
OrderBy: param.orderBy,
|
|
367
|
+
Fields: param.fields,
|
|
368
|
+
UserSearchString: param.userSearchString,
|
|
369
|
+
ExcludeUserViewRunID: param.excludeUserViewRunID,
|
|
370
|
+
OverrideExcludeFilter: param.overrideExcludeFilter,
|
|
371
|
+
SaveViewResults: param.saveViewResults,
|
|
372
|
+
ExcludeDataFromAllPriorViewRuns: param.excludeDataFromAllPriorViewRuns,
|
|
373
|
+
IgnoreMaxRows: param.ignoreMaxRows,
|
|
374
|
+
ForceAuditLog: param.forceAuditLog,
|
|
375
|
+
AuditLogDescription: param.auditLogDescription,
|
|
376
|
+
ResultType: rt,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
let runViewResults: RunViewResult[] = await rv.RunViews(RunViewParams, contextUser);
|
|
381
|
+
|
|
382
|
+
// go through the result and convert all fields that start with __mj_*** to _mj__*** for GraphQL transport
|
|
383
|
+
const mapper = new FieldMapper();
|
|
384
|
+
for(const runViewResult of runViewResults){
|
|
385
|
+
if (runViewResult && runViewResult.Success) {
|
|
386
|
+
for (const result of runViewResult.Results) {
|
|
387
|
+
mapper.MapFields(result);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return runViewResults;
|
|
260
393
|
}
|
|
261
394
|
catch (err) {
|
|
262
395
|
console.log(err);
|
|
@@ -301,7 +434,7 @@ export class ResolverBase {
|
|
|
301
434
|
auditLogTypeName: string,
|
|
302
435
|
status: string,
|
|
303
436
|
details: string | null,
|
|
304
|
-
entityId:
|
|
437
|
+
entityId: string,
|
|
305
438
|
recordId: any | null
|
|
306
439
|
): Promise<any> {
|
|
307
440
|
try {
|
|
@@ -318,30 +451,23 @@ export class ResolverBase {
|
|
|
318
451
|
const auditLog = await md.GetEntityObject<AuditLogEntity>('Audit Logs', userInfo); // must pass user context on back end as we're not authenticated the same way as the front end
|
|
319
452
|
auditLog.NewRecord();
|
|
320
453
|
auditLog.UserID = userInfo.ID;
|
|
321
|
-
auditLog.
|
|
454
|
+
auditLog.AuditLogTypeID = auditLogType.ID;
|
|
322
455
|
|
|
323
456
|
if (authorization)
|
|
324
|
-
auditLog.
|
|
457
|
+
auditLog.AuthorizationID = authorization.ID;
|
|
325
458
|
|
|
326
|
-
if (status?.trim().toLowerCase() === 'success')
|
|
327
|
-
|
|
328
|
-
else
|
|
329
|
-
auditLog.Status = "Failed";
|
|
459
|
+
if (status?.trim().toLowerCase() === 'success') auditLog.Status = 'Success';
|
|
460
|
+
else auditLog.Status = 'Failed';
|
|
330
461
|
|
|
331
|
-
if (details)
|
|
332
|
-
auditLog.Details = details;
|
|
462
|
+
if (details) auditLog.Details = details;
|
|
333
463
|
|
|
334
464
|
auditLog.EntityID = entityId;
|
|
335
465
|
|
|
336
|
-
if (recordId)
|
|
337
|
-
auditLog.RecordID = recordId;
|
|
466
|
+
if (recordId) auditLog.RecordID = recordId;
|
|
338
467
|
|
|
339
|
-
if (await auditLog.Save())
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
throw new Error(`Error saving audit log record`);
|
|
343
|
-
}
|
|
344
|
-
catch (err) {
|
|
468
|
+
if (await auditLog.Save()) return auditLog;
|
|
469
|
+
else throw new Error(`Error saving audit log record`);
|
|
470
|
+
} catch (err) {
|
|
345
471
|
console.log(err);
|
|
346
472
|
return null;
|
|
347
473
|
}
|
|
@@ -385,118 +511,115 @@ export class ResolverBase {
|
|
|
385
511
|
type: 'EntityObjectStatusMessage',
|
|
386
512
|
entityName: entityObject.EntityInfo.Name,
|
|
387
513
|
primaryKey: entityObject.PrimaryKey,
|
|
388
|
-
message: event.args.message
|
|
514
|
+
message: event.args.message,
|
|
389
515
|
}),
|
|
390
|
-
sessionId: userPayload.sessionId
|
|
391
|
-
});
|
|
516
|
+
sessionId: userPayload.sessionId,
|
|
517
|
+
});
|
|
392
518
|
}
|
|
393
519
|
}
|
|
394
520
|
});
|
|
395
521
|
}
|
|
396
522
|
|
|
397
523
|
protected async CreateRecord(entityName: string, input: any, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
|
|
398
|
-
if (await this.BeforeCreate(dataSource, input)) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
414
|
-
else
|
|
415
|
-
return null;
|
|
524
|
+
if (await this.BeforeCreate(dataSource, input)) {
|
|
525
|
+
// fire event and proceed if it wasn't cancelled
|
|
526
|
+
const entityObject = await new Metadata().GetEntityObject(entityName, this.GetUserFromPayload(userPayload));
|
|
527
|
+
entityObject.NewRecord();
|
|
528
|
+
entityObject.SetMany(input);
|
|
529
|
+
|
|
530
|
+
this.ListenForEntityMessages(entityObject, pubSub, userPayload);
|
|
531
|
+
|
|
532
|
+
if (await entityObject.Save()) {
|
|
533
|
+
// save worked, fire the AfterCreate event and then return all the data
|
|
534
|
+
await this.AfterCreate(dataSource, input); // fire event
|
|
535
|
+
return this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll());
|
|
536
|
+
}
|
|
537
|
+
// save failed, return null
|
|
538
|
+
else throw entityObject.LatestResult.Message;
|
|
539
|
+
} else return null;
|
|
416
540
|
}
|
|
417
541
|
|
|
418
542
|
// Before/After CREATE Event Hooks for Sub-Classes to Override
|
|
419
543
|
protected async BeforeCreate(dataSource: DataSource, input: any): Promise<boolean> {
|
|
420
|
-
|
|
544
|
+
return true;
|
|
421
545
|
}
|
|
422
|
-
protected async AfterCreate(dataSource: DataSource, input: any) {
|
|
423
|
-
}
|
|
424
|
-
|
|
546
|
+
protected async AfterCreate(dataSource: DataSource, input: any) {}
|
|
425
547
|
|
|
426
548
|
protected async UpdateRecord(entityName: string, input: any, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
else {
|
|
458
|
-
//
|
|
459
|
-
|
|
460
|
-
extensions: { code: 'LOAD_ENTITY_ERROR', entityName },
|
|
461
|
-
});
|
|
549
|
+
if (await this.BeforeUpdate(dataSource, input)) {
|
|
550
|
+
// fire event and proceed if it wasn't cancelled
|
|
551
|
+
const md = new Metadata();
|
|
552
|
+
const userInfo = this.GetUserFromPayload(userPayload);
|
|
553
|
+
const entityObject = await md.GetEntityObject(entityName, userInfo);
|
|
554
|
+
const entityInfo = entityObject.EntityInfo;
|
|
555
|
+
const clientNewValues = {};
|
|
556
|
+
Object.keys(input).forEach((key) => {
|
|
557
|
+
if (key !== 'OldValues___')
|
|
558
|
+
clientNewValues[key] = input[key];
|
|
559
|
+
}); // grab all the props except for the OldValues property
|
|
560
|
+
|
|
561
|
+
if (entityInfo.TrackRecordChanges || !input.OldValues___) {
|
|
562
|
+
// the entity tracks record changes, so we need to load the old values from the DB to make sure they are not inconsistent
|
|
563
|
+
// with the old values from the input.OldValues property. If they are different, but on different fields, we allow it
|
|
564
|
+
// but if they are different on fields that the current UpdateRecord call is trying to update, we throw an error.
|
|
565
|
+
const cKey = new CompositeKey(
|
|
566
|
+
entityInfo.PrimaryKeys.map((pk) => {
|
|
567
|
+
return {
|
|
568
|
+
FieldName: pk.Name,
|
|
569
|
+
Value: input[pk.CodeName],
|
|
570
|
+
};
|
|
571
|
+
})
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
if (await entityObject.InnerLoad(cKey)) {
|
|
575
|
+
// load worked, now, if we HAVE OldValues, we need to check them against the values in the DB we just loaded.
|
|
576
|
+
if (!input.OldValues___) {
|
|
577
|
+
// no OldValues, so we can just set the new values from input
|
|
578
|
+
entityObject.SetMany(input);
|
|
579
|
+
} else {
|
|
580
|
+
// we DO have OldValues, so we need to do a more in depth analysis
|
|
581
|
+
this.TestAndSetClientOldValuesToDBValues(input, clientNewValues, entityObject);
|
|
462
582
|
}
|
|
583
|
+
} else {
|
|
584
|
+
// save failed, return null
|
|
585
|
+
throw new GraphQLError(`Record not found for ${entityName} with key ${JSON.stringify(cKey)}`, {
|
|
586
|
+
extensions: { code: 'LOAD_ENTITY_ERROR', entityName },
|
|
587
|
+
});
|
|
463
588
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
// 1) load the old values, this will be the initial state of the object
|
|
471
|
-
entityObject.LoadFromData(oldValues);
|
|
589
|
+
} else {
|
|
590
|
+
// not tracking changes and we DO have OldValues, so we can load from them
|
|
591
|
+
const oldValues = {};
|
|
592
|
+
// for each item in the oldValues array, add it to the oldValues object
|
|
593
|
+
input.OldValues___?.forEach((item) => (oldValues[item.Key] = item.Value));
|
|
472
594
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
595
|
+
// 1) load the old values, this will be the initial state of the object
|
|
596
|
+
entityObject.LoadFromData(oldValues);
|
|
476
597
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
// save worked, fire afterevent and return all the data
|
|
480
|
-
await this.AfterUpdate(dataSource, input); // fire event
|
|
481
|
-
return this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll());
|
|
482
|
-
}
|
|
483
|
-
else {
|
|
484
|
-
throw new GraphQLError(entityObject.LatestResult?.Message ?? 'Unknown error', {
|
|
485
|
-
extensions: { code: 'SAVE_ENTITY_ERROR', entityName },
|
|
486
|
-
});
|
|
487
|
-
}
|
|
598
|
+
// 2) set the new values from the input, not including the OldValues property
|
|
599
|
+
entityObject.SetMany(clientNewValues);
|
|
488
600
|
}
|
|
489
|
-
|
|
490
|
-
|
|
601
|
+
|
|
602
|
+
this.ListenForEntityMessages(entityObject, pubSub, userPayload);
|
|
603
|
+
if (await entityObject.Save()) {
|
|
604
|
+
// save worked, fire afterevent and return all the data
|
|
605
|
+
await this.AfterUpdate(dataSource, input); // fire event
|
|
606
|
+
return this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll());
|
|
607
|
+
} else {
|
|
608
|
+
throw new GraphQLError(entityObject.LatestResult?.Message ?? 'Unknown error', {
|
|
491
609
|
extensions: { code: 'SAVE_ENTITY_ERROR', entityName },
|
|
492
610
|
});
|
|
493
|
-
}
|
|
611
|
+
}
|
|
612
|
+
} else
|
|
613
|
+
throw new GraphQLError('Save Canceled by BeforeSave() handler in ResolverBase', {
|
|
614
|
+
extensions: { code: 'SAVE_ENTITY_ERROR', entityName },
|
|
615
|
+
});
|
|
616
|
+
}
|
|
494
617
|
|
|
495
618
|
/**
|
|
496
|
-
* 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
|
|
619
|
+
* 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
|
|
497
620
|
* is trying to update any of those fields (e.g. overlap). If there is overlap, we throw an error. If there is no overlap, we can proceed with the update even if the DB Values
|
|
498
621
|
* and the ClientOldValues are not 100% the same, so long as there is no overlap in the specific FIELDS that are different.
|
|
499
|
-
*
|
|
622
|
+
*
|
|
500
623
|
* ASSUMES: input object has an OldValues___ property that is an array of Key/Value pairs that represent the old values of the record that the client is trying to update.
|
|
501
624
|
*/
|
|
502
625
|
protected TestAndSetClientOldValuesToDBValues(input: any, clientNewValues: any, entityObject: BaseEntity) {
|
|
@@ -507,8 +630,8 @@ export class ResolverBase {
|
|
|
507
630
|
// we need to do a quick transform on the values to make sure they match the TS Type for the given field because item.Value will always be a string
|
|
508
631
|
const field = entityObject.EntityInfo.Fields.find((f) => f.CodeName === item.Key);
|
|
509
632
|
let val = item.Value;
|
|
510
|
-
if (
|
|
511
|
-
val = field.DefaultValue; // set default value as the field was never set
|
|
633
|
+
if ((val === null || val === undefined) && field.DefaultValue !== null && field.DefaultValue !== undefined && !field.AllowsNull)
|
|
634
|
+
val = field.DefaultValue; // set default value as the field was never set and it does NOT allow nulls
|
|
512
635
|
|
|
513
636
|
if (field) {
|
|
514
637
|
switch (field.TSType) {
|
|
@@ -516,12 +639,11 @@ export class ResolverBase {
|
|
|
516
639
|
val = val !== null && val !== undefined ? parseInt(val) : null;
|
|
517
640
|
break;
|
|
518
641
|
case EntityFieldTSType.Boolean:
|
|
519
|
-
val =
|
|
642
|
+
val = val === null || val === undefined || val === 'false' || val === '0' || parseInt(val) === 0 ? false : true;
|
|
520
643
|
break;
|
|
521
644
|
case EntityFieldTSType.Date:
|
|
522
|
-
// first, if val is a string and it is actually a number (milliseconds since epoch), convert it to a number.
|
|
523
|
-
if (val !== null && val !== undefined && val.toString().trim() !== '' && !isNaN(val))
|
|
524
|
-
val = parseInt(val);
|
|
645
|
+
// first, if val is a string and it is actually a number (milliseconds since epoch), convert it to a number.
|
|
646
|
+
if (val !== null && val !== undefined && val.toString().trim() !== '' && !isNaN(val)) val = parseInt(val);
|
|
525
647
|
|
|
526
648
|
val = val !== null && val !== undefined ? new Date(val) : null;
|
|
527
649
|
break;
|
|
@@ -529,7 +651,7 @@ export class ResolverBase {
|
|
|
529
651
|
break; // already a string
|
|
530
652
|
}
|
|
531
653
|
}
|
|
532
|
-
clientOldValues[item.Key] = val
|
|
654
|
+
clientOldValues[item.Key] = val;
|
|
533
655
|
});
|
|
534
656
|
|
|
535
657
|
// clientOldValues now has all of the oldValues the CLIENT passed us. Now we need to build the same kind of object
|
|
@@ -557,12 +679,12 @@ export class ResolverBase {
|
|
|
557
679
|
different = clientOldValues[key] !== dbValues[key];
|
|
558
680
|
break;
|
|
559
681
|
}
|
|
560
|
-
if (different && f && !f.ReadOnly
|
|
682
|
+
if (different && f && !f.ReadOnly) {
|
|
561
683
|
// only include updateable fields
|
|
562
684
|
dbDifferences.push({
|
|
563
685
|
FieldName: key,
|
|
564
686
|
ClientOldValue: clientOldValues[key],
|
|
565
|
-
DBValue: dbValues[key]
|
|
687
|
+
DBValue: dbValues[key],
|
|
566
688
|
});
|
|
567
689
|
}
|
|
568
690
|
});
|
|
@@ -578,7 +700,7 @@ export class ResolverBase {
|
|
|
578
700
|
clientDifferences.push({
|
|
579
701
|
FieldName: key,
|
|
580
702
|
ClientOldValue: clientOldValues[key],
|
|
581
|
-
ClientNewValue: clientNewValues[key]
|
|
703
|
+
ClientNewValue: clientNewValues[key],
|
|
582
704
|
});
|
|
583
705
|
}
|
|
584
706
|
});
|
|
@@ -588,36 +710,43 @@ export class ResolverBase {
|
|
|
588
710
|
const overlap = clientDifferences.filter((cd) => dbDifferences.find((dd) => dd.FieldName === cd.FieldName));
|
|
589
711
|
if (overlap.length > 0) {
|
|
590
712
|
const msg = {
|
|
591
|
-
Message:
|
|
713
|
+
Message:
|
|
714
|
+
'Inconsistency between old values provided for changed fields, and the values of one or more of those fields in the database. Update operation cancelled.',
|
|
592
715
|
ClientDifferences: clientDifferences,
|
|
593
716
|
DBDifferences: dbDifferences,
|
|
594
|
-
Overlap: overlap
|
|
717
|
+
Overlap: overlap,
|
|
595
718
|
};
|
|
596
719
|
throw new Error(JSON.stringify(msg));
|
|
597
720
|
}
|
|
598
721
|
}
|
|
599
722
|
|
|
600
|
-
// If we get here that means we've not thrown an exception, so there is
|
|
723
|
+
// If we get here that means we've not thrown an exception, so there is
|
|
601
724
|
// NO OVERLAP, so we can set the new values from the data provided from the client now...
|
|
602
725
|
entityObject.SetMany(clientNewValues);
|
|
603
726
|
}
|
|
604
727
|
|
|
605
|
-
protected async DeleteRecord(
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
728
|
+
protected async DeleteRecord(
|
|
729
|
+
entityName: string,
|
|
730
|
+
key: CompositeKey,
|
|
731
|
+
options: DeleteOptionsInput,
|
|
732
|
+
dataSource: DataSource,
|
|
733
|
+
userPayload: UserPayload,
|
|
734
|
+
pubSub: PubSubEngine
|
|
735
|
+
) {
|
|
736
|
+
if (await this.BeforeDelete(dataSource, key)) {
|
|
737
|
+
// fire event and proceed if it wasn't cancelled
|
|
738
|
+
const entityObject = await new Metadata().GetEntityObject(entityName, this.GetUserFromPayload(userPayload));
|
|
739
|
+
await entityObject.InnerLoad(key);
|
|
740
|
+
const returnValue = entityObject.GetAll(); // grab the values before we delete so we can return last state before delete if we are successful.
|
|
741
|
+
if (await entityObject.Delete(options)) {
|
|
742
|
+
await this.AfterDelete(dataSource, key); // fire event
|
|
743
|
+
return returnValue;
|
|
744
|
+
} else {
|
|
745
|
+
throw new GraphQLError(entityObject.LatestResult?.Message ?? 'Unknown error', {
|
|
746
|
+
extensions: { code: 'DELETE_ENTITY_ERROR', entityName },
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
} else {
|
|
621
750
|
throw new GraphQLError('Delete operation canceled by BeforeDelete() handler in ResolverBase', {
|
|
622
751
|
extensions: { code: 'DELETE_ENTITY_ERROR', entityName },
|
|
623
752
|
});
|
|
@@ -625,16 +754,14 @@ export class ResolverBase {
|
|
|
625
754
|
}
|
|
626
755
|
|
|
627
756
|
// Before/After DELETE Event Hooks for Sub-Classes to Override
|
|
628
|
-
protected async BeforeDelete(dataSource: DataSource, key: CompositeKey
|
|
629
|
-
|
|
630
|
-
}
|
|
631
|
-
protected async AfterDelete(dataSource: DataSource, key: CompositeKey,) {
|
|
757
|
+
protected async BeforeDelete(dataSource: DataSource, key: CompositeKey): Promise<boolean> {
|
|
758
|
+
return true;
|
|
632
759
|
}
|
|
760
|
+
protected async AfterDelete(dataSource: DataSource, key: CompositeKey) {}
|
|
633
761
|
|
|
634
762
|
// Before/After UPDATE Event Hooks for Sub-Classes to Override
|
|
635
763
|
protected async BeforeUpdate(dataSource: DataSource, input: any): Promise<boolean> {
|
|
636
|
-
|
|
764
|
+
return true;
|
|
637
765
|
}
|
|
638
|
-
protected async AfterUpdate(dataSource: DataSource, input: any) {
|
|
639
|
-
}
|
|
766
|
+
protected async AfterUpdate(dataSource: DataSource, input: any) {}
|
|
640
767
|
}
|