diff --git a/packages/auth/src/objectStore/index.js b/packages/auth/src/objectStore/index.js index 87b67d464e..b5d8475cee 100644 --- a/packages/auth/src/objectStore/index.js +++ b/packages/auth/src/objectStore/index.js @@ -206,6 +206,34 @@ exports.retrieveToTmp = async (bucketName, filepath) => { return outputPath } +/** + * Delete a single file. + */ +exports.deleteFile = async (bucketName, filepath) => { + const objectStore = exports.ObjectStore(bucketName) + await exports.makeSureBucketExists(objectStore, bucketName) + const params = { + Bucket: bucketName, + Key: filepath, + } + return objectStore.deleteObject(params) +} + +exports.deleteFiles = async (bucketName, filepaths) => { + const objectStore = exports.ObjectStore(bucketName) + await exports.makeSureBucketExists(objectStore, bucketName) + const params = { + Bucket: bucketName, + Delete: { + Objects: filepaths.map(path => ({ Key: path })), + }, + } + return objectStore.deleteObjects(params).promise() +} + +/** + * Delete a path, including everything within. + */ exports.deleteFolder = async (bucketName, folder) => { bucketName = sanitizeBucket(bucketName) folder = sanitizeKey(folder) diff --git a/packages/server/src/api/controllers/row/internal.js b/packages/server/src/api/controllers/row/internal.js index ffee1dcec3..70cacda349 100644 --- a/packages/server/src/api/controllers/row/internal.js +++ b/packages/server/src/api/controllers/row/internal.js @@ -11,6 +11,7 @@ const { inputProcessing, outputProcessing, processAutoColumn, + cleanupAttachments, } = require("../../../utilities/rowProcessor") const { FieldTypes } = require("../../../constants") const { isEqual } = require("lodash") @@ -295,6 +296,8 @@ exports.destroy = async function (ctx) { row, tableId: row.tableId, }) + // remove any attachments that were on the row from object storage + await cleanupAttachments(appId, table, { row }) let response if (ctx.params.tableId === InternalTables.USER_METADATA) { @@ -341,6 +344,8 @@ exports.bulkDestroy = async ctx => { } else { await db.bulkDocs(rows.map(row => ({ ...row, _deleted: true }))) } + // remove any attachments that were on the rows from object storage + await cleanupAttachments(appId, table, { rows }) await Promise.all(updates) return { response: { ok: true }, rows } } diff --git a/packages/server/src/utilities/fileSystem/utilities.js b/packages/server/src/utilities/fileSystem/utilities.js index c33ce083c1..4968ef6310 100644 --- a/packages/server/src/utilities/fileSystem/utilities.js +++ b/packages/server/src/utilities/fileSystem/utilities.js @@ -2,6 +2,7 @@ const { ObjectStore, makeSureBucketExists, upload, + deleteFiles, streamUpload, retrieve, retrieveToTmp, @@ -28,3 +29,4 @@ exports.retrieveToTmp = retrieveToTmp exports.deleteFolder = deleteFolder exports.uploadDirectory = uploadDirectory exports.downloadTarball = downloadTarball +exports.deleteFiles = deleteFiles diff --git a/packages/server/src/utilities/rowProcessor/index.js b/packages/server/src/utilities/rowProcessor/index.js index 860063f173..6e4e127f73 100644 --- a/packages/server/src/utilities/rowProcessor/index.js +++ b/packages/server/src/utilities/rowProcessor/index.js @@ -3,6 +3,8 @@ const { cloneDeep } = require("lodash/fp") const { FieldTypes, AutoFieldSubTypes } = require("../../constants") const { attachmentsRelativeURL } = require("../index") const { processFormulas } = require("./utils") +const { deleteFiles } = require("../../utilities/fileSystem/utilities") +const { ObjectStoreBuckets } = require("../../constants") const BASE_AUTO_ID = 1 @@ -272,3 +274,32 @@ exports.outputProcessing = async ( } return wasArray ? enriched : enriched[0] } + +/** + * Clean up any attachments that were attached to a row. + * @param {string} appId The ID of the app from which a row is being deleted. + * @param {object} table The table from which a row is being removed. + * @param {any} row optional - the row being removed. + * @param {any} rows optional - if multiple rows being deleted can do this in bulk. + * @return {Promise} When all attachments have been removed this will return. + */ +exports.cleanupAttachments = async (appId, table, {row, rows}) => { + // TODO: check app ID version + let files = [] + function addFiles(row, key) { + if (row[key]) { + files = files.concat(row[key].map(attachment => attachment.key)) + } + } + for (let [key, schema] of Object.entries(table.schema)) { + if (schema.type !== FieldTypes.ATTACHMENT) { + continue + } + if (row) { + addFiles(row, key) + } else if (rows) { + rows.forEach(row => addFiles(row, key)) + } + } + return deleteFiles(ObjectStoreBuckets.APPS, files) +}