feat: ensures revisions are created and deleted accordingly
This commit is contained in:
@@ -43,6 +43,10 @@ const LocalizedPosts: CollectionConfig = {
|
||||
],
|
||||
enableRichTextRelationship: true,
|
||||
},
|
||||
revisions: {
|
||||
maxPerDoc: 5,
|
||||
retainDeleted: false,
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
|
||||
@@ -11,10 +11,6 @@ const RichText: CollectionConfig = {
|
||||
access: {
|
||||
read: () => true,
|
||||
},
|
||||
revisions: {
|
||||
max: 5,
|
||||
retainDeleted: false,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'defaultRichText',
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
0
src/collections/operations/findRevisionByID.ts
Normal file
0
src/collections/operations/findRevisionByID.ts
Normal file
0
src/collections/operations/findRevisions.ts
Normal file
0
src/collections/operations/findRevisions.ts
Normal 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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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
|
||||
|
||||
38
src/revisions/enforceMaxRevisions.ts
Normal file
38
src/revisions/enforceMaxRevisions.ts
Normal 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}`);
|
||||
}
|
||||
};
|
||||
@@ -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',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type IncomingRevisionsType = {
|
||||
max?: number
|
||||
maxPerDoc?: number
|
||||
retainDeleted?: boolean
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user