@memberjunction/server 2.1.2 → 2.1.4

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 (202) hide show
  1. package/dist/apolloServer/TransactionPlugin.d.ts +4 -0
  2. package/dist/apolloServer/TransactionPlugin.d.ts.map +1 -0
  3. package/dist/apolloServer/TransactionPlugin.js +46 -0
  4. package/dist/apolloServer/TransactionPlugin.js.map +1 -0
  5. package/dist/apolloServer/index.d.ts +11 -0
  6. package/dist/apolloServer/index.d.ts.map +1 -0
  7. package/dist/apolloServer/index.js +25 -0
  8. package/dist/apolloServer/index.js.map +1 -0
  9. package/dist/auth/exampleNewUserSubClass.d.ts +6 -0
  10. package/dist/auth/exampleNewUserSubClass.d.ts.map +1 -0
  11. package/dist/auth/exampleNewUserSubClass.js +49 -0
  12. package/dist/auth/exampleNewUserSubClass.js.map +1 -0
  13. package/dist/auth/index.d.ts +30 -0
  14. package/dist/auth/index.d.ts.map +1 -0
  15. package/dist/auth/index.js +121 -0
  16. package/dist/auth/index.js.map +1 -0
  17. package/dist/auth/newUsers.d.ts +5 -0
  18. package/dist/auth/newUsers.d.ts.map +1 -0
  19. package/{src/auth/newUsers.ts → dist/auth/newUsers.js} +20 -14
  20. package/dist/auth/newUsers.js.map +1 -0
  21. package/dist/auth/tokenExpiredError.d.ts +5 -0
  22. package/dist/auth/tokenExpiredError.d.ts.map +1 -0
  23. package/dist/auth/tokenExpiredError.js +12 -0
  24. package/dist/auth/tokenExpiredError.js.map +1 -0
  25. package/dist/cache.d.ts +3 -0
  26. package/dist/cache.d.ts.map +1 -0
  27. package/{src/cache.ts → dist/cache.js} +4 -6
  28. package/dist/cache.js.map +1 -0
  29. package/dist/config.d.ts +196 -0
  30. package/dist/config.d.ts.map +1 -0
  31. package/{src/config.ts → dist/config.js} +25 -49
  32. package/dist/config.js.map +1 -0
  33. package/dist/context.d.ts +17 -0
  34. package/dist/context.d.ts.map +1 -0
  35. package/dist/context.js +84 -0
  36. package/dist/context.js.map +1 -0
  37. package/dist/directives/Public.d.ts +4 -0
  38. package/dist/directives/Public.d.ts.map +1 -0
  39. package/dist/directives/Public.js +30 -0
  40. package/dist/directives/Public.js.map +1 -0
  41. package/dist/directives/index.d.ts +2 -0
  42. package/dist/directives/index.d.ts.map +1 -0
  43. package/dist/directives/index.js +2 -0
  44. package/dist/directives/index.js.map +1 -0
  45. package/dist/entitySubclasses/DuplicateRunEntity.server.d.ts +6 -0
  46. package/dist/entitySubclasses/DuplicateRunEntity.server.d.ts.map +1 -0
  47. package/dist/entitySubclasses/DuplicateRunEntity.server.js +33 -0
  48. package/dist/entitySubclasses/DuplicateRunEntity.server.js.map +1 -0
  49. package/dist/entitySubclasses/entityPermissions.server.d.ts +23 -0
  50. package/dist/entitySubclasses/entityPermissions.server.d.ts.map +1 -0
  51. package/dist/entitySubclasses/entityPermissions.server.js +93 -0
  52. package/dist/entitySubclasses/entityPermissions.server.js.map +1 -0
  53. package/dist/entitySubclasses/userViewEntity.server.d.ts +13 -0
  54. package/dist/entitySubclasses/userViewEntity.server.d.ts.map +1 -0
  55. package/dist/entitySubclasses/userViewEntity.server.js +161 -0
  56. package/dist/entitySubclasses/userViewEntity.server.js.map +1 -0
  57. package/dist/generated/generated.d.ts +6679 -0
  58. package/dist/generated/generated.d.ts.map +1 -0
  59. package/dist/generated/generated.js +40786 -0
  60. package/dist/generated/generated.js.map +1 -0
  61. package/dist/generic/DeleteOptionsInput.d.ts +5 -0
  62. package/dist/generic/DeleteOptionsInput.d.ts.map +1 -0
  63. package/dist/generic/DeleteOptionsInput.js +27 -0
  64. package/dist/generic/DeleteOptionsInput.js.map +1 -0
  65. package/dist/generic/KeyInputOutputTypes.d.ts +16 -0
  66. package/dist/generic/KeyInputOutputTypes.d.ts.map +1 -0
  67. package/dist/generic/KeyInputOutputTypes.js +65 -0
  68. package/dist/generic/KeyInputOutputTypes.js.map +1 -0
  69. package/dist/generic/KeyValuePairInput.d.ts +5 -0
  70. package/dist/generic/KeyValuePairInput.d.ts.map +1 -0
  71. package/dist/generic/KeyValuePairInput.js +27 -0
  72. package/dist/generic/KeyValuePairInput.js.map +1 -0
  73. package/dist/generic/PushStatusResolver.d.ts +14 -0
  74. package/dist/generic/PushStatusResolver.d.ts.map +1 -0
  75. package/dist/generic/PushStatusResolver.js +58 -0
  76. package/dist/generic/PushStatusResolver.js.map +1 -0
  77. package/dist/generic/ResolverBase.d.ts +39 -0
  78. package/dist/generic/ResolverBase.d.ts.map +1 -0
  79. package/dist/generic/ResolverBase.js +573 -0
  80. package/dist/generic/ResolverBase.js.map +1 -0
  81. package/dist/generic/RunViewResolver.d.ts +123 -0
  82. package/dist/generic/RunViewResolver.d.ts.map +1 -0
  83. package/dist/generic/RunViewResolver.js +728 -0
  84. package/dist/generic/RunViewResolver.js.map +1 -0
  85. package/dist/index.d.ts +26 -0
  86. package/dist/index.d.ts.map +1 -0
  87. package/dist/index.js +108 -0
  88. package/dist/index.js.map +1 -0
  89. package/dist/orm.d.ts +4 -0
  90. package/dist/orm.d.ts.map +1 -0
  91. package/dist/orm.js +32 -0
  92. package/dist/orm.js.map +1 -0
  93. package/dist/resolvers/AskSkipResolver.d.ts +54 -0
  94. package/dist/resolvers/AskSkipResolver.d.ts.map +1 -0
  95. package/dist/resolvers/AskSkipResolver.js +805 -0
  96. package/dist/resolvers/AskSkipResolver.js.map +1 -0
  97. package/dist/resolvers/ColorResolver.d.ts +23 -0
  98. package/dist/resolvers/ColorResolver.d.ts.map +1 -0
  99. package/dist/resolvers/ColorResolver.js +97 -0
  100. package/dist/resolvers/ColorResolver.js.map +1 -0
  101. package/dist/resolvers/DatasetResolver.d.ts +42 -0
  102. package/dist/resolvers/DatasetResolver.d.ts.map +1 -0
  103. package/dist/resolvers/DatasetResolver.js +179 -0
  104. package/dist/resolvers/DatasetResolver.js.map +1 -0
  105. package/dist/resolvers/EntityCommunicationsResolver.d.ts +50 -0
  106. package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -0
  107. package/dist/resolvers/EntityCommunicationsResolver.js +280 -0
  108. package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -0
  109. package/dist/resolvers/EntityRecordNameResolver.d.ts +21 -0
  110. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -0
  111. package/dist/resolvers/EntityRecordNameResolver.js +117 -0
  112. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -0
  113. package/dist/resolvers/EntityResolver.d.ts +6 -0
  114. package/dist/resolvers/EntityResolver.d.ts.map +1 -0
  115. package/dist/resolvers/EntityResolver.js +57 -0
  116. package/dist/resolvers/EntityResolver.js.map +1 -0
  117. package/dist/resolvers/FileCategoryResolver.d.ts +6 -0
  118. package/dist/resolvers/FileCategoryResolver.d.ts.map +1 -0
  119. package/dist/resolvers/FileCategoryResolver.js +61 -0
  120. package/dist/resolvers/FileCategoryResolver.js.map +1 -0
  121. package/dist/resolvers/FileResolver.d.ts +24 -0
  122. package/dist/resolvers/FileResolver.d.ts.map +1 -0
  123. package/dist/resolvers/FileResolver.js +166 -0
  124. package/dist/resolvers/FileResolver.js.map +1 -0
  125. package/dist/resolvers/MergeRecordsResolver.d.ts +59 -0
  126. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -0
  127. package/dist/resolvers/MergeRecordsResolver.js +282 -0
  128. package/dist/resolvers/MergeRecordsResolver.js.map +1 -0
  129. package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts +29 -0
  130. package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts.map +1 -0
  131. package/dist/resolvers/PotentialDuplicateRecordResolver.js +125 -0
  132. package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -0
  133. package/dist/resolvers/QueryResolver.d.ts +13 -0
  134. package/dist/resolvers/QueryResolver.d.ts.map +1 -0
  135. package/dist/resolvers/QueryResolver.js +77 -0
  136. package/dist/resolvers/QueryResolver.js.map +1 -0
  137. package/dist/resolvers/ReportResolver.d.ts +20 -0
  138. package/dist/resolvers/ReportResolver.d.ts.map +1 -0
  139. package/dist/resolvers/ReportResolver.js +182 -0
  140. package/dist/resolvers/ReportResolver.js.map +1 -0
  141. package/dist/resolvers/UserFavoriteResolver.d.ts +42 -0
  142. package/dist/resolvers/UserFavoriteResolver.d.ts.map +1 -0
  143. package/dist/resolvers/UserFavoriteResolver.js +230 -0
  144. package/dist/resolvers/UserFavoriteResolver.js.map +1 -0
  145. package/dist/resolvers/UserResolver.d.ts +10 -0
  146. package/dist/resolvers/UserResolver.d.ts.map +1 -0
  147. package/dist/resolvers/UserResolver.js +71 -0
  148. package/dist/resolvers/UserResolver.js.map +1 -0
  149. package/dist/resolvers/UserViewResolver.d.ts +13 -0
  150. package/dist/resolvers/UserViewResolver.d.ts.map +1 -0
  151. package/dist/resolvers/UserViewResolver.js +99 -0
  152. package/dist/resolvers/UserViewResolver.js.map +1 -0
  153. package/dist/types.d.ts +37 -0
  154. package/dist/types.d.ts.map +1 -0
  155. package/dist/types.js +2 -0
  156. package/dist/types.js.map +1 -0
  157. package/dist/util.d.ts +4 -0
  158. package/dist/util.d.ts.map +1 -0
  159. package/dist/util.js +85 -0
  160. package/dist/util.js.map +1 -0
  161. package/package.json +25 -21
  162. package/CHANGELOG.json +0 -3389
  163. package/CHANGELOG.md +0 -739
  164. package/src/apolloServer/TransactionPlugin.ts +0 -57
  165. package/src/apolloServer/index.ts +0 -33
  166. package/src/auth/exampleNewUserSubClass.ts +0 -74
  167. package/src/auth/index.ts +0 -151
  168. package/src/auth/tokenExpiredError.ts +0 -12
  169. package/src/context.ts +0 -111
  170. package/src/directives/Public.ts +0 -42
  171. package/src/directives/index.ts +0 -1
  172. package/src/entitySubclasses/DuplicateRunEntity.server.ts +0 -29
  173. package/src/entitySubclasses/entityPermissions.server.ts +0 -111
  174. package/src/entitySubclasses/userViewEntity.server.ts +0 -187
  175. package/src/generated/generated.ts +0 -25369
  176. package/src/generic/DeleteOptionsInput.ts +0 -13
  177. package/src/generic/KeyInputOutputTypes.ts +0 -35
  178. package/src/generic/KeyValuePairInput.ts +0 -14
  179. package/src/generic/PushStatusResolver.ts +0 -40
  180. package/src/generic/ResolverBase.ts +0 -767
  181. package/src/generic/RunViewResolver.ts +0 -582
  182. package/src/index.ts +0 -161
  183. package/src/orm.ts +0 -36
  184. package/src/resolvers/AskSkipResolver.ts +0 -926
  185. package/src/resolvers/ColorResolver.ts +0 -61
  186. package/src/resolvers/DatasetResolver.ts +0 -115
  187. package/src/resolvers/EntityCommunicationsResolver.ts +0 -216
  188. package/src/resolvers/EntityRecordNameResolver.ts +0 -78
  189. package/src/resolvers/EntityResolver.ts +0 -37
  190. package/src/resolvers/FileCategoryResolver.ts +0 -62
  191. package/src/resolvers/FileResolver.ts +0 -147
  192. package/src/resolvers/MergeRecordsResolver.ts +0 -182
  193. package/src/resolvers/PotentialDuplicateRecordResolver.ts +0 -91
  194. package/src/resolvers/QueryResolver.ts +0 -42
  195. package/src/resolvers/ReportResolver.ts +0 -147
  196. package/src/resolvers/UserFavoriteResolver.ts +0 -166
  197. package/src/resolvers/UserResolver.ts +0 -33
  198. package/src/resolvers/UserViewResolver.ts +0 -64
  199. package/src/types.ts +0 -40
  200. package/src/util.ts +0 -106
  201. package/tsconfig.json +0 -30
  202. package/typedoc.json +0 -4
@@ -1,767 +0,0 @@
1
- import { BaseEntity, CompositeKey, EntityFieldTSType, EntityPermissionType, LogError, Metadata, RunView, RunViewParams, RunViewResult, 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 { GraphQLError } from 'graphql';
6
- import { DataSource } from 'typeorm';
7
-
8
- import { RunViewGenericParams, UserPayload } from '../types';
9
- import { RunDynamicViewInput, RunViewByIDInput, RunViewByNameInput } from './RunViewResolver';
10
- import { DeleteOptionsInput } from './DeleteOptionsInput';
11
- import { MJGlobal } from '@memberjunction/global';
12
- import { PUSH_STATUS_UPDATES_TOPIC } from './PushStatusResolver';
13
- import { FieldMapper } from '@memberjunction/graphql-dataprovider';
14
-
15
- export class ResolverBase {
16
- protected MapFieldNamesToCodeNames(entityName: string, dataObject: any) {
17
- // for the given entity name provided, check to see if there are any fields
18
- // where the code name is different from the field name, and for just those
19
- // fields, iterate through the dataObject and REPLACE the property that has the field name
20
- // with the CodeName, because we can't transfer those via GraphQL as they are not
21
- // valid property names in GraphQL
22
- if (dataObject) {
23
- const md = new Metadata();
24
- const entityInfo = md.Entities.find((e) => e.Name === entityName);
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
- if (dataObject.hasOwnProperty(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];
35
- }
36
- }
37
- });
38
- }
39
- return dataObject;
40
- }
41
-
42
- protected ArrayMapFieldNamesToCodeNames(entityName: string, dataObjectArray: []) {
43
- // iterate through the array and call MapFieldNamesToCodeNames for each element
44
- if (dataObjectArray && dataObjectArray.length > 0) {
45
- dataObjectArray.forEach((element) => {
46
- this.MapFieldNamesToCodeNames(entityName, element);
47
- });
48
- }
49
- return dataObjectArray;
50
- }
51
-
52
- protected async findBy(dataSource: DataSource, entity: string, params: any) {
53
- // build the SQL query based on the params passed in
54
- const md = new Metadata();
55
- const e = md.Entities.find((e) => e.Name === entity);
56
- if (!e) throw new Error(`Entity ${entity} not found in metadata`);
57
- // now build a SQL string using the entityInfo and using the properties in the params object
58
- let sql = `SELECT * FROM ${e.SchemaName}.${e.BaseView} WHERE `;
59
- const keys = Object.keys(params);
60
- keys.forEach((k, i) => {
61
- if (i > 0) sql += ' AND ';
62
- // look up the field in the entityInfo to see if it needs quotes
63
- const field = e.Fields.find((f) => f.Name === k);
64
- if (!field) throw new Error(`Field ${k} not found in entity ${entity}`);
65
- const quotes = field.NeedsQuotes ? "'" : '';
66
- sql += `${k} = ${quotes}${params[k]}${quotes}`;
67
- });
68
-
69
- // ok, now we have a SQL string, run it and return the results
70
- const result = await dataSource.query(sql);
71
- return result;
72
- }
73
-
74
- async RunViewByNameGeneric(viewInput: RunViewByNameInput, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
75
- try {
76
- const viewInfo: UserViewEntity = this.safeFirstArrayElement(
77
- await this.findBy(dataSource, 'User Views', { Name: viewInput.ViewName })
78
- );
79
- return this.RunViewGenericInternal(
80
- viewInfo,
81
- dataSource,
82
- viewInput.ExtraFilter,
83
- viewInput.OrderBy,
84
- viewInput.UserSearchString,
85
- viewInput.ExcludeUserViewRunID,
86
- viewInput.OverrideExcludeFilter,
87
- viewInput.SaveViewResults,
88
- viewInput.Fields,
89
- viewInput.IgnoreMaxRows,
90
- viewInput.ExcludeDataFromAllPriorViewRuns,
91
- viewInput.ForceAuditLog,
92
- viewInput.AuditLogDescription,
93
- viewInput.ResultType,
94
- userPayload,
95
- pubSub
96
- );
97
- } catch (err) {
98
- console.log(err);
99
- return null;
100
- }
101
- }
102
-
103
- async RunViewByIDGeneric(viewInput: RunViewByIDInput, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
104
- try {
105
- const viewInfo: UserViewEntity = this.safeFirstArrayElement(await this.findBy(dataSource, 'User Views', { ID: viewInput.ViewID }));
106
- return this.RunViewGenericInternal(
107
- viewInfo,
108
- dataSource,
109
- viewInput.ExtraFilter,
110
- viewInput.OrderBy,
111
- viewInput.UserSearchString,
112
- viewInput.ExcludeUserViewRunID,
113
- viewInput.OverrideExcludeFilter,
114
- viewInput.SaveViewResults,
115
- viewInput.Fields,
116
- viewInput.IgnoreMaxRows,
117
- viewInput.ExcludeDataFromAllPriorViewRuns,
118
- viewInput.ForceAuditLog,
119
- viewInput.AuditLogDescription,
120
- viewInput.ResultType,
121
- userPayload,
122
- pubSub
123
- );
124
- } catch (err) {
125
- console.log(err);
126
- return null;
127
- }
128
- }
129
-
130
- async RunDynamicViewGeneric(viewInput: RunDynamicViewInput, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
131
- try {
132
- const md = new Metadata();
133
- const entity = md.Entities.find((e) => e.Name === viewInput.EntityName);
134
- if (!entity) throw new Error(`Entity ${viewInput.EntityName} not found in metadata`);
135
-
136
- const viewInfo: UserViewEntity = {
137
- ID: "",
138
- Entity: viewInput.EntityName,
139
- EntityID: entity.ID,
140
- EntityBaseView: entity.BaseView as string,
141
- } as UserViewEntity; // only providing a few bits of data here, but it's enough to get the view to run
142
-
143
- return this.RunViewGenericInternal(
144
- viewInfo,
145
- dataSource,
146
- viewInput.ExtraFilter,
147
- viewInput.OrderBy,
148
- viewInput.UserSearchString,
149
- viewInput.ExcludeUserViewRunID,
150
- viewInput.OverrideExcludeFilter,
151
- false,
152
- viewInput.Fields,
153
- viewInput.IgnoreMaxRows,
154
- false,
155
- viewInput.ForceAuditLog,
156
- viewInput.AuditLogDescription,
157
- viewInput.ResultType,
158
- userPayload,
159
- pubSub
160
- );
161
- } catch (err) {
162
- console.log(err);
163
- return null;
164
- }
165
- }
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
-
232
- protected CheckUserReadPermissions(entityName: string, userPayload: UserPayload | null) {
233
- const md = new Metadata();
234
- const entityInfo = md.Entities.find((e) => e.Name === entityName);
235
- if (!userPayload) throw new Error(`userPayload is null`);
236
-
237
- // first check permissions, the logged in user must have read permissions on the entity to run the view
238
- if (entityInfo) {
239
- 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
240
- if (!userInfo) throw new Error(`User ${userPayload.email} not found in metadata`);
241
-
242
- const userPermissions = entityInfo.GetUserPermisions(userInfo);
243
- if (!userPermissions.CanRead) throw new Error(`User ${userPayload.email} does not have read permissions on ${entityInfo.Name}`);
244
- } else throw new Error(`Entity not found in metadata`);
245
- }
246
-
247
- protected async RunViewGenericInternal(
248
- viewInfo: UserViewEntity,
249
- dataSource: DataSource,
250
- extraFilter: string,
251
- orderBy: string,
252
- userSearchString: string,
253
- excludeUserViewRunID: string | undefined,
254
- overrideExcludeFilter: string | undefined,
255
- saveViewResults: boolean | undefined,
256
- fields: string[] | undefined,
257
- ignoreMaxRows: boolean | undefined,
258
- excludeDataFromAllPriorViewRuns: boolean | undefined,
259
- forceAuditLog: boolean | undefined,
260
- auditLogDescription: string | undefined,
261
- resultType: string | undefined,
262
- userPayload: UserPayload | null,
263
- pubSub: PubSubEngine
264
- ) {
265
- try {
266
- if (viewInfo && userPayload) {
267
- const md = new Metadata();
268
- const user = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
269
- if (!user) throw new Error(`User ${userPayload?.email} not found in metadata`);
270
-
271
- const entityInfo = md.Entities.find((e) => e.Name === viewInfo.Entity);
272
- if (!entityInfo) throw new Error(`Entity ${viewInfo.Entity} not found in metadata`);
273
-
274
- const rv = new RunView();
275
-
276
- // figure out the result type from the input string (if provided)
277
- let rt: 'simple' | 'entity_object' | 'count_only' = 'simple';
278
- switch (resultType?.trim().toLowerCase()) {
279
- case 'count_only':
280
- rt = 'count_only';
281
- break;
282
- case 'entity_object':
283
- default:
284
- rt = 'simple'; // use simple as the default AND for entity_object becuase on teh server we don't really pass back a true entity_object anyway, just passing back the simple object anyway
285
- break;
286
- }
287
-
288
- const result = await rv.RunView(
289
- {
290
- ViewID: viewInfo.ID,
291
- ViewName: viewInfo.Name,
292
- EntityName: viewInfo.Entity,
293
- ExtraFilter: extraFilter,
294
- OrderBy: orderBy,
295
- Fields: fields,
296
- UserSearchString: userSearchString,
297
- ExcludeUserViewRunID: excludeUserViewRunID,
298
- OverrideExcludeFilter: overrideExcludeFilter,
299
- SaveViewResults: saveViewResults,
300
- ExcludeDataFromAllPriorViewRuns: excludeDataFromAllPriorViewRuns,
301
- IgnoreMaxRows: ignoreMaxRows,
302
- ForceAuditLog: forceAuditLog,
303
- AuditLogDescription: auditLogDescription,
304
- ResultType: rt,
305
- },
306
- user
307
- );
308
- // go through the result and convert all fields that start with __mj_*** to _mj__*** for GraphQL transport
309
- const mapper = new FieldMapper();
310
- if (result && result.Success) {
311
- for (const r of result.Results) {
312
- mapper.MapFields(r);
313
- }
314
- }
315
- return result;
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;
393
- }
394
- catch (err) {
395
- console.log(err);
396
- throw err;
397
- }
398
- }
399
-
400
- protected async createRecordAccessAuditLogRecord(userPayload: UserPayload, entityName: string, recordId: any): Promise<any> {
401
- try {
402
- const md = new Metadata();
403
- const entityInfo = md.Entities.find((e) => e.Name.trim().toLowerCase() === entityName.trim().toLowerCase());
404
- if (!entityInfo) throw new Error(`Entity ${entityName} not found in metadata`);
405
-
406
- if (entityInfo.AuditRecordAccess) {
407
- const userInfo = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
408
- const auditLogTypeName = 'Record Accessed';
409
- const auditLogType = md.AuditLogTypes.find((a) => a.Name.trim().toLowerCase() === auditLogTypeName.trim().toLowerCase());
410
-
411
- if (!userInfo) throw new Error(`User ${userPayload?.email} not found in metadata`);
412
- if (!auditLogType) throw new Error(`Audit Log Type ${auditLogTypeName} not found in metadata`);
413
-
414
- return await this.createAuditLogRecord(userPayload, null, auditLogTypeName, 'Success', null, entityInfo.ID, recordId);
415
- }
416
- } catch (e) {
417
- console.log(e);
418
- }
419
- }
420
-
421
- protected getRowLevelSecurityWhereClause(entityName: string, userPayload: UserPayload, type: EntityPermissionType, returnPrefix: string) {
422
- const md = new Metadata();
423
- const entityInfo = md.Entities.find((e) => e.Name.trim().toLowerCase() === entityName.trim().toLowerCase());
424
- if (!entityInfo) throw new Error(`Entity ${entityName} not found in metadata`);
425
- const user = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
426
- if (!user) throw new Error(`User ${userPayload?.email} not found in metadata`);
427
-
428
- return entityInfo.GetUserRowLevelSecurityWhereClause(user, type, returnPrefix);
429
- }
430
-
431
- protected async createAuditLogRecord(
432
- userPayload: UserPayload,
433
- authorizationName: string | null,
434
- auditLogTypeName: string,
435
- status: string,
436
- details: string | null,
437
- entityId: string,
438
- recordId: any | null
439
- ): Promise<any> {
440
- try {
441
- const md = new Metadata();
442
- const userInfo = UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload?.email.toLowerCase().trim());
443
- const authorization = authorizationName
444
- ? md.Authorizations.find((a) => a.Name.trim().toLowerCase() === authorizationName.trim().toLowerCase())
445
- : null;
446
- const auditLogType = md.AuditLogTypes.find((a) => a.Name.trim().toLowerCase() === auditLogTypeName.trim().toLowerCase());
447
-
448
- if (!userInfo) throw new Error(`User ${userPayload?.email} not found in metadata`);
449
- if (!auditLogType) throw new Error(`Audit Log Type ${auditLogTypeName} not found in metadata`);
450
-
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
452
- auditLog.NewRecord();
453
- auditLog.UserID = userInfo.ID;
454
- auditLog.AuditLogTypeID = auditLogType.ID;
455
-
456
- if (authorization)
457
- auditLog.AuthorizationID = authorization.ID;
458
-
459
- if (status?.trim().toLowerCase() === 'success') auditLog.Status = 'Success';
460
- else auditLog.Status = 'Failed';
461
-
462
- if (details) auditLog.Details = details;
463
-
464
- auditLog.EntityID = entityId;
465
-
466
- if (recordId) auditLog.RecordID = recordId;
467
-
468
- if (await auditLog.Save()) return auditLog;
469
- else throw new Error(`Error saving audit log record`);
470
- } catch (err) {
471
- console.log(err);
472
- return null;
473
- }
474
- }
475
-
476
- protected safeFirstArrayElement(arr: any[]) {
477
- if (arr && arr.length > 0) {
478
- return arr[0];
479
- }
480
- return null;
481
- }
482
-
483
- protected packageSPParam(paramValue: any, quoteString: string) {
484
- return paramValue === null || paramValue === undefined ? null : quoteString + paramValue + quoteString;
485
- }
486
-
487
- protected GetUserFromEmail(email: string): UserInfo | undefined {
488
- const md = new Metadata();
489
- return UserCache.Users.find((u) => u.Email.toLowerCase().trim() === email.toLowerCase().trim());
490
- }
491
- protected GetUserFromPayload(userPayload: UserPayload): UserInfo | undefined {
492
- if (!userPayload || !userPayload.email) return undefined;
493
-
494
- const md = new Metadata();
495
- return UserCache.Users.find((u) => u.Email.toLowerCase().trim() === userPayload.email.toLowerCase().trim());
496
- }
497
-
498
- public get MJCoreSchema(): string {
499
- return Metadata.Provider.ConfigData.MJCoreSchemaName;
500
- }
501
-
502
- protected ListenForEntityMessages(entityObject: BaseEntity, pubSub: PubSubEngine, userPayload: UserPayload) {
503
- // listen for events from the entityObject in case it is a long running task and we can push messages back to the client via pubSub
504
- MJGlobal.Instance.GetEventListener(false).subscribe((event) => {
505
- if (event) {
506
- if (event.component === entityObject && event.args && event.args.message) {
507
- // message from our entity object, relay it to the client
508
- pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
509
- message: JSON.stringify({
510
- status: 'OK',
511
- type: 'EntityObjectStatusMessage',
512
- entityName: entityObject.EntityInfo.Name,
513
- primaryKey: entityObject.PrimaryKey,
514
- message: event.args.message,
515
- }),
516
- sessionId: userPayload.sessionId,
517
- });
518
- }
519
- }
520
- });
521
- }
522
-
523
- protected async CreateRecord(entityName: string, input: any, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
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;
540
- }
541
-
542
- // Before/After CREATE Event Hooks for Sub-Classes to Override
543
- protected async BeforeCreate(dataSource: DataSource, input: any): Promise<boolean> {
544
- return true;
545
- }
546
- protected async AfterCreate(dataSource: DataSource, input: any) {}
547
-
548
- protected async UpdateRecord(entityName: string, input: any, dataSource: DataSource, userPayload: UserPayload, pubSub: PubSubEngine) {
549
- if (await this.BeforeUpdate(dataSource, input)) {
550
- // fire event and proceed if it wasn't cancelled
551
- const md = new Metadata();
552
- const userInfo = this.GetUserFromPayload(userPayload);
553
- const entityObject = await md.GetEntityObject(entityName, userInfo);
554
- const entityInfo = entityObject.EntityInfo;
555
- const clientNewValues = {};
556
- Object.keys(input).forEach((key) => {
557
- if (key !== 'OldValues___')
558
- clientNewValues[key] = input[key];
559
- }); // grab all the props except for the OldValues property
560
-
561
- if (entityInfo.TrackRecordChanges || !input.OldValues___) {
562
- // the entity tracks record changes, so we need to load the old values from the DB to make sure they are not inconsistent
563
- // with the old values from the input.OldValues property. If they are different, but on different fields, we allow it
564
- // but if they are different on fields that the current UpdateRecord call is trying to update, we throw an error.
565
- const cKey = new CompositeKey(
566
- entityInfo.PrimaryKeys.map((pk) => {
567
- return {
568
- FieldName: pk.Name,
569
- Value: input[pk.CodeName],
570
- };
571
- })
572
- );
573
-
574
- if (await entityObject.InnerLoad(cKey)) {
575
- // load worked, now, if we HAVE OldValues, we need to check them against the values in the DB we just loaded.
576
- if (!input.OldValues___) {
577
- // no OldValues, so we can just set the new values from input
578
- entityObject.SetMany(input);
579
- } else {
580
- // we DO have OldValues, so we need to do a more in depth analysis
581
- this.TestAndSetClientOldValuesToDBValues(input, clientNewValues, entityObject);
582
- }
583
- } else {
584
- // save failed, return null
585
- throw new GraphQLError(`Record not found for ${entityName} with key ${JSON.stringify(cKey)}`, {
586
- extensions: { code: 'LOAD_ENTITY_ERROR', entityName },
587
- });
588
- }
589
- } else {
590
- // not tracking changes and we DO have OldValues, so we can load from them
591
- const oldValues = {};
592
- // for each item in the oldValues array, add it to the oldValues object
593
- input.OldValues___?.forEach((item) => (oldValues[item.Key] = item.Value));
594
-
595
- // 1) load the old values, this will be the initial state of the object
596
- entityObject.LoadFromData(oldValues);
597
-
598
- // 2) set the new values from the input, not including the OldValues property
599
- entityObject.SetMany(clientNewValues);
600
- }
601
-
602
- this.ListenForEntityMessages(entityObject, pubSub, userPayload);
603
- if (await entityObject.Save()) {
604
- // save worked, fire afterevent and return all the data
605
- await this.AfterUpdate(dataSource, input); // fire event
606
- return this.MapFieldNamesToCodeNames(entityName, entityObject.GetAll());
607
- } else {
608
- throw new GraphQLError(entityObject.LatestResult?.Message ?? 'Unknown error', {
609
- extensions: { code: 'SAVE_ENTITY_ERROR', entityName },
610
- });
611
- }
612
- } else
613
- throw new GraphQLError('Save Canceled by BeforeSave() handler in ResolverBase', {
614
- extensions: { code: 'SAVE_ENTITY_ERROR', entityName },
615
- });
616
- }
617
-
618
- /**
619
- * This routine compares the OldValues property in the input object to the values in the DB that we just loaded. If there are differences, we need to check to see if the client
620
- * is trying to update any of those fields (e.g. overlap). If there is overlap, we throw an error. If there is no overlap, we can proceed with the update even if the DB Values
621
- * and the ClientOldValues are not 100% the same, so long as there is no overlap in the specific FIELDS that are different.
622
- *
623
- * ASSUMES: input object has an OldValues___ property that is an array of Key/Value pairs that represent the old values of the record that the client is trying to update.
624
- */
625
- protected TestAndSetClientOldValuesToDBValues(input: any, clientNewValues: any, entityObject: BaseEntity) {
626
- // we have OldValues, so we need to compare them to the values we just loaded from the DB
627
- const clientOldValues = {};
628
- // for each item in the oldValues array, add it to the clientOldValues object
629
- input.OldValues___.forEach((item) => {
630
- // we need to do a quick transform on the values to make sure they match the TS Type for the given field because item.Value will always be a string
631
- const field = entityObject.EntityInfo.Fields.find((f) => f.CodeName === item.Key);
632
- let val = item.Value;
633
- if ((val === null || val === undefined) && field.DefaultValue !== null && field.DefaultValue !== undefined && !field.AllowsNull)
634
- val = field.DefaultValue; // set default value as the field was never set and it does NOT allow nulls
635
-
636
- if (field) {
637
- switch (field.TSType) {
638
- case EntityFieldTSType.Number:
639
- val = val !== null && val !== undefined ? parseInt(val) : null;
640
- break;
641
- case EntityFieldTSType.Boolean:
642
- val = val === null || val === undefined || val === 'false' || val === '0' || parseInt(val) === 0 ? false : true;
643
- break;
644
- case EntityFieldTSType.Date:
645
- // first, if val is a string and it is actually a number (milliseconds since epoch), convert it to a number.
646
- if (val !== null && val !== undefined && val.toString().trim() !== '' && !isNaN(val)) val = parseInt(val);
647
-
648
- val = val !== null && val !== undefined ? new Date(val) : null;
649
- break;
650
- default:
651
- break; // already a string
652
- }
653
- }
654
- clientOldValues[item.Key] = val;
655
- });
656
-
657
- // clientOldValues now has all of the oldValues the CLIENT passed us. Now we need to build the same kind of object
658
- // with the DB values
659
- const dbValues = entityObject.GetAll();
660
-
661
- // now we need to compare clientOldValues and dbValues and have a new array that has entries for any differences and have FieldName, clientOldValue and dbValue as properties
662
- const dbDifferences = [];
663
- Object.keys(clientOldValues).forEach((key) => {
664
- const f = entityObject.EntityInfo.Fields.find((f) => f.CodeName === key);
665
- let different = false;
666
- switch (typeof clientOldValues[key]) {
667
- case 'number':
668
- different = clientOldValues[key] !== dbValues[key];
669
- break;
670
- case 'boolean':
671
- different = clientOldValues[key] !== dbValues[key];
672
- break;
673
- case 'object':
674
- if (clientOldValues[key] instanceof Date) {
675
- different = clientOldValues[key].getTime() !== dbValues[key].getTime();
676
- }
677
- break;
678
- default:
679
- different = clientOldValues[key] !== dbValues[key];
680
- break;
681
- }
682
- if (different && f && !f.ReadOnly) {
683
- // only include updateable fields
684
- dbDifferences.push({
685
- FieldName: key,
686
- ClientOldValue: clientOldValues[key],
687
- DBValue: dbValues[key],
688
- });
689
- }
690
- });
691
-
692
- if (dbDifferences.length > 0) {
693
- // now we have an array of any dbDifferences with length > 0, between the clientOldValues and the dbValues, we need to check to see if any of the differences are on fields that the client is trying to update
694
- // first step is to get clientNewValues into an object that is like clientOldValues, get the diff and then compare that diff to the differences array that shows diff between DB and ClientOld
695
- const clientDifferences = [];
696
- Object.keys(clientOldValues).forEach((key) => {
697
- const f = entityObject.EntityInfo.Fields.find((f) => f.CodeName === key);
698
- if (clientOldValues[key] !== clientNewValues[key] && f && f.AllowUpdateAPI && !f.IsPrimaryKey) {
699
- // only include updateable fields
700
- clientDifferences.push({
701
- FieldName: key,
702
- ClientOldValue: clientOldValues[key],
703
- ClientNewValue: clientNewValues[key],
704
- });
705
- }
706
- });
707
-
708
- // now we have clientDifferences which shows what the client thinks they are changing. And, we have the dbDifferences array that shows changes between the clientOldValues and the dbValues
709
- // if there is ANY overlap in the FIELDS that appear in both arrays, we need to throw an error
710
- const overlap = clientDifferences.filter((cd) => dbDifferences.find((dd) => dd.FieldName === cd.FieldName));
711
- if (overlap.length > 0) {
712
- const msg = {
713
- Message:
714
- 'Inconsistency between old values provided for changed fields, and the values of one or more of those fields in the database. Update operation cancelled.',
715
- ClientDifferences: clientDifferences,
716
- DBDifferences: dbDifferences,
717
- Overlap: overlap,
718
- };
719
- throw new Error(JSON.stringify(msg));
720
- }
721
- }
722
-
723
- // If we get here that means we've not thrown an exception, so there is
724
- // NO OVERLAP, so we can set the new values from the data provided from the client now...
725
- entityObject.SetMany(clientNewValues);
726
- }
727
-
728
- protected async DeleteRecord(
729
- entityName: string,
730
- key: CompositeKey,
731
- options: DeleteOptionsInput,
732
- dataSource: DataSource,
733
- userPayload: UserPayload,
734
- pubSub: PubSubEngine
735
- ) {
736
- if (await this.BeforeDelete(dataSource, key)) {
737
- // fire event and proceed if it wasn't cancelled
738
- const entityObject = await new Metadata().GetEntityObject(entityName, this.GetUserFromPayload(userPayload));
739
- await entityObject.InnerLoad(key);
740
- const returnValue = entityObject.GetAll(); // grab the values before we delete so we can return last state before delete if we are successful.
741
- if (await entityObject.Delete(options)) {
742
- await this.AfterDelete(dataSource, key); // fire event
743
- return returnValue;
744
- } else {
745
- throw new GraphQLError(entityObject.LatestResult?.Message ?? 'Unknown error', {
746
- extensions: { code: 'DELETE_ENTITY_ERROR', entityName },
747
- });
748
- }
749
- } else {
750
- throw new GraphQLError('Delete operation canceled by BeforeDelete() handler in ResolverBase', {
751
- extensions: { code: 'DELETE_ENTITY_ERROR', entityName },
752
- });
753
- }
754
- }
755
-
756
- // Before/After DELETE Event Hooks for Sub-Classes to Override
757
- protected async BeforeDelete(dataSource: DataSource, key: CompositeKey): Promise<boolean> {
758
- return true;
759
- }
760
- protected async AfterDelete(dataSource: DataSource, key: CompositeKey) {}
761
-
762
- // Before/After UPDATE Event Hooks for Sub-Classes to Override
763
- protected async BeforeUpdate(dataSource: DataSource, input: any): Promise<boolean> {
764
- return true;
765
- }
766
- protected async AfterUpdate(dataSource: DataSource, input: any) {}
767
- }