mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
refactor(api): emit events logic
This commit is contained in:
parent
ac9c10c576
commit
90256a350b
@ -13,23 +13,15 @@ import { Model } from 'mongoose';
|
||||
|
||||
import { BaseRepository } from '@/utils/generics/base-repository';
|
||||
|
||||
import { Attachment, AttachmentDocument } from '../schemas/attachment.schema';
|
||||
import { Attachment } from '../schemas/attachment.schema';
|
||||
|
||||
@Injectable()
|
||||
export class AttachmentRepository extends BaseRepository<Attachment, never> {
|
||||
constructor(
|
||||
@InjectModel(Attachment.name) readonly model: Model<Attachment>,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
readonly eventEmitter: EventEmitter2,
|
||||
) {
|
||||
super(model, Attachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles post-creation operations for an attachment.
|
||||
*
|
||||
* @param created - The created attachment document.
|
||||
*/
|
||||
async postCreate(created: AttachmentDocument): Promise<void> {
|
||||
this.eventEmitter.emit('hook:chatbot:attachment:upload', created);
|
||||
super.setEventEmitter(eventEmitter);
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import { THydratedDocument } from 'mongoose';
|
||||
import { FileType } from '@/chat/schemas/types/attachment';
|
||||
import { config } from '@/config';
|
||||
import { BaseSchema } from '@/utils/generics/base-schema';
|
||||
import { LifecycleHookManager } from '@/utils/generics/lifecycle-hook-manager';
|
||||
import { buildURL } from '@/utils/helpers/URL';
|
||||
|
||||
import { MIME_REGEX } from '../utilities';
|
||||
@ -115,10 +116,10 @@ export class Attachment extends BaseSchema {
|
||||
|
||||
export type AttachmentDocument = THydratedDocument<Attachment>;
|
||||
|
||||
export const AttachmentModel: ModelDefinition = {
|
||||
export const AttachmentModel: ModelDefinition = LifecycleHookManager.attach({
|
||||
name: Attachment.name,
|
||||
schema: SchemaFactory.createForClass(Attachment),
|
||||
};
|
||||
});
|
||||
|
||||
AttachmentModel.schema.virtual('url').get(function () {
|
||||
if (this._id && this.name)
|
||||
|
@ -16,7 +16,6 @@ import { BaseRepository } from '@/utils/generics/base-repository';
|
||||
import {
|
||||
Conversation,
|
||||
CONVERSATION_POPULATE,
|
||||
ConversationDocument,
|
||||
ConversationFull,
|
||||
ConversationPopulate,
|
||||
} from '../schemas/conversation.schema';
|
||||
@ -29,19 +28,10 @@ export class ConversationRepository extends BaseRepository<
|
||||
> {
|
||||
constructor(
|
||||
@InjectModel(Conversation.name) readonly model: Model<Conversation>,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
readonly eventEmitter: EventEmitter2,
|
||||
) {
|
||||
super(model, Conversation, CONVERSATION_POPULATE, ConversationFull);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a new conversation is created. This method emits the event
|
||||
* with the newly created conversation document.
|
||||
*
|
||||
* @param created - The newly created conversation document.
|
||||
*/
|
||||
async postCreate(created: ConversationDocument): Promise<void> {
|
||||
this.eventEmitter.emit('hook:chatbot:conversation:start', created);
|
||||
super.setEventEmitter(eventEmitter);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -24,7 +24,6 @@ import { SubscriberUpdateDto } from '../dto/subscriber.dto';
|
||||
import {
|
||||
Subscriber,
|
||||
SUBSCRIBER_POPULATE,
|
||||
SubscriberDocument,
|
||||
SubscriberFull,
|
||||
SubscriberPopulate,
|
||||
} from '../schemas/subscriber.schema';
|
||||
@ -37,24 +36,10 @@ export class SubscriberRepository extends BaseRepository<
|
||||
> {
|
||||
constructor(
|
||||
@InjectModel(Subscriber.name) readonly model: Model<Subscriber>,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
readonly eventEmitter: EventEmitter2,
|
||||
) {
|
||||
super(model, Subscriber, SUBSCRIBER_POPULATE, SubscriberFull);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits events related to the creation of a new subscriber.
|
||||
*
|
||||
* @param created - The newly created subscriber document.
|
||||
*/
|
||||
async postCreate(created: SubscriberDocument): Promise<void> {
|
||||
this.eventEmitter.emit(
|
||||
'hook:stats:entry',
|
||||
'new_users',
|
||||
'New users',
|
||||
created,
|
||||
);
|
||||
this.eventEmitter.emit('hook:subscriber:create', created);
|
||||
super.setEventEmitter(eventEmitter);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,12 +65,6 @@ export class SubscriberRepository extends BaseRepository<
|
||||
): Promise<void> {
|
||||
const subscriberUpdates: SubscriberUpdateDto = updates?.['$set'];
|
||||
|
||||
this.eventEmitter.emit(
|
||||
'hook:subscriber:update:before',
|
||||
criteria,
|
||||
subscriberUpdates,
|
||||
);
|
||||
|
||||
const oldSubscriber = await this.findOne(criteria);
|
||||
|
||||
if (subscriberUpdates.assignedTo !== oldSubscriber?.assignedTo) {
|
||||
@ -105,26 +84,6 @@ export class SubscriberRepository extends BaseRepository<
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event after successfully updating a subscriber.
|
||||
* Triggers the event with the updated subscriber data.
|
||||
*
|
||||
* @param _query - The Mongoose query object for finding and updating a subscriber.
|
||||
* @param updated - The updated subscriber entity.
|
||||
*/
|
||||
async postUpdate(
|
||||
_query: Query<
|
||||
Document<Subscriber, any, any>,
|
||||
Document<Subscriber, any, any>,
|
||||
unknown,
|
||||
Subscriber,
|
||||
'findOneAndUpdate'
|
||||
>,
|
||||
updated: Subscriber,
|
||||
) {
|
||||
this.eventEmitter.emit('hook:subscriber:update:after', updated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a query to find a subscriber by their foreign ID.
|
||||
*
|
||||
|
@ -287,7 +287,7 @@ export class ChatService {
|
||||
*
|
||||
* @param subscriber - The end user (subscriber)
|
||||
*/
|
||||
@OnEvent('hook:subscriber:create')
|
||||
@OnEvent('hook:subscriber:postCreate')
|
||||
onSubscriberCreate(subscriber: Subscriber) {
|
||||
this.websocketGateway.broadcastSubscriberNew(subscriber);
|
||||
}
|
||||
@ -297,7 +297,7 @@ export class ChatService {
|
||||
*
|
||||
* @param subscriber - The end user (subscriber)
|
||||
*/
|
||||
@OnEvent('hook:subscriber:update:after')
|
||||
@OnEvent('hook:subscriber:postUpdate')
|
||||
onSubscriberUpdate(subscriber: Subscriber) {
|
||||
this.websocketGateway.broadcastSubscriberUpdate(subscriber);
|
||||
}
|
||||
|
@ -9,9 +9,9 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { Document, Model, Query } from 'mongoose';
|
||||
import { Model } from 'mongoose';
|
||||
|
||||
import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository';
|
||||
import { BaseRepository } from '@/utils/generics/base-repository';
|
||||
|
||||
import {
|
||||
Menu,
|
||||
@ -33,6 +33,7 @@ export class MenuRepository extends BaseRepository<
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {
|
||||
super(model, Menu, MENU_POPULATE, MenuFull);
|
||||
super.setEventEmitter(eventEmitter);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -68,52 +69,4 @@ export class MenuRepository extends BaseRepository<
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-create hook that triggers the event after a `Menu` document has been successfully created.
|
||||
*
|
||||
* @param created - The newly created `MenuDocument`.
|
||||
*/
|
||||
async postCreate(created: MenuDocument): Promise<void> {
|
||||
this.eventEmitter.emit('hook:menu:create', created);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description
|
||||
* Post-update hook that triggers the event after a `Menu` document has been successfully updated.
|
||||
*
|
||||
* @param query - The query used to update the `Menu` document.
|
||||
* @param updated - The updated `Menu` document.
|
||||
*/
|
||||
async postUpdate(
|
||||
_query: Query<
|
||||
Document<Menu, any, any>,
|
||||
Document<Menu, any, any>,
|
||||
unknown,
|
||||
Menu,
|
||||
'findOneAndUpdate'
|
||||
>,
|
||||
updated: Menu,
|
||||
): Promise<void> {
|
||||
this.eventEmitter.emit('hook:menu:update', updated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-delete hook that triggers the event after a `Menu` document has been successfully deleted.
|
||||
*
|
||||
* @param query - The query used to delete the `Menu` document.
|
||||
* @param result - The result of the deletion.
|
||||
*/
|
||||
async postDelete(
|
||||
_query: Query<
|
||||
DeleteResult,
|
||||
Document<Menu, any, any>,
|
||||
unknown,
|
||||
Menu,
|
||||
'deleteOne' | 'deleteMany'
|
||||
>,
|
||||
result: DeleteResult,
|
||||
): Promise<void> {
|
||||
this.eventEmitter.emit('hook:menu:delete', result);
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,9 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { Document, Model, Query, Types } from 'mongoose';
|
||||
import { Model } from 'mongoose';
|
||||
|
||||
import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository';
|
||||
import { BaseRepository } from '@/utils/generics/base-repository';
|
||||
|
||||
import { Translation } from '../../i18n/schemas/translation.schema';
|
||||
|
||||
@ -19,58 +19,9 @@ import { Translation } from '../../i18n/schemas/translation.schema';
|
||||
export class TranslationRepository extends BaseRepository<Translation> {
|
||||
constructor(
|
||||
@InjectModel(Translation.name) readonly model: Model<Translation>,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
readonly eventEmitter: EventEmitter2,
|
||||
) {
|
||||
super(model, Translation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event after a translation document is updated.
|
||||
*
|
||||
* @param query - The query object representing the update operation.
|
||||
* @param updated - The updated translation document.
|
||||
*/
|
||||
async postUpdate(
|
||||
_query: Query<
|
||||
Document<Translation, any, any>,
|
||||
Document<Translation, any, any>,
|
||||
unknown,
|
||||
Translation,
|
||||
'findOneAndUpdate'
|
||||
>,
|
||||
_updated: Translation,
|
||||
) {
|
||||
this.eventEmitter.emit('hook:translation:update');
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event after a new translation document is created.
|
||||
*
|
||||
* @param created - The newly created translation document.
|
||||
*/
|
||||
async postCreate(
|
||||
_created: Document<unknown, unknown, Translation> &
|
||||
Translation & { _id: Types.ObjectId },
|
||||
) {
|
||||
this.eventEmitter.emit('hook:translation:create');
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event after a translation document is deleted.
|
||||
*
|
||||
* @param query - The query object representing the delete operation.
|
||||
* @param result - The result of the delete operation.
|
||||
*/
|
||||
async postDelete(
|
||||
_query: Query<
|
||||
DeleteResult,
|
||||
Document<Translation, any, any>,
|
||||
unknown,
|
||||
Translation,
|
||||
'deleteOne' | 'deleteMany'
|
||||
>,
|
||||
_result: DeleteResult,
|
||||
) {
|
||||
this.eventEmitter.emit('hook:translation:delete');
|
||||
super.setEventEmitter(eventEmitter);
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,9 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { Document, Model, Query, TFilterQuery, Types } from 'mongoose';
|
||||
import { Model, TFilterQuery } from 'mongoose';
|
||||
|
||||
import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository';
|
||||
import { BaseRepository } from '@/utils/generics/base-repository';
|
||||
|
||||
import {
|
||||
Permission,
|
||||
@ -28,59 +28,10 @@ export class PermissionRepository extends BaseRepository<
|
||||
> {
|
||||
constructor(
|
||||
@InjectModel(Permission.name) readonly model: Model<Permission>,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
readonly eventEmitter: EventEmitter2,
|
||||
) {
|
||||
super(model, Permission, PERMISSION_POPULATE, PermissionFull);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event after a permission is created.
|
||||
*
|
||||
* @param created - The created permission document.
|
||||
*/
|
||||
async postCreate(
|
||||
created: Document<unknown, unknown, Permission> &
|
||||
Permission & { _id: Types.ObjectId },
|
||||
): Promise<void> {
|
||||
this.eventEmitter.emit('hook:access:permission:create', created);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event after a permission is updated.
|
||||
*
|
||||
* @param _query - The query used for updating the permission.
|
||||
* @param updated - The updated permission entity.
|
||||
*/
|
||||
async postUpdate(
|
||||
_query: Query<
|
||||
Document<Permission, any, any>,
|
||||
Document<Permission, any, any>,
|
||||
unknown,
|
||||
Permission,
|
||||
'findOneAndUpdate'
|
||||
>,
|
||||
updated: Permission,
|
||||
): Promise<void> {
|
||||
this.eventEmitter.emit('hook:access:permission:update', updated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an event after a permission is deleted.
|
||||
*
|
||||
* @param _query - The query used for deleting the permission.
|
||||
* @param result - The result of the delete operation.
|
||||
*/
|
||||
async postDelete(
|
||||
_query: Query<
|
||||
DeleteResult,
|
||||
Document<Permission, any, any>,
|
||||
unknown,
|
||||
Permission,
|
||||
'deleteOne' | 'deleteMany'
|
||||
>,
|
||||
result: DeleteResult,
|
||||
): Promise<void> {
|
||||
this.eventEmitter.emit('hook:access:permission:delete', result);
|
||||
super.setEventEmitter(eventEmitter);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -9,9 +9,9 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { InjectModel } from '@nestjs/mongoose';
|
||||
import { Document, Model, Query, TFilterQuery, Types } from 'mongoose';
|
||||
import { Model, TFilterQuery } from 'mongoose';
|
||||
|
||||
import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository';
|
||||
import { BaseRepository } from '@/utils/generics/base-repository';
|
||||
import { PageQueryDto } from '@/utils/pagination/pagination-query.dto';
|
||||
|
||||
import { Permission } from '../schemas/permission.schema';
|
||||
@ -32,60 +32,10 @@ export class RoleRepository extends BaseRepository<
|
||||
@InjectModel(Role.name) readonly model: Model<Role>,
|
||||
@InjectModel(Permission.name)
|
||||
private readonly permissionModel: Model<Permission>,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
readonly eventEmitter: EventEmitter2,
|
||||
) {
|
||||
super(model, Role, ROLE_POPULATE, RoleFull);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a hook event after a role is successfully created.
|
||||
*
|
||||
* @param created The created Role document.
|
||||
*/
|
||||
async preCreate(
|
||||
created: Document<unknown, object, Role> & Role & { _id: Types.ObjectId },
|
||||
): Promise<void> {
|
||||
if (created) {
|
||||
this.eventEmitter.emit('hook:access:role:create', created);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a hook event after a role is successfully updated.
|
||||
*
|
||||
* @param _query The query used to update the Role.
|
||||
* @param updated The updated Role entity.
|
||||
*/
|
||||
async postUpdate(
|
||||
_query: Query<
|
||||
Document<Role, any, any>,
|
||||
Document<Role, any, any>,
|
||||
unknown,
|
||||
Role,
|
||||
'findOneAndUpdate'
|
||||
>,
|
||||
updated: Role,
|
||||
): Promise<void> {
|
||||
this.eventEmitter.emit('hook:access:role:update', updated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a hook event after a role is successfully deleted.
|
||||
*
|
||||
* @param _query The query used to delete the Role.
|
||||
* @param result The result of the deletion operation.
|
||||
*/
|
||||
async postDelete(
|
||||
_query: Query<
|
||||
DeleteResult,
|
||||
Document<Role, any, any>,
|
||||
unknown,
|
||||
Role,
|
||||
'deleteOne' | 'deleteMany'
|
||||
>,
|
||||
result: DeleteResult,
|
||||
): Promise<void> {
|
||||
this.eventEmitter.emit('hook:access:role:delete', result);
|
||||
super.setEventEmitter(eventEmitter);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -42,7 +42,8 @@ export class PermissionService extends BaseService<
|
||||
* This method listens to events matching the pattern 'hook:access:*:*' and clears
|
||||
* the permissions cache to ensure fresh data is used for subsequent requests.
|
||||
*/
|
||||
@OnEvent('hook:access:*:*')
|
||||
@OnEvent('hook:role:*')
|
||||
@OnEvent('hook:permission:*')
|
||||
async handlePermissionUpdateEvent() {
|
||||
await this.cacheManager.del(PERMISSION_CACHE_KEY);
|
||||
}
|
||||
|
@ -6,6 +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 { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { ClassTransformOptions, plainToClass } from 'class-transformer';
|
||||
import {
|
||||
Document,
|
||||
@ -28,6 +29,15 @@ export type DeleteResult = {
|
||||
deletedCount: number;
|
||||
};
|
||||
|
||||
export enum EHook {
|
||||
preCreate = 'preCreate',
|
||||
preUpdate = 'preUpdate',
|
||||
preDelete = 'preDelete',
|
||||
postCreate = 'postCreate',
|
||||
postUpdate = 'postUpdate',
|
||||
postDelete = 'postDelete',
|
||||
}
|
||||
|
||||
export abstract class BaseRepository<
|
||||
T extends FlattenMaps<unknown>,
|
||||
P extends string = never,
|
||||
@ -39,6 +49,8 @@ export abstract class BaseRepository<
|
||||
|
||||
private readonly leanOpts = { virtuals: true, defaults: true, getters: true };
|
||||
|
||||
private emitter: EventEmitter2;
|
||||
|
||||
constructor(
|
||||
readonly model: Model<T>,
|
||||
private readonly cls: new () => T,
|
||||
@ -52,6 +64,14 @@ export abstract class BaseRepository<
|
||||
return this.populate;
|
||||
}
|
||||
|
||||
getEventName(suffix: EHook) {
|
||||
return `hook:${this.cls.name.toLocaleLowerCase()}:${suffix.toLocaleLowerCase()}`;
|
||||
}
|
||||
|
||||
setEventEmitter(eventEmitter: EventEmitter2) {
|
||||
this.emitter = eventEmitter;
|
||||
}
|
||||
|
||||
private registerLifeCycleHooks() {
|
||||
const repository = this;
|
||||
const hooks = LifecycleHookManager.getHooks(this.cls.name);
|
||||
@ -71,6 +91,10 @@ export abstract class BaseRepository<
|
||||
});
|
||||
|
||||
hooks?.save.post.execute(async function (created: HydratedDocument<T>) {
|
||||
repository.emitter?.emit(
|
||||
repository.getEventName(EHook.postCreate),
|
||||
created,
|
||||
);
|
||||
await repository.postCreate(created);
|
||||
});
|
||||
|
||||
@ -92,6 +116,10 @@ export abstract class BaseRepository<
|
||||
});
|
||||
|
||||
hooks?.deleteMany.post.execute(async function (result: DeleteResult) {
|
||||
repository.emitter?.emit(
|
||||
repository.getEventName(EHook.postDelete),
|
||||
result,
|
||||
);
|
||||
const query = this as Query<DeleteResult, D, unknown, T, 'deleteMany'>;
|
||||
await repository.postDelete(query, result);
|
||||
});
|
||||
@ -100,6 +128,12 @@ export abstract class BaseRepository<
|
||||
const query = this as Query<D, D, unknown, T, 'findOneAndUpdate'>;
|
||||
const criteria = query.getFilter();
|
||||
const updates = query.getUpdate();
|
||||
|
||||
repository.emitter?.emit(
|
||||
repository.getEventName(EHook.preUpdate),
|
||||
criteria,
|
||||
updates?.['$set'],
|
||||
);
|
||||
await repository.preUpdate(query, criteria, updates);
|
||||
});
|
||||
|
||||
@ -107,6 +141,10 @@ export abstract class BaseRepository<
|
||||
updated: HydratedDocument<T>,
|
||||
) {
|
||||
if (updated) {
|
||||
repository.emitter?.emit(
|
||||
repository.getEventName(EHook.postUpdate),
|
||||
updated,
|
||||
);
|
||||
const query = this as Query<D, D, unknown, T, 'findOneAndUpdate'>;
|
||||
await repository.postUpdate(
|
||||
query,
|
||||
|
Loading…
Reference in New Issue
Block a user