Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ed57d0f
chore: remove jest from dependencies
viniciuslora Sep 30, 2025
9cfacf2
Switch tenantID from req to cds.context
eric-pSAP Oct 1, 2025
335e28b
Added race condition check
KoblerS Oct 6, 2025
27faa77
Enhance malware scanning with error handling and remove race conditio…
KoblerS Oct 6, 2025
27ecb3b
Enhance malware scanning by removing hardcoded tenant ID and adding e…
KoblerS Oct 7, 2025
d144df9
Refactor malware scan handling to improve error management and clarif…
KoblerS Oct 7, 2025
8c131a9
fix: delete attachments after deleting parente when request coming fr…
viniciuslora Oct 13, 2025
faa30cc
Merge branch 'main' into fix/delete-attachments-when-delete-parent
KoblerS Oct 14, 2025
9d2b5a9
Merge branch 'main' into fix/delete-attachments-when-delete-parent
KoblerS Oct 16, 2025
80ded3a
Proper fix for deleting attachment when deleting child entities
schiwekM Oct 16, 2025
2f80fb3
Update lib/plugin.js
schiwekM Oct 17, 2025
b1f6d04
Update lib/plugin.js
schiwekM Oct 17, 2025
e07e549
Update lib/plugin.js
schiwekM Oct 17, 2025
386ea39
Update lib/aws-s3.js
schiwekM Oct 17, 2025
a0be638
Merge branch 'main' into fix/delete-attachments-when-delete-parent
schiwekM Oct 17, 2025
f28bf63
Update lib/aws-s3.js
schiwekM Oct 17, 2025
47470f9
Update lib/aws-s3.js
KoblerS Oct 17, 2025
0328dc6
Update lib/plugin.js
KoblerS Oct 17, 2025
8313658
Update lib/plugin.js
KoblerS Oct 17, 2025
11d2a10
Update lib/plugin.js
KoblerS Oct 17, 2025
ea18bf6
Move generic handlers into basic + outbox
schiwekM Oct 17, 2025
5826462
Update basic.js
schiwekM Oct 17, 2025
0dab512
Added test case to ensure delete event is put into outbox
schiwekM Oct 19, 2025
9de2259
Merge branch 'main' into fix/delete-attachments-when-delete-parent
schiwekM Oct 20, 2025
4569bd3
Update plugin.js
schiwekM Oct 20, 2025
6de417f
Update lib/basic.js
schiwekM Oct 20, 2025
2277ae0
Update lib/basic.js
schiwekM Oct 20, 2025
9caf5cb
Update lib/basic.js
schiwekM Oct 20, 2025
ca3f99e
Removed unused parameter
schiwekM Oct 20, 2025
e3ddeb2
Merge branch 'fix/delete-attachments-when-delete-parent' of https://g…
schiwekM Oct 20, 2025
d5b8811
Update lib/basic.js
schiwekM Oct 20, 2025
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
node_modules/
gen/

package-lock.json
package-lock.json
.vscode/*
98 changes: 56 additions & 42 deletions lib/aws-s3.js
Original file line number Diff line number Diff line change
Expand Up @@ -403,19 +403,29 @@ module.exports = class AWSAttachmentsService extends require("./basic") {
* @param {import('@sap/cds').Request} req - The request object
*/
async attachDeletionData(req) {
const attachments = cds.model.definitions[req?.target?.name + ".attachments"]
if (attachments) {
const diffData = await req.diff()
let deletedAttachments = []
diffData.attachments?.filter((object) => {
return object._op === "delete"
})
.map((attachment) => {
deletedAttachments.push(attachment.ID)
const attachmentCompositions = Object.keys(req?.target?.associations)
.filter(assoc => req?.target?.associations[assoc]._target['@_is_media_data']);
if (attachmentCompositions.length) {
const diffData = await req.diff();
const queries = [];
for (const attachmentsComp of attachmentCompositions) {
let deletedAttachments = []
diffData[attachmentsComp]?.forEach(object => {
if (object._op === "delete") {
deletedAttachments.push(object.ID)
}
})

if (deletedAttachments.length > 0) {
let attachmentsToDelete = await SELECT.from(attachments).columns("url").where({ ID: { in: [...deletedAttachments] } })
if (deletedAttachments.length) {
queries.push(
SELECT.from(req.target.associations[attachmentsComp]._target).columns("url").where({ ID: { in: [...deletedAttachments] } })
)
}
}
if (queries.length > 0) {
let attachmentsToDelete = []
for (const toDeleteAttachments of await Promise.all(queries)) {
attachmentsToDelete = attachmentsToDelete.concat(toDeleteAttachments);
}
if (attachmentsToDelete.length > 0) {
req.attachmentsToDelete = attachmentsToDelete
}
Expand Down Expand Up @@ -549,51 +559,55 @@ module.exports = class AWSAttachmentsService extends require("./basic") {
/**
* @inheritdoc
*/
registerUpdateHandlers(srv, entity, mediaElement) {
registerUpdateHandlers(srv, entity, mediaElements) {
srv.before(["DELETE", "UPDATE"], entity, this.attachDeletionData.bind(this))
srv.after(["DELETE", "UPDATE"], entity, this.deleteAttachmentsWithKeys.bind(this))

srv.prepend(() => {
srv.on(
"PUT",
mediaElement,
this.updateContentHandler.bind(this)
)
})
for (const mediaElement of mediaElements) {
srv.prepend(() => {
srv.on(
"PUT",
mediaElement,
this.updateContentHandler.bind(this)
)
})
}
}

/**
* @inheritdoc
*/
registerDraftUpdateHandlers(srv, entity, mediaElement) {
registerDraftUpdateHandlers(srv, entity, mediaElements) {
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)
)
}
})
for (const mediaElement of mediaElements) {
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
17 changes: 11 additions & 6 deletions lib/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,12 @@ module.exports = class AttachmentsService extends cds.Service {
* @param {cds.Entity} entity - The entity containing attachment associations
* @param {cds.Entity} target - Attachments entity definition to register handlers for
*/
registerUpdateHandlers(srv, entity, target) {
srv.after("PUT", target, async (req) => {
await this.nonDraftHandler(req, target)
})
registerUpdateHandlers(srv, entity, targets) {
for (const target of targets) {
srv.after("PUT", target, async (req) => {
await this.nonDraftHandler(req, target)
})
}
}

/**
Expand All @@ -185,8 +187,10 @@ module.exports = class AttachmentsService extends cds.Service {
* @param {cds.Entity} entity - The entity containing attachment associations
* @param {cds.Entity} target - Attachments entity definition to register handlers for
*/
registerDraftUpdateHandlers(srv, entity, target) {
srv.after("SAVE", entity, this.draftSaveHandler(target))
registerDraftUpdateHandlers(srv, entity, targets) {
for (const target of targets) {
srv.after("SAVE", entity, this.draftSaveHandler(target))
}
return
}

Expand All @@ -202,6 +206,7 @@ module.exports = class AttachmentsService extends cds.Service {
attachmentName: Attachments.name,
attachmentKey: key
})

return await UPDATE(Attachments, key).with(data)
}

Expand Down
17 changes: 12 additions & 5 deletions lib/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,22 @@ cds.once("served", async function registerPluginHandlers() {
srv.before("READ", targets, validateAttachment)

srv.after("READ", targets, readAttachment)


const mediaDraftEntities = [];
const mediaEntities = [];
srv.before("PUT", isDraft ? target.drafts : target, (req) => validateAttachmentSize(req))
if (isDraft) {
srv.before("PUT", target.drafts, (req) => validateAttachmentSize(req))
srv.before("NEW", target.drafts, (req) => onPrepareAttachment(req))
AttachmentsSrv.registerDraftUpdateHandlers(srv, entity, target)
mediaDraftEntities.push(target);
} else {
srv.before("PUT", target, (req) => validateAttachmentSize(req))
srv.before("CREATE", target, (req) => onPrepareAttachment(req))
AttachmentsSrv.registerUpdateHandlers(srv, entity, target)
mediaEntities.push(target);
}
if (mediaDraftEntities.length) {
AttachmentsSrv.registerDraftUpdateHandlers(srv, entity, mediaDraftEntities)
}
if (mediaEntities.length) {
AttachmentsSrv.registerUpdateHandlers(srv, entity, mediaEntities)
}
}
})
Expand Down
3 changes: 3 additions & 0 deletions tests/incidents-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

# added by cds
.cdsrc-private.json
4 changes: 0 additions & 4 deletions tests/incidents-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@
}
}
}
},
"attachments": {
"kind": "db",
"scan": false
}
}
},
Expand Down