Skip to content

Commit 9334503

Browse files
Copilotbewest
authored andcommitted
Address review concerns from PR nightscout#8447 and nightscout#8421
Co-authored-by: bewest <394179+bewest@users.noreply.github.com>
1 parent 9cd304f commit 9334503

File tree

5 files changed

+119
-42
lines changed

5 files changed

+119
-42
lines changed

lib/authorization/delaylist.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ function init (env) {
66

77
const ipDelayList = {};
88

9-
const DELAY_ON_FAIL = _.get(env, 'settings.authFailDelay') || 5000;
9+
const rawAuthFailDelay = _.get(env, 'settings.authFailDelay');
10+
const parsedAuthFailDelay = Number(rawAuthFailDelay);
11+
const DELAY_ON_FAIL = Number.isFinite(parsedAuthFailDelay) && parsedAuthFailDelay > 0 ? parsedAuthFailDelay : 5000;
1012
const FAIL_AGE = 60000;
1113

1214
ipDelayList.addFailedRequest = function addFailedRequest (ip) {

lib/server/bootevent.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ function boot (env, language) {
233233
ctx.activity = require('./activity')(env, ctx);
234234
ctx.entries = require('./entries')(env, ctx);
235235
ctx.treatments = require('./treatments')(env, ctx);
236-
ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx);
236+
ctx.devicestatus = require('./devicestatus')(env, ctx);
237237
ctx.profile = require('./profile')(env.profile_collection, ctx);
238238
ctx.food = require('./food')(env, ctx);
239239
ctx.pebble = require('./pebble')(env, ctx);

lib/server/devicestatus.js

Lines changed: 95 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,110 @@
33
var moment = require('moment');
44
var find_options = require('./query');
55

6-
function storage (collection, ctx) {
6+
/**
7+
* Truncate OpenAPS prediction arrays to a bounded length.
8+
*
9+
* This helper limits the size of `openaps.suggested.predBGs` and
10+
* `openaps.enacted.predBGs` arrays to avoid unbounded growth of
11+
* `devicestatus` documents, which helps keep MongoDB documents
12+
* under the 16 MB size limit.
13+
*
14+
* The function only operates on the following prediction series,
15+
* when present:
16+
* - `IOB` (insulin-on-board-based predictions)
17+
* - `COB` (carbs-on-board-based predictions)
18+
* - `UAM` (unannounced-meal-based predictions)
19+
* - `ZT` (zero-temp-based predictions)
20+
*
21+
* The `maxSize` argument defines the maximum number of prediction
22+
* points retained per series. When not explicitly configured by
23+
* `env.predictionsMaxSize`, this is typically set to 288, which
24+
* corresponds to 24 hours of 5-minute prediction intervals
25+
* (24 * 60 / 5) and provides a practical upper bound to keep
26+
* documents reasonably small for MongoDB storage.
27+
*
28+
* If `maxSize` is falsy or not a positive number, the input
29+
* object is returned unmodified.
30+
*
31+
* @param {Object} obj - The devicestatus-like object potentially containing
32+
* `openaps.suggested.predBGs` and/or `openaps.enacted.predBGs`.
33+
* @param {number} maxSize - Maximum allowed length for each prediction array
34+
* (commonly 288 by default).
35+
* @returns {Object} The same object reference, possibly with truncated
36+
* prediction arrays.
37+
*/
38+
function truncatePredictions (obj, maxSize) {
39+
if (!maxSize || maxSize <= 0) return obj;
40+
41+
var predictionTypes = ['IOB', 'COB', 'UAM', 'ZT'];
42+
43+
if (obj && obj.openaps && obj.openaps.suggested && obj.openaps.suggested.predBGs) {
44+
var suggestedPredBGs = obj.openaps.suggested.predBGs;
45+
predictionTypes.forEach(function(type) {
46+
if (Array.isArray(suggestedPredBGs[type]) && suggestedPredBGs[type].length > maxSize) {
47+
suggestedPredBGs[type] = suggestedPredBGs[type].slice(0, maxSize);
48+
}
49+
});
50+
}
751

8-
function create (statuses, fn) {
52+
if (obj && obj.openaps && obj.openaps.enacted && obj.openaps.enacted.predBGs) {
53+
var enactedPredBGs = obj.openaps.enacted.predBGs;
54+
predictionTypes.forEach(function(type) {
55+
if (Array.isArray(enactedPredBGs[type]) && enactedPredBGs[type].length > maxSize) {
56+
enactedPredBGs[type] = enactedPredBGs[type].slice(0, maxSize);
57+
}
58+
});
59+
}
960

10-
if (!Array.isArray(statuses)) { statuses = [statuses]; }
61+
return obj;
62+
}
1163

12-
const r = [];
13-
let errorOccurred = false;
64+
function storage (env, ctx) {
1465

15-
for (let i = 0; i < statuses.length; i++) {
66+
var collection = env.devicestatus_collection;
67+
var predictionsMaxSize = env.predictionsMaxSize || null;
68+
69+
function create (statuses, fn) {
1670

17-
const obj = statuses[i];
71+
if (!Array.isArray(statuses)) { statuses = [statuses]; }
1872

19-
if (errorOccurred) return;
73+
if (statuses.length === 0) {
74+
return fn(null, []);
75+
}
2076

21-
// Normalize all dates to UTC
22-
const d = moment(obj.created_at).isValid() ? moment.parseZone(obj.created_at) : moment();
77+
// Prepare all documents before insert
78+
statuses.forEach(function(obj) {
79+
var d = moment(obj.created_at).isValid() ? moment.parseZone(obj.created_at) : moment();
2380
obj.created_at = d.toISOString();
2481
obj.utcOffset = d.utcOffset();
82+
truncatePredictions(obj, predictionsMaxSize);
83+
});
2584

26-
api().insertOne(obj, function(err, results) {
27-
if (err !== null && err.message) {
28-
console.log('Error inserting the device status object', err.message);
29-
errorOccurred = true;
30-
fn(err.message, null);
31-
return;
32-
}
33-
34-
if (!err) {
35-
36-
if (!obj._id) obj._id = results.insertedIds[0]._id;
37-
r.push(obj);
38-
39-
ctx.bus.emit('data-update', {
40-
type: 'devicestatus'
41-
, op: 'update'
42-
, changes: ctx.ddata.processRawDataForRuntime([obj])
43-
});
44-
45-
// Last object! Return results
46-
if (i == statuses.length - 1) {
47-
fn(null, r);
48-
ctx.bus.emit('data-received');
49-
}
50-
}
85+
// Use insertMany for batch insert
86+
api().insertMany(statuses, { ordered: true }, function(err, insertResult) {
87+
if (err) {
88+
console.log('Error inserting device status objects', err.message);
89+
fn(err.message || err, null);
90+
return;
91+
}
92+
93+
// Assign _ids from insertMany result
94+
if (insertResult && insertResult.insertedIds) {
95+
Object.keys(insertResult.insertedIds).forEach(function(index) {
96+
statuses[index]._id = insertResult.insertedIds[index];
97+
});
98+
}
99+
100+
// Emit data-update for all inserted documents
101+
ctx.bus.emit('data-update', {
102+
type: 'devicestatus'
103+
, op: 'update'
104+
, changes: ctx.ddata.processRawDataForRuntime(statuses)
51105
});
52-
};
106+
107+
ctx.bus.emit('data-received');
108+
fn(null, statuses);
109+
});
53110
}
54111

55112
function last (fn) {
@@ -103,14 +160,14 @@ function storage (collection, ctx) {
103160
ctx.bus.emit('data-update', {
104161
type: 'devicestatus'
105162
, op: 'remove'
106-
, count: stat.result.n
163+
, count: stat.deletedCount
107164
, changes: opts.find._id
108165
});
109166

110167
fn(err, stat);
111168
}
112169

113-
return api().remove(
170+
return api().deleteMany(
114171
query_for(opts), removed);
115172
}
116173

lib/server/query.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,16 @@ function enforceDateFilter (query, opts) {
8585
}
8686
}
8787

88+
// A MongoDB ObjectID is exactly 24 hexadecimal characters.
89+
const OBJECT_ID_PATTERN = /^[a-fA-F0-9]{24}$/;
90+
8891
/**
8992
* Helper to set ObjectID type for `_id` queries.
90-
* Forces anything named `_id` to be the `ObjectID` type.
93+
* Only converts strings that match the 24-character hexadecimal ObjectID
94+
* format; non-ObjectID strings (e.g. UUIDs) are left unchanged.
9195
*/
9296
function updateIdQuery (query) {
93-
if (query._id && query._id.length) {
97+
if (query._id && typeof query._id === 'string' && OBJECT_ID_PATTERN.test(query._id)) {
9498
query._id = ObjectID(query._id);
9599
}
96100
}

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)