fix: pre/post validate event typing

This commit is contained in:
Mohamed Marrouchi 2024-12-17 08:46:54 +01:00
parent 6b7a5bf0a2
commit 943b2dd3d2
4 changed files with 52 additions and 91 deletions

View File

@ -7,24 +7,19 @@
*/ */
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { import { EventEmitter2 } from '@nestjs/event-emitter';
EventEmitter2,
IHookSettingsGroupLabelOperationMap,
} from '@nestjs/event-emitter';
import { InjectModel } from '@nestjs/mongoose'; import { InjectModel } from '@nestjs/mongoose';
import { import {
Document, Document,
FilterQuery, FilterQuery,
Model, Model,
Query,
Types, Types,
UpdateQuery, UpdateQuery,
UpdateWithAggregationPipeline, UpdateWithAggregationPipeline,
} from 'mongoose'; } from 'mongoose';
import { I18nService } from '@/i18n/services/i18n.service'; import { I18nService } from '@/i18n/services/i18n.service';
import { BaseRepository, EHook } from '@/utils/generics/base-repository'; import { BaseRepository } from '@/utils/generics/base-repository';
import { TFilterQuery } from '@/utils/types/filter.types';
import { Setting } from '../schemas/setting.schema'; import { Setting } from '../schemas/setting.schema';
import { SettingType } from '../schemas/types'; import { SettingType } from '../schemas/types';
@ -42,62 +37,22 @@ export class SettingRepository extends BaseRepository<Setting> {
async preCreateValidate( async preCreateValidate(
doc: Document<unknown, unknown, Setting> & doc: Document<unknown, unknown, Setting> &
Setting & { _id: Types.ObjectId }, Setting & { _id: Types.ObjectId },
filterCriteria: FilterQuery<Setting>,
updates: UpdateWithAggregationPipeline | UpdateQuery<Setting>,
) { ) {
this.validateSettingValue(doc.type, doc.value); this.validateSettingValue(doc.type, doc.value);
if (filterCriteria && updates) {
this.eventEmitter.emit(
`hook:setting:${EHook.preUpdateValidate}`,
filterCriteria,
updates,
);
}
} }
async preUpdateValidate( async preUpdateValidate(
criteria: string | TFilterQuery<Setting>, criteria: FilterQuery<Setting>,
dto: UpdateQuery<Setting>,
filterCriteria: FilterQuery<Setting>,
updates: UpdateWithAggregationPipeline | UpdateQuery<Setting>, updates: UpdateWithAggregationPipeline | UpdateQuery<Setting>,
): Promise<void> { ): Promise<void> {
const payload = dto.$set ? dto.$set : dto; if (!Array.isArray(updates)) {
const payload = updates.$set;
if (typeof payload.value !== 'undefined') { if (typeof payload.value !== 'undefined') {
const { type } = const { type } =
'type' in payload ? payload : await this.findOne(criteria); 'type' in payload ? payload : await this.findOne(criteria);
this.validateSettingValue(type, payload.value); this.validateSettingValue(type, payload.value);
this.eventEmitter.emit(
`hook:setting:${EHook.preUpdateValidate}`,
filterCriteria,
updates,
);
} }
} }
/**
* Emits an event after a `Setting` has been updated.
*
* This method is used to synchronize global settings by emitting an event
* based on the `group` and `label` of the `Setting`.
*
* @param _query The Mongoose query object used to find and update the document.
* @param setting The updated `Setting` object.
*/
async postUpdate(
_query: Query<
Document<Setting, any, any>,
Document<Setting, any, any>,
unknown,
Setting,
'findOneAndUpdate'
>,
setting: Setting,
) {
const group = setting.group as keyof IHookSettingsGroupLabelOperationMap;
const label = setting.label as '*';
// Sync global settings var
this.eventEmitter.emit(`hook:${group}:${label}`, setting);
} }
/** /**

View File

@ -209,14 +209,10 @@ describe('BaseRepository', () => {
await dummyRepository.updateOne(created.id, mockUpdate); await dummyRepository.updateOne(created.id, mockUpdate);
expect(spyPreUpdateValidate).toHaveBeenCalledWith( expect(spyPreUpdateValidate).toHaveBeenCalledWith(
created.id,
mockUpdate,
mockGetFilterValue, mockGetFilterValue,
mockGetUpdateValue, mockGetUpdateValue,
); );
expect(spyPostUpdateValidate).toHaveBeenCalledWith( expect(spyPostUpdateValidate).toHaveBeenCalledWith(
created.id,
mockUpdate,
mockGetFilterValue, mockGetFilterValue,
mockGetUpdateValue, mockGetUpdateValue,
); );
@ -261,8 +257,6 @@ describe('BaseRepository', () => {
).rejects.toThrow('Mocked error while validating dummy'); ).rejects.toThrow('Mocked error while validating dummy');
expect(spyPreUpdateValidate).toHaveBeenCalledWith( expect(spyPreUpdateValidate).toHaveBeenCalledWith(
created.id,
mockUpdate,
mockGetFilterValue, mockGetFilterValue,
mockGetUpdateValue, mockGetUpdateValue,
); );

View File

@ -39,19 +39,20 @@ export type DeleteResult = {
}; };
export enum EHook { export enum EHook {
preCreateValidate = 'preCreateValidate',
preCreate = 'preCreate', preCreate = 'preCreate',
preUpdateValidate = 'preUpdateValidate',
preUpdate = 'preUpdate', preUpdate = 'preUpdate',
preUpdateMany = 'preUpdateMany', preUpdateMany = 'preUpdateMany',
preDelete = 'preDelete', preDelete = 'preDelete',
preCreateValidate = 'preCreateValidate', postCreateValidate = 'postCreateValidate',
postCreate = 'postCreate', postCreate = 'postCreate',
postUpdateValidate = 'postUpdateValidate',
postUpdate = 'postUpdate', postUpdate = 'postUpdate',
postUpdateMany = 'postUpdateMany', postUpdateMany = 'postUpdateMany',
postDelete = 'postDelete', postDelete = 'postDelete',
postCreateValidate = 'postCreateValidate',
preUpdateValidate = 'preUpdateValidate',
postUpdateValidate = 'postUpdateValidate',
} }
// ! ------------------------------------ Note -------------------------------------------- // ! ------------------------------------ Note --------------------------------------------
// Methods like `update()`, `updateOne()`, `updateMany()`, `findOneAndUpdate()`, // Methods like `update()`, `updateOne()`, `updateMany()`, `findOneAndUpdate()`,
// `findByIdAndUpdate()`, `findOneAndReplace()`, `findOneAndDelete()`, and `findByIdAndDelete()` // `findByIdAndUpdate()`, `findOneAndReplace()`, `findOneAndDelete()`, and `findByIdAndDelete()`
@ -476,8 +477,21 @@ export abstract class BaseRepository<
); );
const filterCriteria = query.getFilter(); const filterCriteria = query.getFilter();
const queryUpdates = query.getUpdate(); const queryUpdates = query.getUpdate();
await this.preUpdateValidate(criteria, dto, filterCriteria, queryUpdates);
await this.postUpdateValidate(criteria, dto, filterCriteria, queryUpdates); await this.preUpdateValidate(filterCriteria, queryUpdates);
this.emitter.emit(
this.getEventName(EHook.preUpdateValidate),
filterCriteria,
queryUpdates,
);
await this.postUpdateValidate(filterCriteria, queryUpdates);
this.emitter.emit(
this.getEventName(EHook.postUpdateValidate),
filterCriteria,
queryUpdates,
);
return await this.executeOne(query, this.cls); return await this.executeOne(query, this.cls);
} }
@ -512,18 +526,14 @@ export abstract class BaseRepository<
// Nothing ... // Nothing ...
} }
async preUpdateValidate<D extends Partial<U>>( async preUpdateValidate(
_criteria: string | TFilterQuery<T>,
_dto: UpdateQuery<D>,
_filterCriteria: FilterQuery<T>, _filterCriteria: FilterQuery<T>,
_updates: UpdateWithAggregationPipeline | UpdateQuery<T>, _updates: UpdateWithAggregationPipeline | UpdateQuery<T>,
): Promise<void> { ): Promise<void> {
// Nothing ... // Nothing ...
} }
async postUpdateValidate<D extends Partial<U>>( async postUpdateValidate(
_criteria: string | TFilterQuery<T>,
_dto: UpdateQuery<D>,
_filterCriteria: FilterQuery<T>, _filterCriteria: FilterQuery<T>,
_updates: UpdateWithAggregationPipeline | UpdateQuery<T>, _updates: UpdateWithAggregationPipeline | UpdateQuery<T>,
): Promise<void> { ): Promise<void> {

View File

@ -6,7 +6,7 @@
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/ */
import type { Document, FilterQuery, Query } from 'mongoose'; import type { Document, Query } from 'mongoose';
import { type Socket } from 'socket.io'; import { type Socket } from 'socket.io';
import { type BotStats } from '@/analytics/schemas/bot-stats.schema'; import { type BotStats } from '@/analytics/schemas/bot-stats.schema';
@ -179,11 +179,13 @@ declare module '@nestjs/event-emitter' {
type EventNamespaces = keyof IHookEntityOperationMap; type EventNamespaces = keyof IHookEntityOperationMap;
/* pre hooks */ /* pre hooks */
type TPreValidate<T> = THydratedDocument<T>; type TPreCreateValidate<T> = THydratedDocument<T>;
type TPreCreate<T> = THydratedDocument<T>; type TPreCreate<T> = THydratedDocument<T>;
type TPreUpdate<T> = TFilterQuery<T> & object; type TPreUpdateValidate<T> = FilterQuery<T>;
type TPreUpdate<T> = TFilterQuery<T>;
type TPreDelete<T> = Query< type TPreDelete<T> = Query<
DeleteResult, DeleteResult,
@ -195,27 +197,27 @@ declare module '@nestjs/event-emitter' {
>; >;
type TPreUnion<T> = type TPreUnion<T> =
| TPreValidate<T> | TPreCreateValidate<T>
| TPreCreate<T> | TPreCreate<T>
| TPreUpdateValidate<T>
| TPreUpdate<T> | TPreUpdate<T>
| TPreDelete<T>; | TPreDelete<T>;
/* post hooks */ /* post hooks */
type TPostValidate<T> = THydratedDocument<T>; type TPostCreateValidate<T> = THydratedDocument<T>;
type TPostCreate<T> = THydratedDocument<T>; type TPostCreate<T> = THydratedDocument<T>;
type TPostUpdateValidate<T> = FilterQuery<T>;
type TPostUpdate<T> = THydratedDocument<T>; type TPostUpdate<T> = THydratedDocument<T>;
type TPreUpdateValidate<T> = FilterQuery<T>;
type TPostUpdateValidate<T> = THydratedDocument<T>;
type TPostDelete = DeleteResult; type TPostDelete = DeleteResult;
type TPostUnion<T> = type TPostUnion<T> =
| TPostValidate<T> | TPostCreateValidate<T>
| TPostCreate<T> | TPostCreate<T>
| TPostUpdateValidate<T>
| TPostUpdate<T> | TPostUpdate<T>
| TPostDelete; | TPostDelete;
@ -251,11 +253,14 @@ declare module '@nestjs/event-emitter' {
T = IHookEntityOperationMap[E]['schema'], T = IHookEntityOperationMap[E]['schema'],
> = > =
| { | {
[EHook.preCreateValidate]: TPreValidate<T>; [EHook.preCreateValidate]: TPreCreateValidate<T>;
} }
| { | {
[EHook.preCreate]: TPreCreate<T>; [EHook.preCreate]: TPreCreate<T>;
} }
| {
[EHook.preUpdateValidate]: TPreUpdateValidate<T>;
}
| { | {
[EHook.preUpdate]: TPreUpdate<T>; [EHook.preUpdate]: TPreUpdate<T>;
} }
@ -263,22 +268,19 @@ declare module '@nestjs/event-emitter' {
[EHook.preDelete]: TPreDelete<T>; [EHook.preDelete]: TPreDelete<T>;
} }
| { | {
[EHook.postCreateValidate]: TPostValidate<T>; [EHook.postCreateValidate]: TPostCreateValidate<T>;
} }
| { | {
[EHook.postCreate]: TPostCreate<T>; [EHook.postCreate]: TPostCreate<T>;
} }
| {
[EHook.postUpdateValidate]: TPostUpdateValidate<T>;
}
| { | {
[EHook.postUpdate]: TPostUpdate<T>; [EHook.postUpdate]: TPostUpdate<T>;
} }
| { | {
[EHook.postDelete]: TPostDelete; [EHook.postDelete]: TPostDelete;
}
| {
[EHook.preUpdateValidate]: TPreUpdateValidate<T>;
}
| {
[EHook.postUpdateValidate]: TPostUpdateValidate<T>;
}; };
type TNormalizedHook<E extends keyof IHookEntityOperationMap, O> = Extract< type TNormalizedHook<E extends keyof IHookEntityOperationMap, O> = Extract<