@monorise/core 1.0.2 → 1.0.4-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.
- package/dist/mock/monorise/chapter.d.ts +1 -1
- package/dist/mock/monorise/course.d.ts +1 -1
- package/dist/mock/monorise/learning-journey-config.d.ts +1 -1
- package/dist/mock/monorise/module.d.ts +1 -1
- package/dist/mock/monorise/organization.d.ts +1 -1
- package/dist/services/entity.service.d.ts +2 -2
- package/dist/services/entity.service.d.ts.map +1 -1
- package/package.json +4 -1
- package/configs/service.config.ts +0 -14
- package/constants/table.ts +0 -3
- package/controllers/entity/create-entity.controller.ts +0 -51
- package/controllers/entity/delete-entity.controller.ts +0 -35
- package/controllers/entity/entity.http +0 -62
- package/controllers/entity/get-entity.controller.ts +0 -33
- package/controllers/entity/list-entities.controller.ts +0 -69
- package/controllers/entity/update-entity.controller.ts +0 -56
- package/controllers/entity/upsert-entity.controller.ts +0 -97
- package/controllers/mutual/create-mutual.controller.ts +0 -89
- package/controllers/mutual/delete-mutual.controller.ts +0 -40
- package/controllers/mutual/get-mutual.controller.ts +0 -38
- package/controllers/mutual/list-entities-by-entity.controller.ts +0 -76
- package/controllers/mutual/mutual.http +0 -88
- package/controllers/mutual/update-mutual.controller.ts +0 -50
- package/controllers/setupRoutes.ts +0 -73
- package/controllers/tag/list-tags.controller.ts +0 -57
- package/data/DbUtils.ts +0 -40
- package/data/Entity.ts +0 -499
- package/data/EventUtils.ts +0 -47
- package/data/FileObject.ts +0 -16
- package/data/Mutual.ts +0 -779
- package/data/ProjectionExpression.ts +0 -8
- package/data/Tag.ts +0 -470
- package/data/abstract/Item.base.ts +0 -19
- package/data/abstract/Repository.base.ts +0 -92
- package/errors/api-error.ts +0 -39
- package/errors/extendable-error.ts +0 -35
- package/errors/standard-error.ts +0 -29
- package/helpers/dependencies.ts +0 -10
- package/helpers/event.ts +0 -85
- package/helpers/fromLastKeyQuery.ts +0 -11
- package/helpers/sleep.ts +0 -1
- package/helpers/toLastKeyResponse.ts +0 -11
- package/index.ts +0 -23
- package/middlewares/entity-type-check.ts +0 -20
- package/middlewares/mutual-type-check.ts +0 -26
- package/mock/entity.ts +0 -12
- package/mock/monorise/admin.ts +0 -35
- package/mock/monorise/chapter.ts +0 -94
- package/mock/monorise/course.ts +0 -149
- package/mock/monorise/index.ts +0 -143
- package/mock/monorise/learner.ts +0 -66
- package/mock/monorise/learning-activity.ts +0 -62
- package/mock/monorise/learning-journey-config.ts +0 -34
- package/mock/monorise/module.ts +0 -108
- package/mock/monorise/organization.ts +0 -63
- package/mock/monorise/reference.ts +0 -28
- package/mock/monorise/video.ts +0 -36
- package/processors/create-entity-processor.ts +0 -55
- package/processors/mutual-processor.ts +0 -262
- package/processors/prejoin-processor.ts +0 -264
- package/processors/replication-processor.ts +0 -261
- package/processors/tag-processor.ts +0 -174
- package/services/DependencyContainer.ts +0 -208
- package/services/entity-service-lifecycle.ts +0 -41
- package/services/entity.service.ts +0 -201
- package/services/mutual.service.ts +0 -285
- package/tsconfig.json +0 -116
- package/types/entity.type.ts +0 -62
- package/types/event.ts +0 -84
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
import type { Entity } from '@monorise/base';
|
|
2
|
-
import type { SQSBatchItemFailure, SQSEvent } from 'aws-lambda';
|
|
3
|
-
import { AllowedEntityTypes, EntityConfig } from '#/lambda-layer/monorise';
|
|
4
|
-
import type { MutualRepository } from '../data/Mutual';
|
|
5
|
-
import { PROJECTION_EXPRESSION } from '../data/ProjectionExpression';
|
|
6
|
-
import { parseSQSBusEvent } from '../helpers/event';
|
|
7
|
-
import type { publishEvent as publishEventType } from '../helpers/event';
|
|
8
|
-
import type { EventDetailBody as MutualProcessorEventDetailBody } from '../processors/mutual-processor';
|
|
9
|
-
import { DependencyContainer } from '../services/DependencyContainer';
|
|
10
|
-
import type { Prejoins } from '../types/entity.type';
|
|
11
|
-
import { EVENT } from '../types/event';
|
|
12
|
-
|
|
13
|
-
export type EventDetailBody = {
|
|
14
|
-
byEntityType: Entity;
|
|
15
|
-
byEntityId: string;
|
|
16
|
-
entityType: Entity;
|
|
17
|
-
publishedAt: string;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const container = new DependencyContainer();
|
|
21
|
-
|
|
22
|
-
async function processPrejoins({
|
|
23
|
-
mutualRepository,
|
|
24
|
-
publishEvent,
|
|
25
|
-
byEntityType,
|
|
26
|
-
byEntityId,
|
|
27
|
-
prejoins,
|
|
28
|
-
publishedAt,
|
|
29
|
-
}: {
|
|
30
|
-
mutualRepository: MutualRepository;
|
|
31
|
-
publishEvent: typeof publishEventType;
|
|
32
|
-
byEntityType: Entity;
|
|
33
|
-
byEntityId: string;
|
|
34
|
-
prejoins: Prejoins;
|
|
35
|
-
publishedAt: string;
|
|
36
|
-
}) {
|
|
37
|
-
const mutualCache: Partial<
|
|
38
|
-
Record<
|
|
39
|
-
Entity,
|
|
40
|
-
Array<{
|
|
41
|
-
byEntityType?: Entity;
|
|
42
|
-
byEntityId?: string;
|
|
43
|
-
entityType: Entity;
|
|
44
|
-
entityId: string;
|
|
45
|
-
}>
|
|
46
|
-
>
|
|
47
|
-
> = {
|
|
48
|
-
/*
|
|
49
|
-
course: [{
|
|
50
|
-
byEntityType: 'module',
|
|
51
|
-
byEntityId: '1',
|
|
52
|
-
entityType: 'course',
|
|
53
|
-
entityId: '1',
|
|
54
|
-
}],
|
|
55
|
-
module: [],
|
|
56
|
-
chapter: [],
|
|
57
|
-
video: []
|
|
58
|
-
*/
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
//initiate
|
|
62
|
-
mutualCache[byEntityType] = [
|
|
63
|
-
{
|
|
64
|
-
entityType: byEntityType,
|
|
65
|
-
entityId: byEntityId,
|
|
66
|
-
},
|
|
67
|
-
];
|
|
68
|
-
|
|
69
|
-
for (const { mutualField, targetEntityType, entityPaths } of prejoins) {
|
|
70
|
-
let toBePublishedContext: Record<string, any> = {};
|
|
71
|
-
|
|
72
|
-
for (const [index, entityPath] of entityPaths.entries()) {
|
|
73
|
-
const entityType = entityPath.entityType as keyof typeof mutualCache;
|
|
74
|
-
|
|
75
|
-
// skip cached
|
|
76
|
-
if (!entityPath.skipCache && mutualCache[entityType]) {
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// if skipping cache should not have previous run data
|
|
81
|
-
mutualCache[entityType] = [];
|
|
82
|
-
|
|
83
|
-
const parentEntityType = entityPaths[index - 1]
|
|
84
|
-
.entityType as keyof typeof mutualCache;
|
|
85
|
-
const parentEntities = mutualCache[parentEntityType] ?? [];
|
|
86
|
-
|
|
87
|
-
// find all nested entities
|
|
88
|
-
for (const parentEntity of parentEntities) {
|
|
89
|
-
const { entityType: parentEntityType, entityId: parentEntityId } =
|
|
90
|
-
parentEntity;
|
|
91
|
-
|
|
92
|
-
const { items: mutualItems } =
|
|
93
|
-
await mutualRepository.listEntitiesByEntity(
|
|
94
|
-
parentEntityType,
|
|
95
|
-
parentEntityId,
|
|
96
|
-
entityPath.entityType,
|
|
97
|
-
{
|
|
98
|
-
ProjectionExpression: PROJECTION_EXPRESSION.MUTUAL_DATA_ONLY,
|
|
99
|
-
},
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
// custom processor defined in prejoin config for each path
|
|
103
|
-
const processor =
|
|
104
|
-
entityPath.processor || ((items, context) => ({ items, context }));
|
|
105
|
-
|
|
106
|
-
const processed = processor(mutualItems, toBePublishedContext);
|
|
107
|
-
toBePublishedContext = processed?.context || toBePublishedContext;
|
|
108
|
-
|
|
109
|
-
mutualCache[entityType] = [
|
|
110
|
-
...(mutualCache[entityType] ?? []),
|
|
111
|
-
...(processed ? processed.items : mutualItems),
|
|
112
|
-
];
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (!mutualCache[entityType] && !Array.isArray(mutualCache[entityType])) {
|
|
116
|
-
// to avoid empty array
|
|
117
|
-
mutualCache[entityType] = [];
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const mutualIds = (
|
|
122
|
-
mutualCache[targetEntityType as keyof typeof mutualCache] ?? []
|
|
123
|
-
).map((item) => item.entityId);
|
|
124
|
-
|
|
125
|
-
await publishEvent<MutualProcessorEventDetailBody>({
|
|
126
|
-
event: EVENT.CORE.ENTITY_MUTUAL_TO_UPDATE,
|
|
127
|
-
payload: {
|
|
128
|
-
byEntityType,
|
|
129
|
-
byEntityId,
|
|
130
|
-
entityType: targetEntityType,
|
|
131
|
-
field: mutualField,
|
|
132
|
-
mutualIds,
|
|
133
|
-
customContext: toBePublishedContext,
|
|
134
|
-
publishedAt,
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
async function publishToSubscribers({
|
|
141
|
-
mutualRepository,
|
|
142
|
-
publishEvent,
|
|
143
|
-
byEntityType,
|
|
144
|
-
byEntityId,
|
|
145
|
-
publishedAt,
|
|
146
|
-
}: {
|
|
147
|
-
mutualRepository: MutualRepository;
|
|
148
|
-
publishEvent: typeof publishEventType;
|
|
149
|
-
byEntityType: Entity;
|
|
150
|
-
byEntityId: string;
|
|
151
|
-
publishedAt: string;
|
|
152
|
-
}) {
|
|
153
|
-
const listeners = AllowedEntityTypes.reduce(
|
|
154
|
-
(acc, configKey) => {
|
|
155
|
-
const { subscribes } = EntityConfig[configKey].mutual ?? {};
|
|
156
|
-
|
|
157
|
-
const hasSubscription = (subscribes ?? []).some(
|
|
158
|
-
({ entityType }) => entityType === byEntityType,
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
return [
|
|
162
|
-
...acc,
|
|
163
|
-
...(hasSubscription
|
|
164
|
-
? [
|
|
165
|
-
{
|
|
166
|
-
entityType: configKey,
|
|
167
|
-
},
|
|
168
|
-
]
|
|
169
|
-
: []),
|
|
170
|
-
];
|
|
171
|
-
},
|
|
172
|
-
[] as { entityType: Entity }[],
|
|
173
|
-
);
|
|
174
|
-
|
|
175
|
-
// publish event for each interested entity
|
|
176
|
-
const subscribedMutualItems = await Promise.all(
|
|
177
|
-
listeners.map(({ entityType: subscribedEntityType }) =>
|
|
178
|
-
mutualRepository.listEntitiesByEntity(
|
|
179
|
-
byEntityType,
|
|
180
|
-
byEntityId,
|
|
181
|
-
subscribedEntityType,
|
|
182
|
-
{
|
|
183
|
-
ProjectionExpression: PROJECTION_EXPRESSION.NO_DATA,
|
|
184
|
-
},
|
|
185
|
-
),
|
|
186
|
-
),
|
|
187
|
-
);
|
|
188
|
-
const subscribedMutuals = subscribedMutualItems.flatMap((item) => item.items);
|
|
189
|
-
|
|
190
|
-
await Promise.all(
|
|
191
|
-
subscribedMutuals.map((subscribedMutual) =>
|
|
192
|
-
publishEvent({
|
|
193
|
-
event: EVENT.CORE.PREJOIN_RELATIONSHIP_SYNC,
|
|
194
|
-
payload: {
|
|
195
|
-
byEntityType: subscribedMutual.entityType,
|
|
196
|
-
byEntityId: subscribedMutual.entityId,
|
|
197
|
-
entityType: subscribedMutual.byEntityType,
|
|
198
|
-
publishedAt,
|
|
199
|
-
},
|
|
200
|
-
}),
|
|
201
|
-
),
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export const handler = async (ev: SQSEvent) => {
|
|
206
|
-
const batchItemFailures: SQSBatchItemFailure[] = [];
|
|
207
|
-
|
|
208
|
-
const { mutualRepository, publishEvent } = container;
|
|
209
|
-
|
|
210
|
-
for (const record of ev.Records) {
|
|
211
|
-
const body = parseSQSBusEvent<EventDetailBody>(record.body);
|
|
212
|
-
const { detail } = body;
|
|
213
|
-
const { byEntityType, byEntityId, entityType, publishedAt } = detail;
|
|
214
|
-
let errorContext: Record<string, unknown> = {
|
|
215
|
-
body,
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
const isEntityTypeSubscribed = (
|
|
220
|
-
EntityConfig[byEntityType]?.mutual?.subscribes ?? []
|
|
221
|
-
).some(
|
|
222
|
-
({ entityType: subscribedEntityType }) =>
|
|
223
|
-
subscribedEntityType === entityType,
|
|
224
|
-
);
|
|
225
|
-
const hasPrejoins = EntityConfig[byEntityType]?.mutual?.prejoins;
|
|
226
|
-
const shouldProcessPrejoins = isEntityTypeSubscribed && hasPrejoins;
|
|
227
|
-
errorContext = {
|
|
228
|
-
...errorContext,
|
|
229
|
-
isEntityTypeSubscribed,
|
|
230
|
-
hasPrejoins,
|
|
231
|
-
shouldProcessPrejoins,
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
if (shouldProcessPrejoins) {
|
|
235
|
-
await processPrejoins({
|
|
236
|
-
mutualRepository,
|
|
237
|
-
publishEvent,
|
|
238
|
-
byEntityType,
|
|
239
|
-
byEntityId,
|
|
240
|
-
prejoins: EntityConfig[byEntityType]?.mutual?.prejoins ?? [],
|
|
241
|
-
publishedAt,
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
await publishToSubscribers({
|
|
246
|
-
mutualRepository,
|
|
247
|
-
publishEvent,
|
|
248
|
-
byEntityType,
|
|
249
|
-
byEntityId,
|
|
250
|
-
publishedAt,
|
|
251
|
-
});
|
|
252
|
-
} catch (err) {
|
|
253
|
-
console.log(
|
|
254
|
-
'===PREJOIN-PROCESSOR ERROR===',
|
|
255
|
-
err,
|
|
256
|
-
JSON.stringify({ errorContext }, null, 2),
|
|
257
|
-
);
|
|
258
|
-
|
|
259
|
-
batchItemFailures.push({ itemIdentifier: record.messageId });
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return { batchItemFailures };
|
|
264
|
-
};
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import { ConditionalCheckFailedException } from '@aws-sdk/client-dynamodb';
|
|
2
|
-
import type {
|
|
3
|
-
AttributeValue,
|
|
4
|
-
_Record as DynamoDBStreamEvent,
|
|
5
|
-
} from '@aws-sdk/client-dynamodb-streams';
|
|
6
|
-
import type { DynamoDBBatchItemFailure } from 'aws-lambda';
|
|
7
|
-
import {
|
|
8
|
-
ENTITY_REPLICATION_INDEX,
|
|
9
|
-
MUTUAL_REPLICATION_INDEX,
|
|
10
|
-
} from '../configs/service.config';
|
|
11
|
-
import { StandardError } from '../errors/standard-error';
|
|
12
|
-
import { DependencyContainer } from '../services/DependencyContainer';
|
|
13
|
-
|
|
14
|
-
const container = new DependencyContainer();
|
|
15
|
-
const TableName = process.env.DDB_TABLE;
|
|
16
|
-
|
|
17
|
-
export const handler = async (event: { Records: DynamoDBStreamEvent[] }) => {
|
|
18
|
-
const batchItemFailures: DynamoDBBatchItemFailure[] = [];
|
|
19
|
-
const { dynamodbClient } = container;
|
|
20
|
-
|
|
21
|
-
for (const record of event.Records) {
|
|
22
|
-
const errorContext: any = {};
|
|
23
|
-
errorContext.record = record;
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
if (record.eventName === 'MODIFY') {
|
|
27
|
-
const modifiedItem = record.dynamodb?.NewImage;
|
|
28
|
-
if (!modifiedItem) {
|
|
29
|
-
continue;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const isMetadata = modifiedItem.SK.S?.startsWith('#METADATA#');
|
|
33
|
-
const isMutual = modifiedItem.PK.S?.startsWith('MUTUAL#') && isMetadata;
|
|
34
|
-
const isEntity = isMetadata && !isMutual;
|
|
35
|
-
|
|
36
|
-
if (!isEntity && !isMutual) {
|
|
37
|
-
// skip replicated data
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// default variables
|
|
42
|
-
let targetRPK = 'R1PK';
|
|
43
|
-
const targetData = 'data';
|
|
44
|
-
let targetIndexName = ENTITY_REPLICATION_INDEX;
|
|
45
|
-
const targetUpdatedAt = 'updatedAt';
|
|
46
|
-
|
|
47
|
-
let queryExpression: {
|
|
48
|
-
FilterExpression?: string;
|
|
49
|
-
ExpressionAttributeNames: Record<string, string>;
|
|
50
|
-
ExpressionAttributeValues: Record<string, AttributeValue>;
|
|
51
|
-
} = {
|
|
52
|
-
FilterExpression: `#${targetUpdatedAt} < :${targetUpdatedAt}`,
|
|
53
|
-
ExpressionAttributeNames: {
|
|
54
|
-
[`#${targetRPK}`]: targetRPK,
|
|
55
|
-
[`#${targetUpdatedAt}`]: targetUpdatedAt,
|
|
56
|
-
},
|
|
57
|
-
ExpressionAttributeValues: {
|
|
58
|
-
[`:${targetRPK}`]: modifiedItem.PK,
|
|
59
|
-
[`:${targetUpdatedAt}`]: modifiedItem.updatedAt,
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const updateExpession: {
|
|
64
|
-
ConditionExpression: string;
|
|
65
|
-
UpdateExpression: string;
|
|
66
|
-
ExpressionAttributeNames: Record<string, string>;
|
|
67
|
-
ExpressionAttributeValues: Record<string, AttributeValue>;
|
|
68
|
-
} = {
|
|
69
|
-
UpdateExpression: `SET #${targetUpdatedAt} = :${targetUpdatedAt}, #${targetData} = :${targetData}`,
|
|
70
|
-
ConditionExpression: `#${targetUpdatedAt} < :${targetUpdatedAt}`,
|
|
71
|
-
ExpressionAttributeNames: {
|
|
72
|
-
[`#${targetData}`]: targetData,
|
|
73
|
-
[`#${targetUpdatedAt}`]: targetUpdatedAt,
|
|
74
|
-
},
|
|
75
|
-
ExpressionAttributeValues: {
|
|
76
|
-
[`:${targetData}`]: modifiedItem.data,
|
|
77
|
-
[`:${targetUpdatedAt}`]: modifiedItem.updatedAt,
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
if (isMutual) {
|
|
82
|
-
targetRPK = 'R2PK';
|
|
83
|
-
targetIndexName = MUTUAL_REPLICATION_INDEX;
|
|
84
|
-
|
|
85
|
-
// condition to only replicate to mutualAsEntity
|
|
86
|
-
queryExpression = {
|
|
87
|
-
FilterExpression: `${queryExpression.FilterExpression} AND #SK = :metadata`, // to replicate to mutualAsEntity only
|
|
88
|
-
ExpressionAttributeNames: {
|
|
89
|
-
'#SK': 'SK',
|
|
90
|
-
[`#${targetRPK}`]: targetRPK,
|
|
91
|
-
[`#${targetUpdatedAt}`]: targetUpdatedAt,
|
|
92
|
-
},
|
|
93
|
-
ExpressionAttributeValues: {
|
|
94
|
-
':metadata': { S: '#METADATA#' },
|
|
95
|
-
[`:${targetRPK}`]: modifiedItem.PK,
|
|
96
|
-
[`:${targetUpdatedAt}`]: modifiedItem.mutualUpdatedAt,
|
|
97
|
-
},
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
updateExpession.ExpressionAttributeValues = {
|
|
101
|
-
[`:${targetData}`]: modifiedItem.mutualData,
|
|
102
|
-
[`:${targetUpdatedAt}`]: modifiedItem.mutualUpdatedAt,
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
errorContext.queryExpression = queryExpression;
|
|
107
|
-
errorContext.updateExpession = updateExpession;
|
|
108
|
-
|
|
109
|
-
// retrieve all to be replicated items
|
|
110
|
-
let toBeReplicatedItems: Record<string, AttributeValue>[] = [];
|
|
111
|
-
let lastKey;
|
|
112
|
-
|
|
113
|
-
do {
|
|
114
|
-
const queryResult = await dynamodbClient.query({
|
|
115
|
-
TableName,
|
|
116
|
-
IndexName: targetIndexName,
|
|
117
|
-
KeyConditionExpression: `#${targetRPK} = :${targetRPK}`,
|
|
118
|
-
...queryExpression,
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
toBeReplicatedItems = [
|
|
122
|
-
...toBeReplicatedItems,
|
|
123
|
-
...(queryResult.Items || []),
|
|
124
|
-
];
|
|
125
|
-
lastKey = queryResult.LastEvaluatedKey;
|
|
126
|
-
} while (lastKey);
|
|
127
|
-
errorContext.toBeReplicatedItems = toBeReplicatedItems;
|
|
128
|
-
|
|
129
|
-
const updatePromises = toBeReplicatedItems.map((item) => {
|
|
130
|
-
const updateParams = {
|
|
131
|
-
TableName,
|
|
132
|
-
Key: {
|
|
133
|
-
PK: item.PK,
|
|
134
|
-
SK: item.SK,
|
|
135
|
-
},
|
|
136
|
-
...updateExpession,
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
return dynamodbClient.updateItem(updateParams);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const results = await Promise.allSettled(updatePromises);
|
|
143
|
-
errorContext.results = results;
|
|
144
|
-
|
|
145
|
-
if (
|
|
146
|
-
results.some(
|
|
147
|
-
(result) =>
|
|
148
|
-
result.status === 'rejected' &&
|
|
149
|
-
!(result.reason instanceof ConditionalCheckFailedException),
|
|
150
|
-
)
|
|
151
|
-
) {
|
|
152
|
-
throw new StandardError(
|
|
153
|
-
'REPLICATION_ERROR',
|
|
154
|
-
'Replication error',
|
|
155
|
-
null,
|
|
156
|
-
errorContext,
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (record.eventName === 'REMOVE') {
|
|
162
|
-
const removedKeys = record.dynamodb?.Keys || {};
|
|
163
|
-
const isMetadata = removedKeys.SK.S?.startsWith('#METADATA#');
|
|
164
|
-
const isMutual = removedKeys.PK.S?.startsWith('MUTUAL#') && isMetadata;
|
|
165
|
-
const isEntity = isMetadata && !isMutual;
|
|
166
|
-
|
|
167
|
-
if (!isEntity && !isMutual) {
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// default query settings
|
|
172
|
-
let targetRPK = 'R1PK';
|
|
173
|
-
let targetIndexName: string = ENTITY_REPLICATION_INDEX;
|
|
174
|
-
|
|
175
|
-
if (isMutual) {
|
|
176
|
-
targetRPK = 'R2PK';
|
|
177
|
-
targetIndexName = MUTUAL_REPLICATION_INDEX;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
let itemsToDelete: Record<string, AttributeValue>[] = [];
|
|
181
|
-
let lastKey;
|
|
182
|
-
|
|
183
|
-
do {
|
|
184
|
-
const queryResult = await dynamodbClient.query({
|
|
185
|
-
TableName,
|
|
186
|
-
IndexName: targetIndexName,
|
|
187
|
-
KeyConditionExpression: `#${targetRPK} = :${targetRPK}`,
|
|
188
|
-
ExpressionAttributeNames: {
|
|
189
|
-
[`#${targetRPK}`]: targetRPK,
|
|
190
|
-
},
|
|
191
|
-
ExpressionAttributeValues: {
|
|
192
|
-
[`:${targetRPK}`]: removedKeys.PK,
|
|
193
|
-
},
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
itemsToDelete = [...itemsToDelete, ...(queryResult.Items || [])];
|
|
197
|
-
lastKey = queryResult.LastEvaluatedKey;
|
|
198
|
-
} while (lastKey);
|
|
199
|
-
|
|
200
|
-
const mutualsToDelete = Array.from(
|
|
201
|
-
new Set(
|
|
202
|
-
itemsToDelete
|
|
203
|
-
.filter((item) => item.R2PK?.S?.startsWith('MUTUAL#'))
|
|
204
|
-
.map((filteredItem) => filteredItem.R2PK?.S),
|
|
205
|
-
),
|
|
206
|
-
);
|
|
207
|
-
mutualsToDelete.forEach((mutual) => {
|
|
208
|
-
if (!mutual) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
itemsToDelete.push({ PK: { S: mutual }, SK: { S: '#METADATA#' } });
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
const deleteResults = await Promise.allSettled(
|
|
216
|
-
itemsToDelete.map((item) =>
|
|
217
|
-
dynamodbClient.deleteItem({
|
|
218
|
-
TableName,
|
|
219
|
-
Key: {
|
|
220
|
-
PK: item.PK,
|
|
221
|
-
SK: item.SK,
|
|
222
|
-
},
|
|
223
|
-
}),
|
|
224
|
-
),
|
|
225
|
-
);
|
|
226
|
-
errorContext.deleteResults = deleteResults;
|
|
227
|
-
|
|
228
|
-
if (
|
|
229
|
-
deleteResults.some(
|
|
230
|
-
(result) =>
|
|
231
|
-
result.status === 'rejected' &&
|
|
232
|
-
!(result.reason instanceof ConditionalCheckFailedException),
|
|
233
|
-
)
|
|
234
|
-
) {
|
|
235
|
-
throw new StandardError(
|
|
236
|
-
'REPLICATION_ERROR',
|
|
237
|
-
'Replication error',
|
|
238
|
-
null,
|
|
239
|
-
errorContext,
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
} catch (error) {
|
|
244
|
-
console.error('====REPLICATION_ERROR', error);
|
|
245
|
-
console.log(
|
|
246
|
-
'====REPLICATION_ERROR errorContext',
|
|
247
|
-
JSON.stringify(errorContext, null, 2),
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
batchItemFailures.push({
|
|
251
|
-
itemIdentifier: record.dynamodb?.SequenceNumber || '',
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// immediately return to prevent processing the rest
|
|
255
|
-
// because stream will restart from this point again
|
|
256
|
-
return { batchItemFailures };
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
return { batchItemFailures };
|
|
261
|
-
};
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import type { CreatedEntity, Entity as EntityType } from '@monorise/base';
|
|
2
|
-
import type { SQSBatchItemFailure, SQSEvent } from 'aws-lambda';
|
|
3
|
-
import { EntityConfig } from '#/lambda-layer/monorise';
|
|
4
|
-
import type { Entity } from '../data/Entity';
|
|
5
|
-
import { parseSQSBusEvent } from '../helpers/event';
|
|
6
|
-
import { DependencyContainer } from '../services/DependencyContainer';
|
|
7
|
-
import type { Tag } from '../types/entity.type';
|
|
8
|
-
|
|
9
|
-
export type EventDetailBody = {
|
|
10
|
-
entityType: EntityType;
|
|
11
|
-
entityId: string;
|
|
12
|
-
data: Record<string, any>;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const container = new DependencyContainer();
|
|
16
|
-
|
|
17
|
-
function compareTags(
|
|
18
|
-
existingTags: Tag[],
|
|
19
|
-
newTags: Tag[],
|
|
20
|
-
): {
|
|
21
|
-
old: Tag[];
|
|
22
|
-
new: Tag[];
|
|
23
|
-
remain: Tag[];
|
|
24
|
-
} {
|
|
25
|
-
const oldMap = new Map(
|
|
26
|
-
existingTags.map((item) => [`${item.group}#${item.sortValue}`, item]),
|
|
27
|
-
);
|
|
28
|
-
const newMap = new Map(
|
|
29
|
-
newTags.map((item) => [`${item.group}#${item.sortValue}`, item]),
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
const remain: Tag[] = [];
|
|
33
|
-
const oldDiff: Tag[] = [];
|
|
34
|
-
const newDiff: Tag[] = [];
|
|
35
|
-
|
|
36
|
-
for (const [key, oldItem] of oldMap.entries()) {
|
|
37
|
-
if (newMap.has(key)) {
|
|
38
|
-
remain.push(oldItem);
|
|
39
|
-
newMap.delete(key); // Remove from newMap as it's already in remain
|
|
40
|
-
} else {
|
|
41
|
-
oldDiff.push(oldItem);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Remaining entries in newMap are new
|
|
46
|
-
for (const newItem of newMap.values()) {
|
|
47
|
-
newDiff.push(newItem);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
old: oldDiff,
|
|
52
|
-
new: newDiff,
|
|
53
|
-
remain,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async function batchUpdateTags({
|
|
58
|
-
tagName,
|
|
59
|
-
entity,
|
|
60
|
-
diff,
|
|
61
|
-
}: {
|
|
62
|
-
tagName: string;
|
|
63
|
-
entity: Entity<EntityType>;
|
|
64
|
-
diff: { old: Tag[]; new: Tag[] };
|
|
65
|
-
}): Promise<void> {
|
|
66
|
-
if (!entity.entityId) {
|
|
67
|
-
throw new Error('entityId is required');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const { old: tagsToRemove, new: tagsToAdd } = diff;
|
|
71
|
-
const { entityType, entityId } = entity;
|
|
72
|
-
|
|
73
|
-
const removePromises = tagsToRemove.reduce(
|
|
74
|
-
(acc, tag) => [
|
|
75
|
-
...acc,
|
|
76
|
-
container.tagRepository.deleteTag({
|
|
77
|
-
tagName,
|
|
78
|
-
group: tag.group,
|
|
79
|
-
sortValue: tag.sortValue,
|
|
80
|
-
entityType,
|
|
81
|
-
entityId,
|
|
82
|
-
}),
|
|
83
|
-
],
|
|
84
|
-
[] as Promise<any>[],
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
const addPromises = tagsToAdd.reduce(
|
|
88
|
-
(acc, tag) => [
|
|
89
|
-
...acc,
|
|
90
|
-
container.tagRepository.createTag({
|
|
91
|
-
tagName,
|
|
92
|
-
group: tag.group,
|
|
93
|
-
sortValue: tag.sortValue,
|
|
94
|
-
entity,
|
|
95
|
-
}),
|
|
96
|
-
],
|
|
97
|
-
[] as Promise<any>[],
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
await Promise.all([...removePromises, ...addPromises]);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export const handler = async (ev: SQSEvent) => {
|
|
104
|
-
const batchItemFailures: SQSBatchItemFailure[] = [];
|
|
105
|
-
|
|
106
|
-
for (const record of ev.Records) {
|
|
107
|
-
const body = parseSQSBusEvent<EventDetailBody>(record.body);
|
|
108
|
-
const { detail } = body;
|
|
109
|
-
const { entityType, entityId } = detail;
|
|
110
|
-
|
|
111
|
-
const errorContext: Record<string, unknown> = {};
|
|
112
|
-
errorContext.body = body;
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const tagConfigs = EntityConfig[entityType]?.tags;
|
|
116
|
-
|
|
117
|
-
if (!tagConfigs || !tagConfigs.length) {
|
|
118
|
-
// skip if entity has no tag configs
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
await container.tagRepository.createLock({
|
|
123
|
-
entityType,
|
|
124
|
-
entityId,
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
for (const tagConfig of tagConfigs) {
|
|
128
|
-
const { name, processor } = tagConfig;
|
|
129
|
-
|
|
130
|
-
const existingTags = await container.tagRepository.getExistingTags({
|
|
131
|
-
entityType,
|
|
132
|
-
entityId,
|
|
133
|
-
tagName: name,
|
|
134
|
-
});
|
|
135
|
-
errorContext.existingTags = existingTags;
|
|
136
|
-
|
|
137
|
-
const entity = await container.entityRepository.getEntity(
|
|
138
|
-
entityType,
|
|
139
|
-
entityId,
|
|
140
|
-
);
|
|
141
|
-
errorContext.entity = entity;
|
|
142
|
-
|
|
143
|
-
const newTags = await processor(
|
|
144
|
-
entity as unknown as CreatedEntity<EntityType>,
|
|
145
|
-
);
|
|
146
|
-
errorContext.newTags = newTags;
|
|
147
|
-
|
|
148
|
-
const diff = compareTags(existingTags, newTags);
|
|
149
|
-
errorContext.diff = diff;
|
|
150
|
-
|
|
151
|
-
await batchUpdateTags({
|
|
152
|
-
tagName: name,
|
|
153
|
-
entity,
|
|
154
|
-
diff,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
await container.tagRepository.deleteLock({
|
|
159
|
-
entityType,
|
|
160
|
-
entityId,
|
|
161
|
-
});
|
|
162
|
-
} catch (err) {
|
|
163
|
-
console.log(
|
|
164
|
-
'===TAG-PROCESSOR ERROR===',
|
|
165
|
-
err,
|
|
166
|
-
JSON.stringify({ errorContext }, null, 2),
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
batchItemFailures.push({ itemIdentifier: record.messageId });
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return { batchItemFailures };
|
|
174
|
-
};
|