Skip to content

draft delete gap fix #195

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).

- MTX: Support for deleting tenant-specific objects from S3 upon tenant unsubscription in shared mode.

### Fixed

- Deleted attachments are now removed from S3 when a draft is discarded or deleted.

## Version 2.1.0

### Added
Expand Down
75 changes: 72 additions & 3 deletions lib/aws-s3.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ module.exports = class AWSAttachmentsService extends require("./basic") {
return super.init();
}
}

async createClientS3(tenantID) {
try {
if (s3ClientsCache[tenantID]) {
Expand Down Expand Up @@ -71,7 +71,7 @@ module.exports = class AWSAttachmentsService extends require("./basic") {
DEBUG?.(`Created S3 client for tenant ${tenantID}`);
} catch (error) {
// eslint-disable-next-line no-console
console.error(`Creation of S3 client for tenant ${tenantID} failed`,error);
console.error(`Creation of S3 client for tenant ${tenantID} failed`, error);
}
}

Expand Down Expand Up @@ -142,7 +142,7 @@ module.exports = class AWSAttachmentsService extends require("./basic") {
}

async attachDeletionData(req) {
const attachments = cds.model.definitions[req.query.target.name + ".attachments"];
const attachments = cds.model.definitions[req?.query?.target?.name + ".attachments"];
if (attachments) {
const diffData = await req.diff();
let deletedAttachments = [];
Expand Down Expand Up @@ -196,16 +196,85 @@ module.exports = class AWSAttachmentsService extends require("./basic") {
}
}

async getAttachmentsToDelete({ draftEntity, activeEntity, id }) {
const [draftAttachments, activeAttachments] = await Promise.all([
SELECT.from(draftEntity).columns("url").where(id),
SELECT.from(activeEntity).columns("url").where(id)
]);

const activeUrls = new Set(activeAttachments.map(a => a.url));
return draftAttachments
.filter(({ url }) => !activeUrls.has(url))
.map(({ url }) => ({ url }));
}

async attachDraftDeletionData(req) {
const draftEntity = cds.model.definitions[req?.query?.target?.name];
const name = req?.query?.target?.name;
const activeEntity = name ? cds.model.definitions?.[name.split(".").slice(0, -1).join(".")] : undefined;

if (!draftEntity || !activeEntity) return;

const diff = await req.diff();
if (diff._op !== "delete" || !diff.ID) return;

const attachmentsToDelete = await this.getAttachmentsToDelete({
draftEntity,
activeEntity,
id: { ID: diff.ID }
});

if (attachmentsToDelete.length) {
req.attachmentsToDelete = attachmentsToDelete;
}
}

async attachDraftDiscardDeletionData(req) {
const { ID } = req.data;
const parentEntity = req.target.name.split('.').slice(0, -1).join('.');
const draftEntity = cds.model.definitions[`${parentEntity}.attachments.drafts`];
const activeEntity = cds.model.definitions[`${parentEntity}.attachments`];

if (!draftEntity || !activeEntity) return;

const attachmentsToDelete = await this.getAttachmentsToDelete({
draftEntity,
activeEntity,
id: { up__ID: ID }
});

if (attachmentsToDelete.length) {
req.attachmentsToDelete = attachmentsToDelete;
}
}

registerUpdateHandlers(srv, entity, mediaElement) {
srv.before(["DELETE", "UPDATE"], entity, this.attachDeletionData.bind(this));
srv.after(["DELETE", "UPDATE"], entity, this.deleteAttachmentsWithKeys.bind(this));

// case: attachments uploaded in draft and draft is discarded
srv.before("CANCEL", entity.drafts, this.attachDraftDiscardDeletionData.bind(this));
srv.after("CANCEL", entity.drafts, this.deleteAttachmentsWithKeys.bind(this));

srv.prepend(() => {
if (mediaElement.drafts) {
srv.on(
"PUT",
mediaElement.drafts,
this.updateContentHandler.bind(this)
);

// case: attachments uploaded in draft and deleted before saving
srv.before(
"DELETE",
mediaElement.drafts,
this.attachDraftDeletionData.bind(this)
);
srv.after(
"DELETE",
mediaElement.drafts,
this.deleteAttachmentsWithKeys.bind(this)
);
}
});
}
Expand Down
Loading