From 1f7b52497c3a349f382b06facc51f7b51c75798a Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Thu, 7 Sep 2023 14:45:07 -0400 Subject: [PATCH] postgres sanitize query value --- .../src/queries/sanitizeQueryValue.ts | 2 +- .../createArrayFromCommaDelineated.ts | 7 -- packages/db-postgres/src/find.ts | 3 +- .../src/queries/buildSearchParams.ts | 45 --------- .../db-postgres/src/queries/exploration.ts | 1 - .../db-postgres/src/queries/parseParams.ts | 8 +- .../src/queries/sanitizeQueryValue.ts | 95 +++++++++++++++++++ test/collections-rest/int.spec.ts | 2 +- 8 files changed, 105 insertions(+), 58 deletions(-) delete mode 100644 packages/db-mongodb/src/utilities/createArrayFromCommaDelineated.ts delete mode 100644 packages/db-postgres/src/queries/buildSearchParams.ts delete mode 100644 packages/db-postgres/src/queries/exploration.ts create mode 100644 packages/db-postgres/src/queries/sanitizeQueryValue.ts diff --git a/packages/db-mongodb/src/queries/sanitizeQueryValue.ts b/packages/db-mongodb/src/queries/sanitizeQueryValue.ts index 9ae737c6d9..65fecb102c 100644 --- a/packages/db-mongodb/src/queries/sanitizeQueryValue.ts +++ b/packages/db-mongodb/src/queries/sanitizeQueryValue.ts @@ -1,6 +1,6 @@ import mongoose from 'mongoose'; import { Field, TabAsField } from 'payload/dist/fields/config/types'; -import { createArrayFromCommaDelineated } from '../utilities/createArrayFromCommaDelineated'; +import { createArrayFromCommaDelineated } from 'payload/dist/utilities/createArrayFromCommaDelineated'; type SanitizeQueryValueArgs = { field: Field | TabAsField diff --git a/packages/db-mongodb/src/utilities/createArrayFromCommaDelineated.ts b/packages/db-mongodb/src/utilities/createArrayFromCommaDelineated.ts deleted file mode 100644 index 90949cf754..0000000000 --- a/packages/db-mongodb/src/utilities/createArrayFromCommaDelineated.ts +++ /dev/null @@ -1,7 +0,0 @@ -export function createArrayFromCommaDelineated(input: string): string[] { - if (Array.isArray(input)) return input; - if (input.indexOf(',') > -1) { - return input.split(','); - } - return [input]; -} diff --git a/packages/db-postgres/src/find.ts b/packages/db-postgres/src/find.ts index 674b9f20f8..fe8b88c5a6 100644 --- a/packages/db-postgres/src/find.ts +++ b/packages/db-postgres/src/find.ts @@ -13,7 +13,7 @@ export const find: Find = async function find( collection, where: incomingWhere, page = 1, - limit: limitArg, + limit, sort: sortArg, locale, pagination, @@ -23,7 +23,6 @@ export const find: Find = async function find( const collectionConfig: SanitizedCollectionConfig = this.payload.collections[collection].config; const tableName = toSnakeCase(collection); const table = this.tables[tableName]; - const limit = typeof limitArg === 'number' ? limitArg : collectionConfig.admin.pagination.defaultLimit; const sort = typeof sortArg === 'string' ? sortArg : collectionConfig.defaultSort; let totalDocs: number; let totalPages: number; diff --git a/packages/db-postgres/src/queries/buildSearchParams.ts b/packages/db-postgres/src/queries/buildSearchParams.ts deleted file mode 100644 index a393a60347..0000000000 --- a/packages/db-postgres/src/queries/buildSearchParams.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { a as SQL } from 'drizzle-orm/column.d-aa4e525d'; -import { Field } from 'payload/dist/fields/config/types'; -import { PostgresAdapter } from '../types'; -import { BuildQueryJoins } from './buildQuery'; - -type SearchParam = { - path?: string, - value: SQL, -} - -// TODO: remove async -/** - * Convert the Payload key / value / operator into a Drizzle query - */ -export async function buildSearchParam({ - joins, - fields, - incomingPath, - val, - operator, - collectionSlug, - globalSlug, - adapter, - locale, -}: { - fields: Field[], - joins: BuildQueryJoins, - incomingPath: string, - val: unknown, - operator: string - collectionSlug?: string, - globalSlug?: string, - adapter: PostgresAdapter - locale?: string - sort?: string -}): Promise { - // Replace GraphQL nested field double underscore formatting - const sanitizedPath = incomingPath.replace(/__/gi, '.'); - - - // TODO: - // - switch on field type - // - add joins - // return operatorKey(table[path], formattedValue); -} diff --git a/packages/db-postgres/src/queries/exploration.ts b/packages/db-postgres/src/queries/exploration.ts deleted file mode 100644 index e2b97543a2..0000000000 --- a/packages/db-postgres/src/queries/exploration.ts +++ /dev/null @@ -1 +0,0 @@ -type \ No newline at end of file diff --git a/packages/db-postgres/src/queries/parseParams.ts b/packages/db-postgres/src/queries/parseParams.ts index 3e9b6da756..c7e1ca535c 100644 --- a/packages/db-postgres/src/queries/parseParams.ts +++ b/packages/db-postgres/src/queries/parseParams.ts @@ -9,6 +9,7 @@ import { PostgresAdapter } from '../types'; import { operatorMap } from './operatorMap'; import { BuildQueryJoins } from './buildQuery'; import { getTableColumnFromPath } from './getTableColumnFromPath'; +import { sanitizeQueryValue } from './sanitizeQueryValue'; type Args = { joins: BuildQueryJoins @@ -66,7 +67,12 @@ export async function parseParams({ pathSegments: relationOrPath.split('.'), tableName, }); - constraints.push(operatorMap[operator](tableColumn.table[tableColumn.columnName], where[relationOrPath][operator])); + const queryValue = sanitizeQueryValue({ + field: tableColumn.field, + operator, + val: where[relationOrPath][operator], + }); + constraints.push(operatorMap[operator](tableColumn.table[tableColumn.columnName], queryValue)); } } } diff --git a/packages/db-postgres/src/queries/sanitizeQueryValue.ts b/packages/db-postgres/src/queries/sanitizeQueryValue.ts new file mode 100644 index 0000000000..a77021a70a --- /dev/null +++ b/packages/db-postgres/src/queries/sanitizeQueryValue.ts @@ -0,0 +1,95 @@ +import { Field, TabAsField } from 'payload/dist/fields/config/types'; +import { createArrayFromCommaDelineated } from 'payload/dist/utilities/createArrayFromCommaDelineated'; +import { APIError } from 'payload/errors'; + +type SanitizeQueryValueArgs = { + field: Field | TabAsField + operator: string + val: any +} + +export const sanitizeQueryValue = ({ field, operator, val }: SanitizeQueryValueArgs): unknown => { + let formattedValue = val; + + // // Disregard invalid _ids + // if (path === '_id' && typeof val === 'string' && val.split(',').length === 1) { + // if (!hasCustomID) { + // const isValid = mongoose.Types.ObjectId.isValid(val); + // + // if (!isValid) { + // return undefined; + // } + // } + // + // if (field.type === 'number') { + // const parsedNumber = parseFloat(val); + // + // if (Number.isNaN(parsedNumber)) { + // return undefined; + // } + // } + // } + + // Cast incoming values as proper searchable types + if (field.type === 'checkbox' && typeof val === 'string') { + if (val.toLowerCase() === 'true') formattedValue = true; + if (val.toLowerCase() === 'false') formattedValue = false; + } + + if (['all', 'not_in', 'in'].includes(operator) && typeof formattedValue === 'string') { + formattedValue = createArrayFromCommaDelineated(formattedValue); + + if (field.type === 'number') { + formattedValue = formattedValue.map((arrayVal) => parseFloat(arrayVal)); + } + } + + if (field.type === 'number' && typeof formattedValue === 'string') { + formattedValue = Number(val); + } + + if (field.type === 'date' && typeof val === 'string') { + formattedValue = new Date(val); + if (Number.isNaN(Date.parse(formattedValue))) { + return undefined; + } + } + + + if (['relationship', 'upload'].includes(field.type)) { + if (val === 'null') { + formattedValue = null; + } + + // if (operator === 'in' && Array.isArray(formattedValue)) { + // formattedValue = formattedValue.reduce((formattedValues, inVal) => { + // const newValues = [inVal]; + // if (mongoose.Types.ObjectId.isValid(inVal)) newValues.push(new mongoose.Types.ObjectId(inVal)); + // + // const parsedNumber = parseFloat(inVal); + // if (!Number.isNaN(parsedNumber)) newValues.push(parsedNumber); + // + // return [ + // ...formattedValues, + // ...newValues, + // ]; + // }, []); + // } + } + + if (operator === 'near' || operator === 'within' || operator === 'intersects') { + throw new APIError(`Querying with '${operator}' is not supported with the postgres database adapter.`); + } + + // if (path !== '_id' || (path === '_id' && hasCustomID && field.type === 'text')) { + // if (operator === 'contains') { + // formattedValue = { $regex: formattedValue, $options: 'i' }; + // } + // } + + if (operator === 'exists') { + formattedValue = (formattedValue === 'true' || formattedValue === true); + } + + return formattedValue; +}; diff --git a/test/collections-rest/int.spec.ts b/test/collections-rest/int.spec.ts index 5db2e4a939..480df3a630 100644 --- a/test/collections-rest/int.spec.ts +++ b/test/collections-rest/int.spec.ts @@ -234,7 +234,7 @@ describe('collections-rest', () => { }); const { status, docs } = await client.deleteMany({ - where: { title: { eq: 'title' } }, + where: { title: { equals: 'title' } }, }); expect(status).toEqual(200);