mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
Merge pull request #166 from Hexastack/165-issue-untyped-events-and-event-listners
165 issue untyped events and event listeners
This commit is contained in:
commit
63a79813d6
@ -68,7 +68,7 @@ export class BotStatsService extends BaseService<BotStats> {
|
|||||||
* @param {Subscriber} subscriber - The subscriber object that contains last visit and retention data.
|
* @param {Subscriber} subscriber - The subscriber object that contains last visit and retention data.
|
||||||
*/
|
*/
|
||||||
@OnEvent('hook:user:lastvisit')
|
@OnEvent('hook:user:lastvisit')
|
||||||
private handleLastVisit(subscriber: Subscriber) {
|
handleLastVisit(subscriber: Subscriber) {
|
||||||
const now = +new Date();
|
const now = +new Date();
|
||||||
if (subscriber.lastvisit) {
|
if (subscriber.lastvisit) {
|
||||||
// A loyal subscriber is a subscriber that comes back after some inactivity
|
// A loyal subscriber is a subscriber that comes back after some inactivity
|
||||||
@ -110,7 +110,7 @@ export class BotStatsService extends BaseService<BotStats> {
|
|||||||
* @param name - The name or identifier of the statistics entry (e.g., a specific feature or component being tracked).
|
* @param name - The name or identifier of the statistics entry (e.g., a specific feature or component being tracked).
|
||||||
*/
|
*/
|
||||||
@OnEvent('hook:stats:entry')
|
@OnEvent('hook:stats:entry')
|
||||||
private async handleStatEntry(type: BotStatsType, name: string) {
|
async handleStatEntry(type: BotStatsType, name: string) {
|
||||||
const day = new Date();
|
const day = new Date();
|
||||||
day.setMilliseconds(0);
|
day.setMilliseconds(0);
|
||||||
day.setSeconds(0);
|
day.setSeconds(0);
|
||||||
|
|||||||
@ -235,7 +235,7 @@ export class SubscriberService extends BaseService<
|
|||||||
* @param subscriber The subscriber whose is being handled.
|
* @param subscriber The subscriber whose is being handled.
|
||||||
*/
|
*/
|
||||||
@OnEvent('hook:user:lastvisit')
|
@OnEvent('hook:user:lastvisit')
|
||||||
private async handleLastVisit(subscriber: Subscriber) {
|
async handleLastVisit(subscriber: Subscriber) {
|
||||||
if (subscriber.lastvisit) {
|
if (subscriber.lastvisit) {
|
||||||
try {
|
try {
|
||||||
const user = await this.updateOne(subscriber.id, {
|
const user = await this.updateOne(subscriber.id, {
|
||||||
|
|||||||
376
api/src/eventemitter.d.ts
vendored
Normal file
376
api/src/eventemitter.d.ts
vendored
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
import { type OnEventOptions } from '@nestjs/event-emitter/dist/interfaces';
|
||||||
|
import type { TFilterQuery, HydratedDocument, Query, Document } from 'mongoose';
|
||||||
|
import { type Socket } from 'socket.io';
|
||||||
|
|
||||||
|
import { type BotStats } from '@/analytics/schemas/bot-stats.schema';
|
||||||
|
import { type Attachment } from '@/attachment/schemas/attachment.schema';
|
||||||
|
import type EventWrapper from '@/channel/lib/EventWrapper';
|
||||||
|
import { type Block, BlockFull } from '@/chat/schemas/block.schema';
|
||||||
|
import { type Category } from '@/chat/schemas/category.schema';
|
||||||
|
import { type ContextVar } from '@/chat/schemas/context-var.schema';
|
||||||
|
import { type Conversation } from '@/chat/schemas/conversation.schema';
|
||||||
|
import { LabelDocument, type Label } from '@/chat/schemas/label.schema';
|
||||||
|
import { type Message } from '@/chat/schemas/message.schema';
|
||||||
|
import { type Subscriber } from '@/chat/schemas/subscriber.schema';
|
||||||
|
import { type ContentType } from '@/cms/schemas/content-type.schema';
|
||||||
|
import { type Content } from '@/cms/schemas/content.schema';
|
||||||
|
import { type Menu } from '@/cms/schemas/menu.schema';
|
||||||
|
import { type Language } from '@/i18n/schemas/language.schema';
|
||||||
|
import { type Translation } from '@/i18n/schemas/translation.schema';
|
||||||
|
import type {
|
||||||
|
NlpEntity,
|
||||||
|
NlpEntityDocument,
|
||||||
|
} from '@/nlp/schemas/nlp-entity.schema';
|
||||||
|
import { type NlpSampleEntity } from '@/nlp/schemas/nlp-sample-entity.schema';
|
||||||
|
import { type NlpSample } from '@/nlp/schemas/nlp-sample.schema';
|
||||||
|
import {
|
||||||
|
NlpValueDocument,
|
||||||
|
type NlpValue,
|
||||||
|
} from '@/nlp/schemas/nlp-value.schema';
|
||||||
|
import { type Setting } from '@/setting/schemas/setting.schema';
|
||||||
|
import type { CheckboxSetting, TextSetting } from '@/setting/schemas/types';
|
||||||
|
import { type Invitation } from '@/user/schemas/invitation.schema';
|
||||||
|
import { type Model } from '@/user/schemas/model.schema';
|
||||||
|
import { type Permission } from '@/user/schemas/permission.schema';
|
||||||
|
import { type Role } from '@/user/schemas/role.schema';
|
||||||
|
import { type User } from '@/user/schemas/user.schema';
|
||||||
|
import { EHook, type DeleteResult } from '@/utils/generics/base-repository';
|
||||||
|
|
||||||
|
import { type SubscriberUpdateDto } from './chat/dto/subscriber.dto';
|
||||||
|
|
||||||
|
import '@nestjs/event-emitter';
|
||||||
|
/**
|
||||||
|
* @description Module declaration that extends the NestJS EventEmitter with custom event types and methods.
|
||||||
|
*/
|
||||||
|
declare module '@nestjs/event-emitter' {
|
||||||
|
interface TDefinition<S, O = object> {
|
||||||
|
schema: S;
|
||||||
|
operations: O;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IHookExtensionsOperationMap {
|
||||||
|
messenger: TDefinition<
|
||||||
|
object,
|
||||||
|
{
|
||||||
|
get_started_button: Setting;
|
||||||
|
access_token: Setting;
|
||||||
|
composer_input_disabled: CheckboxSetting;
|
||||||
|
greeting_text: TextSetting;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IHookSettingsGroupLabelOperationMap {
|
||||||
|
chatbot_settings: TDefinition<
|
||||||
|
object,
|
||||||
|
{
|
||||||
|
global_fallback: unknown;
|
||||||
|
fallback_block: unknown;
|
||||||
|
fallback_message: unknown;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
contact: TDefinition<
|
||||||
|
object,
|
||||||
|
{
|
||||||
|
contact_email_recipient: unknown;
|
||||||
|
company_name: unknown;
|
||||||
|
company_phone: unknown;
|
||||||
|
company_email: unknown;
|
||||||
|
company_address1: unknown;
|
||||||
|
company_address2: unknown;
|
||||||
|
company_city: unknown;
|
||||||
|
company_zipcode: unknown;
|
||||||
|
company_state: unknown;
|
||||||
|
company_country: unknown;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
nlp_settings: TDefinition<
|
||||||
|
object,
|
||||||
|
{
|
||||||
|
provider: unknown;
|
||||||
|
endpoint: unknown;
|
||||||
|
token: unknown;
|
||||||
|
threshold: number;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* custom hooks */
|
||||||
|
interface IHookOperationMap
|
||||||
|
extends IHookSettingsGroupLabelOperationMap,
|
||||||
|
IHookExtensionsOperationMap {
|
||||||
|
analytics: TDefinition<
|
||||||
|
object,
|
||||||
|
{
|
||||||
|
block: BlockFull;
|
||||||
|
passation: Subscriber;
|
||||||
|
'fallback-local': BlockFull;
|
||||||
|
'fallback-global': EventWrapper<any, any>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
chatbot: TDefinition<
|
||||||
|
object,
|
||||||
|
{
|
||||||
|
sent: unknown;
|
||||||
|
received: unknown;
|
||||||
|
message: unknown;
|
||||||
|
delivery: unknown;
|
||||||
|
read: unknown;
|
||||||
|
typing: unknown;
|
||||||
|
follow: unknown;
|
||||||
|
echo: unknown;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
websocket: TDefinition<
|
||||||
|
object,
|
||||||
|
{
|
||||||
|
connection: Socket;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* entities hooks */
|
||||||
|
interface IHookEntityOperationMap extends IHookOperationMap {
|
||||||
|
stats: TDefinition<BotStats, { entry: string }>;
|
||||||
|
attachment: TDefinition<Attachment>;
|
||||||
|
block: TDefinition<Block>;
|
||||||
|
category: TDefinition<Category>;
|
||||||
|
contextVar: TDefinition<ContextVar>;
|
||||||
|
conversation: TDefinition<Conversation, { end: unknown; close: unknown }>;
|
||||||
|
label: TDefinition<
|
||||||
|
Label,
|
||||||
|
{ create: LabelDocument; delete: Label | Label[] }
|
||||||
|
>;
|
||||||
|
message: TDefinition<Message>;
|
||||||
|
subscriber: TDefinition<Subscriber, { assign: SubscriberUpdateDto }>;
|
||||||
|
contentType: TDefinition<ContentType>;
|
||||||
|
content: TDefinition<Content>;
|
||||||
|
menu: TDefinition<Menu>;
|
||||||
|
language: TDefinition<Language, { delete: Language | Language[] }>;
|
||||||
|
translation: TDefinition<Translation>;
|
||||||
|
nlpEntity: TDefinition<
|
||||||
|
NlpEntity,
|
||||||
|
{
|
||||||
|
create: NlpEntityDocument;
|
||||||
|
update: NlpEntity;
|
||||||
|
delete: NlpEntity | NlpEntity[];
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
nlpSampleEntity: TDefinition<NlpSampleEntity>;
|
||||||
|
nlpSample: TDefinition<NlpSample>;
|
||||||
|
nlpValue: TDefinition<
|
||||||
|
NlpValue,
|
||||||
|
{
|
||||||
|
create: NlpValueDocument;
|
||||||
|
update: NlpValue;
|
||||||
|
delete: NlpValue | NlpValue[];
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
setting: TDefinition<Setting>;
|
||||||
|
invitation: TDefinition<Invitation>;
|
||||||
|
model: TDefinition<Model>;
|
||||||
|
permission: TDefinition<Permission>;
|
||||||
|
role: TDefinition<Role>;
|
||||||
|
user: TDefinition<User, { lastvisit: Subscriber }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description A constrained string type that allows specific string values while preserving type safety.
|
||||||
|
*/
|
||||||
|
type ConstrainedString = string & Record<never, never>;
|
||||||
|
type EventNamespaces = keyof IHookEntityOperationMap;
|
||||||
|
|
||||||
|
/* pre hooks */
|
||||||
|
type TPreValidate<T> = HydratedDocument<T>;
|
||||||
|
type TPreCreate<T> = HydratedDocument<T>;
|
||||||
|
type TPreUpdate<T> = TFilterQuery<T> & object;
|
||||||
|
type TPreDelete = Query<
|
||||||
|
DeleteResult,
|
||||||
|
Document<T>,
|
||||||
|
unknown,
|
||||||
|
T,
|
||||||
|
'deleteOne',
|
||||||
|
Record<string, never>
|
||||||
|
>;
|
||||||
|
type TPreUnion<T> =
|
||||||
|
| TPreValidate<T>
|
||||||
|
| TPreCreate<T>
|
||||||
|
| TPreUpdate<T>
|
||||||
|
| TPreDelete;
|
||||||
|
|
||||||
|
/* post hooks */
|
||||||
|
type TPostValidate<T> = HydratedDocument<T>;
|
||||||
|
type TPostCreate<T> = HydratedDocument<T>;
|
||||||
|
type TPostUpdate<T> = HydratedDocument<T>;
|
||||||
|
type TPostDelete = DeleteResult;
|
||||||
|
type TPostUnion<T> =
|
||||||
|
| TPostValidate<T>
|
||||||
|
| TPostCreate<T>
|
||||||
|
| TPostUpdate<T>
|
||||||
|
| TPostDelete;
|
||||||
|
|
||||||
|
/* union hooks */
|
||||||
|
type TUnion<G, E> = E extends keyof IHookOperationMap
|
||||||
|
? IHookEntityOperationMap[E]['operations'][keyof IHookEntityOperationMap[E]['operations']]
|
||||||
|
:
|
||||||
|
| TPreUnion<G>
|
||||||
|
| TPostUnion<G>
|
||||||
|
| IHookEntityOperationMap[E]['operations'][keyof IHookEntityOperationMap[E]['operations']];
|
||||||
|
|
||||||
|
type THookSplitter<H> = H extends `hook:${infer E}:${infer O}`
|
||||||
|
? [E, O]
|
||||||
|
: never;
|
||||||
|
|
||||||
|
/* Normalized hook */
|
||||||
|
enum EHookPrefix {
|
||||||
|
pre = 'pre',
|
||||||
|
post = 'post',
|
||||||
|
}
|
||||||
|
|
||||||
|
type TCompatibleHook<
|
||||||
|
P extends `${EHookPrefix}`,
|
||||||
|
T = `${EHook}`,
|
||||||
|
> = T extends `${P}${infer I}` ? `${P}${I}` : never;
|
||||||
|
|
||||||
|
type TPreHook = TCompatibleHook<EHookPrefix.pre>;
|
||||||
|
type TPostHook = TCompatibleHook<EHookPrefix.post>;
|
||||||
|
|
||||||
|
type hookTypes<O = never> = O extends keyof IHookOperationMap
|
||||||
|
? '*'
|
||||||
|
: '*' | TPreHook | TPostHook;
|
||||||
|
|
||||||
|
type TNormalizedPreHook<E, O> = O extends `${EHook.preValidate}`
|
||||||
|
? TPreValidate<IHookEntityOperationMap[E]['schema']>
|
||||||
|
: O extends `${EHook.preCreate}`
|
||||||
|
? TPreCreate<IHookEntityOperationMap[E]['schema']>
|
||||||
|
: O extends `${EHook.preUpdate}`
|
||||||
|
? TPreUpdate<IHookEntityOperationMap[E]['schema']>
|
||||||
|
: O extends `${EHook.preDelete}`
|
||||||
|
? TPreDelete
|
||||||
|
: never;
|
||||||
|
type TNormalizedPostHook<E, O> = O extends `${EHook.postValidate}`
|
||||||
|
? TPostValidate<IHookEntityOperationMap[E]['schema']>
|
||||||
|
: O extends `${EHook.postCreate}`
|
||||||
|
? TPostCreate<IHookEntityOperationMap[E]['schema']>
|
||||||
|
: O extends `${EHook.postUpdate}`
|
||||||
|
? TPostUpdate<IHookEntityOperationMap[E]['schema']>
|
||||||
|
: O extends `${EHook.postDelete}`
|
||||||
|
? TPostDelete
|
||||||
|
: never;
|
||||||
|
type TNormalizedHook<E, O> = TNormalizedPreHook<E, O> &
|
||||||
|
TNormalizedPostHook<E, O>;
|
||||||
|
|
||||||
|
/* Extended hook */
|
||||||
|
type TExtendedHook<E, O> = IHookEntityOperationMap[E]['operations'][O];
|
||||||
|
|
||||||
|
type EventValueOf<
|
||||||
|
G,
|
||||||
|
E = THookSplitter<G>[0],
|
||||||
|
O = THookSplitter<G>[1],
|
||||||
|
> = O extends '*'
|
||||||
|
? TUnion<G, E>
|
||||||
|
: O extends hookTypes
|
||||||
|
? TNormalizedHook<E, O>
|
||||||
|
: TExtendedHook<E, O>;
|
||||||
|
|
||||||
|
type IsHookEvent<G extends EventNamespaces> = G extends EventNamespaces
|
||||||
|
? true
|
||||||
|
: G extends `hook:${infer N}:${string}`
|
||||||
|
? N extends keyof IHookEntityOperationMap
|
||||||
|
? true
|
||||||
|
: false
|
||||||
|
: false;
|
||||||
|
|
||||||
|
type customEvent<G extends EventNamespaces> = G extends EventNamespaces
|
||||||
|
? G extends `hook:${string}`
|
||||||
|
? G
|
||||||
|
: `hook:${G}:${hookTypes<G> | keyof IHookEntityOperationMap[G]['operations']}`
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export interface ListenerFn<G extends EventNamespaces | ConstrainedString> {
|
||||||
|
(value: EventValueOf<G>, ...values: any[]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EventEmitter2 {
|
||||||
|
emit<G extends EventNamespaces | ConstrainedString, H extends G>(
|
||||||
|
customEvent: customEvent<G>,
|
||||||
|
value: EventValueOf<H>,
|
||||||
|
...values: any[]
|
||||||
|
): boolean;
|
||||||
|
|
||||||
|
emitAsync<G extends EventNamespaces | ConstrainedString, H extends G>(
|
||||||
|
customEvent: customEvent<G>,
|
||||||
|
value: EventValueOf<H>,
|
||||||
|
...values: any[]
|
||||||
|
): Promise<any[]>;
|
||||||
|
|
||||||
|
addListener<G extends EventNamespaces | ConstrainedString, H extends G>(
|
||||||
|
customEvent: customEvent<G>,
|
||||||
|
listener: ListenerFn<H>,
|
||||||
|
): this | Listener;
|
||||||
|
|
||||||
|
on<G extends EventNamespaces | ConstrainedString, H extends G>(
|
||||||
|
customEvent: customEvent<G>,
|
||||||
|
listener: ListenerFn<H>,
|
||||||
|
options?: boolean | OnOptions,
|
||||||
|
): this | Listener;
|
||||||
|
|
||||||
|
once<G extends EventNamespaces | ConstrainedString, H extends G>(
|
||||||
|
customEvent: customEvent<G>,
|
||||||
|
listener: ListenerFn<H>,
|
||||||
|
options?: true | OnOptions,
|
||||||
|
): this | Listener;
|
||||||
|
|
||||||
|
prependOnceListener<
|
||||||
|
G extends EventNamespaces | ConstrainedString,
|
||||||
|
H extends G,
|
||||||
|
>(
|
||||||
|
customEvent: customEvent<G>,
|
||||||
|
listener: ListenerFn<H>,
|
||||||
|
options?: boolean | OnOptions,
|
||||||
|
): this | Listener;
|
||||||
|
|
||||||
|
many<G extends EventNamespaces | ConstrainedString, H extends G>(
|
||||||
|
customEvent: customEvent<G>,
|
||||||
|
timesToListen: number,
|
||||||
|
listener: ListenerFn<H>,
|
||||||
|
options?: boolean | OnOptions,
|
||||||
|
): this | Listener;
|
||||||
|
|
||||||
|
prependMany<G extends EventNamespaces | ConstrainedString, H extends G>(
|
||||||
|
customEvent: customEvent<G>,
|
||||||
|
timesToListen: number,
|
||||||
|
listener: ListenerFn<H>,
|
||||||
|
options?: boolean | OnOptions,
|
||||||
|
): this | Listener;
|
||||||
|
|
||||||
|
removeListener<G extends EventNamespaces | ConstrainedString, H extends G>(
|
||||||
|
customEvent: customEvent<G>,
|
||||||
|
listener: ListenerFn<H>,
|
||||||
|
): this;
|
||||||
|
|
||||||
|
off<G extends EventNamespaces | ConstrainedString, H extends G>(
|
||||||
|
customEvent: customEvent<G>,
|
||||||
|
listener: ListenerFn<H>,
|
||||||
|
): this;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare type OnEventMethodDecorator<
|
||||||
|
G extends EventNamespaces | ConstrainedString,
|
||||||
|
> = <T, K extends keyof T>(
|
||||||
|
target: IsHookEvent<G> extends true
|
||||||
|
? [T[K]] extends [(params: EventValueOf<G>, ...rest: any[]) => any]
|
||||||
|
? T
|
||||||
|
: never
|
||||||
|
: T,
|
||||||
|
propertyKey: K,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export declare function OnEvent<
|
||||||
|
G extends EventNamespaces | ConstrainedString,
|
||||||
|
H extends G,
|
||||||
|
>(
|
||||||
|
event: customEvent<G>,
|
||||||
|
options?: OnEventOptions | undefined,
|
||||||
|
): OnEventMethodDecorator<H>;
|
||||||
|
}
|
||||||
@ -835,7 +835,7 @@ export default class OfflineHandler extends ChannelHandler {
|
|||||||
|
|
||||||
const type = event.getEventType();
|
const type = event.getEventType();
|
||||||
if (type) {
|
if (type) {
|
||||||
this.eventEmitter.emit('hook:chatbot:' + type, event);
|
this.eventEmitter.emit(`hook:chatbot:${type}`, event);
|
||||||
} else {
|
} else {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
'Offline Channel Handler : Webhook received unknown event ',
|
'Offline Channel Handler : Webhook received unknown event ',
|
||||||
|
|||||||
@ -38,11 +38,11 @@ export class LanguageRepository extends BaseRepository<Language> {
|
|||||||
Language,
|
Language,
|
||||||
'deleteOne' | 'deleteMany'
|
'deleteOne' | 'deleteMany'
|
||||||
>,
|
>,
|
||||||
criteria: TFilterQuery<Language>,
|
_criteria: TFilterQuery<Language>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (criteria._id) {
|
if (_criteria._id) {
|
||||||
const language = await this.findOne(
|
const language = await this.find(
|
||||||
typeof criteria === 'string' ? { _id: criteria } : criteria,
|
typeof _criteria === 'string' ? { _id: _criteria } : _criteria,
|
||||||
);
|
);
|
||||||
this.eventEmitter.emit('hook:language:delete', language);
|
this.eventEmitter.emit('hook:language:delete', language);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -20,9 +20,17 @@
|
|||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
import { LoggerService } from '@/logger/logger.service';
|
import { LoggerService } from '@/logger/logger.service';
|
||||||
import { NlpEntity, NlpEntityFull } from '@/nlp/schemas/nlp-entity.schema';
|
import {
|
||||||
|
NlpEntity,
|
||||||
|
NlpEntityDocument,
|
||||||
|
NlpEntityFull,
|
||||||
|
} from '@/nlp/schemas/nlp-entity.schema';
|
||||||
import { NlpSample, NlpSampleFull } from '@/nlp/schemas/nlp-sample.schema';
|
import { NlpSample, NlpSampleFull } from '@/nlp/schemas/nlp-sample.schema';
|
||||||
import { NlpValue, NlpValueFull } from '@/nlp/schemas/nlp-value.schema';
|
import {
|
||||||
|
NlpValue,
|
||||||
|
NlpValueDocument,
|
||||||
|
NlpValueFull,
|
||||||
|
} from '@/nlp/schemas/nlp-value.schema';
|
||||||
import { Settings } from '@/setting/schemas/types';
|
import { Settings } from '@/setting/schemas/types';
|
||||||
|
|
||||||
import { Nlp } from './types';
|
import { Nlp } from './types';
|
||||||
@ -68,7 +76,7 @@ export default abstract class BaseNlpHelper {
|
|||||||
* @param entity - The entity to add
|
* @param entity - The entity to add
|
||||||
* @returns The added entity otherwise an error
|
* @returns The added entity otherwise an error
|
||||||
*/
|
*/
|
||||||
addEntity(_entity: NlpEntity): Promise<string> {
|
addEntity(_entity: NlpEntityDocument): Promise<string> {
|
||||||
return new Promise((resolve, _reject) => {
|
return new Promise((resolve, _reject) => {
|
||||||
return resolve(uuidv4());
|
return resolve(uuidv4());
|
||||||
});
|
});
|
||||||
@ -103,7 +111,7 @@ export default abstract class BaseNlpHelper {
|
|||||||
*
|
*
|
||||||
* @returns The added value otherwise it should throw an error
|
* @returns The added value otherwise it should throw an error
|
||||||
*/
|
*/
|
||||||
addValue(_value: NlpValue): Promise<string> {
|
addValue(_value: NlpValueDocument): Promise<string> {
|
||||||
return new Promise((resolve, _reject) => {
|
return new Promise((resolve, _reject) => {
|
||||||
return resolve(uuidv4());
|
return resolve(uuidv4());
|
||||||
});
|
});
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { InjectModel } from '@nestjs/mongoose';
|
import { InjectModel } from '@nestjs/mongoose';
|
||||||
import { Document, Model, Query, TFilterQuery, Types } from 'mongoose';
|
import { Document, Model, Query, TFilterQuery } from 'mongoose';
|
||||||
|
|
||||||
import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository';
|
import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository';
|
||||||
|
|
||||||
@ -18,6 +18,7 @@ import { NlpValueRepository } from './nlp-value.repository';
|
|||||||
import {
|
import {
|
||||||
NLP_ENTITY_POPULATE,
|
NLP_ENTITY_POPULATE,
|
||||||
NlpEntity,
|
NlpEntity,
|
||||||
|
NlpEntityDocument,
|
||||||
NlpEntityFull,
|
NlpEntityFull,
|
||||||
NlpEntityPopulate,
|
NlpEntityPopulate,
|
||||||
} from '../schemas/nlp-entity.schema';
|
} from '../schemas/nlp-entity.schema';
|
||||||
@ -44,13 +45,10 @@ export class NlpEntityRepository extends BaseRepository<
|
|||||||
*
|
*
|
||||||
* @param created - The newly created NLP entity document.
|
* @param created - The newly created NLP entity document.
|
||||||
*/
|
*/
|
||||||
async postCreate(
|
async postCreate(_created: NlpEntityDocument): Promise<void> {
|
||||||
_created: Document<unknown, object, NlpEntity> &
|
if (!_created) {
|
||||||
NlpEntity & { _id: Types.ObjectId },
|
|
||||||
): Promise<void> {
|
|
||||||
if (!_created.builtin) {
|
|
||||||
// Bypass builtin entities (probably fixtures)
|
// Bypass builtin entities (probably fixtures)
|
||||||
this.eventEmitter.emit('hook:nlp:entity:create', _created);
|
this.eventEmitter.emit('hook:nlpEntity:create', _created);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +72,7 @@ export class NlpEntityRepository extends BaseRepository<
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!updated?.builtin) {
|
if (!updated?.builtin) {
|
||||||
// Bypass builtin entities (probably fixtures)
|
// Bypass builtin entities (probably fixtures)
|
||||||
this.eventEmitter.emit('hook:nlp:entity:update', updated);
|
this.eventEmitter.emit('hook:nlpEntity:update', updated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +105,7 @@ export class NlpEntityRepository extends BaseRepository<
|
|||||||
entities
|
entities
|
||||||
.filter((e) => !e.builtin)
|
.filter((e) => !e.builtin)
|
||||||
.map((e) => {
|
.map((e) => {
|
||||||
this.eventEmitter.emit('hook:nlp:entity:delete', e);
|
this.eventEmitter.emit('hook:nlpEntity:delete', e);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Attempted to delete NLP entity using unknown criteria');
|
throw new Error('Attempted to delete NLP entity using unknown criteria');
|
||||||
|
|||||||
@ -44,7 +44,7 @@ export class NlpValueRepository extends BaseRepository<
|
|||||||
async postCreate(created: NlpValueDocument): Promise<void> {
|
async postCreate(created: NlpValueDocument): Promise<void> {
|
||||||
if (!created.builtin) {
|
if (!created.builtin) {
|
||||||
// Bypass builtin entities (probably fixtures)
|
// Bypass builtin entities (probably fixtures)
|
||||||
this.eventEmitter.emit('hook:nlp:value:create', created);
|
this.eventEmitter.emit('hook:nlpValue:create', created);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ export class NlpValueRepository extends BaseRepository<
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!updated?.builtin) {
|
if (!updated?.builtin) {
|
||||||
// Bypass builtin entities (probably fixtures)
|
// Bypass builtin entities (probably fixtures)
|
||||||
this.eventEmitter.emit('hook:nlp:value:update', updated);
|
this.eventEmitter.emit('hook:nlpValue:update', updated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ export class NlpValueRepository extends BaseRepository<
|
|||||||
entities
|
entities
|
||||||
.filter((e) => !e.builtin)
|
.filter((e) => !e.builtin)
|
||||||
.map((e) => {
|
.map((e) => {
|
||||||
this.eventEmitter.emit('hook:nlp:value:delete', e);
|
this.eventEmitter.emit('hook:nlpValue:delete', e);
|
||||||
});
|
});
|
||||||
} else if (criteria.entity) {
|
} else if (criteria.entity) {
|
||||||
// Do nothing : cascading deletes coming from Nlp Sample Entity
|
// Do nothing : cascading deletes coming from Nlp Sample Entity
|
||||||
|
|||||||
@ -16,8 +16,8 @@ import { NlpEntityService } from './nlp-entity.service';
|
|||||||
import { NlpSampleService } from './nlp-sample.service';
|
import { NlpSampleService } from './nlp-sample.service';
|
||||||
import { NlpValueService } from './nlp-value.service';
|
import { NlpValueService } from './nlp-value.service';
|
||||||
import BaseNlpHelper from '../lib/BaseNlpHelper';
|
import BaseNlpHelper from '../lib/BaseNlpHelper';
|
||||||
import { NlpEntity } from '../schemas/nlp-entity.schema';
|
import { NlpEntity, NlpEntityDocument } from '../schemas/nlp-entity.schema';
|
||||||
import { NlpValue } from '../schemas/nlp-value.schema';
|
import { NlpValue, NlpValueDocument } from '../schemas/nlp-value.schema';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NlpService implements OnApplicationBootstrap {
|
export class NlpService implements OnApplicationBootstrap {
|
||||||
@ -102,7 +102,7 @@ export class NlpService implements OnApplicationBootstrap {
|
|||||||
/**
|
/**
|
||||||
* Handles the event triggered when NLP settings are updated. Re-initializes the NLP service.
|
* Handles the event triggered when NLP settings are updated. Re-initializes the NLP service.
|
||||||
*/
|
*/
|
||||||
@OnEvent('hook:settings:nlp_settings:*')
|
@OnEvent('hook:nlp_settings:*')
|
||||||
async handleSettingsUpdate() {
|
async handleSettingsUpdate() {
|
||||||
this.initNLP();
|
this.initNLP();
|
||||||
}
|
}
|
||||||
@ -113,13 +113,13 @@ export class NlpService implements OnApplicationBootstrap {
|
|||||||
* @param entity - The NLP entity to be created.
|
* @param entity - The NLP entity to be created.
|
||||||
* @returns The updated entity after synchronization.
|
* @returns The updated entity after synchronization.
|
||||||
*/
|
*/
|
||||||
@OnEvent('hook:nlp:entity:create')
|
@OnEvent('hook:nlpEntity:create')
|
||||||
async handleEntityCreate(entity: NlpEntity) {
|
async handleEntityCreate(entity: NlpEntityDocument) {
|
||||||
// Synchonize new entity with NLP
|
// Synchonize new entity with NLP
|
||||||
try {
|
try {
|
||||||
const foreignId = await this.getNLP().addEntity(entity);
|
const foreignId = await this.getNLP().addEntity(entity);
|
||||||
this.logger.debug('New entity successfully synced!', foreignId);
|
this.logger.debug('New entity successfully synced!', foreignId);
|
||||||
return await this.nlpEntityService.updateOne(entity.id, {
|
return await this.nlpEntityService.updateOne(entity._id, {
|
||||||
foreign_id: foreignId,
|
foreign_id: foreignId,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -133,7 +133,7 @@ export class NlpService implements OnApplicationBootstrap {
|
|||||||
*
|
*
|
||||||
* @param entity - The NLP entity to be updated.
|
* @param entity - The NLP entity to be updated.
|
||||||
*/
|
*/
|
||||||
@OnEvent('hook:nlp:entity:update')
|
@OnEvent('hook:nlpEntity:update')
|
||||||
async handleEntityUpdate(entity: NlpEntity) {
|
async handleEntityUpdate(entity: NlpEntity) {
|
||||||
// Synchonize new entity with NLP provider
|
// Synchonize new entity with NLP provider
|
||||||
try {
|
try {
|
||||||
@ -149,7 +149,7 @@ export class NlpService implements OnApplicationBootstrap {
|
|||||||
*
|
*
|
||||||
* @param entity - The NLP entity to be deleted.
|
* @param entity - The NLP entity to be deleted.
|
||||||
*/
|
*/
|
||||||
@OnEvent('hook:nlp:entity:delete')
|
@OnEvent('hook:nlpEntity:delete')
|
||||||
async handleEntityDelete(entity: NlpEntity) {
|
async handleEntityDelete(entity: NlpEntity) {
|
||||||
// Synchonize new entity with NLP provider
|
// Synchonize new entity with NLP provider
|
||||||
try {
|
try {
|
||||||
@ -167,13 +167,13 @@ export class NlpService implements OnApplicationBootstrap {
|
|||||||
*
|
*
|
||||||
* @returns The updated value after synchronization.
|
* @returns The updated value after synchronization.
|
||||||
*/
|
*/
|
||||||
@OnEvent('hook:nlp:value:create')
|
@OnEvent('hook:nlpValue:create')
|
||||||
async handleValueCreate(value: NlpValue) {
|
async handleValueCreate(value: NlpValueDocument) {
|
||||||
// Synchonize new value with NLP provider
|
// Synchonize new value with NLP provider
|
||||||
try {
|
try {
|
||||||
const foreignId = await this.getNLP().addValue(value);
|
const foreignId = await this.getNLP().addValue(value);
|
||||||
this.logger.debug('New value successfully synced!', foreignId);
|
this.logger.debug('New value successfully synced!', foreignId);
|
||||||
return await this.nlpValueService.updateOne(value.id, {
|
return await this.nlpValueService.updateOne(value._id, {
|
||||||
foreign_id: foreignId,
|
foreign_id: foreignId,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -187,7 +187,7 @@ export class NlpService implements OnApplicationBootstrap {
|
|||||||
*
|
*
|
||||||
* @param value - The NLP value to be updated.
|
* @param value - The NLP value to be updated.
|
||||||
*/
|
*/
|
||||||
@OnEvent('hook:nlp:value:update')
|
@OnEvent('hook:nlpValue:update')
|
||||||
async handleValueUpdate(value: NlpValue) {
|
async handleValueUpdate(value: NlpValue) {
|
||||||
// Synchonize new value with NLP provider
|
// Synchonize new value with NLP provider
|
||||||
try {
|
try {
|
||||||
@ -203,7 +203,7 @@ export class NlpService implements OnApplicationBootstrap {
|
|||||||
*
|
*
|
||||||
* @param value - The NLP value to be deleted.
|
* @param value - The NLP value to be deleted.
|
||||||
*/
|
*/
|
||||||
@OnEvent('hook:nlp:value:delete')
|
@OnEvent('hook:nlpValue:delete')
|
||||||
async handleValueDelete(value: NlpValue) {
|
async handleValueDelete(value: NlpValue) {
|
||||||
// Synchonize new value with NLP provider
|
// Synchonize new value with NLP provider
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -78,10 +78,9 @@ export class SettingRepository extends BaseRepository<Setting> {
|
|||||||
>,
|
>,
|
||||||
setting: Setting,
|
setting: Setting,
|
||||||
) {
|
) {
|
||||||
|
const group = setting.group as any;
|
||||||
|
const label = setting.label as string;
|
||||||
// Sync global settings var
|
// Sync global settings var
|
||||||
this.eventEmitter.emit(
|
this.eventEmitter.emit(`hook:${group}:${label}`, setting as any);
|
||||||
'hook:settings:' + setting.group + ':' + setting.label,
|
|
||||||
setting,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -107,10 +107,10 @@ export class SettingService extends BaseService<Setting> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event handler for setting updates. Listens to 'hook:settings:*:*' events
|
* Event handler for setting updates. Listens to 'hook:setting:*' events
|
||||||
* and invalidates the cache for settings when triggered.
|
* and invalidates the cache for settings when triggered.
|
||||||
*/
|
*/
|
||||||
@OnEvent('hook:settings:*:*')
|
@OnEvent('hook:setting:*')
|
||||||
async handleSettingUpdateEvent() {
|
async handleSettingUpdateEvent() {
|
||||||
this.cacheManager.del(SETTING_CACHE_KEY);
|
this.cacheManager.del(SETTING_CACHE_KEY);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,9 +33,11 @@ export enum EHook {
|
|||||||
preCreate = 'preCreate',
|
preCreate = 'preCreate',
|
||||||
preUpdate = 'preUpdate',
|
preUpdate = 'preUpdate',
|
||||||
preDelete = 'preDelete',
|
preDelete = 'preDelete',
|
||||||
|
preValidate = 'preValidate',
|
||||||
postCreate = 'postCreate',
|
postCreate = 'postCreate',
|
||||||
postUpdate = 'postUpdate',
|
postUpdate = 'postUpdate',
|
||||||
postDelete = 'postDelete',
|
postDelete = 'postDelete',
|
||||||
|
postValidate = 'postValidate',
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class BaseRepository<
|
export abstract class BaseRepository<
|
||||||
@ -63,8 +65,8 @@ export abstract class BaseRepository<
|
|||||||
return this.populate;
|
return this.populate;
|
||||||
}
|
}
|
||||||
|
|
||||||
getEventName(suffix: EHook) {
|
getEventName(suffix: EHook): any {
|
||||||
return `hook:${this.cls.name.toLocaleLowerCase()}:${suffix.toLocaleLowerCase()}`;
|
return `hook:${this.cls.name.toLocaleLowerCase()}:${suffix}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerLifeCycleHooks() {
|
private registerLifeCycleHooks() {
|
||||||
@ -74,34 +76,50 @@ export abstract class BaseRepository<
|
|||||||
hooks?.validate.pre.execute(async function () {
|
hooks?.validate.pre.execute(async function () {
|
||||||
const doc = this as HydratedDocument<T>;
|
const doc = this as HydratedDocument<T>;
|
||||||
await repository.preValidate(doc);
|
await repository.preValidate(doc);
|
||||||
|
repository.emitter.emit(repository.getEventName(EHook.preValidate), doc);
|
||||||
});
|
});
|
||||||
|
|
||||||
hooks?.validate.post.execute(async function (created: HydratedDocument<T>) {
|
hooks?.validate.post.execute(async function (created: HydratedDocument<T>) {
|
||||||
await repository.postValidate(created);
|
await repository.postValidate(created);
|
||||||
|
repository.emitter.emit(
|
||||||
|
repository.getEventName(EHook.postValidate),
|
||||||
|
created,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
hooks?.save.pre.execute(async function () {
|
hooks?.save.pre.execute(async function () {
|
||||||
const doc = this as HydratedDocument<T>;
|
const doc = this as HydratedDocument<T>;
|
||||||
await repository.preCreate(doc);
|
await repository.preCreate(doc);
|
||||||
|
repository.emitter.emit(repository.getEventName(EHook.preCreate), doc);
|
||||||
});
|
});
|
||||||
|
|
||||||
hooks?.save.post.execute(async function (created: HydratedDocument<T>) {
|
hooks?.save.post.execute(async function (created: HydratedDocument<T>) {
|
||||||
|
await repository.postCreate(created);
|
||||||
repository.emitter.emit(
|
repository.emitter.emit(
|
||||||
repository.getEventName(EHook.postCreate),
|
repository.getEventName(EHook.postCreate),
|
||||||
created,
|
created,
|
||||||
);
|
);
|
||||||
await repository.postCreate(created);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
hooks?.deleteOne.pre.execute(async function () {
|
hooks?.deleteOne.pre.execute(async function () {
|
||||||
const query = this as Query<DeleteResult, D, unknown, T, 'deleteOne'>;
|
const query = this as Query<DeleteResult, D, unknown, T, 'deleteOne'>;
|
||||||
const criteria = query.getQuery();
|
const criteria = query.getQuery();
|
||||||
await repository.preDelete(query, criteria);
|
await repository.preDelete(query, criteria);
|
||||||
|
repository.emitter.emit(
|
||||||
|
repository.getEventName(EHook.preDelete),
|
||||||
|
query as any,
|
||||||
|
criteria,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
hooks?.deleteOne.post.execute(async function (result: DeleteResult) {
|
hooks?.deleteOne.post.execute(async function (result: DeleteResult) {
|
||||||
const query = this as Query<DeleteResult, D, unknown, T, 'deleteOne'>;
|
const query = this as Query<DeleteResult, D, unknown, T, 'deleteOne'>;
|
||||||
await repository.postDelete(query, result);
|
await repository.postDelete(query, result);
|
||||||
|
repository.emitter.emit(
|
||||||
|
repository.getEventName(EHook.postDelete),
|
||||||
|
query as any,
|
||||||
|
result,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
hooks?.deleteMany.pre.execute(async function () {
|
hooks?.deleteMany.pre.execute(async function () {
|
||||||
@ -113,7 +131,7 @@ export abstract class BaseRepository<
|
|||||||
hooks?.deleteMany.post.execute(async function (result: DeleteResult) {
|
hooks?.deleteMany.post.execute(async function (result: DeleteResult) {
|
||||||
repository.emitter.emit(
|
repository.emitter.emit(
|
||||||
repository.getEventName(EHook.postDelete),
|
repository.getEventName(EHook.postDelete),
|
||||||
result,
|
result as any,
|
||||||
);
|
);
|
||||||
const query = this as Query<DeleteResult, D, unknown, T, 'deleteMany'>;
|
const query = this as Query<DeleteResult, D, unknown, T, 'deleteMany'>;
|
||||||
await repository.postDelete(query, result);
|
await repository.postDelete(query, result);
|
||||||
@ -124,27 +142,27 @@ export abstract class BaseRepository<
|
|||||||
const criteria = query.getFilter();
|
const criteria = query.getFilter();
|
||||||
const updates = query.getUpdate();
|
const updates = query.getUpdate();
|
||||||
|
|
||||||
|
await repository.preUpdate(query, criteria, updates);
|
||||||
repository.emitter.emit(
|
repository.emitter.emit(
|
||||||
repository.getEventName(EHook.preUpdate),
|
repository.getEventName(EHook.preUpdate),
|
||||||
criteria,
|
criteria as any,
|
||||||
updates?.['$set'],
|
updates?.['$set'],
|
||||||
);
|
);
|
||||||
await repository.preUpdate(query, criteria, updates);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
hooks?.findOneAndUpdate.post.execute(async function (
|
hooks?.findOneAndUpdate.post.execute(async function (
|
||||||
updated: HydratedDocument<T>,
|
updated: HydratedDocument<T>,
|
||||||
) {
|
) {
|
||||||
if (updated) {
|
if (updated) {
|
||||||
repository.emitter.emit(
|
|
||||||
repository.getEventName(EHook.postUpdate),
|
|
||||||
updated,
|
|
||||||
);
|
|
||||||
const query = this as Query<D, D, unknown, T, 'findOneAndUpdate'>;
|
const query = this as Query<D, D, unknown, T, 'findOneAndUpdate'>;
|
||||||
await repository.postUpdate(
|
await repository.postUpdate(
|
||||||
query,
|
query,
|
||||||
plainToClass(repository.cls, updated, repository.transformOpts),
|
plainToClass(repository.cls, updated, repository.transformOpts),
|
||||||
);
|
);
|
||||||
|
repository.emitter.emit(
|
||||||
|
repository.getEventName(EHook.postUpdate),
|
||||||
|
updated,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user