From 5eea398e4340224db4792147fd3e3bcd4d44317f Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Wed, 22 Dec 2021 14:24:24 -0500 Subject: [PATCH] feat: adds restore revisions to collections --- src/collections/init.ts | 4 +- .../operations/findRevisionByID.ts | 14 +- src/collections/operations/findRevisions.ts | 3 +- .../operations/local/findRevisionByID.ts | 48 ++++++ .../operations/local/findRevisions.ts | 53 ++++++ src/collections/operations/local/index.ts | 6 + .../operations/local/restoreRevision.ts | 48 ++++++ src/collections/operations/restoreRevision.ts | 51 ++++++ .../requestHandlers/restoreRevision.ts | 29 ++++ src/globals/init.ts | 3 +- src/globals/operations/restoreRevision.ts | 162 ++++++++++++++++++ .../requestHandlers/restoreRevision.ts | 25 +++ src/index.ts | 49 +++++- src/init/bindOperations.ts | 42 +++++ src/init/bindRequestHandlers.ts | 6 + src/revisions/tests/rest.spec.ts | 36 ++++ 16 files changed, 570 insertions(+), 9 deletions(-) create mode 100644 src/collections/operations/local/findRevisionByID.ts create mode 100644 src/collections/operations/local/findRevisions.ts create mode 100644 src/collections/operations/local/restoreRevision.ts create mode 100644 src/collections/operations/restoreRevision.ts create mode 100644 src/collections/requestHandlers/restoreRevision.ts create mode 100644 src/globals/operations/restoreRevision.ts create mode 100644 src/globals/requestHandlers/restoreRevision.ts diff --git a/src/collections/init.ts b/src/collections/init.ts index 605d822ce0..5210d1dc82 100644 --- a/src/collections/init.ts +++ b/src/collections/init.ts @@ -106,6 +106,7 @@ export default function registerCollections(ctx: Payload): void { findByID, findRevisions, findRevisionByID, + restoreRevision, delete: deleteHandler, } = ctx.requestHandlers.collections; @@ -180,7 +181,8 @@ export default function registerCollections(ctx: Payload): void { .get(findRevisions); router.route(`/${slug}/revisions/:id`) - .get(findRevisionByID); + .get(findRevisionByID) + .post(restoreRevision); } router.route(`/${slug}`) diff --git a/src/collections/operations/findRevisionByID.ts b/src/collections/operations/findRevisionByID.ts index 8c63b8e185..754d257cb3 100644 --- a/src/collections/operations/findRevisionByID.ts +++ b/src/collections/operations/findRevisionByID.ts @@ -1,8 +1,10 @@ /* eslint-disable no-underscore-dangle */ +import httpStatus from 'http-status'; +import { Payload } from '../../index'; import { PayloadRequest } from '../../express/types'; -import { Collection } from '../config/types'; +import { Collection, CollectionModel } from '../config/types'; import sanitizeInternalFields from '../../utilities/sanitizeInternalFields'; -import { Forbidden, NotFound } from '../../errors'; +import { APIError, Forbidden, NotFound } from '../../errors'; import executeAccess from '../../auth/executeAccess'; import { Where } from '../../types'; import { hasWhereAccessResult } from '../../auth/types'; @@ -19,7 +21,7 @@ export type Arguments = { depth?: number } -async function findRevisionByID = any>(args: Arguments): Promise { +async function findRevisionByID = any>(this: Payload, args: Arguments): Promise { const { depth, collection: { @@ -36,7 +38,11 @@ async function findRevisionByID = any>(args: Argum showHiddenFields, } = args; - const RevisionsModel = this.revisions[collectionConfig.slug]; + if (!id) { + throw new APIError('Missing ID of revision.', httpStatus.BAD_REQUEST); + } + + const RevisionsModel = (this.revisions[collectionConfig.slug]) as CollectionModel; // ///////////////////////////////////// // Access diff --git a/src/collections/operations/findRevisions.ts b/src/collections/operations/findRevisions.ts index d63aedb15a..554101e482 100644 --- a/src/collections/operations/findRevisions.ts +++ b/src/collections/operations/findRevisions.ts @@ -8,6 +8,7 @@ import flattenWhereConstraints from '../../utilities/flattenWhereConstraints'; import { buildSortParam } from '../../mongoose/buildSortParam'; import { PaginatedDocs } from '../../mongoose/types'; import { TypeWithRevision } from '../../revisions/types'; +import { Payload } from '../../index'; export type Arguments = { collection: Collection @@ -21,7 +22,7 @@ export type Arguments = { showHiddenFields?: boolean } -async function findRevisions = any>(args: Arguments): Promise> { +async function findRevisions = any>(this: Payload, args: Arguments): Promise> { const { where, page, diff --git a/src/collections/operations/local/findRevisionByID.ts b/src/collections/operations/local/findRevisionByID.ts new file mode 100644 index 0000000000..5303ae807c --- /dev/null +++ b/src/collections/operations/local/findRevisionByID.ts @@ -0,0 +1,48 @@ +import { Document } from '../../../types'; +import { PayloadRequest } from '../../../express/types'; +import { TypeWithRevision } from '../../../revisions/types'; + +export type Options = { + collection: string + id: string + depth?: number + locale?: string + fallbackLocale?: string + user?: Document + overrideAccess?: boolean + showHiddenFields?: boolean + disableErrors?: boolean + req?: PayloadRequest +} + +export default async function findRevisionByID = any>(options: Options): Promise { + const { + collection: collectionSlug, + depth, + id, + locale = this?.config?.localization?.defaultLocale, + fallbackLocale = null, + overrideAccess = true, + disableErrors = false, + showHiddenFields, + req, + } = options; + + const collection = this.collections[collectionSlug]; + + return this.operations.collections.findRevisionByID({ + depth, + id, + collection, + overrideAccess, + disableErrors, + showHiddenFields, + req: { + ...req, + payloadAPI: 'local', + locale, + fallbackLocale, + payload: this, + }, + }); +} diff --git a/src/collections/operations/local/findRevisions.ts b/src/collections/operations/local/findRevisions.ts new file mode 100644 index 0000000000..c01429e337 --- /dev/null +++ b/src/collections/operations/local/findRevisions.ts @@ -0,0 +1,53 @@ +import { Document, Where } from '../../../types'; +import { PaginatedDocs } from '../../../mongoose/types'; +import { TypeWithRevision } from '../../../revisions/types'; + +export type Options = { + collection: string + depth?: number + page?: number + limit?: number + locale?: string + fallbackLocale?: string + user?: Document + overrideAccess?: boolean + showHiddenFields?: boolean + sort?: string + where?: Where +} + +export default async function findRevisions = any>(options: Options): Promise> { + const { + collection: collectionSlug, + depth, + page, + limit, + where, + locale = this?.config?.localization?.defaultLocale, + fallbackLocale = null, + user, + overrideAccess = true, + showHiddenFields, + sort, + } = options; + + const collection = this.collections[collectionSlug]; + + return this.operations.collections.findRevisions({ + where, + page, + limit, + depth, + collection, + sort, + overrideAccess, + showHiddenFields, + req: { + user, + payloadAPI: 'local', + locale, + fallbackLocale, + payload: this, + }, + }); +} diff --git a/src/collections/operations/local/index.ts b/src/collections/operations/local/index.ts index a7f16bb919..2ccb69670c 100644 --- a/src/collections/operations/local/index.ts +++ b/src/collections/operations/local/index.ts @@ -4,6 +4,9 @@ import create from './create'; import update from './update'; import localDelete from './delete'; import auth from '../../../auth/operations/local'; +import findRevisionByID from './findRevisionByID'; +import findRevisions from './findRevisions'; +import restoreRevision from './restoreRevision'; export default { find, @@ -12,4 +15,7 @@ export default { update, localDelete, auth, + findRevisionByID, + findRevisions, + restoreRevision, }; diff --git a/src/collections/operations/local/restoreRevision.ts b/src/collections/operations/local/restoreRevision.ts new file mode 100644 index 0000000000..6080aae48d --- /dev/null +++ b/src/collections/operations/local/restoreRevision.ts @@ -0,0 +1,48 @@ +import { Document } from '../../../types'; +import { TypeWithRevision } from '../../../revisions/types'; + +export type Options = { + collection: string + id: string + data: Record + depth?: number + locale?: string + fallbackLocale?: string + user?: Document + overrideAccess?: boolean + showHiddenFields?: boolean +} + +export default async function restoreRevision = any>(options: Options): Promise { + const { + collection: collectionSlug, + depth, + locale = this?.config?.localization?.defaultLocale, + fallbackLocale = null, + data, + id, + user, + overrideAccess = true, + showHiddenFields, + } = options; + + const collection = this.collections[collectionSlug]; + + const args = { + depth, + data, + collection, + overrideAccess, + id, + showHiddenFields, + req: { + user, + payloadAPI: 'local', + locale, + fallbackLocale, + payload: this, + }, + }; + + return this.operations.collections.restoreRevision(args); +} diff --git a/src/collections/operations/restoreRevision.ts b/src/collections/operations/restoreRevision.ts new file mode 100644 index 0000000000..fd1deb619d --- /dev/null +++ b/src/collections/operations/restoreRevision.ts @@ -0,0 +1,51 @@ +/* eslint-disable no-underscore-dangle */ +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 { Payload } from '../../index'; + +export type Arguments = { + collection: Collection + id: string + req: PayloadRequest + disableErrors?: boolean + currentDepth?: number + overrideAccess?: boolean + showHiddenFields?: boolean + depth?: number +} + +async function restoreRevision = any>(this: Payload, args: Arguments): Promise { + const { + collection, + id, + // overrideAccess = false, + } = args; + + if (!id) { + throw new APIError('Missing ID of revision to restore.', httpStatus.BAD_REQUEST); + } + + // ///////////////////////////////////// + // Retrieve revision + // ///////////////////////////////////// + + const revision = await this.findRevisionByID({ + ...args, + collection: collection.config.slug, + }); + + const result = await this.update({ + ...args, + id: revision.parent, + collection: collection.config.slug, + data: revision.revision, + locale: args.req.locale, + }); + + return result; +} + +export default restoreRevision; diff --git a/src/collections/requestHandlers/restoreRevision.ts b/src/collections/requestHandlers/restoreRevision.ts new file mode 100644 index 0000000000..71254e62d4 --- /dev/null +++ b/src/collections/requestHandlers/restoreRevision.ts @@ -0,0 +1,29 @@ +import { Response, NextFunction } from 'express'; +import httpStatus from 'http-status'; +import { PayloadRequest } from '../../express/types'; +import { Document } from '../../types'; +import formatSuccessResponse from '../../express/responses/formatSuccess'; + +export type RestoreResult = { + message: string + doc: Document +}; + +export default async function restoreRevision(req: PayloadRequest, res: Response, next: NextFunction): Promise | void> { + const options = { + req, + collection: req.collection, + id: req.params.id, + depth: req.query.depth, + }; + + try { + const doc = await this.operations.collections.restoreRevision(options); + return res.status(httpStatus.OK).json({ + ...formatSuccessResponse('Restored successfully.', 'message'), + doc, + }); + } catch (error) { + return next(error); + } +} diff --git a/src/globals/init.ts b/src/globals/init.ts index b44f809576..5b73dd6812 100644 --- a/src/globals/init.ts +++ b/src/globals/init.ts @@ -53,7 +53,8 @@ export default function initGlobals(ctx: Payload): void { .get(ctx.requestHandlers.globals.findRevisions(global)); router.route(`/globals/${global.slug}/revisions/:id`) - .get(ctx.requestHandlers.globals.findRevisionByID(global)); + .get(ctx.requestHandlers.globals.findRevisionByID(global)) + .post(ctx.requestHandlers.globals.restoreRevision(global)); } }); diff --git a/src/globals/operations/restoreRevision.ts b/src/globals/operations/restoreRevision.ts new file mode 100644 index 0000000000..51c5e51aad --- /dev/null +++ b/src/globals/operations/restoreRevision.ts @@ -0,0 +1,162 @@ +import { Where } from '../../types'; +import { PayloadRequest } from '../../express/types'; +import executeAccess from '../../auth/executeAccess'; +import sanitizeInternalFields from '../../utilities/sanitizeInternalFields'; +import { PaginatedDocs } from '../../mongoose/types'; +import { hasWhereAccessResult } from '../../auth/types'; +import flattenWhereConstraints from '../../utilities/flattenWhereConstraints'; +import { buildSortParam } from '../../mongoose/buildSortParam'; +import { TypeWithRevision } from '../../revisions/types'; +import { SanitizedGlobalConfig } from '../config/types'; + +export type Arguments = { + globalConfig: SanitizedGlobalConfig + where?: Where + page?: number + limit?: number + sort?: string + depth?: number + req?: PayloadRequest + overrideAccess?: boolean + showHiddenFields?: boolean +} + +// TODO: finish + +async function restoreRevision = any>(args: Arguments): Promise> { + const { + where, + page, + limit, + depth, + globalConfig, + req, + req: { + locale, + }, + overrideAccess, + showHiddenFields, + } = args; + + const RevisionsModel = this.revisions[globalConfig.slug]; + + // ///////////////////////////////////// + // Access + // ///////////////////////////////////// + + const queryToBuild: { where?: Where} = {}; + let useEstimatedCount = false; + + if (where) { + let and = []; + + if (Array.isArray(where.and)) and = where.and; + if (Array.isArray(where.AND)) and = where.AND; + + queryToBuild.where = { + ...where, + and: [ + ...and, + ], + }; + + const constraints = flattenWhereConstraints(queryToBuild); + + useEstimatedCount = constraints.some((prop) => Object.keys(prop).some((key) => key === 'near')); + } + + if (!overrideAccess) { + const accessResults = await executeAccess({ req }, globalConfig.access.readRevisions); + + if (hasWhereAccessResult(accessResults)) { + if (!where) { + queryToBuild.where = { + and: [ + accessResults, + ], + }; + } else { + (queryToBuild.where.and as Where[]).push(accessResults); + } + } + } + + const query = await RevisionsModel.buildQuery(queryToBuild, locale); + + // ///////////////////////////////////// + // Find + // ///////////////////////////////////// + + const [sortProperty, sortOrder] = buildSortParam(args.sort, true); + + const optionsToExecute = { + page: page || 1, + limit: limit || 10, + sort: { + [sortProperty]: sortOrder, + }, + lean: true, + leanWithId: true, + useEstimatedCount, + }; + + const paginatedDocs = await RevisionsModel.paginate(query, optionsToExecute); + + // ///////////////////////////////////// + // afterRead - Fields + // ///////////////////////////////////// + + let result = { + ...paginatedDocs, + docs: await Promise.all(paginatedDocs.docs.map(async (data) => ({ + ...data, + revision: await this.performFieldOperations( + globalConfig, + { + depth, + data: data.revision, + req, + id: data.revision.id, + hook: 'afterRead', + operation: 'read', + overrideAccess, + flattenLocales: true, + showHiddenFields, + isRevision: true, + }, + ), + }))), + }; + + // ///////////////////////////////////// + // afterRead - Collection + // ///////////////////////////////////// + + result = { + ...result, + docs: await Promise.all(result.docs.map(async (doc) => { + const docRef = doc; + + await globalConfig.hooks.afterRead.reduce(async (priorHook, hook) => { + await priorHook; + + docRef.revision = await hook({ req, query, doc: doc.revision }) || doc.revision; + }, Promise.resolve()); + + return docRef; + })), + }; + + // ///////////////////////////////////// + // Return results + // ///////////////////////////////////// + + result = { + ...result, + docs: result.docs.map((doc) => sanitizeInternalFields(doc)), + }; + + return result; +} + +export default restoreRevision; diff --git a/src/globals/requestHandlers/restoreRevision.ts b/src/globals/requestHandlers/restoreRevision.ts new file mode 100644 index 0000000000..996549a231 --- /dev/null +++ b/src/globals/requestHandlers/restoreRevision.ts @@ -0,0 +1,25 @@ +import { Response, NextFunction } from 'express'; +import { PayloadRequest } from '../../express/types'; +import { Document } from '../../types'; +import { SanitizedGlobalConfig } from '../config/types'; + +export default function (globalConfig: SanitizedGlobalConfig) { + async function handler(req: PayloadRequest, res: Response, next: NextFunction): Promise | void> { + const options = { + req, + globalConfig, + id: req.params.id, + depth: req.query.depth, + }; + + try { + const doc = await this.operations.globals.restoreRevision(options); + return res.json(doc); + } catch (error) { + return next(error); + } + } + + const restoreRevisionHandler = handler.bind(this); + return restoreRevisionHandler; +} diff --git a/src/index.ts b/src/index.ts index 5e3f834d7d..e7a4d42708 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,10 +9,11 @@ import { EmailOptions, InitOptions, } from './config/types'; +import { TypeWithRevision } from './revisions/types'; import { PaginatedDocs } from './mongoose/types'; import Logger from './utilities/logger'; -import bindOperations from './init/bindOperations'; +import bindOperations, { Operations } from './init/bindOperations'; import bindRequestHandlers, { RequestHandlers } from './init/bindRequestHandlers'; import loadConfig from './config/load'; import authenticate, { PayloadAuthenticate } from './express/middleware/authenticate'; @@ -45,6 +46,9 @@ import { Options as FindOptions } from './collections/operations/local/find'; import { Options as FindByIDOptions } from './collections/operations/local/findByID'; import { Options as UpdateOptions } from './collections/operations/local/update'; import { Options as DeleteOptions } from './collections/operations/local/delete'; +import { Options as FindRevisionsOptions } from './collections/operations/local/findRevisions'; +import { Options as FindRevisionByIDOptions } from './collections/operations/local/findRevisionByID'; +import { Options as RestoreRevisionOptions } from './collections/operations/local/restoreRevision'; require('isomorphic-fetch'); @@ -94,7 +98,7 @@ export class Payload { decrypt = decrypt; - operations: { [key: string]: any }; + operations: Operations; errorHandler: ErrorHandler; @@ -269,6 +273,47 @@ export class Payload { return deleteOperation(options); } + /** + * @description Find revisions with criteria + * @param options + * @returns revisions satisfying query + */ + findRevisions = async = any>(options: FindRevisionsOptions): Promise> => { + let { findRevisions } = localOperations; + findRevisions = findRevisions.bind(this); + return findRevisions(options); + } + + /** + * @description Find revision by ID + * @param options + * @returns revision with specified ID + */ + findRevisionByID = async = any>(options: FindRevisionByIDOptions): Promise => { + let { findRevisionByID } = localOperations; + findRevisionByID = findRevisionByID.bind(this); + return findRevisionByID(options); + } + + /** + * @description Restore revision by ID + * @param options + * @returns revision with specified ID + */ + restoreRevision = async = any>(options: RestoreRevisionOptions): Promise => { + let { restoreRevision } = localOperations; + restoreRevision = restoreRevision.bind(this); + return restoreRevision(options); + } + + // TODO: globals + // findRevisionGlobal + // findRevisionByIDGlobal + // restoreRevisionGlobal + // TODO: + // graphql operations & request handlers, where + // tests + login = async (options): Promise => { let { login } = localOperations.auth; login = login.bind(this); diff --git a/src/init/bindOperations.ts b/src/init/bindOperations.ts index 1a7fbab2a4..bb93824135 100644 --- a/src/init/bindOperations.ts +++ b/src/init/bindOperations.ts @@ -16,18 +16,58 @@ import find from '../collections/operations/find'; import findByID from '../collections/operations/findByID'; import findRevisions from '../collections/operations/findRevisions'; import findRevisionByID from '../collections/operations/findRevisionByID'; +import restoreRevision from '../collections/operations/restoreRevision'; import update from '../collections/operations/update'; import deleteHandler from '../collections/operations/delete'; import findOne from '../globals/operations/findOne'; import findGlobalRevisions from '../globals/operations/findRevisions'; import findGlobalRevisionByID from '../globals/operations/findRevisionByID'; +import restoreGlobalRevision from '../globals/operations/restoreRevision'; import globalUpdate from '../globals/operations/update'; import preferenceUpdate from '../preferences/operations/update'; import preferenceFindOne from '../preferences/operations/findOne'; import preferenceDelete from '../preferences/operations/delete'; +export type Operations = { + collections: { + create: typeof create + find: typeof find + findByID: typeof findByID + findRevisions: typeof findRevisions + findRevisionByID: typeof findRevisionByID + restoreRevision: typeof restoreRevision + update: typeof update + delete: typeof deleteHandler + auth: { + access: typeof access + forgotPassword: typeof forgotPassword + init: typeof init + login: typeof login + logout: typeof logout + me: typeof me + refresh: typeof refresh + registerFirstUser: typeof registerFirstUser + resetPassword: typeof resetPassword + verifyEmail: typeof verifyEmail + unlock: typeof unlock + } + } + globals: { + findOne: typeof findOne + findRevisions: typeof findGlobalRevisions + findRevisionByID: typeof findGlobalRevisionByID + restoreRevision: typeof restoreGlobalRevision + update: typeof globalUpdate + } + preferences: { + update: typeof preferenceUpdate + findOne: typeof preferenceFindOne + delete: typeof preferenceDelete + } +} + function bindOperations(ctx: Payload): void { ctx.operations = { collections: { @@ -36,6 +76,7 @@ function bindOperations(ctx: Payload): void { findByID: findByID.bind(ctx), findRevisions: findRevisions.bind(ctx), findRevisionByID: findRevisionByID.bind(ctx), + restoreRevision: restoreRevision.bind(ctx), update: update.bind(ctx), delete: deleteHandler.bind(ctx), auth: { @@ -56,6 +97,7 @@ function bindOperations(ctx: Payload): void { findOne: findOne.bind(ctx), findRevisions: findGlobalRevisions.bind(ctx), findRevisionByID: findGlobalRevisionByID.bind(ctx), + restoreRevision: restoreGlobalRevision.bind(ctx), update: globalUpdate.bind(ctx), }, preferences: { diff --git a/src/init/bindRequestHandlers.ts b/src/init/bindRequestHandlers.ts index 3d29f569e8..f42312adb6 100644 --- a/src/init/bindRequestHandlers.ts +++ b/src/init/bindRequestHandlers.ts @@ -15,12 +15,14 @@ import find from '../collections/requestHandlers/find'; import findByID from '../collections/requestHandlers/findByID'; import findRevisions from '../collections/requestHandlers/findRevisions'; import findRevisionByID from '../collections/requestHandlers/findRevisionByID'; +import restoreRevision from '../collections/requestHandlers/restoreRevision'; import update from '../collections/requestHandlers/update'; import deleteHandler from '../collections/requestHandlers/delete'; import findOne from '../globals/requestHandlers/findOne'; import findGlobalRevisions from '../globals/requestHandlers/findRevisions'; import findGlobalRevisionByID from '../globals/requestHandlers/findRevisionByID'; +import restoreGlobalRevision from '../globals/requestHandlers/restoreRevision'; import globalUpdate from '../globals/requestHandlers/update'; import { Payload } from '../index'; import preferenceUpdate from '../preferences/requestHandlers/update'; @@ -34,6 +36,7 @@ export type RequestHandlers = { findByID: typeof findByID, findRevisions: typeof findRevisions, findRevisionByID: typeof findRevisionByID, + restoreRevision: typeof restoreRevision, update: typeof update, delete: typeof deleteHandler, auth: { @@ -55,6 +58,7 @@ export type RequestHandlers = { update: typeof globalUpdate, findRevisions: typeof findGlobalRevisions findRevisionByID: typeof findGlobalRevisionByID + restoreRevision: typeof restoreGlobalRevision }, preferences: { update: typeof preferenceUpdate, @@ -71,6 +75,7 @@ function bindRequestHandlers(ctx: Payload): void { findByID: findByID.bind(ctx), findRevisions: findRevisions.bind(ctx), findRevisionByID: findRevisionByID.bind(ctx), + restoreRevision: restoreRevision.bind(ctx), update: update.bind(ctx), delete: deleteHandler.bind(ctx), auth: { @@ -92,6 +97,7 @@ function bindRequestHandlers(ctx: Payload): void { update: globalUpdate.bind(ctx), findRevisions: findGlobalRevisions.bind(ctx), findRevisionByID: findGlobalRevisionByID.bind(ctx), + restoreRevision: restoreGlobalRevision.bind(ctx), }, preferences: { update: preferenceUpdate.bind(ctx), diff --git a/src/revisions/tests/rest.spec.ts b/src/revisions/tests/rest.spec.ts index 97c277c6d6..f015f1a0b0 100644 --- a/src/revisions/tests/rest.spec.ts +++ b/src/revisions/tests/rest.spec.ts @@ -116,4 +116,40 @@ describe('Revisions - REST', () => { expect(revisions.docs[0].revision.title.es).toStrictEqual(spanishTitle); }); }); + + describe('Restore', () => { + it('should allow a revision to be restored', async () => { + const title2 = 'Here is an updated post title in EN'; + + const updatedPost = await fetch(`${url}/api/localized-posts/${postID}`, { + body: JSON.stringify({ + title: title2, + }), + headers, + method: 'put', + }).then((res) => res.json()); + + expect(updatedPost.doc.title).toBe(title2); + + const revisions = await fetch(`${url}/api/localized-posts/revisions`, { + headers, + }).then((res) => res.json()); + + revisionID = revisions.docs[0].id; + + const restore = await fetch(`${url}/api/localized-posts/revisions/${revisionID}`, { + headers, + method: 'post', + }).then((res) => res.json()); + + expect(restore.message).toBeDefined(); + expect(restore.doc.title).toBeDefined(); + + const restoredPost = await fetch(`${url}/api/localized-posts/${postID}`, { + headers, + }).then((res) => res.json()); + + expect(restoredPost.title).toBe(restore.doc.title); + }); + }); });