diff --git a/src/storage/version/put.js b/src/storage/version/put.js
index d0f8bf8f..c88ab877 100644
--- a/src/storage/version/put.js
+++ b/src/storage/version/put.js
@@ -14,6 +14,7 @@ import {
PutObjectCommand,
} from '@aws-sdk/client-s3';
+import { EMPTY_DOC_SIZE } from '../../utils/constants.js';
import getS3Config from '../utils/config.js';
import {
getUsersForMetadata, ifMatch, ifNoneMatch,
@@ -114,9 +115,19 @@ export async function putObjectWithVersion(env, daCtx, update, body, guid) {
const pps = current.metadata?.preparsingstore || '0';
// Store the body if preparsingstore is not defined, so a once-off store
- const storeBody = !body && pps === '0';
+ let storeBody = !body && pps === '0';
const Preparsingstore = storeBody ? Timestamp : pps;
- const Label = storeBody ? 'Collab Parse' : update.label;
+ let Label = storeBody ? 'Collab Parse' : update.label;
+
+ if (daCtx.method === 'PUT' && current.contentLength > EMPTY_DOC_SIZE && (!update.body || update.body.size <= EMPTY_DOC_SIZE)) {
+ // we are about to empty the document body
+ // this should never happen but in some cases it does
+ // we want then to store a version of the full document as a restore point
+ // eslint-disable-next-line no-console
+ console.warn('Empty body, creating a restore point');
+ storeBody = true;
+ Label = 'Restore Point';
+ }
const versionResp = await putVersion(config, {
Bucket: input.Bucket,
diff --git a/src/utils/constants.js b/src/utils/constants.js
index e53f9d6c..f674b13a 100644
--- a/src/utils/constants.js
+++ b/src/utils/constants.js
@@ -20,3 +20,7 @@ export const SUPPORTED_TYPES = [
'image/svg+xml',
'video/mp4',
];
+
+// this is the size of the empty document
+//
+export const EMPTY_DOC_SIZE = 83;
diff --git a/src/utils/daCtx.js b/src/utils/daCtx.js
index bab8d229..84cefa41 100644
--- a/src/utils/daCtx.js
+++ b/src/utils/daCtx.js
@@ -44,6 +44,7 @@ export default async function getDaCtx(req, env) {
users,
fullKey,
origin: new URL(req.url).origin,
+ method: req.method,
};
// Sanitize the remaining path parts
diff --git a/test/storage/version/put.test.js b/test/storage/version/put.test.js
index b5497794..74f0639f 100644
--- a/test/storage/version/put.test.js
+++ b/test/storage/version/put.test.js
@@ -570,7 +570,7 @@ describe('Version Put', () => {
},
});
- const resp = await putObjectWithVersion({}, {}, {});
+ const resp = await putObjectWithVersion({}, { method: 'HEAD' }, {});
assert.equal(1, sentToS3.length);
const input = sentToS3[0].input;
assert.equal('', input.Body, 'Empty body for HEAD');
@@ -657,6 +657,88 @@ describe('Version Put', () => {
assert(input2.Metadata.Version && input2.Metadata.Version !== 101);
});
+ it('Test putObjectWithVersion BODY - new BODY is empty creates a restore point', async () => {
+ const mockGetObject = async () => {
+ const metadata = {
+ id: 'idabc',
+ version: '101',
+ path: '/qwerty',
+ timestamp: 1234,
+ }
+ return { body: 'Somebody...', metadata, contentLength: 616 };
+ };
+
+ const sentToS3 = [];
+ const s3Client = {
+ send: async (c) => {
+ sentToS3.push(c);
+ return {
+ $metadata: {
+ httpStatusCode: 200
+ }
+ };
+ }
+ };
+ const mockS3Client = () => s3Client;
+
+ const sentToS3_2 = [];
+ const s3Client2 = {
+ send: async (c) => {
+ sentToS3_2.push(c);
+ return {
+ $metadata: {
+ httpStatusCode: 200
+ }
+ };
+ }
+ };
+ const mockS3Client2 = () => s3Client2;
+
+ const { putObjectWithVersion } = await esmock('../../../src/storage/version/put.js', {
+ '../../../src/storage/object/get.js': {
+ default: mockGetObject
+ },
+ '../../../src/storage/utils/version.js': {
+ ifNoneMatch: mockS3Client,
+ ifMatch: mockS3Client2
+ },
+ });
+
+ const update = {
+ org: 'o1',
+ body: '',
+ key: 'mypath',
+ type: 'test/plain',
+ }
+ const ctx = {
+ org: 'o1',
+ users: [{ email: 'hi@acme.com' }],
+ method: 'PUT',
+ }
+
+ const resp = await putObjectWithVersion({}, ctx, update, true);
+
+ assert.equal(1, sentToS3.length);
+ const input = sentToS3[0].input;
+
+ assert.equal('Somebody...', input.Body);
+ assert.equal(616, input.ContentLength);
+ assert.equal('/qwerty', input.Metadata.Path);
+ assert.equal(1234, input.Metadata.Timestamp);
+ assert.equal('[{"email":"anonymous"}]', input.Metadata.Users);
+ assert.equal('Restore Point', input.Metadata.Label);
+
+ assert.equal(1, sentToS3_2.length);
+ const input2 = sentToS3_2[0].input;
+ assert.equal('', input2.Body);
+ assert.equal(0, input2.ContentLength);
+ assert.equal('test/plain', input2.ContentType);
+ assert.equal('o1/mypath', input2.Key);
+ assert.equal('mypath', input2.Metadata.Path);
+ assert.equal('[{"email":"hi@acme.com"}]', input2.Metadata.Users);
+ assert(input2.Metadata.Version && input2.Metadata.Version !== 101);
+ });
+
it('exception without metadata', async () => {
const s3client1 = {
send: async (c) => {