diff --git a/packages/server/src/api/controllers/row/internal.js b/packages/server/src/api/controllers/row/internal.js index 84e06f5855..2299a20580 100644 --- a/packages/server/src/api/controllers/row/internal.js +++ b/packages/server/src/api/controllers/row/internal.js @@ -23,6 +23,19 @@ const CALCULATION_TYPES = { STATS: "stats", } +async function storeResponse(ctx, db, row, oldTable, table) { + row.type = "row" + const response = await db.put(row) + // don't worry about rev, tables handle rev/lastID updates + if (!isEqual(oldTable, table)) { + await db.put(table) + } + row._rev = response.rev + // process the row before return, to include relationships + row = await outputProcessing(ctx, table, row, { squash: false }) + return { row, table } +} + exports.patch = async ctx => { const appId = ctx.appId const db = new CouchDB(appId) @@ -77,14 +90,7 @@ exports.patch = async ctx => { return { row: ctx.body, table } } - const response = await db.put(row) - // don't worry about rev, tables handle rev/lastID updates - if (!isEqual(dbTable, table)) { - await db.put(table) - } - row._rev = response.rev - row.type = "row" - return { row, table } + return storeResponse(ctx, db, row, dbTable, table) } exports.save = async function (ctx) { @@ -118,14 +124,7 @@ exports.save = async function (ctx) { table, }) - row.type = "row" - const response = await db.put(row) - // don't worry about rev, tables handle rev/lastID updates - if (!isEqual(dbTable, table)) { - await db.put(table) - } - row._rev = response.rev - return { row, table } + return storeResponse(ctx, db, row, dbTable, table) } exports.fetchView = async ctx => { @@ -221,34 +220,47 @@ exports.destroy = async function (ctx) { const appId = ctx.appId const db = new CouchDB(appId) const { _id, _rev } = ctx.request.body - const row = await db.get(_id) + let row = await db.get(_id) if (row.tableId !== ctx.params.tableId) { throw "Supplied tableId doesn't match the row's tableId" } + const table = await db.get(row.tableId) + // update the row to include full relationships before deleting them + row = await outputProcessing(ctx, table, row, { squash: false }) + // now remove the relationships await linkRows.updateLinks({ appId, eventType: linkRows.EventType.ROW_DELETE, row, tableId: row.tableId, }) + + let response if (ctx.params.tableId === InternalTables.USER_METADATA) { ctx.params = { id: _id, } await userController.destroyMetadata(ctx) - return { response: ctx.body, row } + response = ctx.body } else { - const response = await db.remove(_id, _rev) - return { response, row } + response = await db.remove(_id, _rev) } + return { response, row } } exports.bulkDestroy = async ctx => { const appId = ctx.appId - const { rows } = ctx.request.body const db = new CouchDB(appId) + const tableId = ctx.params.tableId + const table = await db.get(tableId) + let { rows } = ctx.request.body + // before carrying out any updates, make sure the rows are ready to be returned + // they need to be the full rows (including previous relationships) for automations + rows = await outputProcessing(ctx, table, rows, { squash: false }) + + // remove the relationships first let updates = rows.map(row => linkRows.updateLinks({ appId, @@ -257,8 +269,7 @@ exports.bulkDestroy = async ctx => { tableId: row.tableId, }) ) - // TODO remove special user case in future - if (ctx.params.tableId === InternalTables.USER_METADATA) { + if (tableId === InternalTables.USER_METADATA) { updates = updates.concat( rows.map(row => { ctx.params = { diff --git a/packages/server/src/db/linkedRows/index.js b/packages/server/src/db/linkedRows/index.js index 34be44336c..67412e7e89 100644 --- a/packages/server/src/db/linkedRows/index.js +++ b/packages/server/src/db/linkedRows/index.js @@ -36,6 +36,18 @@ exports.IncludeDocs = IncludeDocs exports.getLinkDocuments = getLinkDocuments exports.createLinkView = createLinkView +function clearRelationshipFields(table, rows) { + for (let [key, field] of Object.entries(table.schema)) { + if (field.type === FieldTypes.LINK) { + rows = rows.map(row => { + delete row[key] + return row + }) + } + } + return rows +} + async function getLinksForRows(appId, rows) { const tableIds = [...new Set(rows.map(el => el.tableId))] // start by getting all the link values for performance reasons @@ -126,33 +138,6 @@ exports.updateLinks = async function (args) { } } -/** - * Update a row with information about the links that pertain to it. - * @param {string} appId The instance in which this row has been created. - * @param {object} rows The row(s) themselves which is to be updated with info (if applicable). This can be - * a single row object or an array of rows - both will be handled. - * @returns {Promise} The updated row (this may be the same if no links were found). If an array was input - * then an array will be output, object input -> object output. - */ -exports.attachLinkIDs = async (appId, rows) => { - const links = await getLinksForRows(appId, rows) - // now iterate through the rows and all field information - for (let row of rows) { - // find anything that matches the row's ID we are searching for and join it - links - .filter(el => el.thisId === row._id) - .forEach(link => { - if (row[link.fieldName] == null) { - row[link.fieldName] = [] - } - row[link.fieldName].push(link.id) - }) - } - // if it was an array when it came in then handle it as an array in response - // otherwise return the first element as there was only one input - return rows -} - /** * Given a table and a list of rows this will retrieve all of the attached docs and enrich them into the row. * This is required for formula fields, this may only be utilised internally (for now). @@ -173,6 +158,9 @@ exports.attachFullLinkedDocs = async (ctx, table, rows) => { const links = (await getLinksForRows(appId, rows)).filter(link => rows.some(row => row._id === link.thisId) ) + // clear any existing links that could be dupe'd + rows = clearRelationshipFields(table, rows) + // now get the docs and combine into the rows let linked = await getFullLinkedDocs(ctx, appId, links) const linkedTables = [] for (let row of rows) { diff --git a/packages/server/src/db/tests/linkTests.spec.js b/packages/server/src/db/tests/linkTests.spec.js index 3fed6938b7..8dad7be049 100644 --- a/packages/server/src/db/tests/linkTests.spec.js +++ b/packages/server/src/db/tests/linkTests.spec.js @@ -59,16 +59,4 @@ describe("test link functionality", () => { expect(Array.isArray(output)).toBe(true) }) }) - - describe("attachLinkIDs", () => { - it("should be able to attach linkIDs", async () => { - await config.init() - await config.createTable() - const table = await config.createLinkedTable() - const row = await config.createRow() - const linkRow = await config.createRow(basicLinkedRow(table._id, row._id)) - const attached = await links.attachLinkIDs(config.getAppId(), [linkRow]) - expect(attached[0].link[0]).toBe(row._id) - }) - }) }) \ No newline at end of file diff --git a/packages/server/src/utilities/rowProcessor/index.js b/packages/server/src/utilities/rowProcessor/index.js index 7cb3ac7e02..bb4ac98bb7 100644 --- a/packages/server/src/utilities/rowProcessor/index.js +++ b/packages/server/src/utilities/rowProcessor/index.js @@ -185,10 +185,16 @@ exports.inputProcessing = (user = {}, table, row) => { * @param {object} ctx the request which is looking for enriched rows. * @param {object} table the table from which these rows came from originally, this is used to determine * the schema of the rows and then enrich. - * @param {object[]} rows the rows which are to be enriched. - * @returns {object[]} the enriched rows will be returned. + * @param {object[]|object} rows the rows which are to be enriched. + * @param {object} opts used to set some options for the output, such as disabling relationship squashing. + * @returns {object[]|object} the enriched rows will be returned. */ -exports.outputProcessing = async (ctx, table, rows) => { +exports.outputProcessing = async ( + ctx, + table, + rows, + opts = { squash: true } +) => { const appId = ctx.appId let wasArray = true if (!(rows instanceof Array)) { @@ -214,6 +220,12 @@ exports.outputProcessing = async (ctx, table, rows) => { } } } - enriched = await linkRows.squashLinksToPrimaryDisplay(appId, table, enriched) + if (opts.squash) { + enriched = await linkRows.squashLinksToPrimaryDisplay( + appId, + table, + enriched + ) + } return wasArray ? enriched : enriched[0] }