@memberjunction/server 3.4.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/dist/agents/skip-agent.d.ts +65 -0
  2. package/dist/agents/skip-agent.d.ts.map +1 -1
  3. package/dist/agents/skip-agent.js +63 -5
  4. package/dist/agents/skip-agent.js.map +1 -1
  5. package/dist/agents/skip-sdk.d.ts +163 -0
  6. package/dist/agents/skip-sdk.d.ts.map +1 -1
  7. package/dist/agents/skip-sdk.js +143 -12
  8. package/dist/agents/skip-sdk.js.map +1 -1
  9. package/dist/apolloServer/TransactionPlugin.d.ts +4 -0
  10. package/dist/apolloServer/TransactionPlugin.d.ts.map +1 -0
  11. package/dist/apolloServer/TransactionPlugin.js +46 -0
  12. package/dist/apolloServer/TransactionPlugin.js.map +1 -0
  13. package/dist/apolloServer/index.d.ts +0 -1
  14. package/dist/apolloServer/index.d.ts.map +1 -1
  15. package/dist/auth/APIKeyScopeAuth.d.ts +82 -0
  16. package/dist/auth/APIKeyScopeAuth.d.ts.map +1 -1
  17. package/dist/auth/APIKeyScopeAuth.js +78 -0
  18. package/dist/auth/APIKeyScopeAuth.js.map +1 -1
  19. package/dist/auth/AuthProviderFactory.d.ts +35 -0
  20. package/dist/auth/AuthProviderFactory.d.ts.map +1 -1
  21. package/dist/auth/AuthProviderFactory.js +51 -4
  22. package/dist/auth/AuthProviderFactory.js.map +1 -1
  23. package/dist/auth/BaseAuthProvider.d.ts +21 -0
  24. package/dist/auth/BaseAuthProvider.d.ts.map +1 -1
  25. package/dist/auth/BaseAuthProvider.js +24 -9
  26. package/dist/auth/BaseAuthProvider.js.map +1 -1
  27. package/dist/auth/IAuthProvider.d.ts +32 -0
  28. package/dist/auth/IAuthProvider.d.ts.map +1 -1
  29. package/dist/auth/__tests__/backward-compatibility.test.d.ts +2 -0
  30. package/dist/auth/__tests__/backward-compatibility.test.d.ts.map +1 -0
  31. package/dist/auth/__tests__/backward-compatibility.test.js +135 -0
  32. package/dist/auth/__tests__/backward-compatibility.test.js.map +1 -0
  33. package/dist/auth/exampleNewUserSubClass.d.ts +5 -1
  34. package/dist/auth/exampleNewUserSubClass.d.ts.map +1 -1
  35. package/dist/auth/exampleNewUserSubClass.js +21 -6
  36. package/dist/auth/exampleNewUserSubClass.js.map +1 -1
  37. package/dist/auth/index.d.ts +14 -0
  38. package/dist/auth/index.d.ts.map +1 -1
  39. package/dist/auth/index.js +35 -22
  40. package/dist/auth/index.js.map +1 -1
  41. package/dist/auth/initializeProviders.d.ts +3 -0
  42. package/dist/auth/initializeProviders.d.ts.map +1 -1
  43. package/dist/auth/initializeProviders.js +6 -0
  44. package/dist/auth/initializeProviders.js.map +1 -1
  45. package/dist/auth/newUsers.js +11 -2
  46. package/dist/auth/newUsers.js.map +1 -1
  47. package/dist/auth/providers/Auth0Provider.d.ts +9 -0
  48. package/dist/auth/providers/Auth0Provider.d.ts.map +1 -1
  49. package/dist/auth/providers/Auth0Provider.js +10 -0
  50. package/dist/auth/providers/Auth0Provider.js.map +1 -1
  51. package/dist/auth/providers/CognitoProvider.d.ts +9 -0
  52. package/dist/auth/providers/CognitoProvider.d.ts.map +1 -1
  53. package/dist/auth/providers/CognitoProvider.js +10 -0
  54. package/dist/auth/providers/CognitoProvider.js.map +1 -1
  55. package/dist/auth/providers/GoogleProvider.d.ts +9 -0
  56. package/dist/auth/providers/GoogleProvider.d.ts.map +1 -1
  57. package/dist/auth/providers/GoogleProvider.js +11 -1
  58. package/dist/auth/providers/GoogleProvider.js.map +1 -1
  59. package/dist/auth/providers/MSALProvider.d.ts +9 -0
  60. package/dist/auth/providers/MSALProvider.d.ts.map +1 -1
  61. package/dist/auth/providers/MSALProvider.js +10 -0
  62. package/dist/auth/providers/MSALProvider.js.map +1 -1
  63. package/dist/auth/providers/OktaProvider.d.ts +9 -0
  64. package/dist/auth/providers/OktaProvider.d.ts.map +1 -1
  65. package/dist/auth/providers/OktaProvider.js +10 -0
  66. package/dist/auth/providers/OktaProvider.js.map +1 -1
  67. package/dist/config.d.ts +12 -0
  68. package/dist/config.d.ts.map +1 -1
  69. package/dist/config.js +42 -8
  70. package/dist/config.js.map +1 -1
  71. package/dist/context.d.ts +8 -1
  72. package/dist/context.d.ts.map +1 -1
  73. package/dist/context.js +26 -4
  74. package/dist/context.js.map +1 -1
  75. package/dist/directives/Public.js +2 -0
  76. package/dist/directives/Public.js.map +1 -1
  77. package/dist/entitySubclasses/entityPermissions.server.d.ts +7 -2
  78. package/dist/entitySubclasses/entityPermissions.server.d.ts.map +1 -1
  79. package/dist/entitySubclasses/entityPermissions.server.js +26 -8
  80. package/dist/entitySubclasses/entityPermissions.server.js.map +1 -1
  81. package/dist/generated/generated.d.ts +529 -6
  82. package/dist/generated/generated.d.ts.map +1 -1
  83. package/dist/generated/generated.js +10054 -15076
  84. package/dist/generated/generated.js.map +1 -1
  85. package/dist/generic/DeleteOptionsInput.d.ts +3 -0
  86. package/dist/generic/DeleteOptionsInput.d.ts.map +1 -1
  87. package/dist/generic/DeleteOptionsInput.js +3 -2
  88. package/dist/generic/DeleteOptionsInput.js.map +1 -1
  89. package/dist/generic/KeyInputOutputTypes.js +0 -6
  90. package/dist/generic/KeyInputOutputTypes.js.map +1 -1
  91. package/dist/generic/KeyValuePairInput.d.ts +4 -0
  92. package/dist/generic/KeyValuePairInput.d.ts.map +1 -1
  93. package/dist/generic/KeyValuePairInput.js +4 -2
  94. package/dist/generic/KeyValuePairInput.js.map +1 -1
  95. package/dist/generic/PushStatusResolver.js +0 -3
  96. package/dist/generic/PushStatusResolver.js.map +1 -1
  97. package/dist/generic/ResolverBase.d.ts +58 -0
  98. package/dist/generic/ResolverBase.d.ts.map +1 -1
  99. package/dist/generic/ResolverBase.js +203 -18
  100. package/dist/generic/ResolverBase.js.map +1 -1
  101. package/dist/generic/RunViewResolver.d.ts +22 -0
  102. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  103. package/dist/generic/RunViewResolver.js +42 -108
  104. package/dist/generic/RunViewResolver.js.map +1 -1
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +82 -37
  107. package/dist/index.js.map +1 -1
  108. package/dist/orm.d.ts.map +1 -1
  109. package/dist/orm.js +2 -1
  110. package/dist/orm.js.map +1 -1
  111. package/dist/resolvers/APIKeyResolver.d.ts +74 -0
  112. package/dist/resolvers/APIKeyResolver.d.ts.map +1 -1
  113. package/dist/resolvers/APIKeyResolver.js +49 -10
  114. package/dist/resolvers/APIKeyResolver.js.map +1 -1
  115. package/dist/resolvers/ActionResolver.d.ts +189 -0
  116. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  117. package/dist/resolvers/ActionResolver.js +152 -21
  118. package/dist/resolvers/ActionResolver.js.map +1 -1
  119. package/dist/resolvers/AskSkipResolver.d.ts +123 -0
  120. package/dist/resolvers/AskSkipResolver.d.ts.map +1 -0
  121. package/dist/resolvers/AskSkipResolver.js +1788 -0
  122. package/dist/resolvers/AskSkipResolver.js.map +1 -0
  123. package/dist/resolvers/ColorResolver.js +0 -5
  124. package/dist/resolvers/ColorResolver.js.map +1 -1
  125. package/dist/resolvers/ComponentRegistryResolver.d.ts +65 -0
  126. package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
  127. package/dist/resolvers/ComponentRegistryResolver.js +118 -40
  128. package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
  129. package/dist/resolvers/CreateQueryResolver.d.ts +47 -0
  130. package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
  131. package/dist/resolvers/CreateQueryResolver.js +92 -116
  132. package/dist/resolvers/CreateQueryResolver.js.map +1 -1
  133. package/dist/resolvers/DatasetResolver.js +2 -14
  134. package/dist/resolvers/DatasetResolver.js.map +1 -1
  135. package/dist/resolvers/EntityCommunicationsResolver.d.ts +40 -0
  136. package/dist/resolvers/EntityCommunicationsResolver.d.ts.map +1 -1
  137. package/dist/resolvers/EntityCommunicationsResolver.js +2 -36
  138. package/dist/resolvers/EntityCommunicationsResolver.js.map +1 -1
  139. package/dist/resolvers/EntityRecordNameResolver.js +0 -7
  140. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  141. package/dist/resolvers/FileCategoryResolver.js +13 -1
  142. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  143. package/dist/resolvers/FileResolver.d.ts +16 -0
  144. package/dist/resolvers/FileResolver.d.ts.map +1 -1
  145. package/dist/resolvers/FileResolver.js +59 -74
  146. package/dist/resolvers/FileResolver.js.map +1 -1
  147. package/dist/resolvers/GetDataContextDataResolver.d.ts +18 -1
  148. package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
  149. package/dist/resolvers/GetDataContextDataResolver.js +17 -9
  150. package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
  151. package/dist/resolvers/GetDataResolver.d.ts +19 -0
  152. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  153. package/dist/resolvers/GetDataResolver.js +35 -35
  154. package/dist/resolvers/GetDataResolver.js.map +1 -1
  155. package/dist/resolvers/InfoResolver.d.ts.map +1 -1
  156. package/dist/resolvers/InfoResolver.js +4 -7
  157. package/dist/resolvers/InfoResolver.js.map +1 -1
  158. package/dist/resolvers/MCPResolver.d.ts +325 -1
  159. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  160. package/dist/resolvers/MCPResolver.js +931 -24
  161. package/dist/resolvers/MCPResolver.js.map +1 -1
  162. package/dist/resolvers/MergeRecordsResolver.js +3 -29
  163. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  164. package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts.map +1 -1
  165. package/dist/resolvers/PotentialDuplicateRecordResolver.js +0 -3
  166. package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -1
  167. package/dist/resolvers/QueryResolver.d.ts +20 -0
  168. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  169. package/dist/resolvers/QueryResolver.js +44 -36
  170. package/dist/resolvers/QueryResolver.js.map +1 -1
  171. package/dist/resolvers/ReportResolver.d.ts +3 -0
  172. package/dist/resolvers/ReportResolver.d.ts.map +1 -1
  173. package/dist/resolvers/ReportResolver.js +9 -10
  174. package/dist/resolvers/ReportResolver.js.map +1 -1
  175. package/dist/resolvers/RunAIAgentResolver.d.ts +54 -0
  176. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  177. package/dist/resolvers/RunAIAgentResolver.js +116 -40
  178. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  179. package/dist/resolvers/RunAIPromptResolver.d.ts +42 -0
  180. package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
  181. package/dist/resolvers/RunAIPromptResolver.js +95 -22
  182. package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
  183. package/dist/resolvers/RunTemplateResolver.js +9 -6
  184. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  185. package/dist/resolvers/RunTestResolver.d.ts +12 -0
  186. package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
  187. package/dist/resolvers/RunTestResolver.js +35 -21
  188. package/dist/resolvers/RunTestResolver.js.map +1 -1
  189. package/dist/resolvers/SqlLoggingConfigResolver.d.ts +312 -0
  190. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  191. package/dist/resolvers/SqlLoggingConfigResolver.js +295 -45
  192. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  193. package/dist/resolvers/SyncDataResolver.d.ts +21 -0
  194. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  195. package/dist/resolvers/SyncDataResolver.js +36 -22
  196. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  197. package/dist/resolvers/SyncRolesUsersResolver.d.ts +14 -0
  198. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  199. package/dist/resolvers/SyncRolesUsersResolver.js +54 -21
  200. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  201. package/dist/resolvers/TaskResolver.d.ts +13 -0
  202. package/dist/resolvers/TaskResolver.d.ts.map +1 -1
  203. package/dist/resolvers/TaskResolver.js +22 -7
  204. package/dist/resolvers/TaskResolver.js.map +1 -1
  205. package/dist/resolvers/TelemetryResolver.d.ts +22 -0
  206. package/dist/resolvers/TelemetryResolver.d.ts.map +1 -1
  207. package/dist/resolvers/TelemetryResolver.js +45 -79
  208. package/dist/resolvers/TelemetryResolver.js.map +1 -1
  209. package/dist/resolvers/TransactionGroupResolver.js +11 -13
  210. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  211. package/dist/resolvers/UserFavoriteResolver.js +3 -12
  212. package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
  213. package/dist/resolvers/UserResolver.js +10 -0
  214. package/dist/resolvers/UserResolver.js.map +1 -1
  215. package/dist/resolvers/UserViewResolver.js +4 -0
  216. package/dist/resolvers/UserViewResolver.js.map +1 -1
  217. package/dist/resolvers/VersionHistoryResolver.d.ts +39 -0
  218. package/dist/resolvers/VersionHistoryResolver.d.ts.map +1 -0
  219. package/dist/resolvers/VersionHistoryResolver.js +208 -0
  220. package/dist/resolvers/VersionHistoryResolver.js.map +1 -0
  221. package/dist/rest/EntityCRUDHandler.d.ts +19 -0
  222. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  223. package/dist/rest/EntityCRUDHandler.js +55 -0
  224. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  225. package/dist/rest/OAuthCallbackHandler.d.ts +143 -0
  226. package/dist/rest/OAuthCallbackHandler.d.ts.map +1 -0
  227. package/dist/rest/OAuthCallbackHandler.js +634 -0
  228. package/dist/rest/OAuthCallbackHandler.js.map +1 -0
  229. package/dist/rest/RESTEndpointHandler.d.ts +120 -0
  230. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  231. package/dist/rest/RESTEndpointHandler.js +213 -24
  232. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  233. package/dist/rest/ViewOperationsHandler.d.ts +19 -0
  234. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  235. package/dist/rest/ViewOperationsHandler.js +39 -0
  236. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  237. package/dist/rest/index.d.ts +1 -0
  238. package/dist/rest/index.d.ts.map +1 -1
  239. package/dist/rest/index.js +1 -0
  240. package/dist/rest/index.js.map +1 -1
  241. package/dist/rest/setupRESTEndpoints.d.ts +35 -0
  242. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -1
  243. package/dist/rest/setupRESTEndpoints.js +15 -1
  244. package/dist/rest/setupRESTEndpoints.js.map +1 -1
  245. package/dist/scheduler/LearningCycleScheduler.d.ts +4 -0
  246. package/dist/scheduler/LearningCycleScheduler.d.ts.map +1 -0
  247. package/dist/scheduler/LearningCycleScheduler.js +4 -0
  248. package/dist/scheduler/LearningCycleScheduler.js.map +1 -0
  249. package/dist/services/ScheduledJobsService.d.ts +31 -0
  250. package/dist/services/ScheduledJobsService.d.ts.map +1 -1
  251. package/dist/services/ScheduledJobsService.js +38 -4
  252. package/dist/services/ScheduledJobsService.js.map +1 -1
  253. package/dist/services/TaskOrchestrator.d.ts +73 -0
  254. package/dist/services/TaskOrchestrator.d.ts.map +1 -1
  255. package/dist/services/TaskOrchestrator.js +137 -15
  256. package/dist/services/TaskOrchestrator.js.map +1 -1
  257. package/dist/types.d.ts +14 -0
  258. package/dist/types.d.ts.map +1 -1
  259. package/dist/types.js +0 -13
  260. package/dist/types.js.map +1 -1
  261. package/dist/util.d.ts +37 -1
  262. package/dist/util.d.ts.map +1 -1
  263. package/dist/util.js +55 -8
  264. package/dist/util.js.map +1 -1
  265. package/package.json +79 -78
  266. package/src/auth/exampleNewUserSubClass.ts +1 -5
  267. package/src/entitySubclasses/entityPermissions.server.ts +1 -3
  268. package/src/generated/generated.ts +4682 -2681
  269. package/src/index.ts +61 -62
  270. package/src/resolvers/InfoResolver.ts +5 -1
  271. package/src/resolvers/MCPResolver.ts +910 -10
  272. package/src/resolvers/PotentialDuplicateRecordResolver.ts +0 -4
  273. package/src/resolvers/VersionHistoryResolver.ts +177 -0
  274. package/src/rest/OAuthCallbackHandler.ts +766 -0
  275. package/src/rest/RESTEndpointHandler.ts +58 -35
  276. package/src/rest/index.ts +2 -1
  277. package/src/rest/setupRESTEndpoints.ts +13 -12
@@ -2,31 +2,55 @@ import express from 'express';
2
2
  import { CompositeKey, EntityDeleteOptions, LogError, Metadata } from '@memberjunction/core';
3
3
  import { EntityCRUDHandler } from './EntityCRUDHandler.js';
4
4
  import { ViewOperationsHandler } from './ViewOperationsHandler.js';
5
+ /**
6
+ * RESTEndpointHandler provides REST API functionality for MemberJunction entities
7
+ * This class handles request routing and processing for a /rest endpoint that exposes
8
+ * entity operations via REST instead of GraphQL
9
+ */
5
10
  export class RESTEndpointHandler {
6
- router;
7
- options;
8
11
  constructor(options = {}) {
9
12
  this.router = express.Router();
10
13
  this.options = options;
11
14
  this.setupRoutes();
12
15
  }
16
+ /**
17
+ * Helper to safely extract a string from Express route params
18
+ * Express 5.x types params as string | string[] | undefined
19
+ */
20
+ getStringParam(param) {
21
+ if (Array.isArray(param)) {
22
+ return param[0] || '';
23
+ }
24
+ return param || '';
25
+ }
26
+ /**
27
+ * Determines if an entity is allowed based on include/exclude lists
28
+ * with support for wildcards and schema-level filtering
29
+ * @param entityName The name of the entity to check
30
+ * @returns True if the entity is allowed, false otherwise
31
+ */
13
32
  isEntityAllowed(entityName) {
14
33
  const name = entityName.toLowerCase();
15
34
  const md = new Metadata();
16
35
  const entity = md.Entities.find(e => e.Name.toLowerCase() === name);
36
+ // If entity not found in metadata, don't allow it
17
37
  if (!entity) {
18
38
  return false;
19
39
  }
20
40
  const schemaName = entity.SchemaName.toLowerCase();
41
+ // 1. Check schema exclusions first (these take highest precedence)
21
42
  if (this.options.excludeSchemas && this.options.excludeSchemas.length > 0) {
22
43
  if (this.options.excludeSchemas.some(schema => schema.toLowerCase() === schemaName)) {
23
44
  return false;
24
45
  }
25
46
  }
47
+ // 2. Check entity exclusions next (these override entity inclusions)
26
48
  if (this.options.excludeEntities && this.options.excludeEntities.length > 0) {
49
+ // Check for direct match
27
50
  if (this.options.excludeEntities.includes(name)) {
28
51
  return false;
29
52
  }
53
+ // Check for wildcard matches
30
54
  for (const pattern of this.options.excludeEntities) {
31
55
  if (pattern.includes('*')) {
32
56
  const regex = new RegExp('^' + pattern.toLowerCase().replace(/\*/g, '.*') + '$');
@@ -36,15 +60,19 @@ export class RESTEndpointHandler {
36
60
  }
37
61
  }
38
62
  }
63
+ // 3. Check schema inclusions (if specified, only entities from these schemas are allowed)
39
64
  if (this.options.includeSchemas && this.options.includeSchemas.length > 0) {
40
65
  if (!this.options.includeSchemas.some(schema => schema.toLowerCase() === schemaName)) {
41
66
  return false;
42
67
  }
43
68
  }
69
+ // 4. Check entity inclusions
44
70
  if (this.options.includeEntities && this.options.includeEntities.length > 0) {
71
+ // Check for direct match
45
72
  if (this.options.includeEntities.includes(name)) {
46
73
  return true;
47
74
  }
75
+ // Check for wildcard matches
48
76
  for (const pattern of this.options.includeEntities) {
49
77
  if (pattern.includes('*')) {
50
78
  const regex = new RegExp('^' + pattern.toLowerCase().replace(/\*/g, '.*') + '$');
@@ -53,41 +81,61 @@ export class RESTEndpointHandler {
53
81
  }
54
82
  }
55
83
  }
84
+ // If include list is specified but no matches found, entity is not allowed
56
85
  return false;
57
86
  }
87
+ // By default, allow all entities
58
88
  return true;
59
89
  }
90
+ /**
91
+ * Set up all the API routes for the REST endpoints
92
+ */
60
93
  setupRoutes() {
94
+ // Middleware to extract MJ user
61
95
  this.router.use(this.extractMJUser);
96
+ // Middleware to check entity allowlist/blocklist
62
97
  this.router.use('/entities/:entityName', this.checkEntityAccess.bind(this));
63
98
  this.router.use('/views/:entityName', this.checkEntityAccess.bind(this));
64
99
  this.router.use('/metadata/entities/:entityName', this.checkEntityAccess.bind(this));
65
100
  this.router.use('/users/:userId/favorites/:entityName', this.checkEntityAccess.bind(this));
101
+ // Entity collection operations
66
102
  this.router.get('/entities/:entityName', this.getEntityList.bind(this));
67
103
  this.router.post('/entities/:entityName', this.createEntity.bind(this));
104
+ // Individual entity operations
68
105
  this.router.get('/entities/:entityName/:id', this.getEntity.bind(this));
69
106
  this.router.put('/entities/:entityName/:id', this.updateEntity.bind(this));
70
107
  this.router.delete('/entities/:entityName/:id', this.deleteEntity.bind(this));
108
+ // Record changes and dependencies
71
109
  this.router.get('/entities/:entityName/:id/changes', this.getRecordChanges.bind(this));
72
110
  this.router.get('/entities/:entityName/:id/dependencies', this.getRecordDependencies.bind(this));
73
111
  this.router.get('/entities/:entityName/:id/name', this.getEntityRecordName.bind(this));
112
+ // View operations
74
113
  this.router.post('/views/:entityName', this.runView.bind(this));
75
114
  this.router.post('/views/batch', this.runViews.bind(this));
76
115
  this.router.get('/views/entity', this.getViewEntity.bind(this));
116
+ // Metadata endpoints
77
117
  this.router.get('/metadata/entities', this.getEntityMetadata.bind(this));
78
118
  this.router.get('/metadata/entities/:entityName', this.getEntityFieldMetadata.bind(this));
119
+ // Views metadata
79
120
  this.router.get('/views/:entityName/metadata', this.getViewsMetadata.bind(this));
121
+ // User operations
80
122
  this.router.get('/users/current', this.getCurrentUser.bind(this));
81
123
  this.router.get('/users/:userId/favorites/:entityName/:id', this.getRecordFavoriteStatus.bind(this));
82
124
  this.router.put('/users/:userId/favorites/:entityName/:id', this.setRecordFavoriteStatus.bind(this));
83
125
  this.router.delete('/users/:userId/favorites/:entityName/:id', this.removeRecordFavoriteStatus.bind(this));
126
+ // Transaction operations
84
127
  this.router.post('/transactions', this.executeTransaction.bind(this));
128
+ // Reports and queries
85
129
  this.router.get('/reports/:reportId', this.runReport.bind(this));
86
130
  this.router.post('/queries/run', this.runQuery.bind(this));
131
+ // Error handling
87
132
  this.router.use(this.errorHandler);
88
133
  }
134
+ /**
135
+ * Middleware to check entity access based on include/exclude lists
136
+ */
89
137
  checkEntityAccess(req, res, next) {
90
- const entityName = req.params.entityName;
138
+ const entityName = this.getStringParam(req.params.entityName);
91
139
  if (!entityName) {
92
140
  next();
93
141
  return;
@@ -101,11 +149,19 @@ export class RESTEndpointHandler {
101
149
  }
102
150
  next();
103
151
  }
152
+ /**
153
+ * Middleware to extract MJ user from request
154
+ */
104
155
  async extractMJUser(req, res, next) {
105
156
  try {
157
+ // If authentication middleware has already set req.user with basic info
106
158
  if (req['user']) {
159
+ // Get the full MemberJunction user
107
160
  const md = new Metadata();
108
161
  const userInfo = req['user'];
162
+ // Get user info based on email or ID
163
+ // Note: The actual implementation here would depend on how the MemberJunction core handles user lookup
164
+ // This is a simplification that would need to be implemented properly
109
165
  req['mjUser'] = userInfo;
110
166
  if (!req['mjUser']) {
111
167
  res.status(401).json({ error: 'User not found in MemberJunction' });
@@ -122,6 +178,9 @@ export class RESTEndpointHandler {
122
178
  next(error);
123
179
  }
124
180
  }
181
+ /**
182
+ * Error handling middleware
183
+ */
125
184
  errorHandler(err, req, res, next) {
126
185
  LogError(err);
127
186
  if (err.name === 'UnauthorizedError') {
@@ -130,9 +189,13 @@ export class RESTEndpointHandler {
130
189
  }
131
190
  res.status(500).json({ error: err?.message || 'Internal server error' });
132
191
  }
192
+ /**
193
+ * Get the current user
194
+ */
133
195
  async getCurrentUser(req, res) {
134
196
  try {
135
197
  const user = req['mjUser'];
198
+ // Return user info without sensitive data
136
199
  res.json({
137
200
  ID: user.ID,
138
201
  Name: user.Name,
@@ -152,11 +215,15 @@ export class RESTEndpointHandler {
152
215
  res.status(500).json({ error: error?.message || 'Unknown error' });
153
216
  }
154
217
  }
218
+ /**
219
+ * Lists entities with optional filtering
220
+ */
155
221
  async getEntityList(req, res) {
156
222
  try {
157
- const { entityName } = req.params;
223
+ const entityName = this.getStringParam(req.params.entityName);
158
224
  const { filter, orderBy, fields, maxRows, startRow } = req.query;
159
225
  const user = req['mjUser'];
226
+ // Convert the request to a RunViewParams object
160
227
  const params = {
161
228
  EntityName: entityName,
162
229
  ExtraFilter: filter,
@@ -173,10 +240,14 @@ export class RESTEndpointHandler {
173
240
  res.status(500).json({ error: error?.message || 'Unknown error' });
174
241
  }
175
242
  }
243
+ /**
244
+ * Get a single entity by ID
245
+ */
176
246
  async getEntity(req, res) {
177
247
  try {
178
- const { entityName, id } = req.params;
179
- const { include } = req.query;
248
+ const entityName = this.getStringParam(req.params.entityName);
249
+ const id = this.getStringParam(req.params.id);
250
+ const { include } = req.query; // Optional related entities to include
180
251
  const user = req['mjUser'];
181
252
  const relatedEntities = include ? include.split(',') : null;
182
253
  const result = await EntityCRUDHandler.getEntity(entityName, id, relatedEntities, user);
@@ -192,9 +263,12 @@ export class RESTEndpointHandler {
192
263
  res.status(500).json({ error: error?.message || 'Unknown error' });
193
264
  }
194
265
  }
266
+ /**
267
+ * Create a new entity
268
+ */
195
269
  async createEntity(req, res) {
196
270
  try {
197
- const { entityName } = req.params;
271
+ const entityName = this.getStringParam(req.params.entityName);
198
272
  const entityData = req.body;
199
273
  const user = req['mjUser'];
200
274
  const result = await EntityCRUDHandler.createEntity(entityName, entityData, user);
@@ -213,9 +287,13 @@ export class RESTEndpointHandler {
213
287
  res.status(500).json({ error: error?.message || 'Unknown error' });
214
288
  }
215
289
  }
290
+ /**
291
+ * Update an existing entity
292
+ */
216
293
  async updateEntity(req, res) {
217
294
  try {
218
- const { entityName, id } = req.params;
295
+ const entityName = this.getStringParam(req.params.entityName);
296
+ const id = this.getStringParam(req.params.id);
219
297
  const updateData = req.body;
220
298
  const user = req['mjUser'];
221
299
  const result = await EntityCRUDHandler.updateEntity(entityName, id, updateData, user);
@@ -234,11 +312,16 @@ export class RESTEndpointHandler {
234
312
  res.status(500).json({ error: error?.message || 'Unknown error' });
235
313
  }
236
314
  }
315
+ /**
316
+ * Delete an entity
317
+ */
237
318
  async deleteEntity(req, res) {
238
319
  try {
239
- const { entityName, id } = req.params;
320
+ const entityName = this.getStringParam(req.params.entityName);
321
+ const id = this.getStringParam(req.params.id);
240
322
  const options = req.query.options ? JSON.parse(req.query.options) : {};
241
323
  const user = req['mjUser'];
324
+ // Convert options to EntityDeleteOptions
242
325
  const deleteOptions = new EntityDeleteOptions();
243
326
  if (options.SkipEntityAIActions !== undefined)
244
327
  deleteOptions.SkipEntityAIActions = !!options.SkipEntityAIActions;
@@ -259,14 +342,23 @@ export class RESTEndpointHandler {
259
342
  res.status(500).json({ error: error?.message || 'Unknown error' });
260
343
  }
261
344
  }
345
+ /**
346
+ * Get record changes for an entity
347
+ */
262
348
  async getRecordChanges(req, res) {
263
349
  try {
264
- const { entityName, id } = req.params;
350
+ const entityName = this.getStringParam(req.params.entityName);
351
+ const id = this.getStringParam(req.params.id);
265
352
  const user = req['mjUser'];
353
+ // Get the entity object
266
354
  const md = new Metadata();
267
355
  const entity = await md.GetEntityObject(entityName, user);
356
+ // Create a composite key
268
357
  const compositeKey = this.createCompositeKey(entity.EntityInfo, id);
269
- const changes = [];
358
+ // Use a direct approach for getting record changes
359
+ // Note: This is a simplification. The actual implementation may need to be adjusted
360
+ // based on how the MemberJunction core handles record changes
361
+ const changes = []; // This would be populated with actual record changes
270
362
  res.json(changes);
271
363
  }
272
364
  catch (error) {
@@ -274,14 +366,23 @@ export class RESTEndpointHandler {
274
366
  res.status(500).json({ error: error?.message || 'Unknown error' });
275
367
  }
276
368
  }
369
+ /**
370
+ * Get record dependencies for an entity
371
+ */
277
372
  async getRecordDependencies(req, res) {
278
373
  try {
279
- const { entityName, id } = req.params;
374
+ const entityName = this.getStringParam(req.params.entityName);
375
+ const id = this.getStringParam(req.params.id);
280
376
  const user = req['mjUser'];
377
+ // Get the entity object
281
378
  const md = new Metadata();
282
379
  const entity = await md.GetEntityObject(entityName, user);
380
+ // Create a composite key
283
381
  const compositeKey = this.createCompositeKey(entity.EntityInfo, id);
284
- const dependencies = [];
382
+ // Use a direct approach for getting record dependencies
383
+ // Note: This is a simplification. The actual implementation may need to be adjusted
384
+ // based on how the MemberJunction core handles record dependencies
385
+ const dependencies = []; // This would be populated with actual record dependencies
285
386
  res.json(dependencies);
286
387
  }
287
388
  catch (error) {
@@ -289,14 +390,22 @@ export class RESTEndpointHandler {
289
390
  res.status(500).json({ error: error?.message || 'Unknown error' });
290
391
  }
291
392
  }
393
+ /**
394
+ * Get entity record name
395
+ */
292
396
  async getEntityRecordName(req, res) {
293
397
  try {
294
- const { entityName, id } = req.params;
398
+ const entityName = this.getStringParam(req.params.entityName);
399
+ const id = this.getStringParam(req.params.id);
295
400
  const user = req['mjUser'];
401
+ // Get the entity object
296
402
  const md = new Metadata();
297
403
  const entity = await md.GetEntityObject(entityName, user);
404
+ // Create a composite key
298
405
  const compositeKey = this.createCompositeKey(entity.EntityInfo, id);
299
- const recordName = "Record Name";
406
+ // Use a direct approach for getting entity record name
407
+ // Note: This is a simplification. The actual implementation may need to be adjusted
408
+ const recordName = "Record Name"; // This would be populated with the actual record name
300
409
  res.json({ recordName });
301
410
  }
302
411
  catch (error) {
@@ -304,11 +413,15 @@ export class RESTEndpointHandler {
304
413
  res.status(500).json({ error: error?.message || 'Unknown error' });
305
414
  }
306
415
  }
416
+ /**
417
+ * Run a view
418
+ */
307
419
  async runView(req, res) {
308
420
  try {
309
- const { entityName } = req.params;
421
+ const entityName = this.getStringParam(req.params.entityName);
310
422
  const viewParams = req.body;
311
423
  const user = req['mjUser'];
424
+ // Create RunViewParams from the request body
312
425
  const params = {
313
426
  EntityName: entityName,
314
427
  ...viewParams
@@ -326,6 +439,9 @@ export class RESTEndpointHandler {
326
439
  res.status(500).json({ error: error?.message || 'Unknown error' });
327
440
  }
328
441
  }
442
+ /**
443
+ * Run multiple views in batch
444
+ */
329
445
  async runViews(req, res) {
330
446
  try {
331
447
  const { params } = req.body;
@@ -334,7 +450,10 @@ export class RESTEndpointHandler {
334
450
  res.status(400).json({ error: 'params must be an array of RunViewParams' });
335
451
  return;
336
452
  }
453
+ // Filter out any views for entities that aren't allowed
454
+ // using our enhanced entity filtering with wildcards and schema support
337
455
  const filteredParams = params.filter(p => this.isEntityAllowed(p.EntityName));
456
+ // If all requested entities were filtered out, return an error
338
457
  if (filteredParams.length === 0 && params.length > 0) {
339
458
  res.status(403).json({
340
459
  error: 'None of the requested entities are allowed through the REST API',
@@ -355,6 +474,9 @@ export class RESTEndpointHandler {
355
474
  res.status(500).json({ error: error?.message || 'Unknown error' });
356
475
  }
357
476
  }
477
+ /**
478
+ * Get entity for a view
479
+ */
358
480
  async getViewEntity(req, res) {
359
481
  try {
360
482
  const { ViewID, ViewName } = req.query;
@@ -363,7 +485,8 @@ export class RESTEndpointHandler {
363
485
  res.status(400).json({ error: 'Either ViewID or ViewName must be provided' });
364
486
  return;
365
487
  }
366
- const entityName = "SampleEntity";
488
+ // Placeholder implementation - this would need to be implemented to lookup view metadata
489
+ const entityName = "SampleEntity"; // This would be determined by looking up the view
367
490
  res.json({ entityName });
368
491
  }
369
492
  catch (error) {
@@ -371,14 +494,20 @@ export class RESTEndpointHandler {
371
494
  res.status(500).json({ error: error?.message || 'Unknown error' });
372
495
  }
373
496
  }
497
+ /**
498
+ * Get metadata for all entities
499
+ */
374
500
  async getEntityMetadata(req, res) {
375
501
  try {
376
502
  const user = req['mjUser'];
503
+ // Filter entities based on user permissions and REST API configuration
377
504
  const md = new Metadata();
378
505
  const entities = md.Entities.filter(e => {
506
+ // First check if entity is allowed based on configuration
379
507
  if (!this.isEntityAllowed(e.Name)) {
380
508
  return false;
381
509
  }
510
+ // Then check user permissions
382
511
  const permissions = e.GetUserPermisions(user);
383
512
  return permissions.CanRead;
384
513
  });
@@ -400,9 +529,12 @@ export class RESTEndpointHandler {
400
529
  res.status(500).json({ error: error?.message || 'Unknown error' });
401
530
  }
402
531
  }
532
+ /**
533
+ * Get field metadata for a specific entity
534
+ */
403
535
  async getEntityFieldMetadata(req, res) {
404
536
  try {
405
- const { entityName } = req.params;
537
+ const entityName = this.getStringParam(req.params.entityName);
406
538
  const user = req['mjUser'];
407
539
  const md = new Metadata();
408
540
  const entity = md.Entities.find(e => e.Name === entityName);
@@ -410,6 +542,7 @@ export class RESTEndpointHandler {
410
542
  res.status(404).json({ error: `Entity '${entityName}' not found` });
411
543
  return;
412
544
  }
545
+ // Check if user can read this entity
413
546
  const permissions = entity.GetUserPermisions(user);
414
547
  if (!permissions.CanRead) {
415
548
  res.status(403).json({ error: 'Permission denied' });
@@ -435,11 +568,16 @@ export class RESTEndpointHandler {
435
568
  res.status(500).json({ error: error?.message || 'Unknown error' });
436
569
  }
437
570
  }
571
+ /**
572
+ * Get metadata about available views for an entity
573
+ */
438
574
  async getViewsMetadata(req, res) {
439
575
  try {
440
- const { entityName } = req.params;
576
+ const entityName = this.getStringParam(req.params.entityName);
441
577
  const user = req['mjUser'];
442
- const views = [];
578
+ // This would need to be implemented to retrieve available views
579
+ // Placeholder implementation
580
+ const views = []; // Would need to query available views for this entity
443
581
  res.json(views);
444
582
  }
445
583
  catch (error) {
@@ -447,14 +585,23 @@ export class RESTEndpointHandler {
447
585
  res.status(500).json({ error: error?.message || 'Unknown error' });
448
586
  }
449
587
  }
588
+ /**
589
+ * Get favorite status for a record
590
+ */
450
591
  async getRecordFavoriteStatus(req, res) {
451
592
  try {
452
- const { userId, entityName, id } = req.params;
593
+ const userId = this.getStringParam(req.params.userId);
594
+ const entityName = this.getStringParam(req.params.entityName);
595
+ const id = this.getStringParam(req.params.id);
453
596
  const user = req['mjUser'];
597
+ // Get the entity object
454
598
  const md = new Metadata();
455
599
  const entity = await md.GetEntityObject(entityName, user);
600
+ // Create a composite key
456
601
  const compositeKey = this.createCompositeKey(entity.EntityInfo, id);
457
- const isFavorite = false;
602
+ // Use a direct approach for getting favorite status
603
+ // Note: This is a simplification. The actual implementation may need to be adjusted
604
+ const isFavorite = false; // This would be populated with the actual favorite status
458
605
  res.json({ isFavorite });
459
606
  }
460
607
  catch (error) {
@@ -462,13 +609,23 @@ export class RESTEndpointHandler {
462
609
  res.status(500).json({ error: error?.message || 'Unknown error' });
463
610
  }
464
611
  }
612
+ /**
613
+ * Set favorite status for a record
614
+ */
465
615
  async setRecordFavoriteStatus(req, res) {
466
616
  try {
467
- const { userId, entityName, id } = req.params;
617
+ const userId = this.getStringParam(req.params.userId);
618
+ const entityName = this.getStringParam(req.params.entityName);
619
+ const id = this.getStringParam(req.params.id);
468
620
  const user = req['mjUser'];
621
+ // Get the entity object
469
622
  const md = new Metadata();
470
623
  const entity = await md.GetEntityObject(entityName, user);
624
+ // Create a composite key
471
625
  const compositeKey = this.createCompositeKey(entity.EntityInfo, id);
626
+ // Use a direct approach for setting favorite status
627
+ // Note: This is a simplification. The actual implementation may need to be adjusted
628
+ // This would set the favorite status to true
472
629
  res.status(204).send();
473
630
  }
474
631
  catch (error) {
@@ -476,13 +633,23 @@ export class RESTEndpointHandler {
476
633
  res.status(500).json({ error: error?.message || 'Unknown error' });
477
634
  }
478
635
  }
636
+ /**
637
+ * Remove favorite status for a record
638
+ */
479
639
  async removeRecordFavoriteStatus(req, res) {
480
640
  try {
481
- const { userId, entityName, id } = req.params;
641
+ const userId = this.getStringParam(req.params.userId);
642
+ const entityName = this.getStringParam(req.params.entityName);
643
+ const id = this.getStringParam(req.params.id);
482
644
  const user = req['mjUser'];
645
+ // Get the entity object
483
646
  const md = new Metadata();
484
647
  const entity = await md.GetEntityObject(entityName, user);
648
+ // Create a composite key
485
649
  const compositeKey = this.createCompositeKey(entity.EntityInfo, id);
650
+ // Use a direct approach for setting favorite status
651
+ // Note: This is a simplification. The actual implementation may need to be adjusted
652
+ // This would set the favorite status to false
486
653
  res.status(204).send();
487
654
  }
488
655
  catch (error) {
@@ -490,8 +657,12 @@ export class RESTEndpointHandler {
490
657
  res.status(500).json({ error: error?.message || 'Unknown error' });
491
658
  }
492
659
  }
660
+ /**
661
+ * Execute a transaction
662
+ */
493
663
  async executeTransaction(req, res) {
494
664
  try {
665
+ // Placeholder implementation - this would need to be implemented to handle transactions
495
666
  res.status(501).json({ error: 'Not implemented' });
496
667
  }
497
668
  catch (error) {
@@ -499,8 +670,12 @@ export class RESTEndpointHandler {
499
670
  res.status(500).json({ error: error?.message || 'Unknown error' });
500
671
  }
501
672
  }
673
+ /**
674
+ * Run a report
675
+ */
502
676
  async runReport(req, res) {
503
677
  try {
678
+ // Placeholder implementation - this would need to be implemented to run reports
504
679
  res.status(501).json({ error: 'Not implemented' });
505
680
  }
506
681
  catch (error) {
@@ -508,8 +683,12 @@ export class RESTEndpointHandler {
508
683
  res.status(500).json({ error: error?.message || 'Unknown error' });
509
684
  }
510
685
  }
686
+ /**
687
+ * Run a query
688
+ */
511
689
  async runQuery(req, res) {
512
690
  try {
691
+ // Placeholder implementation - this would need to be implemented to run queries
513
692
  res.status(501).json({ error: 'Not implemented' });
514
693
  }
515
694
  catch (error) {
@@ -517,19 +696,29 @@ export class RESTEndpointHandler {
517
696
  res.status(500).json({ error: error?.message || 'Unknown error' });
518
697
  }
519
698
  }
699
+ /**
700
+ * Helper method to create a composite key from an ID
701
+ */
520
702
  createCompositeKey(entityInfo, id) {
521
703
  if (entityInfo.PrimaryKeys.length === 1) {
704
+ // Single primary key
522
705
  const primaryKeyField = entityInfo.PrimaryKeys[0].Name;
523
706
  const compositeKey = new CompositeKey();
707
+ // Use key-value pairs instead of SetValue
524
708
  compositeKey.KeyValuePairs = [
525
709
  { FieldName: primaryKeyField, Value: id }
526
710
  ];
527
711
  return compositeKey;
528
712
  }
529
713
  else {
714
+ // Composite primary key
715
+ // This is a simplification - in a real implementation, we would need to handle composite keys properly
530
716
  throw new Error('Composite primary keys are not supported in this implementation');
531
717
  }
532
718
  }
719
+ /**
720
+ * Get the Express router with all configured routes
721
+ */
533
722
  getRouter() {
534
723
  return this.router;
535
724
  }