From bdec14f04d6d8bd877f119525b09b3883a672fab Mon Sep 17 00:00:00 2001 From: Gabriel Tozatti Date: Thu, 15 Jan 2026 17:58:06 -0300 Subject: [PATCH 1/4] fix: enforce duplicate name validation --- .../api/src/modules/predicate/services.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/api/src/modules/predicate/services.ts b/packages/api/src/modules/predicate/services.ts index 81754853e..a087ccfff 100644 --- a/packages/api/src/modules/predicate/services.ts +++ b/packages/api/src/modules/predicate/services.ts @@ -16,6 +16,7 @@ import { IPagination, Pagination, PaginationParams } from '@src/utils/pagination import { Predicate, User, Workspace } from '@models/index'; import GeneralError, { ErrorTypes } from '@utils/error/GeneralError'; +import {BadRequest} from "@utils/error"; import Internal from '@utils/error/Internal'; import App from '@src/server/app'; @@ -61,6 +62,22 @@ export class PredicateService implements IPredicateService { 'p.version', ]; + private async validateUniqueName(name: string, workspaceId: string): Promise { + const exists = await Predicate.createQueryBuilder('p') + .where('LOWER(p.name) = LOWER(:name)', { name }) + .andWhere('p.workspace_id = :workspaceId', { workspaceId }) + .andWhere('p.deletedAt IS NULL') + .getOne(); + + if (exists) { + throw new BadRequest({ + type: ErrorTypes.Create, + title: 'Predicate name already exists', + detail: `A predicate with name "${name}" already exists in this workspace`, + }); + } + } + filter(filter: IPredicateFilterParams) { this._filter = filter; return this; @@ -83,6 +100,8 @@ export class PredicateService implements IPredicateService { workspace: Workspace, ): Promise { try { + await this.validateUniqueName(payload.name, workspace.id); + const userService = new UserService(); const config = JSON.parse(payload.configurable); From e93d5c91c9052163426f20270e02c3b9c7e45bc0 Mon Sep 17 00:00:00 2001 From: Gabriel Tozatti Date: Thu, 15 Jan 2026 18:01:18 -0300 Subject: [PATCH 2/4] fix: correctly handle BadRequest in create() to return HTTP 400 --- packages/api/src/modules/predicate/services.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/api/src/modules/predicate/services.ts b/packages/api/src/modules/predicate/services.ts index a087ccfff..016957a85 100644 --- a/packages/api/src/modules/predicate/services.ts +++ b/packages/api/src/modules/predicate/services.ts @@ -151,6 +151,7 @@ export class PredicateService implements IPredicateService { // return predicate; } catch (e) { console.log(e); + if (e instanceof BadRequest) throw e; throw new Internal({ type: ErrorTypes.Internal, title: 'Error on predicate creation', From 0823c027f8531ac62f36839bf77008e486d33762 Mon Sep 17 00:00:00 2001 From: Gabriel Tozatti Date: Thu, 15 Jan 2026 18:44:08 -0300 Subject: [PATCH 3/4] fix: validate unique name on create and update --- .../api/src/modules/predicate/services.ts | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/packages/api/src/modules/predicate/services.ts b/packages/api/src/modules/predicate/services.ts index 016957a85..b5ddfbff1 100644 --- a/packages/api/src/modules/predicate/services.ts +++ b/packages/api/src/modules/predicate/services.ts @@ -62,16 +62,26 @@ export class PredicateService implements IPredicateService { 'p.version', ]; - private async validateUniqueName(name: string, workspaceId: string): Promise { - const exists = await Predicate.createQueryBuilder('p') + private static async validateUniqueName( + name: string, + workspaceId: string, + type: ErrorTypes, + predicateId?: string, + ): Promise { + const query = Predicate.createQueryBuilder('p') .where('LOWER(p.name) = LOWER(:name)', { name }) .andWhere('p.workspace_id = :workspaceId', { workspaceId }) .andWhere('p.deletedAt IS NULL') - .getOne(); + + if (predicateId) { + query.andWhere('p.id != :predicateId', { predicateId }); + } + + const exists = await query.getOne(); if (exists) { throw new BadRequest({ - type: ErrorTypes.Create, + type, title: 'Predicate name already exists', detail: `A predicate with name "${name}" already exists in this workspace`, }); @@ -100,7 +110,10 @@ export class PredicateService implements IPredicateService { workspace: Workspace, ): Promise { try { - await this.validateUniqueName(payload.name, workspace.id); + await PredicateService.validateUniqueName(payload.name, + workspace.id, + ErrorTypes.Create + ); const userService = new UserService(); const config = JSON.parse(payload.configurable); @@ -419,7 +432,10 @@ export class PredicateService implements IPredicateService { payload?: Partial, ): Promise { try { - const currentPredicate = await this.findById(id); + const currentPredicate = await Predicate.findOne({ + where: { id }, + relations: ['workspace'] + }); if (!currentPredicate) { throw new NotFound({ @@ -429,6 +445,15 @@ export class PredicateService implements IPredicateService { }); } + if (payload?.name && payload.name !== currentPredicate.name) { + await PredicateService.validateUniqueName( + payload.name, + currentPredicate.workspace.id, + ErrorTypes.Update, + currentPredicate.id, + ); + } + const updatedPredicate = await Predicate.merge(currentPredicate, { name: payload?.name, description: payload?.description, From c12bd59aa35ed3e703af68194e306e1a3e224cf8 Mon Sep 17 00:00:00 2001 From: Gabriel Tozatti Date: Fri, 16 Jan 2026 17:21:25 -0300 Subject: [PATCH 4/4] fix: import formatting: add spaces around braces for BadRequest import --- .../api/src/modules/predicate/services.ts | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/api/src/modules/predicate/services.ts b/packages/api/src/modules/predicate/services.ts index b5ddfbff1..c23496fa7 100644 --- a/packages/api/src/modules/predicate/services.ts +++ b/packages/api/src/modules/predicate/services.ts @@ -16,7 +16,7 @@ import { IPagination, Pagination, PaginationParams } from '@src/utils/pagination import { Predicate, User, Workspace } from '@models/index'; import GeneralError, { ErrorTypes } from '@utils/error/GeneralError'; -import {BadRequest} from "@utils/error"; +import { BadRequest } from '@utils/error'; import Internal from '@utils/error/Internal'; import App from '@src/server/app'; @@ -63,15 +63,15 @@ export class PredicateService implements IPredicateService { ]; private static async validateUniqueName( - name: string, - workspaceId: string, - type: ErrorTypes, - predicateId?: string, + name: string, + workspaceId: string, + type: ErrorTypes, + predicateId?: string, ): Promise { const query = Predicate.createQueryBuilder('p') - .where('LOWER(p.name) = LOWER(:name)', { name }) - .andWhere('p.workspace_id = :workspaceId', { workspaceId }) - .andWhere('p.deletedAt IS NULL') + .where('LOWER(p.name) = LOWER(:name)', { name }) + .andWhere('p.workspace_id = :workspaceId', { workspaceId }) + .andWhere('p.deletedAt IS NULL'); if (predicateId) { query.andWhere('p.id != :predicateId', { predicateId }); @@ -110,9 +110,10 @@ export class PredicateService implements IPredicateService { workspace: Workspace, ): Promise { try { - await PredicateService.validateUniqueName(payload.name, - workspace.id, - ErrorTypes.Create + await PredicateService.validateUniqueName( + payload.name, + workspace.id, + ErrorTypes.Create, ); const userService = new UserService(); @@ -434,7 +435,7 @@ export class PredicateService implements IPredicateService { try { const currentPredicate = await Predicate.findOne({ where: { id }, - relations: ['workspace'] + relations: ['workspace'], }); if (!currentPredicate) { @@ -447,10 +448,10 @@ export class PredicateService implements IPredicateService { if (payload?.name && payload.name !== currentPredicate.name) { await PredicateService.validateUniqueName( - payload.name, - currentPredicate.workspace.id, - ErrorTypes.Update, - currentPredicate.id, + payload.name, + currentPredicate.workspace.id, + ErrorTypes.Update, + currentPredicate.id, ); }