@memberjunction/server 1.8.1 → 2.0.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/CHANGELOG.json +1 -1
- package/CHANGELOG.md +2 -2
- package/package.json +30 -39
- 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/generated/generated.ts +9030 -8051
- package/src/generic/ResolverBase.ts +297 -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,114 @@ 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
|
-
|
|
458
|
-
|
|
459
|
-
throw new GraphQLError(`Record not found for ${entityName} with key ${JSON.stringify(cKey)}`, {
|
|
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___') clientNewValues[key] = input[key];
|
|
558
|
+
}); // grab all the props except for the OldValues property
|
|
559
|
+
|
|
560
|
+
if (entityInfo.TrackRecordChanges || !input.OldValues___) {
|
|
561
|
+
// the entity tracks record changes, so we need to load the old values from the DB to make sure they are not inconsistent
|
|
562
|
+
// with the old values from the input.OldValues property. If they are different, but on different fields, we allow it
|
|
563
|
+
// but if they are different on fields that the current UpdateRecord call is trying to update, we throw an error.
|
|
564
|
+
const cKey = new CompositeKey(
|
|
565
|
+
entityInfo.PrimaryKeys.map((pk) => {
|
|
566
|
+
return {
|
|
567
|
+
FieldName: pk.Name,
|
|
568
|
+
Value: input[pk.CodeName],
|
|
569
|
+
};
|
|
570
|
+
})
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
if (await entityObject.InnerLoad(cKey)) {
|
|
574
|
+
// load worked, now, if we HAVE OldValues, we need to check them against the values in the DB we just loaded.
|
|
575
|
+
if (!input.OldValues___) {
|
|
576
|
+
// no OldValues, so we can just set the new values from input
|
|
577
|
+
entityObject.SetMany(input);
|
|
578
|
+
} else {
|
|
579
|
+
// we DO have OldValues, so we need to do a more in depth analysis
|
|
580
|
+
this.TestAndSetClientOldValuesToDBValues(input, clientNewValues, entityObject);
|
|
462
581
|
}
|
|
582
|
+
} else {
|
|
583
|
+
// save failed, return null
|
|
584
|
+
throw new GraphQLError(`Record not found for ${entityName} with key ${JSON.stringify(cKey)}`, {
|
|
585
|
+
extensions: { code: 'LOAD_ENTITY_ERROR', entityName },
|
|
586
|
+
});
|
|
463
587
|
}
|
|
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);
|
|
588
|
+
} else {
|
|
589
|
+
// not tracking changes and we DO have OldValues, so we can load from them
|
|
590
|
+
const oldValues = {};
|
|
591
|
+
// for each item in the oldValues array, add it to the oldValues object
|
|
592
|
+
input.OldValues___?.forEach((item) => (oldValues[item.Key] = item.Value));
|
|
472
593
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
594
|
+
// 1) load the old values, this will be the initial state of the object
|
|
595
|
+
entityObject.LoadFromData(oldValues);
|
|
476
596
|
|
|
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
|
-
}
|
|
597
|
+
// 2) set the new values from the input, not including the OldValues property
|
|
598
|
+
entityObject.SetMany(clientNewValues);
|
|
488
599
|
}
|
|
489
|
-
|
|
490
|
-
|
|
600
|
+
|
|
601
|
+
this.ListenForEntityMessages(entityObject, pubSub, userPayload);
|
|
602
|
+
if (await entityObject.Save()) {
|
|
603
|
+
// save worked, fire afterevent and return all the data
|
|
604
|
+
await this.AfterUpdate(dataSource, input); // fire event
|
|
605
|
+
return this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll());
|
|
606
|
+
} else {
|
|
607
|
+
throw new GraphQLError(entityObject.LatestResult?.Message ?? 'Unknown error', {
|
|
491
608
|
extensions: { code: 'SAVE_ENTITY_ERROR', entityName },
|
|
492
609
|
});
|
|
493
|
-
}
|
|
610
|
+
}
|
|
611
|
+
} else
|
|
612
|
+
throw new GraphQLError('Save Canceled by BeforeSave() handler in ResolverBase', {
|
|
613
|
+
extensions: { code: 'SAVE_ENTITY_ERROR', entityName },
|
|
614
|
+
});
|
|
615
|
+
}
|
|
494
616
|
|
|
495
617
|
/**
|
|
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
|
|
618
|
+
* 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
619
|
* 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
620
|
* and the ClientOldValues are not 100% the same, so long as there is no overlap in the specific FIELDS that are different.
|
|
499
|
-
*
|
|
621
|
+
*
|
|
500
622
|
* 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
623
|
*/
|
|
502
624
|
protected TestAndSetClientOldValuesToDBValues(input: any, clientNewValues: any, entityObject: BaseEntity) {
|
|
@@ -507,8 +629,7 @@ export class ResolverBase {
|
|
|
507
629
|
// 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
630
|
const field = entityObject.EntityInfo.Fields.find((f) => f.CodeName === item.Key);
|
|
509
631
|
let val = item.Value;
|
|
510
|
-
if (
|
|
511
|
-
val = field.DefaultValue; // set default value as the field was never set
|
|
632
|
+
if ((val === null || val === undefined) && field.DefaultValue !== null && field.DefaultValue !== undefined) val = field.DefaultValue; // set default value as the field was never set
|
|
512
633
|
|
|
513
634
|
if (field) {
|
|
514
635
|
switch (field.TSType) {
|
|
@@ -516,12 +637,11 @@ export class ResolverBase {
|
|
|
516
637
|
val = val !== null && val !== undefined ? parseInt(val) : null;
|
|
517
638
|
break;
|
|
518
639
|
case EntityFieldTSType.Boolean:
|
|
519
|
-
val =
|
|
640
|
+
val = val === null || val === undefined || val === 'false' || val === '0' || parseInt(val) === 0 ? false : true;
|
|
520
641
|
break;
|
|
521
642
|
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);
|
|
643
|
+
// first, if val is a string and it is actually a number (milliseconds since epoch), convert it to a number.
|
|
644
|
+
if (val !== null && val !== undefined && val.toString().trim() !== '' && !isNaN(val)) val = parseInt(val);
|
|
525
645
|
|
|
526
646
|
val = val !== null && val !== undefined ? new Date(val) : null;
|
|
527
647
|
break;
|
|
@@ -529,7 +649,7 @@ export class ResolverBase {
|
|
|
529
649
|
break; // already a string
|
|
530
650
|
}
|
|
531
651
|
}
|
|
532
|
-
clientOldValues[item.Key] = val
|
|
652
|
+
clientOldValues[item.Key] = val;
|
|
533
653
|
});
|
|
534
654
|
|
|
535
655
|
// clientOldValues now has all of the oldValues the CLIENT passed us. Now we need to build the same kind of object
|
|
@@ -557,12 +677,12 @@ export class ResolverBase {
|
|
|
557
677
|
different = clientOldValues[key] !== dbValues[key];
|
|
558
678
|
break;
|
|
559
679
|
}
|
|
560
|
-
if (different && f && !f.ReadOnly
|
|
680
|
+
if (different && f && !f.ReadOnly) {
|
|
561
681
|
// only include updateable fields
|
|
562
682
|
dbDifferences.push({
|
|
563
683
|
FieldName: key,
|
|
564
684
|
ClientOldValue: clientOldValues[key],
|
|
565
|
-
DBValue: dbValues[key]
|
|
685
|
+
DBValue: dbValues[key],
|
|
566
686
|
});
|
|
567
687
|
}
|
|
568
688
|
});
|
|
@@ -578,7 +698,7 @@ export class ResolverBase {
|
|
|
578
698
|
clientDifferences.push({
|
|
579
699
|
FieldName: key,
|
|
580
700
|
ClientOldValue: clientOldValues[key],
|
|
581
|
-
ClientNewValue: clientNewValues[key]
|
|
701
|
+
ClientNewValue: clientNewValues[key],
|
|
582
702
|
});
|
|
583
703
|
}
|
|
584
704
|
});
|
|
@@ -588,36 +708,43 @@ export class ResolverBase {
|
|
|
588
708
|
const overlap = clientDifferences.filter((cd) => dbDifferences.find((dd) => dd.FieldName === cd.FieldName));
|
|
589
709
|
if (overlap.length > 0) {
|
|
590
710
|
const msg = {
|
|
591
|
-
Message:
|
|
711
|
+
Message:
|
|
712
|
+
'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
713
|
ClientDifferences: clientDifferences,
|
|
593
714
|
DBDifferences: dbDifferences,
|
|
594
|
-
Overlap: overlap
|
|
715
|
+
Overlap: overlap,
|
|
595
716
|
};
|
|
596
717
|
throw new Error(JSON.stringify(msg));
|
|
597
718
|
}
|
|
598
719
|
}
|
|
599
720
|
|
|
600
|
-
// If we get here that means we've not thrown an exception, so there is
|
|
721
|
+
// If we get here that means we've not thrown an exception, so there is
|
|
601
722
|
// NO OVERLAP, so we can set the new values from the data provided from the client now...
|
|
602
723
|
entityObject.SetMany(clientNewValues);
|
|
603
724
|
}
|
|
604
725
|
|
|
605
|
-
protected async DeleteRecord(
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
726
|
+
protected async DeleteRecord(
|
|
727
|
+
entityName: string,
|
|
728
|
+
key: CompositeKey,
|
|
729
|
+
options: DeleteOptionsInput,
|
|
730
|
+
dataSource: DataSource,
|
|
731
|
+
userPayload: UserPayload,
|
|
732
|
+
pubSub: PubSubEngine
|
|
733
|
+
) {
|
|
734
|
+
if (await this.BeforeDelete(dataSource, key)) {
|
|
735
|
+
// fire event and proceed if it wasn't cancelled
|
|
736
|
+
const entityObject = await new Metadata().GetEntityObject(entityName, this.GetUserFromPayload(userPayload));
|
|
737
|
+
await entityObject.InnerLoad(key);
|
|
738
|
+
const returnValue = entityObject.GetAll(); // grab the values before we delete so we can return last state before delete if we are successful.
|
|
739
|
+
if (await entityObject.Delete(options)) {
|
|
740
|
+
await this.AfterDelete(dataSource, key); // fire event
|
|
741
|
+
return returnValue;
|
|
742
|
+
} else {
|
|
743
|
+
throw new GraphQLError(entityObject.LatestResult?.Message ?? 'Unknown error', {
|
|
744
|
+
extensions: { code: 'DELETE_ENTITY_ERROR', entityName },
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
} else {
|
|
621
748
|
throw new GraphQLError('Delete operation canceled by BeforeDelete() handler in ResolverBase', {
|
|
622
749
|
extensions: { code: 'DELETE_ENTITY_ERROR', entityName },
|
|
623
750
|
});
|
|
@@ -625,16 +752,14 @@ export class ResolverBase {
|
|
|
625
752
|
}
|
|
626
753
|
|
|
627
754
|
// 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,) {
|
|
755
|
+
protected async BeforeDelete(dataSource: DataSource, key: CompositeKey): Promise<boolean> {
|
|
756
|
+
return true;
|
|
632
757
|
}
|
|
758
|
+
protected async AfterDelete(dataSource: DataSource, key: CompositeKey) {}
|
|
633
759
|
|
|
634
760
|
// Before/After UPDATE Event Hooks for Sub-Classes to Override
|
|
635
761
|
protected async BeforeUpdate(dataSource: DataSource, input: any): Promise<boolean> {
|
|
636
|
-
|
|
762
|
+
return true;
|
|
637
763
|
}
|
|
638
|
-
protected async AfterUpdate(dataSource: DataSource, input: any) {
|
|
639
|
-
}
|
|
764
|
+
protected async AfterUpdate(dataSource: DataSource, input: any) {}
|
|
640
765
|
}
|