From b847d85e60032b47a8eacc2c9426fdd373dff879 Mon Sep 17 00:00:00 2001 From: Jessica Boezwinkle Date: Thu, 12 Jan 2023 13:12:46 +0000 Subject: [PATCH 01/19] feat: throws descriptive error when collection or global slug not found --- src/auth/operations/local/forgotPassword.ts | 5 +++++ src/auth/operations/local/login.ts | 7 ++++++- src/auth/operations/local/resetPassword.ts | 5 +++++ src/auth/operations/local/unlock.ts | 5 +++++ src/auth/operations/local/verifyEmail.ts | 5 +++++ src/collections/operations/local/create.ts | 5 +++++ src/collections/operations/local/delete.ts | 6 ++++++ src/collections/operations/local/find.ts | 5 +++++ src/collections/operations/local/findByID.ts | 5 +++++ src/collections/operations/local/findVersionByID.ts | 5 +++++ src/collections/operations/local/findVersions.ts | 5 +++++ src/collections/operations/local/restoreVersion.ts | 6 ++++++ src/collections/operations/local/update.ts | 6 ++++++ src/globals/operations/local/findOne.ts | 6 ++++++ src/globals/operations/local/findVersionByID.ts | 5 +++++ src/globals/operations/local/findVersions.ts | 5 +++++ src/globals/operations/local/restoreVersion.ts | 5 +++++ src/globals/operations/local/update.ts | 5 +++++ 18 files changed, 95 insertions(+), 1 deletion(-) diff --git a/src/auth/operations/local/forgotPassword.ts b/src/auth/operations/local/forgotPassword.ts index 612889267..eafb8331f 100644 --- a/src/auth/operations/local/forgotPassword.ts +++ b/src/auth/operations/local/forgotPassword.ts @@ -3,6 +3,7 @@ import forgotPassword, { Result } from '../forgotPassword'; import { Payload } from '../../..'; import { getDataLoader } from '../../../collections/dataloader'; import i18n from '../../../translations/init'; +import { APIError } from '../../../errors'; export type Options = { collection: string @@ -25,6 +26,10 @@ async function localForgotPassword(payload: Payload, options: Options): Promise< const collection = payload.collections[collectionSlug]; + if (!collection) { + throw new APIError(`The collection with slug ${collectionSlug} can't be found.`); + } + req.payloadAPI = 'local'; req.i18n = i18n(payload.config.i18n); diff --git a/src/auth/operations/local/login.ts b/src/auth/operations/local/login.ts index ef92235c0..f1921ea9e 100644 --- a/src/auth/operations/local/login.ts +++ b/src/auth/operations/local/login.ts @@ -5,6 +5,7 @@ import { TypeWithID } from '../../../collections/config/types'; import { Payload } from '../../..'; import { getDataLoader } from '../../../collections/dataloader'; import i18n from '../../../translations/init'; +import { APIError } from '../../../errors'; export type Options = { collection: string @@ -21,7 +22,7 @@ export type Options = { showHiddenFields?: boolean } -async function localLogin(payload: Payload, options: Options): Promise { +async function localLogin(payload: Payload, options: Options): Promise { const { collection: collectionSlug, req = {} as PayloadRequest, @@ -36,6 +37,10 @@ async function localLogin(payload: Payload, options: const collection = payload.collections[collectionSlug]; + if (!collection) { + throw new APIError(`The collection with slug ${collectionSlug} can't be found.`); + } + req.payloadAPI = 'local'; req.payload = payload; req.i18n = i18n(payload.config.i18n); diff --git a/src/auth/operations/local/resetPassword.ts b/src/auth/operations/local/resetPassword.ts index 55f354115..969850d9d 100644 --- a/src/auth/operations/local/resetPassword.ts +++ b/src/auth/operations/local/resetPassword.ts @@ -3,6 +3,7 @@ import resetPassword, { Result } from '../resetPassword'; import { PayloadRequest } from '../../../express/types'; import { getDataLoader } from '../../../collections/dataloader'; import i18n from '../../../translations/init'; +import { APIError } from '../../../errors'; export type Options = { collection: string @@ -24,6 +25,10 @@ async function localResetPassword(payload: Payload, options: Options): Promise const collection = payload.collections[collectionSlug]; + if (!collection) { + throw new APIError(`The collection with slug ${collectionSlug} can't be found.`); + } + req.payload = payload; req.payloadAPI = 'local'; req.i18n = i18n(payload.config.i18n); diff --git a/src/auth/operations/local/verifyEmail.ts b/src/auth/operations/local/verifyEmail.ts index 22228d5ac..c4466bbc9 100644 --- a/src/auth/operations/local/verifyEmail.ts +++ b/src/auth/operations/local/verifyEmail.ts @@ -1,3 +1,4 @@ +import { APIError } from '../../../errors'; import { Payload } from '../../../index'; import verifyEmail from '../verifyEmail'; @@ -14,6 +15,10 @@ async function localVerifyEmail(payload: Payload, options: Options): Promise = { collection: string @@ -46,6 +47,10 @@ export default async function createLocal(payload: Payload, options: Op const collection = payload.collections[collectionSlug]; const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null; + if (!collection) { + throw new APIError(`The collection with slug ${collectionSlug} can't be found.`); + } + req.payloadAPI = 'local'; req.locale = locale ?? req?.locale ?? defaultLocale; req.fallbackLocale = fallbackLocale ?? req?.fallbackLocale ?? defaultLocale; diff --git a/src/collections/operations/local/delete.ts b/src/collections/operations/local/delete.ts index 0b8a0188a..386c1e797 100644 --- a/src/collections/operations/local/delete.ts +++ b/src/collections/operations/local/delete.ts @@ -5,6 +5,7 @@ import { Payload } from '../../../index'; import deleteOperation from '../delete'; import { getDataLoader } from '../../dataloader'; import i18n from '../../../translations/init'; +import { APIError } from '../../../errors'; export type Options = { collection: string @@ -32,6 +33,11 @@ export default async function deleteLocal(payload: P const collection = payload.collections[collectionSlug]; const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null; + + if (!collection) { + throw new APIError(`The collection with slug ${collectionSlug} can't be found.`); + } + const req = { user, payloadAPI: 'local', diff --git a/src/collections/operations/local/find.ts b/src/collections/operations/local/find.ts index 342a91dcb..cea2105c9 100644 --- a/src/collections/operations/local/find.ts +++ b/src/collections/operations/local/find.ts @@ -6,6 +6,7 @@ import { PayloadRequest } from '../../../express/types'; import find from '../find'; import { getDataLoader } from '../../dataloader'; import i18n from '../../../translations/init'; +import { APIError } from '../../../errors'; export type Options = { collection: string @@ -50,6 +51,10 @@ export default async function findLocal(payload: Pay const collection = payload.collections[collectionSlug]; const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null; + if (!collection) { + throw new APIError(`The collection with slug ${collectionSlug} can't be found.`); + } + req.payloadAPI = 'local'; req.locale = locale ?? req?.locale ?? defaultLocale; req.fallbackLocale = fallbackLocale ?? req?.fallbackLocale ?? defaultLocale; diff --git a/src/collections/operations/local/findByID.ts b/src/collections/operations/local/findByID.ts index ba3addeb1..9f013b041 100644 --- a/src/collections/operations/local/findByID.ts +++ b/src/collections/operations/local/findByID.ts @@ -5,6 +5,7 @@ import findByID from '../findByID'; import { Payload } from '../../..'; import { getDataLoader } from '../../dataloader'; import i18n from '../../../translations/init'; +import { APIError } from '../../../errors'; export type Options = { collection: string @@ -41,6 +42,10 @@ export default async function findByIDLocal(payload: const collection = payload.collections[collectionSlug]; const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null; + if (!collection) { + throw new APIError(`The collection with slug ${collectionSlug} can't be found.`); + } + req.payloadAPI = 'local'; req.locale = locale ?? req?.locale ?? defaultLocale; req.fallbackLocale = fallbackLocale ?? req?.fallbackLocale ?? defaultLocale; diff --git a/src/collections/operations/local/findVersionByID.ts b/src/collections/operations/local/findVersionByID.ts index 240ce53bd..e19eba9dd 100644 --- a/src/collections/operations/local/findVersionByID.ts +++ b/src/collections/operations/local/findVersionByID.ts @@ -5,6 +5,7 @@ import { TypeWithVersion } from '../../../versions/types'; import findVersionByID from '../findVersionByID'; import { getDataLoader } from '../../dataloader'; import i18n from '../../../translations/init'; +import { APIError } from '../../../errors'; export type Options = { collection: string @@ -35,6 +36,10 @@ export default async function findVersionByIDLocal const collection = payload.collections[collectionSlug]; const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null; + if (!collection) { + throw new APIError(`The collection with slug ${collectionSlug} can't be found.`); + } + req.payloadAPI = 'local'; req.locale = locale ?? req?.locale ?? defaultLocale; req.fallbackLocale = fallbackLocale ?? req?.fallbackLocale ?? defaultLocale; diff --git a/src/collections/operations/local/findVersions.ts b/src/collections/operations/local/findVersions.ts index 6d833cf97..e7f100021 100644 --- a/src/collections/operations/local/findVersions.ts +++ b/src/collections/operations/local/findVersions.ts @@ -6,6 +6,7 @@ import { PayloadRequest } from '../../../express/types'; import findVersions from '../findVersions'; import { getDataLoader } from '../../dataloader'; import i18nInit from '../../../translations/init'; +import { APIError } from '../../../errors'; export type Options = { collection: string @@ -39,6 +40,10 @@ export default async function findVersionsLocal = a const collection = payload.collections[collectionSlug]; const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null; + if (!collection) { + throw new APIError(`The collection with slug ${collectionSlug} can't be found.`); + } + const i18n = i18nInit(payload.config.i18n); const req = { user, diff --git a/src/collections/operations/local/restoreVersion.ts b/src/collections/operations/local/restoreVersion.ts index f01646874..67d3ee6bb 100644 --- a/src/collections/operations/local/restoreVersion.ts +++ b/src/collections/operations/local/restoreVersion.ts @@ -5,6 +5,7 @@ import { TypeWithVersion } from '../../../versions/types'; import { getDataLoader } from '../../dataloader'; import restoreVersion from '../restoreVersion'; import i18nInit from '../../../translations/init'; +import { APIError } from '../../../errors'; export type Options = { collection: string @@ -30,6 +31,11 @@ export default async function restoreVersionLocal = } = options; const collection = payload.collections[collectionSlug]; + + if (!collection) { + throw new APIError(`The collection with slug ${collectionSlug} can't be found.`); + } + const i18n = i18nInit(payload.config.i18n); const req = { user, diff --git a/src/collections/operations/local/update.ts b/src/collections/operations/local/update.ts index 57fd71a7f..a4e86c80b 100644 --- a/src/collections/operations/local/update.ts +++ b/src/collections/operations/local/update.ts @@ -6,6 +6,7 @@ import { PayloadRequest } from '../../../express/types'; import { getDataLoader } from '../../dataloader'; import { File } from '../../../uploads/types'; import i18nInit from '../../../translations/init'; +import { APIError } from '../../../errors'; export type Options = { collection: string @@ -43,6 +44,11 @@ export default async function updateLocal(payload: Payload, options: Op } = options; const collection = payload.collections[collectionSlug]; + + if (!collection) { + throw new APIError(`The collection with slug ${collectionSlug} can't be found.`); + } + const i18n = i18nInit(payload.config.i18n); const defaultLocale = payload.config.localization ? payload.config.localization?.defaultLocale : null; diff --git a/src/globals/operations/local/findOne.ts b/src/globals/operations/local/findOne.ts index 8d9f04cab..60f9d196e 100644 --- a/src/globals/operations/local/findOne.ts +++ b/src/globals/operations/local/findOne.ts @@ -5,6 +5,7 @@ import { Document } from '../../../types'; import { TypeWithID } from '../../config/types'; import findOne from '../findOne'; import i18nInit from '../../../translations/init'; +import { APIError } from '../../../errors'; export type Options = { slug: string @@ -32,6 +33,11 @@ export default async function findOneLocal(payload: const globalConfig = payload.globals.config.find((config) => config.slug === globalSlug); const i18n = i18nInit(payload.config.i18n); + + if (!globalConfig) { + throw new APIError(`The global with slug ${globalSlug} can't be found.`); + } + const req = { user, payloadAPI: 'local', diff --git a/src/globals/operations/local/findVersionByID.ts b/src/globals/operations/local/findVersionByID.ts index 8d9cf748a..6e14dd948 100644 --- a/src/globals/operations/local/findVersionByID.ts +++ b/src/globals/operations/local/findVersionByID.ts @@ -5,6 +5,7 @@ import { Document } from '../../../types'; import { TypeWithVersion } from '../../../versions/types'; import findVersionByID from '../findVersionByID'; import i18nInit from '../../../translations/init'; +import { APIError } from '../../../errors'; export type Options = { slug: string @@ -34,6 +35,10 @@ export default async function findVersionByIDLocal const globalConfig = payload.globals.config.find((config) => config.slug === globalSlug); const i18n = i18nInit(payload.config.i18n); + if (!globalConfig) { + throw new APIError(`The global with slug ${globalSlug} can't be found.`); + } + const req = { user, payloadAPI: 'local', diff --git a/src/globals/operations/local/findVersions.ts b/src/globals/operations/local/findVersions.ts index 321b15317..b53f334dd 100644 --- a/src/globals/operations/local/findVersions.ts +++ b/src/globals/operations/local/findVersions.ts @@ -6,6 +6,7 @@ import { PayloadRequest } from '../../../express/types'; import findVersions from '../findVersions'; import { getDataLoader } from '../../../collections/dataloader'; import i18nInit from '../../../translations/init'; +import { APIError } from '../../../errors'; export type Options = { slug: string @@ -39,6 +40,10 @@ export default async function findVersionsLocal = a const globalConfig = payload.globals.config.find((config) => config.slug === globalSlug); const i18n = i18nInit(payload.config.i18n); + if (!globalConfig) { + throw new APIError(`The global with slug ${globalSlug} can't be found.`); + } + const req = { user, payloadAPI: 'local', diff --git a/src/globals/operations/local/restoreVersion.ts b/src/globals/operations/local/restoreVersion.ts index 97a83bd03..4bd185d57 100644 --- a/src/globals/operations/local/restoreVersion.ts +++ b/src/globals/operations/local/restoreVersion.ts @@ -5,6 +5,7 @@ import { Document } from '../../../types'; import { TypeWithVersion } from '../../../versions/types'; import restoreVersion from '../restoreVersion'; import i18nInit from '../../../translations/init'; +import { APIError } from '../../../errors'; export type Options = { slug: string @@ -32,6 +33,10 @@ export default async function restoreVersionLocal = const globalConfig = payload.globals.config.find((config) => config.slug === globalSlug); const i18n = i18nInit(payload.config.i18n); + if (!globalConfig) { + throw new APIError(`The global with slug ${globalSlug} can't be found.`); + } + const req = { user, payloadAPI: 'local', diff --git a/src/globals/operations/local/update.ts b/src/globals/operations/local/update.ts index 264235f1b..169f7d74c 100644 --- a/src/globals/operations/local/update.ts +++ b/src/globals/operations/local/update.ts @@ -5,6 +5,7 @@ import { TypeWithID } from '../../config/types'; import update from '../update'; import { getDataLoader } from '../../../collections/dataloader'; import i18nInit from '../../../translations/init'; +import { APIError } from '../../../errors'; export type Options = { slug: string @@ -34,6 +35,10 @@ export default async function updateLocal(payload: P const globalConfig = payload.globals.config.find((config) => config.slug === globalSlug); const i18n = i18nInit(payload.config.i18n); + if (!globalConfig) { + throw new APIError(`The global with slug ${globalSlug} can't be found.`); + } + const req = { user, payloadAPI: 'local', From 69026c577914ba029f2c45423d9f621b605a3ca0 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 12 Jan 2023 09:50:24 -0500 Subject: [PATCH 02/19] fix: ensures find with draft=true does not improperly exclude draft ids --- src/versions/drafts/mergeDrafts.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/versions/drafts/mergeDrafts.ts b/src/versions/drafts/mergeDrafts.ts index dbd472d64..06f2cc2ed 100644 --- a/src/versions/drafts/mergeDrafts.ts +++ b/src/versions/drafts/mergeDrafts.ts @@ -5,7 +5,6 @@ import { PaginatedDocs } from '../../mongoose/types'; import { Collection, CollectionModel, TypeWithID } from '../../collections/config/types'; import { hasWhereAccessResult } from '../../auth'; import { appendVersionToQueryKey } from './appendVersionToQueryKey'; -import sanitizeInternalFields from '../../utilities/sanitizeInternalFields'; import replaceWithDraftIfAvailable from './replaceWithDraftIfAvailable'; type AggregateVersion = { @@ -125,6 +124,9 @@ export const mergeDrafts = async ({ updatedAt: { $gt: parentDocUpdatedAt, }, + parent: { + $eq: parentDocID, + }, }, {}, { limit: 1 }).lean(); // If there are, From f018fc04b02f70d0e6ea545d5eb36ea860206964 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 12 Jan 2023 10:06:44 -0500 Subject: [PATCH 03/19] fix: ensures querying with drafts works on both published and non-published posts --- src/versions/drafts/mergeDrafts.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/versions/drafts/mergeDrafts.ts b/src/versions/drafts/mergeDrafts.ts index 06f2cc2ed..e2734a5f2 100644 --- a/src/versions/drafts/mergeDrafts.ts +++ b/src/versions/drafts/mergeDrafts.ts @@ -154,8 +154,8 @@ export const mergeDrafts = async ({ finalQueryToBuild.where.and.push(accessResult); } - if (where) { - finalQueryToBuild.where.and[0].or.push(where); + if (incomingWhere) { + finalQueryToBuild.where.and[0].or.push(incomingWhere); } if (includedParentIDs.length > 0) { From 1ea6e8b4f374b3367bf96c154d02a13de918a590 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 12 Jan 2023 10:20:20 -0500 Subject: [PATCH 04/19] chore: ensures that while querying with draft=true, outdated versions are excluded --- src/versions/drafts/mergeDrafts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/versions/drafts/mergeDrafts.ts b/src/versions/drafts/mergeDrafts.ts index e2734a5f2..1e612ce56 100644 --- a/src/versions/drafts/mergeDrafts.ts +++ b/src/versions/drafts/mergeDrafts.ts @@ -167,7 +167,7 @@ export const mergeDrafts = async ({ } if (excludedParentIDs.length > 0) { - finalQueryToBuild.where.and[0].or.push({ + finalQueryToBuild.where.and.push({ id: { not_in: excludedParentIDs, }, From 4460ad0577fcdbff4559dc8dcbcc43b437072e43 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 12 Jan 2023 10:25:33 -0500 Subject: [PATCH 05/19] chore(release): v1.5.7 --- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97fe654e6..fca8a67b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ +## [1.5.7](https://github.com/payloadcms/payload/compare/v1.5.6...v1.5.7) (2023-01-12) + + +### Bug Fixes + +* ensures find with draft=true does not improperly exclude draft ids ([69026c5](https://github.com/payloadcms/payload/commit/69026c577914ba029f2c45423d9f621b605a3ca0)) +* ensures querying with drafts works on both published and non-published posts ([f018fc0](https://github.com/payloadcms/payload/commit/f018fc04b02f70d0e6ea545d5eb36ea860206964)) + ## [1.5.6](https://github.com/payloadcms/payload/compare/v1.5.5...v1.5.6) (2023-01-11) diff --git a/package.json b/package.json index a4521c0ce..6988fb38b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "payload", - "version": "1.5.6", + "version": "1.5.7", "description": "Node, React and MongoDB Headless CMS and Application Framework", "license": "MIT", "engines": { From af7b42e531d08a2ab5cac1b1c5f906bcf2a5ec83 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 12 Jan 2023 10:39:35 -0500 Subject: [PATCH 06/19] chore: renames beta releases to canary --- .release-it.beta.json => .release-it.canary.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .release-it.beta.json => .release-it.canary.json (89%) diff --git a/.release-it.beta.json b/.release-it.canary.json similarity index 89% rename from .release-it.beta.json rename to .release-it.canary.json index 5dff68cbd..f65c00ec6 100644 --- a/.release-it.beta.json +++ b/.release-it.canary.json @@ -1,5 +1,5 @@ { - "preReleaseId": "beta", + "preReleaseId": "canary", "git": { "requireCleanWorkingDir": false, "commit": false, @@ -11,7 +11,7 @@ }, "npm": { "skipChecks": true, - "tag": "beta" + "tag": "canary" }, "hooks": { "before:init": ["yarn", "yarn clean", "yarn test"] From a054cf098c2a90eb0abe0985f8f727115da7f21d Mon Sep 17 00:00:00 2001 From: James Date: Thu, 12 Jan 2023 13:35:17 -0500 Subject: [PATCH 07/19] chore: excludes versions that do not have a parent document from draft queries --- src/versions/drafts/mergeDrafts.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/versions/drafts/mergeDrafts.ts b/src/versions/drafts/mergeDrafts.ts index 1e612ce56..2ec040de5 100644 --- a/src/versions/drafts/mergeDrafts.ts +++ b/src/versions/drafts/mergeDrafts.ts @@ -99,6 +99,28 @@ export const mergeDrafts = async ({ createdAt: { $first: '$createdAt' }, }, }, + { + $addFields: { + id: { + $toObjectId: '$_id', + }, + }, + }, + { + $lookup: { + from: collection.config.slug, + localField: 'id', + foreignField: '_id', + as: 'parent', + }, + }, + { + $match: { + parent: { + $size: 1, + }, + }, + }, { $match: versionQuery }, { $limit: paginationOptions.limit }, ]).then((res) => res.reduce>((map, { _id, updatedAt, createdAt, version }) => { From d6d490614808baff4c719fef5e9c03056b2af661 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 12 Jan 2023 13:56:19 -0500 Subject: [PATCH 08/19] chore(release): v1.5.8 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fca8a67b5..4edaa5bdc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ +## [1.5.8](https://github.com/payloadcms/payload/compare/v1.5.7...v1.5.8) (2023-01-12) + + +### Features + +* throws descriptive error when collection or global slug not found ([b847d85](https://github.com/payloadcms/payload/commit/b847d85e60032b47a8eacc2c9426fdd373dff879)) + ## [1.5.7](https://github.com/payloadcms/payload/compare/v1.5.6...v1.5.7) (2023-01-12) diff --git a/package.json b/package.json index 6988fb38b..5a1e5eb44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "payload", - "version": "1.5.7", + "version": "1.5.8", "description": "Node, React and MongoDB Headless CMS and Application Framework", "license": "MIT", "engines": { From b06ca700be36cc3a945f81e3fa23ebb53d06ca23 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 15 Jan 2023 09:41:54 -0500 Subject: [PATCH 09/19] fix: #1877, #1867 - mimeTypes and imageSizes no longer cause error in admin ui --- .../forms/Form/buildStateFromSchema/addFieldStatePromise.ts | 2 +- test/uploads/config.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/admin/components/forms/Form/buildStateFromSchema/addFieldStatePromise.ts b/src/admin/components/forms/Form/buildStateFromSchema/addFieldStatePromise.ts index b1572d9d2..dfc96a1c0 100644 --- a/src/admin/components/forms/Form/buildStateFromSchema/addFieldStatePromise.ts +++ b/src/admin/components/forms/Form/buildStateFromSchema/addFieldStatePromise.ts @@ -191,7 +191,7 @@ export const addFieldStatePromise = async ({ id, operation, fields: field.fields, - data: data?.[field.name], + data: data?.[field.name] || {}, fullData, parentPassesCondition: passesCondition, path: `${path}${field.name}.`, diff --git a/test/uploads/config.ts b/test/uploads/config.ts index 93cc7ce21..c4a9f1c0b 100644 --- a/test/uploads/config.ts +++ b/test/uploads/config.ts @@ -39,6 +39,7 @@ export default buildConfig({ upload: { staticURL: '/media', staticDir: './media', + mimeTypes: ['image/png', 'image/jpg', 'image/jpeg', 'image/svg+xml'], resizeOptions: { width: 1280, height: 720, From d8cce02e9775079179a9971e7173fd80e4fe4942 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 15 Jan 2023 09:48:19 -0500 Subject: [PATCH 10/19] chore(release): v1.5.9 --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4edaa5bdc..91e29d5bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ +## [1.5.9](https://github.com/payloadcms/payload/compare/v1.5.8...v1.5.9) (2023-01-15) + + +### Bug Fixes + +* [#1877](https://github.com/payloadcms/payload/issues/1877), [#1867](https://github.com/payloadcms/payload/issues/1867) - mimeTypes and imageSizes no longer cause error in admin ui ([b06ca70](https://github.com/payloadcms/payload/commit/b06ca700be36cc3a945f81e3fa23ebb53d06ca23)) + ## [1.5.8](https://github.com/payloadcms/payload/compare/v1.5.7...v1.5.8) (2023-01-12) diff --git a/package.json b/package.json index 5a1e5eb44..c046be66d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "payload", - "version": "1.5.8", + "version": "1.5.9", "description": "Node, React and MongoDB Headless CMS and Application Framework", "license": "MIT", "engines": { From 671adc7e0eab6dfc517979e3ecd6969ab5eea969 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 16 Jan 2023 18:09:38 -0500 Subject: [PATCH 11/19] chore: begins simplifying versions --- .../components/elements/Autosave/index.tsx | 2 +- .../elements/VersionsCount/index.tsx | 22 +---- src/admin/components/views/Versions/index.tsx | 66 ++++---------- src/collections/operations/create.ts | 18 ++++ src/collections/operations/update.ts | 55 +++--------- src/collections/requestHandlers/create.ts | 6 +- src/versions/drafts/saveCollectionDraft.ts | 79 ---------------- src/versions/saveCollectionVersion.ts | 89 +++++++------------ test/versions/collections/Versions.ts | 56 ++++++++++++ test/versions/config.ts | 2 + 10 files changed, 146 insertions(+), 249 deletions(-) delete mode 100644 src/versions/drafts/saveCollectionDraft.ts create mode 100644 test/versions/collections/Versions.ts diff --git a/src/admin/components/elements/Autosave/index.tsx b/src/admin/components/elements/Autosave/index.tsx index e51cbb5fd..d444277c4 100644 --- a/src/admin/components/elements/Autosave/index.tsx +++ b/src/admin/components/elements/Autosave/index.tsx @@ -44,7 +44,7 @@ const Autosave: React.FC = ({ collection, global, id, publishedDocUpdated modifiedRef.current = modified; const createCollectionDoc = useCallback(async () => { - const res = await fetch(`${serverURL}${api}/${collection.slug}?locale=${locale}&fallback-locale=null&depth=0&draft=true`, { + const res = await fetch(`${serverURL}${api}/${collection.slug}?locale=${locale}&fallback-locale=null&depth=0&draft=true&autosave=true`, { method: 'POST', credentials: 'include', headers: { diff --git a/src/admin/components/elements/VersionsCount/index.tsx b/src/admin/components/elements/VersionsCount/index.tsx index f58731fd5..2ca490fa6 100644 --- a/src/admin/components/elements/VersionsCount/index.tsx +++ b/src/admin/components/elements/VersionsCount/index.tsx @@ -4,9 +4,6 @@ import { useConfig } from '../../utilities/Config'; import Button from '../Button'; import { Props } from './types'; import { useDocumentInfo } from '../../utilities/DocumentInfo'; -import { SanitizedCollectionConfig } from '../../../../collections/config/types'; -import { SanitizedGlobalConfig } from '../../../../globals/config/types'; -import { shouldIncrementVersionCount } from '../../../../versions/shouldIncrementVersionCount'; import './index.scss'; @@ -14,35 +11,20 @@ const baseClass = 'versions-count'; const VersionsCount: React.FC = ({ collection, global, id }) => { const { routes: { admin } } = useConfig(); - const { versions, publishedDoc, unpublishedVersions } = useDocumentInfo(); + const { versions } = useDocumentInfo(); const { t } = useTranslation('version'); - // Doc status could come from three places: - // 1. the newest unpublished version (a draft) - // 2. the published doc's status, in the event that the doc is published and there are no newer versions - // 3. if there is no published doc, it's a draft - const docStatus = unpublishedVersions?.docs?.[0]?.version?._status || publishedDoc?._status || 'draft'; - let versionsURL: string; - let entity: SanitizedCollectionConfig | SanitizedGlobalConfig; if (collection) { versionsURL = `${admin}/collections/${collection.slug}/${id}/versions`; - entity = collection; } if (global) { versionsURL = `${admin}/globals/${global.slug}/versions`; - entity = global; } - let initialVersionsCount = 0; - - if (shouldIncrementVersionCount({ entity, versions, docStatus })) { - initialVersionsCount = 1; - } - - const versionCount = (versions?.totalDocs || 0) + initialVersionsCount; + const versionCount = versions?.totalDocs || 0; return (
diff --git a/src/admin/components/views/Versions/index.tsx b/src/admin/components/views/Versions/index.tsx index eca3f3d7a..123f793f0 100644 --- a/src/admin/components/views/Versions/index.tsx +++ b/src/admin/components/views/Versions/index.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react'; import { useRouteMatch } from 'react-router-dom'; -import format from 'date-fns/format'; import { useTranslation } from 'react-i18next'; import { useConfig } from '../../utilities/Config'; import usePayloadAPI from '../../../hooks/usePayloadAPI'; @@ -16,10 +15,6 @@ import Table from '../../elements/Table'; import Paginator from '../../elements/Paginator'; import PerPage from '../../elements/PerPage'; import { useSearchParams } from '../../utilities/SearchParams'; -import { Banner, Pill } from '../..'; -import { SanitizedCollectionConfig } from '../../../../collections/config/types'; -import { SanitizedGlobalConfig } from '../../../../globals/config/types'; -import { shouldIncrementVersionCount } from '../../../../versions/shouldIncrementVersionCount'; import { Gutter } from '../../elements/Gutter'; import { getTranslation } from '../../../../utilities/getTranslation'; @@ -28,7 +23,7 @@ import './index.scss'; const baseClass = 'versions'; const Versions: React.FC = ({ collection, global }) => { - const { serverURL, routes: { admin, api }, admin: { dateFormat } } = useConfig(); + const { serverURL, routes: { admin, api } } = useConfig(); const { setStepNav } = useStepNav(); const { params: { id } } = useRouteMatch<{ id: string }>(); const { t, i18n } = useTranslation('version'); @@ -39,14 +34,12 @@ const Versions: React.FC = ({ collection, global }) => { let docURL: string; let entityLabel: string; let slug: string; - let entity: SanitizedCollectionConfig | SanitizedGlobalConfig; let editURL: string; if (collection) { ({ slug } = collection); docURL = `${serverURL}${api}/${slug}/${id}`; entityLabel = getTranslation(collection.labels.singular, i18n); - entity = collection; editURL = `${admin}/collections/${collection.slug}/${id}`; } @@ -54,7 +47,6 @@ const Versions: React.FC = ({ collection, global }) => { ({ slug } = global); docURL = `${serverURL}${api}/globals/${slug}`; entityLabel = getTranslation(global.label, i18n); - entity = global; editURL = `${admin}/globals/${global.slug}`; } @@ -164,10 +156,6 @@ const Versions: React.FC = ({ collection, global }) => { useIDLabel = false; } - const docStatus = doc?._status; - const docUpdatedAt = doc?.updatedAt; - const showParentDoc = versionsData?.page === 1 && shouldIncrementVersionCount({ entity, docStatus, versions: versionsData }); - return (
= ({ collection, global }) => { {isLoadingVersions && ( )} - {showParentDoc && ( - - {t('currentDocumentStatus', { docStatus })} - - - {' '} - {format(new Date(docUpdatedAt), dateFormat)} -
-    - - {t('general:edit')} - -
-
- )} {versionsData?.totalDocs > 0 && ( = ({ collection, global }) => { numberOfNeighbors={1} /> {versionsData?.totalDocs > 0 && ( - -
- {(versionsData.page * versionsData.limit) - (versionsData.limit - 1)} - - - {versionsData.totalPages > 1 && versionsData.totalPages !== versionsData.page ? (versionsData.limit * versionsData.page) : versionsData.totalDocs} - {' '} - {t('of')} - {' '} - {versionsData.totalDocs} -
- -
- )} + +
+ {(versionsData.page * versionsData.limit) - (versionsData.limit - 1)} + - + {versionsData.totalPages > 1 && versionsData.totalPages !== versionsData.page ? (versionsData.limit * versionsData.page) : versionsData.totalDocs} + {' '} + {t('of')} + {' '} + {versionsData.totalDocs} +
+ +
+ )} )} diff --git a/src/collections/operations/create.ts b/src/collections/operations/create.ts index d51509823..35a436cce 100644 --- a/src/collections/operations/create.ts +++ b/src/collections/operations/create.ts @@ -16,6 +16,7 @@ import { beforeValidate } from '../../fields/hooks/beforeValidate'; import { afterChange } from '../../fields/hooks/afterChange'; import { afterRead } from '../../fields/hooks/afterRead'; import { generateFileData } from '../../uploads/generateFileData'; +import { saveCollectionVersion } from '../../versions/saveCollectionVersion'; export type Arguments = { collection: Collection @@ -27,6 +28,7 @@ export type Arguments = { data: Record overwriteExistingFiles?: boolean draft?: boolean + autosave?: boolean } async function create(incomingArgs: Arguments): Promise { @@ -65,6 +67,7 @@ async function create(incomingArgs: Arguments): Promise { showHiddenFields, overwriteExistingFiles = false, draft = false, + autosave = false, } = args; let { data } = args; @@ -213,6 +216,21 @@ async function create(incomingArgs: Arguments): Promise { result = JSON.parse(result); result = sanitizeInternalFields(result); + // ///////////////////////////////////// + // Create version + // ///////////////////////////////////// + + if (collectionConfig.versions) { + await saveCollectionVersion({ + payload, + config: collectionConfig, + req, + id: result.id, + docWithLocales: result, + autosave, + }); + } + // ///////////////////////////////////// // Send verification email if applicable // ///////////////////////////////////// diff --git a/src/collections/operations/update.ts b/src/collections/operations/update.ts index 095081e3c..954b19862 100644 --- a/src/collections/operations/update.ts +++ b/src/collections/operations/update.ts @@ -6,11 +6,8 @@ import executeAccess from '../../auth/executeAccess'; import { NotFound, Forbidden, APIError, ValidationError } from '../../errors'; import { PayloadRequest } from '../../express/types'; import { hasWhereAccessResult } from '../../auth/types'; -import { saveCollectionDraft } from '../../versions/drafts/saveCollectionDraft'; import { saveCollectionVersion } from '../../versions/saveCollectionVersion'; import { uploadFiles } from '../../uploads/uploadFiles'; -import cleanUpFailedVersion from '../../versions/cleanUpFailedVersion'; -import { ensurePublishedCollectionVersion } from '../../versions/ensurePublishedCollectionVersion'; import { beforeChange } from '../../fields/hooks/beforeChange'; import { beforeValidate } from '../../fields/hooks/beforeValidate'; import { afterChange } from '../../fields/hooks/afterChange'; @@ -221,44 +218,11 @@ async function update(incomingArgs: Arguments): Promise { delete result.password; } - // ///////////////////////////////////// - // Create version from existing doc - // ///////////////////////////////////// - - let createdVersion; - - if (collectionConfig.versions && !shouldSaveDraft) { - createdVersion = await saveCollectionVersion({ - payload, - config: collectionConfig, - req, - docWithLocales, - id, - }); - } - // ///////////////////////////////////// // Update // ///////////////////////////////////// - if (shouldSaveDraft) { - await ensurePublishedCollectionVersion({ - payload, - config: collectionConfig, - req, - docWithLocales, - id, - }); - - result = await saveCollectionDraft({ - payload, - config: collectionConfig, - req, - data: result, - id, - autosave, - }); - } else { + if (!shouldSaveDraft) { try { result = await Model.findByIdAndUpdate( { _id: id }, @@ -266,12 +230,6 @@ async function update(incomingArgs: Arguments): Promise { { new: true }, ); } catch (error) { - cleanUpFailedVersion({ - payload, - entityConfig: collectionConfig, - version: createdVersion, - }); - // Handle uniqueness error from MongoDB throw error.code === 11000 && error.keyValue ? new ValidationError([{ message: 'Value must be unique', field: Object.keys(error.keyValue)[0] }], t) @@ -285,6 +243,17 @@ async function update(incomingArgs: Arguments): Promise { result.id = result._id; } + if (collectionConfig.versions) { + result = await saveCollectionVersion({ + payload, + config: collectionConfig, + req, + docWithLocales: result, + id, + autosave, + }); + } + result = sanitizeInternalFields(result); // ///////////////////////////////////// diff --git a/src/collections/requestHandlers/create.ts b/src/collections/requestHandlers/create.ts index 29b23aff6..fc30fddc2 100644 --- a/src/collections/requestHandlers/create.ts +++ b/src/collections/requestHandlers/create.ts @@ -13,12 +13,16 @@ export type CreateResult = { export default async function createHandler(req: PayloadRequest, res: Response, next: NextFunction): Promise | void> { try { + const autosave = req.query.autosave === 'true'; + const draft = req.query.draft === 'true'; + const doc = await create({ req, collection: req.collection, data: req.body, depth: Number(req.query.depth), - draft: req.query.draft === 'true', + draft, + autosave, }); return res.status(httpStatus.CREATED).json({ diff --git a/src/versions/drafts/saveCollectionDraft.ts b/src/versions/drafts/saveCollectionDraft.ts deleted file mode 100644 index bab2678f2..000000000 --- a/src/versions/drafts/saveCollectionDraft.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Payload } from '../..'; -import { SanitizedCollectionConfig } from '../../collections/config/types'; -import { enforceMaxVersions } from '../enforceMaxVersions'; -import { PayloadRequest } from '../../express/types'; - -type Args = { - payload: Payload - config?: SanitizedCollectionConfig - req: PayloadRequest - data: any - id: string | number - autosave: boolean -} - -export const saveCollectionDraft = async ({ - payload, - config, - id, - data, - autosave, -}: Args): Promise> => { - const VersionsModel = payload.versions[config.slug]; - - const dataAsDraft = { ...data, _status: 'draft' }; - - let existingAutosaveVersion; - - if (autosave) { - existingAutosaveVersion = await VersionsModel.findOne({ - parent: id, - }, {}, { sort: { updatedAt: 'desc' } }); - } - - let result; - - try { - // If there is an existing autosave document, - // Update it - if (autosave && existingAutosaveVersion?.autosave === true) { - result = await VersionsModel.findByIdAndUpdate( - { - _id: existingAutosaveVersion._id, - }, - { - version: dataAsDraft, - }, - { new: true, lean: true }, - ); - // Otherwise, create a new one - } else { - result = await VersionsModel.create({ - parent: id, - version: dataAsDraft, - autosave: Boolean(autosave), - }); - } - } catch (err) { - payload.logger.error(`There was an error while creating a draft ${config.labels.singular} with ID ${id}.`); - payload.logger.error(err); - } - - if (config.versions.maxPerDoc) { - enforceMaxVersions({ - id, - payload, - Model: VersionsModel, - slug: config.slug, - entityType: 'collection', - max: config.versions.maxPerDoc, - }); - } - - result = result.version; - result = JSON.stringify(result); - result = JSON.parse(result); - result.id = id; - - return result; -}; diff --git a/src/versions/saveCollectionVersion.ts b/src/versions/saveCollectionVersion.ts index 5ac722896..7b2bca8f0 100644 --- a/src/versions/saveCollectionVersion.ts +++ b/src/versions/saveCollectionVersion.ts @@ -3,7 +3,6 @@ import { SanitizedCollectionConfig } from '../collections/config/types'; import { enforceMaxVersions } from './enforceMaxVersions'; import { PayloadRequest } from '../express/types'; import sanitizeInternalFields from '../utilities/sanitizeInternalFields'; -import { afterRead } from '../fields/hooks/afterRead'; type Args = { payload: Payload @@ -11,71 +10,49 @@ type Args = { req: PayloadRequest docWithLocales: any id: string | number + autosave?: boolean } export const saveCollectionVersion = async ({ payload, config, - req, id, docWithLocales, -}: Args): Promise => { + autosave, +}: Args): Promise> => { const VersionModel = payload.versions[config.slug]; - let version = docWithLocales; + let result = { ...docWithLocales }; - if (config.versions?.drafts) { - const latestVersion = await VersionModel.findOne({ - parent: { - $eq: docWithLocales.id, - }, - updatedAt: { - $gt: docWithLocales.updatedAt, - }, - }, - {}, - { - lean: true, - leanWithId: true, - sort: { - updatedAt: 'desc', - }, - }); + if (result._id) delete result._id; - if (latestVersion) { - // If the latest version is a draft, no need to re-save it - // Example: when "promoting" a draft to published, the draft already exists. - // Instead, return null - if (latestVersion?.version?._status === 'draft') { - return null; - } + let existingAutosaveVersion; - version = latestVersion.version; - version = JSON.parse(JSON.stringify(version)); - version = sanitizeInternalFields(version); - } + if (autosave) { + existingAutosaveVersion = await VersionModel.findOne({ + parent: id, + }, {}, { sort: { updatedAt: 'desc' } }); } - version = await afterRead({ - depth: 0, - doc: version, - entityConfig: config, - req, - overrideAccess: true, - showHiddenFields: true, - flattenLocales: false, - }); - - if (version._id) delete version._id; - - let createdVersion; - try { - createdVersion = await VersionModel.create({ - parent: id, - version, - autosave: false, - }); + if (autosave && existingAutosaveVersion?.autosave === true) { + result = await VersionModel.findByIdAndUpdate( + { + _id: existingAutosaveVersion._id, + }, + { + version: result, + }, + { new: true, lean: true }, + ); + // Otherwise, create a new one + } else { + result = await VersionModel.create({ + parent: id, + version: docWithLocales, + autosave: Boolean(autosave), + }); + } } catch (err) { payload.logger.error(`There was an error while saving a version for the ${config.labels.singular} with ID ${id}.`); payload.logger.error(err); @@ -92,10 +69,10 @@ export const saveCollectionVersion = async ({ }); } - if (createdVersion) { - createdVersion = JSON.parse(JSON.stringify(createdVersion)); - createdVersion = sanitizeInternalFields(createdVersion); - } + result = result.version; + result = JSON.parse(JSON.stringify(result)); + result = sanitizeInternalFields(result); + result.id = id; - return createdVersion; + return result; }; diff --git a/test/versions/collections/Versions.ts b/test/versions/collections/Versions.ts new file mode 100644 index 000000000..1d38c7ca2 --- /dev/null +++ b/test/versions/collections/Versions.ts @@ -0,0 +1,56 @@ +import type { CollectionConfig } from '../../../src/collections/config/types'; + +const VersionPosts: CollectionConfig = { + slug: 'version-posts', + admin: { + useAsTitle: 'title', + defaultColumns: ['title', 'description', 'createdAt'], + preview: () => 'https://payloadcms.com', + }, + versions: { + drafts: false, + maxPerDoc: 35, + retainDeleted: false, + }, + access: { + read: ({ req: { user } }) => { + if (user) { + return true; + } + + return { + or: [ + { + _status: { + equals: 'published', + }, + }, + { + _status: { + exists: false, + }, + }, + ], + }; + }, + readVersions: ({ req: { user } }) => Boolean(user), + }, + fields: [ + { + name: 'title', + label: 'Title', + type: 'text', + required: true, + unique: true, + localized: true, + }, + { + name: 'description', + label: 'Description', + type: 'textarea', + required: true, + }, + ], +}; + +export default VersionPosts; diff --git a/test/versions/config.ts b/test/versions/config.ts index 7711c9e2b..9e0b4383d 100644 --- a/test/versions/config.ts +++ b/test/versions/config.ts @@ -4,11 +4,13 @@ import DraftPosts from './collections/Drafts'; import AutosaveGlobal from './globals/Autosave'; import { devUser } from '../credentials'; import DraftGlobal from './globals/Draft'; +import VersionPosts from './collections/Versions'; export default buildConfig({ collections: [ AutosavePosts, DraftPosts, + VersionPosts, ], globals: [ AutosaveGlobal, From 0a4766a61e22362f361ddded826575d761675940 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 16 Jan 2023 19:04:09 -0500 Subject: [PATCH 12/19] chore: passing tests --- .../components/elements/Autosave/index.tsx | 10 +-- src/collections/operations/update.ts | 1 + .../drafts/replaceWithDraftIfAvailable.ts | 1 - .../ensurePublishedCollectionVersion.ts | 80 ------------------- src/versions/saveCollectionVersion.ts | 6 +- test/versions/int.spec.ts | 14 ++-- 6 files changed, 19 insertions(+), 93 deletions(-) delete mode 100644 src/versions/ensurePublishedCollectionVersion.ts diff --git a/src/admin/components/elements/Autosave/index.tsx b/src/admin/components/elements/Autosave/index.tsx index d444277c4..71706806e 100644 --- a/src/admin/components/elements/Autosave/index.tsx +++ b/src/admin/components/elements/Autosave/index.tsx @@ -95,13 +95,13 @@ const Autosave: React.FC = ({ collection, global, id, publishedDocUpdated } if (url) { - const body = { - ...reduceFieldsToValues(fieldRef.current, true), - _status: 'draft', - }; - setTimeout(async () => { if (modifiedRef.current) { + const body = { + ...reduceFieldsToValues(fieldRef.current, true), + _status: 'draft', + }; + const res = await fetch(url, { method, credentials: 'include', diff --git a/src/collections/operations/update.ts b/src/collections/operations/update.ts index 954b19862..786e7fe91 100644 --- a/src/collections/operations/update.ts +++ b/src/collections/operations/update.ts @@ -251,6 +251,7 @@ async function update(incomingArgs: Arguments): Promise { docWithLocales: result, id, autosave, + draft: shouldSaveDraft, }); } diff --git a/src/versions/drafts/replaceWithDraftIfAvailable.ts b/src/versions/drafts/replaceWithDraftIfAvailable.ts index d8decffa7..374edb5e1 100644 --- a/src/versions/drafts/replaceWithDraftIfAvailable.ts +++ b/src/versions/drafts/replaceWithDraftIfAvailable.ts @@ -3,7 +3,6 @@ import { docHasTimestamps, Where } from '../../types'; import { hasWhereAccessResult } from '../../auth'; import { AccessResult } from '../../config/types'; import { CollectionModel, SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types'; -import flattenWhereConstraints from '../../utilities/flattenWhereConstraints'; import sanitizeInternalFields from '../../utilities/sanitizeInternalFields'; import { appendVersionToQueryKey } from './appendVersionToQueryKey'; import { SanitizedGlobalConfig } from '../../globals/config/types'; diff --git a/src/versions/ensurePublishedCollectionVersion.ts b/src/versions/ensurePublishedCollectionVersion.ts deleted file mode 100644 index d440871a6..000000000 --- a/src/versions/ensurePublishedCollectionVersion.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Payload } from '..'; -import { SanitizedCollectionConfig } from '../collections/config/types'; -import { enforceMaxVersions } from './enforceMaxVersions'; -import { PayloadRequest } from '../express/types'; -import { afterRead } from '../fields/hooks/afterRead'; - -type Args = { - payload: Payload - config?: SanitizedCollectionConfig - req: PayloadRequest - docWithLocales: any - id: string | number -} - -export const ensurePublishedCollectionVersion = async ({ - payload, - config, - req, - id, - docWithLocales, -}: Args): Promise => { - // If there are no newer drafts, - // And the current doc is published, - // We need to keep a version of the published document - - if (docWithLocales?._status === 'published') { - const VersionModel = payload.versions[config.slug]; - - const moreRecentDrafts = await VersionModel.find({ - parent: { - $eq: docWithLocales.id, - }, - updatedAt: { - $gt: docWithLocales.updatedAt, - }, - }, - {}, - { - lean: true, - leanWithId: true, - sort: { - updatedAt: 'desc', - }, - }); - - if (moreRecentDrafts?.length === 0) { - const version = await afterRead({ - depth: 0, - doc: docWithLocales, - entityConfig: config, - req, - overrideAccess: true, - showHiddenFields: true, - flattenLocales: false, - }); - - try { - await VersionModel.create({ - parent: id, - version, - autosave: false, - }); - } catch (err) { - payload.logger.error(`There was an error while saving a version for the ${config.slug} with ID ${id}.`); - payload.logger.error(err); - } - - if (config.versions.maxPerDoc) { - enforceMaxVersions({ - id, - payload, - Model: VersionModel, - slug: config.slug, - entityType: 'collection', - max: config.versions.maxPerDoc, - }); - } - } - } -}; diff --git a/src/versions/saveCollectionVersion.ts b/src/versions/saveCollectionVersion.ts index 7b2bca8f0..846377162 100644 --- a/src/versions/saveCollectionVersion.ts +++ b/src/versions/saveCollectionVersion.ts @@ -11,6 +11,7 @@ type Args = { docWithLocales: any id: string | number autosave?: boolean + draft?: boolean } export const saveCollectionVersion = async ({ @@ -19,11 +20,14 @@ export const saveCollectionVersion = async ({ id, docWithLocales, autosave, + draft, }: Args): Promise> => { const VersionModel = payload.versions[config.slug]; let result = { ...docWithLocales }; + if (draft) result._status = 'draft'; + if (result._id) delete result._id; let existingAutosaveVersion; @@ -49,7 +53,7 @@ export const saveCollectionVersion = async ({ } else { result = await VersionModel.create({ parent: id, - version: docWithLocales, + version: result, autosave: Boolean(autosave), }); } diff --git a/test/versions/int.spec.ts b/test/versions/int.spec.ts index b54bad5f9..f3a3658df 100644 --- a/test/versions/int.spec.ts +++ b/test/versions/int.spec.ts @@ -159,7 +159,7 @@ describe('Versions', () => { locale: 'all', }); - expect(versions.docs[0].version.title.en).toStrictEqual(englishTitle); + expect(versions.docs[0].version.title.en).toStrictEqual(newEnglishTitle); expect(versions.docs[0].version.title.es).toStrictEqual(spanishTitle); }); }); @@ -184,7 +184,7 @@ describe('Versions', () => { const restore = await payload.restoreVersion({ collection, - id: versions.docs[0].id, + id: versions.docs[1].id, }); expect(restore.title).toBeDefined(); @@ -195,7 +195,7 @@ describe('Versions', () => { draft: true, }); - expect(restoredPost.title).toBe(restore.title); + expect(restoredPost.title).toBe(versions.docs[1].version.title); }); }); @@ -226,13 +226,15 @@ describe('Versions', () => { draft: true, }); + const spanishTitle = 'es title'; + // second update to existing draft await payload.update({ id: collectionLocalPostID, collection, locale: 'es', data: { - title: updatedTitle, + title: spanishTitle, }, draft: true, }); @@ -251,7 +253,7 @@ describe('Versions', () => { expect(publishedPost.title).toBe(originalTitle); expect(draftPost.title.en).toBe(updatedTitle); - expect(draftPost.title.es).toBe(updatedTitle); + expect(draftPost.title.es).toBe(spanishTitle); }); }); }); @@ -423,7 +425,7 @@ describe('Versions', () => { expect(data.id).toBeDefined(); expect(data.parent.id).toStrictEqual(collectionGraphQLPostID); - expect(data.version.title).toStrictEqual(collectionGraphQLOriginalTitle); + expect(data.version.title).toStrictEqual(updatedTitle); }); it('should allow read of versions by querying version content', async () => { From fc7709da51e5d9c382930d9d7640e6685712810b Mon Sep 17 00:00:00 2001 From: James Date: Mon, 16 Jan 2023 20:43:37 -0500 Subject: [PATCH 13/19] chore: flattens saving versions into a single file --- src/collections/operations/create.ts | 6 +- src/collections/operations/update.ts | 8 +-- src/versions/saveVersion.ts | 97 ++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 src/versions/saveVersion.ts diff --git a/src/collections/operations/create.ts b/src/collections/operations/create.ts index 35a436cce..8fea30e3c 100644 --- a/src/collections/operations/create.ts +++ b/src/collections/operations/create.ts @@ -16,7 +16,7 @@ import { beforeValidate } from '../../fields/hooks/beforeValidate'; import { afterChange } from '../../fields/hooks/afterChange'; import { afterRead } from '../../fields/hooks/afterRead'; import { generateFileData } from '../../uploads/generateFileData'; -import { saveCollectionVersion } from '../../versions/saveCollectionVersion'; +import { saveVersion } from '../../versions/saveVersion'; export type Arguments = { collection: Collection @@ -221,9 +221,9 @@ async function create(incomingArgs: Arguments): Promise { // ///////////////////////////////////// if (collectionConfig.versions) { - await saveCollectionVersion({ + await saveVersion({ payload, - config: collectionConfig, + collection: collectionConfig, req, id: result.id, docWithLocales: result, diff --git a/src/collections/operations/update.ts b/src/collections/operations/update.ts index 786e7fe91..a6ad382ae 100644 --- a/src/collections/operations/update.ts +++ b/src/collections/operations/update.ts @@ -6,7 +6,7 @@ import executeAccess from '../../auth/executeAccess'; import { NotFound, Forbidden, APIError, ValidationError } from '../../errors'; import { PayloadRequest } from '../../express/types'; import { hasWhereAccessResult } from '../../auth/types'; -import { saveCollectionVersion } from '../../versions/saveCollectionVersion'; +import { saveVersion } from '../../versions/saveVersion'; import { uploadFiles } from '../../uploads/uploadFiles'; import { beforeChange } from '../../fields/hooks/beforeChange'; import { beforeValidate } from '../../fields/hooks/beforeValidate'; @@ -241,12 +241,13 @@ async function update(incomingArgs: Arguments): Promise { // custom id type reset result.id = result._id; + result = sanitizeInternalFields(result); } if (collectionConfig.versions) { - result = await saveCollectionVersion({ + result = await saveVersion({ payload, - config: collectionConfig, + collection: collectionConfig, req, docWithLocales: result, id, @@ -255,7 +256,6 @@ async function update(incomingArgs: Arguments): Promise { }); } - result = sanitizeInternalFields(result); // ///////////////////////////////////// // afterRead - Fields diff --git a/src/versions/saveVersion.ts b/src/versions/saveVersion.ts new file mode 100644 index 000000000..a8ca6a0dc --- /dev/null +++ b/src/versions/saveVersion.ts @@ -0,0 +1,97 @@ +import { FilterQuery } from 'mongoose'; +import { Payload } from '..'; +import { SanitizedCollectionConfig } from '../collections/config/types'; +import { enforceMaxVersions } from './enforceMaxVersions'; +import { PayloadRequest } from '../express/types'; +import sanitizeInternalFields from '../utilities/sanitizeInternalFields'; +import { SanitizedGlobalConfig } from '../globals/config/types'; + +type Args = { + payload: Payload + global?: SanitizedGlobalConfig + collection?: SanitizedCollectionConfig + req: PayloadRequest + docWithLocales: any + id?: string | number + autosave?: boolean + draft?: boolean +} + +export const saveVersion = async ({ + payload, + collection, + global, + id, + docWithLocales, + autosave, + draft, +}: Args): Promise> => { + let entityConfig; + if (collection) entityConfig = collection; + if (global) entityConfig = global; + + const VersionModel = payload.versions[entityConfig.slug]; + + const versionData = { ...docWithLocales }; + if (draft) versionData._status = 'draft'; + if (versionData._id) delete versionData._id; + + let existingAutosaveVersion; + + if (autosave) { + const query: FilterQuery = {}; + if (collection) query.parent = id; + existingAutosaveVersion = await VersionModel.findOne(query, {}, { sort: { updatedAt: 'desc' } }); + } + + let result; + + try { + if (autosave && existingAutosaveVersion?.autosave === true) { + result = await VersionModel.findByIdAndUpdate( + { + _id: existingAutosaveVersion._id, + }, + { + version: versionData, + }, + { new: true, lean: true }, + ); + // Otherwise, create a new one + } else { + const data: Record = { + version: versionData, + autosave: Boolean(autosave), + }; + + if (collection) data.parent = id; + + result = await VersionModel.create(data); + } + } catch (err) { + let errorMessage: string; + + if (collection) errorMessage = `There was an error while saving a version for the ${collection.labels.singular} with ID ${id}.`; + if (global) errorMessage = `There was an error while saving a version for the global ${global.label}.`; + payload.logger.error(errorMessage); + payload.logger.error(err); + } + + if (collection && collection.versions.maxPerDoc) { + enforceMaxVersions({ + id, + payload, + Model: VersionModel, + slug: entityConfig.slug, + entityType: 'collection', + max: collection.versions.maxPerDoc, + }); + } + + result = result.version; + result = JSON.parse(JSON.stringify(result)); + result = sanitizeInternalFields(result); + result.id = id; + + return result; +}; From 8cfa5509540225100237e6f569eb9eb1a7d5448e Mon Sep 17 00:00:00 2001 From: James Date: Mon, 16 Jan 2023 21:02:32 -0500 Subject: [PATCH 14/19] feat: simplifies versions logic --- src/collections/operations/update.ts | 18 ++--- src/globals/operations/update.ts | 79 ++++++++-------------- src/versions/drafts/saveGlobalDraft.ts | 70 ------------------- src/versions/saveCollectionVersion.ts | 82 ---------------------- src/versions/saveGlobalVersion.ts | 94 -------------------------- src/versions/saveVersion.ts | 38 ++++++----- 6 files changed, 57 insertions(+), 324 deletions(-) delete mode 100644 src/versions/drafts/saveGlobalDraft.ts delete mode 100644 src/versions/saveCollectionVersion.ts delete mode 100644 src/versions/saveGlobalVersion.ts diff --git a/src/collections/operations/update.ts b/src/collections/operations/update.ts index a6ad382ae..891e49219 100644 --- a/src/collections/operations/update.ts +++ b/src/collections/operations/update.ts @@ -235,17 +235,18 @@ async function update(incomingArgs: Arguments): Promise { ? new ValidationError([{ message: 'Value must be unique', field: Object.keys(error.keyValue)[0] }], t) : error; } - - const resultString = JSON.stringify(result); - result = JSON.parse(resultString); - - // custom id type reset - result.id = result._id; - result = sanitizeInternalFields(result); } + result = JSON.parse(JSON.stringify(result)); + result.id = result._id; + result = sanitizeInternalFields(result); + + // ///////////////////////////////////// + // Create version + // ///////////////////////////////////// + if (collectionConfig.versions) { - result = await saveVersion({ + await saveVersion({ payload, collection: collectionConfig, req, @@ -256,7 +257,6 @@ async function update(incomingArgs: Arguments): Promise { }); } - // ///////////////////////////////////// // afterRead - Fields // ///////////////////////////////////// diff --git a/src/globals/operations/update.ts b/src/globals/operations/update.ts index 7847effac..11cdb3fc8 100644 --- a/src/globals/operations/update.ts +++ b/src/globals/operations/update.ts @@ -1,17 +1,14 @@ import { docHasTimestamps, Where } from '../../types'; import { SanitizedGlobalConfig, TypeWithID } from '../config/types'; import executeAccess from '../../auth/executeAccess'; -import sanitizeInternalFields from '../../utilities/sanitizeInternalFields'; -import { saveGlobalVersion } from '../../versions/saveGlobalVersion'; -import { saveGlobalDraft } from '../../versions/drafts/saveGlobalDraft'; -import { ensurePublishedGlobalVersion } from '../../versions/ensurePublishedGlobalVersion'; -import cleanUpFailedVersion from '../../versions/cleanUpFailedVersion'; import { hasWhereAccessResult } from '../../auth'; import { beforeChange } from '../../fields/hooks/beforeChange'; import { beforeValidate } from '../../fields/hooks/beforeValidate'; import { afterChange } from '../../fields/hooks/afterChange'; import { afterRead } from '../../fields/hooks/afterRead'; import { PayloadRequest } from '../../express/types'; +import { saveVersion } from '../../versions/saveVersion'; +import sanitizeInternalFields from '../../utilities/sanitizeInternalFields'; type Args = { globalConfig: SanitizedGlobalConfig @@ -182,57 +179,20 @@ async function update(args: Args): Promise { skipValidation: shouldSaveDraft, }); - // ///////////////////////////////////// - // Create version from existing doc - // ///////////////////////////////////// - - let createdVersion; - - if (globalConfig.versions && !shouldSaveDraft) { - createdVersion = await saveGlobalVersion({ - payload, - config: globalConfig, - req, - docWithLocales: result, - }); - } - // ///////////////////////////////////// // Update // ///////////////////////////////////// - if (shouldSaveDraft) { - await ensurePublishedGlobalVersion({ - payload, - config: globalConfig, - req, - docWithLocales: result, - }); - - global = await saveGlobalDraft({ - payload, - config: globalConfig, - data: result, - autosave, - }); - } else { - try { - if (existingGlobal) { - global = await Model.findOneAndUpdate( - { globalType: slug }, - result, - { new: true }, - ); - } else { - result.globalType = slug; - global = await Model.create(result); - } - } catch (error) { - cleanUpFailedVersion({ - payload, - entityConfig: globalConfig, - version: createdVersion, - }); + if (!shouldSaveDraft) { + if (existingGlobal) { + global = await Model.findOneAndUpdate( + { globalType: slug }, + result, + { new: true }, + ); + } else { + result.globalType = slug; + global = await Model.create(result); } } @@ -240,6 +200,21 @@ async function update(args: Args): Promise { global = JSON.parse(global); global = sanitizeInternalFields(global); + // ///////////////////////////////////// + // Create version + // ///////////////////////////////////// + + if (globalConfig.versions) { + await saveVersion({ + payload, + global: globalConfig, + req, + docWithLocales: result, + autosave, + draft: shouldSaveDraft, + }); + } + // ///////////////////////////////////// // afterRead - Fields // ///////////////////////////////////// diff --git a/src/versions/drafts/saveGlobalDraft.ts b/src/versions/drafts/saveGlobalDraft.ts deleted file mode 100644 index de38a9aad..000000000 --- a/src/versions/drafts/saveGlobalDraft.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Payload } from '../..'; -import { enforceMaxVersions } from '../enforceMaxVersions'; -import { SanitizedGlobalConfig } from '../../globals/config/types'; - -type Args = { - payload: Payload - config?: SanitizedGlobalConfig - data: any - autosave: boolean -} - -export const saveGlobalDraft = async ({ - payload, - config, - data, - autosave, -}: Args): Promise => { - const VersionsModel = payload.versions[config.slug]; - - const dataAsDraft = { ...data, _status: 'draft' }; - - let existingAutosaveVersion; - - if (autosave) { - existingAutosaveVersion = await VersionsModel.findOne(); - } - - let result; - - try { - // If there is an existing autosave document, - // Update it - if (autosave && existingAutosaveVersion?.autosave === true) { - result = await VersionsModel.findByIdAndUpdate( - { - _id: existingAutosaveVersion._id, - }, - { - version: dataAsDraft, - }, - { new: true, lean: true }, - ); - // Otherwise, create a new one - } else { - result = await VersionsModel.create({ - version: dataAsDraft, - autosave: Boolean(autosave), - }); - } - } catch (err) { - payload.logger.error(`There was an error while saving a draft for the Global ${config.slug}.`); - payload.logger.error(err); - } - - if (config.versions.max) { - enforceMaxVersions({ - payload: this, - Model: VersionsModel, - slug: config.slug, - entityType: 'global', - max: config.versions.max, - }); - } - - result = result.version; - result = JSON.stringify(result); - result = JSON.parse(result); - - return result; -}; diff --git a/src/versions/saveCollectionVersion.ts b/src/versions/saveCollectionVersion.ts deleted file mode 100644 index 846377162..000000000 --- a/src/versions/saveCollectionVersion.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Payload } from '..'; -import { SanitizedCollectionConfig } from '../collections/config/types'; -import { enforceMaxVersions } from './enforceMaxVersions'; -import { PayloadRequest } from '../express/types'; -import sanitizeInternalFields from '../utilities/sanitizeInternalFields'; - -type Args = { - payload: Payload - config?: SanitizedCollectionConfig - req: PayloadRequest - docWithLocales: any - id: string | number - autosave?: boolean - draft?: boolean -} - -export const saveCollectionVersion = async ({ - payload, - config, - id, - docWithLocales, - autosave, - draft, -}: Args): Promise> => { - const VersionModel = payload.versions[config.slug]; - - let result = { ...docWithLocales }; - - if (draft) result._status = 'draft'; - - if (result._id) delete result._id; - - let existingAutosaveVersion; - - if (autosave) { - existingAutosaveVersion = await VersionModel.findOne({ - parent: id, - }, {}, { sort: { updatedAt: 'desc' } }); - } - - try { - if (autosave && existingAutosaveVersion?.autosave === true) { - result = await VersionModel.findByIdAndUpdate( - { - _id: existingAutosaveVersion._id, - }, - { - version: result, - }, - { new: true, lean: true }, - ); - // Otherwise, create a new one - } else { - result = await VersionModel.create({ - parent: id, - version: result, - autosave: Boolean(autosave), - }); - } - } catch (err) { - payload.logger.error(`There was an error while saving a version for the ${config.labels.singular} with ID ${id}.`); - payload.logger.error(err); - } - - if (config.versions.maxPerDoc) { - enforceMaxVersions({ - id, - payload, - Model: VersionModel, - slug: config.slug, - entityType: 'collection', - max: config.versions.maxPerDoc, - }); - } - - result = result.version; - result = JSON.parse(JSON.stringify(result)); - result = sanitizeInternalFields(result); - result.id = id; - - return result; -}; diff --git a/src/versions/saveGlobalVersion.ts b/src/versions/saveGlobalVersion.ts deleted file mode 100644 index 176a9a2bc..000000000 --- a/src/versions/saveGlobalVersion.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Payload } from '..'; -import { enforceMaxVersions } from './enforceMaxVersions'; -import { PayloadRequest } from '../express/types'; -import { SanitizedGlobalConfig } from '../globals/config/types'; -import sanitizeInternalFields from '../utilities/sanitizeInternalFields'; -import { afterRead } from '../fields/hooks/afterRead'; - -type Args = { - payload: Payload - config?: SanitizedGlobalConfig - req: PayloadRequest - docWithLocales: any -} - -export const saveGlobalVersion = async ({ - payload, - config, - req, - docWithLocales, -}: Args): Promise => { - const VersionModel = payload.versions[config.slug]; - - let version = docWithLocales; - - if (config.versions?.drafts) { - const latestVersion = await VersionModel.findOne({ - updatedAt: { - $gt: docWithLocales.updatedAt, - }, - }, - {}, - { - lean: true, - leanWithId: true, - sort: { - updatedAt: 'desc', - }, - }); - - if (latestVersion) { - // If the latest version is a draft, no need to re-save it - // Example: when "promoting" a draft to published, the draft already exists. - // Instead, return null - if (latestVersion?.version?._status === 'draft') { - return null; - } - - version = latestVersion.version; - version = JSON.parse(JSON.stringify(version)); - version = sanitizeInternalFields(version); - } - } - - version = await afterRead({ - depth: 0, - doc: version, - entityConfig: config, - flattenLocales: false, - overrideAccess: true, - req, - showHiddenFields: true, - }); - - if (version._id) delete version._id; - - let createdVersion; - - try { - createdVersion = await VersionModel.create({ - version, - autosave: false, - }); - } catch (err) { - payload.logger.error(`There was an error while saving a version for the Global ${config.slug}.`); - payload.logger.error(err); - } - - if (config.versions.max) { - enforceMaxVersions({ - payload: this, - Model: VersionModel, - slug: config.slug, - entityType: 'global', - max: config.versions.max, - }); - } - - if (createdVersion) { - createdVersion = JSON.parse(JSON.stringify(createdVersion)); - createdVersion = sanitizeInternalFields(createdVersion); - } - - return createdVersion; -}; diff --git a/src/versions/saveVersion.ts b/src/versions/saveVersion.ts index a8ca6a0dc..7a706a560 100644 --- a/src/versions/saveVersion.ts +++ b/src/versions/saveVersion.ts @@ -3,7 +3,6 @@ import { Payload } from '..'; import { SanitizedCollectionConfig } from '../collections/config/types'; import { enforceMaxVersions } from './enforceMaxVersions'; import { PayloadRequest } from '../express/types'; -import sanitizeInternalFields from '../utilities/sanitizeInternalFields'; import { SanitizedGlobalConfig } from '../globals/config/types'; type Args = { @@ -25,10 +24,19 @@ export const saveVersion = async ({ docWithLocales, autosave, draft, -}: Args): Promise> => { +}: Args): Promise => { let entityConfig; - if (collection) entityConfig = collection; - if (global) entityConfig = global; + let entityType: 'global' | 'collection'; + + if (collection) { + entityConfig = collection; + entityType = 'collection'; + } + + if (global) { + entityConfig = global; + entityType = 'global'; + } const VersionModel = payload.versions[entityConfig.slug]; @@ -44,11 +52,9 @@ export const saveVersion = async ({ existingAutosaveVersion = await VersionModel.findOne(query, {}, { sort: { updatedAt: 'desc' } }); } - let result; - try { if (autosave && existingAutosaveVersion?.autosave === true) { - result = await VersionModel.findByIdAndUpdate( + await VersionModel.findByIdAndUpdate( { _id: existingAutosaveVersion._id, }, @@ -66,7 +72,7 @@ export const saveVersion = async ({ if (collection) data.parent = id; - result = await VersionModel.create(data); + await VersionModel.create(data); } } catch (err) { let errorMessage: string; @@ -77,21 +83,19 @@ export const saveVersion = async ({ payload.logger.error(err); } + let max: number; + + if (collection && typeof collection.versions.maxPerDoc === 'number') max = collection.versions.maxPerDoc; + if (global && typeof global.versions.max === 'number') max = global.versions.max; + if (collection && collection.versions.maxPerDoc) { enforceMaxVersions({ id, payload, Model: VersionModel, slug: entityConfig.slug, - entityType: 'collection', - max: collection.versions.maxPerDoc, + entityType, + max, }); } - - result = result.version; - result = JSON.parse(JSON.stringify(result)); - result = sanitizeInternalFields(result); - result.id = id; - - return result; }; From dff840c49bdda46ccbb2029893de7ac27f7602c2 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 16 Jan 2023 21:05:40 -0500 Subject: [PATCH 15/19] chore: adds version count tests --- test/versions/int.spec.ts | 55 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/test/versions/int.spec.ts b/test/versions/int.spec.ts index f3a3658df..e45107d44 100644 --- a/test/versions/int.spec.ts +++ b/test/versions/int.spec.ts @@ -256,6 +256,61 @@ describe('Versions', () => { expect(draftPost.title.es).toBe(spanishTitle); }); }); + + describe('Draft Count', () => { + it('creates proper number of drafts', async () => { + const originalDraft = await payload.create({ + collection: 'draft-posts', + draft: true, + data: { + title: 'A', + description: 'A', + _status: 'draft', + }, + }); + + await payload.update({ + collection: 'draft-posts', + id: originalDraft.id, + draft: true, + data: { + title: 'B', + description: 'B', + _status: 'draft', + }, + }); + + await payload.update({ + collection: 'draft-posts', + id: originalDraft.id, + draft: true, + data: { + title: 'C', + description: 'C', + _status: 'draft', + }, + }); + + const mostRecentDraft = await payload.findByID({ + collection: 'draft-posts', + id: originalDraft.id, + draft: true, + }); + + expect(mostRecentDraft.title).toStrictEqual('C'); + + const versions = await payload.findVersions({ + collection: 'draft-posts', + where: { + parent: { + equals: originalDraft.id, + }, + }, + }); + + expect(versions.docs).toHaveLength(3); + }); + }); }); describe('Querying', () => { From 34582da561945cacc53d0c0664b54e63ebc8da25 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 16 Jan 2023 21:40:34 -0500 Subject: [PATCH 16/19] chore: leverages versions refactor to simplify draft query logic --- package.json | 1 + src/collections/init.ts | 5 + src/collections/operations/create.ts | 1 + src/collections/operations/find.ts | 5 +- src/collections/operations/update.ts | 1 + src/globals/operations/update.ts | 1 + src/versions/drafts/mergeDrafts.ts | 229 --------------------------- src/versions/drafts/queryDrafts.ts | 86 ++++++++++ src/versions/saveVersion.ts | 13 +- yarn.lock | 5 + 10 files changed, 112 insertions(+), 235 deletions(-) delete mode 100644 src/versions/drafts/mergeDrafts.ts create mode 100644 src/versions/drafts/queryDrafts.ts diff --git a/package.json b/package.json index c046be66d..50e204242 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "minimist": "^1.2.0", "mkdirp": "^1.0.4", "mongoose": "6.5.0", + "mongoose-aggregate-paginate-v2": "^1.0.6", "mongoose-paginate-v2": "^1.6.1", "nodemailer": "^6.4.2", "object-to-formdata": "^4.1.0", diff --git a/src/collections/init.ts b/src/collections/init.ts index 00626d125..fdd83d5ef 100644 --- a/src/collections/init.ts +++ b/src/collections/init.ts @@ -3,6 +3,7 @@ import paginate from 'mongoose-paginate-v2'; import express from 'express'; import passport from 'passport'; import passportLocalMongoose from 'passport-local-mongoose'; +import mongooseAggregatePaginate from 'mongoose-aggregate-paginate-v2'; import { buildVersionCollectionFields } from '../versions/buildCollectionFields'; import buildQueryPlugin from '../mongoose/buildQuery'; import apiKeyStrategy from '../auth/strategies/apiKey'; @@ -82,6 +83,10 @@ export default function registerCollections(ctx: Payload): void { versionSchema.plugin(paginate, { useEstimatedCount: true }) .plugin(buildQueryPlugin); + if (collection.versions?.drafts) { + versionSchema.plugin(mongooseAggregatePaginate); + } + ctx.versions[collection.slug] = mongoose.model(versionModelName, versionSchema) as CollectionModel; } diff --git a/src/collections/operations/create.ts b/src/collections/operations/create.ts index 8fea30e3c..5818f6a95 100644 --- a/src/collections/operations/create.ts +++ b/src/collections/operations/create.ts @@ -228,6 +228,7 @@ async function create(incomingArgs: Arguments): Promise { id: result.id, docWithLocales: result, autosave, + createdAt: result.createdAt, }); } diff --git a/src/collections/operations/find.ts b/src/collections/operations/find.ts index 46e5677bf..9f195d553 100644 --- a/src/collections/operations/find.ts +++ b/src/collections/operations/find.ts @@ -9,7 +9,7 @@ import flattenWhereConstraints from '../../utilities/flattenWhereConstraints'; import { buildSortParam } from '../../mongoose/buildSortParam'; import { AccessResult } from '../../config/types'; import { afterRead } from '../../fields/hooks/afterRead'; -import { mergeDrafts } from '../../versions/drafts/mergeDrafts'; +import { queryDrafts } from '../../versions/drafts/queryDrafts'; export type Arguments = { collection: Collection @@ -160,13 +160,12 @@ async function find(incomingArgs: Arguments): Promis }; if (collectionConfig.versions?.drafts && draftsEnabled) { - result = await mergeDrafts({ + result = await queryDrafts({ accessResult, collection, locale, paginationOptions, payload, - query, where, }); } else { diff --git a/src/collections/operations/update.ts b/src/collections/operations/update.ts index 891e49219..496a5f7d6 100644 --- a/src/collections/operations/update.ts +++ b/src/collections/operations/update.ts @@ -254,6 +254,7 @@ async function update(incomingArgs: Arguments): Promise { id, autosave, draft: shouldSaveDraft, + createdAt: result.createdAt as string, }); } diff --git a/src/globals/operations/update.ts b/src/globals/operations/update.ts index 11cdb3fc8..9ba1f4e7c 100644 --- a/src/globals/operations/update.ts +++ b/src/globals/operations/update.ts @@ -212,6 +212,7 @@ async function update(args: Args): Promise { docWithLocales: result, autosave, draft: shouldSaveDraft, + createdAt: global.createdAt, }); } diff --git a/src/versions/drafts/mergeDrafts.ts b/src/versions/drafts/mergeDrafts.ts deleted file mode 100644 index 2ec040de5..000000000 --- a/src/versions/drafts/mergeDrafts.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { AccessResult } from '../../config/types'; -import { Where } from '../../types'; -import { Payload } from '../..'; -import { PaginatedDocs } from '../../mongoose/types'; -import { Collection, CollectionModel, TypeWithID } from '../../collections/config/types'; -import { hasWhereAccessResult } from '../../auth'; -import { appendVersionToQueryKey } from './appendVersionToQueryKey'; -import replaceWithDraftIfAvailable from './replaceWithDraftIfAvailable'; - -type AggregateVersion = { - _id: string - version: T - updatedAt: string - createdAt: string -} - -type VersionCollectionMatchMap = { - [_id: string | number]: { - updatedAt: string - createdAt: string - version: T - } -} - -type Args = { - accessResult: AccessResult - collection: Collection - locale: string - paginationOptions: any - payload: Payload - query: Record - where: Where -} - -export const mergeDrafts = async ({ - accessResult, - collection, - locale, - payload, - paginationOptions, - query, - where: incomingWhere, -}: Args): Promise> => { - // Query the main collection for any IDs that match the query - // Create object "map" for performant lookup - const mainCollectionMatchMap = await collection.Model.find(query, { updatedAt: 1 }, { limit: paginationOptions.limit, sort: paginationOptions.sort }) - .lean().then((res) => res.reduce((map, { _id, updatedAt }) => { - const newMap = map; - newMap[_id] = updatedAt; - return newMap; - }, {})); - - // Query the versions collection with a version-specific query - const VersionModel = payload.versions[collection.config.slug] as CollectionModel; - - const where = appendVersionToQueryKey(incomingWhere || {}); - - const versionQueryToBuild: { where: Where } = { - where: { - ...where, - and: [ - ...where?.and || [], - { - 'version._status': { - equals: 'draft', - }, - }, - ], - }, - }; - - if (hasWhereAccessResult(accessResult)) { - const versionAccessResult = appendVersionToQueryKey(accessResult); - versionQueryToBuild.where.and.push(versionAccessResult); - } - - const versionQuery = await VersionModel.buildQuery(versionQueryToBuild, locale); - const includedParentIDs: (string | number)[] = []; - - // Create version "map" for performant lookup - // and in the same loop, check if there are matched versions without a matched parent - // This means that the newer version's parent should appear in the main query. - // To do so, add the version's parent ID into an explicit `includedIDs` array - const versionCollectionMatchMap = await VersionModel.aggregate>([ - { - $sort: Object.entries(paginationOptions.sort).reduce((sort, [key, order]) => { - return { - ...sort, - [key]: order === 'asc' ? 1 : -1, - }; - }, {}), - }, - { - $group: { - _id: '$parent', - versionID: { $first: '$_id' }, - version: { $first: '$version' }, - updatedAt: { $first: '$updatedAt' }, - createdAt: { $first: '$createdAt' }, - }, - }, - { - $addFields: { - id: { - $toObjectId: '$_id', - }, - }, - }, - { - $lookup: { - from: collection.config.slug, - localField: 'id', - foreignField: '_id', - as: 'parent', - }, - }, - { - $match: { - parent: { - $size: 1, - }, - }, - }, - { $match: versionQuery }, - { $limit: paginationOptions.limit }, - ]).then((res) => res.reduce>((map, { _id, updatedAt, createdAt, version }) => { - const newMap = map; - newMap[_id] = { version, updatedAt, createdAt }; - - const matchedParent = mainCollectionMatchMap[_id]; - if (!matchedParent) includedParentIDs.push(_id); - return newMap; - }, {})); - - // Now we need to explicitly exclude any parent matches that have newer versions - // which did NOT appear in the versions query - const excludedParentIDs = await Promise.all(Object.entries(mainCollectionMatchMap).map(async ([parentDocID, parentDocUpdatedAt]) => { - // If there is a matched version, and it's newer, this parent should remain - if (versionCollectionMatchMap[parentDocID] && versionCollectionMatchMap[parentDocID].updatedAt > parentDocUpdatedAt) { - return null; - } - - // Otherwise, we need to check if there are newer versions present - // that did not get returned from the versions query - const versionsQuery = await VersionModel.find({ - updatedAt: { - $gt: parentDocUpdatedAt, - }, - parent: { - $eq: parentDocID, - }, - }, {}, { limit: 1 }).lean(); - - // If there are, - // this says that the newest version does not match the incoming query, - // and the parent ID should be excluded - if (versionsQuery.length > 0) { - return parentDocID; - } - - return null; - })).then((res) => res.filter((result) => Boolean(result))); - - // Run a final query against the main collection, - // passing in any ids to exclude and include - // so that they appear properly paginated - const finalQueryToBuild: { where: Where } = { - where: { - and: [], - }, - }; - - finalQueryToBuild.where.and.push({ or: [] }); - - if (hasWhereAccessResult(accessResult)) { - finalQueryToBuild.where.and.push(accessResult); - } - - if (incomingWhere) { - finalQueryToBuild.where.and[0].or.push(incomingWhere); - } - - if (includedParentIDs.length > 0) { - finalQueryToBuild.where.and[0].or.push({ - id: { - in: includedParentIDs, - }, - }); - } - - if (excludedParentIDs.length > 0) { - finalQueryToBuild.where.and.push({ - id: { - not_in: excludedParentIDs, - }, - }); - } - - const finalQuery = await collection.Model.buildQuery(finalQueryToBuild, locale); - - let result = await collection.Model.paginate(finalQuery, paginationOptions); - - result = { - ...result, - docs: await Promise.all(result.docs.map(async (doc) => { - const matchedVersion = versionCollectionMatchMap[doc.id]; - - if (matchedVersion && matchedVersion.updatedAt > doc.updatedAt) { - return { - ...doc, - ...matchedVersion.version, - createdAt: matchedVersion.createdAt, - updatedAt: matchedVersion.updatedAt, - }; - } - - return replaceWithDraftIfAvailable({ - accessResult, - payload, - entity: collection.config, - entityType: 'collection', - doc, - locale, - }); - })), - }; - - return result; -}; diff --git a/src/versions/drafts/queryDrafts.ts b/src/versions/drafts/queryDrafts.ts new file mode 100644 index 000000000..34a9ce139 --- /dev/null +++ b/src/versions/drafts/queryDrafts.ts @@ -0,0 +1,86 @@ +import { AccessResult } from '../../config/types'; +import { Where } from '../../types'; +import { Payload } from '../..'; +import { PaginatedDocs } from '../../mongoose/types'; +import { Collection, CollectionModel, TypeWithID } from '../../collections/config/types'; +import { hasWhereAccessResult } from '../../auth'; +import { appendVersionToQueryKey } from './appendVersionToQueryKey'; + +type AggregateVersion = { + _id: string + version: T + updatedAt: string + createdAt: string +} + +type Args = { + accessResult: AccessResult + collection: Collection + locale: string + paginationOptions: any + payload: Payload + where: Where +} + +export const queryDrafts = async ({ + accessResult, + collection, + locale, + payload, + paginationOptions, + where: incomingWhere, +}: Args): Promise> => { + const VersionModel = payload.versions[collection.config.slug] as CollectionModel; + + const where = appendVersionToQueryKey(incomingWhere || {}); + + const versionQueryToBuild: { where: Where } = { + where: { + ...where, + and: [ + ...where?.and || [], + ], + }, + }; + + if (hasWhereAccessResult(accessResult)) { + const versionAccessResult = appendVersionToQueryKey(accessResult); + versionQueryToBuild.where.and.push(versionAccessResult); + } + + const versionQuery = await VersionModel.buildQuery(versionQueryToBuild, locale); + + const aggregate = VersionModel.aggregate>([ + { + $sort: Object.entries(paginationOptions.sort).reduce((sort, [key, order]) => { + return { + ...sort, + [key]: order === 'asc' ? 1 : -1, + }; + }, {}), + }, + { + $group: { + _id: '$parent', + versionID: { $first: '$_id' }, + version: { $first: '$version' }, + updatedAt: { $first: '$updatedAt' }, + createdAt: { $first: '$createdAt' }, + }, + }, + { $match: versionQuery }, + { $limit: paginationOptions.limit }, + ]); + + const result = await VersionModel.aggregatePaginate(aggregate, paginationOptions); + + return { + ...result, + docs: result.docs.map((doc) => ({ + _id: doc._id, + ...doc.version, + updatedAt: doc.updatedAt, + createdAt: doc.createdAt, + })), + }; +}; diff --git a/src/versions/saveVersion.ts b/src/versions/saveVersion.ts index 7a706a560..912daff08 100644 --- a/src/versions/saveVersion.ts +++ b/src/versions/saveVersion.ts @@ -14,6 +14,7 @@ type Args = { id?: string | number autosave?: boolean draft?: boolean + createdAt?: string } export const saveVersion = async ({ @@ -24,6 +25,7 @@ export const saveVersion = async ({ docWithLocales, autosave, draft, + createdAt, }: Args): Promise => { let entityConfig; let entityType: 'global' | 'collection'; @@ -54,13 +56,17 @@ export const saveVersion = async ({ try { if (autosave && existingAutosaveVersion?.autosave === true) { + const data: Record = { + version: versionData, + }; + + if (createdAt) data.updatedAt = createdAt; + await VersionModel.findByIdAndUpdate( { _id: existingAutosaveVersion._id, }, - { - version: versionData, - }, + data, { new: true, lean: true }, ); // Otherwise, create a new one @@ -70,6 +76,7 @@ export const saveVersion = async ({ autosave: Boolean(autosave), }; + if (createdAt) data.createdAt = createdAt; if (collection) data.parent = id; await VersionModel.create(data); diff --git a/yarn.lock b/yarn.lock index ca47c7084..497ea791f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8514,6 +8514,11 @@ mongodb@^3.7.3: optionalDependencies: saslprep "^1.0.0" +mongoose-aggregate-paginate-v2@^1.0.6: + version "1.0.6" + resolved "https://registry.npmjs.org/mongoose-aggregate-paginate-v2/-/mongoose-aggregate-paginate-v2-1.0.6.tgz#fd2f2564d1bbf52f49a196f0b7b03675913dacca" + integrity sha512-UuALu+mjhQa1K9lMQvjLL3vm3iALvNw8PQNIh2gp1b+tO5hUa0NC0Wf6/8QrT9PSJVTihXaD8hQVy3J4e0jO0Q== + mongoose-paginate-v2@*, mongoose-paginate-v2@^1.6.1: version "1.7.1" resolved "https://registry.yarnpkg.com/mongoose-paginate-v2/-/mongoose-paginate-v2-1.7.1.tgz#0b390f5eb8e5dca55ffcb1fd7b4d8078636cb8f1" From a53682acee17f154d3e76da8cd921a3bfb214fc1 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 17 Jan 2023 09:59:32 -0500 Subject: [PATCH 17/19] chore: uses resulting version in update ops if draft=true --- src/collections/operations/update.ts | 2 +- src/globals/operations/update.ts | 2 +- src/versions/drafts/queryDrafts.ts | 6 ++++++ src/versions/saveVersion.ts | 16 +++++++++++++--- 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/collections/operations/update.ts b/src/collections/operations/update.ts index 496a5f7d6..7f95a8b6d 100644 --- a/src/collections/operations/update.ts +++ b/src/collections/operations/update.ts @@ -246,7 +246,7 @@ async function update(incomingArgs: Arguments): Promise { // ///////////////////////////////////// if (collectionConfig.versions) { - await saveVersion({ + result = await saveVersion({ payload, collection: collectionConfig, req, diff --git a/src/globals/operations/update.ts b/src/globals/operations/update.ts index 9ba1f4e7c..aed1e05d9 100644 --- a/src/globals/operations/update.ts +++ b/src/globals/operations/update.ts @@ -205,7 +205,7 @@ async function update(args: Args): Promise { // ///////////////////////////////////// if (globalConfig.versions) { - await saveVersion({ + global = await saveVersion({ payload, global: globalConfig, req, diff --git a/src/versions/drafts/queryDrafts.ts b/src/versions/drafts/queryDrafts.ts index 34a9ce139..98ad57f08 100644 --- a/src/versions/drafts/queryDrafts.ts +++ b/src/versions/drafts/queryDrafts.ts @@ -50,6 +50,12 @@ export const queryDrafts = async ({ const versionQuery = await VersionModel.buildQuery(versionQueryToBuild, locale); + // TODO + // 1. Before group, we could potentially run $match to reduce matches + // 2. Then before group, need to sort by updatedAt so first versions are newest versions + // 3. Finally can group + // 4. Then need to sort on user-defined sort again, if it differs from default + const aggregate = VersionModel.aggregate>([ { $sort: Object.entries(paginationOptions.sort).reduce((sort, [key, order]) => { diff --git a/src/versions/saveVersion.ts b/src/versions/saveVersion.ts index 912daff08..42098bc5f 100644 --- a/src/versions/saveVersion.ts +++ b/src/versions/saveVersion.ts @@ -4,6 +4,7 @@ import { SanitizedCollectionConfig } from '../collections/config/types'; import { enforceMaxVersions } from './enforceMaxVersions'; import { PayloadRequest } from '../express/types'; import { SanitizedGlobalConfig } from '../globals/config/types'; +import sanitizeInternalFields from '../utilities/sanitizeInternalFields'; type Args = { payload: Payload @@ -26,7 +27,7 @@ export const saveVersion = async ({ autosave, draft, createdAt, -}: Args): Promise => { +}: Args): Promise> => { let entityConfig; let entityType: 'global' | 'collection'; @@ -54,6 +55,8 @@ export const saveVersion = async ({ existingAutosaveVersion = await VersionModel.findOne(query, {}, { sort: { updatedAt: 'desc' } }); } + let result; + try { if (autosave && existingAutosaveVersion?.autosave === true) { const data: Record = { @@ -62,7 +65,7 @@ export const saveVersion = async ({ if (createdAt) data.updatedAt = createdAt; - await VersionModel.findByIdAndUpdate( + result = await VersionModel.findByIdAndUpdate( { _id: existingAutosaveVersion._id, }, @@ -79,7 +82,7 @@ export const saveVersion = async ({ if (createdAt) data.createdAt = createdAt; if (collection) data.parent = id; - await VersionModel.create(data); + result = await VersionModel.create(data); } } catch (err) { let errorMessage: string; @@ -105,4 +108,11 @@ export const saveVersion = async ({ max, }); } + + result = result.version; + result = JSON.parse(JSON.stringify(result)); + result = sanitizeInternalFields(result); + result.id = id; + + return result; }; From 3bb262fd660fe95cf09c101c702d1623552e14d8 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 17 Jan 2023 15:01:15 -0500 Subject: [PATCH 18/19] chore: passing tests --- src/collections/init.ts | 2 +- src/collections/operations/create.ts | 1 + src/collections/operations/update.ts | 2 +- src/globals/init.ts | 2 +- src/versions/buildCollectionFields.ts | 14 ++++++++++++ src/versions/buildGlobalFields.ts | 14 ++++++++++++ src/versions/drafts/queryDrafts.ts | 31 +++++++++++++-------------- src/versions/saveVersion.ts | 10 +++++++-- test/versions/int.spec.ts | 5 +++++ 9 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/collections/init.ts b/src/collections/init.ts index fdd83d5ef..e60b95eb9 100644 --- a/src/collections/init.ts +++ b/src/collections/init.ts @@ -75,7 +75,7 @@ export default function registerCollections(ctx: Payload): void { disableUnique: true, draftsEnabled: true, options: { - timestamps: true, + timestamps: false, }, }, ); diff --git a/src/collections/operations/create.ts b/src/collections/operations/create.ts index 5818f6a95..532029cd3 100644 --- a/src/collections/operations/create.ts +++ b/src/collections/operations/create.ts @@ -229,6 +229,7 @@ async function create(incomingArgs: Arguments): Promise { docWithLocales: result, autosave, createdAt: result.createdAt, + onCreate: true, }); } diff --git a/src/collections/operations/update.ts b/src/collections/operations/update.ts index 7f95a8b6d..5b520bf07 100644 --- a/src/collections/operations/update.ts +++ b/src/collections/operations/update.ts @@ -254,7 +254,7 @@ async function update(incomingArgs: Arguments): Promise { id, autosave, draft: shouldSaveDraft, - createdAt: result.createdAt as string, + createdAt: originalDoc.createdAt, }); } diff --git a/src/globals/init.ts b/src/globals/init.ts index 8558aa2cb..13d58c16a 100644 --- a/src/globals/init.ts +++ b/src/globals/init.ts @@ -30,7 +30,7 @@ export default function initGlobals(ctx: Payload): void { disableUnique: true, draftsEnabled: true, options: { - timestamps: true, + timestamps: false, }, }, ); diff --git a/src/versions/buildCollectionFields.ts b/src/versions/buildCollectionFields.ts index ff6b55275..69395e23e 100644 --- a/src/versions/buildCollectionFields.ts +++ b/src/versions/buildCollectionFields.ts @@ -14,6 +14,20 @@ export const buildVersionCollectionFields = (collection: SanitizedCollectionConf type: 'group', fields: collection.fields, }, + { + name: 'createdAt', + type: 'date', + admin: { + disabled: true, + }, + }, + { + name: 'updatedAt', + type: 'date', + admin: { + disabled: true, + }, + }, ]; if (collection?.versions?.drafts && collection?.versions?.drafts?.autosave) { diff --git a/src/versions/buildGlobalFields.ts b/src/versions/buildGlobalFields.ts index 23102fec9..8afb8723c 100644 --- a/src/versions/buildGlobalFields.ts +++ b/src/versions/buildGlobalFields.ts @@ -8,6 +8,20 @@ export const buildVersionGlobalFields = (global: SanitizedGlobalConfig): Field[] type: 'group', fields: global.fields, }, + { + name: 'createdAt', + type: 'date', + admin: { + disabled: true, + }, + }, + { + name: 'updatedAt', + type: 'date', + admin: { + disabled: true, + }, + }, ]; if (global?.versions?.drafts && global?.versions?.drafts?.autosave) { diff --git a/src/versions/drafts/queryDrafts.ts b/src/versions/drafts/queryDrafts.ts index 98ad57f08..437d698ff 100644 --- a/src/versions/drafts/queryDrafts.ts +++ b/src/versions/drafts/queryDrafts.ts @@ -50,13 +50,21 @@ export const queryDrafts = async ({ const versionQuery = await VersionModel.buildQuery(versionQueryToBuild, locale); - // TODO - // 1. Before group, we could potentially run $match to reduce matches - // 2. Then before group, need to sort by updatedAt so first versions are newest versions - // 3. Finally can group - // 4. Then need to sort on user-defined sort again, if it differs from default - const aggregate = VersionModel.aggregate>([ + // Sort so that newest are first + { $sort: { updatedAt: -1 } }, + // Group by parent ID, and take the first of each + { + $group: { + _id: '$parent', + version: { $first: '$version' }, + updatedAt: { $first: '$updatedAt' }, + createdAt: { $first: '$createdAt' }, + }, + }, + // Filter based on incoming query + { $match: versionQuery }, + // Re-sort based on incoming sort { $sort: Object.entries(paginationOptions.sort).reduce((sort, [key, order]) => { return { @@ -65,16 +73,7 @@ export const queryDrafts = async ({ }; }, {}), }, - { - $group: { - _id: '$parent', - versionID: { $first: '$_id' }, - version: { $first: '$version' }, - updatedAt: { $first: '$updatedAt' }, - createdAt: { $first: '$createdAt' }, - }, - }, - { $match: versionQuery }, + // Add pagination limit { $limit: paginationOptions.limit }, ]); diff --git a/src/versions/saveVersion.ts b/src/versions/saveVersion.ts index 42098bc5f..9599e3c5c 100644 --- a/src/versions/saveVersion.ts +++ b/src/versions/saveVersion.ts @@ -15,7 +15,8 @@ type Args = { id?: string | number autosave?: boolean draft?: boolean - createdAt?: string + createdAt: string + onCreate?: boolean } export const saveVersion = async ({ @@ -27,6 +28,7 @@ export const saveVersion = async ({ autosave, draft, createdAt, + onCreate = false, }: Args): Promise> => { let entityConfig; let entityType: 'global' | 'collection'; @@ -56,11 +58,14 @@ export const saveVersion = async ({ } let result; + const now = new Date().toISOString(); try { if (autosave && existingAutosaveVersion?.autosave === true) { const data: Record = { version: versionData, + createdAt, + updatedAt: now, }; if (createdAt) data.updatedAt = createdAt; @@ -77,9 +82,10 @@ export const saveVersion = async ({ const data: Record = { version: versionData, autosave: Boolean(autosave), + updatedAt: onCreate ? createdAt : now, + createdAt: createdAt || now, }; - if (createdAt) data.createdAt = createdAt; if (collection) data.parent = id; result = await VersionModel.create(data); diff --git a/test/versions/int.spec.ts b/test/versions/int.spec.ts index e45107d44..d7689797e 100644 --- a/test/versions/int.spec.ts +++ b/test/versions/int.spec.ts @@ -157,6 +157,11 @@ describe('Versions', () => { const versions = await payload.findVersions({ collection, locale: 'all', + where: { + parent: { + equals: collectionLocalPostID, + }, + }, }); expect(versions.docs[0].version.title.en).toStrictEqual(newEnglishTitle); From 2c6844c327c4dd365e8f2020c30f5be58a2e0a26 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 17 Jan 2023 15:03:38 -0500 Subject: [PATCH 19/19] chore: removes unused code --- src/versions/cleanUpFailedVersion.ts | 21 ------ src/versions/ensurePublishedGlobalVersion.ts | 73 -------------------- src/versions/shouldIncrementVersionCount.ts | 14 ---- 3 files changed, 108 deletions(-) delete mode 100644 src/versions/cleanUpFailedVersion.ts delete mode 100644 src/versions/ensurePublishedGlobalVersion.ts delete mode 100644 src/versions/shouldIncrementVersionCount.ts diff --git a/src/versions/cleanUpFailedVersion.ts b/src/versions/cleanUpFailedVersion.ts deleted file mode 100644 index 6767c02fa..000000000 --- a/src/versions/cleanUpFailedVersion.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Payload } from '..'; -import { SanitizedCollectionConfig } from '../collections/config/types'; -import { SanitizedGlobalConfig } from '../globals/config/types'; -import { TypeWithVersion } from './types'; - -type Args = { - payload: Payload, - entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig, - version: TypeWithVersion -} - -const cleanUpFailedVersion = (args: Args) => { - const { payload, entityConfig, version } = args; - - if (version) { - const VersionModel = payload.versions[entityConfig.slug]; - VersionModel.findOneAndDelete({ _id: version.id }); - } -}; - -export default cleanUpFailedVersion; diff --git a/src/versions/ensurePublishedGlobalVersion.ts b/src/versions/ensurePublishedGlobalVersion.ts deleted file mode 100644 index c4b5b85a9..000000000 --- a/src/versions/ensurePublishedGlobalVersion.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Payload } from '..'; -import { enforceMaxVersions } from './enforceMaxVersions'; -import { PayloadRequest } from '../express/types'; -import { SanitizedGlobalConfig } from '../globals/config/types'; -import { afterRead } from '../fields/hooks/afterRead'; - -type Args = { - payload: Payload - config?: SanitizedGlobalConfig - req: PayloadRequest - docWithLocales: any -} - -export const ensurePublishedGlobalVersion = async ({ - payload, - config, - req, - docWithLocales, -}: Args): Promise => { - // If there are no newer drafts, - // And the current doc is published, - // We need to keep a version of the published document - - if (docWithLocales?._status === 'published') { - const VersionModel = payload.versions[config.slug]; - - const moreRecentDrafts = await VersionModel.find({ - updatedAt: { - $gt: docWithLocales.updatedAt, - }, - }, - {}, - { - lean: true, - leanWithId: true, - sort: { - updatedAt: 'desc', - }, - }); - - if (moreRecentDrafts?.length === 0) { - const version = await afterRead({ - depth: 0, - doc: docWithLocales, - entityConfig: config, - req, - overrideAccess: true, - showHiddenFields: true, - flattenLocales: false, - }); - - try { - await VersionModel.create({ - version, - autosave: false, - }); - } catch (err) { - payload.logger.error(`There was an error while saving a version for the Global ${config.label}.`); - payload.logger.error(err); - } - - if (config.versions.max) { - enforceMaxVersions({ - payload: this, - Model: VersionModel, - slug: config.slug, - entityType: 'global', - max: config.versions.max, - }); - } - } - } -}; diff --git a/src/versions/shouldIncrementVersionCount.ts b/src/versions/shouldIncrementVersionCount.ts deleted file mode 100644 index 4839c3343..000000000 --- a/src/versions/shouldIncrementVersionCount.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { SanitizedCollectionConfig } from '../collections/config/types'; -import { SanitizedGlobalConfig } from '../globals/config/types'; -import { PaginatedDocs } from '../mongoose/types'; - -type ShouldIncrementVersionCount = (args: { - entity: SanitizedGlobalConfig | SanitizedCollectionConfig - versions: PaginatedDocs<{ version?: { _status: string} }> - docStatus: string -}) => boolean - -export const shouldIncrementVersionCount: ShouldIncrementVersionCount = ({ entity, docStatus, versions }) => { - return !(entity?.versions?.drafts && entity.versions.drafts?.autosave) - && (docStatus === 'published' || (docStatus === 'draft' && versions?.docs?.[0]?.version?._status !== 'draft')); -};