From 210488ba4ebfc98c22a618de9db499bbf0a59a79 Mon Sep 17 00:00:00 2001 From: James Date: Wed, 22 Dec 2021 16:25:19 -0500 Subject: [PATCH] feat: progress to revision restore --- .../views/Revision/Restore/index.tsx | 10 +- src/collections/operations/restoreRevision.ts | 154 ++++++++++++++++-- 2 files changed, 144 insertions(+), 20 deletions(-) diff --git a/src/admin/components/views/Revision/Restore/index.tsx b/src/admin/components/views/Revision/Restore/index.tsx index a77076d3b0..0d804b3a44 100644 --- a/src/admin/components/views/Revision/Restore/index.tsx +++ b/src/admin/components/views/Revision/Restore/index.tsx @@ -19,18 +19,18 @@ const Restore: React.FC = ({ collection, global, className, revisionID, o const [processing, setProcessing] = useState(false); let fetchURL = `${serverURL}${api}`; - let redirectURL = `${serverURL}${admin}`; + let redirectURL: string; let restoreMessage: string; if (collection) { - fetchURL += `/${collection.slug}/revisions/${revisionID}`; - redirectURL += `/collections/${collection.slug}/${originalDocID}`; + fetchURL = `${fetchURL}/${collection.slug}/revisions/${revisionID}`; + redirectURL = `${admin}/collections/${collection.slug}/${originalDocID}`; restoreMessage = `You are about to restore this ${collection.labels.singular} document to the state that it was in on ${revisionDate}.`; } if (global) { - fetchURL += `/globals/${global.slug}/revisions/${revisionID}`; - redirectURL += `/globals/${global.slug}`; + fetchURL = `${fetchURL}/globals/${global.slug}/revisions/${revisionID}`; + redirectURL = `${admin}/globals/${global.slug}`; restoreMessage = `You are about to restore the global ${global.label} to the state that it was in on ${revisionDate}.`; } diff --git a/src/collections/operations/restoreRevision.ts b/src/collections/operations/restoreRevision.ts index fd1deb619d..4219c9a5ea 100644 --- a/src/collections/operations/restoreRevision.ts +++ b/src/collections/operations/restoreRevision.ts @@ -2,9 +2,13 @@ import httpStatus from 'http-status'; import { PayloadRequest } from '../../express/types'; import { Collection } from '../config/types'; -import { APIError } from '../../errors'; -import { TypeWithRevision } from '../../revisions/types'; +import { APIError, Forbidden, NotFound } from '../../errors'; +import executeAccess from '../../auth/executeAccess'; import { Payload } from '../../index'; +import { hasWhereAccessResult } from '../../auth/types'; +import { Where } from '../../types'; +import { TypeWithID } from '../../globals/config/types'; +import sanitizeInternalFields from '../../utilities/sanitizeInternalFields'; export type Arguments = { collection: Collection @@ -17,11 +21,20 @@ export type Arguments = { depth?: number } -async function restoreRevision = any>(this: Payload, args: Arguments): Promise { +async function restoreRevision(this: Payload, args: Arguments): Promise { const { - collection, + collection: { + Model, + config: collectionConfig, + }, id, - // overrideAccess = false, + overrideAccess = false, + showHiddenFields, + depth, + req: { + locale, + }, + req, } = args; if (!id) { @@ -29,22 +42,133 @@ async function restoreRevision = any>(this: Payloa } // ///////////////////////////////////// - // Retrieve revision + // Retrieve original raw revision to get parent ID // ///////////////////////////////////// - const revision = await this.findRevisionByID({ - ...args, - collection: collection.config.slug, + const RevisionModel = this.revisions[collectionConfig.slug]; + + let rawRevision = await RevisionModel.findOne({ + _id: id, }); - const result = await this.update({ - ...args, - id: revision.parent, - collection: collection.config.slug, - data: revision.revision, - locale: args.req.locale, + if (!rawRevision) { + throw new NotFound(); + } + + rawRevision = rawRevision.toJSON({ virtuals: true }); + + const parentDocID = rawRevision.parent; + + // ///////////////////////////////////// + // Access + // ///////////////////////////////////// + + const accessResults = !overrideAccess ? await executeAccess({ req, id: parentDocID }, collectionConfig.access.update) : true; + const hasWherePolicy = hasWhereAccessResult(accessResults); + + // ///////////////////////////////////// + // Retrieve document + // ///////////////////////////////////// + + const queryToBuild: { where: Where } = { + where: { + and: [ + { + id: { + equals: parentDocID, + }, + }, + ], + }, + }; + + if (hasWhereAccessResult(accessResults)) { + (queryToBuild.where.and as Where[]).push(accessResults); + } + + const query = await Model.buildQuery(queryToBuild, locale); + + const doc = await Model.findOne(query); + + if (!doc && !hasWherePolicy) throw new NotFound(); + if (!doc && hasWherePolicy) throw new Forbidden(); + + // ///////////////////////////////////// + // Update + // ///////////////////////////////////// + + let result = await Model.findByIdAndUpdate( + { _id: parentDocID }, + rawRevision.revision, + { new: true }, + ); + + result = result.toJSON({ virtuals: true }); + + // custom id type reset + result.id = result._id; + result = JSON.stringify(result); + result = JSON.parse(result); + result = sanitizeInternalFields(result); + + // ///////////////////////////////////// + // afterRead - Fields + // ///////////////////////////////////// + + result = await this.performFieldOperations(collectionConfig, { + id: parentDocID, + depth, + req, + data: result, + hook: 'afterRead', + operation: 'update', + overrideAccess, + flattenLocales: true, + showHiddenFields, }); + // ///////////////////////////////////// + // afterRead - Collection + // ///////////////////////////////////// + + await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => { + await priorHook; + + result = await hook({ + req, + doc: result, + }) || result; + }, Promise.resolve()); + + // ///////////////////////////////////// + // afterChange - Fields + // ///////////////////////////////////// + + result = await this.performFieldOperations(collectionConfig, { + data: result, + hook: 'afterChange', + operation: 'update', + req, + id: parentDocID, + depth, + overrideAccess, + showHiddenFields, + }); + + // ///////////////////////////////////// + // afterChange - Collection + // ///////////////////////////////////// + + await collectionConfig.hooks.afterChange.reduce(async (priorHook, hook) => { + await priorHook; + + result = await hook({ + doc: result, + req, + operation: 'update', + }) || result; + }, Promise.resolve()); + return result; }