Skip to content

Commit

Permalink
Version history behaviours on copy and move (#91)
Browse files Browse the repository at this point in the history
Fixes: #88
  • Loading branch information
bosschaert authored Dec 4, 2024
1 parent 6d96a9c commit 2811b46
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 50 deletions.
9 changes: 1 addition & 8 deletions src/routes/source.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,11 @@
import getObject from '../storage/object/get.js';
import putObject from '../storage/object/put.js';
import deleteObjects from '../storage/object/delete.js';
import { invalidateCollab } from '../storage/utils/object.js';

import putHelper from '../helpers/source.js';
import deleteHelper from '../helpers/delete.js';

async function invalidateCollab(api, url, env) {
const invPath = `/api/v1/${api}?doc=${url}`;

// Use dacollab service binding, hostname is not relevant
const invURL = `https://localhost${invPath}`;
await env.dacollab.fetch(invURL);
}

export async function deleteSource({ req, env, daCtx }) {
const details = await deleteHelper(req);
return /* await */ deleteObjects(env, daCtx, details);
Expand Down
56 changes: 48 additions & 8 deletions src/storage/object/copy.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ import {
CopyObjectCommand,
} from '@aws-sdk/client-s3';

import getObject from './get.js';
import getS3Config from '../utils/config.js';
import { invalidateCollab } from '../utils/object.js';
import { postObjectVersionWithLabel, putObjectWithVersion } from '../version/put.js';
import { listCommand } from '../utils/list.js';

const MAX_KEYS = 900;

export const copyFile = async (client, daCtx, sourceKey, details, isRename) => {
export const copyFile = async (config, env, daCtx, sourceKey, details, isRename) => {
const Key = `${sourceKey.replace(details.source, details.destination)}`;

const input = {
Expand All @@ -44,13 +47,50 @@ export const copyFile = async (client, daCtx, sourceKey, details, isRename) => {
}

try {
await client.send(new CopyObjectCommand(input));
const client = new S3Client(config);
client.middlewareStack.add(
(next) => async (args) => {
// eslint-disable-next-line no-param-reassign
args.request.headers['cf-copy-destination-if-none-match'] = '*';
return next(args);
},
{
step: 'build',
name: 'ifNoneMatchMiddleware',
tags: ['METADATA', 'IF-NONE-MATCH'],
},
);
const resp = await client.send(new CopyObjectCommand(input));
return resp;
} catch (e) {
console.log({
code: e.$metadata.httpStatusCode,
dest: Key,
src: `${daCtx.org}-content/${sourceKey}`,
});
if (e.$metadata.httpStatusCode === 412) {
// Not the happy path - something is at the destination already.
if (!isRename) {
// This is a copy so just put the source into the target to keep the history.

const original = await getObject(env, { org: daCtx.org, key: sourceKey });
return /* await */ putObjectWithVersion(env, daCtx, {
org: daCtx.org,
key: Key,
body: original.body,
contentLength: original.contentLength,
type: original.contentType,
});
}
await postObjectVersionWithLabel('Moved', env, daCtx);

const client = new S3Client(config);
// This is a move so copy to the new location
return /* await */ client.send(new CopyObjectCommand(input));
} else if (e.$metadata.httpStatusCode === 404) {
return { $metadata: e.$metadata };
}
throw e;
} finally {
if (Key.endsWith('.html')) {
// Reset the collab cached state for the copied object
await invalidateCollab('syncAdmin', `${daCtx.origin}/source/${daCtx.org}/${Key}`, env);
}
}
};

Expand Down Expand Up @@ -81,7 +121,7 @@ export default async function copyObject(env, daCtx, details, isRename) {
}
}
await Promise.all(sourceKeys.map(async (key) => {
await copyFile(client, daCtx, key, details, isRename);
await copyFile(config, env, daCtx, key, details, isRename);
}));

if (remainingKeys.length) {
Expand Down
9 changes: 1 addition & 8 deletions src/storage/object/delete.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,10 @@ import {
} from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import getS3Config from '../utils/config.js';
import { invalidateCollab } from '../utils/object.js';
// import { postObjectVersionWithLabel } from '../version/put.js';
import { listCommand } from '../utils/list.js';

async function invalidateCollab(api, url, env) {
const invPath = `/api/v1/${api}?doc=${url}`;

// Use dacollab service binding, hostname is not relevant
const invURL = `https://localhost${invPath}`;
await env.dacollab.fetch(invURL);
}

export async function deleteObject(client, daCtx, Key, env /* , isMove = false */) {
// const fname = Key.split('/').pop();

Expand Down
21 changes: 2 additions & 19 deletions src/storage/object/move.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
import {
S3Client,
ListObjectsV2Command,
CopyObjectCommand,
} from '@aws-sdk/client-s3';

import getS3Config from '../utils/config.js';
import { deleteObject } from './delete.js';
import { copyFile } from './copy.js';

function buildInput(org, key) {
return {
Expand All @@ -25,23 +25,6 @@ function buildInput(org, key) {
};
}

const copyFile = async (client, org, sourceKey, details) => {
const Key = `${sourceKey.replace(details.source, details.destination)}`;

try {
const resp = await client.send(
new CopyObjectCommand({
Bucket: `${org}-content`,
Key,
CopySource: `${org}-content/${sourceKey}`,
}),
);
return resp;
} catch (e) {
return e;
}
};

export default async function moveObject(env, daCtx, details) {
const config = getS3Config(env);
const client = new S3Client(config);
Expand All @@ -68,7 +51,7 @@ export default async function moveObject(env, daCtx, details) {

const movedLoad = sourceKeys.map(async (key) => {
const result = { key };
const copied = await copyFile(client, daCtx.org, key, details);
const copied = await copyFile(config, env, daCtx, key, details, true);
// Only delete the source if the file was successfully copied
if (copied.$metadata.httpStatusCode === 200) {
const deleted = await deleteObject(client, daCtx, key, env, true);
Expand Down
18 changes: 18 additions & 0 deletions src/storage/utils/object.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
export async function invalidateCollab(api, url, env) {
const invPath = `/api/v1/${api}?doc=${url}`;

// Use dacollab service binding, hostname is not relevant
const invURL = `https://localhost${invPath}`;
await env.dacollab.fetch(invURL);
}
Loading

0 comments on commit 2811b46

Please sign in to comment.