feat: ensures revisions are created and deleted accordingly

This commit is contained in:
James
2021-11-26 19:54:35 -05:00
parent 8ef1cc5373
commit 8df767e9a2
14 changed files with 102 additions and 27 deletions

View File

@@ -43,6 +43,10 @@ const LocalizedPosts: CollectionConfig = {
],
enableRichTextRelationship: true,
},
revisions: {
maxPerDoc: 5,
retainDeleted: false,
},
access: {
read: () => true,
},

View File

@@ -11,10 +11,6 @@ const RichText: CollectionConfig = {
access: {
read: () => true,
},
revisions: {
max: 5,
retainDeleted: false,
},
fields: [
{
name: 'defaultRichText',

View File

@@ -79,12 +79,8 @@ const collectionSchema = joi.object().keys({
),
revisions: joi.alternatives().try(
joi.object({
max: joi.number(),
maxPerDoc: joi.number(),
retainDeleted: joi.boolean(),
access: joi.object({
read: joi.func(),
modifyStatus: joi.func(),
}),
}),
joi.boolean(),
),

View File

@@ -205,10 +205,11 @@ export type CollectionConfig = {
timestamps?: boolean
};
export interface SanitizedCollectionConfig extends Omit<DeepRequired<CollectionConfig>, 'auth' | 'upload' | 'fields'> {
export interface SanitizedCollectionConfig extends Omit<DeepRequired<CollectionConfig>, 'auth' | 'upload' | 'fields' | 'revisions'> {
auth: Auth;
upload: Upload;
fields: Field[];
revisions: IncomingRevisionsType
}
export type Collection = {

View File

@@ -1,6 +1,8 @@
import httpStatus from 'http-status';
import path from 'path';
import { UploadedFile } from 'express-fileupload';
import { enforceMaxRevisions } from '../../revisions/enforceMaxRevisions';
import { Payload } from '../..';
import { Where, Document } from '../../types';
import { Collection } from '../config/types';
@@ -30,8 +32,8 @@ export type Arguments = {
overwriteExistingFiles?: boolean
}
async function update(incomingArgs: Arguments): Promise<Document> {
const { performFieldOperations, config } = this;
async function update(this: Payload, incomingArgs: Arguments): Promise<Document> {
const { config } = this;
let args = incomingArgs;
@@ -106,7 +108,7 @@ async function update(incomingArgs: Arguments): Promise<Document> {
docWithLocales = JSON.stringify(docWithLocales);
docWithLocales = JSON.parse(docWithLocales);
const originalDoc = await performFieldOperations(collectionConfig, {
const originalDoc = await this.performFieldOperations(collectionConfig, {
id,
depth: 0,
req,
@@ -189,7 +191,7 @@ async function update(incomingArgs: Arguments): Promise<Document> {
// beforeValidate - Fields
// /////////////////////////////////////
data = await performFieldOperations(collectionConfig, {
data = await this.performFieldOperations(collectionConfig, {
data,
req,
id,
@@ -233,7 +235,7 @@ async function update(incomingArgs: Arguments): Promise<Document> {
// beforeChange - Fields
// /////////////////////////////////////
let result = await performFieldOperations(collectionConfig, {
let result = await this.performFieldOperations(collectionConfig, {
data,
req,
id,
@@ -283,11 +285,44 @@ async function update(incomingArgs: Arguments): Promise<Document> {
result = JSON.parse(result);
result = sanitizeInternalFields(result);
// /////////////////////////////////////
// Create revision from existing doc
// /////////////////////////////////////
if (collectionConfig.revisions) {
const RevisionsModel = this.revisions[collectionConfig.slug];
const newRevisionData = { ...originalDoc };
delete newRevisionData.id;
let revisionCreationPromise;
try {
revisionCreationPromise = RevisionsModel.create({
parent: originalDoc.id,
revision: originalDoc,
});
} catch (err) {
this.logger.error(`There was an error while saving a revision for the ${collectionConfig.labels.singular} with ID ${originalDoc.id}.`);
}
if (collectionConfig.revisions.maxPerDoc) {
enforceMaxRevisions({
payload: this,
Model: RevisionsModel,
label: collectionConfig.labels.plural,
entityType: 'collection',
maxPerDoc: collectionConfig.revisions.maxPerDoc,
revisionCreationPromise,
});
}
}
// /////////////////////////////////////
// afterRead - Fields
// /////////////////////////////////////
result = await performFieldOperations(collectionConfig, {
result = await this.performFieldOperations(collectionConfig, {
id,
depth,
req,
@@ -316,7 +351,7 @@ async function update(incomingArgs: Arguments): Promise<Document> {
// afterChange - Fields
// /////////////////////////////////////
result = await performFieldOperations(collectionConfig, {
result = await this.performFieldOperations(collectionConfig, {
data: result,
hook: 'afterChange',
operation: 'update',

View File

@@ -25,7 +25,7 @@ type Arguments = {
currentDepth?: number
}
export default async function performFieldOperations(this: Payload, entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig, args: Arguments): Promise<{ [key: string]: unknown }> {
export default async function performFieldOperations(this: Payload, entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig, args: Arguments): Promise<any> {
const {
data,
originalDoc: fullOriginalDoc,

View File

@@ -22,6 +22,12 @@ const globalSchema = joi.object().keys({
update: joi.func(),
}),
fields: joi.array(),
revisions: joi.alternatives().try(
joi.object({
max: joi.number(),
}),
joi.boolean(),
),
}).unknown();
export default globalSchema;

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { Model, Document } from 'mongoose';
import { DeepRequired } from 'ts-essentials';
import { IncomingRevisionsType } from '../../revisions/types';
import { PayloadRequest } from '../../express/types';
import { Access, GeneratePreviewURL } from '../../config/types';
import { Field } from '../../fields/config/types';
@@ -40,6 +41,9 @@ export type GlobalConfig = {
slug: string
label?: string
preview?: GeneratePreviewURL
revisions?: {
max?: number
} | true
hooks?: {
beforeValidate?: BeforeValidateHook[]
beforeChange?: BeforeChangeHook[]

View File

@@ -7,7 +7,7 @@ import { SanitizedConfig } from '../config/types';
import { ArrayField, Block, BlockField, CheckboxField, CodeField, DateField, EmailField, Field, fieldAffectsData, GroupField, NumberField, PointField, RadioField, RelationshipField, RichTextField, RowField, SelectField, TextareaField, TextField, UploadField, fieldIsPresentationalOnly, NonPresentationalField } from '../fields/config/types';
import sortableFieldTypes from '../fields/sortableFieldTypes';
type BuildSchemaOptions = {
export type BuildSchemaOptions = {
options?: SchemaOptions
allowIDField?: boolean
disableRequired?: boolean

View File

@@ -0,0 +1,38 @@
import { Payload } from '..';
import { CollectionModel } from '../collections/config/types';
type Args = {
payload: Payload
Model: CollectionModel
maxPerDoc: number
label: string
entityType: 'global' | 'collection'
revisionCreationPromise: Promise<any>
}
export const enforceMaxRevisions = async ({
payload,
Model,
maxPerDoc,
label,
entityType,
revisionCreationPromise,
}: Args): Promise<void> => {
try {
if (revisionCreationPromise) await revisionCreationPromise;
const oldestAllowedDoc = await Model.find().limit(1).skip(maxPerDoc).sort({ createdAt: -1 });
if (oldestAllowedDoc?.[0]?.createdAt) {
const deleteLessThan = oldestAllowedDoc[0].createdAt;
await Model.deleteMany({
createdAt: {
$lte: deleteLessThan,
},
});
}
} catch (err) {
payload.logger.error(`There was an error cleaning up old revisions for the ${entityType} ${label}`);
}
};

View File

@@ -35,14 +35,9 @@ describe('Revisions - REST', () => {
describe('Create', () => {
it('should allow a new revision to be created', async () => {
const revision = await fetch(`${url}/api/rich-text`, {
const revision = await fetch(`${url}/api/localized-posts`, {
body: JSON.stringify({
defaultRichText: [{
children: [{ text: 'Here is some default rich text content' }],
}],
customRichText: [{
children: [{ text: 'Here is some custom rich text content' }],
}],
title: 'Here is a localized post in EN',
}),
headers,
method: 'post',

View File

@@ -1,4 +1,4 @@
export type IncomingRevisionsType = {
max?: number
maxPerDoc?: number
retainDeleted?: boolean
}