@memberjunction/server 1.0.6 → 1.0.7-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/.eslintignore +4 -4
  2. package/.eslintrc +24 -24
  3. package/CHANGELOG.json +92 -0
  4. package/CHANGELOG.md +25 -0
  5. package/README.md +141 -141
  6. package/dist/config.d.ts +3 -0
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +4 -1
  9. package/dist/config.js.map +1 -1
  10. package/dist/entitySubclasses/entityPermissions.server.d.ts +23 -0
  11. package/dist/entitySubclasses/entityPermissions.server.d.ts.map +1 -0
  12. package/dist/entitySubclasses/entityPermissions.server.js +99 -0
  13. package/dist/entitySubclasses/entityPermissions.server.js.map +1 -0
  14. package/dist/entitySubclasses/userViewEntity.server.js +17 -17
  15. package/dist/generated/generated.d.ts.map +1 -1
  16. package/dist/generated/generated.js.map +1 -1
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +1 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/resolvers/AskSkipResolver.js +10 -10
  22. package/dist/resolvers/FileCategoryResolver.js +2 -2
  23. package/dist/resolvers/ReportResolver.js +4 -4
  24. package/package.json +80 -80
  25. package/src/apolloServer/TransactionPlugin.ts +57 -57
  26. package/src/apolloServer/index.ts +33 -33
  27. package/src/auth/exampleNewUserSubClass.ts +73 -73
  28. package/src/auth/index.ts +151 -151
  29. package/src/auth/newUsers.ts +56 -56
  30. package/src/auth/tokenExpiredError.ts +12 -12
  31. package/src/cache.ts +10 -10
  32. package/src/config.ts +89 -84
  33. package/src/context.ts +119 -119
  34. package/src/directives/Public.ts +42 -42
  35. package/src/directives/index.ts +1 -1
  36. package/src/entitySubclasses/entityPermissions.server.ts +111 -0
  37. package/src/entitySubclasses/userViewEntity.server.ts +187 -187
  38. package/src/generated/generated.ts +2573 -2573
  39. package/src/generic/PushStatusResolver.ts +40 -40
  40. package/src/generic/ResolverBase.ts +331 -331
  41. package/src/generic/RunViewResolver.ts +350 -350
  42. package/src/index.ts +133 -137
  43. package/src/orm.ts +36 -36
  44. package/src/resolvers/AskSkipResolver.ts +782 -782
  45. package/src/resolvers/ColorResolver.ts +72 -72
  46. package/src/resolvers/DatasetResolver.ts +115 -115
  47. package/src/resolvers/EntityRecordNameResolver.ts +77 -77
  48. package/src/resolvers/EntityResolver.ts +37 -37
  49. package/src/resolvers/FileCategoryResolver.ts +38 -38
  50. package/src/resolvers/FileResolver.ts +110 -110
  51. package/src/resolvers/MergeRecordsResolver.ts +198 -198
  52. package/src/resolvers/PotentialDuplicateRecordResolver.ts +59 -59
  53. package/src/resolvers/QueryResolver.ts +42 -42
  54. package/src/resolvers/ReportResolver.ts +131 -131
  55. package/src/resolvers/UserFavoriteResolver.ts +102 -102
  56. package/src/resolvers/UserResolver.ts +29 -29
  57. package/src/resolvers/UserViewResolver.ts +64 -64
  58. package/src/types.ts +19 -19
  59. package/src/util.ts +106 -106
  60. package/tsconfig.json +31 -31
  61. package/typedoc.json +4 -4
  62. package/build.log.json +0 -47
@@ -1,331 +1,331 @@
1
- import { EntityPermissionType, Metadata, RunView, UserInfo } from '@memberjunction/core';
2
- import { AuditLogEntity, UserViewEntity } from '@memberjunction/core-entities';
3
- import { UserCache } from '@memberjunction/sqlserver-dataprovider';
4
- import { PubSubEngine } from 'type-graphql';
5
- import { DataSource } from 'typeorm';
6
-
7
- import { UserPayload } from '../types';
8
- import { RunDynamicViewInput, RunViewByIDInput, RunViewByNameInput } from './RunViewResolver';
9
-
10
- export class ResolverBase {
11
-
12
- protected MapFieldNamesToCodeNames(entityName: string, dataObject: any) {
13
- // for the given entity name provided, check to see if there are any fields
14
- // where the code name is different from the field name, and for just those
15
- // fields, iterate through the dataObject and REPLACE the property that has the field name
16
- // with the CodeName, because we can't transfer those via GraphQL as they are not
17
- // valid property names in GraphQL
18
- if (dataObject) {
19
- const md = new Metadata();
20
- const entityInfo = md.Entities.find((e) => e.Name === entityName);
21
- if (!entityInfo)
22
- throw new Error(`Entity ${entityName} not found in metadata`);
23
- const fields = entityInfo.Fields.filter((f) => f.Name !== f.CodeName);
24
- fields.forEach((f) => {
25
- if (dataObject.hasOwnProperty(f.Name)) {
26
- dataObject[f.CodeName] = dataObject[f.Name];
27
- delete dataObject[f.Name];
28
- }
29
- });
30
- }
31
- return dataObject;
32
- }
33
-
34
- protected ArrayMapFieldNamesToCodeNames(entityName: string, dataObjectArray: []) {
35
- // iterate through the array and call MapFieldNamesToCodeNames for each element
36
- if (dataObjectArray && dataObjectArray.length > 0) {
37
- dataObjectArray.forEach((element) => {
38
- this.MapFieldNamesToCodeNames(entityName, element);
39
- });
40
- }
41
- return dataObjectArray;
42
- }
43
-
44
- protected async findBy(dataSource: DataSource, entity: string, params: any) {
45
- // build the SQL query based on the params passed in
46
- const md = new Metadata();
47
- const e = md.Entities.find((e) => e.Name === entity);
48
- if (!e) throw new Error(`Entity ${entity} not found in metadata`);
49
- // now build a SQL string using the entityInfo and using the properties in the params object
50
- let sql = `SELECT * FROM ${e.SchemaName}.${e.BaseView} WHERE `;
51
- const keys = Object.keys(params);
52
- keys.forEach((k, i) => {
53
- if (i > 0) sql += ' AND ';
54
- // look up the field in the entityInfo to see if it needs quotes
55
- const field = e.Fields.find((f) => f.Name === k);
56
- if (!field) throw new Error(`Field ${k} not found in entity ${entity}`);
57
- const quotes = field.NeedsQuotes ? "'" : '';
58
- sql += `${k} = ${quotes}${params[k]}${quotes}`;
59
- });
60
-
61
- // ok, now we have a SQL string, run it and return the results
62
- const result = await dataSource.query(sql);
63
- return result;
64
- }
65
-
66
- async RunViewByNameGeneric(viewInput: RunViewByNameInput, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
67
- try {
68
- const viewInfo: UserViewEntity = this.safeFirstArrayElement(await this.findBy(dataSource, 'User Views', { Name: viewInput.ViewName }));;
69
- return this.RunViewGenericInternal(
70
- viewInfo,
71
- dataSource,
72
- viewInput.ExtraFilter,
73
- viewInput.OrderBy,
74
- viewInput.UserSearchString,
75
- viewInput.ExcludeUserViewRunID,
76
- viewInput.OverrideExcludeFilter,
77
- viewInput.SaveViewResults,
78
- viewInput.Fields,
79
- viewInput.IgnoreMaxRows,
80
- viewInput.ExcludeDataFromAllPriorViewRuns,
81
- viewInput.ForceAuditLog,
82
- viewInput.AuditLogDescription,
83
- userPayload,
84
- pubSub
85
- );
86
- } catch (err) {
87
- console.log(err);
88
- return null;
89
- }
90
- }
91
-
92
- async RunViewByIDGeneric(viewInput: RunViewByIDInput, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
93
- try {
94
- const viewInfo: UserViewEntity = this.safeFirstArrayElement(await this.findBy(dataSource, 'User Views', { ID: viewInput.ViewID }));
95
- return this.RunViewGenericInternal(
96
- viewInfo,
97
- dataSource,
98
- viewInput.ExtraFilter,
99
- viewInput.OrderBy,
100
- viewInput.UserSearchString,
101
- viewInput.ExcludeUserViewRunID,
102
- viewInput.OverrideExcludeFilter,
103
- viewInput.SaveViewResults,
104
- viewInput.Fields,
105
- viewInput.IgnoreMaxRows,
106
- viewInput.ExcludeDataFromAllPriorViewRuns,
107
- viewInput.ForceAuditLog,
108
- viewInput.AuditLogDescription,
109
- userPayload,
110
- pubSub
111
- );
112
- } catch (err) {
113
- console.log(err);
114
- return null;
115
- }
116
- }
117
-
118
- async RunDynamicViewGeneric(viewInput: RunDynamicViewInput, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
119
- try {
120
- const md = new Metadata();
121
- const entity = md.Entities.find((e) => e.Name === viewInput.EntityName);
122
- if (!entity) throw new Error(`Entity ${viewInput.EntityName} not found in metadata`);
123
-
124
- const viewInfo: UserViewEntity = {
125
- ID: -1,
126
- Entity: viewInput.EntityName,
127
- EntityID: entity.ID as number,
128
- EntityBaseView: entity.BaseView as string,
129
- } as UserViewEntity; // only providing a few bits of data here, but it's enough to get the view to run
130
-
131
- return this.RunViewGenericInternal(
132
- viewInfo,
133
- dataSource,
134
- viewInput.ExtraFilter,
135
- viewInput.OrderBy,
136
- viewInput.UserSearchString,
137
- viewInput.ExcludeUserViewRunID,
138
- viewInput.OverrideExcludeFilter,
139
- false,
140
- viewInput.Fields,
141
- viewInput.IgnoreMaxRows,
142
- false,
143
- viewInput.ForceAuditLog,
144
- viewInput.AuditLogDescription,
145
- userPayload,
146
- pubSub
147
- );
148
- } catch (err) {
149
- console.log(err);
150
- return null;
151
- }
152
- }
153
-
154
- protected CheckUserReadPermissions(entityName: string, userPayload: UserPayload | null) {
155
- const md = new Metadata();
156
- const entityInfo = md.Entities.find((e) => e.Name === entityName);
157
- if (!userPayload) throw new Error(`userPayload is null`);
158
-
159
- // first check permissions, the logged in user must have read permissions on the entity to run the view
160
- if (entityInfo) {
161
- const userInfo = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload.email.toLowerCase().trim()); // get the user record from MD so we have ROLES attached, don't use the one from payload directly
162
- if (!userInfo) throw new Error(`User ${userPayload.email} not found in metadata`);
163
-
164
- const userPermissions = entityInfo.GetUserPermisions(userInfo);
165
- if (!userPermissions.CanRead) throw new Error(`User ${userPayload.email} does not have read permissions on ${entityInfo.Name}`);
166
- } else throw new Error(`Entity not found in metadata`);
167
- }
168
-
169
- protected async RunViewGenericInternal(
170
- viewInfo: UserViewEntity,
171
- dataSource: DataSource,
172
- extraFilter: string,
173
- orderBy: string,
174
- userSearchString: string,
175
- excludeUserViewRunID: number | undefined,
176
- overrideExcludeFilter: string | undefined,
177
- saveViewResults: boolean | undefined,
178
- fields: string[] | undefined,
179
- ignoreMaxRows: boolean | undefined,
180
- excludeDataFromAllPriorViewRuns: boolean | undefined,
181
- forceAuditLog: boolean | undefined,
182
- auditLogDescription: string | undefined,
183
- userPayload: UserPayload | null,
184
- pubSub: PubSubEngine
185
- ) {
186
- try {
187
- if (viewInfo && userPayload) {
188
- const md = new Metadata();
189
- const user = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
190
- if (!user) throw new Error(`User ${userPayload?.email} not found in metadata`);
191
-
192
- const entityInfo = md.Entities.find((e) => e.Name === viewInfo.Entity);
193
- if (!entityInfo) throw new Error(`Entity ${viewInfo.Entity} not found in metadata`);
194
-
195
- const rv = new RunView();
196
- const result = await rv.RunView(
197
- {
198
- ViewID: viewInfo.ID,
199
- ViewName: viewInfo.Name,
200
- EntityName: viewInfo.Entity,
201
- ExtraFilter: extraFilter,
202
- OrderBy: orderBy,
203
- Fields: fields,
204
- UserSearchString: userSearchString,
205
- ExcludeUserViewRunID: excludeUserViewRunID,
206
- OverrideExcludeFilter: overrideExcludeFilter,
207
- SaveViewResults: saveViewResults,
208
- ExcludeDataFromAllPriorViewRuns: excludeDataFromAllPriorViewRuns,
209
- IgnoreMaxRows: ignoreMaxRows,
210
- ForceAuditLog: forceAuditLog,
211
- AuditLogDescription: auditLogDescription,
212
- },
213
- user
214
- );
215
- return result;
216
- } else return null;
217
- } catch (err) {
218
- console.log(err);
219
- throw err;
220
- }
221
- }
222
-
223
- protected async createRecordAccessAuditLogRecord(userPayload: UserPayload, entityName: string, recordId: any): Promise<any> {
224
- try {
225
- const md = new Metadata();
226
- const entityInfo = md.Entities.find((e) => e.Name.trim().toLowerCase() === entityName.trim().toLowerCase());
227
- if (!entityInfo) throw new Error(`Entity ${entityName} not found in metadata`);
228
-
229
- if (entityInfo.AuditRecordAccess) {
230
- const userInfo = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
231
- const auditLogTypeName = 'Record Accessed';
232
- const auditLogType = md.AuditLogTypes.find((a) => a.Name.trim().toLowerCase() === auditLogTypeName.trim().toLowerCase());
233
-
234
- if (!userInfo) throw new Error(`User ${userPayload?.email} not found in metadata`);
235
- if (!auditLogType) throw new Error(`Audit Log Type ${auditLogTypeName} not found in metadata`);
236
-
237
- return await this.createAuditLogRecord(userPayload, null, auditLogTypeName, 'Success', null, entityInfo.ID, recordId);
238
- }
239
- } catch (e) {
240
- console.log(e);
241
- }
242
- }
243
-
244
- protected getRowLevelSecurityWhereClause(entityName: string, userPayload: UserPayload, type: EntityPermissionType, returnPrefix: string) {
245
- const md = new Metadata();
246
- const entityInfo = md.Entities.find((e) => e.Name.trim().toLowerCase() === entityName.trim().toLowerCase());
247
- if (!entityInfo) throw new Error(`Entity ${entityName} not found in metadata`);
248
- const user = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
249
- if (!user) throw new Error(`User ${userPayload?.email} not found in metadata`);
250
-
251
- return entityInfo.GetUserRowLevelSecurityWhereClause(user, type, returnPrefix);
252
- }
253
-
254
- protected async createAuditLogRecord(
255
- userPayload: UserPayload,
256
- authorizationName: string | null,
257
- auditLogTypeName: string,
258
- status: string,
259
- details: string | null,
260
- entityId: number,
261
- recordId: any | null
262
- ): Promise<any> {
263
- try {
264
- const md = new Metadata();
265
- const userInfo = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
266
- const authorization = authorizationName
267
- ? md.Authorizations.find((a) => a.Name.trim().toLowerCase() === authorizationName.trim().toLowerCase())
268
- : null;
269
- const auditLogType = md.AuditLogTypes.find((a) => a.Name.trim().toLowerCase() === auditLogTypeName.trim().toLowerCase());
270
-
271
- if (!userInfo) throw new Error(`User ${userPayload?.email} not found in metadata`);
272
- if (!auditLogType) throw new Error(`Audit Log Type ${auditLogTypeName} not found in metadata`);
273
-
274
- 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
275
- auditLog.NewRecord();
276
- auditLog.UserID = userInfo.ID;
277
- auditLog.AuditLogTypeName = auditLogType.Name;
278
-
279
- if (authorization)
280
- auditLog.AuthorizationName = authorization.Name;
281
-
282
- if (status?.trim().toLowerCase() === 'success')
283
- auditLog.Status = "Success"
284
- else
285
- auditLog.Status = "Failed";
286
-
287
- if (details)
288
- auditLog.Details = details;
289
-
290
- auditLog.EntityID = entityId;
291
-
292
- if (recordId)
293
- auditLog.RecordID = recordId;
294
-
295
- if (await auditLog.Save())
296
- return auditLog;
297
- else
298
- throw new Error(`Error saving audit log record`);
299
- }
300
- catch (err) {
301
- console.log(err);
302
- return null;
303
- }
304
- }
305
-
306
- protected safeFirstArrayElement(arr: any[]) {
307
- if (arr && arr.length > 0) {
308
- return arr[0];
309
- }
310
- return null;
311
- }
312
-
313
- protected packageSPParam(paramValue: any, quoteString: string) {
314
- return paramValue === null || paramValue === undefined ? null : quoteString + paramValue + quoteString;
315
- }
316
-
317
- protected GetUserFromEmail(email: string): UserInfo | undefined {
318
- const md = new Metadata();
319
- return UserCache.Users.find((u) => u.Email.toLowerCase().trim() === email.toLowerCase().trim());
320
- }
321
- protected GetUserFromPayload(userPayload: UserPayload): UserInfo | undefined {
322
- if (!userPayload || !userPayload.email) return undefined;
323
-
324
- const md = new Metadata();
325
- return UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload.email.toLowerCase().trim());
326
- }
327
-
328
- public get MJCoreSchema(): string {
329
- return Metadata.Provider.ConfigData.MJCoreSchemaName;
330
- }
331
- }
1
+ import { EntityPermissionType, Metadata, RunView, UserInfo } from '@memberjunction/core';
2
+ import { AuditLogEntity, UserViewEntity } from '@memberjunction/core-entities';
3
+ import { UserCache } from '@memberjunction/sqlserver-dataprovider';
4
+ import { PubSubEngine } from 'type-graphql';
5
+ import { DataSource } from 'typeorm';
6
+
7
+ import { UserPayload } from '../types';
8
+ import { RunDynamicViewInput, RunViewByIDInput, RunViewByNameInput } from './RunViewResolver';
9
+
10
+ export class ResolverBase {
11
+
12
+ protected MapFieldNamesToCodeNames(entityName: string, dataObject: any) {
13
+ // for the given entity name provided, check to see if there are any fields
14
+ // where the code name is different from the field name, and for just those
15
+ // fields, iterate through the dataObject and REPLACE the property that has the field name
16
+ // with the CodeName, because we can't transfer those via GraphQL as they are not
17
+ // valid property names in GraphQL
18
+ if (dataObject) {
19
+ const md = new Metadata();
20
+ const entityInfo = md.Entities.find((e) => e.Name === entityName);
21
+ if (!entityInfo)
22
+ throw new Error(`Entity ${entityName} not found in metadata`);
23
+ const fields = entityInfo.Fields.filter((f) => f.Name !== f.CodeName);
24
+ fields.forEach((f) => {
25
+ if (dataObject.hasOwnProperty(f.Name)) {
26
+ dataObject[f.CodeName] = dataObject[f.Name];
27
+ delete dataObject[f.Name];
28
+ }
29
+ });
30
+ }
31
+ return dataObject;
32
+ }
33
+
34
+ protected ArrayMapFieldNamesToCodeNames(entityName: string, dataObjectArray: []) {
35
+ // iterate through the array and call MapFieldNamesToCodeNames for each element
36
+ if (dataObjectArray && dataObjectArray.length > 0) {
37
+ dataObjectArray.forEach((element) => {
38
+ this.MapFieldNamesToCodeNames(entityName, element);
39
+ });
40
+ }
41
+ return dataObjectArray;
42
+ }
43
+
44
+ protected async findBy(dataSource: DataSource, entity: string, params: any) {
45
+ // build the SQL query based on the params passed in
46
+ const md = new Metadata();
47
+ const e = md.Entities.find((e) => e.Name === entity);
48
+ if (!e) throw new Error(`Entity ${entity} not found in metadata`);
49
+ // now build a SQL string using the entityInfo and using the properties in the params object
50
+ let sql = `SELECT * FROM ${e.SchemaName}.${e.BaseView} WHERE `;
51
+ const keys = Object.keys(params);
52
+ keys.forEach((k, i) => {
53
+ if (i > 0) sql += ' AND ';
54
+ // look up the field in the entityInfo to see if it needs quotes
55
+ const field = e.Fields.find((f) => f.Name === k);
56
+ if (!field) throw new Error(`Field ${k} not found in entity ${entity}`);
57
+ const quotes = field.NeedsQuotes ? "'" : '';
58
+ sql += `${k} = ${quotes}${params[k]}${quotes}`;
59
+ });
60
+
61
+ // ok, now we have a SQL string, run it and return the results
62
+ const result = await dataSource.query(sql);
63
+ return result;
64
+ }
65
+
66
+ async RunViewByNameGeneric(viewInput: RunViewByNameInput, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
67
+ try {
68
+ const viewInfo: UserViewEntity = this.safeFirstArrayElement(await this.findBy(dataSource, 'User Views', { Name: viewInput.ViewName }));;
69
+ return this.RunViewGenericInternal(
70
+ viewInfo,
71
+ dataSource,
72
+ viewInput.ExtraFilter,
73
+ viewInput.OrderBy,
74
+ viewInput.UserSearchString,
75
+ viewInput.ExcludeUserViewRunID,
76
+ viewInput.OverrideExcludeFilter,
77
+ viewInput.SaveViewResults,
78
+ viewInput.Fields,
79
+ viewInput.IgnoreMaxRows,
80
+ viewInput.ExcludeDataFromAllPriorViewRuns,
81
+ viewInput.ForceAuditLog,
82
+ viewInput.AuditLogDescription,
83
+ userPayload,
84
+ pubSub
85
+ );
86
+ } catch (err) {
87
+ console.log(err);
88
+ return null;
89
+ }
90
+ }
91
+
92
+ async RunViewByIDGeneric(viewInput: RunViewByIDInput, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
93
+ try {
94
+ const viewInfo: UserViewEntity = this.safeFirstArrayElement(await this.findBy(dataSource, 'User Views', { ID: viewInput.ViewID }));
95
+ return this.RunViewGenericInternal(
96
+ viewInfo,
97
+ dataSource,
98
+ viewInput.ExtraFilter,
99
+ viewInput.OrderBy,
100
+ viewInput.UserSearchString,
101
+ viewInput.ExcludeUserViewRunID,
102
+ viewInput.OverrideExcludeFilter,
103
+ viewInput.SaveViewResults,
104
+ viewInput.Fields,
105
+ viewInput.IgnoreMaxRows,
106
+ viewInput.ExcludeDataFromAllPriorViewRuns,
107
+ viewInput.ForceAuditLog,
108
+ viewInput.AuditLogDescription,
109
+ userPayload,
110
+ pubSub
111
+ );
112
+ } catch (err) {
113
+ console.log(err);
114
+ return null;
115
+ }
116
+ }
117
+
118
+ async RunDynamicViewGeneric(viewInput: RunDynamicViewInput, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
119
+ try {
120
+ const md = new Metadata();
121
+ const entity = md.Entities.find((e) => e.Name === viewInput.EntityName);
122
+ if (!entity) throw new Error(`Entity ${viewInput.EntityName} not found in metadata`);
123
+
124
+ const viewInfo: UserViewEntity = {
125
+ ID: -1,
126
+ Entity: viewInput.EntityName,
127
+ EntityID: entity.ID as number,
128
+ EntityBaseView: entity.BaseView as string,
129
+ } as UserViewEntity; // only providing a few bits of data here, but it's enough to get the view to run
130
+
131
+ return this.RunViewGenericInternal(
132
+ viewInfo,
133
+ dataSource,
134
+ viewInput.ExtraFilter,
135
+ viewInput.OrderBy,
136
+ viewInput.UserSearchString,
137
+ viewInput.ExcludeUserViewRunID,
138
+ viewInput.OverrideExcludeFilter,
139
+ false,
140
+ viewInput.Fields,
141
+ viewInput.IgnoreMaxRows,
142
+ false,
143
+ viewInput.ForceAuditLog,
144
+ viewInput.AuditLogDescription,
145
+ userPayload,
146
+ pubSub
147
+ );
148
+ } catch (err) {
149
+ console.log(err);
150
+ return null;
151
+ }
152
+ }
153
+
154
+ protected CheckUserReadPermissions(entityName: string, userPayload: UserPayload | null) {
155
+ const md = new Metadata();
156
+ const entityInfo = md.Entities.find((e) => e.Name === entityName);
157
+ if (!userPayload) throw new Error(`userPayload is null`);
158
+
159
+ // first check permissions, the logged in user must have read permissions on the entity to run the view
160
+ if (entityInfo) {
161
+ const userInfo = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload.email.toLowerCase().trim()); // get the user record from MD so we have ROLES attached, don't use the one from payload directly
162
+ if (!userInfo) throw new Error(`User ${userPayload.email} not found in metadata`);
163
+
164
+ const userPermissions = entityInfo.GetUserPermisions(userInfo);
165
+ if (!userPermissions.CanRead) throw new Error(`User ${userPayload.email} does not have read permissions on ${entityInfo.Name}`);
166
+ } else throw new Error(`Entity not found in metadata`);
167
+ }
168
+
169
+ protected async RunViewGenericInternal(
170
+ viewInfo: UserViewEntity,
171
+ dataSource: DataSource,
172
+ extraFilter: string,
173
+ orderBy: string,
174
+ userSearchString: string,
175
+ excludeUserViewRunID: number | undefined,
176
+ overrideExcludeFilter: string | undefined,
177
+ saveViewResults: boolean | undefined,
178
+ fields: string[] | undefined,
179
+ ignoreMaxRows: boolean | undefined,
180
+ excludeDataFromAllPriorViewRuns: boolean | undefined,
181
+ forceAuditLog: boolean | undefined,
182
+ auditLogDescription: string | undefined,
183
+ userPayload: UserPayload | null,
184
+ pubSub: PubSubEngine
185
+ ) {
186
+ try {
187
+ if (viewInfo && userPayload) {
188
+ const md = new Metadata();
189
+ const user = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
190
+ if (!user) throw new Error(`User ${userPayload?.email} not found in metadata`);
191
+
192
+ const entityInfo = md.Entities.find((e) => e.Name === viewInfo.Entity);
193
+ if (!entityInfo) throw new Error(`Entity ${viewInfo.Entity} not found in metadata`);
194
+
195
+ const rv = new RunView();
196
+ const result = await rv.RunView(
197
+ {
198
+ ViewID: viewInfo.ID,
199
+ ViewName: viewInfo.Name,
200
+ EntityName: viewInfo.Entity,
201
+ ExtraFilter: extraFilter,
202
+ OrderBy: orderBy,
203
+ Fields: fields,
204
+ UserSearchString: userSearchString,
205
+ ExcludeUserViewRunID: excludeUserViewRunID,
206
+ OverrideExcludeFilter: overrideExcludeFilter,
207
+ SaveViewResults: saveViewResults,
208
+ ExcludeDataFromAllPriorViewRuns: excludeDataFromAllPriorViewRuns,
209
+ IgnoreMaxRows: ignoreMaxRows,
210
+ ForceAuditLog: forceAuditLog,
211
+ AuditLogDescription: auditLogDescription,
212
+ },
213
+ user
214
+ );
215
+ return result;
216
+ } else return null;
217
+ } catch (err) {
218
+ console.log(err);
219
+ throw err;
220
+ }
221
+ }
222
+
223
+ protected async createRecordAccessAuditLogRecord(userPayload: UserPayload, entityName: string, recordId: any): Promise<any> {
224
+ try {
225
+ const md = new Metadata();
226
+ const entityInfo = md.Entities.find((e) => e.Name.trim().toLowerCase() === entityName.trim().toLowerCase());
227
+ if (!entityInfo) throw new Error(`Entity ${entityName} not found in metadata`);
228
+
229
+ if (entityInfo.AuditRecordAccess) {
230
+ const userInfo = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
231
+ const auditLogTypeName = 'Record Accessed';
232
+ const auditLogType = md.AuditLogTypes.find((a) => a.Name.trim().toLowerCase() === auditLogTypeName.trim().toLowerCase());
233
+
234
+ if (!userInfo) throw new Error(`User ${userPayload?.email} not found in metadata`);
235
+ if (!auditLogType) throw new Error(`Audit Log Type ${auditLogTypeName} not found in metadata`);
236
+
237
+ return await this.createAuditLogRecord(userPayload, null, auditLogTypeName, 'Success', null, entityInfo.ID, recordId);
238
+ }
239
+ } catch (e) {
240
+ console.log(e);
241
+ }
242
+ }
243
+
244
+ protected getRowLevelSecurityWhereClause(entityName: string, userPayload: UserPayload, type: EntityPermissionType, returnPrefix: string) {
245
+ const md = new Metadata();
246
+ const entityInfo = md.Entities.find((e) => e.Name.trim().toLowerCase() === entityName.trim().toLowerCase());
247
+ if (!entityInfo) throw new Error(`Entity ${entityName} not found in metadata`);
248
+ const user = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
249
+ if (!user) throw new Error(`User ${userPayload?.email} not found in metadata`);
250
+
251
+ return entityInfo.GetUserRowLevelSecurityWhereClause(user, type, returnPrefix);
252
+ }
253
+
254
+ protected async createAuditLogRecord(
255
+ userPayload: UserPayload,
256
+ authorizationName: string | null,
257
+ auditLogTypeName: string,
258
+ status: string,
259
+ details: string | null,
260
+ entityId: number,
261
+ recordId: any | null
262
+ ): Promise<any> {
263
+ try {
264
+ const md = new Metadata();
265
+ const userInfo = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
266
+ const authorization = authorizationName
267
+ ? md.Authorizations.find((a) => a.Name.trim().toLowerCase() === authorizationName.trim().toLowerCase())
268
+ : null;
269
+ const auditLogType = md.AuditLogTypes.find((a) => a.Name.trim().toLowerCase() === auditLogTypeName.trim().toLowerCase());
270
+
271
+ if (!userInfo) throw new Error(`User ${userPayload?.email} not found in metadata`);
272
+ if (!auditLogType) throw new Error(`Audit Log Type ${auditLogTypeName} not found in metadata`);
273
+
274
+ 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
275
+ auditLog.NewRecord();
276
+ auditLog.UserID = userInfo.ID;
277
+ auditLog.AuditLogTypeName = auditLogType.Name;
278
+
279
+ if (authorization)
280
+ auditLog.AuthorizationName = authorization.Name;
281
+
282
+ if (status?.trim().toLowerCase() === 'success')
283
+ auditLog.Status = "Success"
284
+ else
285
+ auditLog.Status = "Failed";
286
+
287
+ if (details)
288
+ auditLog.Details = details;
289
+
290
+ auditLog.EntityID = entityId;
291
+
292
+ if (recordId)
293
+ auditLog.RecordID = recordId;
294
+
295
+ if (await auditLog.Save())
296
+ return auditLog;
297
+ else
298
+ throw new Error(`Error saving audit log record`);
299
+ }
300
+ catch (err) {
301
+ console.log(err);
302
+ return null;
303
+ }
304
+ }
305
+
306
+ protected safeFirstArrayElement(arr: any[]) {
307
+ if (arr && arr.length > 0) {
308
+ return arr[0];
309
+ }
310
+ return null;
311
+ }
312
+
313
+ protected packageSPParam(paramValue: any, quoteString: string) {
314
+ return paramValue === null || paramValue === undefined ? null : quoteString + paramValue + quoteString;
315
+ }
316
+
317
+ protected GetUserFromEmail(email: string): UserInfo | undefined {
318
+ const md = new Metadata();
319
+ return UserCache.Users.find((u) => u.Email.toLowerCase().trim() === email.toLowerCase().trim());
320
+ }
321
+ protected GetUserFromPayload(userPayload: UserPayload): UserInfo | undefined {
322
+ if (!userPayload || !userPayload.email) return undefined;
323
+
324
+ const md = new Metadata();
325
+ return UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload.email.toLowerCase().trim());
326
+ }
327
+
328
+ public get MJCoreSchema(): string {
329
+ return Metadata.Provider.ConfigData.MJCoreSchemaName;
330
+ }
331
+ }