@memberjunction/server 1.0.6 → 1.0.7
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/.eslintignore +4 -4
- package/.eslintrc +24 -24
- package/CHANGELOG.json +92 -0
- package/CHANGELOG.md +25 -0
- package/README.md +141 -141
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +4 -1
- package/dist/config.js.map +1 -1
- package/dist/entitySubclasses/entityPermissions.server.d.ts +23 -0
- package/dist/entitySubclasses/entityPermissions.server.d.ts.map +1 -0
- package/dist/entitySubclasses/entityPermissions.server.js +99 -0
- package/dist/entitySubclasses/entityPermissions.server.js.map +1 -0
- package/dist/entitySubclasses/userViewEntity.server.js +17 -17
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +10 -10
- package/dist/resolvers/FileCategoryResolver.js +2 -2
- package/dist/resolvers/ReportResolver.js +4 -4
- package/package.json +80 -80
- package/src/apolloServer/TransactionPlugin.ts +57 -57
- package/src/apolloServer/index.ts +33 -33
- package/src/auth/exampleNewUserSubClass.ts +73 -73
- package/src/auth/index.ts +151 -151
- package/src/auth/newUsers.ts +56 -56
- package/src/auth/tokenExpiredError.ts +12 -12
- package/src/cache.ts +10 -10
- package/src/config.ts +89 -84
- package/src/context.ts +119 -119
- package/src/directives/Public.ts +42 -42
- package/src/directives/index.ts +1 -1
- package/src/entitySubclasses/entityPermissions.server.ts +111 -0
- package/src/entitySubclasses/userViewEntity.server.ts +187 -187
- package/src/generated/generated.ts +2573 -2573
- package/src/generic/PushStatusResolver.ts +40 -40
- package/src/generic/ResolverBase.ts +331 -331
- package/src/generic/RunViewResolver.ts +350 -350
- package/src/index.ts +133 -137
- package/src/orm.ts +36 -36
- package/src/resolvers/AskSkipResolver.ts +782 -782
- package/src/resolvers/ColorResolver.ts +72 -72
- package/src/resolvers/DatasetResolver.ts +115 -115
- package/src/resolvers/EntityRecordNameResolver.ts +77 -77
- package/src/resolvers/EntityResolver.ts +37 -37
- package/src/resolvers/FileCategoryResolver.ts +38 -38
- package/src/resolvers/FileResolver.ts +110 -110
- package/src/resolvers/MergeRecordsResolver.ts +198 -198
- package/src/resolvers/PotentialDuplicateRecordResolver.ts +59 -59
- package/src/resolvers/QueryResolver.ts +42 -42
- package/src/resolvers/ReportResolver.ts +131 -131
- package/src/resolvers/UserFavoriteResolver.ts +102 -102
- package/src/resolvers/UserResolver.ts +29 -29
- package/src/resolvers/UserViewResolver.ts +64 -64
- package/src/types.ts +19 -19
- package/src/util.ts +106 -106
- package/tsconfig.json +31 -31
- package/typedoc.json +4 -4
- 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
|
+
}
|