Skip to content

Commit fda275d

Browse files
authored
Extensions (#60)
* Enhance event handling in AbstractServiceSchema with before and after event names for improved logging and extensibility * Add Extensions module and DTOs for extension management and validation * Skip processing of lifecycle sources with trigger-based rules in LifecycleService * Add jsondiffpatch and microdiff dependencies to enhance diffing capabilities * Remove jsondiffpatch dependency from package.json * Implement auditing functionality with history tracking and agent reference in the Audits module * Add history tracking plugin to Agents schema for auditing purposes * Refactor metadata structure in history plugin to simplify createdBy and createdAt fields * Remove console log from post-save hook in history plugin * Add search filter options and schema decorators with tests for filtering functionality * Refactor package dependencies and update import paths for restools; add DTO and ObjectId validation pipes * feat: implement custom exceptions for factory drive module * refactor: improve lifecycle service cron job handling and simplify trigger logic * refactor: remove unused import from config.ts
1 parent b9d139b commit fda275d

File tree

64 files changed

+3342
-1019
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+3342
-1019
lines changed

extensions/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*
2+
!.gitignore

package.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@
4646
"start:debug:webstorm": "node $NODE_DEBUG_OPTION -r ts-node/register -r tsconfig-paths/register src/main.ts"
4747
},
4848
"dependencies": {
49+
"@aws-sdk/client-s3": ">=3.600.0",
50+
"@aws-sdk/s3-request-presigner": "^3.600.0",
4951
"@nestjs-modules/ioredis": "^2.0.2",
5052
"@nestjs-modules/mailer": "^2.0.2",
5153
"@nestjs/axios": "^4.0.1",
@@ -60,9 +62,6 @@
6062
"@nestjs/platform-express": "^10.4.8",
6163
"@nestjs/schedule": "^6.0.0",
6264
"@nestjs/swagger": "^8.0.7",
63-
"@the-software-compagny/nestjs_module_factorydrive": "^1.1.5",
64-
"@the-software-compagny/nestjs_module_factorydrive-s3": "^1.0.1",
65-
"@the-software-compagny/nestjs_module_restools": "^0.0.11",
6665
"ajv": "^8.16.0",
6766
"ajv-errors": "^3.0.0",
6867
"ajv-formats": "^3.0.1",
@@ -74,19 +73,24 @@
7473
"class-transformer": "^0.5.1",
7574
"class-validator": "^0.14.1",
7675
"cookie-parser": "^1.4.6",
76+
"dayjs": "^1.11.18",
7777
"fast-password-entropy": "^1.1.1",
78+
"fs-extra": "^11.3.2",
7879
"glob": "^11.0.0",
7980
"handlebars": "^4.7.8",
8081
"helmet": "^7.1.0",
8182
"hibp": "^14.1.2",
8283
"ioredis": "^5.4.1",
84+
"is-plain-object": "^5.0.0",
8385
"loglevel": "^1.9.1",
8486
"lru-cache": "^11.0.2",
87+
"microdiff": "^1.5.0",
8588
"mjml": "^4.15.3",
8689
"mongoose": "^8.9.5",
8790
"nest-commander": "^3.13.0",
8891
"nest-winston": "^1.10.0",
8992
"nestjs-request-context": "^3.0.0",
93+
"node-exceptions": "^4.0.1",
9094
"nodemailer": "^6.9.16",
9195
"ora": "^8.1.0",
9296
"passport": "^0.7.0",
@@ -171,4 +175,4 @@
171175
"downloadUrl": "https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-debian10-5.0.22.tgz"
172176
}
173177
}
174-
}
178+
}

src/_common/abstracts/abstract.service.schema.ts

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { ServiceSchemaInterface } from './interfaces/service.schema.interface';
1818
import { AbstractSchema } from './schemas/abstract.schema';
1919
import mongodb from 'mongodb';
2020
import { omit } from 'radash';
21+
import { cp } from 'fs';
2122

2223
@Injectable()
2324
export abstract class AbstractServiceSchema extends AbstractService implements ServiceSchemaInterface {
@@ -38,9 +39,10 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
3839
options?: QueryOptions<T> | null | undefined,
3940
): Promise<Query<Array<T>, T, any, T>[]> {
4041
if (this.eventEmitter) {
42+
const eventName = [this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeFind'].join(EventEmitterSeparator)
4143
const beforeEvents = await this.eventEmitter?.emitAsync(
42-
[this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeFind'].join(EventEmitterSeparator),
43-
{ filter, projection, options },
44+
eventName,
45+
{ filter, projection, options, eventName },
4446
)
4547
for (const beforeEvent of beforeEvents) {
4648
if (beforeEvent?.stop) throw beforeEvent?.stop
@@ -55,9 +57,10 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
5557

5658
public async count<T extends AbstractSchema | Document>(filter?: FilterQuery<T>, options?: (mongodb.CountOptions & MongooseBaseQueryOptions<T>) | null): Promise<number> {
5759
if (this.eventEmitter) {
60+
const eventName = [this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeCount'].join(EventEmitterSeparator)
5861
const beforeEvents = await this.eventEmitter?.emitAsync(
59-
[this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeCount'].join(EventEmitterSeparator),
60-
{ filter, options },
62+
eventName,
63+
{ filter, options, eventName },
6164
)
6265
for (const beforeEvent of beforeEvents) {
6366
if (beforeEvent?.stop) throw beforeEvent?.stop
@@ -85,9 +88,10 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
8588
): Promise<[Array<T & Query<T, T, any, T>>, number]> {
8689
this.logger.debug(['findAndCount', JSON.stringify(Object.values(arguments))].join(' '))
8790
if (this.eventEmitter) {
91+
const beforeEventName = [this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeFindAndCount'].join(EventEmitterSeparator)
8892
const beforeEvents = await this.eventEmitter?.emitAsync(
89-
[this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeFindAndCount'].join(EventEmitterSeparator),
90-
{ filter, projection, options },
93+
beforeEventName,
94+
{ filter, projection, options, eventName: beforeEventName },
9195
)
9296
for (const beforeEvent of beforeEvents) {
9397
if (beforeEvent?.stop) throw beforeEvent?.stop
@@ -102,9 +106,10 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
102106
let data = await this._model.find<T & Query<T, T, any, T>>(filter, projection, options).exec()
103107

104108
if (this.eventEmitter) {
109+
const eventName = [this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'afterFindAndCount'].join(EventEmitterSeparator)
105110
const afterEvents = await this.eventEmitter?.emitAsync(
106-
[this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'afterFindAndCount'].join(EventEmitterSeparator),
107-
{ data, count },
111+
eventName,
112+
{ data, count, eventName },
108113
)
109114
for (const afterEvent of afterEvents) {
110115
if (afterEvent?.data) data = { ...data, ...afterEvent.data }
@@ -123,9 +128,10 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
123128
this.logger.debug(['findById', JSON.stringify(Object.values(arguments))].join(' '))
124129

125130
if (this.eventEmitter) {
131+
const beforeEventName = [this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeFindById'].join(EventEmitterSeparator)
126132
const beforeEvents = await this.eventEmitter?.emitAsync(
127-
[this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeFindById'].join(EventEmitterSeparator),
128-
{ _id, projection, options },
133+
beforeEventName,
134+
{ _id, projection, options, eventName: beforeEventName },
129135
)
130136
for (const beforeEvent of beforeEvents) {
131137
if (beforeEvent?.stop) throw beforeEvent?.stop
@@ -137,9 +143,10 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
137143
let data = await this._model.findById<Query<T | null, T, any, T>>(_id, projection, options).exec()
138144

139145
if (this.eventEmitter) {
146+
const eventName = [this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'afterFindById'].join(EventEmitterSeparator)
140147
const afterEvents = await this.eventEmitter?.emitAsync(
141-
[this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'afterFindById'].join(EventEmitterSeparator),
142-
{ data },
148+
eventName,
149+
{ data, eventName },
143150
)
144151
for (const afterEvent of afterEvents) {
145152
if (afterEvent?.data) data = { ...data, ...afterEvent.data }
@@ -160,9 +167,10 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
160167
): Promise<Query<T, T, any, T>> {
161168
this.logger.debug(['findOne', JSON.stringify(Object.values(arguments))].join(' '))
162169
if (this.eventEmitter) {
170+
const beforeEventName = [this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeFindOne'].join(EventEmitterSeparator)
163171
const beforeEvents = await this.eventEmitter?.emitAsync(
164-
[this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeFindOne'].join(EventEmitterSeparator),
165-
{ filter, projection, options },
172+
beforeEventName,
173+
{ filter, projection, options, eventName: beforeEventName },
166174
)
167175
for (const beforeEvent of beforeEvents) {
168176
if (beforeEvent?.stop) throw beforeEvent?.stop
@@ -177,9 +185,10 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
177185
throw new NotFoundException()
178186
}
179187
if (this.eventEmitter) {
188+
const eventName = [this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'afterFindOne'].join(EventEmitterSeparator)
180189
const afterEvents = await this.eventEmitter?.emitAsync(
181-
[this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'afterFindOne'].join(EventEmitterSeparator),
182-
{ data },
190+
eventName,
191+
{ data, eventName },
183192
)
184193
for (const afterEvent of afterEvents) {
185194
if (afterEvent?.data) data = { ...data, ...afterEvent.data }
@@ -198,9 +207,10 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
198207
})
199208
this.logger.debug(['create', JSON.stringify(logInfos)].join(' '))
200209
if (this.eventEmitter) {
210+
const beforeEventName = [this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeCreate'].join(EventEmitterSeparator)
201211
const beforeEvents = await this.eventEmitter?.emitAsync(
202-
[this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeCreate'].join(EventEmitterSeparator),
203-
{ data, options },
212+
beforeEventName,
213+
{ data, options, eventName: beforeEventName },
204214
)
205215
for (const beforeEvent of beforeEvents) {
206216
if (beforeEvent?.stop) throw beforeEvent?.stop
@@ -219,9 +229,10 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
219229
})
220230
let created = document.save(options)
221231
if (this.eventEmitter) {
232+
const eventName = [this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'afterCreate'].join(EventEmitterSeparator)
222233
const afterEvents = await this.eventEmitter?.emitAsync(
223-
[this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'afterCreate'].join(EventEmitterSeparator),
224-
{ created },
234+
eventName,
235+
{ created, eventName },
225236
)
226237
for (const afterEvent of afterEvents) {
227238
if (afterEvent?.created) created = { ...created, ...afterEvent.created }
@@ -244,9 +255,10 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
244255
})
245256
this.logger.debug(['update', JSON.stringify(logInfos)].join(' '))
246257
if (this.eventEmitter) {
258+
const beforeEventName = [this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeUpdate'].join(EventEmitterSeparator)
247259
const beforeEvents = await this.eventEmitter?.emitAsync(
248-
[this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeUpdate'].join(EventEmitterSeparator),
249-
{ _id, update, options },
260+
beforeEventName,
261+
{ _id, update, options, eventName: beforeEventName },
250262
)
251263
for (const beforeEvent of beforeEvents) {
252264
if (beforeEvent?.stop) throw beforeEvent?.stop
@@ -282,9 +294,10 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
282294
throw new NotFoundException()
283295
}
284296
if (this.eventEmitter) {
297+
const eventName = [this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'afterUpdate'].join(EventEmitterSeparator)
285298
const afterEvents = await this.eventEmitter?.emitAsync(
286-
[this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'afterUpdate'].join(EventEmitterSeparator),
287-
{ before, updated },
299+
eventName,
300+
{ before, updated, eventName },
288301
)
289302
for (const afterEvent of afterEvents) {
290303
if (afterEvent?.updated) updated = { ...updated, ...afterEvent.updated }
@@ -300,9 +313,10 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
300313
): Promise<ModifyResult<Query<T, T, any, T>>> {
301314
this.logger.debug(['upsert', JSON.stringify(Object.values(arguments))].join(' '));
302315
if (this.eventEmitter) {
316+
const beforeEventName = [this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeUpsert'].join(EventEmitterSeparator);
303317
const beforeEvents = await this.eventEmitter?.emitAsync(
304-
[this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeUpsert'].join(EventEmitterSeparator),
305-
{ filter, update, options },
318+
beforeEventName,
319+
{ filter, update, options, eventName: beforeEventName },
306320
);
307321
for (const beforeEvent of beforeEvents) {
308322
if (beforeEvent?.stop) throw beforeEvent?.stop;
@@ -338,9 +352,10 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
338352
.exec();
339353

340354
if (this.eventEmitter) {
355+
const eventName = [this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'afterUpsert'].join(EventEmitterSeparator)
341356
const afterEvents = await this.eventEmitter?.emitAsync(
342-
[this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'afterUpsert'].join(EventEmitterSeparator),
343-
{ result, before },
357+
eventName,
358+
{ result, before, eventName },
344359
);
345360
for (const afterEvent of afterEvents) {
346361
if (afterEvent?.result) result = { ...result, ...afterEvent.result };
@@ -358,9 +373,10 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
358373
public async delete<T extends AbstractSchema | Document>(_id: Types.ObjectId | any, options?: QueryOptions<T> | null | undefined): Promise<Query<T, T, any, T>> {
359374
this.logger.debug(['delete', JSON.stringify(Object.values(arguments))].join(' '))
360375
if (this.eventEmitter) {
376+
const beforeEventName = [this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeDelete'].join(EventEmitterSeparator)
361377
const beforeEvents = await this.eventEmitter?.emitAsync(
362-
[this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'beforeDelete'].join(EventEmitterSeparator),
363-
{ _id, options },
378+
beforeEventName,
379+
{ _id, options, eventName: beforeEventName },
364380
)
365381
for (const beforeEvent of beforeEvents) {
366382
if (beforeEvent?.stop) throw beforeEvent?.stop
@@ -374,9 +390,10 @@ export abstract class AbstractServiceSchema extends AbstractService implements S
374390
throw new NotFoundException()
375391
}
376392
if (this.eventEmitter) {
393+
const eventName = [this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'afterDelete'].join(EventEmitterSeparator)
377394
const afterEvents = await this.eventEmitter?.emitAsync(
378-
[this.moduleName.toLowerCase(), this.serviceName.toLowerCase(), 'service', 'afterDelete'].join(EventEmitterSeparator),
379-
{ before, deleted },
395+
eventName,
396+
{ before, deleted, eventName },
380397
)
381398
for (const afterEvent of afterEvents) {
382399
if (afterEvent?.deleted) deleted = { ...deleted, ...afterEvent.deleted }
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { RuntimeException } from 'node-exceptions'
2+
3+
export class AuthorizationRequiredException extends RuntimeException {
4+
public raw: Error
5+
6+
public constructor(err: Error, path: string) {
7+
super(`Unauthorized to access file ${path}\n${err.message}`, 500, 'E_AUTHORIZATION_REQUIRED')
8+
this.raw = err
9+
}
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { RuntimeException } from 'node-exceptions'
2+
3+
export class DriverNotSupportedException extends RuntimeException {
4+
public driver!: string
5+
6+
public static driver(name: string): DriverNotSupportedException {
7+
const exception = new this(`Driver ${name} is not supported`, 400)
8+
exception.driver = name
9+
return exception
10+
}
11+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { RuntimeException } from 'node-exceptions'
2+
3+
export class FileNotFoundException extends RuntimeException {
4+
public raw: Error
5+
6+
public constructor(err: Error, path: string) {
7+
super(`The file ${path} doesn't exist\n${err.message}`, 500, 'E_FILE_NOT_FOUND')
8+
this.raw = err
9+
}
10+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export * from './authorization-required.exception'
2+
export * from './driver-not-supported.exception'
3+
export * from './file-not-found.exception'
4+
export * from './invalid-config.exception'
5+
export * from './method-not-supported.exception'
6+
export * from './no-such-bucket.exception'
7+
export * from './permission-missing.exception'
8+
export * from './unknown.exception'
9+
export * from './wrong-key-path.exception'
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { RuntimeException } from 'node-exceptions'
2+
3+
export class InvalidConfigException extends RuntimeException {
4+
public static missingDiskName(): InvalidConfigException {
5+
return new this('Make sure to define a default disk name inside config file', 500, 'E_INVALID_CONFIG')
6+
}
7+
8+
public static missingDiskConfig(name: string): InvalidConfigException {
9+
return new this(`Make sure to define config for ${name} disk`, 500, 'E_INVALID_CONFIG')
10+
}
11+
12+
public static missingDiskDriver(name: string): InvalidConfigException {
13+
return new this(`Make sure to define driver for ${name} disk`, 500, 'E_INVALID_CONFIG')
14+
}
15+
16+
public static duplicateDiskName(name: string): InvalidConfigException {
17+
return new this(`A disk named ${name} is already defined`, 500, 'E_INVALID_CONFIG')
18+
}
19+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { RuntimeException } from 'node-exceptions'
2+
3+
export class MethodNotSupportedException extends RuntimeException {
4+
public constructor(name: string, driver: string) {
5+
super(`Method ${name} is not supported for the driver ${driver}`, 500, 'E_METHOD_NOT_SUPPORTED')
6+
}
7+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { RuntimeException } from 'node-exceptions'
2+
3+
export class NoSuchBucketException extends RuntimeException {
4+
public raw: Error
5+
6+
public constructor(err: Error, bucket: string) {
7+
super(`The bucket ${bucket} doesn't exist\n${err.message}`, 500, 'E_NO_SUCH_BUCKET')
8+
this.raw = err
9+
}
10+
}

0 commit comments

Comments
 (0)