@mondaydotcomorg/monday-authorization 3.6.0-feat-shaime-support-entity-attributes-1-f58d933 → 3.6.0-feat-shaime-support-entity-attributes-3-0d092e0

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 (96) hide show
  1. package/dist/authorization-attributes-ms-service.d.ts +68 -0
  2. package/dist/authorization-attributes-ms-service.d.ts.map +1 -0
  3. package/dist/authorization-attributes-ms-service.js +263 -0
  4. package/dist/authorization-attributes-service.d.ts +25 -47
  5. package/dist/authorization-attributes-service.d.ts.map +1 -1
  6. package/dist/authorization-attributes-service.js +32 -171
  7. package/dist/authorization-attributes-sns-service.d.ts +84 -0
  8. package/dist/authorization-attributes-sns-service.d.ts.map +1 -0
  9. package/dist/authorization-attributes-sns-service.js +196 -0
  10. package/dist/constants/sns.d.ts +12 -2
  11. package/dist/constants/sns.d.ts.map +1 -1
  12. package/dist/constants/sns.js +22 -2
  13. package/dist/entity-attribute-assignment.d.ts.map +1 -1
  14. package/dist/entity-attribute-assignment.js +0 -2
  15. package/dist/esm/authorization-attributes-ms-service.d.ts +68 -0
  16. package/dist/esm/authorization-attributes-ms-service.d.ts.map +1 -0
  17. package/dist/esm/authorization-attributes-ms-service.mjs +261 -0
  18. package/dist/esm/authorization-attributes-service.d.ts +25 -47
  19. package/dist/esm/authorization-attributes-service.d.ts.map +1 -1
  20. package/dist/esm/authorization-attributes-service.mjs +32 -167
  21. package/dist/esm/authorization-attributes-sns-service.d.ts +84 -0
  22. package/dist/esm/authorization-attributes-sns-service.d.ts.map +1 -0
  23. package/dist/esm/authorization-attributes-sns-service.mjs +190 -0
  24. package/dist/esm/constants/sns.d.ts +12 -2
  25. package/dist/esm/constants/sns.d.ts.map +1 -1
  26. package/dist/esm/constants/sns.mjs +17 -3
  27. package/dist/esm/entity-attribute-assignment.d.ts.map +1 -1
  28. package/dist/esm/entity-attribute-assignment.mjs +0 -2
  29. package/dist/esm/index.d.ts +6 -0
  30. package/dist/esm/index.d.ts.map +1 -1
  31. package/dist/esm/index.mjs +5 -0
  32. package/dist/esm/resource-attribute-assignment.d.ts.map +1 -1
  33. package/dist/esm/resource-attribute-assignment.mjs +0 -2
  34. package/dist/esm/resource-attributes-constants.d.ts +2 -1
  35. package/dist/esm/resource-attributes-constants.d.ts.map +1 -1
  36. package/dist/esm/resource-attributes-constants.mjs +1 -0
  37. package/dist/esm/types/authorization-attributes-contracts.d.ts +2 -17
  38. package/dist/esm/types/authorization-attributes-contracts.d.ts.map +1 -1
  39. package/dist/esm/types/authorization-attributes-service.interface.d.ts +14 -25
  40. package/dist/esm/types/authorization-attributes-service.interface.d.ts.map +1 -1
  41. package/dist/esm/types/general.d.ts +4 -3
  42. package/dist/esm/types/general.d.ts.map +1 -1
  43. package/dist/esm/utils/assignment-schema.d.ts +51 -0
  44. package/dist/esm/utils/assignment-schema.d.ts.map +1 -0
  45. package/dist/esm/utils/assignment-schema.mjs +49 -0
  46. package/dist/esm/utils/resource-schema.d.ts +9 -0
  47. package/dist/esm/utils/resource-schema.d.ts.map +1 -0
  48. package/dist/esm/utils/resource-schema.mjs +16 -0
  49. package/dist/esm/utils/validation.d.ts +11 -3
  50. package/dist/esm/utils/validation.d.ts.map +1 -1
  51. package/dist/esm/utils/validation.mjs +50 -13
  52. package/dist/index.d.ts +6 -0
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +22 -0
  55. package/dist/resource-attribute-assignment.d.ts.map +1 -1
  56. package/dist/resource-attribute-assignment.js +0 -2
  57. package/dist/resource-attributes-constants.d.ts +2 -1
  58. package/dist/resource-attributes-constants.d.ts.map +1 -1
  59. package/dist/resource-attributes-constants.js +1 -0
  60. package/dist/types/authorization-attributes-contracts.d.ts +2 -17
  61. package/dist/types/authorization-attributes-contracts.d.ts.map +1 -1
  62. package/dist/types/authorization-attributes-service.interface.d.ts +14 -25
  63. package/dist/types/authorization-attributes-service.interface.d.ts.map +1 -1
  64. package/dist/types/general.d.ts +4 -3
  65. package/dist/types/general.d.ts.map +1 -1
  66. package/dist/utils/assignment-schema.d.ts +51 -0
  67. package/dist/utils/assignment-schema.d.ts.map +1 -0
  68. package/dist/utils/assignment-schema.js +58 -0
  69. package/dist/utils/resource-schema.d.ts +9 -0
  70. package/dist/utils/resource-schema.d.ts.map +1 -0
  71. package/dist/utils/resource-schema.js +22 -0
  72. package/dist/utils/validation.d.ts +11 -3
  73. package/dist/utils/validation.d.ts.map +1 -1
  74. package/dist/utils/validation.js +50 -13
  75. package/package.json +1 -1
  76. package/src/authorization-attributes-ms-service.ts +397 -0
  77. package/src/authorization-attributes-service.ts +34 -222
  78. package/src/authorization-attributes-sns-service.ts +321 -0
  79. package/src/constants/sns.ts +19 -2
  80. package/src/entity-attribute-assignment.ts +0 -2
  81. package/src/index.ts +17 -0
  82. package/src/resource-attribute-assignment.ts +0 -2
  83. package/src/resource-attributes-constants.ts +1 -0
  84. package/src/types/authorization-attributes-contracts.ts +2 -24
  85. package/src/types/authorization-attributes-service.interface.ts +19 -31
  86. package/src/types/general.ts +4 -3
  87. package/src/utils/assignment-schema.ts +51 -0
  88. package/src/utils/resource-schema.ts +16 -0
  89. package/src/utils/validation.ts +76 -15
  90. package/dist/esm/utils/assigment-schema.d.ts +0 -29
  91. package/dist/esm/utils/assigment-schema.d.ts.map +0 -1
  92. package/dist/esm/utils/assigment-schema.mjs +0 -29
  93. package/dist/utils/assigment-schema.d.ts +0 -29
  94. package/dist/utils/assigment-schema.d.ts.map +0 -1
  95. package/dist/utils/assigment-schema.js +0 -36
  96. package/src/utils/assigment-schema.ts +0 -29
@@ -2,7 +2,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
 
3
3
  const Ajv = require('ajv');
4
4
  const errors_argumentError = require('../errors/argument-error.js');
5
- const utils_assigmentSchema = require('./assigment-schema.js');
5
+ const utils_assignmentSchema = require('./assignment-schema.js');
6
+ const utils_resourceSchema = require('./resource-schema.js');
6
7
 
7
8
  const _interopDefault = e => e && e.__esModule ? e : { default: e };
8
9
 
@@ -37,27 +38,63 @@ class ValidationUtils {
37
38
  throw new errors_argumentError.ArgumentError('Invalid string array');
38
39
  }
39
40
  }
41
+ static validateResource(resource) {
42
+ const isValid = utils_resourceSchema.resourceSchema(resource);
43
+ if (!isValid) {
44
+ const errorMessage = this.formatValidationErrors(utils_resourceSchema.resourceSchema.errors);
45
+ throw new errors_argumentError.ArgumentError(errorMessage ? `Invalid resource: ${errorMessage}` : 'Invalid resource');
46
+ }
47
+ }
40
48
  /**
41
49
  * Validates an attribute assignment object using a single AJV schema.
42
50
  * Preserves legacy error messages for each field.
43
51
  */
44
- static validateResourceAssignment(resource) {
45
- const valid = utils_assigmentSchema.resourceAssignmentSchema(resource);
52
+ static validatUpsertResourceAssignment(resource) {
53
+ const valid = utils_assignmentSchema.resourceUpsertAssignmentSchema(resource);
54
+ if (!valid) {
55
+ const errorMessage = this.formatValidationErrors(utils_assignmentSchema.resourceUpsertAssignmentSchema.errors);
56
+ throw new errors_argumentError.ArgumentError(errorMessage
57
+ ? `Invalid resource attribute assignment: ${errorMessage}`
58
+ : 'Invalid resource attribute assignment');
59
+ }
60
+ }
61
+ static validatDeleteResourceAssignment(resource) {
62
+ const valid = utils_assignmentSchema.resourceDeleteAssignmentSchema(resource);
63
+ if (!valid) {
64
+ const errorMessage = this.formatValidationErrors(utils_assignmentSchema.resourceUpsertAssignmentSchema.errors);
65
+ throw new errors_argumentError.ArgumentError(errorMessage
66
+ ? `Invalid resource attribute assignment: ${errorMessage}`
67
+ : 'Invalid resource attribute assignment');
68
+ }
69
+ }
70
+ static validatUpsertEntityAssignment(entity) {
71
+ const valid = utils_assignmentSchema.entityUpsertAssignmentSchema(entity);
46
72
  if (!valid) {
47
- const errorMessages = (utils_assigmentSchema.resourceAssignmentSchema.errors || [])
48
- .map(err => err.message || 'validation failed')
49
- .join(', ');
50
- throw new errors_argumentError.ArgumentError(`Invalid resource attribute assignment: ${errorMessages}`);
73
+ const errorMessage = this.formatValidationErrors(utils_assignmentSchema.entityUpsertAssignmentSchema.errors);
74
+ throw new errors_argumentError.ArgumentError(errorMessage ? `Invalid entity attribute assignment: ${errorMessage}` : 'Invalid entity attribute assignment');
51
75
  }
52
76
  }
53
- static validateEntityAssignment(entity) {
54
- const valid = utils_assigmentSchema.entityAssignmentSchema(entity);
77
+ static validatDeleteEntityAssignment(entity) {
78
+ const valid = utils_assignmentSchema.entityDeleteAssignmentSchema(entity);
55
79
  if (!valid) {
56
- const errorMessages = (utils_assigmentSchema.entityAssignmentSchema.errors || [])
57
- .map(err => err.message || 'validation failed')
58
- .join(', ');
59
- throw new errors_argumentError.ArgumentError(`Invalid entity attribute assignment: ${errorMessages}`);
80
+ const errorMessage = this.formatValidationErrors(utils_assignmentSchema.entityDeleteAssignmentSchema.errors);
81
+ throw new errors_argumentError.ArgumentError(errorMessage ? `Invalid entity attribute assignment: ${errorMessage}` : 'Invalid entity attribute assignment');
82
+ }
83
+ }
84
+ /**
85
+ * Formats AJV validation errors into a readable error message
86
+ */
87
+ static formatValidationErrors(errors) {
88
+ if (!errors || errors.length === 0) {
89
+ return '';
60
90
  }
91
+ return errors
92
+ .map(err => {
93
+ const path = err.instancePath || (err.params && 'missingProperty' in err.params ? `/${err.params.missingProperty}` : '');
94
+ const message = err.message || 'validation failed';
95
+ return path ? `${path}: ${message}` : message;
96
+ })
97
+ .join('; ');
61
98
  }
62
99
  }
63
100
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mondaydotcomorg/monday-authorization",
3
- "version": "3.6.0-feat-shaime-support-entity-attributes-1-f58d933",
3
+ "version": "3.6.0-feat-shaime-support-entity-attributes-3-0d092e0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "license": "BSD-3-Clause",
@@ -0,0 +1,397 @@
1
+ import { Api, HttpClient } from '@mondaydotcomorg/trident-backend-api';
2
+ import { signAuthorizationHeader } from '@mondaydotcomorg/monday-jwt';
3
+ import { ResourceAttributeAssignment } from './resource-attribute-assignment';
4
+ import { EntityAttributeAssignment } from './entity-attribute-assignment';
5
+ import {
6
+ AttributeOperation,
7
+ ResourceAttributeUpsertOperation,
8
+ ResourceAttributeDeleteOperation,
9
+ EntityAttributeUpsertOperation,
10
+ EntityAttributeDeleteOperation,
11
+ } from './types/authorization-attributes-contracts';
12
+ import { EntityType } from './entity-attributes-constants';
13
+ import { AuthorizationInternalService, logger } from './authorization-internal-service';
14
+ import { getAttributionsFromApi } from './attributions-service';
15
+ import { APP_NAME } from './constants';
16
+ import { ValidationUtils } from './utils/validation';
17
+ import { AuthorizationAttributesService } from './types/authorization-attributes-service.interface';
18
+ import { Resource } from './types/general';
19
+
20
+ const INTERNAL_APP_NAME = 'internal_ms';
21
+ const UPSERT_RESOURCE_ATTRIBUTES_PATH = '/attributes/{accountId}/resource';
22
+ const DELETE_RESOURCE_ATTRIBUTES_PATH = '/attributes/{accountId}/resource/{resourceType}/{resourceId}';
23
+ const UPSERT_ENTITY_ATTRIBUTES_PATH = '/attributes/{accountId}/entity';
24
+ const DELETE_ENTITY_ATTRIBUTES_PATH = '/attributes/{accountId}/entity/{entityType}/{entityId}';
25
+
26
+ interface DeleteRequestBody {
27
+ keys: string[];
28
+ }
29
+
30
+ /**
31
+ * Service class for managing resource attributes in the authorization microservice.
32
+ * Provides synchronous HTTP operations to create/update and delete attributes on resources.
33
+ */
34
+ export class AuthorizationAttributesMsService implements AuthorizationAttributesService {
35
+ private static LOG_TAG = 'authorization_attributes_ms';
36
+ private static httpClient: HttpClient | undefined = Api.getPart('httpClient');
37
+
38
+ constructor() {
39
+ if (!AuthorizationAttributesMsService.httpClient) {
40
+ AuthorizationAttributesMsService.httpClient = Api.getPart('httpClient');
41
+ if (!AuthorizationAttributesMsService.httpClient) {
42
+ throw new Error('HTTP client is not initialized');
43
+ }
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Deletes a specific attribute from a resource synchronously.
49
+ * @param accountId The account ID
50
+ * @param resource Object with resourceType (string) and resourceId (number)
51
+ * @param attributeKey Attribute key string to delete
52
+ * @returns Promise<ResourceAttributeDeleteOperation>
53
+ */
54
+ async deleteResourceAttributes(
55
+ accountId: number,
56
+ resource: Resource,
57
+ attributeKey: string,
58
+ _appName?: string,
59
+ _callerActionIdentifier?: string
60
+ ): Promise<ResourceAttributeDeleteOperation> {
61
+ ValidationUtils.validateResource(resource);
62
+ ValidationUtils.validatDeleteResourceAssignment({
63
+ resourceType: resource.type,
64
+ resourceId: resource.id,
65
+ key: attributeKey,
66
+ });
67
+ await AuthorizationAttributesMsService.executeDeleteRequest(
68
+ accountId,
69
+ DELETE_RESOURCE_ATTRIBUTES_PATH,
70
+ {
71
+ resourceType: resource.type,
72
+ resourceId: resource.id,
73
+ },
74
+ [attributeKey],
75
+ 'resource',
76
+ 'deleteResourceAttributesSync'
77
+ );
78
+ return {
79
+ resourceType: resource.type,
80
+ resourceId: resource.id,
81
+ key: attributeKey,
82
+ operationType: AttributeOperation.DELETE,
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Deletes a specific attribute from an entity synchronously.
88
+ * @param accountId The account ID
89
+ * @param entityType The entity type
90
+ * @param entityId The entity ID
91
+ * @param attributeKey Attribute key string to delete
92
+ * @returns Promise<EntityAttributeDeleteOperation>
93
+ */
94
+ async deleteEntityAttributes(
95
+ accountId: number,
96
+ entityType: EntityType,
97
+ entityId: number,
98
+ attributeKey: string,
99
+ _appName?: string,
100
+ _callerActionIdentifier?: string
101
+ ): Promise<EntityAttributeDeleteOperation> {
102
+ ValidationUtils.validateInteger(accountId);
103
+ await AuthorizationAttributesMsService.executeDeleteRequest(
104
+ accountId,
105
+ DELETE_ENTITY_ATTRIBUTES_PATH,
106
+ {
107
+ entityType,
108
+ entityId,
109
+ },
110
+ [attributeKey],
111
+ 'entity',
112
+ 'deleteEntityAttributesSync'
113
+ );
114
+ return {
115
+ entityType,
116
+ entityId,
117
+ key: attributeKey,
118
+ operationType: AttributeOperation.DELETE,
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Updates a resource attribute (single operation - upsert only).
124
+ * @param accountId The account ID
125
+ * @param appName App name (required for interface compatibility, but not used in MS service)
126
+ * @param callerActionIdentifier Action identifier (required for interface compatibility, but not used in MS service)
127
+ * @param resourceAttributeOperation Operation to perform (must be UPSERT)
128
+ * @returns Promise<ResourceAttributeUpsertOperation> Processed operation
129
+ */
130
+ async updateResourceAttributes(
131
+ accountId: number,
132
+ _appName: string,
133
+ _callerActionIdentifier: string,
134
+ resourceAttributeOperation: ResourceAttributeUpsertOperation
135
+ ): Promise<ResourceAttributeUpsertOperation> {
136
+ ValidationUtils.validatUpsertResourceAssignment({
137
+ resourceId: resourceAttributeOperation.resourceId,
138
+ resourceType: resourceAttributeOperation.resourceType,
139
+ key: resourceAttributeOperation.key,
140
+ value: resourceAttributeOperation.value,
141
+ });
142
+ await AuthorizationAttributesMsService.executeUpsertRequest(
143
+ accountId,
144
+ [
145
+ new ResourceAttributeAssignment(
146
+ resourceAttributeOperation.resourceId,
147
+ resourceAttributeOperation.resourceType,
148
+ resourceAttributeOperation.key,
149
+ resourceAttributeOperation.value || ''
150
+ ),
151
+ ],
152
+ UPSERT_RESOURCE_ATTRIBUTES_PATH,
153
+ 'resourceAttributeAssignments',
154
+ 'resource',
155
+ 'updateResourceAttributesSync'
156
+ );
157
+
158
+ return resourceAttributeOperation;
159
+ }
160
+
161
+ /**
162
+ * Updates an entity attribute (single operation - upsert only).
163
+ * @param accountId The account ID
164
+ * @param appName App name (required for interface compatibility, but not used in MS service)
165
+ * @param callerActionIdentifier Action identifier (required for interface compatibility, but not used in MS service)
166
+ * @param entityAttributeOperation Operation to perform (must be UPSERT)
167
+ * @returns Promise<EntityAttributeUpsertOperation> Processed operation
168
+ */
169
+ async updateEntityAttributes(
170
+ accountId: number,
171
+ _appName: string,
172
+ _callerActionIdentifier: string,
173
+ entityAttributeOperation: EntityAttributeUpsertOperation
174
+ ): Promise<EntityAttributeUpsertOperation> {
175
+ // Validate before processing
176
+ ValidationUtils.validatUpsertEntityAssignment({
177
+ entityId: entityAttributeOperation.entityId,
178
+ entityType: entityAttributeOperation.entityType,
179
+ key: entityAttributeOperation.key,
180
+ value: entityAttributeOperation.value,
181
+ });
182
+
183
+ await AuthorizationAttributesMsService.executeUpsertRequest(
184
+ accountId,
185
+ [
186
+ new EntityAttributeAssignment(
187
+ entityAttributeOperation.entityId,
188
+ entityAttributeOperation.entityType,
189
+ entityAttributeOperation.key,
190
+ entityAttributeOperation.value
191
+ ),
192
+ ],
193
+ UPSERT_ENTITY_ATTRIBUTES_PATH,
194
+ 'entityAttributeAssignments',
195
+ 'entity',
196
+ 'upsertEntityAttributesSync'
197
+ );
198
+
199
+ return entityAttributeOperation;
200
+ }
201
+
202
+ /**
203
+ * Replaces path template parameters with actual values
204
+ * @param template Path template with placeholders like {accountId}
205
+ * @param params Object with parameter names and values
206
+ * @returns Path with all placeholders replaced
207
+ */
208
+ private static replacePathParams(template: string, params: Record<string, string | number>): string {
209
+ let path = template;
210
+ for (const [key, value] of Object.entries(params)) {
211
+ path = path.replace(`{${key}}`, String(value));
212
+ }
213
+ return path;
214
+ }
215
+
216
+ /**
217
+ * Generic helper for executing delete requests
218
+ */
219
+ private static async executeDeleteRequest(
220
+ accountId: number,
221
+ pathTemplate: string,
222
+ pathParams: Record<string, string | number>,
223
+ keys: string[],
224
+ logPrefix: string,
225
+ methodName: string
226
+ ): Promise<void> {
227
+ // Validate inputs
228
+ ValidationUtils.validateInteger(accountId);
229
+ ValidationUtils.validateStringArray(keys);
230
+
231
+ if (!keys.length) {
232
+ logger.warn({ tag: this.LOG_TAG, accountId, ...pathParams }, `${methodName} called with empty keys array`);
233
+ return;
234
+ }
235
+ const requestBody: DeleteRequestBody = { keys };
236
+
237
+ if (!AuthorizationAttributesMsService.httpClient) {
238
+ throw new Error('AuthorizationAttributesMsService: HTTP client is not initialized');
239
+ }
240
+ const path = AuthorizationAttributesMsService.replacePathParams(pathTemplate, { accountId, ...pathParams });
241
+ const headers = AuthorizationAttributesMsService.getRequestHeaders(accountId);
242
+
243
+ try {
244
+ logger.info(
245
+ { tag: AuthorizationAttributesMsService.LOG_TAG, accountId, ...pathParams, keys },
246
+ `Deleting ${logPrefix} attributes`
247
+ );
248
+
249
+ await AuthorizationAttributesMsService.httpClient.fetch(
250
+ {
251
+ url: {
252
+ appName: APP_NAME,
253
+ path,
254
+ },
255
+ method: 'DELETE',
256
+ headers,
257
+ body: JSON.stringify(requestBody),
258
+ },
259
+ {
260
+ timeout: AuthorizationInternalService.getRequestTimeout(),
261
+ retryPolicy: AuthorizationInternalService.getRetriesPolicy(),
262
+ }
263
+ );
264
+
265
+ logger.debug(
266
+ { tag: AuthorizationAttributesMsService.LOG_TAG, accountId, ...pathParams, keys },
267
+ `Successfully deleted ${logPrefix} attributes`
268
+ );
269
+ } catch (err) {
270
+ logger.error(
271
+ {
272
+ tag: AuthorizationAttributesMsService.LOG_TAG,
273
+ method: methodName,
274
+ accountId,
275
+ ...pathParams,
276
+ error: err instanceof Error ? err.message : String(err),
277
+ },
278
+ `Failed in ${methodName}`
279
+ );
280
+ throw err;
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Gets request headers including Authorization, Content-Type, and optional attribution headers
286
+ */
287
+ private static getRequestHeaders(accountId: number, userId?: number): Record<string, string> {
288
+ const headers: Record<string, string> = {
289
+ 'Content-Type': 'application/json',
290
+ };
291
+
292
+ // Generate Authorization token
293
+ const authToken = signAuthorizationHeader({
294
+ appName: INTERNAL_APP_NAME,
295
+ accountId,
296
+ userId,
297
+ });
298
+ headers.Authorization = authToken;
299
+
300
+ // Add attribution headers if available
301
+ const attributionHeaders = getAttributionsFromApi();
302
+ for (const key in attributionHeaders) {
303
+ if (Object.prototype.hasOwnProperty.call(attributionHeaders, key)) {
304
+ headers[key] = attributionHeaders[key];
305
+ }
306
+ }
307
+
308
+ // Add X-REQUEST-ID if available from context
309
+ try {
310
+ const tridentContext = Api.getPart('context');
311
+ if (tridentContext?.runtimeAttributions) {
312
+ const outgoingHeaders = tridentContext.runtimeAttributions.buildOutgoingHeaders('HTTP_INTERNAL');
313
+ if (outgoingHeaders) {
314
+ const attributionHeadersMap: Record<string, string> = {};
315
+ for (const [key, value] of outgoingHeaders) {
316
+ attributionHeadersMap[key] = value;
317
+ }
318
+ if (attributionHeadersMap['x-request-id']) {
319
+ headers['X-REQUEST-ID'] = attributionHeadersMap['x-request-id'];
320
+ }
321
+ }
322
+ }
323
+ } catch (error) {
324
+ // Silently fail if context is not available
325
+ logger.debug({ tag: AuthorizationAttributesMsService.LOG_TAG, error }, 'Failed to get request ID from context');
326
+ }
327
+
328
+ // Add X-REQUEST-START timestamp
329
+ headers['X-REQUEST-START'] = Math.floor(Date.now() / 1000).toString();
330
+
331
+ return headers;
332
+ }
333
+
334
+ /**
335
+ * Generic helper for executing upsert requests
336
+ */
337
+ private static async executeUpsertRequest<T extends ResourceAttributeAssignment | EntityAttributeAssignment>(
338
+ accountId: number,
339
+ assignments: T[],
340
+ pathTemplate: string,
341
+ requestBodyKey: 'resourceAttributeAssignments' | 'entityAttributeAssignments',
342
+ logPrefix: string,
343
+ methodName: string
344
+ ): Promise<void> {
345
+ const assignmentDto = assignments.map(assignment => assignment.toDataTransferObject());
346
+
347
+ const requestBody =
348
+ requestBodyKey === 'resourceAttributeAssignments'
349
+ ? { resourceAttributeAssignments: assignmentDto }
350
+ : { entityAttributeAssignments: assignmentDto };
351
+
352
+ if (!AuthorizationAttributesMsService.httpClient) {
353
+ throw new Error('AuthorizationAttributesMsService: HTTP client is not initialized');
354
+ }
355
+ const path = AuthorizationAttributesMsService.replacePathParams(pathTemplate, { accountId });
356
+ const headers = AuthorizationAttributesMsService.getRequestHeaders(accountId);
357
+
358
+ try {
359
+ logger.info(
360
+ { tag: AuthorizationAttributesMsService.LOG_TAG, accountId, count: assignments.length },
361
+ `Upserting ${logPrefix} attributes`
362
+ );
363
+
364
+ await AuthorizationAttributesMsService.httpClient.fetch(
365
+ {
366
+ url: {
367
+ appName: APP_NAME,
368
+ path,
369
+ },
370
+ method: 'POST',
371
+ headers,
372
+ body: JSON.stringify(requestBody),
373
+ },
374
+ {
375
+ timeout: AuthorizationInternalService.getRequestTimeout(),
376
+ retryPolicy: AuthorizationInternalService.getRetriesPolicy(),
377
+ }
378
+ );
379
+
380
+ logger.debug(
381
+ { tag: AuthorizationAttributesMsService.LOG_TAG, accountId, count: assignments.length },
382
+ `Successfully upserted ${logPrefix} attributes`
383
+ );
384
+ } catch (err) {
385
+ logger.error(
386
+ {
387
+ tag: AuthorizationAttributesMsService.LOG_TAG,
388
+ method: methodName,
389
+ accountId,
390
+ error: err instanceof Error ? err.message : String(err),
391
+ },
392
+ `Failed in ${methodName}`
393
+ );
394
+ throw err;
395
+ }
396
+ }
397
+ }