From a1bbd50c0bfdf8f4ce73f6da231f3152b7638693 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 29 Apr 2025 00:09:30 +0400 Subject: [PATCH] feat: build strict find filter --- backend/src/lib/knex/index.ts | 47 +++++++++++++++++++ .../secret-v2-bridge/secret-v2-bridge-dal.ts | 11 ++++- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/backend/src/lib/knex/index.ts b/backend/src/lib/knex/index.ts index d43d2af8e..c5dccc4bf 100644 --- a/backend/src/lib/knex/index.ts +++ b/backend/src/lib/knex/index.ts @@ -2,6 +2,8 @@ import { Knex } from "knex"; import { Tables } from "knex/types/tables"; +import { TableName } from "@app/db/schemas"; + import { DatabaseError } from "../errors"; import { buildDynamicKnexQuery, TKnexDynamicOperator } from "./dynamic"; @@ -25,6 +27,51 @@ export type TFindFilter = Partial & { $search?: Partial<{ [k in keyof R]: R[k] }>; $complex?: TKnexDynamicOperator; }; + +export const buildStrictFindFilter = + ( + { $in, $notNull, $search, $complex, ...filter }: TFindFilter, + tableName: TableName, + excludeKeys?: Array + ) => + (bd: Knex.QueryBuilder) => { + const strictFilter = Object.fromEntries( + Object.entries(filter) + .filter(([key]) => !excludeKeys || !excludeKeys.includes(key as keyof R)) + .map(([key, value]) => [`${tableName}.${key}`, value]) + ); + + void bd.where(strictFilter); + if ($in) { + Object.entries($in).forEach(([key, val]) => { + if (val) { + void bd.whereIn([`${tableName}.${key}`] as never, val as never); + } + }); + } + + if ($notNull?.length) { + $notNull.forEach((key) => { + void bd.whereNotNull([`${tableName}.${key as string}`] as never); + }); + } + + if ($search) { + Object.entries($search).forEach(([key, val]) => { + if (val) { + void bd.whereILike([`${tableName}.${key}`] as never, val as never); + } + }); + } + if ($complex) { + return buildDynamicKnexQuery(bd, $complex); + } + return bd; + }; + +/** + * @deprecated Use `buildStrictFindFilter` instead + */ export const buildFindFilter = ({ $in, $notNull, $search, $complex, ...filter }: TFindFilter) => (bd: Knex.QueryBuilder) => { diff --git a/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts b/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts index cf389ac2a..813ab6385 100644 --- a/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts +++ b/backend/src/services/secret-v2-bridge/secret-v2-bridge-dal.ts @@ -10,6 +10,7 @@ import { generateCacheKeyFromData } from "@app/lib/crypto/cache"; import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors"; import { buildFindFilter, + buildStrictFindFilter, ormify, selectAllTableCols, sqlNestRelationships, @@ -63,7 +64,8 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => { const findOne = async (filter: Partial, tx?: Knex) => { try { const docs = await (tx || db)(TableName.SecretV2) - .where(filter) + // eslint-disable-next-line @typescript-eslint/no-misused-promises + .where(buildStrictFindFilter(filter, TableName.SecretV2)) .leftJoin( TableName.SecretV2JnTag, `${TableName.SecretV2}.id`, @@ -142,9 +144,14 @@ export const secretV2BridgeDALFactory = ({ db, keyStore }: TSecretV2DalArg) => { const find = async (filter: TFindFilter, opts: TFindOpt = {}) => { const { offset, limit, sort, tx } = opts; try { + const qualifiedFilter = { ...filter }; + if ("userId" in qualifiedFilter) { + delete qualifiedFilter.userId; + } + const query = (tx || db)(TableName.SecretV2) // eslint-disable-next-line @typescript-eslint/no-misused-promises - .where(buildFindFilter(filter)) + .where(buildFindFilter(qualifiedFilter)) .leftJoin( TableName.SecretV2JnTag, `${TableName.SecretV2}.id`,