From dcc8dad53b006f86e93150f9439eafc8d9e01d79 Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Tue, 13 Sep 2022 09:47:28 -0400 Subject: [PATCH 01/20] fix: dashboard links to globals --- src/admin/components/views/Dashboard/Default.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/admin/components/views/Dashboard/Default.tsx b/src/admin/components/views/Dashboard/Default.tsx index 93df778904..e12ff63039 100644 --- a/src/admin/components/views/Dashboard/Default.tsx +++ b/src/admin/components/views/Dashboard/Default.tsx @@ -82,7 +82,7 @@ const Dashboard: React.FC = (props) => { if (type === EntityType.global) { title = entity.label; - onClick = () => push({ pathname: `${admin}/globals/${global.slug}` }); + onClick = () => push({ pathname: `${admin}/globals/${entity.slug}` }); } return ( From 9c4f2b68b07bbdd2ac9a6dee280f50379638fc50 Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Tue, 13 Sep 2022 11:53:52 -0400 Subject: [PATCH 02/20] fix: conditions on conditionals --- src/admin/components/forms/RenderFields/index.tsx | 2 +- src/admin/components/forms/withCondition/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/admin/components/forms/RenderFields/index.tsx b/src/admin/components/forms/RenderFields/index.tsx index 9c07df0c79..468b83adfc 100644 --- a/src/admin/components/forms/RenderFields/index.tsx +++ b/src/admin/components/forms/RenderFields/index.tsx @@ -88,7 +88,7 @@ const RenderFields: React.FC = (props) => { DefaultComponent={FieldComponent} componentProps={{ ...field, - path: field.path || (isFieldAffectingData ? field.name : undefined), + path: field.path || (isFieldAffectingData ? field.name : ''), fieldTypes, admin: { ...(field.admin || {}), diff --git a/src/admin/components/forms/withCondition/index.tsx b/src/admin/components/forms/withCondition/index.tsx index f9472992fe..a4a1eb1f3c 100644 --- a/src/admin/components/forms/withCondition/index.tsx +++ b/src/admin/components/forms/withCondition/index.tsx @@ -28,7 +28,7 @@ const withCondition =

>(Field: React.Component path?: string }; - const path = pathFromProps || name; + const path = typeof pathFromProps === 'string' ? pathFromProps : name; const { getData, getSiblingData, getField, dispatchFields } = useWatchForm(); From 7cf66b081aa7dcff2c3496a13959a44a9f7ba5c0 Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Tue, 13 Sep 2022 13:20:24 -0400 Subject: [PATCH 03/20] chore(release): v1.1.1 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4a3d861c6..f2d977f433 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [1.1.1](https://github.com/payloadcms/payload/compare/v1.1.0...v1.1.1) (2022-09-13) + + +### Bug Fixes + +* conditions on conditionals ([9c4f2b6](https://github.com/payloadcms/payload/commit/9c4f2b68b07bbdd2ac9a6dee280f50379638fc50)) +* dashboard links to globals ([dcc8dad](https://github.com/payloadcms/payload/commit/dcc8dad53b006f86e93150f9439eafc8d9e01d79)) + # [1.1.0](https://github.com/payloadcms/payload/compare/v1.0.36...v1.1.0) (2022-09-13) diff --git a/package.json b/package.json index b35debecb9..6654fd9bc8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "payload", - "version": "1.1.0", + "version": "1.1.1", "description": "Node, React and MongoDB Headless CMS and Application Framework", "license": "MIT", "author": { From ed8e58162934d024331cea94a68c38c199bb258f Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Tue, 13 Sep 2022 13:44:22 -0400 Subject: [PATCH 04/20] chore: changelog update --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2d977f433..f9de73a3d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ### Bug Fixes -* conditions on conditionals ([9c4f2b6](https://github.com/payloadcms/payload/commit/9c4f2b68b07bbdd2ac9a6dee280f50379638fc50)) +* conditions on collapsible fields ([9c4f2b6](https://github.com/payloadcms/payload/commit/9c4f2b68b07bbdd2ac9a6dee280f50379638fc50)) * dashboard links to globals ([dcc8dad](https://github.com/payloadcms/payload/commit/dcc8dad53b006f86e93150f9439eafc8d9e01d79)) # [1.1.0](https://github.com/payloadcms/payload/compare/v1.0.36...v1.1.0) (2022-09-13) From 8bfe2531577c6eb4934dd5190b328b5571eb0711 Mon Sep 17 00:00:00 2001 From: Luciano Greiner Date: Tue, 13 Sep 2022 17:55:22 -0300 Subject: [PATCH 05/20] Avoid adding password and email to schemas when disableLocalStrategy is on --- src/collections/graphql/init.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/collections/graphql/init.ts b/src/collections/graphql/init.ts index ddbc3b0a36..3f30988fb3 100644 --- a/src/collections/graphql/init.ts +++ b/src/collections/graphql/init.ts @@ -119,7 +119,7 @@ function initCollectionsGraphQL(payload: Payload): void { singularLabel, ); - if (collection.config.auth) { + if (collection.config.auth && !collection.config.auth.disableLocalStrategy) { fields.push({ name: 'password', label: 'Password', @@ -274,21 +274,23 @@ function initCollectionsGraphQL(payload: Payload): void { } if (collection.config.auth) { + const authFields: Field[] = collection.config.auth.disableLocalStrategy ? [] : [{ + name: 'email', + type: 'email', + required: true, + }] collection.graphQL.JWT = buildObjectType({ payload, name: formatName(`${slug}JWT`), - fields: collection.config.fields.filter((field) => fieldAffectsData(field) && field.saveToJWT).concat([ - { - name: 'email', - type: 'email', - required: true, - }, + fields: [ + ...collection.config.fields.filter((field) => fieldAffectsData(field) && field.saveToJWT), + ...authFields, { name: 'collection', type: 'text', required: true, - }, - ]), + } + ], parentName: formatName(`${slug}JWT`), }); From 205404a88ae914c9fbfd694c3da6473f1e72faaf Mon Sep 17 00:00:00 2001 From: Elliot DeNolf Date: Tue, 13 Sep 2022 20:09:24 -0700 Subject: [PATCH 06/20] chore: remove unused cypress package --- package.json | 1 - yarn.lock | 24 ------------------------ 2 files changed, 25 deletions(-) diff --git a/package.json b/package.json index 6654fd9bc8..bee0fb681b 100644 --- a/package.json +++ b/package.json @@ -188,7 +188,6 @@ "webpack-hot-middleware": "^2.25.0" }, "devDependencies": { - "@bahmutov/cy-api": "^2.1.3", "@playwright/test": "^1.23.1", "@release-it/conventional-changelog": "^2.0.0", "@testing-library/jest-dom": "^5.11.4", diff --git a/yarn.lock b/yarn.lock index cba6ca6b96..421e3dc6a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1046,15 +1046,6 @@ "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" -"@bahmutov/cy-api@^2.1.3": - version "2.1.3" - resolved "https://registry.npmjs.org/@bahmutov/cy-api/-/cy-api-2.1.3.tgz#739fc5923629a62e095f95edc3d39ffabd4bf0b7" - integrity sha512-1Djwka3jg9eIE/eVwc1geSBvFLB8dy631A4C9Mwgz4MA89YaLgLz8zr2elTfsylr/3OH/1Uj21bWOp9atRUY7g== - dependencies: - "@types/common-tags" "1.8.1" - common-tags "1.8.2" - highlight.js "11.4.0" - "@bcherny/json-schema-ref-parser@9.0.9": version "9.0.9" resolved "https://registry.npmjs.org/@bcherny/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz#09899d405bc708c0acac0066ae8db5b94d465ca4" @@ -1951,11 +1942,6 @@ "@types/node" "*" source-map "^0.6.0" -"@types/common-tags@1.8.1": - version "1.8.1" - resolved "https://registry.npmjs.org/@types/common-tags/-/common-tags-1.8.1.tgz#a5a49ca5ebbb58e0f8947f3ec98950c8970a68a9" - integrity sha512-20R/mDpKSPWdJs5TOpz3e7zqbeCNuMCPhV7Yndk9KU2Rbij2r5W4RzwDPkzC+2lzUqXYu9rFzTktCBnDjHuNQg== - "@types/compression@^1.7.0": version "1.7.2" resolved "https://registry.npmjs.org/@types/compression/-/compression-1.7.2.tgz#7cc1cdb01b4730eea284615a68fc70a2cdfd5e71" @@ -4114,11 +4100,6 @@ commander@^8.3.0: resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== -common-tags@1.8.2: - version "1.8.2" - resolved "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz#94ebb3c076d26032745fd54face7f688ef5ac9c6" - integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== - commondir@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -6595,11 +6576,6 @@ he@^1.2.0: resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -highlight.js@11.4.0: - version "11.4.0" - resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-11.4.0.tgz#34ceadd49e1596ee5aba3d99346cdfd4845ee05a" - integrity sha512-nawlpCBCSASs7EdvZOYOYVkJpGmAOKMYZgZtUqSRqodZE0GRVcFKwo1RcpeOemqh9hyttTdd5wDBwHkuSyUfnA== - history@^4.9.0: version "4.10.1" resolved "https://registry.npmjs.org/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" From 9fea2b4e08239b9c58f4dd2d5d03488d357f99ac Mon Sep 17 00:00:00 2001 From: Marco Sangalli Date: Wed, 14 Sep 2022 14:43:47 +0200 Subject: [PATCH 07/20] chore: fixing uploadFile when using s3 bucket --- src/uploads/imageResizer.ts | 14 +++++++------- src/uploads/uploadFile.ts | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/uploads/imageResizer.ts b/src/uploads/imageResizer.ts index d3cd3bd9a8..de32629529 100644 --- a/src/uploads/imageResizer.ts +++ b/src/uploads/imageResizer.ts @@ -1,12 +1,12 @@ -import fs from 'fs'; -import sharp from 'sharp'; -import sanitize from 'sanitize-filename'; import { fromBuffer } from 'file-type'; -import { ProbedImageSize } from './getImageSize'; -import fileExists from './fileExists'; +import fs from 'fs'; +import sanitize from 'sanitize-filename'; +import sharp from 'sharp'; import { SanitizedCollectionConfig } from '../collections/config/types'; -import { FileSizes, ImageSize } from './types'; import { PayloadRequest } from '../express/types'; +import fileExists from './fileExists'; +import { ProbedImageSize } from './getImageSize'; +import { FileSizes, ImageSize } from './types'; type Args = { req: PayloadRequest @@ -50,7 +50,7 @@ export default async function resizeAndSave({ const sizes = imageSizes .filter((desiredSize) => needsResize(desiredSize, dimensions)) .map(async (desiredSize) => { - let resized = await sharp(file).resize(desiredSize); + let resized = sharp(file).resize(desiredSize); if (desiredSize.formatOptions) { resized = resized.toFormat(desiredSize.formatOptions.format, desiredSize.formatOptions.options); diff --git a/src/uploads/uploadFile.ts b/src/uploads/uploadFile.ts index 3c3b2b3705..0acd186166 100644 --- a/src/uploads/uploadFile.ts +++ b/src/uploads/uploadFile.ts @@ -1,18 +1,18 @@ +import { fromBuffer } from 'file-type'; import mkdirp from 'mkdirp'; import path from 'path'; -import sharp, { Sharp } from 'sharp'; -import { fromBuffer } from 'file-type'; import sanitize from 'sanitize-filename'; -import { SanitizedConfig } from '../config/types'; +import sharp, { Sharp } from 'sharp'; import { Collection } from '../collections/config/types'; +import { SanitizedConfig } from '../config/types'; import { FileUploadError, MissingFile } from '../errors'; import { PayloadRequest } from '../express/types'; -import { FileData } from './types'; -import saveBufferToFile from './saveBufferToFile'; -import getSafeFileName from './getSafeFilename'; import getImageSize from './getImageSize'; +import getSafeFileName from './getSafeFilename'; import resizeAndSave from './imageResizer'; import isImage from './isImage'; +import saveBufferToFile from './saveBufferToFile'; +import { FileData } from './types'; type Args = { config: SanitizedConfig, @@ -103,7 +103,7 @@ const uploadFile = async ({ dimensions, staticPath, config: collectionConfig, - savedFilename: fsSafeName, + savedFilename: fsSafeName || file.name, mimeType: fileData.mimeType, }); } From 7b756f3421f02d1ff55374a72396e15e9f3e23d7 Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Wed, 14 Sep 2022 11:34:40 -0400 Subject: [PATCH 08/20] fix: resize images without local storage --- package.json | 2 +- src/uploads/uploadFile.ts | 67 +++++++++++++++++++-------------------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index bee0fb681b..a7553bd0b2 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "build:tsc": "tsc --p tsconfig.admin.json && tsc --p tsconfig.server.json", "build:components": "webpack --config dist/webpack/components.config.js", "build": "yarn copyfiles && yarn build:tsc && yarn build:components", - "build:watch": "nodemon --watch 'src/**' --ext 'ts,tsx' --exec 'yarn build:tsc'", + "build:watch": "nodemon --watch 'src/**' --ext 'ts,tsx' --exec \"yarn build:tsc\"", "dev": "nodemon", "dev:generate-types": "node ./test/generateTypes.js", "pretest": "yarn build", diff --git a/src/uploads/uploadFile.ts b/src/uploads/uploadFile.ts index 0acd186166..7305c97d55 100644 --- a/src/uploads/uploadFile.ts +++ b/src/uploads/uploadFile.ts @@ -60,53 +60,52 @@ const uploadFile = async ({ if (file) { try { let fsSafeName: string; - let fileBuffer: Buffer; - let mimeType: string; - let fileSize: number; - - if (!disableLocalStorage) { - let resized: Sharp | undefined; + let resized: Sharp | undefined; + if (isImage(file.mimetype)) { if (resizeOptions) { - resized = sharp(file.data).resize(resizeOptions); + resized = sharp(file.data) + .resize(resizeOptions); } if (formatOptions) { resized = (resized ?? sharp(file.data)).toFormat(formatOptions.format, formatOptions.options); } - fileBuffer = resized ? (await resized.toBuffer()) : file.data; - const { mime, ext } = await fromBuffer(fileBuffer); - mimeType = mime; - fileSize = fileBuffer.length; - const baseFilename = sanitize(file.name.substring(0, file.name.lastIndexOf('.')) || file.name); - fsSafeName = `${baseFilename}.${ext}`; + } - if (!overwriteExistingFiles) { - fsSafeName = await getSafeFileName(Model, staticPath, fsSafeName); - } + const fileBuffer = resized ? (await resized.toBuffer()) : file.data; + const { + mime, + ext, + } = await fromBuffer(fileBuffer); + const fileSize = fileBuffer.length; + const baseFilename = sanitize(file.name.substring(0, file.name.lastIndexOf('.')) || file.name); + fsSafeName = `${baseFilename}.${ext}`; + if (!overwriteExistingFiles) { + fsSafeName = await getSafeFileName(Model, staticPath, fsSafeName); + } + + if (!disableLocalStorage) { await saveBufferToFile(fileBuffer, `${staticPath}/${fsSafeName}`); } fileData.filename = fsSafeName || (!overwriteExistingFiles ? await getSafeFileName(Model, staticPath, file.name) : file.name); fileData.filesize = fileSize || file.size; - fileData.mimeType = mimeType || (await fromBuffer(file.data)).mime; + fileData.mimeType = mime || (await fromBuffer(file.data)).mime; + const dimensions = await getImageSize(file); + fileData.width = dimensions.width; + fileData.height = dimensions.height; - if (isImage(file.mimetype)) { - const dimensions = await getImageSize(file); - fileData.width = dimensions.width; - fileData.height = dimensions.height; - - if (Array.isArray(imageSizes) && file.mimetype !== 'image/svg+xml') { - req.payloadUploadSizes = {}; - fileData.sizes = await resizeAndSave({ - req, - file: file.data, - dimensions, - staticPath, - config: collectionConfig, - savedFilename: fsSafeName || file.name, - mimeType: fileData.mimeType, - }); - } + if (Array.isArray(imageSizes) && file.mimetype !== 'image/svg+xml') { + req.payloadUploadSizes = {}; + fileData.sizes = await resizeAndSave({ + req, + file: file.data, + dimensions, + staticPath, + config: collectionConfig, + savedFilename: fsSafeName || file.name, + mimeType: fileData.mimeType, + }); } } catch (err) { console.error(err); From 14966796ae0d0bcff8cb56b62e3a21c2de2176da Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Wed, 14 Sep 2022 13:07:18 -0400 Subject: [PATCH 09/20] fix: resize images without local storage --- src/uploads/canResizeImage.ts | 3 +++ src/uploads/uploadFile.ts | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 src/uploads/canResizeImage.ts diff --git a/src/uploads/canResizeImage.ts b/src/uploads/canResizeImage.ts new file mode 100644 index 0000000000..6bb6f63d5d --- /dev/null +++ b/src/uploads/canResizeImage.ts @@ -0,0 +1,3 @@ +export default function canResizeImage(mimeType: string): boolean { + return ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].indexOf(mimeType) > -1; +} diff --git a/src/uploads/uploadFile.ts b/src/uploads/uploadFile.ts index 7305c97d55..db35a958a5 100644 --- a/src/uploads/uploadFile.ts +++ b/src/uploads/uploadFile.ts @@ -7,12 +7,12 @@ import { Collection } from '../collections/config/types'; import { SanitizedConfig } from '../config/types'; import { FileUploadError, MissingFile } from '../errors'; import { PayloadRequest } from '../express/types'; -import getImageSize from './getImageSize'; +import getImageSize, { ProbedImageSize } from './getImageSize'; import getSafeFileName from './getSafeFilename'; import resizeAndSave from './imageResizer'; -import isImage from './isImage'; import saveBufferToFile from './saveBufferToFile'; import { FileData } from './types'; +import canResizeImage from './canResizeImage'; type Args = { config: SanitizedConfig, @@ -59,9 +59,11 @@ const uploadFile = async ({ if (file) { try { + const shouldResize = canResizeImage(file.mimetype); let fsSafeName: string; let resized: Sharp | undefined; - if (isImage(file.mimetype)) { + let dimensions: ProbedImageSize; + if (shouldResize) { if (resizeOptions) { resized = sharp(file.data) .resize(resizeOptions); @@ -69,13 +71,14 @@ const uploadFile = async ({ if (formatOptions) { resized = (resized ?? sharp(file.data)).toFormat(formatOptions.format, formatOptions.options); } + dimensions = await getImageSize(file); + fileData.width = dimensions.width; + fileData.height = dimensions.height; } const fileBuffer = resized ? (await resized.toBuffer()) : file.data; - const { - mime, - ext, - } = await fromBuffer(fileBuffer); + + const { mime, ext } = await fromBuffer(fileBuffer) ?? { mime: file.mimetype, ext: file.name.split('.').pop() }; const fileSize = fileBuffer.length; const baseFilename = sanitize(file.name.substring(0, file.name.lastIndexOf('.')) || file.name); fsSafeName = `${baseFilename}.${ext}`; @@ -91,11 +94,8 @@ const uploadFile = async ({ fileData.filename = fsSafeName || (!overwriteExistingFiles ? await getSafeFileName(Model, staticPath, file.name) : file.name); fileData.filesize = fileSize || file.size; fileData.mimeType = mime || (await fromBuffer(file.data)).mime; - const dimensions = await getImageSize(file); - fileData.width = dimensions.width; - fileData.height = dimensions.height; - if (Array.isArray(imageSizes) && file.mimetype !== 'image/svg+xml') { + if (Array.isArray(imageSizes) && shouldResize) { req.payloadUploadSizes = {}; fileData.sizes = await resizeAndSave({ req, From 4be14a12d03951cb464af63a0d161f6bcc64c45b Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Wed, 14 Sep 2022 15:42:13 -0400 Subject: [PATCH 10/20] chore(release): v1.1.2 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9de73a3d7..3fa74286c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [1.1.2](https://github.com/payloadcms/payload/compare/v1.1.1...v1.1.2) (2022-09-14) + + +### Bug Fixes + +* resize images without local storage ([1496679](https://github.com/payloadcms/payload/commit/14966796ae0d0bcff8cb56b62e3a21c2de2176da)) +* resize images without local storage ([7b756f3](https://github.com/payloadcms/payload/commit/7b756f3421f02d1ff55374a72396e15e9f3e23d7)) + ## [1.1.1](https://github.com/payloadcms/payload/compare/v1.1.0...v1.1.1) (2022-09-13) diff --git a/package.json b/package.json index a7553bd0b2..85cf44a37f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "payload", - "version": "1.1.1", + "version": "1.1.2", "description": "Node, React and MongoDB Headless CMS and Application Framework", "license": "MIT", "author": { From eabb981243e005facb5fff6d9222903d4704ca55 Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Fri, 16 Sep 2022 12:26:18 -0400 Subject: [PATCH 11/20] fix: duplicate with relationships --- .../components/elements/DuplicateDocument/index.tsx | 13 +++++++++---- test/fields-relationship/e2e.spec.ts | 10 ++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/admin/components/elements/DuplicateDocument/index.tsx b/src/admin/components/elements/DuplicateDocument/index.tsx index b1cc135135..93dad6ddf0 100644 --- a/src/admin/components/elements/DuplicateDocument/index.tsx +++ b/src/admin/components/elements/DuplicateDocument/index.tsx @@ -32,9 +32,11 @@ const Duplicate: React.FC = ({ slug, collection, id }) => { return; } - const create = async (locale?: string): Promise => { - const localeParam = locale ? `locale=${locale}` : ''; - const response = await requests.get(`${serverURL}${api}/${slug}/${id}?${localeParam}`); + const create = async (locale = ''): Promise => { + const response = await requests.get(`${serverURL}${api}/${slug}/${id}`, { + locale, + depth: 0, + }); const data = await response.json(); const result = await requests.post(`${serverURL}${api}/${slug}`, { headers: { @@ -59,7 +61,10 @@ const Duplicate: React.FC = ({ slug, collection, id }) => { .filter((locale) => locale !== localization.defaultLocale) .forEach(async (locale) => { if (!abort) { - const res = await requests.get(`${serverURL}${api}/${slug}/${id}?locale=${locale}`); + const res = await requests.get(`${serverURL}${api}/${slug}/${id}`, { + locale, + depth: 0, + }); const localizedDoc = await res.json(); const patchResult = await requests.patch(`${serverURL}${api}/${slug}/${duplicateID}?locale=${locale}`, { headers: { diff --git a/test/fields-relationship/e2e.spec.ts b/test/fields-relationship/e2e.spec.ts index 8c096a55ba..79aa38fd9d 100644 --- a/test/fields-relationship/e2e.spec.ts +++ b/test/fields-relationship/e2e.spec.ts @@ -169,6 +169,16 @@ describe('fields - relationship', () => { await saveDocAndAssert(page); }); + test('should duplicate document with relationships', async () => { + await page.goto(url.edit(docWithExistingRelations.id)); + + await page.locator('.btn.duplicate').first().click(); + await expect(page.locator('.Toastify')).toContainText('successfully'); + const field = page.locator('#field-relationship .rs__value-container'); + + await expect(field).toHaveText(relationOneDoc.id); + }); + describe('existing relationships', () => { test('should highlight existing relationship', async () => { await page.goto(url.edit(docWithExistingRelations.id)); From b3bb421c6ca4176974488b3270384386a151560c Mon Sep 17 00:00:00 2001 From: Elliot DeNolf Date: Fri, 16 Sep 2022 09:40:21 -0700 Subject: [PATCH 12/20] fix: adjust prevPage and nextPage graphql typing (#1140) --- src/graphql/schema/buildPaginatedListType.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/graphql/schema/buildPaginatedListType.ts b/src/graphql/schema/buildPaginatedListType.ts index 20c4a8f7cd..76b4e99056 100644 --- a/src/graphql/schema/buildPaginatedListType.ts +++ b/src/graphql/schema/buildPaginatedListType.ts @@ -14,8 +14,8 @@ const buildPaginatedListType = (name, docType) => new GraphQLObjectType({ pagingCounter: { type: GraphQLInt }, hasPrevPage: { type: GraphQLBoolean }, hasNextPage: { type: GraphQLBoolean }, - prevPage: { type: GraphQLBoolean }, - nextPage: { type: GraphQLBoolean }, + prevPage: { type: GraphQLInt }, + nextPage: { type: GraphQLInt }, }, }); From 686085496a5bb677361e72c5e6504ab232d9da66 Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Fri, 16 Sep 2022 17:05:57 -0400 Subject: [PATCH 13/20] chore(release): v1.1.3 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fa74286c9..658b54b7f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [1.1.3](https://github.com/payloadcms/payload/compare/v1.1.2...v1.1.3) (2022-09-16) + + +### Bug Fixes + +* adjust prevPage and nextPage graphql typing ([#1140](https://github.com/payloadcms/payload/issues/1140)) ([b3bb421](https://github.com/payloadcms/payload/commit/b3bb421c6ca4176974488b3270384386a151560c)) +* duplicate with relationships ([eabb981](https://github.com/payloadcms/payload/commit/eabb981243e005facb5fff6d9222903d4704ca55)) + ## [1.1.2](https://github.com/payloadcms/payload/compare/v1.1.1...v1.1.2) (2022-09-14) diff --git a/package.json b/package.json index 85cf44a37f..fbab8546e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "payload", - "version": "1.1.2", + "version": "1.1.3", "description": "Node, React and MongoDB Headless CMS and Application Framework", "license": "MIT", "author": { From 314671b3b71d591ea02f080e0b1f47d86301690b Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Wed, 21 Sep 2022 14:20:01 -0400 Subject: [PATCH 14/20] docs: cell component props and example --- docs/admin/components.mdx | 50 +++++++++++++++++++++++++++++++++++---- docs/fields/ui.mdx | 12 +++++----- docs/plugins/overview.mdx | 2 +- 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/docs/admin/components.mdx b/docs/admin/components.mdx index 6e82397e8a..9950df5e40 100644 --- a/docs/admin/components.mdx +++ b/docs/admin/components.mdx @@ -97,11 +97,51 @@ All Payload fields support the ability to swap in your own React components. So, **Fields support the following custom components:** -| Component | Description | -| --------------- | -------------| -| **`Filter`** | Override the text input that is presented in the `List` view when a user is filtering documents by the customized field. | -| **`Cell`** | Used in the `List` view's table to represent a table-based preview of the data stored in the field. | -| **`Field`** | Swap out the field itself within all `Edit` views. | +| Component | Description | +| --------------- |------------------------------------------------------------------------------------------------------------------------------| +| **`Filter`** | Override the text input that is presented in the `List` view when a user is filtering documents by the customized field. | +| **`Cell`** | Used in the `List` view's table to represent a table-based preview of the data stored in the field. [More](#cell-component) | +| **`Field`** | Swap out the field itself within all `Edit` views. [More](#field-component) | + +## Cell Component + +These are the props that will be passed to your custom Cell to use in your own components. + +| Property | Description | +|--------------|-------------------------------------------------------------------| +| **`field`** | An object that includes the field configuration. | +| **`colIndex`** | A unique number for the column in the list. | +| **`collection`** | An object with the config of the collection that the field is in. | +| **`cellData`** | The data for the field that the cell represents. | +| **`rowData`** | An object with all the field values for the row. | + +#### Example + +```tsx +import React from 'react'; +import './index.scss'; +const baseClass = 'custom-cell'; + +const CustomCell: React.FC = (props) => { + const { + field, + colIndex, + collection, + cellData, + rowData, + } = props; + + return ( + + { cellData } + + ); +}; +``` + +## Field Component + +When writing your own custom components you can make use of a number of hooks to set data, get reactive changes to other fields, get the id of the document or interact with a context from a custom provider. ### Sending and receiving values from the form diff --git a/docs/fields/ui.mdx b/docs/fields/ui.mdx index 89737a40f7..657f3466ec 100644 --- a/docs/fields/ui.mdx +++ b/docs/fields/ui.mdx @@ -23,12 +23,12 @@ With this field, you can also inject custom `Cell` components that appear as add ### Config -| Option | Description | -| ---------------------------- | ----------- | -| **`name`** * | A unique identifier for this field. | -| **`label`** | Human-readable label for this UI field. | -| **`admin.components.Field`** | React component to be rendered for this field within the Edit view. | -| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. | +| Option | Description | +| ---------------------------- |-------------------------------------------------------------------------------------------------------------------| +| **`name`** * | A unique identifier for this field. | +| **`label`** | Human-readable label for this UI field. | +| **`admin.components.Field`** | React component to be rendered for this field within the Edit view. [More](/admin/components/#field-component) | +| **`admin.components.Cell`** | React component to be rendered as a Cell within collection List views. [More](/admin/components/#field-component) | *\* An asterisk denotes that a property is required.* diff --git a/docs/plugins/overview.mdx b/docs/plugins/overview.mdx index 180d19f755..bd4224fbd8 100644 --- a/docs/plugins/overview.mdx +++ b/docs/plugins/overview.mdx @@ -134,7 +134,7 @@ const addLastModified: Plugin = (incomingConfig: Config): Config => { export default addLastModified; ``` -#### Available Plugins +### Available Plugins You can discover existing plugins by browsing the `payload-plugin` topic on [GitHub](https://github.com/topics/payload-plugin). From cc63167307c2725e9cf06a8caa776dd03186fdad Mon Sep 17 00:00:00 2001 From: addison-codes Date: Thu, 22 Sep 2022 00:51:25 -0400 Subject: [PATCH 15/20] docs: fix highlighting --- docs/plugins/overview.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/overview.mdx b/docs/plugins/overview.mdx index 180d19f755..a92034dc0d 100644 --- a/docs/plugins/overview.mdx +++ b/docs/plugins/overview.mdx @@ -27,7 +27,7 @@ Writing plugins is no more complex than writing regular JavaScript. If you know ### How to install plugins -The base Payload config allows for a `plugins` property which takes an `array` of [`Plugin`s](https://github.com/payloadcms/payload/blob/master/src/config/types.ts#L21). +The base Payload config allows for a `plugins` property which takes an `array` of [`Plugins`](https://github.com/payloadcms/payload/blob/master/src/config/types.ts#L21). ```js import { buildConfig } from 'payload/config'; From 52cd3b4a7ed9bc85e93d753a3aaf190489ca98cd Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Thu, 22 Sep 2022 10:43:20 -0400 Subject: [PATCH 16/20] feat: supports root endpoints --- docs/rest-api/overview.mdx | 1 + src/collections/config/types.ts | 2 +- src/collections/init.ts | 2 +- src/config/schema.ts | 1 + src/config/types.ts | 7 +++- src/express/mountEndpoints.ts | 10 +++-- src/globals/config/types.ts | 2 +- src/globals/init.ts | 2 +- src/init.ts | 2 +- test/admin/payload-types.ts | 67 ++++++++++++++++++++++++++++++--- test/endpoints/config.ts | 23 ++++++++++- test/fields/payload-types.ts | 48 +++++++++++++++++++++++ 12 files changed, 150 insertions(+), 17 deletions(-) diff --git a/docs/rest-api/overview.mdx b/docs/rest-api/overview.mdx index 57d8dad1db..7f4e3152e9 100644 --- a/docs/rest-api/overview.mdx +++ b/docs/rest-api/overview.mdx @@ -86,6 +86,7 @@ Each endpoint object needs to have: | **`path`** | A string for the endpoint route after the collection or globals slug | | **`method`** | The lowercase HTTP verb to use: 'get', 'head', 'post', 'put', 'delete', 'connect' or 'options' | | **`handler`** | A function or array of functions to be called with **req**, **res** and **next** arguments. [Express](https://expressjs.com/en/guide/routing.html#route-handlers) | +| **`root`** | When `true`, defines the endpoint on the root Express app, bypassing Payload handler and the `routes.api` subpath. Note: this only applies to top-level endpoints of your Payload config, endpoints defined on `collections` or `globals` cannot be root. | Example: diff --git a/src/collections/config/types.ts b/src/collections/config/types.ts index c8d4ec15b6..f9aa0c41b3 100644 --- a/src/collections/config/types.ts +++ b/src/collections/config/types.ts @@ -226,7 +226,7 @@ export type CollectionConfig = { /** * Custom rest api endpoints */ - endpoints?: Endpoint[] + endpoints?: Omit[] /** * Access control */ diff --git a/src/collections/init.ts b/src/collections/init.ts index fb94bdde98..736e42590d 100644 --- a/src/collections/init.ts +++ b/src/collections/init.ts @@ -113,7 +113,7 @@ export default function registerCollections(ctx: Payload): void { } const endpoints = buildEndpoints(collection); - mountEndpoints(router, endpoints); + mountEndpoints(ctx.express, router, endpoints); ctx.router.use(`/${slug}`, router); } diff --git a/src/config/schema.ts b/src/config/schema.ts index 14a815e876..83b8c6f47a 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -8,6 +8,7 @@ const component = joi.alternatives().try( export const endpointsSchema = joi.array().items(joi.object({ path: joi.string(), method: joi.string().valid('get', 'head', 'post', 'put', 'patch', 'delete', 'connect', 'options'), + root: joi.bool(), handler: joi.alternatives().try( joi.array().items(joi.func()), joi.func(), diff --git a/src/config/types.ts b/src/config/types.ts index 80291c03b6..e8517a6ac9 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -79,16 +79,19 @@ export type AccessResult = boolean | Where; */ export type Access = (args?: any) => AccessResult | Promise; -export interface PayloadHandler {( +export interface PayloadHandler { + ( req: PayloadRequest, res: Response, next: NextFunction, - ): void } + ): void +} export type Endpoint = { path: string method: 'get' | 'head' | 'post' | 'put' | 'patch' | 'delete' | 'connect' | 'options' | string handler: PayloadHandler | PayloadHandler[] + root?: boolean } export type AdminView = React.ComponentType<{ user: User, canAccessAdmin: boolean }> diff --git a/src/express/mountEndpoints.ts b/src/express/mountEndpoints.ts index eb0b547e5f..8ac026d461 100644 --- a/src/express/mountEndpoints.ts +++ b/src/express/mountEndpoints.ts @@ -1,9 +1,13 @@ -import { Router } from 'express'; +import { Express, Router } from 'express'; import { Endpoint } from '../config/types'; -function mountEndpoints(router: Router, endpoints: Endpoint[]): void { +function mountEndpoints(express: Express, router: Router, endpoints: Endpoint[]): void { endpoints.forEach((endpoint) => { - router[endpoint.method](endpoint.path, endpoint.handler); + if (!endpoint.root) { + router[endpoint.method](endpoint.path, endpoint.handler); + } else { + express[endpoint.method](endpoint.path, endpoint.handler); + } }); } diff --git a/src/globals/config/types.ts b/src/globals/config/types.ts index c46d8de9ab..14a14a0552 100644 --- a/src/globals/config/types.ts +++ b/src/globals/config/types.ts @@ -56,7 +56,7 @@ export type GlobalConfig = { beforeRead?: BeforeReadHook[] afterRead?: AfterReadHook[] } - endpoints?: Endpoint[], + endpoints?: Omit[], access?: { read?: Access; readDrafts?: Access; diff --git a/src/globals/init.ts b/src/globals/init.ts index e8732291bd..a82d946d92 100644 --- a/src/globals/init.ts +++ b/src/globals/init.ts @@ -48,7 +48,7 @@ export default function initGlobals(ctx: Payload): void { const { slug } = global; const endpoints = buildEndpoints(global); - mountEndpoints(router, endpoints); + mountEndpoints(ctx.express, router, endpoints); ctx.router.use(`/globals/${slug}`, router); }); diff --git a/src/init.ts b/src/init.ts index 1f5d7daeb8..2a3dd916fb 100644 --- a/src/init.ts +++ b/src/init.ts @@ -106,7 +106,7 @@ export const init = (payload: Payload, options: InitOptions): void => { initGraphQLPlayground(payload); } - mountEndpoints(payload.router, payload.config.endpoints); + mountEndpoints(options.express, payload.router, payload.config.endpoints); // Bind router to API payload.express.use(payload.config.routes.api, payload.router); diff --git a/test/admin/payload-types.ts b/test/admin/payload-types.ts index b1e2c80c45..fa4d331688 100644 --- a/test/admin/payload-types.ts +++ b/test/admin/payload-types.ts @@ -16,14 +16,19 @@ export interface Global { } /** * This interface was referenced by `Config`'s JSON-Schema - * via the `definition` "posts". + * via the `definition` "group-globals-one". */ -export interface Post { +export interface GroupGlobalsOne { + id: string; + title?: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "group-globals-two". + */ +export interface GroupGlobalsTwo { id: string; title?: string; - description?: string; - createdAt: string; - updatedAt: string; } /** * This interface was referenced by `Config`'s JSON-Schema @@ -39,3 +44,55 @@ export interface User { createdAt: string; updatedAt: string; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "posts". + */ +export interface Post { + id: string; + title?: string; + description?: string; + number?: number; + createdAt: string; + updatedAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "group-one-collection-ones". + */ +export interface GroupOneCollectionOne { + id: string; + title?: string; + createdAt: string; + updatedAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "group-one-collection-twos". + */ +export interface GroupOneCollectionTwo { + id: string; + title?: string; + createdAt: string; + updatedAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "group-two-collection-ones". + */ +export interface GroupTwoCollectionOne { + id: string; + title?: string; + createdAt: string; + updatedAt: string; +} +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "group-two-collection-twos". + */ +export interface GroupTwoCollectionTwo { + id: string; + title?: string; + createdAt: string; + updatedAt: string; +} diff --git a/test/endpoints/config.ts b/test/endpoints/config.ts index 171a3690c2..23e9ca3869 100644 --- a/test/endpoints/config.ts +++ b/test/endpoints/config.ts @@ -3,14 +3,16 @@ import { devUser } from '../credentials'; import { buildConfig } from '../buildConfig'; import { openAccess } from '../helpers/configHelpers'; import { PayloadRequest } from '../../src/express/types'; +import { Config } from '../../src/config/types'; export const collectionSlug = 'endpoints'; export const globalSlug = 'global-endpoints'; export const globalEndpoint = 'global'; export const applicationEndpoint = 'path'; +export const rootEndpoint = 'root'; -export default buildConfig({ +const MyConfig: Config = { collections: [ { slug: collectionSlug, @@ -77,6 +79,21 @@ export default buildConfig({ res.json(req.body); }, }, + { + path: `/${applicationEndpoint}`, + method: 'get', + handler: (req: PayloadRequest, res: Response): void => { + res.json({ message: 'Hello, world!' }); + }, + }, + { + path: `/${rootEndpoint}`, + method: 'get', + root: true, + handler: (req: PayloadRequest, res: Response): void => { + res.json({ message: 'Root.' }); + }, + }, ], onInit: async (payload) => { await payload.create({ @@ -87,4 +104,6 @@ export default buildConfig({ }, }); }, -}); +} + +export default buildConfig(MyConfig); diff --git a/test/fields/payload-types.ts b/test/fields/payload-types.ts index a10e766fe8..8bba442033 100644 --- a/test/fields/payload-types.ts +++ b/test/fields/payload-types.ts @@ -16,6 +16,10 @@ export interface ArrayField { text: string; id?: string; }[]; + collapsedArray: { + text: string; + id?: string; + }[]; localized: { text: string; id?: string; @@ -80,6 +84,49 @@ export interface BlockField { blockType: 'tabs'; } )[]; + collapsedByDefaultBlocks: ( + | { + text: string; + richText?: { + [k: string]: unknown; + }[]; + id?: string; + blockName?: string; + blockType: 'text'; + } + | { + number: number; + id?: string; + blockName?: string; + blockType: 'number'; + } + | { + subBlocks: ( + | { + text: string; + id?: string; + blockName?: string; + blockType: 'text'; + } + | { + number: number; + id?: string; + blockName?: string; + blockType: 'number'; + } + )[]; + id?: string; + blockName?: string; + blockType: 'subBlocks'; + } + | { + textInCollapsible?: string; + textInRow?: string; + id?: string; + blockName?: string; + blockType: 'tabs'; + } + )[]; localizedBlocks: ( | { text: string; @@ -153,6 +200,7 @@ export interface CollapsibleField { textWithinSubGroup?: string; }; }; + someText?: string; createdAt: string; updatedAt: string; } From 75bab716d1303e4c2d38f510a3eafe39132edea9 Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Thu, 22 Sep 2022 13:23:42 -0400 Subject: [PATCH 17/20] chore: adds root endpoint test --- docs/rest-api/overview.mdx | 2 +- test/endpoints/config.ts | 15 +++++++++++++-- test/endpoints/int.spec.ts | 22 ++++++++++++++++------ test/helpers/rest.ts | 4 ++-- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/docs/rest-api/overview.mdx b/docs/rest-api/overview.mdx index 7f4e3152e9..005feee871 100644 --- a/docs/rest-api/overview.mdx +++ b/docs/rest-api/overview.mdx @@ -86,7 +86,7 @@ Each endpoint object needs to have: | **`path`** | A string for the endpoint route after the collection or globals slug | | **`method`** | The lowercase HTTP verb to use: 'get', 'head', 'post', 'put', 'delete', 'connect' or 'options' | | **`handler`** | A function or array of functions to be called with **req**, **res** and **next** arguments. [Express](https://expressjs.com/en/guide/routing.html#route-handlers) | -| **`root`** | When `true`, defines the endpoint on the root Express app, bypassing Payload handler and the `routes.api` subpath. Note: this only applies to top-level endpoints of your Payload config, endpoints defined on `collections` or `globals` cannot be root. | +| **`root`** | When `true`, defines the endpoint on the root Express app, bypassing Payload handlers and the `routes.api` subpath. Note: this only applies to top-level endpoints of your Payload config, endpoints defined on `collections` or `globals` cannot be root. | Example: diff --git a/test/endpoints/config.ts b/test/endpoints/config.ts index 23e9ca3869..4d105cc7e1 100644 --- a/test/endpoints/config.ts +++ b/test/endpoints/config.ts @@ -1,4 +1,4 @@ -import { Response } from 'express'; +import express, { Response } from 'express'; import { devUser } from '../credentials'; import { buildConfig } from '../buildConfig'; import { openAccess } from '../helpers/configHelpers'; @@ -91,9 +91,20 @@ const MyConfig: Config = { method: 'get', root: true, handler: (req: PayloadRequest, res: Response): void => { - res.json({ message: 'Root.' }); + res.json({ message: 'Hello, world!' }); }, }, + { + path: `/${rootEndpoint}`, + method: 'post', + root: true, + handler: [ + express.json({ type: 'application/json' }), + (req: PayloadRequest, res: Response): void => { + res.json(req.body); + } + ], + }, ], onInit: async (payload) => { await payload.create({ diff --git a/test/endpoints/int.spec.ts b/test/endpoints/int.spec.ts index 404f1dcf49..9844e852a5 100644 --- a/test/endpoints/int.spec.ts +++ b/test/endpoints/int.spec.ts @@ -1,6 +1,6 @@ import { initPayloadTest } from '../helpers/configHelpers'; import { RESTClient } from '../helpers/rest'; -import { applicationEndpoint, collectionSlug, globalEndpoint, globalSlug } from './config'; +import { applicationEndpoint, collectionSlug, globalEndpoint, globalSlug, rootEndpoint } from './config'; require('isomorphic-fetch'); @@ -15,21 +15,21 @@ describe('Endpoints', () => { describe('Collections', () => { it('should GET a static endpoint', async () => { - const { status, data } = await client.endpoint(`/${collectionSlug}/say-hello/joe-bloggs`); + const { status, data } = await client.endpoint(`/api/${collectionSlug}/say-hello/joe-bloggs`); expect(status).toBe(200); expect(data.message).toStrictEqual('Hey Joey!'); }); it('should GET an endpoint with a parameter', async () => { const name = 'George'; - const { status, data } = await client.endpoint(`/${collectionSlug}/say-hello/${name}`); + const { status, data } = await client.endpoint(`/api/${collectionSlug}/say-hello/${name}`); expect(status).toBe(200); expect(data.message).toStrictEqual(`Hello ${name}!`); }); it('should POST an endpoint with data', async () => { const params = { name: 'George', age: 29 }; - const { status, data } = await client.endpoint(`/${collectionSlug}/whoami`, 'post', params); + const { status, data } = await client.endpoint(`/api/${collectionSlug}/whoami`, 'post', params); expect(status).toBe(200); expect(data.name).toStrictEqual(params.name); expect(data.age).toStrictEqual(params.age); @@ -39,7 +39,7 @@ describe('Endpoints', () => { describe('Globals', () => { it('should call custom endpoint', async () => { const params = { globals: 'response' }; - const { status, data } = await client.endpoint(`/globals/${globalSlug}/${globalEndpoint}`, 'post', params); + const { status, data } = await client.endpoint(`/api/globals/${globalSlug}/${globalEndpoint}`, 'post', params); expect(status).toBe(200); expect(params).toMatchObject(data); @@ -49,7 +49,17 @@ describe('Endpoints', () => { describe('API', () => { it('should call custom endpoint', async () => { const params = { app: 'response' }; - const { status, data } = await client.endpoint(`/${applicationEndpoint}`, 'post', params); + const { status, data } = await client.endpoint(`/api/${applicationEndpoint}`, 'post', params); + + expect(status).toBe(200); + expect(params).toMatchObject(data); + }); + }); + + describe('Root', () => { + it('should call custom root endpoint', async () => { + const params = { root: 'response' }; + const { status, data } = await client.endpoint(`/${rootEndpoint}`, 'post', params); expect(status).toBe(200); expect(params).toMatchObject(data); diff --git a/test/helpers/rest.ts b/test/helpers/rest.ts index 3188a4b27c..59b0358568 100644 --- a/test/helpers/rest.ts +++ b/test/helpers/rest.ts @@ -255,8 +255,8 @@ export class RESTClient { return { status, doc: result }; } - async endpoint(path: string, method = 'get', params = undefined): Promise<{status: number, data: T}> { - const response = await fetch(`${this.serverURL}/api${path}`, { + async endpoint(path: string, method = 'get', params = undefined): Promise<{ status: number, data: T }> { + const response = await fetch(`${this.serverURL}${path}`, { headers: { 'Content-Type': 'application/json', }, From 22ea98ca33770a0ec6652f814726454abb6da24e Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Thu, 22 Sep 2022 21:37:02 -0400 Subject: [PATCH 18/20] fix: field level access for nested fields --- src/admin/components/forms/RenderFields/types.ts | 2 +- .../components/forms/field-types/Collapsible/index.tsx | 2 +- src/admin/components/forms/field-types/Row/index.tsx | 2 +- src/admin/components/forms/field-types/Tabs/index.tsx | 2 +- src/auth/operations/access.ts | 8 +++++++- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/admin/components/forms/RenderFields/types.ts b/src/admin/components/forms/RenderFields/types.ts index b8b2df0089..3ee4d6b298 100644 --- a/src/admin/components/forms/RenderFields/types.ts +++ b/src/admin/components/forms/RenderFields/types.ts @@ -6,7 +6,7 @@ export type Props = { className?: string readOnly?: boolean forceRender?: boolean - permissions?: { + permissions?: FieldPermissions | { [field: string]: FieldPermissions } filter?: (field: Field) => boolean diff --git a/src/admin/components/forms/field-types/Collapsible/index.tsx b/src/admin/components/forms/field-types/Collapsible/index.tsx index a48adaee52..23d9ac538d 100644 --- a/src/admin/components/forms/field-types/Collapsible/index.tsx +++ b/src/admin/components/forms/field-types/Collapsible/index.tsx @@ -75,7 +75,7 @@ const CollapsibleField: React.FC = (props) => { ({ ...field, diff --git a/src/admin/components/forms/field-types/Row/index.tsx b/src/admin/components/forms/field-types/Row/index.tsx index c5878dd542..88a1542c8c 100644 --- a/src/admin/components/forms/field-types/Row/index.tsx +++ b/src/admin/components/forms/field-types/Row/index.tsx @@ -28,7 +28,7 @@ const Row: React.FC = (props) => { ({ ...field, diff --git a/src/admin/components/forms/field-types/Tabs/index.tsx b/src/admin/components/forms/field-types/Tabs/index.tsx index b6646eced1..84bafbddfe 100644 --- a/src/admin/components/forms/field-types/Tabs/index.tsx +++ b/src/admin/components/forms/field-types/Tabs/index.tsx @@ -71,7 +71,7 @@ const TabsField: React.FC = (props) => { key={String(activeTab.label)} forceRender readOnly={readOnly} - permissions={permissions?.fields} + permissions={tabHasName(activeTab) ? permissions[activeTab.name].fields : permissions} fieldTypes={fieldTypes} fieldSchema={activeTab.fields.map((field) => ({ ...field, diff --git a/src/auth/operations/access.ts b/src/auth/operations/access.ts index a9dfbc6a4b..40cd096f4e 100644 --- a/src/auth/operations/access.ts +++ b/src/auth/operations/access.ts @@ -1,6 +1,7 @@ import { PayloadRequest } from '../../express/types'; import { Permissions } from '../types'; import { adminInit as adminInitTelemetry } from '../../utilities/telemetry/events/adminInit'; +import { tabHasName } from '../../fields/config/types'; const allOperations = ['create', 'read', 'update', 'delete']; @@ -66,7 +67,12 @@ async function accessOperation(args: Arguments): Promise { executeFieldPolicies(updatedObj, field.fields, operation); } else if (field.type === 'tabs') { field.tabs.forEach((tab) => { - executeFieldPolicies(updatedObj, tab.fields, operation); + if (tabHasName(tab)) { + if (!updatedObj[tab.name]) updatedObj[tab.name] = { fields: {} }; + executeFieldPolicies(updatedObj[tab.name].fields, tab.fields, operation); + } else { + executeFieldPolicies(updatedObj, tab.fields, operation); + } }); } }); From c64f15d4d98f17caf5960f4fc135314972a29543 Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Fri, 23 Sep 2022 03:26:26 -0400 Subject: [PATCH 19/20] test: field level access for nested fields --- test/access-control/config.ts | 46 ++++++++++++++++++++++++++++++++- test/access-control/e2e.spec.ts | 26 ++++++++++++++++++- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/test/access-control/config.ts b/test/access-control/config.ts index 3cc59faf8a..8eed5be93a 100644 --- a/test/access-control/config.ts +++ b/test/access-control/config.ts @@ -25,7 +25,7 @@ const PublicReadabilityAccess: FieldAccess = ({ req: { user }, siblingData }) => return false; }; -export const requestHeaders = {authorization: 'Bearer testBearerToken'}; +export const requestHeaders = { authorization: 'Bearer testBearerToken' }; const UseRequestHeadersAccess: FieldAccess = ({ req: { headers } }) => { return !!headers && headers.authorization === requestHeaders.authorization; }; @@ -47,6 +47,50 @@ export default buildConfig({ update: () => false, }, }, + { + type: 'group', + name: 'group', + fields: [ + { + name: 'restrictedGroupText', + type: 'text', + access: { + read: () => false, + update: () => false, + create: () => false, + }, + }, + ], + }, + { + type: 'row', + fields: [ + { + name: 'restrictedRowText', + type: 'text', + access: { + read: () => false, + update: () => false, + create: () => false, + }, + }, + ], + }, + { + type: 'collapsible', + label: 'Access', + fields: [ + { + name: 'restrictedCollapsibleText', + type: 'text', + access: { + read: () => false, + update: () => false, + create: () => false, + }, + }, + ], + }, ], }, { diff --git a/test/access-control/e2e.spec.ts b/test/access-control/e2e.spec.ts index f248966fa0..afbdbe64f9 100644 --- a/test/access-control/e2e.spec.ts +++ b/test/access-control/e2e.spec.ts @@ -44,7 +44,31 @@ describe('access control', () => { await page.goto(url.edit(id)); - await expect(page.locator('input[name="restrictedField"]')).toHaveCount(0); + await expect(page.locator('#field-restrictedField')).toHaveCount(0); + }); + + test('field without read access inside a group should not show', async () => { + const { id } = await createDoc({ restrictedField: 'restricted' }); + + await page.goto(url.edit(id)); + + await expect(page.locator('#field-group__restrictedGroupText')).toHaveCount(0); + }); + + test('field without read access inside a collapsible should not show', async () => { + const { id } = await createDoc({ restrictedField: 'restricted' }); + + await page.goto(url.edit(id)); + + await expect(page.locator('#field-restrictedRowText')).toHaveCount(0); + }); + + test('field without read access inside a row should not show', async () => { + const { id } = await createDoc({ restrictedField: 'restricted' }); + + await page.goto(url.edit(id)); + + await expect(page.locator('#field-restrictedCollapsibleText')).toHaveCount(0); }); describe('restricted collection', () => { From aa0302c05e803e3ce4850cb5e57ebae450ba571a Mon Sep 17 00:00:00 2001 From: Jacob Fletcher Date: Fri, 23 Sep 2022 12:22:40 -0400 Subject: [PATCH 20/20] docs: adds tip for clearing webpack cache when aliasing server modules --- docs/admin/webpack.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/admin/webpack.mdx b/docs/admin/webpack.mdx index b48060b87d..c696a678a6 100644 --- a/docs/admin/webpack.mdx +++ b/docs/admin/webpack.mdx @@ -155,6 +155,11 @@ export default {}; Now, when Webpack sees that you're attempting to import your `createStripeSubscriptionPath` file, it'll disregard that actual file and load your mock file instead. Not only will your Admin panel now bundle successfully, you will have optimized its filesize by removing unnecessary code! And you might have learned something about Webpack, too. + + Tip:
+ If changes to your Webpack aliases are not surfacing, they might be [cached](https://webpack.js.org/configuration/cache/) in `node_modules/.cache/webpack`. Try deleting that folder and restarting your server. +
+ ## Admin environment vars