From 46b750b3ec18bd2540f6f96e1dc231bce8bbdb16 Mon Sep 17 00:00:00 2001 From: Jarrod Flesch Date: Mon, 30 Jan 2023 10:29:38 -0500 Subject: [PATCH] chore: ensures only drafts use current time in versioning --- src/collections/operations/create.ts | 5 +- src/collections/operations/restoreVersion.ts | 11 ++- src/collections/operations/update.ts | 17 +++-- src/globals/operations/update.ts | 50 ++++++-------- src/versions/getLatestCollectionVersion.ts | 55 +++++++++------ src/versions/saveVersion.ts | 72 +++++++++----------- 6 files changed, 106 insertions(+), 104 deletions(-) diff --git a/src/collections/operations/create.ts b/src/collections/operations/create.ts index fce3004e05..d513805463 100644 --- a/src/collections/operations/create.ts +++ b/src/collections/operations/create.ts @@ -214,8 +214,7 @@ async function create( // custom id type reset result.id = result._id; - result = JSON.stringify(result); - result = JSON.parse(result); + result = JSON.parse(JSON.stringify(result)); result = sanitizeInternalFields(result); // ///////////////////////////////////// @@ -230,8 +229,6 @@ async function create( id: result.id, docWithLocales: result, autosave, - createdAt: result.createdAt, - onCreate: true, }); } diff --git a/src/collections/operations/restoreVersion.ts b/src/collections/operations/restoreVersion.ts index c3d061302c..8988a4f302 100644 --- a/src/collections/operations/restoreVersion.ts +++ b/src/collections/operations/restoreVersion.ts @@ -9,7 +9,7 @@ import { Where } from '../../types'; import sanitizeInternalFields from '../../utilities/sanitizeInternalFields'; import { afterChange } from '../../fields/hooks/afterChange'; import { afterRead } from '../../fields/hooks/afterRead'; -import { getLatestCollectionVersion } from '../../versions/getLatestCollectionVersion'; +import { getLatestEntityVersion } from '../../versions/getLatestCollectionVersion'; export type Arguments = { collection: Collection @@ -24,7 +24,6 @@ export type Arguments = { async function restoreVersion(args: Arguments): Promise { const { - collection, collection: { Model, config: collectionConfig, @@ -101,11 +100,12 @@ async function restoreVersion(args: Arguments): Prom // fetch previousDoc // ///////////////////////////////////// - const prevDocWithLocales = await getLatestCollectionVersion({ + const prevDocWithLocales = await getLatestEntityVersion({ payload, - collection, id: parentDocID, query, + Model, + config: collectionConfig, }); // ///////////////////////////////////// @@ -122,8 +122,7 @@ async function restoreVersion(args: Arguments): Prom // custom id type reset result.id = result._id; - result = JSON.stringify(result); - result = JSON.parse(result); + result = JSON.parse(JSON.stringify(result)); result = sanitizeInternalFields(result); // ///////////////////////////////////// diff --git a/src/collections/operations/update.ts b/src/collections/operations/update.ts index efd25a546e..c26aeb5484 100644 --- a/src/collections/operations/update.ts +++ b/src/collections/operations/update.ts @@ -14,7 +14,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 { getLatestCollectionVersion } from '../../versions/getLatestCollectionVersion'; +import { getLatestEntityVersion } from '../../versions/getLatestCollectionVersion'; export type Arguments = { collection: Collection @@ -111,7 +111,14 @@ async function update( const query = await Model.buildQuery(queryToBuild, locale); - const doc = await getLatestCollectionVersion({ payload, collection, id, query, lean }); + const doc = await getLatestEntityVersion({ + payload, + Model, + config: collectionConfig, + id, + query, + lean, + }); if (!doc && !hasWherePolicy) throw new NotFound(t); if (!doc && hasWherePolicy) throw new Forbidden(t); @@ -253,11 +260,13 @@ async function update( payload, collection: collectionConfig, req, - docWithLocales: result, + docWithLocales: { + ...result, + createdAt: docWithLocales.createdAt, + }, id, autosave, draft: shouldSaveDraft, - createdAt: originalDoc.createdAt, }); } diff --git a/src/globals/operations/update.ts b/src/globals/operations/update.ts index 70c933361a..2c8957ce63 100644 --- a/src/globals/operations/update.ts +++ b/src/globals/operations/update.ts @@ -1,5 +1,5 @@ import { Config as GeneratedTypes } from 'payload/generated-types'; -import { docHasTimestamps, Where } from '../../types'; +import { Where } from '../../types'; import { SanitizedGlobalConfig } from '../config/types'; import executeAccess from '../../auth/executeAccess'; import { hasWhereAccessResult } from '../../auth'; @@ -10,6 +10,7 @@ import { afterRead } from '../../fields/hooks/afterRead'; import { PayloadRequest } from '../../express/types'; import { saveVersion } from '../../versions/saveVersion'; import sanitizeInternalFields from '../../utilities/sanitizeInternalFields'; +import { getLatestEntityVersion } from '../../versions/getLatestCollectionVersion'; type Args = { globalConfig: SanitizedGlobalConfig @@ -82,36 +83,21 @@ async function update( // 2. Retrieve document // ///////////////////////////////////// - let version; - let global; + let global = await getLatestEntityVersion({ + payload, + Model, + config: globalConfig, + query, + lean: true, + entityType: 'global', + }); - if (globalConfig.versions?.drafts) { - version = payload.versions[globalConfig.slug].findOne({}, {}, { - sort: { - updatedAt: 'desc', - }, - lean: true, - }); - } - - const existingGlobal = await payload.globals.Model.findOne(query).lean(); - version = await version; - - if (!version || (existingGlobal && docHasTimestamps(existingGlobal) && version.updatedAt < existingGlobal.updatedAt)) { - global = existingGlobal; - } else { - global = { - ...version.version, - updatedAt: version.updatedAt, - createdAt: version.createdAt, - }; - } + const globalExists = Boolean(global); let globalJSON: Record = {}; if (global) { - const globalJSONString = JSON.stringify(global); - globalJSON = JSON.parse(globalJSONString); + globalJSON = JSON.parse(JSON.stringify(global)); if (globalJSON._id) { delete globalJSON._id; @@ -187,7 +173,7 @@ async function update( // ///////////////////////////////////// if (!shouldSaveDraft) { - if (existingGlobal) { + if (globalExists) { global = await Model.findOneAndUpdate( { globalType: slug }, result, @@ -199,8 +185,7 @@ async function update( } } - global = JSON.stringify(global); - global = JSON.parse(global); + global = JSON.parse(JSON.stringify(global)); global = sanitizeInternalFields(global); // ///////////////////////////////////// @@ -212,10 +197,13 @@ async function update( payload, global: globalConfig, req, - docWithLocales: result, + docWithLocales: { + ...result, + createdAt: global.createdAt, + updatedAt: global.updatedAt, + }, autosave, draft: shouldSaveDraft, - createdAt: global.createdAt, }); } diff --git a/src/versions/getLatestCollectionVersion.ts b/src/versions/getLatestCollectionVersion.ts index c31fda71a1..9ebaf26e6f 100644 --- a/src/versions/getLatestCollectionVersion.ts +++ b/src/versions/getLatestCollectionVersion.ts @@ -1,45 +1,58 @@ -import { Document } from '../types'; +import { docHasTimestamps, Document } from '../types'; import { Payload } from '../payload'; -import { Collection, TypeWithID } from '../collections/config/types'; -import sanitizeInternalFields from '../utilities/sanitizeInternalFields'; +import { CollectionModel, SanitizedCollectionConfig, TypeWithID } from '../collections/config/types'; +import { GlobalModel, SanitizedGlobalConfig } from '../globals/config/types'; type Args = { payload: Payload - collection: Collection, query: Record - id: string | number lean?: boolean -} +} & ({ + entityType: 'global' + id?: never + Model: GlobalModel + config: SanitizedGlobalConfig +} | { + entityType?: 'collection' + id: string | number + Model: CollectionModel + config: SanitizedCollectionConfig +}) -export const getLatestCollectionVersion = async ({ +export const getLatestEntityVersion = async ({ payload, - collection: { - config, - Model, - }, + entityType = 'collection', + config, + Model, query, id, lean = true, }: Args): Promise => { - let version; + let latestVersion; + if (config.versions?.drafts) { - version = payload.versions[config.slug].findOne({ + latestVersion = await payload.versions[config.slug].findOne({ parent: id, }, {}, { sort: { updatedAt: 'desc' }, lean, }); } - const collection = await Model.findOne(query, {}, { lean }) as Document; - version = await version; - if (!version || version.updatedAt < collection.updatedAt) { - collection.id = collection._id; - return collection; + + const doc = await (Model as any).findOne(query, {}, { lean }) as Document; + + if (!latestVersion || (docHasTimestamps(doc) && latestVersion.updatedAt < doc.updatedAt)) { + if (entityType === 'collection') { + doc.id = doc._id; + return doc; + } + return doc; } + return { - ...version.version, + ...latestVersion.version, id, - updatedAt: version.updatedAt, - createdAt: version.createdAt, + updatedAt: latestVersion.updatedAt, + createdAt: latestVersion.createdAt, }; }; diff --git a/src/versions/saveVersion.ts b/src/versions/saveVersion.ts index a63bb7810c..f5499a5ee2 100644 --- a/src/versions/saveVersion.ts +++ b/src/versions/saveVersion.ts @@ -15,8 +15,6 @@ type Args = { id?: string | number autosave?: boolean draft?: boolean - createdAt: string - onCreate?: boolean } export const saveVersion = async ({ @@ -24,12 +22,11 @@ export const saveVersion = async ({ collection, global, id, - docWithLocales, + docWithLocales: doc, autosave, draft, - createdAt, - onCreate = false, }: Args): Promise => { + let result; let entityConfig; let entityType: 'global' | 'collection'; @@ -45,49 +42,48 @@ export const saveVersion = async ({ const VersionModel = payload.versions[entityConfig.slug]; - const versionData = { ...docWithLocales }; + const versionData = { ...doc }; 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; - const now = new Date().toISOString(); - try { - if (autosave && existingAutosaveVersion?.autosave === true) { - const data: Record = { - version: versionData, - createdAt, - updatedAt: now, - }; + let createNewVersion = true; + const now = new Date().toISOString(); - if (createdAt) data.updatedAt = createdAt; + if (autosave) { + const query: FilterQuery = {}; + if (collection) query.parent = id; + const latestVersion = await VersionModel.findOne(query, {}, { sort: { updatedAt: 'desc' } }); - result = await VersionModel.findByIdAndUpdate( - { - _id: existingAutosaveVersion._id, - }, - data, - { new: true, lean: true }, - ); - // Otherwise, create a new one - } else { + // overwrite the latest version if it's set to autosave + if (latestVersion?.autosave === true) { + createNewVersion = false; + + const data: Record = { + version: versionData, + createdAt: new Date(latestVersion.createdAt).toISOString(), + updatedAt: draft ? now : new Date(doc.updatedAt).toISOString(), + }; + + result = await VersionModel.findByIdAndUpdate( + { + _id: latestVersion._id, + }, + data, + { new: true, lean: true }, + ); + } + } + + if (createNewVersion) { const data: Record = { - version: versionData, + ...versionData, autosave: Boolean(autosave), - updatedAt: onCreate ? createdAt : now, - createdAt: createdAt || now, + version: versionData, + createdAt: draft ? now : new Date(doc.createdAt).toISOString(), + updatedAt: draft ? now : new Date(doc.updatedAt).toISOString(), }; - if (collection) data.parent = id; - result = await VersionModel.create(data); } } catch (err) {