From 2bb349a381dbc1cd568b2aeab085400fcc8d41b0 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 30 Sep 2020 15:37:38 +0100 Subject: [PATCH] Changing relationship system around a little, to not work with fieldNames anymore in the view and simplifying quite a few of the update systems. --- packages/server/src/api/controllers/record.js | 32 +++-- packages/server/src/api/routes/record.js | 9 +- .../src/db/linkedRecords/LinkController.js | 34 +++--- packages/server/src/db/linkedRecords/index.js | 114 ++++++++---------- 4 files changed, 95 insertions(+), 94 deletions(-) diff --git a/packages/server/src/api/controllers/record.js b/packages/server/src/api/controllers/record.js index a0711b0dca..2b8240ea9d 100644 --- a/packages/server/src/api/controllers/record.js +++ b/packages/server/src/api/controllers/record.js @@ -170,7 +170,7 @@ exports.find = async function(ctx) { ctx.throw(400, "Supplied modelId does not match the records modelId") return } - ctx.body = await linkRecords.attachLinkInfoSingleRecord(instanceId, record) + ctx.body = await linkRecords.attachLinkInfo(instanceId, record) } exports.destroy = async function(ctx) { @@ -222,11 +222,10 @@ async function validate({ instanceId, modelId, record, model }) { return { valid: Object.keys(errors).length === 0, errors } } -exports.fetchLinkedRecords = async function(ctx) { +exports.fetchEnrichedRecord = async function(ctx) { const instanceId = ctx.user.instanceId const db = new CouchDB(instanceId) const modelId = ctx.params.modelId - const fieldName = ctx.params.fieldName const recordId = ctx.params.recordId if (instanceId == null || modelId == null || recordId == null) { ctx.status = 400 @@ -237,18 +236,35 @@ exports.fetchLinkedRecords = async function(ctx) { } return } + // // need model to work out where links go in record + const modelAndRecord = await Promise.all([db.get(modelId), db.get(recordId)]) + const model = modelAndRecord[0] + const record = modelAndRecord[1] // get the link docs - const linkDocIds = await linkRecords.getLinkDocuments({ + const linkVals = await linkRecords.getLinkDocuments({ instanceId, modelId, - fieldName, recordId, }) - // now get the docs from the all docs index + // look up the actual records based on the ids const response = await db.allDocs({ include_docs: true, - keys: linkDocIds, + keys: linkVals.map(linkVal => linkVal.id), }) - ctx.body = response.rows.map(row => row.doc) + // need to include the IDs in these records for any links they may have + let linkedRecords = await linkRecords.attachLinkInfo( + instanceId, + response.rows.map(row => row.doc) + ) + // insert the link records in the correct place throughout the main record + for (let fieldName of Object.keys(model.schema)) { + let field = model.schema[fieldName] + if (field.type === "link") { + record[fieldName] = linkedRecords.filter( + linkRecord => linkRecord.modelId === field.modelId + ) + } + } + ctx.body = record ctx.status = 200 } diff --git a/packages/server/src/api/routes/record.js b/packages/server/src/api/routes/record.js index b9cee5b23b..ddc26a55af 100644 --- a/packages/server/src/api/routes/record.js +++ b/packages/server/src/api/routes/record.js @@ -7,14 +7,9 @@ const router = Router() router .get( - "/api/:modelId/:recordId/:fieldName/links", + "/api/:modelId/:recordId/enrich", authorized(READ_MODEL, ctx => ctx.params.modelId), - recordController.fetchLinkedRecords - ) - .get( - "/api/:modelId/:recordId/links", - authorized(READ_MODEL, ctx => ctx.params.modelId), - recordController.fetchLinkedRecords + recordController.fetchEnrichedRecord ) .get( "/api/:modelId/records", diff --git a/packages/server/src/db/linkedRecords/LinkController.js b/packages/server/src/db/linkedRecords/LinkController.js index ee31e61501..6c308a65d5 100644 --- a/packages/server/src/db/linkedRecords/LinkController.js +++ b/packages/server/src/db/linkedRecords/LinkController.js @@ -79,13 +79,12 @@ class LinkController { /** * Utility function for main getLinkDocuments function - refer to it for functionality. */ - getLinkDocs(includeDocs, fieldName = null, recordId = null) { + getLinkDocs(recordId = null) { return linkedRecords.getLinkDocuments({ instanceId: this._instanceId, modelId: this._modelId, - fieldName, recordId, - includeDocs, + includeDocs: false, }) } @@ -101,6 +100,8 @@ class LinkController { const model = await this.model() const record = this._record const operations = [] + // get link docs to compare against + const linkVals = await this.getLinkDocs(record._id) for (let fieldName of Object.keys(model.schema)) { // get the links this record wants to make const recordField = record[fieldName] @@ -110,8 +111,11 @@ class LinkController { recordField != null && recordField.length !== 0 ) { - // get link docs to compare against - const linkDocIds = await this.getLinkDocs(false, fieldName, record._id) + // check which links actual pertain to the update in this record + let linkDocIds = linkVals.filter( + linkVal => linkVal.fieldName === fieldName + ) + linkDocIds = linkDocIds.map(linkVal => linkVal.id) // iterate through the link IDs in the record field, see if any don't exist already for (let linkId of recordField) { if (linkId && linkId !== "" && linkDocIds.indexOf(linkId) === -1) { @@ -135,7 +139,7 @@ class LinkController { ) } // replace this field with a simple entry to denote there are links - record[fieldName] = { type: "link" } + delete record[fieldName] } } await this._db.bulkDocs(operations) @@ -151,13 +155,15 @@ class LinkController { async recordDeleted() { const record = this._record // need to get the full link docs to be be able to delete it - const linkDocs = await this.getLinkDocs(true, null, record._id) - if (linkDocs.length === 0) { + const linkDocIds = await this.getLinkDocs(record._id).map( + linkVal => linkVal.id + ) + if (linkDocIds.length === 0) { return null } - const toDelete = linkDocs.map(doc => { + const toDelete = linkDocIds.map(id => { return { - ...doc, + _id: id, _deleted: true, } }) @@ -211,14 +217,14 @@ class LinkController { } } // need to get the full link docs to delete them - const linkDocs = await this.getLinkDocs(true) - if (linkDocs.length === 0) { + const linkDocIds = await this.getLinkDocs().map(linkVal => linkVal.id) + if (linkDocIds.length === 0) { return null } // get link docs for this model and configure for deletion - const toDelete = linkDocs.map(doc => { + const toDelete = linkDocIds.map(id => { return { - ...doc, + _id: id, _deleted: true, } }) diff --git a/packages/server/src/db/linkedRecords/index.js b/packages/server/src/db/linkedRecords/index.js index 15e40f032f..7528d02f07 100644 --- a/packages/server/src/db/linkedRecords/index.js +++ b/packages/server/src/db/linkedRecords/index.js @@ -32,10 +32,14 @@ exports.createLinkView = async instanceId => { if (doc.type === "link") { let doc1 = doc.doc1 let doc2 = doc.doc2 - emit([doc1.modelId, 1, doc1.fieldName, doc1.recordId], doc2.recordId) - emit([doc2.modelId, 1, doc2.fieldName, doc2.recordId], doc1.recordId) - emit([doc1.modelId, 2, doc1.recordId], doc2.recordId) - emit([doc2.modelId, 2, doc2.recordId], doc1.recordId) + emit([doc1.modelId, doc1.recordId], { + id: doc2.recordId, + fieldName: doc1.fieldName, + }) + emit([doc2.modelId, doc2.recordId], { + id: doc1.recordId, + fieldName: doc2.fieldName, + }) } }.toString(), } @@ -92,62 +96,50 @@ exports.updateLinks = async ({ } } -/** - * Utility function to in parallel up a list of records with link info. - * @param {string} instanceId The instance in which this record has been created. - * @param {object[]} records A list records to be updated with link info. - * @returns {Promise} The updated records (this may be the same if no links were found). - */ -exports.attachLinkInfo = async (instanceId, records) => { - let recordPromises = [] - for (let record of records) { - recordPromises.push(exports.attachLinkInfoSingleRecord(instanceId, record)) - } - return await Promise.all(recordPromises) -} - /** * Update a record with information about the links that pertain to it. * @param {string} instanceId The instance in which this record has been created. - * @param {object} record The record itself which is to be updated with info (if applicable). - * @returns {Promise} The updated record (this may be the same if no links were found). + * @param {object} records The record(s) themselves which is to be updated with info (if applicable). This can be + * a single record object or an array of records - both will be handled. + * @returns {Promise} The updated record (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.attachLinkInfoSingleRecord = async (instanceId, record) => { - // first check if the record has any link fields and set counts to zero - let hasLinkedRecords = false - for (let fieldName of Object.keys(record)) { - let field = record[fieldName] - if (field != null && field.type === "link") { - hasLinkedRecords = true - field.count = 0 +exports.attachLinkInfo = async (instanceId, records) => { + // handle a single record as well as multiple + let wasArray = true + if (!(records instanceof Array)) { + records = [records] + wasArray = false + } + // start by getting all the link values for performance reasons + let responses = await Promise.all( + records.map(record => + exports.getLinkDocuments({ + instanceId, + modelId: record.modelId, + recordId: record._id, + includeDocs: false, + }) + ) + ) + // can just use an index to access responses, order maintained + let index = 0 + // now iterate through the records and all field information + for (let record of records) { + // get all links for record, ignore fieldName for now + const linkVals = responses[index++] + for (let linkVal of linkVals) { + // work out which link pertains to this record + if (!(record[linkVal.fieldName] instanceof Array)) { + record[linkVal.fieldName] = [linkVal.id] + } else { + record[linkVal.fieldName].push(linkVal.id) + } } } - // no linked records, can simply return - if (!hasLinkedRecords) { - return record - } - const recordId = record._id - const modelId = record.modelId - // get all links for record, ignore fieldName for now - const linkDocs = await exports.getLinkDocuments({ - instanceId, - modelId, - recordId, - includeDocs: true, - }) - if (linkDocs == null || linkDocs.length === 0) { - return record - } - for (let linkDoc of linkDocs) { - // work out which link pertains to this record - const doc = linkDoc.doc1.recordId === recordId ? linkDoc.doc1 : linkDoc.doc2 - if (record[doc.fieldName] == null || isNaN(record[doc.fieldName].count)) { - record[doc.fieldName] = { type: "link", count: 1 } - } else { - record[doc.fieldName].count++ - } - } - return record + // 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 wasArray ? records : records[0] } /** @@ -167,25 +159,17 @@ exports.attachLinkInfoSingleRecord = async (instanceId, record) => { exports.getLinkDocuments = async ({ instanceId, modelId, - fieldName, recordId, includeDocs, }) => { const db = new CouchDB(instanceId) let params - if (fieldName != null && recordId != null) { - params = { key: [modelId, 1, fieldName, recordId] } - } else if (fieldName != null && recordId == null) { - params = { - startKey: [modelId, 1, fieldName], - endKey: [modelId, 1, fieldName, {}], - } - } else if (fieldName == null && recordId != null) { - params = { key: [modelId, 2, recordId] } + if (recordId != null) { + params = { key: [modelId, recordId] } } // only model is known else { - params = { startKey: [modelId, 1], endKey: [modelId, 1, {}] } + params = { startKey: [modelId], endKey: [modelId, {}] } } params.include_docs = !!includeDocs try {