From 630106d9a2dd04a1383ce97397286f69e0b819f2 Mon Sep 17 00:00:00 2001 From: kptdobe Date: Tue, 16 Sep 2025 10:33:32 +0200 Subject: [PATCH 1/2] feat: add restore point if body is empty --- src/storage/version/put.js | 14 +++++- src/utils/daCtx.js | 1 + test/storage/version/put.test.js | 84 +++++++++++++++++++++++++++++++- 3 files changed, 96 insertions(+), 3 deletions(-) diff --git a/src/storage/version/put.js b/src/storage/version/put.js index d0f8bf8f..4b0b450c 100644 --- a/src/storage/version/put.js +++ b/src/storage/version/put.js @@ -114,9 +114,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 > 83 && (!update.body || update.body.size <= 83)) { + // 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/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) => { From 16c3c3d7ce3d52c6ec1b1d4e27b01e3efcf8f1ff Mon Sep 17 00:00:00 2001 From: kptdobe Date: Tue, 16 Sep 2025 14:05:03 +0200 Subject: [PATCH 2/2] chore: use a constant --- src/storage/version/put.js | 3 ++- src/utils/constants.js | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/storage/version/put.js b/src/storage/version/put.js index 4b0b450c..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, @@ -118,7 +119,7 @@ export async function putObjectWithVersion(env, daCtx, update, body, guid) { const Preparsingstore = storeBody ? Timestamp : pps; let Label = storeBody ? 'Collab Parse' : update.label; - if (daCtx.method === 'PUT' && current.contentLength > 83 && (!update.body || update.body.size <= 83)) { + 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 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;