@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,62 +0,0 @@
|
|
|
1
|
-
import { createEntityConfig } from '@monorise/cli';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
|
|
4
|
-
const baseSchema = z.object({
|
|
5
|
-
activityType: z.enum(['REFLECTION', 'QUIZ', 'FILE_UPLOAD']),
|
|
6
|
-
questionType: z.enum(['TEXT', 'RADIO', 'CHECKBOX']).optional(),
|
|
7
|
-
question: z.string(),
|
|
8
|
-
explanation: z.string().optional(),
|
|
9
|
-
options: z
|
|
10
|
-
.object({
|
|
11
|
-
label: z.string(),
|
|
12
|
-
isCorrect: z.boolean(),
|
|
13
|
-
})
|
|
14
|
-
.array()
|
|
15
|
-
.optional(),
|
|
16
|
-
remark: z.string().optional(),
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
const config = createEntityConfig({
|
|
20
|
-
name: 'learning-activity',
|
|
21
|
-
displayName: 'Learning Activity',
|
|
22
|
-
baseSchema,
|
|
23
|
-
effect: (schema) => {
|
|
24
|
-
return schema.refine(
|
|
25
|
-
(value) => {
|
|
26
|
-
if (value.activityType === 'REFLECTION' && value.question) {
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
if (value.activityType === 'FILE_UPLOAD' && value.question) {
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
(value.questionType === 'CHECKBOX' ||
|
|
36
|
-
value.questionType === 'RADIO') &&
|
|
37
|
-
value.options?.length &&
|
|
38
|
-
value.options.length >= 2 &&
|
|
39
|
-
value.options.filter((v) => v.label).length ===
|
|
40
|
-
value.options.length &&
|
|
41
|
-
value.options.some((v) => v.isCorrect)
|
|
42
|
-
) {
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (value.questionType === 'TEXT' && value.question) {
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return false;
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
message:
|
|
54
|
-
'For checkbox and radio question types, please provide at least two options and at least one correct answer',
|
|
55
|
-
path: ['options'],
|
|
56
|
-
},
|
|
57
|
-
);
|
|
58
|
-
},
|
|
59
|
-
searchableFields: ['question', 'remark'],
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
export default config;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { createEntityConfig } from '@monorise/cli';
|
|
2
|
-
import type { Mutual } from '@monorise/react';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import { Entity } from '../entity';
|
|
5
|
-
|
|
6
|
-
const baseSchema = z.object({}).partial();
|
|
7
|
-
|
|
8
|
-
const mutualSchema = z
|
|
9
|
-
.object({
|
|
10
|
-
courseOrders: z.string().array(),
|
|
11
|
-
})
|
|
12
|
-
.partial();
|
|
13
|
-
|
|
14
|
-
const config = createEntityConfig({
|
|
15
|
-
name: 'learning-journey-config',
|
|
16
|
-
displayName: 'Learning Journey Config',
|
|
17
|
-
baseSchema,
|
|
18
|
-
mutual: {
|
|
19
|
-
mutualSchema,
|
|
20
|
-
mutualFields: {
|
|
21
|
-
courseOrders: {
|
|
22
|
-
entityType: Entity.COURSE,
|
|
23
|
-
mutualDataProcessor: (
|
|
24
|
-
ids: string[],
|
|
25
|
-
context: Mutual<Entity.LEARNING_JOURNEY_CONFIG, Entity.COURSE>,
|
|
26
|
-
) => {
|
|
27
|
-
return { index: ids.indexOf(context.entityId) };
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
export default config;
|
package/mock/monorise/module.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { createEntityConfig } from '@monorise/cli';
|
|
2
|
-
import type { Mutual } from '@monorise/react';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import { Entity } from '../entity';
|
|
5
|
-
|
|
6
|
-
const allowedTypes = ['MODULE', 'TOUCHPOINT', 'CHECKPOINT'] as const;
|
|
7
|
-
|
|
8
|
-
const baseSchema = z
|
|
9
|
-
.object({
|
|
10
|
-
title: z.string(),
|
|
11
|
-
description: z.string(),
|
|
12
|
-
shortDescription: z.string(),
|
|
13
|
-
remark: z
|
|
14
|
-
.string()
|
|
15
|
-
.describe('For internal use, eg. This module is for tracks'),
|
|
16
|
-
type: z.enum(allowedTypes),
|
|
17
|
-
})
|
|
18
|
-
.partial();
|
|
19
|
-
|
|
20
|
-
const createSchema = baseSchema.extend({
|
|
21
|
-
title: z.string(),
|
|
22
|
-
type: z.enum(allowedTypes),
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
const mutualSchema = z
|
|
26
|
-
.object({
|
|
27
|
-
chapterOrders: z.string().array(),
|
|
28
|
-
})
|
|
29
|
-
.partial();
|
|
30
|
-
|
|
31
|
-
const config = createEntityConfig({
|
|
32
|
-
name: 'module',
|
|
33
|
-
displayName: 'Module',
|
|
34
|
-
baseSchema,
|
|
35
|
-
createSchema,
|
|
36
|
-
searchableFields: ['title'],
|
|
37
|
-
mutual: {
|
|
38
|
-
subscribes: [{ entityType: Entity.CHAPTER }],
|
|
39
|
-
mutualSchema,
|
|
40
|
-
mutualFields: {
|
|
41
|
-
chapterOrders: {
|
|
42
|
-
entityType: Entity.CHAPTER,
|
|
43
|
-
mutualDataProcessor: (
|
|
44
|
-
ids: string[],
|
|
45
|
-
context: Mutual<Entity.MODULE, Entity.CHAPTER>,
|
|
46
|
-
) => {
|
|
47
|
-
return { index: ids.indexOf(context.entityId) };
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
videos: {
|
|
51
|
-
entityType: Entity.VIDEO,
|
|
52
|
-
mutualDataProcessor: (
|
|
53
|
-
ids: string[],
|
|
54
|
-
context: Mutual<Entity.MODULE, Entity.VIDEO>,
|
|
55
|
-
prejoinContext?: Record<string, any>,
|
|
56
|
-
) => ({
|
|
57
|
-
index: ids.indexOf(context.entityId),
|
|
58
|
-
chapterId: prejoinContext?.[context.entityId],
|
|
59
|
-
}),
|
|
60
|
-
},
|
|
61
|
-
learningActivities: {
|
|
62
|
-
entityType: Entity.LEARNING_ACTIVITY,
|
|
63
|
-
mutualDataProcessor: (
|
|
64
|
-
ids: string[],
|
|
65
|
-
context: Mutual<Entity.MODULE, Entity.LEARNING_ACTIVITY>,
|
|
66
|
-
prejoinContext?: Record<string, any>,
|
|
67
|
-
) => ({
|
|
68
|
-
index: ids.indexOf(context.entityId),
|
|
69
|
-
chapterId: prejoinContext?.[context.entityId],
|
|
70
|
-
}),
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
prejoins: [
|
|
74
|
-
{
|
|
75
|
-
mutualField: 'videos',
|
|
76
|
-
targetEntityType: Entity.VIDEO,
|
|
77
|
-
entityPaths: [
|
|
78
|
-
{
|
|
79
|
-
entityType: Entity.MODULE,
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
entityType: Entity.CHAPTER,
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
entityType: Entity.VIDEO,
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
mutualField: 'learningActivities',
|
|
91
|
-
targetEntityType: Entity.LEARNING_ACTIVITY,
|
|
92
|
-
entityPaths: [
|
|
93
|
-
{
|
|
94
|
-
entityType: Entity.MODULE,
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
entityType: Entity.CHAPTER,
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
entityType: Entity.LEARNING_ACTIVITY,
|
|
101
|
-
},
|
|
102
|
-
],
|
|
103
|
-
},
|
|
104
|
-
],
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
export default config;
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { createEntityConfig } from '@monorise/cli';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
|
|
4
|
-
import type { Mutual } from '@monorise/react';
|
|
5
|
-
import { Entity } from '../entity';
|
|
6
|
-
|
|
7
|
-
export enum EXPIRY_TYPE {
|
|
8
|
-
ORG_WIDE = 'ORG_WIDE',
|
|
9
|
-
PER_INDIVIDUAL = 'PER_INDIVIDUAL',
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const baseSchema = z
|
|
13
|
-
.object({
|
|
14
|
-
name: z.string(),
|
|
15
|
-
licenseCount: z.coerce.number(),
|
|
16
|
-
allowSelfRegistration: z.boolean(),
|
|
17
|
-
isPrimary: z.boolean(),
|
|
18
|
-
domains: z.string().array(),
|
|
19
|
-
selfRegistrationDisclaimer: z.string(),
|
|
20
|
-
welcomeMessage: z.string(),
|
|
21
|
-
pathwayWelcomeMessage: z.string(),
|
|
22
|
-
expiryType: z.nativeEnum(EXPIRY_TYPE),
|
|
23
|
-
orgExpiryDate: z.string().datetime(),
|
|
24
|
-
individualAccessDuration: z.coerce.number(),
|
|
25
|
-
})
|
|
26
|
-
.partial();
|
|
27
|
-
|
|
28
|
-
const createSchema = baseSchema.extend({
|
|
29
|
-
name: z.string().min(1, 'Please provide a name for this organization'),
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const mutualSchema = z
|
|
33
|
-
.object({
|
|
34
|
-
organizationOrders: z.string().array(),
|
|
35
|
-
})
|
|
36
|
-
.partial();
|
|
37
|
-
|
|
38
|
-
const config = createEntityConfig({
|
|
39
|
-
name: 'organization',
|
|
40
|
-
displayName: 'Organization',
|
|
41
|
-
baseSchema,
|
|
42
|
-
createSchema,
|
|
43
|
-
searchableFields: ['name'],
|
|
44
|
-
mutual: {
|
|
45
|
-
mutualSchema,
|
|
46
|
-
mutualFields: {
|
|
47
|
-
organizationOrders: {
|
|
48
|
-
entityType: Entity.ORGANIZATION,
|
|
49
|
-
mutualDataProcessor: (
|
|
50
|
-
ids: string[],
|
|
51
|
-
context: Mutual<Entity.ORGANIZATION, Entity.ORGANIZATION>,
|
|
52
|
-
) => {
|
|
53
|
-
return {
|
|
54
|
-
index: ids.indexOf(context.entityId),
|
|
55
|
-
primaryOrganizationId: context.byEntityId,
|
|
56
|
-
};
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
export default config;
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { createEntityConfig } from '@monorise/cli';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
|
|
4
|
-
const baseSchema = z
|
|
5
|
-
.object({
|
|
6
|
-
label: z.string(),
|
|
7
|
-
link: z.string(),
|
|
8
|
-
prelabel: z.string(),
|
|
9
|
-
postlabel: z.string(),
|
|
10
|
-
})
|
|
11
|
-
.partial();
|
|
12
|
-
|
|
13
|
-
const createSchema = baseSchema.extend({
|
|
14
|
-
label: z.string().min(1, {
|
|
15
|
-
message: 'Label is required',
|
|
16
|
-
}),
|
|
17
|
-
link: z.string().url('This is not a valid url'),
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
const config = createEntityConfig({
|
|
21
|
-
name: 'reference',
|
|
22
|
-
displayName: 'Reference',
|
|
23
|
-
baseSchema,
|
|
24
|
-
createSchema,
|
|
25
|
-
searchableFields: ['label', 'link', 'prelabel', 'postlabel'],
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
export default config;
|
package/mock/monorise/video.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { createEntityConfig } from '@monorise/cli';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
|
|
4
|
-
const baseSchema = z
|
|
5
|
-
.object({
|
|
6
|
-
title: z.string(),
|
|
7
|
-
categories: z.string().array(),
|
|
8
|
-
additionalFiles: z
|
|
9
|
-
.object({
|
|
10
|
-
type: z.string(),
|
|
11
|
-
id: z.string(),
|
|
12
|
-
content: z.any(),
|
|
13
|
-
})
|
|
14
|
-
.array()
|
|
15
|
-
.optional(),
|
|
16
|
-
keyTakeaway: z.string(),
|
|
17
|
-
remark: z.string(),
|
|
18
|
-
duration: z.number(),
|
|
19
|
-
})
|
|
20
|
-
.partial();
|
|
21
|
-
|
|
22
|
-
const createSchema = baseSchema.extend({
|
|
23
|
-
title: z.string().min(4, {
|
|
24
|
-
message: 'Title must be at least 4 characters.',
|
|
25
|
-
}),
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const config = createEntityConfig({
|
|
29
|
-
name: 'video',
|
|
30
|
-
displayName: 'Video',
|
|
31
|
-
baseSchema,
|
|
32
|
-
createSchema,
|
|
33
|
-
searchableFields: ['title', 'remark'],
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
export default config;
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import type { Entity } from '@monorise/base';
|
|
2
|
-
import type { SQSBatchItemFailure, SQSEvent } from 'aws-lambda';
|
|
3
|
-
import { StandardError } from '../errors/standard-error';
|
|
4
|
-
import { parseSQSBusEvent } from '../helpers/event';
|
|
5
|
-
import { DependencyContainer } from '../services/DependencyContainer';
|
|
6
|
-
|
|
7
|
-
const container = new DependencyContainer();
|
|
8
|
-
|
|
9
|
-
type EventDetailBody = {
|
|
10
|
-
entityType: Entity;
|
|
11
|
-
entityId?: string;
|
|
12
|
-
entityPayload: Record<string, any>;
|
|
13
|
-
accountId?: string;
|
|
14
|
-
options: {
|
|
15
|
-
createAndUpdateDatetime?: string;
|
|
16
|
-
mutualId?: string;
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export const handler = async (ev: SQSEvent) => {
|
|
21
|
-
const { entityService } = container;
|
|
22
|
-
const batchItemFailures: SQSBatchItemFailure[] = [];
|
|
23
|
-
|
|
24
|
-
for (const record of ev.Records) {
|
|
25
|
-
const errorContext: Record<string, unknown> = {};
|
|
26
|
-
const body = parseSQSBusEvent<EventDetailBody>(record.body);
|
|
27
|
-
const { detail } = body;
|
|
28
|
-
const { entityType, entityId, entityPayload, accountId, options } = detail;
|
|
29
|
-
errorContext.body = body;
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
await entityService.createEntity({
|
|
33
|
-
entityType,
|
|
34
|
-
entityId,
|
|
35
|
-
entityPayload,
|
|
36
|
-
accountId,
|
|
37
|
-
options,
|
|
38
|
-
});
|
|
39
|
-
} catch (err) {
|
|
40
|
-
console.error(
|
|
41
|
-
'=====CREATE_ENTITY_PROCESSOR_ERROR=====',
|
|
42
|
-
err,
|
|
43
|
-
JSON.stringify({ errorContext }, null, 2),
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
if (err instanceof StandardError && err.code === 'INVALID_ENTITY_TYPE') {
|
|
47
|
-
continue; // do not retry
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
batchItemFailures.push({ itemIdentifier: record.messageId });
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return { batchItemFailures };
|
|
55
|
-
};
|
|
@@ -1,262 +0,0 @@
|
|
|
1
|
-
import { TransactionCanceledException } from '@aws-sdk/client-dynamodb';
|
|
2
|
-
import type { Entity } from '@monorise/base';
|
|
3
|
-
import type { SQSBatchItemFailure, SQSEvent } from 'aws-lambda';
|
|
4
|
-
import { EntityConfig } from '#/lambda-layer/monorise';
|
|
5
|
-
import { Mutual } from '../data/Mutual';
|
|
6
|
-
import { StandardError } from '../errors/standard-error';
|
|
7
|
-
import { parseSQSBusEvent } from '../helpers/event';
|
|
8
|
-
import { DependencyContainer } from '../services/DependencyContainer';
|
|
9
|
-
import { EVENT } from '../types/event';
|
|
10
|
-
|
|
11
|
-
export type EventDetailBody = {
|
|
12
|
-
mutualIds: string[];
|
|
13
|
-
byEntityType: Entity;
|
|
14
|
-
byEntityId: string;
|
|
15
|
-
entityType: Entity;
|
|
16
|
-
field: string;
|
|
17
|
-
publishedAt: string;
|
|
18
|
-
customContext?: Record<string, unknown>;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const container = new DependencyContainer();
|
|
22
|
-
|
|
23
|
-
const processEntities = async (
|
|
24
|
-
entityIds: string[],
|
|
25
|
-
action: (id: string) => Promise<void>,
|
|
26
|
-
) => Promise.allSettled(entityIds.map(action));
|
|
27
|
-
|
|
28
|
-
export const handler = async (ev: SQSEvent) => {
|
|
29
|
-
const batchItemFailures: SQSBatchItemFailure[] = [];
|
|
30
|
-
const { entityRepository, mutualRepository, publishEvent } = container;
|
|
31
|
-
|
|
32
|
-
await Promise.allSettled(
|
|
33
|
-
ev.Records.map(async (record) => {
|
|
34
|
-
const errorContext: Record<string, unknown> = {};
|
|
35
|
-
const body = parseSQSBusEvent<EventDetailBody>(record.body);
|
|
36
|
-
const { detail } = body;
|
|
37
|
-
const {
|
|
38
|
-
mutualIds,
|
|
39
|
-
byEntityType,
|
|
40
|
-
byEntityId,
|
|
41
|
-
entityType,
|
|
42
|
-
field,
|
|
43
|
-
publishedAt,
|
|
44
|
-
customContext,
|
|
45
|
-
} = detail;
|
|
46
|
-
errorContext.body = body;
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
// Validate if mutual configuration exists
|
|
50
|
-
const config =
|
|
51
|
-
EntityConfig[byEntityType]?.mutual?.mutualFields?.[field];
|
|
52
|
-
|
|
53
|
-
if (!config) {
|
|
54
|
-
throw new StandardError('INVALID_MUTUAL', 'Invalid mutual');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const mutualDataProcessor = config.mutualDataProcessor ?? (() => ({}));
|
|
58
|
-
|
|
59
|
-
// Create a lock to prevent concurrent modifications
|
|
60
|
-
await mutualRepository.createMutualLock({
|
|
61
|
-
byEntityType,
|
|
62
|
-
byEntityId,
|
|
63
|
-
entityType,
|
|
64
|
-
version: publishedAt,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// Fetch related entities in parallel
|
|
68
|
-
const [byEntity, mutuals] = await Promise.all([
|
|
69
|
-
entityRepository.getEntity(byEntityType, byEntityId),
|
|
70
|
-
mutualRepository.listEntitiesByEntity(
|
|
71
|
-
byEntityType,
|
|
72
|
-
byEntityId,
|
|
73
|
-
entityType,
|
|
74
|
-
),
|
|
75
|
-
]);
|
|
76
|
-
|
|
77
|
-
// Determine which entities were added, removed, or need updates
|
|
78
|
-
const existingEntityIds = new Set(mutuals.items.map((m) => m.entityId));
|
|
79
|
-
const newMutualIds = new Set(mutualIds ?? []);
|
|
80
|
-
|
|
81
|
-
const addedEntityIds = [...newMutualIds].filter(
|
|
82
|
-
(id) => !existingEntityIds.has(id),
|
|
83
|
-
);
|
|
84
|
-
const deletedEntityIds = [...existingEntityIds].filter(
|
|
85
|
-
(id) => !newMutualIds.has(id),
|
|
86
|
-
);
|
|
87
|
-
const toUpdateEntityIds = [...existingEntityIds].filter((id) =>
|
|
88
|
-
newMutualIds.has(id),
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
errorContext.existingEntityIds = [...existingEntityIds];
|
|
92
|
-
errorContext.addedEntityIds = addedEntityIds;
|
|
93
|
-
errorContext.deletedEntityIds = deletedEntityIds;
|
|
94
|
-
errorContext.toUpdateEntityIds = toUpdateEntityIds;
|
|
95
|
-
|
|
96
|
-
const addEntities = await processEntities(
|
|
97
|
-
addedEntityIds,
|
|
98
|
-
async (id) => {
|
|
99
|
-
const entity = await entityRepository.getEntity(entityType, id);
|
|
100
|
-
await mutualRepository.createMutual(
|
|
101
|
-
byEntityType,
|
|
102
|
-
byEntityId,
|
|
103
|
-
byEntity.data,
|
|
104
|
-
entityType,
|
|
105
|
-
id,
|
|
106
|
-
entity.data,
|
|
107
|
-
mutualDataProcessor(
|
|
108
|
-
mutualIds,
|
|
109
|
-
new Mutual(
|
|
110
|
-
byEntityType,
|
|
111
|
-
byEntityId,
|
|
112
|
-
byEntity.data,
|
|
113
|
-
entityType,
|
|
114
|
-
id,
|
|
115
|
-
entity.data,
|
|
116
|
-
{},
|
|
117
|
-
),
|
|
118
|
-
customContext,
|
|
119
|
-
),
|
|
120
|
-
{
|
|
121
|
-
ConditionExpression:
|
|
122
|
-
'attribute_not_exists(#mutualUpdatedAt) OR #mutualUpdatedAt < :publishedAt',
|
|
123
|
-
ExpressionAttributeNames: {
|
|
124
|
-
'#mutualUpdatedAt': 'mutualUpdatedAt',
|
|
125
|
-
},
|
|
126
|
-
ExpressionAttributeValues: {
|
|
127
|
-
':publishedAt': { S: publishedAt },
|
|
128
|
-
},
|
|
129
|
-
createAndUpdateDatetime: new Date(publishedAt),
|
|
130
|
-
},
|
|
131
|
-
);
|
|
132
|
-
},
|
|
133
|
-
);
|
|
134
|
-
|
|
135
|
-
const deleteEntities = await processEntities(
|
|
136
|
-
deletedEntityIds,
|
|
137
|
-
async (id) => {
|
|
138
|
-
await mutualRepository.deleteMutual(
|
|
139
|
-
byEntityType,
|
|
140
|
-
byEntityId,
|
|
141
|
-
entityType,
|
|
142
|
-
id,
|
|
143
|
-
{
|
|
144
|
-
ConditionExpression:
|
|
145
|
-
'attribute_exists(PK) AND #mutualUpdatedAt < :publishedAt',
|
|
146
|
-
ExpressionAttributeNames: {
|
|
147
|
-
'#mutualUpdatedAt': 'mutualUpdatedAt',
|
|
148
|
-
},
|
|
149
|
-
ExpressionAttributeValues: {
|
|
150
|
-
':publishedAt': { S: publishedAt },
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
);
|
|
154
|
-
},
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
const updateEntities = await processEntities(
|
|
158
|
-
toUpdateEntityIds,
|
|
159
|
-
async (id) => {
|
|
160
|
-
await mutualRepository.updateMutual(
|
|
161
|
-
byEntityType,
|
|
162
|
-
byEntityId,
|
|
163
|
-
entityType,
|
|
164
|
-
id,
|
|
165
|
-
{
|
|
166
|
-
mutualData: mutualDataProcessor(
|
|
167
|
-
mutualIds,
|
|
168
|
-
new Mutual(
|
|
169
|
-
byEntityType,
|
|
170
|
-
byEntityId,
|
|
171
|
-
byEntity.data,
|
|
172
|
-
entityType,
|
|
173
|
-
id,
|
|
174
|
-
{},
|
|
175
|
-
{},
|
|
176
|
-
),
|
|
177
|
-
customContext,
|
|
178
|
-
),
|
|
179
|
-
mutualUpdatedAt: publishedAt,
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
ConditionExpression:
|
|
183
|
-
'attribute_exists(PK) AND #mutualUpdatedAt < :publishedAt',
|
|
184
|
-
ExpressionAttributeNames: {
|
|
185
|
-
'#mutualUpdatedAt': 'mutualUpdatedAt',
|
|
186
|
-
},
|
|
187
|
-
ExpressionAttributeValues: {
|
|
188
|
-
':publishedAt': { S: publishedAt },
|
|
189
|
-
},
|
|
190
|
-
},
|
|
191
|
-
);
|
|
192
|
-
},
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
errorContext.results = { addEntities, deleteEntities, updateEntities };
|
|
196
|
-
|
|
197
|
-
// release lock
|
|
198
|
-
await mutualRepository.deleteMutualLock({
|
|
199
|
-
byEntityType,
|
|
200
|
-
byEntityId,
|
|
201
|
-
entityType,
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
// Check for unprocessable errors in processing results
|
|
205
|
-
if (
|
|
206
|
-
[...addEntities, ...deleteEntities, ...updateEntities].some(
|
|
207
|
-
(res) =>
|
|
208
|
-
res.status === 'rejected' &&
|
|
209
|
-
!(
|
|
210
|
-
res.reason instanceof TransactionCanceledException ||
|
|
211
|
-
(res.reason instanceof StandardError &&
|
|
212
|
-
res.reason.code === 'MUTUAL_NOT_FOUND')
|
|
213
|
-
),
|
|
214
|
-
)
|
|
215
|
-
) {
|
|
216
|
-
throw new StandardError(
|
|
217
|
-
'MUTUAL_PROCESSOR_ERROR',
|
|
218
|
-
'Mutual processor error',
|
|
219
|
-
null,
|
|
220
|
-
errorContext,
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
await publishEvent({
|
|
225
|
-
event: EVENT.CORE.ENTITY_MUTUAL_PROCESSED,
|
|
226
|
-
payload: {
|
|
227
|
-
byEntityType,
|
|
228
|
-
byEntityId,
|
|
229
|
-
entityType,
|
|
230
|
-
field,
|
|
231
|
-
mutualIds,
|
|
232
|
-
publishedAt,
|
|
233
|
-
},
|
|
234
|
-
});
|
|
235
|
-
} catch (err) {
|
|
236
|
-
console.error(
|
|
237
|
-
'=====MUTUAL_PROCESSOR_ERROR=====',
|
|
238
|
-
err,
|
|
239
|
-
JSON.stringify({ errorContext }, null, 2),
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
// Release the lock to avoid deadlocks
|
|
243
|
-
await mutualRepository.deleteMutualLock({
|
|
244
|
-
byEntityType,
|
|
245
|
-
byEntityId,
|
|
246
|
-
entityType,
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
if (
|
|
250
|
-
err instanceof StandardError &&
|
|
251
|
-
['INVALID_MUTUAL', 'MUTUAL_LOCK_CONFLICT'].includes(err.code)
|
|
252
|
-
) {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
batchItemFailures.push({ itemIdentifier: record.messageId });
|
|
257
|
-
}
|
|
258
|
-
}),
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
return { batchItemFailures };
|
|
262
|
-
};
|