Make sure attachments are deleted when table is deleted, or column is removed.

This commit is contained in:
mike12345567 2022-01-24 16:32:41 +00:00
parent 3bc51864b4
commit da26761773
3 changed files with 50 additions and 13 deletions

View File

@ -13,6 +13,7 @@ const usageQuota = require("../../../utilities/usageQuota")
const { doesContainString } = require("@budibase/string-templates") const { doesContainString } = require("@budibase/string-templates")
const { cloneDeep } = require("lodash/fp") const { cloneDeep } = require("lodash/fp")
const { isEqual } = require("lodash") const { isEqual } = require("lodash")
const { cleanupAttachments } = require("../../../utilities/rowProcessor")
/** /**
* This function adds a note to related tables that they are * This function adds a note to related tables that they are
@ -220,6 +221,7 @@ exports.destroy = async function (ctx) {
// has to run after, make sure it has _id // has to run after, make sure it has _id
await updateRelatedTablesForFormula(db, tableToDelete, { deletion: true }) await updateRelatedTablesForFormula(db, tableToDelete, { deletion: true })
await cleanupAttachments(appId, tableToDelete, { rows })
return tableToDelete return tableToDelete
} }

View File

@ -7,9 +7,12 @@ const {
getTableParams, getTableParams,
BudibaseInternalDB, BudibaseInternalDB,
} = require("../../../db/utils") } = require("../../../db/utils")
const { isEqual } = require("lodash/fp") const { isEqual } = require("lodash")
const { AutoFieldSubTypes, FieldTypes } = require("../../../constants") const { AutoFieldSubTypes, FieldTypes } = require("../../../constants")
const { inputProcessing } = require("../../../utilities/rowProcessor") const {
inputProcessing,
cleanupAttachments,
} = require("../../../utilities/rowProcessor")
const { USERS_TABLE_SCHEMA, SwitchableTypes } = require("../../../constants") const { USERS_TABLE_SCHEMA, SwitchableTypes } = require("../../../constants")
const { const {
isExternalTable, isExternalTable,
@ -19,8 +22,25 @@ const {
const { getViews, saveView } = require("../view/utils") const { getViews, saveView } = require("../view/utils")
const viewTemplate = require("../view/viewBuilder") const viewTemplate = require("../view/viewBuilder")
const usageQuota = require("../../../utilities/usageQuota") const usageQuota = require("../../../utilities/usageQuota")
const { cloneDeep } = require("lodash/fp")
exports.checkForColumnUpdates = async (db, oldTable, updatedTable) => { exports.deleteColumn = async (db, table, columns) => {
columns.forEach(colName => delete table.schema[colName])
const rows = await db.allDocs(
getRowParams(table._id, null, {
include_docs: true,
})
)
await db.put(table)
return db.bulkDocs(
rows.rows.map(({ doc }) => {
columns.forEach(colName => delete doc[colName])
return doc
})
)
}
exports.checkForColumnUpdates = async (appId, db, oldTable, updatedTable) => {
let updatedRows = [] let updatedRows = []
const rename = updatedTable._rename const rename = updatedTable._rename
let deletedColumns = [] let deletedColumns = []
@ -29,7 +49,7 @@ exports.checkForColumnUpdates = async (db, oldTable, updatedTable) => {
colName => updatedTable.schema[colName] == null colName => updatedTable.schema[colName] == null
) )
} }
// check for renaming of columns, deleted columns or static formula update // check for renaming of columns or deleted columns
if (rename || deletedColumns.length !== 0) { if (rename || deletedColumns.length !== 0) {
// Update all rows // Update all rows
const rows = await db.allDocs( const rows = await db.allDocs(
@ -37,16 +57,20 @@ exports.checkForColumnUpdates = async (db, oldTable, updatedTable) => {
include_docs: true, include_docs: true,
}) })
) )
updatedRows = rows.rows.map(({ doc }) => { const rawRows = rows.rows.map(({ doc }) => doc)
updatedRows = rawRows.map(row => {
row = cloneDeep(row)
if (rename) { if (rename) {
doc[rename.updated] = doc[rename.old] row[rename.updated] = row[rename.old]
delete doc[rename.old] delete row[rename.old]
} else if (deletedColumns.length !== 0) { } else if (deletedColumns.length !== 0) {
deletedColumns.forEach(colName => delete doc[colName]) deletedColumns.forEach(colName => delete row[colName])
} }
return doc return row
}) })
// cleanup any attachments from object storage for deleted attachment columns
await cleanupAttachments(appId, updatedTable, { oldTable, rows: rawRows })
// Update views // Update views
await exports.checkForViewUpdates(db, updatedTable, rename, deletedColumns) await exports.checkForViewUpdates(db, updatedTable, rename, deletedColumns)
delete updatedTable._rename delete updatedTable._rename
@ -207,6 +231,7 @@ class TableSaveFunctions {
// when confirmed valid // when confirmed valid
async mid(table) { async mid(table) {
let response = await exports.checkForColumnUpdates( let response = await exports.checkForColumnUpdates(
this.appId,
this.db, this.db,
this.oldTable, this.oldTable,
table table

View File

@ -307,9 +307,15 @@ exports.outputProcessing = async (
* @param {any} row optional - the row being removed. * @param {any} row optional - the row being removed.
* @param {any} rows optional - if multiple rows being deleted can do this in bulk. * @param {any} rows optional - if multiple rows being deleted can do this in bulk.
* @param {any} oldRow optional - if updating a row this will determine the difference. * @param {any} oldRow optional - if updating a row this will determine the difference.
* @param {any} oldTable optional - if updating a table, can supply the old table to look for
* deleted attachment columns.
* @return {Promise<void>} When all attachments have been removed this will return. * @return {Promise<void>} When all attachments have been removed this will return.
*/ */
exports.cleanupAttachments = async (appId, table, { row, rows, oldRow }) => { exports.cleanupAttachments = async (
appId,
table,
{ row, rows, oldRow, oldTable }
) => {
if (!isProdAppID(appId)) { if (!isProdAppID(appId)) {
const prodAppId = getDeployedAppID(appId) const prodAppId = getDeployedAppID(appId)
// if prod exists, then don't allow deleting // if prod exists, then don't allow deleting
@ -324,12 +330,16 @@ exports.cleanupAttachments = async (appId, table, { row, rows, oldRow }) => {
files = files.concat(row[key].map(attachment => attachment.key)) files = files.concat(row[key].map(attachment => attachment.key))
} }
} }
for (let [key, schema] of Object.entries(table.schema)) { const schemaToUse = oldTable ? oldTable.schema : table.schema
for (let [key, schema] of Object.entries(schemaToUse)) {
if (schema.type !== FieldTypes.ATTACHMENT) { if (schema.type !== FieldTypes.ATTACHMENT) {
continue continue
} }
// if updating, need to manage the differences // old table had this column, new table doesn't - delete it
if (oldRow && row) { if (oldTable && !table.schema[key]) {
rows.forEach(row => addFiles(row, key))
} else if (oldRow && row) {
// if updating, need to manage the differences
files = files.concat(getRemovedAttachmentKeys(oldRow, row, key)) files = files.concat(getRemovedAttachmentKeys(oldRow, row, key))
} else if (row) { } else if (row) {
addFiles(row, key) addFiles(row, key)