Skip to content

Commit 1231ec6

Browse files
committed
Handle UUID treatment ids in v1 API
1 parent 505e375 commit 1231ec6

File tree

4 files changed

+186
-15
lines changed

4 files changed

+186
-15
lines changed

lib/server/query.js

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
const traverse = require('traverse');
44
const ObjectID = require('mongodb-legacy').ObjectId;
55
const moment = require('moment');
6+
const OBJECT_ID_HEX_RE = /^[0-9a-fA-F]{24}$/;
67

78
const TWO_DAYS = 172800000;
89
/**
@@ -90,11 +91,32 @@ function enforceDateFilter (query, opts) {
9091
* Forces anything named `_id` to be the `ObjectID` type.
9192
*/
9293
function updateIdQuery (query) {
93-
if (query._id && query._id.length) {
94-
query._id = ObjectID(query._id);
94+
if (!Object.prototype.hasOwnProperty.call(query, '_id')) {
95+
return;
96+
}
97+
98+
if (typeof query._id === 'string') {
99+
query._id = normalizeIdValue(query._id);
100+
return;
101+
}
102+
103+
if (query._id && typeof query._id === 'object') {
104+
traverse(query._id).forEach(function (x) {
105+
if (this.isLeaf) {
106+
this.update(normalizeIdValue(x));
107+
}
108+
});
95109
}
96110
}
97111

112+
function normalizeIdValue (value) {
113+
if (typeof value === 'string' && OBJECT_ID_HEX_RE.test(value)) {
114+
return new ObjectID(value);
115+
}
116+
117+
return value;
118+
}
119+
98120
/**
99121
* @param QueryParams params Object returned by qs.parse or https://github.com/hapijs/qs
100122
* @param BuilderOpts opts Options for how to translate types.
@@ -213,7 +235,7 @@ walker.walk_prop = walk_prop;
213235
create.walker = walker;
214236
create.parseRegEx = parseRegEx;
215237
create.default_options = default_options;
238+
create.normalizeIdValue = normalizeIdValue;
216239

217240
// expose module as single high level function
218241
exports = module.exports = create;
219-

lib/server/treatments.js

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var find_options = require('./query');
77

88
function storage (env, ctx) {
99
var ObjectID = require('mongodb-legacy').ObjectId;
10+
var OBJECT_ID_HEX_RE = /^[0-9a-fA-F]{24}$/;
1011

1112
function create (objOrArray, fn) {
1213

@@ -49,10 +50,11 @@ function storage (env, ctx) {
4950
// Build bulkWrite operations for regular docs (no preBolus)
5051
// Prepare data and build bulk ops together
5152
var bulkOps = objOrArray.map(function(obj) {
53+
normalizeTreatmentId(obj);
5254
var results = prepareData(obj);
5355
return {
5456
replaceOne: {
55-
filter: { created_at: results.created_at, eventType: obj.eventType },
57+
filter: upsertQueryFor(obj, results),
5658
replacement: obj,
5759
upsert: true
5860
}
@@ -90,13 +92,10 @@ function storage (env, ctx) {
9092
}
9193

9294
function upsert (obj, fn) {
95+
normalizeTreatmentId(obj);
9396

9497
var results = prepareData(obj);
95-
96-
var query = {
97-
created_at: results.created_at
98-
, eventType: obj.eventType
99-
};
98+
var query = upsertQueryFor(obj, results);
10099

101100
api( ).replaceOne(query, obj, {upsert: true}, function complete (err, updateResults) {
102101

@@ -121,8 +120,11 @@ function storage (env, ctx) {
121120
pbTreat.notes = obj.notes;
122121
}
123122

124-
query.created_at = pbTreat.created_at;
125-
api( ).replaceOne(query, pbTreat, {upsert: true}, function pbComplete (err, updateResults) {
123+
var pbQuery = {
124+
created_at: pbTreat.created_at,
125+
eventType: pbTreat.eventType
126+
};
127+
api( ).replaceOne(pbQuery, pbTreat, {upsert: true}, function pbComplete (err, updateResults) {
126128

127129
if (updateResults) {
128130
if (updateResults.upsertedCount == 1) {
@@ -191,11 +193,14 @@ function storage (env, ctx) {
191193
}
192194

193195
function save (obj, fn) {
194-
obj._id = new ObjectID(obj._id);
196+
normalizeTreatmentId(obj);
195197
prepareData(obj);
196198

197-
function saved (err, created) {
199+
function saved (err, updateResults) {
198200
if (!err) {
201+
if (updateResults && updateResults.upsertedCount == 1) {
202+
obj._id = updateResults.upsertedId;
203+
}
199204
// console.log('Treatment updated', created);
200205

201206
ctx.ddata.processRawDataForRuntime(obj);
@@ -209,10 +214,10 @@ function storage (env, ctx) {
209214
}
210215
if (err) console.error('Problem saving treating', err);
211216

212-
fn(err, created);
217+
fn(err, obj);
213218
}
214219

215-
api().save(obj, saved);
220+
api().replaceOne(upsertQueryFor(obj, { created_at: obj.created_at }), obj, {upsert: true}, saved);
216221

217222
ctx.bus.emit('data-received');
218223
}
@@ -221,6 +226,27 @@ function storage (env, ctx) {
221226
return ctx.store.collection(env.treatments_collection);
222227
}
223228

229+
function upsertQueryFor (obj, results) {
230+
if (Object.prototype.hasOwnProperty.call(obj, '_id') && obj._id !== null && obj._id !== '') {
231+
return { _id: obj._id };
232+
}
233+
234+
return {
235+
created_at: results.created_at
236+
, eventType: obj.eventType
237+
};
238+
}
239+
240+
function normalizeTreatmentId (obj) {
241+
if (!Object.prototype.hasOwnProperty.call(obj, '_id') || obj._id === null || obj._id === '') {
242+
return;
243+
}
244+
245+
if (typeof obj._id === 'string' && OBJECT_ID_HEX_RE.test(obj._id)) {
246+
obj._id = new ObjectID(obj._id);
247+
}
248+
}
249+
224250
api.list = list;
225251
api.create = create;
226252
api.query_for = query_for;

tests/api.treatments.test.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,113 @@ describe('Treatment API', function ( ) {
246246
}
247247
});
248248
});
249+
250+
it('supports UUID treatment ids for post, put, and delete', function (done) {
251+
var treatmentId = '69F15FD2-8075-4DEB-AEA3-4352F455840D';
252+
var originalCreatedAt = '2026-02-17T02:00:16.000Z';
253+
var repostedCreatedAt = '2026-02-17T02:05:16.000Z';
254+
var updatedCreatedAt = '2026-02-17T02:10:16.000Z';
255+
256+
self.ctx.treatments.remove({ find: { created_at: { '$gte': '1999-01-01T00:00:00.000Z' } } }, function () {
257+
request(self.app)
258+
.post('/api/treatments/')
259+
.set('api-secret', api_secret_hash || '')
260+
.send({
261+
_id: treatmentId,
262+
eventType: 'Temporary Override',
263+
created_at: originalCreatedAt,
264+
durationType: 'indefinite',
265+
correctionRange: [90, 110],
266+
insulinNeedsScaleFactor: 1.2,
267+
reason: 'test override'
268+
})
269+
.expect(200)
270+
.end(function (err) {
271+
if (err) {
272+
return done(err);
273+
}
274+
275+
request(self.app)
276+
.post('/api/treatments/')
277+
.set('api-secret', api_secret_hash || '')
278+
.send({
279+
_id: treatmentId,
280+
eventType: 'Temporary Override',
281+
created_at: repostedCreatedAt,
282+
duration: 60,
283+
correctionRange: [90, 110],
284+
insulinNeedsScaleFactor: 1.2,
285+
reason: 'reposted override'
286+
})
287+
.expect(200)
288+
.end(function (err) {
289+
if (err) {
290+
return done(err);
291+
}
292+
293+
self.ctx.treatments.list({ find: { _id: treatmentId } }, function (err, list) {
294+
if (err) {
295+
return done(err);
296+
}
297+
298+
list.length.should.equal(1);
299+
list[0]._id.should.equal(treatmentId);
300+
list[0].created_at.should.equal(repostedCreatedAt);
301+
list[0].duration.should.equal(60);
302+
should.not.exist(list[0].durationType);
303+
304+
request(self.app)
305+
.put('/api/treatments/')
306+
.set('api-secret', api_secret_hash || '')
307+
.send({
308+
_id: treatmentId,
309+
eventType: 'Temporary Override',
310+
created_at: updatedCreatedAt,
311+
duration: 30,
312+
correctionRange: [90, 110],
313+
insulinNeedsScaleFactor: 1.2,
314+
reason: 'updated override'
315+
})
316+
.expect(200)
317+
.end(function (err) {
318+
if (err) {
319+
return done(err);
320+
}
321+
322+
self.ctx.treatments.list({ find: { _id: treatmentId } }, function (err, updatedList) {
323+
if (err) {
324+
return done(err);
325+
}
326+
327+
updatedList.length.should.equal(1);
328+
updatedList[0]._id.should.equal(treatmentId);
329+
updatedList[0].created_at.should.equal(updatedCreatedAt);
330+
updatedList[0].duration.should.equal(30);
331+
updatedList[0].reason.should.equal('updated override');
332+
333+
request(self.app)
334+
.delete('/api/treatments/' + encodeURIComponent(treatmentId))
335+
.set('api-secret', api_secret_hash || '')
336+
.expect(200)
337+
.end(function (err) {
338+
if (err) {
339+
return done(err);
340+
}
341+
342+
self.ctx.treatments.list({ find: { _id: treatmentId } }, function (err, deletedList) {
343+
if (err) {
344+
return done(err);
345+
}
346+
347+
deletedList.length.should.equal(0);
348+
done();
349+
});
350+
});
351+
});
352+
});
353+
});
354+
});
355+
});
356+
});
357+
});
249358
});

tests/query.test.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,18 @@ describe('query', function ( ) {
3535

3636
(typeof opts.date).should.equal('undefined')
3737
});
38+
39+
it('should keep non-ObjectId _id queries as strings', function ( ) {
40+
var uuid = '69F15FD2-8075-4DEB-AEA3-4352F455840D';
41+
var opts = query({ find: { _id: uuid } });
42+
43+
opts._id.should.equal(uuid);
44+
});
45+
46+
it('should convert ObjectId-shaped _id queries', function ( ) {
47+
var objectId = '55cbd4e47e726599048a3f91';
48+
var opts = query({ find: { _id: objectId } });
49+
50+
opts._id.toString().should.equal(objectId);
51+
});
3852
});

0 commit comments

Comments
 (0)