From cd07873fc544766b4aeeff873dfb8d6e3e97e9dc Mon Sep 17 00:00:00 2001 From: Jarrod Flesch <30633324+JarrodMFlesch@users.noreply.github.com> Date: Wed, 29 Nov 2023 11:03:57 -0500 Subject: [PATCH] fix(db-postgres): allow for nested block fields to be queried (#4237) Co-authored-by: Dan Ribbens --- .../src/queries/getTableColumnFromPath.ts | 3 + .../queryValidation/validateQueryPaths.ts | 6 +- .../queryValidation/validateSearchParams.ts | 32 +++++++--- test/fields/int.spec.ts | 64 +++++++++++++++++++ 4 files changed, 96 insertions(+), 9 deletions(-) diff --git a/packages/db-postgres/src/queries/getTableColumnFromPath.ts b/packages/db-postgres/src/queries/getTableColumnFromPath.ts index 13f37c8a4..e636865dc 100644 --- a/packages/db-postgres/src/queries/getTableColumnFromPath.ts +++ b/packages/db-postgres/src/queries/getTableColumnFromPath.ts @@ -297,6 +297,9 @@ export const getTableColumnFromPath = ({ table: adapter.tables[newTableName], } } + if (pathSegments[1] === 'blockType') { + throw new APIError('Querying on blockType is not supported') + } break } diff --git a/packages/payload/src/database/queryValidation/validateQueryPaths.ts b/packages/payload/src/database/queryValidation/validateQueryPaths.ts index 9164b4c75..85e777b74 100644 --- a/packages/payload/src/database/queryValidation/validateQueryPaths.ts +++ b/packages/payload/src/database/queryValidation/validateQueryPaths.ts @@ -33,7 +33,11 @@ type Args = { const flattenWhere = (query: Where): WhereField[] => Object.entries(query).reduce((flattenedConstraints, [key, val]) => { if ((key === 'and' || key === 'or') && Array.isArray(val)) { - return [...flattenedConstraints, ...val.map((subVal) => flattenWhere(subVal))] + const subWhereConstraints: Where[] = val.reduce((acc, subVal) => { + const subWhere = flattenWhere(subVal) + return [...acc, ...subWhere] + }, []) + return [...flattenedConstraints, ...subWhereConstraints] } return [...flattenedConstraints, { [key]: val }] diff --git a/packages/payload/src/database/queryValidation/validateSearchParams.ts b/packages/payload/src/database/queryValidation/validateSearchParams.ts index d923774a6..beafc922b 100644 --- a/packages/payload/src/database/queryValidation/validateSearchParams.ts +++ b/packages/payload/src/database/queryValidation/validateSearchParams.ts @@ -116,12 +116,24 @@ export async function validateSearchParam({ const segments = fieldPath.split('.') if (versionFields) { - if (fieldPath === 'parent' || fieldPath === 'version') { - fieldAccess = policies[entityType][entitySlug].read.permission - } else if (segments[0] === 'parent' || segments[0] === 'version') { - fieldAccess = policies[entityType][entitySlug].read.permission + fieldAccess = policies[entityType][entitySlug] + if (segments[0] === 'parent' || segments[0] === 'version') { segments.shift() + } else { + segments.forEach((segment, pathIndex) => { + if (fieldAccess[segment]) { + if (pathIndex === segments.length - 1) { + fieldAccess = fieldAccess[segment] + } else if ('fields' in fieldAccess[segment]) { + fieldAccess = fieldAccess[segment].fields + } else if ('blocks' in fieldAccess[segment]) { + fieldAccess = fieldAccess[segment] + } + } + }) } + + fieldAccess = fieldAccess.read.permission } else { fieldAccess = policies[entityType][entitySlug].fields @@ -129,10 +141,14 @@ export async function validateSearchParam({ fieldAccess = fieldAccess[field.name] } else { segments.forEach((segment, pathIndex) => { - if (pathIndex === segments.length - 1) { - fieldAccess = fieldAccess[segment] - } else { - fieldAccess = fieldAccess[segment].fields + if (fieldAccess[segment]) { + if (pathIndex === segments.length - 1) { + fieldAccess = fieldAccess[segment] + } else if ('fields' in fieldAccess[segment]) { + fieldAccess = fieldAccess[segment].fields + } else if ('blocks' in fieldAccess[segment]) { + fieldAccess = fieldAccess[segment] + } } }) } diff --git a/test/fields/int.spec.ts b/test/fields/int.spec.ts index 4618e2449..11f2a8861 100644 --- a/test/fields/int.spec.ts +++ b/test/fields/int.spec.ts @@ -8,6 +8,7 @@ import type { PaginatedDocs } from '../../packages/payload/src/database/types' import type { RichTextField } from './payload-types' import payload from '../../packages/payload/src' +import { devUser } from '../credentials' import { initPayloadTest } from '../helpers/configHelpers' import { isMongoose } from '../helpers/isMongoose' import { RESTClient } from '../helpers/rest' @@ -35,6 +36,7 @@ let graphQLClient: GraphQLClient let serverURL: string let config: SanitizedConfig let token: string +let user: any describe('Fields', () => { beforeAll(async () => { @@ -45,6 +47,14 @@ describe('Fields', () => { const graphQLURL = `${serverURL}${config.routes.api}${config.routes.graphQL}` graphQLClient = new GraphQLClient(graphQLURL) token = await client.login() + + user = await payload.login({ + collection: 'users', + data: { + email: devUser.email, + password: devUser.password, + }, + }) }) beforeEach(async () => { @@ -834,6 +844,60 @@ describe('Fields', () => { expect(result.id).toBeDefined() }) + + it('should filter based on nested block fields', async () => { + await payload.create({ + collection: 'block-fields', + data: { + blocks: [ + { + blockType: 'content', + text: 'green', + }, + ], + }, + }) + await payload.create({ + collection: 'block-fields', + data: { + blocks: [ + { + blockType: 'content', + text: 'pink', + }, + ], + }, + }) + await payload.create({ + collection: 'block-fields', + data: { + blocks: [ + { + blockType: 'content', + text: 'green', + }, + ], + }, + }) + + const blockFields = await payload.find({ + collection: 'block-fields', + overrideAccess: false, + user, + where: { + and: [ + { + 'blocks.text': { + equals: 'green', + }, + }, + ], + }, + }) + + const { docs } = blockFields + expect(docs).toHaveLength(2) + }) }) describe('json', () => {