Merge pull request #1128 from Hexastack/1086-api-make-sure-hookentityprepostdelete-is-used-across-the-board

fix(api): resolve predefined delete hooks bug [main<-1086]
This commit is contained in:
Med Marrouchi 2025-06-18 16:01:03 +01:00 committed by GitHub
commit ada1989236
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 276 additions and 227 deletions

View File

@ -91,7 +91,7 @@ describe('CategoryController', () => {
{
provide: BlockService,
useValue: {
findOne: jest.fn(),
findOneAndPopulate: jest.fn(),
},
},
],

View File

@ -24,14 +24,11 @@ export class CategoryRepository extends BaseRepository<
never,
CategoryDto
> {
private readonly blockService: BlockService;
constructor(
@InjectModel(Category.name) readonly model: Model<Category>,
@Optional() blockService?: BlockService,
@Optional() private readonly blockService?: BlockService,
) {
super(model, Category);
this.blockService = blockService!;
}
/**
@ -42,7 +39,7 @@ export class CategoryRepository extends BaseRepository<
* @param criteria - The filter criteria for finding blocks to delete.
*/
async preDelete(
query: Query<
_query: Query<
DeleteResult,
Document<Category, any, any>,
unknown,
@ -51,23 +48,18 @@ export class CategoryRepository extends BaseRepository<
>,
criteria: TFilterQuery<Category>,
) {
criteria = query.getQuery();
const ids = Array.isArray(criteria._id?.$in)
? criteria._id.$in
: Array.isArray(criteria._id)
? criteria._id
: [criteria._id];
for (const id of ids) {
const associatedBlocks = await this.blockService.findOne({
category: id,
if (criteria._id) {
const block = await this.blockService?.findOneAndPopulate({
category: criteria._id,
});
if (associatedBlocks) {
const category = await this.findOne({ _id: id });
if (block) {
throw new ForbiddenException(
`Category ${category?.label || id} has blocks associated with it`,
`Category ${block.category?.label} has at least one associated block`,
);
}
} else {
throw new Error('Attempted to delete category using unknown criteria');
}
}
}

View File

@ -8,10 +8,9 @@
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Document, Model, Query } from 'mongoose';
import { Model } from 'mongoose';
import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository';
import { TFilterQuery } from '@/utils/types/filter.types';
import { BaseRepository } from '@/utils/generics/base-repository';
import { LabelDto } from '../dto/label.dto';
import {
@ -59,32 +58,4 @@ export class LabelRepository extends BaseRepository<
},
);
}
/**
* Before deleting a label, this method fetches the label(s) based on the given criteria and emits a delete event.
*
* @param query - The Mongoose query object used for deletion.
* @param criteria - The filter criteria for finding the labels to be deleted.
*
* @returns {Promise<void>} A promise that resolves once the event is emitted.
*/
async preDelete(
_query: Query<
DeleteResult,
Document<Label, any, any>,
unknown,
Label,
'deleteOne' | 'deleteMany'
>,
_criteria: TFilterQuery<Label>,
): Promise<void> {
const ids = Array.isArray(_criteria._id?.$in)
? _criteria._id.$in
: Array.isArray(_criteria._id)
? _criteria._id
: [_criteria._id];
const labels = await this.find({ _id: { $in: ids } });
this.eventEmitter.emit('hook:label:delete', labels);
}
}

View File

@ -22,6 +22,7 @@ import { SettingService } from '@/setting/services/setting.service';
import { FALLBACK_DEFAULT_NLU_PENALTY_FACTOR } from '@/utils/constants/nlp';
import { BaseService } from '@/utils/generics/base-service';
import { getRandomElement } from '@/utils/helpers/safeRandom';
import { TFilterQuery } from '@/utils/types/filter.types';
import { getDefaultFallbackOptions } from '../constants/block';
import { BlockDto } from '../dto/block.dto';
@ -790,28 +791,31 @@ export class BlockService extends BaseService<
/**
* Updates the `trigger_labels` and `assign_labels` fields of a block when a label is deleted.
*
*
* This method removes the deleted label from the `trigger_labels` and `assign_labels` fields of all blocks that have the label.
*
* @param label The label that is being deleted.
* @param _query - The Mongoose query object used for deletion.
* @param criteria - The filter criteria for finding the labels to be deleted.
*/
@OnEvent('hook:label:delete')
async handleLabelDelete(labels: Label[]) {
const blocks = await this.find({
$or: [
{ trigger_labels: { $in: labels.map((l) => l.id) } },
{ assign_labels: { $in: labels.map((l) => l.id) } },
],
});
for (const block of blocks) {
const trigger_labels = block.trigger_labels.filter(
(labelId) => !labels.find((l) => l.id === labelId),
@OnEvent('hook:label:preDelete')
async handleLabelPreDelete(
_query: unknown,
criteria: TFilterQuery<Label>,
): Promise<void> {
if (criteria._id) {
await this.getRepository().model.updateMany(
{
$or: [
{ trigger_labels: criteria._id },
{ assign_labels: criteria._id },
],
},
{
$pull: {
trigger_labels: criteria._id,
assign_labels: criteria._id,
},
},
);
const assign_labels = block.assign_labels.filter(
(labelId) => !labels.find((l) => l.id === labelId),
);
await this.updateOne(block.id, { trigger_labels, assign_labels });
} else {
throw new Error('Attempted to delete label using unknown criteria');
}
}
}

View File

@ -24,6 +24,7 @@ import {
} from '@/attachment/types';
import { config } from '@/config';
import { BaseService } from '@/utils/generics/base-service';
import { TFilterQuery } from '@/utils/types/filter.types';
import {
SocketGet,
SocketPost,
@ -260,22 +261,23 @@ export class SubscriberService extends BaseService<
}
/**
* Updates the `labels` field of a subscriber when a label is deleted.
* Before deleting a `Label`, this method updates the `labels` field of a subscriber.
*
* This method removes the deleted label from the `labels` field of all subscribers that have the label.
*
* @param label The label that is being deleted.
* @param _query - The Mongoose query object used for deletion.
* @param criteria - The filter criteria for finding the labels to be deleted.
*/
@OnEvent('hook:label:delete')
async handleLabelDelete(labels: Label[]) {
const subscribers = await this.find({
labels: { $in: labels.map((l) => l.id) },
});
for (const subscriber of subscribers) {
const updatedLabels = subscriber.labels.filter(
(label) => !labels.find((l) => l.id === label),
@OnEvent('hook:label:preDelete')
async handleLabelDelete(
_query: unknown,
criteria: TFilterQuery<Label>,
): Promise<void> {
if (criteria._id) {
await this.getRepository().model.updateMany(
{ labels: criteria._id },
{ $pull: { labels: criteria._id } },
);
await this.updateOne(subscriber.id, { labels: updatedLabels });
} else {
throw new Error('Attempted to delete label using unknown criteria');
}
}
}

View File

@ -52,14 +52,15 @@ export class ContentTypeRepository extends BaseRepository<
>,
criteria: TFilterQuery<ContentType>,
) {
const entityId: string = criteria._id as string;
const associatedBlocks = await this.blockService?.findOne({
'options.content.entity': entityId,
});
if (associatedBlocks) {
throw new ForbiddenException(`Content type have blocks associated to it`);
}
if (criteria._id) {
const associatedBlock = await this.blockService?.findOne({
'options.content.entity': criteria._id,
});
if (associatedBlock) {
throw new ForbiddenException(
'Content type have blocks associated to it',
);
}
await this.contentModel.deleteMany({ entity: criteria._id });
} else {
throw new Error(

View File

@ -6,8 +6,18 @@
* 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 { CACHE_MANAGER } from '@nestjs/cache-manager';
import { MongooseModule } from '@nestjs/mongoose';
import LlmNluHelper from '@/extensions/helpers/llm-nlu/index.helper';
import { HelperService } from '@/helper/helper.service';
import { LanguageRepository } from '@/i18n/repositories/language.repository';
import { LanguageModel } from '@/i18n/schemas/language.schema';
import { LanguageService } from '@/i18n/services/language.service';
import { SettingRepository } from '@/setting/repositories/setting.repository';
import { SettingModel } from '@/setting/schemas/setting.schema';
import { SettingSeeder } from '@/setting/seeds/setting.seed';
import { SettingService } from '@/setting/services/setting.service';
import { nlpEntityFixtures } from '@/utils/test/fixtures/nlpentity';
import { installNlpValueFixtures } from '@/utils/test/fixtures/nlpvalue';
import { getPageQuery } from '@/utils/test/pagination';
@ -19,40 +29,85 @@ import { buildTestingMocks } from '@/utils/test/utils';
import { NlpEntity, NlpEntityModel } from '../schemas/nlp-entity.schema';
import { NlpSampleEntityModel } from '../schemas/nlp-sample-entity.schema';
import { NlpSampleModel } from '../schemas/nlp-sample.schema';
import { NlpValueModel } from '../schemas/nlp-value.schema';
import { NlpEntityService } from '../services/nlp-entity.service';
import { NlpSampleEntityService } from '../services/nlp-sample-entity.service';
import { NlpSampleService } from '../services/nlp-sample.service';
import { NlpValueService } from '../services/nlp-value.service';
import { NlpService } from '../services/nlp.service';
import { NlpEntityRepository } from './nlp-entity.repository';
import { NlpSampleEntityRepository } from './nlp-sample-entity.repository';
import { NlpSampleRepository } from './nlp-sample.repository';
import { NlpValueRepository } from './nlp-value.repository';
describe('NlpEntityRepository', () => {
let nlpEntityRepository: NlpEntityRepository;
let nlpValueRepository: NlpValueRepository;
let firstNameNlpEntity: NlpEntity | null;
let nlpService: NlpService;
beforeAll(async () => {
const { getMocks } = await buildTestingMocks({
const { getMocks, module } = await buildTestingMocks({
imports: [
rootMongooseTestModule(installNlpValueFixtures),
MongooseModule.forFeature([
NlpEntityModel,
NlpValueModel,
NlpSampleEntityModel,
NlpSampleModel,
LanguageModel,
SettingModel,
]),
],
providers: [
HelperService,
NlpEntityRepository,
NlpValueRepository,
NlpSampleEntityRepository,
NlpService,
NlpSampleService,
NlpEntityService,
NlpValueService,
{
provide: CACHE_MANAGER,
useValue: {
del: jest.fn(),
get: jest.fn(),
set: jest.fn(),
},
},
NlpSampleEntityService,
NlpSampleRepository,
LanguageService,
{
provide: SettingService,
useValue: {
getSettings: jest.fn(() => ({
chatbot_settings: {
default_nlu_helper: 'llm-nlu-helper',
},
})),
},
},
LanguageRepository,
SettingRepository,
SettingSeeder,
LlmNluHelper,
],
});
[nlpEntityRepository, nlpValueRepository] = await getMocks([
[nlpEntityRepository, nlpValueRepository, nlpService] = await getMocks([
NlpEntityRepository,
NlpValueRepository,
NlpService,
]);
firstNameNlpEntity = await nlpEntityRepository.findOne({
name: 'firstname',
});
const llmNluHelper = module.get(LlmNluHelper);
module.get(HelperService).register(llmNluHelper);
});
afterAll(closeInMongodConnection);
@ -61,6 +116,12 @@ describe('NlpEntityRepository', () => {
describe('The deleteCascadeOne function', () => {
it('should delete a nlp entity', async () => {
nlpValueRepository.eventEmitter.once(
'hook:nlpEntity:preDelete',
async (...[query, criteria]) => {
await nlpService.handleEntityDelete(query, criteria);
},
);
const intentNlpEntity = await nlpEntityRepository.findOne({
name: 'intent',
});

View File

@ -10,8 +10,7 @@ import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Document, Model, Query } from 'mongoose';
import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository';
import { TFilterQuery } from '@/utils/types/filter.types';
import { BaseRepository } from '@/utils/generics/base-repository';
import { NlpEntityDto } from '../dto/nlp-entity.dto';
import {
@ -22,9 +21,6 @@ import {
NlpEntityPopulate,
} from '../schemas/nlp-entity.schema';
import { NlpSampleEntityRepository } from './nlp-sample-entity.repository';
import { NlpValueRepository } from './nlp-value.repository';
@Injectable()
export class NlpEntityRepository extends BaseRepository<
NlpEntity,
@ -32,11 +28,7 @@ export class NlpEntityRepository extends BaseRepository<
NlpEntityFull,
NlpEntityDto
> {
constructor(
@InjectModel(NlpEntity.name) readonly model: Model<NlpEntity>,
private readonly nlpValueRepository: NlpValueRepository,
private readonly nlpSampleEntityRepository: NlpSampleEntityRepository,
) {
constructor(@InjectModel(NlpEntity.name) readonly model: Model<NlpEntity>) {
super(model, NlpEntity, NLP_ENTITY_POPULATE, NlpEntityFull);
}
@ -77,40 +69,4 @@ export class NlpEntityRepository extends BaseRepository<
this.eventEmitter.emit('hook:nlpEntity:update', updated);
}
}
/**
* Pre-delete hook that triggers before an NLP entity is deleted.
* Deletes related NLP values and sample entities before the entity deletion.
* Emits an event to notify other parts of the system about the deletion.
* Bypasses built-in entities.
*
* @param query The query used to delete the entity.
* @param criteria The filter criteria used to find the entity for deletion.
*/
async preDelete(
_query: Query<
DeleteResult,
Document<NlpEntity, any, any>,
unknown,
NlpEntity,
'deleteOne' | 'deleteMany'
>,
criteria: TFilterQuery<NlpEntity>,
): Promise<void> {
if (criteria._id) {
await this.nlpValueRepository.deleteMany({ entity: criteria._id });
await this.nlpSampleEntityRepository.deleteMany({ entity: criteria._id });
const entities = await this.find(
typeof criteria === 'string' ? { _id: criteria } : criteria,
);
entities
.filter((e) => !e.builtin)
.map((e) => {
this.eventEmitter.emit('hook:nlpEntity:delete', e);
});
} else {
throw new Error('Attempted to delete NLP entity using unknown criteria');
}
}
}

View File

@ -24,6 +24,7 @@ import { PageQueryDto } from '@/utils/pagination/pagination-query.dto';
import { TFilterQuery } from '@/utils/types/filter.types';
import { TNlpSampleDto } from '../dto/nlp-sample.dto';
import { NlpSampleEntity } from '../schemas/nlp-sample-entity.schema';
import {
NLP_SAMPLE_POPULATE,
NlpSample,
@ -33,8 +34,6 @@ import {
} from '../schemas/nlp-sample.schema';
import { NlpValue } from '../schemas/nlp-value.schema';
import { NlpSampleEntityRepository } from './nlp-sample-entity.repository';
@Injectable()
export class NlpSampleRepository extends BaseRepository<
NlpSample,
@ -44,7 +43,8 @@ export class NlpSampleRepository extends BaseRepository<
> {
constructor(
@InjectModel(NlpSample.name) readonly model: Model<NlpSample>,
private readonly nlpSampleEntityRepository: NlpSampleEntityRepository,
@InjectModel(NlpSampleEntity.name)
private readonly nlpSampleEntityModel: Model<NlpSampleEntity>,
) {
super(model, NlpSample, NLP_SAMPLE_POPULATE, NlpSampleFull);
}
@ -310,7 +310,7 @@ export class NlpSampleRepository extends BaseRepository<
criteria: TFilterQuery<NlpSample>,
) {
if (criteria._id) {
await this.nlpSampleEntityRepository.deleteMany({
await this.nlpSampleEntityModel.deleteMany({
sample: criteria._id,
});
} else {

View File

@ -6,8 +6,18 @@
* 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 { CACHE_MANAGER } from '@nestjs/cache-manager';
import { MongooseModule } from '@nestjs/mongoose';
import LlmNluHelper from '@/extensions/helpers/llm-nlu/index.helper';
import { HelperService } from '@/helper/helper.service';
import { LanguageRepository } from '@/i18n/repositories/language.repository';
import { LanguageModel } from '@/i18n/schemas/language.schema';
import { LanguageService } from '@/i18n/services/language.service';
import { SettingRepository } from '@/setting/repositories/setting.repository';
import { SettingModel } from '@/setting/schemas/setting.schema';
import { SettingSeeder } from '@/setting/seeds/setting.seed';
import { SettingService } from '@/setting/services/setting.service';
import { nlpEntityFixtures } from '@/utils/test/fixtures/nlpentity';
import { installNlpSampleEntityFixtures } from '@/utils/test/fixtures/nlpsampleentity';
import { nlpValueFixtures } from '@/utils/test/fixtures/nlpvalue';
@ -21,38 +31,86 @@ import { buildTestingMocks } from '@/utils/test/utils';
import { NlpEntityModel } from '../schemas/nlp-entity.schema';
import { NlpSampleEntityModel } from '../schemas/nlp-sample-entity.schema';
import { NlpSampleModel } from '../schemas/nlp-sample.schema';
import {
NlpValue,
NlpValueFull,
NlpValueModel,
} from '../schemas/nlp-value.schema';
import { NlpEntityService } from '../services/nlp-entity.service';
import { NlpSampleEntityService } from '../services/nlp-sample-entity.service';
import { NlpSampleService } from '../services/nlp-sample.service';
import { NlpValueService } from '../services/nlp-value.service';
import { NlpService } from '../services/nlp.service';
import { NlpEntityRepository } from './nlp-entity.repository';
import { NlpSampleEntityRepository } from './nlp-sample-entity.repository';
import { NlpSampleRepository } from './nlp-sample.repository';
import { NlpValueRepository } from './nlp-value.repository';
describe('NlpValueRepository', () => {
let nlpValueRepository: NlpValueRepository;
let nlpSampleEntityRepository: NlpSampleEntityRepository;
let nlpValues: NlpValue[];
let nlpService: NlpService;
beforeAll(async () => {
const { getMocks } = await buildTestingMocks({
const { getMocks, module } = await buildTestingMocks({
imports: [
rootMongooseTestModule(installNlpSampleEntityFixtures),
MongooseModule.forFeature([
NlpValueModel,
NlpSampleEntityModel,
NlpEntityModel,
LanguageModel,
SettingModel,
NlpSampleModel,
]),
],
providers: [NlpValueRepository, NlpSampleEntityRepository],
providers: [
LanguageService,
LanguageRepository,
{
provide: CACHE_MANAGER,
useValue: {
set: jest.fn(),
},
},
NlpService,
NlpSampleService,
NlpEntityService,
NlpValueService,
NlpValueRepository,
NlpEntityRepository,
NlpSampleEntityService,
NlpSampleRepository,
NlpSampleEntityRepository,
HelperService,
{
provide: SettingService,
useValue: {
getSettings: jest.fn(() => ({
chatbot_settings: {
default_nlu_helper: 'llm-nlu-helper',
},
})),
},
},
SettingRepository,
SettingSeeder,
LlmNluHelper,
],
});
[nlpValueRepository, nlpSampleEntityRepository] = await getMocks([
NlpValueRepository,
NlpSampleEntityRepository,
]);
[nlpValueRepository, nlpSampleEntityRepository, nlpService] =
await getMocks([
NlpValueRepository,
NlpSampleEntityRepository,
NlpService,
]);
nlpValues = await nlpValueRepository.findAll();
const llmNluHelper = module.get(LlmNluHelper);
module.get(HelperService).register(llmNluHelper);
});
afterAll(closeInMongodConnection);
@ -107,7 +165,14 @@ describe('NlpValueRepository', () => {
describe('The deleteCascadeOne function', () => {
it('should delete a nlp Value', async () => {
nlpValueRepository.eventEmitter.once(
'hook:nlpValue:preDelete',
async (...[query, criteria]) => {
await nlpService.handleValueDelete(query, criteria);
},
);
const result = await nlpValueRepository.deleteOne(nlpValues[1].id);
expect(result.deletedCount).toEqual(1);
const sampleEntities = await nlpSampleEntityRepository.find({
value: nlpValues[1].id,

View File

@ -18,7 +18,7 @@ import {
Types,
} 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 { TFilterQuery } from '@/utils/types/filter.types';
import { Format } from '@/utils/types/format.types';
@ -35,8 +35,6 @@ import {
TNlpValueCount,
} from '../schemas/nlp-value.schema';
import { NlpSampleEntityRepository } from './nlp-sample-entity.repository';
@Injectable()
export class NlpValueRepository extends BaseRepository<
NlpValue,
@ -44,10 +42,7 @@ export class NlpValueRepository extends BaseRepository<
NlpValueFull,
NlpValueDto
> {
constructor(
@InjectModel(NlpValue.name) readonly model: Model<NlpValue>,
private readonly nlpSampleEntityRepository: NlpSampleEntityRepository,
) {
constructor(@InjectModel(NlpValue.name) readonly model: Model<NlpValue>) {
super(model, NlpValue, NLP_VALUE_POPULATE, NlpValueFull);
}
@ -85,41 +80,6 @@ export class NlpValueRepository extends BaseRepository<
}
}
/**
* Handles deletion of NLP values and associated entities. If the criteria includes an ID,
* emits an event for each deleted entity.
*
* @param _query - The query used to delete the NLP value(s).
* @param criteria - The filter criteria used to identify the NLP value(s) to delete.
*/
async preDelete(
_query: Query<
DeleteResult,
Document<NlpValue, any, any>,
unknown,
NlpValue,
'deleteOne' | 'deleteMany'
>,
criteria: TFilterQuery<NlpValue>,
): Promise<void> {
if (criteria._id) {
await this.nlpSampleEntityRepository.deleteMany({ value: criteria._id });
const entities = await this.find(
typeof criteria === 'string' ? { _id: criteria } : criteria,
);
entities
.filter((e) => !e.builtin)
.map((e) => {
this.eventEmitter.emit('hook:nlpValue:delete', e);
});
} else if (criteria.entity) {
// Do nothing : cascading deletes coming from Nlp Sample Entity
} else {
throw new Error('Attempted to delete a NLP value using unknown criteria');
}
}
private getSortDirection(sortOrder: SortOrder) {
return typeof sortOrder === 'number'
? sortOrder

View File

@ -12,11 +12,13 @@ import { OnEvent } from '@nestjs/event-emitter';
import { HelperService } from '@/helper/helper.service';
import { HelperType, NLU } from '@/helper/types';
import { LoggerService } from '@/logger/logger.service';
import { TFilterQuery } from '@/utils/types/filter.types';
import { NlpEntity, NlpEntityDocument } from '../schemas/nlp-entity.schema';
import { NlpValue, NlpValueDocument } from '../schemas/nlp-value.schema';
import { NlpEntityService } from './nlp-entity.service';
import { NlpSampleEntityService } from './nlp-sample-entity.service';
import { NlpSampleService } from './nlp-sample.service';
import { NlpValueService } from './nlp-value.service';
@ -28,6 +30,7 @@ export class NlpService {
protected readonly nlpEntityService: NlpEntityService,
protected readonly nlpValueService: NlpValueService,
protected readonly helperService: HelperService,
protected readonly nlpSampleEntityService: NlpSampleEntityService,
) {}
/**
@ -102,24 +105,42 @@ export class NlpService {
}
/**
* Handles the event triggered when an NLP entity is deleted. Synchronizes the deletion with the external NLP provider.
* Before deleting a `nlpEntity`, this method deletes the related `nlpValue` and `nlpSampleEntity`. Synchronizes the deletion with the external NLP provider
*
* @param entity - The NLP entity to be deleted.
* @param _query - The Mongoose query object used for deletion.
* @param criteria - The filter criteria for finding the nlpEntities to be deleted.
*/
@OnEvent('hook:nlpEntity:delete')
async handleEntityDelete(entity: NlpEntity) {
// Synchonize new entity with NLP provider
try {
if (entity.foreign_id) {
const helper = await this.helperService.getDefaultNluHelper();
await helper.deleteEntity(entity.foreign_id);
this.logger.debug('Deleted entity successfully synced!', entity);
} else {
this.logger.error(`Entity ${entity} is missing foreign_id`);
throw new NotFoundException(`Entity ${entity} is missing foreign_id`);
@OnEvent('hook:nlpEntity:preDelete')
async handleEntityDelete(
_query: unknown,
criteria: TFilterQuery<NlpEntity>,
): Promise<void> {
if (criteria._id) {
await this.nlpValueService.deleteMany({ entity: criteria._id });
await this.nlpSampleEntityService.deleteMany({ entity: criteria._id });
const entities = await this.nlpEntityService.find({
...(typeof criteria === 'string' ? { _id: criteria } : criteria),
builtin: false,
});
const helper = await this.helperService.getDefaultHelper(HelperType.NLU);
for (const entity of entities) {
// Synchonize new entity with NLP provider
try {
if (entity.foreign_id) {
await helper.deleteEntity(entity.foreign_id);
this.logger.debug('Deleted entity successfully synced!', entity);
} else {
this.logger.error(`Entity ${entity} is missing foreign_id`);
throw new NotFoundException(
`Entity ${entity} is missing foreign_id`,
);
}
} catch (err) {
this.logger.error('Unable to sync deleted entity', err);
}
}
} catch (err) {
this.logger.error('Unable to sync deleted entity', err);
}
}
@ -164,24 +185,45 @@ export class NlpService {
}
/**
* Handles the event triggered when an NLP value is deleted. Synchronizes the deletion with the external NLP provider.
* Before deleting a `nlpValue`, this method deletes the related `nlpSampleEntity`. Synchronizes the deletion with the external NLP provider
*
* @param value - The NLP value to be deleted.
* @param _query - The Mongoose query object used for deletion.
* @param criteria - The filter criteria for finding the nlpValues to be deleted.
*/
@OnEvent('hook:nlpValue:delete')
async handleValueDelete(value: NlpValue) {
// Synchonize new value with NLP provider
try {
const helper = await this.helperService.getDefaultNluHelper();
const populatedValue = await this.nlpValueService.findOneAndPopulate(
value.id,
);
if (populatedValue) {
await helper.deleteValue(populatedValue);
this.logger.debug('Deleted value successfully synced!', value);
@OnEvent('hook:nlpValue:preDelete')
async handleValueDelete(
_query: unknown,
criteria: TFilterQuery<NlpValue>,
): Promise<void> {
if (criteria._id) {
await this.nlpSampleEntityService.deleteMany({
value: criteria._id,
});
const values = await this.nlpValueService.find({
...(typeof criteria === 'string' ? { _id: criteria } : criteria),
builtin: false,
});
const helper = await this.helperService.getDefaultHelper(HelperType.NLU);
for (const value of values) {
// Synchonize new value with NLP provider
try {
const populatedValue = await this.nlpValueService.findOneAndPopulate(
value.id,
);
if (populatedValue) {
await helper.deleteValue(populatedValue);
this.logger.debug('Deleted value successfully synced!', value);
}
} catch (err) {
this.logger.error('Unable to sync deleted value', err);
}
}
} catch (err) {
this.logger.error('Unable to sync deleted value', err);
} else if (criteria.entity) {
// Do nothing : cascading deletes coming from Nlp Sample Entity
} else {
throw new Error('Attempted to delete a NLP value using unknown criteria');
}
}
}

View File

@ -130,23 +130,19 @@ declare module '@nestjs/event-emitter' {
category: TDefinition<Category>;
contextVar: TDefinition<ContextVar>;
conversation: TDefinition<Conversation, { end: unknown; close: unknown }>;
label: TDefinition<
Label,
{ create: LabelDocument; delete: Label | Label[] }
>;
label: TDefinition<Label, { create: LabelDocument }>;
message: TDefinition<Message>;
subscriber: TDefinition<Subscriber, { assign: SubscriberUpdateDto }>;
contentType: TDefinition<ContentType>;
content: TDefinition<Content>;
menu: TDefinition<Menu>;
language: TDefinition<Language, { delete: Language | Language[] }>;
language: TDefinition<Language>;
translation: TDefinition<Translation>;
nlpEntity: TDefinition<
NlpEntity,
{
create: NlpEntityDocument;
update: NlpEntity;
delete: NlpEntity | NlpEntity[];
}
>;
nlpSampleEntity: TDefinition<NlpSampleEntity>;
@ -156,7 +152,6 @@ declare module '@nestjs/event-emitter' {
{
create: NlpValueDocument;
update: NlpValue;
delete: NlpValue | NlpValue[];
}
>;
setting: TDefinition<Setting>;