@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.
Files changed (69) hide show
  1. package/dist/mock/monorise/chapter.d.ts +1 -1
  2. package/dist/mock/monorise/course.d.ts +1 -1
  3. package/dist/mock/monorise/learning-journey-config.d.ts +1 -1
  4. package/dist/mock/monorise/module.d.ts +1 -1
  5. package/dist/mock/monorise/organization.d.ts +1 -1
  6. package/dist/services/entity.service.d.ts +2 -2
  7. package/dist/services/entity.service.d.ts.map +1 -1
  8. package/package.json +4 -1
  9. package/configs/service.config.ts +0 -14
  10. package/constants/table.ts +0 -3
  11. package/controllers/entity/create-entity.controller.ts +0 -51
  12. package/controllers/entity/delete-entity.controller.ts +0 -35
  13. package/controllers/entity/entity.http +0 -62
  14. package/controllers/entity/get-entity.controller.ts +0 -33
  15. package/controllers/entity/list-entities.controller.ts +0 -69
  16. package/controllers/entity/update-entity.controller.ts +0 -56
  17. package/controllers/entity/upsert-entity.controller.ts +0 -97
  18. package/controllers/mutual/create-mutual.controller.ts +0 -89
  19. package/controllers/mutual/delete-mutual.controller.ts +0 -40
  20. package/controllers/mutual/get-mutual.controller.ts +0 -38
  21. package/controllers/mutual/list-entities-by-entity.controller.ts +0 -76
  22. package/controllers/mutual/mutual.http +0 -88
  23. package/controllers/mutual/update-mutual.controller.ts +0 -50
  24. package/controllers/setupRoutes.ts +0 -73
  25. package/controllers/tag/list-tags.controller.ts +0 -57
  26. package/data/DbUtils.ts +0 -40
  27. package/data/Entity.ts +0 -499
  28. package/data/EventUtils.ts +0 -47
  29. package/data/FileObject.ts +0 -16
  30. package/data/Mutual.ts +0 -779
  31. package/data/ProjectionExpression.ts +0 -8
  32. package/data/Tag.ts +0 -470
  33. package/data/abstract/Item.base.ts +0 -19
  34. package/data/abstract/Repository.base.ts +0 -92
  35. package/errors/api-error.ts +0 -39
  36. package/errors/extendable-error.ts +0 -35
  37. package/errors/standard-error.ts +0 -29
  38. package/helpers/dependencies.ts +0 -10
  39. package/helpers/event.ts +0 -85
  40. package/helpers/fromLastKeyQuery.ts +0 -11
  41. package/helpers/sleep.ts +0 -1
  42. package/helpers/toLastKeyResponse.ts +0 -11
  43. package/index.ts +0 -23
  44. package/middlewares/entity-type-check.ts +0 -20
  45. package/middlewares/mutual-type-check.ts +0 -26
  46. package/mock/entity.ts +0 -12
  47. package/mock/monorise/admin.ts +0 -35
  48. package/mock/monorise/chapter.ts +0 -94
  49. package/mock/monorise/course.ts +0 -149
  50. package/mock/monorise/index.ts +0 -143
  51. package/mock/monorise/learner.ts +0 -66
  52. package/mock/monorise/learning-activity.ts +0 -62
  53. package/mock/monorise/learning-journey-config.ts +0 -34
  54. package/mock/monorise/module.ts +0 -108
  55. package/mock/monorise/organization.ts +0 -63
  56. package/mock/monorise/reference.ts +0 -28
  57. package/mock/monorise/video.ts +0 -36
  58. package/processors/create-entity-processor.ts +0 -55
  59. package/processors/mutual-processor.ts +0 -262
  60. package/processors/prejoin-processor.ts +0 -264
  61. package/processors/replication-processor.ts +0 -261
  62. package/processors/tag-processor.ts +0 -174
  63. package/services/DependencyContainer.ts +0 -208
  64. package/services/entity-service-lifecycle.ts +0 -41
  65. package/services/entity.service.ts +0 -201
  66. package/services/mutual.service.ts +0 -285
  67. package/tsconfig.json +0 -116
  68. package/types/entity.type.ts +0 -62
  69. package/types/event.ts +0 -84
package/data/Entity.ts DELETED
@@ -1,499 +0,0 @@
1
- import {
2
- type AttributeValue,
3
- ConditionalCheckFailedException,
4
- type DynamoDB,
5
- type QueryCommandInput,
6
- type TransactWriteItem,
7
- type UpdateItemCommandInput,
8
- } from '@aws-sdk/client-dynamodb';
9
- import { marshall, unmarshall } from '@aws-sdk/util-dynamodb';
10
- import type { EntitySchemaMap, Entity as EntityType } from '@monorise/base';
11
- import { ulid } from 'ulid';
12
- import {
13
- EmailAuthEnabledEntities,
14
- EntityConfig,
15
- } from '#/lambda-layer/monorise';
16
- import { StandardError } from '../errors/standard-error';
17
- import type { ProjectionExpressionValues } from './ProjectionExpression';
18
- import { Item } from './abstract/Item.base';
19
- import { Repository } from './abstract/Repository.base';
20
-
21
- export class Entity<T extends EntityType> extends Item {
22
- public fullId: string;
23
-
24
- constructor(
25
- public entityType: T,
26
- public entityId?: string,
27
- public data: Partial<EntitySchemaMap[T]> = {},
28
- private _createdAt?: Date,
29
- private _updatedAt?: Date,
30
- ) {
31
- super();
32
- this.fullId = this.pk;
33
- }
34
-
35
- static fromItem<T extends EntityType>(
36
- item?: Record<string, AttributeValue>,
37
- ): Entity<T> {
38
- if (!item)
39
- throw new StandardError('ENTITY_IS_UNDEFINED', 'Entity item empty');
40
-
41
- const parsedItem = unmarshall(item);
42
-
43
- return new Entity<T>(
44
- parsedItem.entityType,
45
- parsedItem.entityId,
46
- parsedItem.data,
47
- parsedItem.createdAt ? new Date(parsedItem.createdAt) : undefined,
48
- parsedItem.updatedAt ? new Date(parsedItem.updatedAt) : undefined,
49
- );
50
- }
51
-
52
- get pk(): string {
53
- return `${this.entityType}#${this.entityId}`;
54
- }
55
-
56
- get sk(): string {
57
- return '#METADATA#';
58
- }
59
-
60
- get listActionKey(): string {
61
- return `LIST#${this.entityType}`;
62
- }
63
-
64
- get emailKeys(): Record<string, AttributeValue> {
65
- return {
66
- PK: {
67
- S: `EMAIL#${(this.data as Partial<EntitySchemaMap[T]> & { email: string }).email}`,
68
- },
69
- SK: {
70
- S: `${this.entityType}#${this.entityId}`,
71
- },
72
- };
73
- }
74
-
75
- get createdAt(): string | undefined {
76
- return this._createdAt?.toISOString();
77
- }
78
-
79
- get updatedAt(): string | undefined {
80
- return this._updatedAt?.toISOString();
81
- }
82
-
83
- toItem(): Record<string, AttributeValue> {
84
- return {
85
- ...marshall(this.toJSON(), { removeUndefinedValues: true }),
86
- ...this.keys(),
87
- };
88
- }
89
-
90
- toJSON(): Record<string, unknown> {
91
- return {
92
- entityType: this.entityType,
93
- entityId: this.entityId,
94
- data: this.data,
95
- createdAt: this.createdAt,
96
- updatedAt: this.updatedAt,
97
- };
98
- }
99
- }
100
-
101
- export class EntityRepository extends Repository {
102
- constructor(
103
- private readonly TABLE_NAME: string,
104
- private readonly dynamodbClient: DynamoDB,
105
- ) {
106
- super();
107
- }
108
-
109
- async listEntities<T extends EntityType>({
110
- entityType,
111
- limit, // if this is not set, it will return all items
112
- between,
113
- options = {},
114
- }: {
115
- entityType: T;
116
- limit?: number;
117
- between?: {
118
- start: string;
119
- end: string;
120
- };
121
- options?: {
122
- lastKey?: Record<string, AttributeValue>;
123
- ProjectionExpression?: ProjectionExpressionValues;
124
- };
125
- }): Promise<{
126
- items: Entity<T>[];
127
- totalCount?: number;
128
- lastKey?: Record<string, AttributeValue>;
129
- }> {
130
- const entity = new Entity(entityType);
131
- // when query for records that SK are between provided start and end
132
- const expression: Pick<
133
- QueryCommandInput,
134
- | 'KeyConditionExpression'
135
- | 'ExpressionAttributeNames'
136
- | 'ExpressionAttributeValues'
137
- > = between
138
- ? {
139
- KeyConditionExpression:
140
- '#PK = :PK and #SK between :SKStart and :SKEnd',
141
- ExpressionAttributeNames: {
142
- '#PK': 'PK',
143
- '#SK': 'SK',
144
- },
145
- ExpressionAttributeValues: {
146
- ':PK': {
147
- S: entity.listActionKey,
148
- },
149
- ':SKStart': {
150
- S: `${entityType}#${between.start}`,
151
- },
152
- ':SKEnd': {
153
- S: `${entityType}#${between.end}`,
154
- },
155
- },
156
- }
157
- : {
158
- KeyConditionExpression: '#PK = :PK',
159
- ExpressionAttributeNames: {
160
- '#PK': 'PK',
161
- },
162
- ExpressionAttributeValues: {
163
- ':PK': {
164
- S: entity.listActionKey,
165
- },
166
- },
167
- };
168
-
169
- const defaultListQuery: QueryCommandInput = {
170
- TableName: this.TABLE_NAME,
171
- Limit: limit,
172
- ScanIndexForward: false,
173
- ProjectionExpression: options?.ProjectionExpression,
174
- ...expression,
175
- };
176
-
177
- let lastKey = options.lastKey;
178
- let items: Record<string, AttributeValue>[] = [];
179
- let remainingCount = limit ?? 0;
180
- do {
181
- const resp = await this.dynamodbClient.query({
182
- ...defaultListQuery,
183
- ...(remainingCount && { Limit: remainingCount }),
184
- ...(lastKey && {
185
- ExclusiveStartKey: lastKey,
186
- }),
187
- });
188
- items = items.concat(resp.Items ?? []);
189
-
190
- lastKey = resp.LastEvaluatedKey;
191
-
192
- if (limit) {
193
- remainingCount = remainingCount - (resp.Items?.length ?? 0);
194
- }
195
- } while (
196
- // limit is given, haven't reach limit, and there are still items to retrieve
197
- (limit && remainingCount && lastKey) ||
198
- // no limit is given and there are still items to retrieve
199
- (!limit && lastKey)
200
- );
201
-
202
- return {
203
- items: (items || []).map(Entity.fromItem<T>),
204
- totalCount: items.length,
205
- lastKey,
206
- };
207
- }
208
-
209
- async getEntity<T extends EntityType>(
210
- entityType: T,
211
- entityId: string,
212
- ): Promise<Entity<T>> {
213
- const entity = new Entity(entityType, entityId);
214
- const resp = await this.dynamodbClient.getItem({
215
- TableName: this.TABLE_NAME,
216
- Key: entity.keys(),
217
- });
218
-
219
- return Entity.fromItem(resp.Item);
220
- }
221
-
222
- async getEntityByEmail<T extends EntityType>(
223
- entityType: T,
224
- email: string,
225
- ): Promise<Entity<T>> {
226
- const resp = await this.dynamodbClient.query({
227
- TableName: this.TABLE_NAME,
228
- KeyConditionExpression: '#PK = :PK and begins_with(#SK, :SK)',
229
- ExpressionAttributeNames: {
230
- '#PK': 'PK',
231
- '#SK': 'SK',
232
- },
233
- ExpressionAttributeValues: {
234
- ':PK': { S: `EMAIL#${email}` },
235
- ':SK': { S: entityType as unknown as string },
236
- },
237
- });
238
-
239
- return Entity.fromItem(resp.Items?.[0]);
240
- }
241
-
242
- async getEmailAvailability<T extends EntityType>(
243
- entityType: T,
244
- email: string,
245
- ): Promise<void> {
246
- const resp = await this.dynamodbClient.query({
247
- TableName: this.TABLE_NAME,
248
- KeyConditionExpression: '#PK = :PK and begins_with(#SK, :SK)',
249
- ExpressionAttributeNames: {
250
- '#PK': 'PK',
251
- '#SK': 'SK',
252
- },
253
- ExpressionAttributeValues: {
254
- ':PK': { S: `EMAIL#${email}` },
255
- ':SK': { S: entityType as unknown as string },
256
- },
257
- });
258
-
259
- if (resp.Items?.[0]) {
260
- throw new StandardError('EMAIL_EXISTS', 'Email already exists');
261
- }
262
-
263
- return;
264
- }
265
-
266
- createEntityTransactItems<T extends EntityType>(
267
- entity: Entity<T>,
268
- opts?: {
269
- mutualId?: string;
270
- },
271
- ): TransactWriteItem[] {
272
- const TransactItems: TransactWriteItem[] = [
273
- {
274
- Put: {
275
- TableName: this.TABLE_NAME,
276
- ConditionExpression: 'attribute_not_exists(PK)',
277
- Item: {
278
- ...entity.toItem(),
279
- ...(opts?.mutualId && {
280
- R2PK: { S: opts.mutualId },
281
- R2SK: { S: entity.pk },
282
- }),
283
- },
284
- },
285
- },
286
- {
287
- Put: {
288
- TableName: this.TABLE_NAME,
289
- ConditionExpression: 'attribute_not_exists(PK)',
290
- Item: {
291
- ...entity.toItem(),
292
- PK: { S: entity.listActionKey },
293
- SK: entity.keys().PK,
294
- R1PK: entity.keys().PK,
295
- R1SK: { S: entity.listActionKey },
296
- },
297
- },
298
- },
299
- ];
300
-
301
- // currently when detected it's an account, create email record
302
- // TODO: Future improvement, if we introduce multiple ways to register/login,
303
- // here we should also check if entity has the respective auth method defined
304
- // in the config file
305
- if (EmailAuthEnabledEntities.includes(entity.entityType)) {
306
- TransactItems.push({
307
- Put: {
308
- TableName: this.TABLE_NAME,
309
- ConditionExpression: 'attribute_not_exists(PK)',
310
- Item: {
311
- ...entity.toItem(),
312
- ...entity.emailKeys,
313
- R1PK: entity.emailKeys.SK,
314
- R1SK: entity.emailKeys.PK,
315
- },
316
- },
317
- });
318
- }
319
-
320
- return TransactItems;
321
- }
322
-
323
- async createEntity<T extends EntityType>(
324
- entityType: T,
325
- entityPayload: EntitySchemaMap[T],
326
- entityId?: string,
327
- opts?: {
328
- createAndUpdateDatetime?: Date;
329
- mutualId?: string;
330
- },
331
- ): Promise<Entity<T>> {
332
- const currentDatetime = opts?.createAndUpdateDatetime ?? new Date();
333
- const entity = new Entity<T>(
334
- entityType,
335
- entityId || ulid(),
336
- entityPayload,
337
- currentDatetime,
338
- currentDatetime,
339
- );
340
- const TransactItems = this.createEntityTransactItems<T>(entity, {
341
- mutualId: opts?.mutualId,
342
- });
343
-
344
- await this.dynamodbClient.transactWriteItems({ TransactItems });
345
-
346
- return entity;
347
- }
348
-
349
- async upsertEntity<T extends EntityType>(
350
- entityType: T,
351
- entityId: string,
352
- payload: Partial<EntitySchemaMap[T]>,
353
- ): Promise<Entity<T>> {
354
- const toUpdateExpressions = this.toUpdate({
355
- entityType,
356
- entityId,
357
- data: payload,
358
- });
359
- const params: UpdateItemCommandInput = {
360
- TableName: this.TABLE_NAME,
361
- ReturnValues: 'ALL_NEW',
362
- Key: new Entity(entityType, entityId).keys(),
363
- UpdateExpression: toUpdateExpressions.UpdateExpression,
364
- ExpressionAttributeNames: {
365
- ...toUpdateExpressions.ExpressionAttributeNames,
366
- },
367
- ExpressionAttributeValues: {
368
- ...toUpdateExpressions.ExpressionAttributeValues,
369
- },
370
- };
371
-
372
- const resp = await this.dynamodbClient.updateItem(params);
373
- const updatedEntity = Entity.fromItem<T>(resp.Attributes);
374
- return updatedEntity;
375
- }
376
-
377
- async updateEntity<T extends EntityType>(
378
- entityType: T,
379
- entityId: string,
380
- toUpdate: {
381
- data: Partial<EntitySchemaMap[T]>;
382
- updatedAt?: string;
383
- },
384
- opts?: {
385
- ConditionExpression: string;
386
- ExpressionAttributeNames: Record<string, string>;
387
- ExpressionAttributeValues: Record<string, AttributeValue>;
388
- },
389
- ): Promise<Entity<T>> {
390
- try {
391
- const currentDatetime = new Date().toISOString();
392
- const toUpdateExpressions = this.toUpdate({
393
- updatedAt: currentDatetime,
394
- ...toUpdate,
395
- });
396
- const params: UpdateItemCommandInput = {
397
- TableName: this.TABLE_NAME,
398
- ReturnValues: 'ALL_NEW',
399
- Key: new Entity(entityType, entityId).keys(),
400
- ConditionExpression:
401
- opts?.ConditionExpression || 'attribute_exists(PK)',
402
- UpdateExpression: toUpdateExpressions.UpdateExpression,
403
- ExpressionAttributeNames: {
404
- ...toUpdateExpressions.ExpressionAttributeNames,
405
- ...opts?.ExpressionAttributeNames,
406
- },
407
- ExpressionAttributeValues: {
408
- ...toUpdateExpressions.ExpressionAttributeValues,
409
- ...opts?.ExpressionAttributeValues,
410
- },
411
- };
412
-
413
- const resp = await this.dynamodbClient.updateItem(params);
414
- const updatedEntity = Entity.fromItem<T>(resp.Attributes);
415
- return updatedEntity;
416
- } catch (err) {
417
- if (err instanceof ConditionalCheckFailedException) {
418
- throw new StandardError('ENTITY_NOT_FOUND', 'Entity not found', err, {
419
- entityId,
420
- toUpdate,
421
- });
422
- }
423
-
424
- throw err;
425
- }
426
- }
427
-
428
- async deleteEntity<T extends EntityType>(
429
- entityType: T,
430
- entityId: string,
431
- ): Promise<void> {
432
- try {
433
- const entity = new Entity(entityType, entityId);
434
-
435
- await this.dynamodbClient.deleteItem({
436
- TableName: this.TABLE_NAME,
437
- Key: entity.keys(),
438
- ConditionExpression: 'attribute_exists(PK)',
439
- });
440
- } catch (err) {
441
- if (err instanceof ConditionalCheckFailedException) {
442
- throw new StandardError('ENTITY_NOT_FOUND', 'Entity not found', err, {
443
- entityId,
444
- });
445
- }
446
-
447
- throw err;
448
- }
449
- }
450
-
451
- async queryEntities<T extends EntityType>(
452
- entityType: T,
453
- query: string,
454
- ): Promise<{
455
- items: Entity<T>[];
456
- totalCount?: number;
457
- filteredCount?: number;
458
- }> {
459
- const results: { items: Entity<T>[]; totalCount: number } = {
460
- items: [],
461
- totalCount: 0,
462
- };
463
- // let regex be empty if its invalid (eg. +)
464
- let queryRegex = /(?:)/;
465
- try {
466
- queryRegex = new RegExp(query.toLowerCase());
467
- } catch (err) {
468
- return results;
469
- }
470
-
471
- const listResults = await this.listEntities<T>({
472
- entityType,
473
- });
474
- results.items.push(...listResults.items);
475
- results.totalCount += listResults.totalCount || 0;
476
-
477
- const filteredItems: Entity<T>[] = [];
478
-
479
- const { searchableFields } = EntityConfig[entityType];
480
-
481
- for (const item of results.items) {
482
- const searchTerm = (searchableFields ?? [])
483
- .map((field) =>
484
- (item.data as Record<string, any>)[field]?.toLowerCase(),
485
- )
486
- .join(' ');
487
- const isMatched = queryRegex.test(searchTerm);
488
- if (isMatched) {
489
- filteredItems.push(item);
490
- }
491
- }
492
-
493
- return {
494
- items: filteredItems,
495
- totalCount: results.totalCount,
496
- filteredCount: filteredItems.length,
497
- };
498
- }
499
- }
@@ -1,47 +0,0 @@
1
- import type { Entity as EntityType } from '@monorise/base';
2
- import { EntityConfig } from '#/lambda-layer/monorise';
3
- import type { publishEvent as publishEventType } from '../helpers/event';
4
- import { EVENT } from '../types/event';
5
- import type { Entity } from './Entity';
6
-
7
- type PublishEventProps<T extends EntityType> = {
8
- entity: Entity<T>;
9
- mutualPayload: Record<string, any>;
10
- };
11
-
12
- export class EventUtils {
13
- constructor(private publishEvent: typeof publishEventType) {}
14
-
15
- // Always when create entity, this must be called following, to make sure mutual data processor will be called
16
- publishCreateMutualsEvent = async <T extends EntityType>({
17
- entity,
18
- mutualPayload,
19
- }: PublishEventProps<T>) => {
20
- const publishEventPromises = [];
21
- for (const [fieldKey, config] of Object.entries(
22
- EntityConfig[entity.entityType].mutual?.mutualFields || {},
23
- )) {
24
- const toMutualIds = config.toMutualIds;
25
- const mutualPayloadByFieldKey = mutualPayload[fieldKey];
26
- if (!mutualPayloadByFieldKey) continue;
27
-
28
- publishEventPromises.push(
29
- this.publishEvent({
30
- event: EVENT.CORE.ENTITY_MUTUAL_TO_CREATE,
31
- payload: {
32
- byEntityType: entity.entityType,
33
- byEntityId: entity.entityId,
34
- entityType: config.entityType,
35
- field: fieldKey,
36
- mutualIds: toMutualIds
37
- ? toMutualIds(mutualPayloadByFieldKey)
38
- : mutualPayloadByFieldKey,
39
- customContext: toMutualIds ? mutualPayloadByFieldKey : {},
40
- publishedAt: entity.updatedAt || new Date().toISOString(),
41
- },
42
- }),
43
- );
44
- }
45
- await Promise.allSettled(publishEventPromises);
46
- };
47
- }
@@ -1,16 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- export const fileObjectSchema = z.object({
4
- name: z.string(),
5
- url: z.string(),
6
- });
7
-
8
- export const fileSchema = z.object({
9
- uid: z.string(),
10
- name: z.string(),
11
- regular: fileObjectSchema,
12
- thumbnail: fileObjectSchema.optional(),
13
- jobId: z.string().optional(),
14
- });
15
-
16
- export type File = z.infer<typeof fileSchema>;