@mondaydotcomorg/monday-authorization 3.5.0-feat-shaime-support-entity-attributes-in-authorization-sdk-c9e4cfc → 3.5.0-feat-shaime-support-entity-attributes-in-authorization-sdk-8d846f1

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 (38) hide show
  1. package/dist/authorization-attributes-ms-service.d.ts +17 -0
  2. package/dist/authorization-attributes-ms-service.d.ts.map +1 -1
  3. package/dist/authorization-attributes-ms-service.js +166 -1
  4. package/dist/authorization-attributes-service.d.ts +52 -66
  5. package/dist/authorization-attributes-service.d.ts.map +1 -1
  6. package/dist/authorization-attributes-service.js +105 -353
  7. package/dist/constants/sns.d.ts +2 -0
  8. package/dist/constants/sns.d.ts.map +1 -1
  9. package/dist/constants/sns.js +4 -0
  10. package/dist/esm/authorization-attributes-ms-service.d.ts +17 -0
  11. package/dist/esm/authorization-attributes-ms-service.d.ts.map +1 -1
  12. package/dist/esm/authorization-attributes-ms-service.mjs +166 -1
  13. package/dist/esm/authorization-attributes-service.d.ts +52 -66
  14. package/dist/esm/authorization-attributes-service.d.ts.map +1 -1
  15. package/dist/esm/authorization-attributes-service.mjs +107 -355
  16. package/dist/esm/constants/sns.d.ts +2 -0
  17. package/dist/esm/constants/sns.d.ts.map +1 -1
  18. package/dist/esm/constants/sns.mjs +3 -1
  19. package/dist/esm/resource-attribute-assignment.d.ts.map +1 -1
  20. package/dist/esm/resource-attributes-constants.d.ts +1 -1
  21. package/dist/esm/resource-attributes-constants.d.ts.map +1 -1
  22. package/dist/esm/types/authorization-attributes-contracts.d.ts +16 -0
  23. package/dist/esm/types/authorization-attributes-contracts.d.ts.map +1 -1
  24. package/dist/esm/types/authorization-attributes-contracts.mjs +6 -1
  25. package/dist/resource-attribute-assignment.d.ts.map +1 -1
  26. package/dist/resource-attributes-constants.d.ts +1 -1
  27. package/dist/resource-attributes-constants.d.ts.map +1 -1
  28. package/dist/types/authorization-attributes-contracts.d.ts +16 -0
  29. package/dist/types/authorization-attributes-contracts.d.ts.map +1 -1
  30. package/dist/types/authorization-attributes-contracts.js +5 -0
  31. package/package.json +1 -1
  32. package/src/authorization-attributes-ms-service.ts +258 -17
  33. package/src/authorization-attributes-service.ts +146 -428
  34. package/src/constants/sns.ts +2 -0
  35. package/src/errors/argument-error.ts +1 -2
  36. package/src/resource-attribute-assignment.ts +1 -4
  37. package/src/resource-attributes-constants.ts +1 -2
  38. package/src/types/authorization-attributes-contracts.ts +22 -5
@@ -1,151 +1,132 @@
1
1
  import chunk from 'lodash/chunk.js';
2
- import { Api, FetcherConfig, HttpClient } from '@mondaydotcomorg/trident-backend-api';
3
2
  import { getTopicAttributes, sendToSns } from '@mondaydotcomorg/monday-sns';
4
- import { HttpFetcherError, RecursivePartial } from '@mondaydotcomorg/monday-fetch-api';
5
3
  import {
6
4
  ResourceAttributeAssignment,
7
- ResourceAttributeResponse,
8
5
  ResourceAttributesOperation,
9
6
  EntityAttributeAssignment,
10
- EntityAttributeResponse,
11
- ResourceType,
12
- EntityType,
13
- ResourceAttributeKeyType,
14
- EntityAttributeKeyType,
7
+ EntityAttributesOperation,
8
+ ResourceAttributeOperationEnum,
9
+ EntityAttributeOperationEnum,
15
10
  } from './types/authorization-attributes-contracts';
16
- import { AuthorizationInternalService } from './authorization-internal-service';
17
11
  import { Resource } from './types/general';
18
12
  import { logger } from './authorization-internal-service';
19
- import { getAttributionsFromApi } from './attributions-service';
20
13
  import {
21
14
  ASYNC_RESOURCE_ATTRIBUTES_MAX_OPERATIONS_PER_MESSAGE,
15
+ ASYNC_ENTITY_ATTRIBUTES_MAX_OPERATIONS_PER_MESSAGE,
22
16
  RESOURCE_ATTRIBUTES_SNS_UPDATE_OPERATION_MESSAGE_KIND,
17
+ ENTITY_ATTRIBUTES_SNS_UPDATE_OPERATION_MESSAGE_KIND,
23
18
  SNS_ARN_ENV_VAR_NAME,
24
19
  SNS_DEV_TEST_NAME,
25
20
  } from './constants/sns';
26
- import { APP_NAME, DEFAULT_FETCH_OPTIONS, ERROR_MESSAGES } from './constants';
27
21
  import type { TopicAttributesMap } from 'aws-sdk/clients/sns';
28
22
 
29
23
  export class AuthorizationAttributesService {
30
24
  private static LOG_TAG = 'authorization_attributes';
31
- private static API_PATHS = {
32
- UPSERT_RESOURCE_ATTRIBUTES: '/attributes/{accountId}/resource',
33
- DELETE_RESOURCE_ATTRIBUTES: '/attributes/{accountId}/resource/{resourceType}/{resourceId}',
34
- UPSERT_ENTITY_ATTRIBUTES: '/attributes/{accountId}/entity',
35
- DELETE_ENTITY_ATTRIBUTES: '/attributes/{accountId}/entity/{entityType}/{entityId}',
36
- } as const;
37
- private httpClient: HttpClient;
38
- private fetchOptions: RecursivePartial<FetcherConfig>;
39
25
  private snsArn: string;
40
26
 
41
27
  /**
42
28
  * Public constructor to create the AuthorizationAttributesService instance.
43
- * @param httpClient The HTTP client to use for API requests, if not provided, the default HTTP client from Api will be used.
44
- * @param fetchOptions The fetch options to use for API requests, if not provided, the default fetch options will be used.
45
29
  */
46
- constructor(httpClient?: HttpClient, fetchOptions?: RecursivePartial<FetcherConfig>) {
47
- if (!httpClient) {
48
- httpClient = Api.getPart('httpClient');
49
- if (!httpClient) {
50
- throw new Error(ERROR_MESSAGES.HTTP_CLIENT_NOT_INITIALIZED);
51
- }
52
- }
53
-
54
- if (!fetchOptions) {
55
- fetchOptions = DEFAULT_FETCH_OPTIONS;
56
- } else {
57
- fetchOptions = {
58
- ...DEFAULT_FETCH_OPTIONS,
59
- ...fetchOptions,
60
- };
61
- }
62
- this.httpClient = httpClient;
63
- this.fetchOptions = fetchOptions;
30
+ constructor() {
64
31
  this.snsArn = AuthorizationAttributesService.getSnsTopicArn();
65
32
  }
66
33
 
67
34
  /**
68
- * Upsert resource attributes synchronously, performing http call to the authorization MS to assign the given attributes to the given resource.
69
- * @param accountId
70
- * @param resourceAttributeAssignments - Array of resource (resourceType, resourceId) and attribute (key, value) pairs to upsert in the authorization MS.
71
- * e.g. [{ resourceType: 'board', resourceId: 123, key: 'board_kind', value: 'private' }]
72
- * @returns ResourceAttributeResponse - The affected (created and updated_ resource attributes assignments in the `attributes` field.
35
+ * Async function to upsert resource attributes using SNS.
36
+ * Sends the updates request to SNS and returns before the change actually took place.
37
+ * @param accountId The account ID
38
+ * @param appName App name of the calling app
39
+ * @param callerActionIdentifier Action identifier
40
+ * @param resourceAttributeAssignments Array of resource attribute assignments to upsert
41
+ * @return Promise with array of sent operations
73
42
  */
74
- async upsertResourceAttributes(
43
+ async upsertResourceAttributesAsync(
75
44
  accountId: number,
45
+ appName: string,
46
+ callerActionIdentifier: string,
76
47
  resourceAttributeAssignments: ResourceAttributeAssignment[]
77
- ): Promise<ResourceAttributeResponse> {
78
- const attributionHeaders = getAttributionsFromApi();
79
- try {
80
- return await this.httpClient.fetch<ResourceAttributeResponse>(
81
- {
82
- url: {
83
- appName: APP_NAME,
84
- path: AuthorizationAttributesService.API_PATHS.UPSERT_RESOURCE_ATTRIBUTES.replace(
85
- '{accountId}',
86
- accountId.toString()
87
- ),
88
- },
89
- method: 'POST',
90
- headers: {
91
- 'Content-Type': 'application/json',
92
- ...attributionHeaders,
93
- },
94
- body: JSON.stringify({ resourceAttributeAssignments }),
95
- },
96
- this.fetchOptions
97
- );
98
- } catch (err) {
99
- if (err instanceof HttpFetcherError) {
100
- throw new Error(ERROR_MESSAGES.REQUEST_FAILED('upsertResourceAttributes', err.status, err.message));
101
- }
102
- throw err;
103
- }
48
+ ): Promise<ResourceAttributesOperation[]> {
49
+ const operations: ResourceAttributesOperation[] = resourceAttributeAssignments.map(assignment => ({
50
+ ...assignment,
51
+ operationType: ResourceAttributeOperationEnum.UPSERT,
52
+ }));
53
+ return this.updateResourceAttributesAsync(accountId, appName, callerActionIdentifier, operations);
104
54
  }
105
55
 
106
56
  /**
107
- * Delete resource attributes assignments synchronously, performing http call to the authorization MS to delete the given attributes from the given singular resource.
108
- * @param accountId
109
- * @param resource - The resource (resourceType, resourceId) to delete the attributes for.
110
- * @param attributeKeys - Array of attribute keys to delete for the resource.
111
- * @returns ResourceAttributeResponse - The affected (deleted) resource attributes assignments in the `attributes` field.
57
+ * Async function to delete resource attributes using SNS.
58
+ * Sends the updates request to SNS and returns before the change actually took place.
59
+ * @param accountId The account ID
60
+ * @param appName App name of the calling app
61
+ * @param callerActionIdentifier Action identifier
62
+ * @param resource The resource (resourceType, resourceId)
63
+ * @param attributeKeys Array of attribute keys to delete
64
+ * @return Promise with array of sent operations
112
65
  */
113
- async deleteResourceAttributes(
66
+ async deleteResourceAttributesAsync(
114
67
  accountId: number,
68
+ appName: string,
69
+ callerActionIdentifier: string,
115
70
  resource: Resource,
116
71
  attributeKeys: string[]
117
- ): Promise<ResourceAttributeResponse> {
118
- const attributionHeaders = getAttributionsFromApi();
119
- if (!resource.id) {
120
- throw new Error('Resource ID is required');
121
- }
122
- try {
123
- return await this.httpClient.fetch<ResourceAttributeResponse>(
124
- {
125
- url: {
126
- appName: APP_NAME,
127
- path: AuthorizationAttributesService.API_PATHS.DELETE_RESOURCE_ATTRIBUTES.replace(
128
- '{accountId}',
129
- accountId.toString()
130
- )
131
- .replace('{resourceType}', resource.type)
132
- .replace('{resourceId}', resource.id.toString()),
133
- },
134
- method: 'DELETE',
135
- headers: {
136
- 'Content-Type': 'application/json',
137
- ...attributionHeaders,
138
- },
139
- body: JSON.stringify({ keys: attributeKeys }),
140
- },
141
- this.fetchOptions
142
- );
143
- } catch (err) {
144
- if (err instanceof HttpFetcherError) {
145
- throw new Error(ERROR_MESSAGES.REQUEST_FAILED('deleteResourceAttributes', err.status, err.message));
146
- }
147
- throw err;
148
- }
72
+ ): Promise<ResourceAttributesOperation[]> {
73
+ const operations: ResourceAttributesOperation[] = attributeKeys.map(key => ({
74
+ resourceType: resource.type,
75
+ resourceId: resource.id!,
76
+ key,
77
+ operationType: ResourceAttributeOperationEnum.DELETE,
78
+ }));
79
+ return this.updateResourceAttributesAsync(accountId, appName, callerActionIdentifier, operations);
80
+ }
81
+
82
+ /**
83
+ * Async function to upsert entity attributes using SNS.
84
+ * Sends the updates request to SNS and returns before the change actually took place.
85
+ * @param accountId The account ID
86
+ * @param appName App name of the calling app
87
+ * @param callerActionIdentifier Action identifier
88
+ * @param entityAttributeAssignments Array of entity attribute assignments to upsert
89
+ * @return Promise with array of sent operations
90
+ */
91
+ async upsertEntityAttributesAsync(
92
+ accountId: number,
93
+ appName: string,
94
+ callerActionIdentifier: string,
95
+ entityAttributeAssignments: EntityAttributeAssignment[]
96
+ ): Promise<EntityAttributesOperation[]> {
97
+ const operations: EntityAttributesOperation[] = entityAttributeAssignments.map(assignment => ({
98
+ ...assignment,
99
+ operationType: EntityAttributeOperationEnum.UPSERT,
100
+ }));
101
+ return this.updateEntityAttributesAsync(accountId, appName, callerActionIdentifier, operations);
102
+ }
103
+
104
+ /**
105
+ * Async function to delete entity attributes using SNS.
106
+ * Sends the updates request to SNS and returns before the change actually took place.
107
+ * @param accountId The account ID
108
+ * @param appName App name of the calling app
109
+ * @param callerActionIdentifier Action identifier
110
+ * @param entityType The entity type
111
+ * @param entityId The entity ID
112
+ * @param attributeKeys Array of attribute keys to delete
113
+ * @return Promise with array of sent operations
114
+ */
115
+ async deleteEntityAttributesAsync(
116
+ accountId: number,
117
+ appName: string,
118
+ callerActionIdentifier: string,
119
+ entityType: string,
120
+ entityId: number,
121
+ attributeKeys: string[]
122
+ ): Promise<EntityAttributesOperation[]> {
123
+ const operations: EntityAttributesOperation[] = attributeKeys.map(key => ({
124
+ entityType: entityType as any,
125
+ entityId,
126
+ key,
127
+ operationType: EntityAttributeOperationEnum.DELETE,
128
+ }));
129
+ return this.updateEntityAttributesAsync(accountId, appName, callerActionIdentifier, operations);
149
130
  }
150
131
 
151
132
  /**
@@ -167,13 +148,38 @@ export class AuthorizationAttributesService {
167
148
  const operationChucks = chunk(resourceAttributeOperations, ASYNC_RESOURCE_ATTRIBUTES_MAX_OPERATIONS_PER_MESSAGE);
168
149
  for (const operationsChunk of operationChucks) {
169
150
  sendToSnsPromises.push(
170
- this.sendSingleSnsMessage(topicArn, accountId, appName, callerActionIdentifier, operationsChunk)
151
+ this.sendSingleResourceSnsMessage(topicArn, accountId, appName, callerActionIdentifier, operationsChunk)
171
152
  );
172
153
  }
173
154
  return (await Promise.all(sendToSnsPromises)).flat();
174
155
  }
175
156
 
176
- private async sendSingleSnsMessage(
157
+ /**
158
+ * Async function, this function only send the updates request to SNS and return before the change actually took place
159
+ * @param accountId
160
+ * @param appName - App name of the calling app
161
+ * @param callerActionIdentifier - action identifier
162
+ * @param entityAttributeOperations - Array of operations to do on entity attributes.
163
+ * @return {Promise<EntityAttributesOperation[]>} Array of sent operations
164
+ * */
165
+ async updateEntityAttributesAsync(
166
+ accountId: number,
167
+ appName: string,
168
+ callerActionIdentifier: string,
169
+ entityAttributeOperations: EntityAttributesOperation[]
170
+ ): Promise<EntityAttributesOperation[]> {
171
+ const topicArn: string = this.snsArn;
172
+ const sendToSnsPromises: Promise<EntityAttributesOperation[]>[] = [];
173
+ const operationChucks = chunk(entityAttributeOperations, ASYNC_ENTITY_ATTRIBUTES_MAX_OPERATIONS_PER_MESSAGE);
174
+ for (const operationsChunk of operationChucks) {
175
+ sendToSnsPromises.push(
176
+ this.sendSingleEntitySnsMessage(topicArn, accountId, appName, callerActionIdentifier, operationsChunk)
177
+ );
178
+ }
179
+ return (await Promise.all(sendToSnsPromises)).flat();
180
+ }
181
+
182
+ private async sendSingleResourceSnsMessage(
177
183
  topicArn: string,
178
184
  accountId: number,
179
185
  appName: string,
@@ -201,6 +207,34 @@ export class AuthorizationAttributesService {
201
207
  }
202
208
  }
203
209
 
210
+ private async sendSingleEntitySnsMessage(
211
+ topicArn: string,
212
+ accountId: number,
213
+ appName: string,
214
+ callerActionIdentifier: string,
215
+ operations: EntityAttributesOperation[]
216
+ ): Promise<EntityAttributesOperation[]> {
217
+ const payload = {
218
+ kind: ENTITY_ATTRIBUTES_SNS_UPDATE_OPERATION_MESSAGE_KIND,
219
+ payload: {
220
+ accountId: accountId,
221
+ callerAppName: appName,
222
+ callerActionIdentifier: callerActionIdentifier,
223
+ operations: operations,
224
+ },
225
+ };
226
+ try {
227
+ await sendToSns(payload, topicArn);
228
+ return operations;
229
+ } catch (error) {
230
+ logger.error(
231
+ { error, tag: AuthorizationAttributesService.LOG_TAG },
232
+ 'Authorization entity attributes async update: failed to send operations to SNS'
233
+ );
234
+ return [];
235
+ }
236
+ }
237
+
204
238
  private static getSnsTopicArn(): string {
205
239
  const arnFromEnv: string | undefined = process.env[SNS_ARN_ENV_VAR_NAME];
206
240
  if (arnFromEnv) {
@@ -240,320 +274,4 @@ export class AuthorizationAttributesService {
240
274
  return false;
241
275
  }
242
276
  }
243
-
244
- /**
245
- * Validates resource attribute assignments array
246
- */
247
- private static validateResourceAttributeAssignments(
248
- assignments: ResourceAttributeAssignment[]
249
- ): void {
250
- if (!Array.isArray(assignments)) {
251
- throw new Error('resourceAttributeAssignments must be an array');
252
- }
253
- if (assignments.length === 0) {
254
- throw new Error('resourceAttributeAssignments must contain at least 1 item');
255
- }
256
- if (assignments.length > 100) {
257
- throw new Error('resourceAttributeAssignments must contain at most 100 items');
258
- }
259
- for (let i = 0; i < assignments.length; i++) {
260
- const assignment = assignments[i];
261
- if (!assignment.resourceId || typeof assignment.resourceId !== 'number') {
262
- throw new Error(`resourceAttributeAssignments[${i}].resourceId is required and must be a number`);
263
- }
264
- if (!assignment.resourceType || typeof assignment.resourceType !== 'string') {
265
- throw new Error(`resourceAttributeAssignments[${i}].resourceType is required and must be a string`);
266
- }
267
- if (!assignment.key || typeof assignment.key !== 'string') {
268
- throw new Error(`resourceAttributeAssignments[${i}].key is required and must be a string`);
269
- }
270
- if (assignment.value === undefined || typeof assignment.value !== 'string') {
271
- throw new Error(`resourceAttributeAssignments[${i}].value is required and must be a string`);
272
- }
273
- }
274
- }
275
-
276
- /**
277
- * Validates entity attribute assignments array
278
- */
279
- private static validateEntityAttributeAssignments(assignments: EntityAttributeAssignment[]): void {
280
- if (!Array.isArray(assignments)) {
281
- throw new Error('entityAttributeAssignments must be an array');
282
- }
283
- if (assignments.length === 0) {
284
- throw new Error('entityAttributeAssignments must contain at least 1 item');
285
- }
286
- if (assignments.length > 100) {
287
- throw new Error('entityAttributeAssignments must contain at most 100 items');
288
- }
289
- for (let i = 0; i < assignments.length; i++) {
290
- const assignment = assignments[i];
291
- if (!assignment.entityId || typeof assignment.entityId !== 'number') {
292
- throw new Error(`entityAttributeAssignments[${i}].entityId is required and must be a number`);
293
- }
294
- if (!assignment.entityType || typeof assignment.entityType !== 'string') {
295
- throw new Error(`entityAttributeAssignments[${i}].entityType is required and must be a string`);
296
- }
297
- if (!assignment.key || typeof assignment.key !== 'string') {
298
- throw new Error(`entityAttributeAssignments[${i}].key is required and must be a string`);
299
- }
300
- if (assignment.value === undefined || typeof assignment.value !== 'string') {
301
- throw new Error(`entityAttributeAssignments[${i}].value is required and must be a string`);
302
- }
303
- }
304
- }
305
-
306
- /**
307
- * Upsert resource attributes synchronously.
308
- * Matches API endpoint: POST /attributes/:accountId/resource
309
- * @param accountId The account ID
310
- * @param resourceAttributeAssignments Array of ResourceAttributeAssignment objects (1-100 items)
311
- * @returns Promise with response containing affected attributes
312
- */
313
- static async upsertResourceAttributesSync(
314
- accountId: number,
315
- resourceAttributeAssignments: ResourceAttributeAssignment[]
316
- ): Promise<ResourceAttributeResponse> {
317
- // Validate inputs
318
- if (!Number.isInteger(accountId)) {
319
- throw new Error(`accountId must be an integer, got: ${accountId}`);
320
- }
321
- AuthorizationAttributesService.validateResourceAttributeAssignments(resourceAttributeAssignments);
322
-
323
- const httpClient = Api.getPart('httpClient');
324
- if (!httpClient) {
325
- throw new Error(ERROR_MESSAGES.HTTP_CLIENT_NOT_INITIALIZED);
326
- }
327
-
328
- const attributionHeaders = getAttributionsFromApi();
329
- const path = AuthorizationAttributesService.API_PATHS.UPSERT_RESOURCE_ATTRIBUTES.replace(
330
- '{accountId}',
331
- accountId.toString()
332
- );
333
-
334
- try {
335
- return await httpClient.fetch<ResourceAttributeResponse>(
336
- {
337
- url: {
338
- appName: APP_NAME,
339
- path,
340
- },
341
- method: 'POST',
342
- headers: {
343
- 'Content-Type': 'application/json',
344
- ...attributionHeaders,
345
- },
346
- body: JSON.stringify({ resourceAttributeAssignments }),
347
- },
348
- {
349
- timeout: AuthorizationInternalService.getRequestTimeout(),
350
- retryPolicy: AuthorizationInternalService.getRetriesPolicy(),
351
- }
352
- );
353
- } catch (err) {
354
- if (err instanceof HttpFetcherError) {
355
- throw new Error(ERROR_MESSAGES.REQUEST_FAILED('upsertResourceAttributesSync', err.status, err.message));
356
- }
357
- throw err;
358
- }
359
- }
360
-
361
- /**
362
- * Delete resource attributes synchronously.
363
- * Matches API endpoint: DELETE /attributes/:accountId/resource/:resourceType/:resourceId
364
- * @param accountId The account ID
365
- * @param resourceType The resource type
366
- * @param resourceId The resource ID
367
- * @param keys Array of attribute keys to delete
368
- * @returns Promise with response containing affected attributes
369
- */
370
- static async deleteResourceAttributesSync(
371
- accountId: number,
372
- resourceType: ResourceType,
373
- resourceId: number,
374
- keys: ResourceAttributeKeyType[]
375
- ): Promise<ResourceAttributeResponse> {
376
- // Validate inputs
377
- if (!Number.isInteger(accountId)) {
378
- throw new Error(`accountId must be an integer, got: ${accountId}`);
379
- }
380
- if (!resourceType || typeof resourceType !== 'string') {
381
- throw new Error(`resourceType must be a string, got: ${typeof resourceType}`);
382
- }
383
- if (!Number.isInteger(resourceId)) {
384
- throw new Error(`resourceId must be an integer, got: ${resourceId}`);
385
- }
386
- if (!Array.isArray(keys)) {
387
- throw new Error('keys must be an array');
388
- }
389
- if (keys.length === 0) {
390
- throw new Error('keys must contain at least 1 item');
391
- }
392
-
393
- const httpClient = Api.getPart('httpClient');
394
- if (!httpClient) {
395
- throw new Error(ERROR_MESSAGES.HTTP_CLIENT_NOT_INITIALIZED);
396
- }
397
-
398
- const attributionHeaders = getAttributionsFromApi();
399
- const path = AuthorizationAttributesService.API_PATHS.DELETE_RESOURCE_ATTRIBUTES.replace(
400
- '{accountId}',
401
- accountId.toString()
402
- )
403
- .replace('{resourceType}', resourceType)
404
- .replace('{resourceId}', resourceId.toString());
405
-
406
- try {
407
- return await httpClient.fetch<ResourceAttributeResponse>(
408
- {
409
- url: {
410
- appName: APP_NAME,
411
- path,
412
- },
413
- method: 'DELETE',
414
- headers: {
415
- 'Content-Type': 'application/json',
416
- ...attributionHeaders,
417
- },
418
- body: JSON.stringify({ keys }),
419
- },
420
- {
421
- timeout: AuthorizationInternalService.getRequestTimeout(),
422
- retryPolicy: AuthorizationInternalService.getRetriesPolicy(),
423
- }
424
- );
425
- } catch (err) {
426
- if (err instanceof HttpFetcherError) {
427
- throw new Error(ERROR_MESSAGES.REQUEST_FAILED('deleteResourceAttributesSync', err.status, err.message));
428
- }
429
- throw err;
430
- }
431
- }
432
-
433
- /**
434
- * Upsert entity attributes synchronously.
435
- * Matches API endpoint: POST /attributes/:accountId/entity
436
- * @param accountId The account ID
437
- * @param entityAttributeAssignments Array of EntityAttributeAssignment objects (1-100 items)
438
- * @returns Promise with response containing affected attributes
439
- */
440
- static async upsertEntityAttributesSync(
441
- accountId: number,
442
- entityAttributeAssignments: EntityAttributeAssignment[]
443
- ): Promise<EntityAttributeResponse> {
444
- // Validate inputs
445
- if (!Number.isInteger(accountId)) {
446
- throw new Error(`accountId must be an integer, got: ${accountId}`);
447
- }
448
- AuthorizationAttributesService.validateEntityAttributeAssignments(entityAttributeAssignments);
449
-
450
- const httpClient = Api.getPart('httpClient');
451
- if (!httpClient) {
452
- throw new Error(ERROR_MESSAGES.HTTP_CLIENT_NOT_INITIALIZED);
453
- }
454
-
455
- const attributionHeaders = getAttributionsFromApi();
456
- const path = AuthorizationAttributesService.API_PATHS.UPSERT_ENTITY_ATTRIBUTES.replace(
457
- '{accountId}',
458
- accountId.toString()
459
- );
460
-
461
- try {
462
- return await httpClient.fetch<EntityAttributeResponse>(
463
- {
464
- url: {
465
- appName: APP_NAME,
466
- path,
467
- },
468
- method: 'POST',
469
- headers: {
470
- 'Content-Type': 'application/json',
471
- ...attributionHeaders,
472
- },
473
- body: JSON.stringify({ entityAttributeAssignments }),
474
- },
475
- {
476
- timeout: AuthorizationInternalService.getRequestTimeout(),
477
- retryPolicy: AuthorizationInternalService.getRetriesPolicy(),
478
- }
479
- );
480
- } catch (err) {
481
- if (err instanceof HttpFetcherError) {
482
- throw new Error(ERROR_MESSAGES.REQUEST_FAILED('upsertEntityAttributesSync', err.status, err.message));
483
- }
484
- throw err;
485
- }
486
- }
487
-
488
- /**
489
- * Delete entity attributes synchronously.
490
- * Matches API endpoint: DELETE /attributes/:accountId/entity/:entityType/:entityId
491
- * @param accountId The account ID
492
- * @param entityType The entity type
493
- * @param entityId The entity ID
494
- * @param keys Array of attribute keys to delete
495
- * @returns Promise with response containing affected attributes
496
- */
497
- static async deleteEntityAttributesSync(
498
- accountId: number,
499
- entityType: EntityType,
500
- entityId: number,
501
- keys: EntityAttributeKeyType[]
502
- ): Promise<EntityAttributeResponse> {
503
- // Validate inputs
504
- if (!Number.isInteger(accountId)) {
505
- throw new Error(`accountId must be an integer, got: ${accountId}`);
506
- }
507
- if (!entityType || typeof entityType !== 'string') {
508
- throw new Error(`entityType must be a string, got: ${typeof entityType}`);
509
- }
510
- if (!Number.isInteger(entityId)) {
511
- throw new Error(`entityId must be an integer, got: ${entityId}`);
512
- }
513
- if (!Array.isArray(keys)) {
514
- throw new Error('keys must be an array');
515
- }
516
- if (keys.length === 0) {
517
- throw new Error('keys must contain at least 1 item');
518
- }
519
-
520
- const httpClient = Api.getPart('httpClient');
521
- if (!httpClient) {
522
- throw new Error(ERROR_MESSAGES.HTTP_CLIENT_NOT_INITIALIZED);
523
- }
524
-
525
- const attributionHeaders = getAttributionsFromApi();
526
- const path = AuthorizationAttributesService.API_PATHS.DELETE_ENTITY_ATTRIBUTES.replace(
527
- '{accountId}',
528
- accountId.toString()
529
- )
530
- .replace('{entityType}', entityType)
531
- .replace('{entityId}', entityId.toString());
532
-
533
- try {
534
- return await httpClient.fetch<EntityAttributeResponse>(
535
- {
536
- url: {
537
- appName: APP_NAME,
538
- path,
539
- },
540
- method: 'DELETE',
541
- headers: {
542
- 'Content-Type': 'application/json',
543
- ...attributionHeaders,
544
- },
545
- body: JSON.stringify({ keys }),
546
- },
547
- {
548
- timeout: AuthorizationInternalService.getRequestTimeout(),
549
- retryPolicy: AuthorizationInternalService.getRetriesPolicy(),
550
- }
551
- );
552
- } catch (err) {
553
- if (err instanceof HttpFetcherError) {
554
- throw new Error(ERROR_MESSAGES.REQUEST_FAILED('deleteEntityAttributesSync', err.status, err.message));
555
- }
556
- throw err;
557
- }
558
- }
559
277
  }
@@ -2,4 +2,6 @@ export const SNS_ARN_ENV_VAR_NAME = 'SHARED_AUTHORIZATION_SNS_ENDPOINT_RESOURCE_
2
2
  export const SNS_DEV_TEST_NAME =
3
3
  'arn:aws:sns:us-east-1:000000000000:monday-authorization-resource-attributes-sns-local';
4
4
  export const RESOURCE_ATTRIBUTES_SNS_UPDATE_OPERATION_MESSAGE_KIND = 'resourceAttributeModification';
5
+ export const ENTITY_ATTRIBUTES_SNS_UPDATE_OPERATION_MESSAGE_KIND = 'entityAttributeModification';
5
6
  export const ASYNC_RESOURCE_ATTRIBUTES_MAX_OPERATIONS_PER_MESSAGE = 100;
7
+ export const ASYNC_ENTITY_ATTRIBUTES_MAX_OPERATIONS_PER_MESSAGE = 100;
@@ -1,8 +1,7 @@
1
1
  export class ArgumentError extends Error {
2
2
  constructor(message: string) {
3
3
  super(message);
4
- this.name = 'ArgumentError';
4
+ this.name = 'ArgumentError';
5
5
  Object.setPrototypeOf(this, ArgumentError.prototype);
6
6
  }
7
7
  }
8
-
@@ -16,9 +16,7 @@ export class ResourceAttributeAssignment {
16
16
  // Validate resourceType
17
17
  const validResourceTypes = Object.values(RESOURCE_TYPES);
18
18
  if (!validResourceTypes.includes(resourceType as ResourceType)) {
19
- throw new ArgumentError(
20
- `resourceType must be one of [${validResourceTypes.join(', ')}], got: ${resourceType}`
21
- );
19
+ throw new ArgumentError(`resourceType must be one of [${validResourceTypes.join(', ')}], got: ${resourceType}`);
22
20
  }
23
21
 
24
22
  // Validate attributeKey
@@ -67,4 +65,3 @@ export class ResourceAttributeAssignment {
67
65
  );
68
66
  }
69
67
  }
70
-
@@ -23,5 +23,4 @@ export const RESOURCE_ATTRIBUTES_CONSTANTS = {
23
23
  },
24
24
  } as const;
25
25
 
26
- export type ResourceType = typeof RESOURCE_TYPES[keyof typeof RESOURCE_TYPES];
27
-
26
+ export type ResourceType = (typeof RESOURCE_TYPES)[keyof typeof RESOURCE_TYPES];