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,
} from '@nestjs/event-emitter';
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 { 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 { SettingType } from '../schemas/types';
@ -30,48 +39,38 @@ export class SettingRepository extends BaseRepository<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(
setting: Document<unknown, unknown, Setting> &
doc: Document<unknown, unknown, Setting> &
Setting & { _id: Types.ObjectId },
filterCriteria: FilterQuery<Setting>,
updates: UpdateWithAggregationPipeline | UpdateQuery<Setting>,
) {
if (
(setting.type === SettingType.text ||
setting.type === SettingType.textarea) &&
typeof setting.value !== 'string' &&
setting.value !== null
) {
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) => {
return typeof v === 'string';
});
if (!isStringArray) {
throw new Error('Setting Model : Value must be a string array!');
this.validateSettingValue(doc.type, doc.value);
if (filterCriteria && updates) {
this.eventEmitter.emit(
`hook:setting:${EHook.preUpdateValidate}`,
filterCriteria,
updates,
);
}
} else if (
setting.type === SettingType.checkbox &&
typeof setting.value !== 'boolean' &&
setting.value !== null
) {
throw new Error('Setting Model : Value must be a boolean!');
} else if (
setting.type === SettingType.number &&
typeof setting.value !== 'number' &&
setting.value !== null
) {
throw new Error('Setting Model : Value must be a number!');
}
async preUpdateValidate(
criteria: string | TFilterQuery<Setting>,
dto: UpdateQuery<Setting>,
filterCriteria: FilterQuery<Setting>,
updates: UpdateWithAggregationPipeline | UpdateQuery<Setting>,
): Promise<void> {
const payload = dto.$set ? dto.$set : dto;
if (typeof payload.value !== 'undefined') {
const { type } =
'type' in payload ? payload : await this.findOne(criteria);
this.validateSettingValue(type, payload.value);
this.eventEmitter.emit(
`hook:setting:${EHook.preUpdateValidate}`,
filterCriteria,
updates,
);
}
}
@ -100,4 +99,45 @@ export class SettingRepository extends BaseRepository<Setting> {
// Sync global settings var
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 {
Document,
FilterQuery,
FlattenMaps,
HydratedDocument,
Model,
@ -48,6 +49,8 @@ export enum EHook {
postUpdateMany = 'postUpdateMany',
postDelete = 'postDelete',
postCreateValidate = 'postCreateValidate',
preUpdateValidate = 'preUpdateValidate',
postUpdateValidate = 'postUpdateValidate',
}
export abstract class BaseRepository<
@ -460,6 +463,10 @@ export abstract class BaseRepository<
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);
}
@ -482,7 +489,11 @@ export abstract class BaseRepository<
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 ...
}
@ -490,6 +501,24 @@ export abstract class BaseRepository<
// 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> {
// 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).
*/
import type { Document, Query } from 'mongoose';
import type { Document, FilterQuery, Query } from 'mongoose';
import { type Socket } from 'socket.io';
import { type BotStats } from '@/analytics/schemas/bot-stats.schema';
@ -207,6 +207,10 @@ declare module '@nestjs/event-emitter' {
type TPostUpdate<T> = THydratedDocument<T>;
type TPreUpdateValidate<T> = FilterQuery<T>;
type TPostUpdateValidate<T> = THydratedDocument<T>;
type TPostDelete = DeleteResult;
type TPostUnion<T> =
@ -269,6 +273,12 @@ declare module '@nestjs/event-emitter' {
}
| {
[EHook.postDelete]: TPostDelete;
}
| {
[EHook.preUpdateValidate]: TPreUpdateValidate<T>;
}
| {
[EHook.postUpdateValidate]: TPostUpdateValidate<T>;
};
type TNormalizedHook<E extends keyof IHookEntityOperationMap, O> = Extract<