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