diff --git a/lib/model/frames.js b/lib/model/frames.js index cc3b05779..45fd4fc46 100644 --- a/lib/model/frames.js +++ b/lib/model/frames.js @@ -116,7 +116,7 @@ Form.Attachment = class extends Frame.define( const data = { name: this.name, type: this.type, - md5: this.md5, + hash: this.blobHash, exists: (this.blobId != null || this.datasetId != null), blobExists: this.blobId != null, datasetExists: this.datasetId != null diff --git a/lib/model/query/form-attachments.js b/lib/model/query/form-attachments.js index ccb0bf03d..4e4ec82e9 100644 --- a/lib/model/query/form-attachments.js +++ b/lib/model/query/form-attachments.js @@ -133,12 +133,12 @@ update.audit = (form, fa, blobId, datasetId = null) => (log) => log('form.attach // This unjoiner pulls md5 from blob table (if it exists) and adds it to attachment frame const _unjoinMd5 = unjoiner(Form.Attachment, Frame.define(into('openRosa'), 'md5')); -// This function returns the blob md5 if it exists, otherwise it returns null. -// This is used for listing the form attachments within Central and unlike `_chooseHash` -// below, doesn't need to look at the dataset timestamp to compute a synthetic hash. -const _chooseSimpleHash = (attachment) => { - if (attachment.blobId) return attachment.with({ md5: attachment.aux.openRosa.md5 }); - return attachment.with({ md5: null }); +// This function returns the blob's md5 hash if it exists, otherwise it returns null. +// This is used for listing the form attachments within Central and unlike `_chooseDynamicHash` +// below, doesn't need to look at the dataset timestamp to compute a dynamic hash. +const _chooseBlobHash = (attachment) => { + if (attachment.blobId) return attachment.with({ blobHash: attachment.aux.openRosa.md5 }); + return attachment.with({ blobHash: null }); }; const getAllByFormDefId = (formDefId) => ({ all }) => @@ -146,20 +146,21 @@ const getAllByFormDefId = (formDefId) => ({ all }) => left outer join (select id, md5 from blobs) as blobs on form_attachments."blobId"=blobs.id where "formDefId"=${formDefId} order by name asc`) .then(map(_unjoinMd5)) - .then(map(_chooseSimpleHash)); + .then(map(_chooseBlobHash)); const getByFormDefIdAndName = (formDefId, name) => ({ maybeOne }) => maybeOne(sql` select ${_unjoinMd5.fields} from form_attachments left outer join (select id, md5 from blobs) as blobs on form_attachments."blobId"=blobs.id where "formDefId"=${formDefId} and "name"=${name}`) .then(map(_unjoinMd5)) - .then(map(_chooseSimpleHash)); + .then(map(_chooseBlobHash)); -// This function decides on the openrosa hash (functionally equivalent to an http Etag) +// This function decides on the OpenRosa hash (functionally equivalent to an http Etag) // It uses the blob md5 directly if it exists. // If the attachment is actually an entity list, it looks up the last modified time // in the database, which is computed from the latest dataset/entity audit timestamp. -const _chooseHash = (attachment) => async ({ Datasets }) => { +// It is dynamic because it can change when a dataset's data is updated. +const _chooseDynamicHash = (attachment) => async ({ Datasets }) => { const { md5 } = attachment.aux.openRosa; if (attachment.blobId) return attachment.with({ openRosaHash: md5, md5 }); @@ -176,7 +177,7 @@ select ${_unjoinMd5.fields} from form_attachments left outer join (select id, md5 from blobs) as blobs on form_attachments."blobId"=blobs.id where "formDefId"=${formDefId}`) .then(map(_unjoinMd5)) - .then((attachments) => Promise.all(attachments.map(FormAttachments._chooseHash))); + .then((attachments) => Promise.all(attachments.map(FormAttachments._chooseDynamicHash))); module.exports = { @@ -184,6 +185,6 @@ module.exports = { update, getAllByFormDefId, getByFormDefIdAndName, getAllByFormDefIdForOpenRosa, - _chooseHash + _chooseDynamicHash }; diff --git a/test/integration/api/forms/draft.js b/test/integration/api/forms/draft.js index e718a8690..48f6bc726 100644 --- a/test/integration/api/forms/draft.js +++ b/test/integration/api/forms/draft.js @@ -538,8 +538,8 @@ describe('api: /projects/:id/forms (drafts)', () => { .expect(200) .then(({ body }) => { body.should.eql([ - { name: 'goodone.csv', type: 'file', exists: false, blobExists: false, datasetExists: false, md5: null }, - { name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, md5: null } + { name: 'goodone.csv', type: 'file', exists: false, blobExists: false, datasetExists: false, hash: null }, + { name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, hash: null } ]); }))))); @@ -572,8 +572,8 @@ describe('api: /projects/:id/forms (drafts)', () => { // eslint-disable-next-line no-param-reassign delete body[0].updatedAt; body.should.eql([ - { name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false, md5: '2af2751b79eccfaa8f452331e76e679e' }, - { name: 'greattwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, md5: null } + { name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false, hash: '2af2751b79eccfaa8f452331e76e679e' }, + { name: 'greattwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, hash: null } ]); }))))); @@ -604,8 +604,8 @@ describe('api: /projects/:id/forms (drafts)', () => { // eslint-disable-next-line no-param-reassign delete body[0].updatedAt; body.should.eql([ - { name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false, md5: '2af2751b79eccfaa8f452331e76e679e' }, - { name: 'greattwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, md5: null } + { name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false, hash: '2af2751b79eccfaa8f452331e76e679e' }, + { name: 'greattwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, hash: null } ]); }))))); @@ -1139,8 +1139,8 @@ describe('api: /projects/:id/forms (drafts)', () => { // eslint-disable-next-line no-param-reassign delete body[0].updatedAt; body.should.eql([ - { name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false, md5: '2af2751b79eccfaa8f452331e76e679e' }, - { name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, md5: null } + { name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false, hash: '2af2751b79eccfaa8f452331e76e679e' }, + { name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, hash: null } ]); }))))); diff --git a/test/integration/api/forms/forms.js b/test/integration/api/forms/forms.js index 7bac45d8d..635eb3e76 100644 --- a/test/integration/api/forms/forms.js +++ b/test/integration/api/forms/forms.js @@ -1226,8 +1226,8 @@ describe('api: /projects/:id/forms (create, read, update)', () => { .expect(200) .then(({ body }) => { body.should.eql([ - { name: 'goodone.csv', type: 'file', exists: false, blobExists: false, datasetExists: false, md5: null }, - { name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, md5: null } + { name: 'goodone.csv', type: 'file', exists: false, blobExists: false, datasetExists: false, hash: null }, + { name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, hash: null } ]); }))))); @@ -1252,8 +1252,8 @@ describe('api: /projects/:id/forms (create, read, update)', () => { delete body[0].updatedAt; body.should.eql([ - { name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false, md5: '2241de57bbec8144c8ad387e69b3a3ba' }, - { name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, md5: null } + { name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false, hash: '2241de57bbec8144c8ad387e69b3a3ba' }, + { name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, hash: null } ]); }))))); diff --git a/test/integration/api/forms/versions.js b/test/integration/api/forms/versions.js index e4fc02b6b..3304638bd 100644 --- a/test/integration/api/forms/versions.js +++ b/test/integration/api/forms/versions.js @@ -354,8 +354,8 @@ describe('api: /projects/:id/forms (versions)', () => { delete body[0].updatedAt; body.should.eql([ - { name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false, md5: '2af2751b79eccfaa8f452331e76e679e' }, - { name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, md5: null } + { name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false, hash: '2af2751b79eccfaa8f452331e76e679e' }, + { name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, hash: null } ]); }))))); diff --git a/test/integration/other/form-entities-version.js b/test/integration/other/form-entities-version.js index f21059794..8e07eaf98 100644 --- a/test/integration/other/form-entities-version.js +++ b/test/integration/other/form-entities-version.js @@ -284,8 +284,8 @@ describe('Update / migrate entities-version within form', () => { // eslint-disable-next-line no-param-reassign delete body[0].updatedAt; body.should.eql([ - { name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false, md5: '2241de57bbec8144c8ad387e69b3a3ba' }, - { name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, md5: null } + { name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false, hash: '2241de57bbec8144c8ad387e69b3a3ba' }, + { name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, hash: null } ]); }); @@ -318,8 +318,8 @@ describe('Update / migrate entities-version within form', () => { // eslint-disable-next-line no-param-reassign delete body[0].updatedAt; body.should.eql([ - { name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false, md5: '2241de57bbec8144c8ad387e69b3a3ba' }, - { name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, md5: null } + { name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false, hash: '2241de57bbec8144c8ad387e69b3a3ba' }, + { name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, hash: null } ]); }); @@ -329,8 +329,8 @@ describe('Update / migrate entities-version within form', () => { // eslint-disable-next-line no-param-reassign delete body[0].updatedAt; body.should.eql([ - { name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false, md5: '2241de57bbec8144c8ad387e69b3a3ba' }, - { name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, md5: null } + { name: 'goodone.csv', type: 'file', exists: true, blobExists: true, datasetExists: false, hash: '2241de57bbec8144c8ad387e69b3a3ba' }, + { name: 'goodtwo.mp3', type: 'audio', exists: false, blobExists: false, datasetExists: false, hash: null } ]); }); }));