feat: restrict deletion for context vars that are in use

This commit is contained in:
hexastack 2024-10-08 17:05:09 +01:00
parent aa25ce6b03
commit bbba54b2c1
5 changed files with 60 additions and 11 deletions

View File

@ -137,7 +137,7 @@ describe('ContextVarController', () => {
contextVarController.deleteOne(contextVarToDelete.id),
).rejects.toThrow(
new NotFoundException(
`ContextVar with ID ${contextVarToDelete.id} not found`,
`Context var with ID ${contextVarToDelete.id} not found.`,
),
);
});

View File

@ -6,21 +6,70 @@
* 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 { Injectable } from '@nestjs/common';
import {
ForbiddenException,
Injectable,
NotFoundException,
Optional,
} from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Document, Model, Query, TFilterQuery } from 'mongoose';
import { BaseRepository } from '@/utils/generics/base-repository';
import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository';
import { ContextVar } from '../schemas/context-var.schema';
import { BlockService } from '../services/block.service';
@Injectable()
export class ContextVarRepository extends BaseRepository<ContextVar> {
private readonly blockService: BlockService;
constructor(
readonly eventEmitter: EventEmitter2,
@InjectModel(ContextVar.name) readonly model: Model<ContextVar>,
@Optional() blockService?: BlockService,
) {
super(eventEmitter, model, ContextVar);
this.blockService = blockService;
}
/**
* Pre-processing logic before deleting a context var.
* It avoids deleting a context var if its unique name is used in blocks within the capture_vars array.
* If the context var is found in blocks, the block IDs are returned in the exception message.
*
* @param query - The delete query.
* @param criteria - The filter criteria for finding context vars to delete.
*/
async preDelete(
_query: Query<
DeleteResult,
Document<ContextVar, any, any>,
unknown,
ContextVar,
'deleteOne' | 'deleteMany'
>,
criteria: TFilterQuery<ContextVar>,
) {
const ids = Array.isArray(criteria._id) ? criteria._id : [criteria._id];
for (const id of ids) {
const contextVar = await this.findOne({ _id: id });
if (!contextVar) {
throw new NotFoundException(`Context var with ID ${id} not found.`);
}
const associatedBlocks = await this.blockService?.find({
capture_vars: { $elemMatch: { context_var: contextVar.name } },
});
if (associatedBlocks?.length > 0) {
const blockIds = associatedBlocks.map((block) => block.id).join(', ');
throw new ForbiddenException(
`Context var "${contextVar.name}" is associated with the following block(s): ${blockIds} and cannot be deleted.`,
);
}
}
}
}

View File

@ -10,6 +10,7 @@ import { Prop, Schema, SchemaFactory, ModelDefinition } from '@nestjs/mongoose';
import { THydratedDocument } from 'mongoose';
import { BaseSchema } from '@/utils/generics/base-schema';
import { LifecycleHookManager } from '@/utils/generics/lifecycle-hook-manager';
@Schema({ timestamps: true })
export class ContextVar extends BaseSchema {
@ -40,10 +41,10 @@ export class ContextVar extends BaseSchema {
permanent?: boolean;
}
export const ContextVarModel: ModelDefinition = {
export const ContextVarModel: ModelDefinition = LifecycleHookManager.attach({
name: ContextVar.name,
schema: SchemaFactory.createForClass(ContextVar),
};
});
export type ContextVarDocument = THydratedDocument<ContextVar>;

View File

@ -138,8 +138,7 @@ export const Categories = () => {
if (selectedCategories.length > 0) {
deleteCategories(selectedCategories), setSelectedCategories([]);
deleteDialogCtl.closeDialog();
}
if (deleteDialogCtl?.data) {
} else if (deleteDialogCtl?.data) {
{
deleteCategory(deleteDialogCtl.data);
deleteDialogCtl.closeDialog();

View File

@ -63,8 +63,8 @@ export const ContextVars = () => {
},
});
const { mutateAsync: deleteContextVar } = useDelete(EntityType.CONTEXT_VAR, {
onError: () => {
toast.error(t("message.internal_server_error"));
onError: (error) => {
toast.error(error);
},
onSuccess() {
deleteDialogCtl.closeDialog();
@ -76,7 +76,7 @@ export const ContextVars = () => {
EntityType.CONTEXT_VAR,
{
onError: (error) => {
toast.error(error.message || t("message.internal_server_error"));
toast.error(error);
},
onSuccess: () => {
deleteDialogCtl.closeDialog();