fix: add event emitting logic

This commit is contained in:
abdou6666 2024-12-13 18:30:22 +01:00
parent 04851f7048
commit 9a3165f8da
3 changed files with 122 additions and 43 deletions

View File

@ -12,10 +12,19 @@ import {
IHookSettingsGroupLabelOperationMap, IHookSettingsGroupLabelOperationMap,
} from '@nestjs/event-emitter'; } from '@nestjs/event-emitter';
import { InjectModel } from '@nestjs/mongoose'; import { InjectModel } from '@nestjs/mongoose';
import { Document, Model, Query, Types } from 'mongoose'; import {
Document,
FilterQuery,
Model,
Query,
Types,
UpdateQuery,
UpdateWithAggregationPipeline,
} from 'mongoose';
import { I18nService } from '@/i18n/services/i18n.service'; import { I18nService } from '@/i18n/services/i18n.service';
import { BaseRepository } from '@/utils/generics/base-repository'; import { BaseRepository, EHook } 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';
@ -30,48 +39,38 @@ export class SettingRepository extends BaseRepository<Setting> {
super(eventEmitter, model, Setting); super(eventEmitter, model, Setting);
} }
/**
* Validates the `Setting` document after it has been retrieved.
*
* Checks the `type` of the setting and validates the `value` field according to the type:
* - `text` expects a string.
* - `multiple_text` expects an array of strings.
* - `checkbox` expects a boolean.
*
* @param setting The `Setting` document to be validated.
*/
async preCreateValidate( async preCreateValidate(
setting: Document<unknown, unknown, Setting> & doc: Document<unknown, unknown, Setting> &
Setting & { _id: Types.ObjectId }, Setting & { _id: Types.ObjectId },
filterCriteria: FilterQuery<Setting>,
updates: UpdateWithAggregationPipeline | UpdateQuery<Setting>,
) { ) {
if ( this.validateSettingValue(doc.type, doc.value);
(setting.type === SettingType.text || if (filterCriteria && updates) {
setting.type === SettingType.textarea) && this.eventEmitter.emit(
typeof setting.value !== 'string' && `hook:setting:${EHook.preUpdateValidate}`,
setting.value !== null filterCriteria,
) { updates,
throw new Error('Setting Model : Value must be a string!'); );
} else if (setting.type === SettingType.multiple_text) { }
const isStringArray = }
Array.isArray(setting.value) &&
setting.value.every((v) => { async preUpdateValidate(
return typeof v === 'string'; criteria: string | TFilterQuery<Setting>,
}); dto: UpdateQuery<Setting>,
if (!isStringArray) { filterCriteria: FilterQuery<Setting>,
throw new Error('Setting Model : Value must be a string array!'); updates: UpdateWithAggregationPipeline | UpdateQuery<Setting>,
} ): Promise<void> {
} else if ( const payload = dto.$set ? dto.$set : dto;
setting.type === SettingType.checkbox && if (typeof payload.value !== 'undefined') {
typeof setting.value !== 'boolean' && const { type } =
setting.value !== null 'type' in payload ? payload : await this.findOne(criteria);
) { this.validateSettingValue(type, payload.value);
throw new Error('Setting Model : Value must be a boolean!'); this.eventEmitter.emit(
} else if ( `hook:setting:${EHook.preUpdateValidate}`,
setting.type === SettingType.number && filterCriteria,
typeof setting.value !== 'number' && updates,
setting.value !== null );
) {
throw new Error('Setting Model : Value must be a number!');
} }
} }
@ -100,4 +99,45 @@ export class SettingRepository extends BaseRepository<Setting> {
// Sync global settings var // Sync global settings var
this.eventEmitter.emit(`hook:${group}:${label}`, setting); this.eventEmitter.emit(`hook:${group}:${label}`, setting);
} }
/**
* Validates the `Setting` document after it has been retrieved.
*
* Checks the `type` of the setting and validates the `value` field according to the type:
* - `text` expects a string.
* - `multiple_text` expects an array of strings.
* - `checkbox` expects a boolean.
*
* @param setting The `Setting` document to be validated.
*/
private validateSettingValue(type: SettingType, value: any) {
if (
(type === SettingType.text || type === SettingType.textarea) &&
typeof value !== 'string' &&
value !== null
) {
throw new Error('Setting Model : Value must be a string!');
} else if (type === SettingType.multiple_text) {
const isStringArray =
Array.isArray(value) &&
value.every((v) => {
return typeof v === 'string';
});
if (!isStringArray) {
throw new Error('Setting Model : Value must be a string array!');
}
} else if (
type === SettingType.checkbox &&
typeof value !== 'boolean' &&
value !== null
) {
throw new Error('Setting Model : Value must be a boolean!');
} else if (
type === SettingType.number &&
typeof value !== 'number' &&
value !== null
) {
throw new Error('Setting Model : Value must be a number!');
}
}
} }

View File

@ -14,6 +14,7 @@ import {
import { ClassTransformOptions, plainToClass } from 'class-transformer'; import { ClassTransformOptions, plainToClass } from 'class-transformer';
import { import {
Document, Document,
FilterQuery,
FlattenMaps, FlattenMaps,
HydratedDocument, HydratedDocument,
Model, Model,
@ -48,6 +49,8 @@ export enum EHook {
postUpdateMany = 'postUpdateMany', postUpdateMany = 'postUpdateMany',
postDelete = 'postDelete', postDelete = 'postDelete',
postCreateValidate = 'postCreateValidate', postCreateValidate = 'postCreateValidate',
preUpdateValidate = 'preUpdateValidate',
postUpdateValidate = 'postUpdateValidate',
} }
export abstract class BaseRepository< export abstract class BaseRepository<
@ -460,6 +463,10 @@ export abstract class BaseRepository<
new: true, new: true,
}, },
); );
const filterCriteria = query.getFilter();
const queryUpdates = query.getUpdate();
await this.preUpdateValidate(criteria, dto, filterCriteria, queryUpdates);
await this.postUpdateValidate(criteria, dto, filterCriteria, queryUpdates);
return await this.executeOne(query, this.cls); return await this.executeOne(query, this.cls);
} }
@ -482,7 +489,11 @@ export abstract class BaseRepository<
return await this.model.deleteMany(criteria); return await this.model.deleteMany(criteria);
} }
async preCreateValidate(_doc: HydratedDocument<T>): Promise<void> { async preCreateValidate(
_doc: HydratedDocument<T>,
_filterCriteria?: FilterQuery<T>,
_updates?: UpdateWithAggregationPipeline | UpdateQuery<T>,
): Promise<void> {
// Nothing ... // Nothing ...
} }
@ -490,6 +501,24 @@ export abstract class BaseRepository<
// Nothing ... // Nothing ...
} }
async preUpdateValidate<D extends Partial<U>>(
_criteria: string | TFilterQuery<T>,
_dto: UpdateQuery<D>,
_filterCriteria: FilterQuery<T>,
_updates: UpdateWithAggregationPipeline | UpdateQuery<T>,
): Promise<void> {
// Nothing ...
}
async postUpdateValidate<D extends Partial<U>>(
_criteria: string | TFilterQuery<T>,
_dto: UpdateQuery<D>,
_filterCriteria: FilterQuery<T>,
_updates: UpdateWithAggregationPipeline | UpdateQuery<T>,
): Promise<void> {
// Nothing ...
}
async preCreate(_doc: HydratedDocument<T>): Promise<void> { async preCreate(_doc: HydratedDocument<T>): Promise<void> {
// Nothing ... // Nothing ...
} }

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, Query } from 'mongoose'; import type { Document, FilterQuery, 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';
@ -207,6 +207,10 @@ declare module '@nestjs/event-emitter' {
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> =
@ -269,6 +273,12 @@ declare module '@nestjs/event-emitter' {
} }
| { | {
[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<