diff --git a/app/src/services/object.js b/app/src/services/object.js index 3b9c1735..f6a53ef0 100644 --- a/app/src/services/object.js +++ b/app/src/services/object.js @@ -210,6 +210,30 @@ const service = { } }, + /** + * @function exists + * Checks if an object db record exists + * @param {string} objId The object uuid to read + * @param {object} [etrx=undefined] An optional Objection Transaction object + * @returns {Promise} true if object exists in db, false otherwise + * @throws The error encountered upon db transaction failure + */ + exists: async (objId, etrx = undefined) => { + let trx; + try { + trx = etrx ? etrx : await ObjectModel.startTransaction(); + + const response = await ObjectModel.query(trx).findById(objId); + + if (!etrx) await trx.commit(); + + return response ? true : false; + } catch (err) { + if (!etrx && trx) await trx.rollback(); + throw err; + } + }, + /** * @function update * Update an object DB record diff --git a/app/src/services/sync.js b/app/src/services/sync.js index 1477e3d2..6b524186 100644 --- a/app/src/services/sync.js +++ b/app/src/services/sync.js @@ -21,10 +21,12 @@ const service = { * @function _deriveObjectId * Checks an S3 Object for any previous `coms-id` tag traces and returns it if found. * Otherwise it writes a new `coms-id` to the S3 Object if none were previously found. + * If the `coms-id` conflicts with an existing COMS object, it is replaced with a new one. * @param {object | boolean} s3Object The result of an S3 HeadObject operation * @param {string} path String representing the canonical path for the specified object * @param {string | null} bucketId uuid of bucket or `null` if syncing object in default bucket - * @returns {Promise} Resolves to an existing objectId or creates a new one + * @returns {Promise} Resolves to an existing objectId (if not already in COMS) + * or creates a new one */ _deriveObjectId: async (s3Object, path, bucketId) => { let objId = uuidv4(); @@ -32,12 +34,21 @@ const service = { if (typeof s3Object === 'object') { // If regular S3 Object const TagSet = await storageService.getObjectTagging({ filePath: path, bucketId: bucketId }) .then(result => result.TagSet ?? []); + const s3ObjectComsId = TagSet.find(obj => (obj.Key === 'coms-id'))?.Value; - if (s3ObjectComsId && uuidValidate(s3ObjectComsId)) { + if (s3ObjectComsId + && uuidValidate(s3ObjectComsId) + && !(await objectService.exists(s3ObjectComsId))) { + // re-use existing coms-id (if no conflict) objId = s3ObjectComsId; - } else { // Update S3 Object if there is still remaining space in the TagSet - if (TagSet.length < 10) { // putObjectTagging replaces S3 tags so new TagSet must contain existing values + } else { + // remove `coms-id` tag since it conflicts with an existing COMS object + TagSet.filter(x => x.Key != 'coms-id'); + + // Update S3 Object if there is still remaining space in the TagSet + if (TagSet.length < 10) { + // putObjectTagging replaces S3 tags so new TagSet must contain existing values await storageService.putObjectTagging({ filePath: path, bucketId: bucketId, diff --git a/app/tests/unit/services/object.spec.js b/app/tests/unit/services/object.spec.js index f13fcdef..60f4f722 100644 --- a/app/tests/unit/services/object.spec.js +++ b/app/tests/unit/services/object.spec.js @@ -226,6 +226,36 @@ describe('read', () => { }); }); +describe('exists', () => { + it('returns true if coms-id already exists in db ', async () => { + ObjectModel.findById.mockResolvedValueOnce(true); + + const result = await service.exists(data.id); + + expect(result).toBeTruthy(); + expect(ObjectModel.startTransaction).toHaveBeenCalledTimes(1); + expect(ObjectModel.query).toHaveBeenCalledTimes(1); + expect(ObjectModel.query).toHaveBeenCalledWith(expect.anything()); + expect(ObjectModel.findById).toHaveBeenCalledTimes(1); + expect(ObjectModel.findById).toBeCalledWith(OBJECT_ID); + expect(objectModelTrx.commit).toHaveBeenCalledTimes(1); + }); + + it('returns false if coms-id does not exist in db', async () => { + ObjectModel.findById.mockResolvedValueOnce(false); + + const result = await service.exists(data.id); + + expect(result).toBeFalsy(); + expect(ObjectModel.startTransaction).toHaveBeenCalledTimes(1); + expect(ObjectModel.query).toHaveBeenCalledTimes(1); + expect(ObjectModel.query).toHaveBeenCalledWith(expect.anything()); + expect(ObjectModel.findById).toHaveBeenCalledTimes(1); + expect(ObjectModel.findById).toBeCalledWith(OBJECT_ID); + expect(objectModelTrx.commit).toHaveBeenCalledTimes(1); + }); +}); + describe('update', () => { it('Update an object DB record', async () => {