@budibase/server
Version:
Budibase Web Server
217 lines (197 loc) • 5.88 kB
text/typescript
import { context, db as dbCore, objectStore } from "@budibase/backend-core"
import {
FieldType,
RenameColumn,
Row,
RowAttachment,
Table,
} from "@budibase/types"
import { ObjectStoreBuckets } from "../../constants"
export class AttachmentCleanup {
static async coreCleanup(
fileListFn: () => string[] | Promise<string[]>,
opts: { tableId?: string; schema?: Table["schema"]; rowId?: string } = {}
): Promise<void> {
let files = await Promise.resolve(fileListFn())
if (files.length === 0) {
return
}
const appId = context.getWorkspaceId()
if (!dbCore.isProdWorkspaceID(appId)) {
const prodAppId = dbCore.getProdWorkspaceID(appId!)
const exists = await dbCore.dbExists(prodAppId)
if (exists) {
if (!opts.rowId) {
return
}
files = await AttachmentCleanup.excludeFilesUsedInProd(
opts.rowId,
files,
prodAppId,
opts
)
if (files.length === 0) {
return
}
}
}
await objectStore.deleteFiles(ObjectStoreBuckets.APPS, files)
}
private static async excludeFilesUsedInProd(
rowId: string,
files: string[],
prodAppId: string,
opts: { tableId?: string; schema?: Table["schema"] }
): Promise<string[]> {
const { tableId, schema } = opts
if (!tableId || !schema) {
return files
}
const prodDb = dbCore.getDB(prodAppId)
const response = await prodDb.tryGet(rowId)
const usedKeys = new Set<string>()
if (!response) {
return files
}
const row = response as Row
for (const [key, column] of Object.entries(schema)) {
const columnKeys = AttachmentCleanup.extractAttachmentKeys(
column.type,
row[key]
)
columnKeys.forEach(value => usedKeys.add(value))
}
return files.filter(file => !usedKeys.has(file))
}
private static extractAttachmentKeys(
type: FieldType,
rowData: RowAttachment[] | RowAttachment
): string[] {
if (
type !== FieldType.ATTACHMENTS &&
type !== FieldType.ATTACHMENT_SINGLE &&
type !== FieldType.SIGNATURE_SINGLE
) {
return []
}
if (!rowData) {
return []
}
if (type === FieldType.ATTACHMENTS && Array.isArray(rowData)) {
return rowData
.filter(attachment => attachment.key)
.map(attachment => attachment.key!)
} else if ("key" in rowData && rowData.key) {
return [rowData.key]
}
return []
}
private static async tableChange(
table: Table,
rows: Row[],
opts: { oldTable?: Table; rename?: RenameColumn; deleting?: boolean }
) {
const tableSchema = opts.oldTable?.schema || table.schema
return AttachmentCleanup.coreCleanup(
() =>
Object.entries(tableSchema).reduce<string[]>((files, [key, schema]) => {
if (
schema.type !== FieldType.ATTACHMENTS &&
schema.type !== FieldType.ATTACHMENT_SINGLE &&
schema.type !== FieldType.SIGNATURE_SINGLE
) {
return files
}
const columnRemoved = opts.oldTable && !table.schema[key]
const renaming = opts.rename?.old === key
if ((columnRemoved && !renaming) || opts.deleting) {
rows.forEach(row => {
const keys = AttachmentCleanup.extractAttachmentKeys(
schema.type,
row[key]
)
files.push(...keys)
})
}
return files
}, []),
{
tableId: table._id,
schema: tableSchema,
}
)
}
static async tableDelete(table: Table, rows: Row[]) {
return AttachmentCleanup.tableChange(table, rows, { deleting: true })
}
static async tableUpdate(
table: Table,
rows: Row[],
opts: { oldTable?: Table; rename?: RenameColumn }
) {
return AttachmentCleanup.tableChange(table, rows, opts)
}
static async rowDelete(table: Table, rows: Row[]) {
for (const row of rows) {
await AttachmentCleanup.coreCleanup(
() =>
Object.entries(table.schema).reduce<string[]>(
(files, [key, schema]) => {
if (
schema.type !== FieldType.ATTACHMENTS &&
schema.type !== FieldType.ATTACHMENT_SINGLE &&
schema.type !== FieldType.SIGNATURE_SINGLE
) {
return files
}
const columnFiles = AttachmentCleanup.extractAttachmentKeys(
schema.type,
row[key]
)
return files.concat(columnFiles)
},
[]
),
{
tableId: table._id,
schema: table.schema,
rowId: row._id,
}
)
}
}
static async rowUpdate(table: Table, opts: { row: Row; oldRow: Row }) {
await AttachmentCleanup.coreCleanup(
() =>
Object.entries(table.schema).reduce<string[]>(
(files, [key, schema]) => {
if (
schema.type !== FieldType.ATTACHMENTS &&
schema.type !== FieldType.ATTACHMENT_SINGLE &&
schema.type !== FieldType.SIGNATURE_SINGLE
) {
return files
}
const oldKeys = AttachmentCleanup.extractAttachmentKeys(
schema.type,
opts.oldRow[key]
)
const newKeys = AttachmentCleanup.extractAttachmentKeys(
schema.type,
opts.row[key]
)
const columnFiles = oldKeys.filter(
(key: string) => newKeys.indexOf(key) === -1
)
return files.concat(columnFiles)
},
[]
),
{
tableId: table._id,
schema: table.schema,
rowId: opts.row._id,
}
)
}
}