Handle UUID treatment ids in v1 API

This commit is contained in:
Andy Low
2026-03-08 01:01:57 +00:00
parent 505e375efc
commit 1231ec6b65
4 changed files with 186 additions and 15 deletions
+25 -3
View File
@@ -3,6 +3,7 @@
const traverse = require('traverse');
const ObjectID = require('mongodb-legacy').ObjectId;
const moment = require('moment');
const OBJECT_ID_HEX_RE = /^[0-9a-fA-F]{24}$/;
const TWO_DAYS = 172800000;
/**
@@ -90,9 +91,30 @@ function enforceDateFilter (query, opts) {
* Forces anything named `_id` to be the `ObjectID` type.
*/
function updateIdQuery (query) {
if (query._id && query._id.length) {
query._id = ObjectID(query._id);
if (!Object.prototype.hasOwnProperty.call(query, '_id')) {
return;
}
if (typeof query._id === 'string') {
query._id = normalizeIdValue(query._id);
return;
}
if (query._id && typeof query._id === 'object') {
traverse(query._id).forEach(function (x) {
if (this.isLeaf) {
this.update(normalizeIdValue(x));
}
});
}
}
function normalizeIdValue (value) {
if (typeof value === 'string' && OBJECT_ID_HEX_RE.test(value)) {
return new ObjectID(value);
}
return value;
}
/**
@@ -213,7 +235,7 @@ walker.walk_prop = walk_prop;
create.walker = walker;
create.parseRegEx = parseRegEx;
create.default_options = default_options;
create.normalizeIdValue = normalizeIdValue;
// expose module as single high level function
exports = module.exports = create;
+38 -12
View File
@@ -7,6 +7,7 @@ var find_options = require('./query');
function storage (env, ctx) {
var ObjectID = require('mongodb-legacy').ObjectId;
var OBJECT_ID_HEX_RE = /^[0-9a-fA-F]{24}$/;
function create (objOrArray, fn) {
@@ -49,10 +50,11 @@ function storage (env, ctx) {
// Build bulkWrite operations for regular docs (no preBolus)
// Prepare data and build bulk ops together
var bulkOps = objOrArray.map(function(obj) {
normalizeTreatmentId(obj);
var results = prepareData(obj);
return {
replaceOne: {
filter: { created_at: results.created_at, eventType: obj.eventType },
filter: upsertQueryFor(obj, results),
replacement: obj,
upsert: true
}
@@ -90,13 +92,10 @@ function storage (env, ctx) {
}
function upsert (obj, fn) {
normalizeTreatmentId(obj);
var results = prepareData(obj);
var query = {
created_at: results.created_at
, eventType: obj.eventType
};
var query = upsertQueryFor(obj, results);
api( ).replaceOne(query, obj, {upsert: true}, function complete (err, updateResults) {
@@ -121,8 +120,11 @@ function storage (env, ctx) {
pbTreat.notes = obj.notes;
}
query.created_at = pbTreat.created_at;
api( ).replaceOne(query, pbTreat, {upsert: true}, function pbComplete (err, updateResults) {
var pbQuery = {
created_at: pbTreat.created_at,
eventType: pbTreat.eventType
};
api( ).replaceOne(pbQuery, pbTreat, {upsert: true}, function pbComplete (err, updateResults) {
if (updateResults) {
if (updateResults.upsertedCount == 1) {
@@ -191,11 +193,14 @@ function storage (env, ctx) {
}
function save (obj, fn) {
obj._id = new ObjectID(obj._id);
normalizeTreatmentId(obj);
prepareData(obj);
function saved (err, created) {
function saved (err, updateResults) {
if (!err) {
if (updateResults && updateResults.upsertedCount == 1) {
obj._id = updateResults.upsertedId;
}
// console.log('Treatment updated', created);
ctx.ddata.processRawDataForRuntime(obj);
@@ -209,10 +214,10 @@ function storage (env, ctx) {
}
if (err) console.error('Problem saving treating', err);
fn(err, created);
fn(err, obj);
}
api().save(obj, saved);
api().replaceOne(upsertQueryFor(obj, { created_at: obj.created_at }), obj, {upsert: true}, saved);
ctx.bus.emit('data-received');
}
@@ -221,6 +226,27 @@ function storage (env, ctx) {
return ctx.store.collection(env.treatments_collection);
}
function upsertQueryFor (obj, results) {
if (Object.prototype.hasOwnProperty.call(obj, '_id') && obj._id !== null && obj._id !== '') {
return { _id: obj._id };
}
return {
created_at: results.created_at
, eventType: obj.eventType
};
}
function normalizeTreatmentId (obj) {
if (!Object.prototype.hasOwnProperty.call(obj, '_id') || obj._id === null || obj._id === '') {
return;
}
if (typeof obj._id === 'string' && OBJECT_ID_HEX_RE.test(obj._id)) {
obj._id = new ObjectID(obj._id);
}
}
api.list = list;
api.create = create;
api.query_for = query_for;
+109
View File
@@ -246,4 +246,113 @@ describe('Treatment API', function ( ) {
}
});
});
it('supports UUID treatment ids for post, put, and delete', function (done) {
var treatmentId = '69F15FD2-8075-4DEB-AEA3-4352F455840D';
var originalCreatedAt = '2026-02-17T02:00:16.000Z';
var repostedCreatedAt = '2026-02-17T02:05:16.000Z';
var updatedCreatedAt = '2026-02-17T02:10:16.000Z';
self.ctx.treatments.remove({ find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } } }, function () {
request(self.app)
.post('/api/treatments/')
.set('api-secret', api_secret_hash || '')
.send({
_id: treatmentId,
eventType: 'Temporary Override',
created_at: originalCreatedAt,
durationType: 'indefinite',
correctionRange: [90, 110],
insulinNeedsScaleFactor: 1.2,
reason: 'test override'
})
.expect(200)
.end(function (err) {
if (err) {
return done(err);
}
request(self.app)
.post('/api/treatments/')
.set('api-secret', api_secret_hash || '')
.send({
_id: treatmentId,
eventType: 'Temporary Override',
created_at: repostedCreatedAt,
duration: 60,
correctionRange: [90, 110],
insulinNeedsScaleFactor: 1.2,
reason: 'reposted override'
})
.expect(200)
.end(function (err) {
if (err) {
return done(err);
}
self.ctx.treatments.list({ find: { _id: treatmentId } }, function (err, list) {
if (err) {
return done(err);
}
list.length.should.equal(1);
list[0]._id.should.equal(treatmentId);
list[0].created_at.should.equal(repostedCreatedAt);
list[0].duration.should.equal(60);
should.not.exist(list[0].durationType);
request(self.app)
.put('/api/treatments/')
.set('api-secret', api_secret_hash || '')
.send({
_id: treatmentId,
eventType: 'Temporary Override',
created_at: updatedCreatedAt,
duration: 30,
correctionRange: [90, 110],
insulinNeedsScaleFactor: 1.2,
reason: 'updated override'
})
.expect(200)
.end(function (err) {
if (err) {
return done(err);
}
self.ctx.treatments.list({ find: { _id: treatmentId } }, function (err, updatedList) {
if (err) {
return done(err);
}
updatedList.length.should.equal(1);
updatedList[0]._id.should.equal(treatmentId);
updatedList[0].created_at.should.equal(updatedCreatedAt);
updatedList[0].duration.should.equal(30);
updatedList[0].reason.should.equal('updated override');
request(self.app)
.delete('/api/treatments/' + encodeURIComponent(treatmentId))
.set('api-secret', api_secret_hash || '')
.expect(200)
.end(function (err) {
if (err) {
return done(err);
}
self.ctx.treatments.list({ find: { _id: treatmentId } }, function (err, deletedList) {
if (err) {
return done(err);
}
deletedList.length.should.equal(0);
done();
});
});
});
});
});
});
});
});
});
});
+14
View File
@@ -35,4 +35,18 @@ describe('query', function ( ) {
(typeof opts.date).should.equal('undefined')
});
it('should keep non-ObjectId _id queries as strings', function ( ) {
var uuid = '69F15FD2-8075-4DEB-AEA3-4352F455840D';
var opts = query({ find: { _id: uuid } });
opts._id.should.equal(uuid);
});
it('should convert ObjectId-shaped _id queries', function ( ) {
var objectId = '55cbd4e47e726599048a3f91';
var opts = query({ find: { _id: objectId } });
opts._id.toString().should.equal(objectId);
});
});