Activity data collection baseline implementation & swagger fixes (#3442)

* First pass at adding a new activity data collection & API to Nightscout

* Attempt of fixing /api-docs.html
Converted swagger.yaml to OpenAPI 3.0 format using:
https://github.com/Mermade/swagger2openapi
https://mermade.org.uk/openapi-converter
Conversion / Validation engine v2.11.5
Web frontend version v1.3.8

* add swagger-ui-dist and exported swagger.json

* add swagger-config.yaml

* move swagger-ui to static folder, update static/api-docs.html (based on swagger-ui-dist, without bower)

* move swagger.json to static

* add tokenbased authentication

* token based authentication

* add json webtokens for swagger

* npm update, add swagger-ui-dist and expose that to webroot swagger-ui-dist

* remove swagger-config.yaml (not needed)

* Fix swagger and upgrade to openapi3 (and npm update) (#3366)

* fix swagger and upgrade to openapi3, reapply changes of issue https://github.com/nightscout/cgm-remote-monitor/pull/3345

* use nightscout icon for swagger ui

* Comment out deprecated API call
This commit is contained in:
Sulka Haro
2018-05-07 20:31:01 +03:00
committed by GitHub
parent 9dda2998d9
commit 57beae5f10
14 changed files with 2103 additions and 756 deletions
+1
View File
@@ -216,6 +216,7 @@ To learn more about the Nightscout API, visit https://YOUR-SITE.com/api-docs.htm
* `MONGO_DEVICESTATUS_COLLECTION`(`devicestatus`) - The collection used to store device status information such as uploader battery
* `MONGO_PROFILE_COLLECTION`(`profile`) - The collection used to store your profiles
* `MONGO_FOOD_COLLECTION`(`food`) - The collection used to store your food database
* `MONGO_ACTIVITY_COLLECTION`(`activity`) - The collection used to store activity data
* `PORT` (`1337`) - The port that the node.js application will listen on.
* `HOSTNAME` - The hostname that the node.js application will listen on, null by default for any hostname for IPv6 you may need to use `::`.
* `SSL_KEY` - Path to your ssl key file, so that ssl(https) can be enabled directly in node.js
+10 -3
View File
@@ -101,9 +101,9 @@ function create(env, ctx) {
// pebble data
app.get('/pebble', ctx.pebble);
// expose swagger.yaml
app.get('/swagger.yaml', function(req, res) {
res.sendFile(__dirname + '/swagger.yaml');
// expose swagger.json
app.get('/swagger.json', function(req, res) {
res.sendFile(__dirname + '/swagger.json');
});
/*
@@ -141,6 +141,13 @@ function create(env, ctx) {
// serve the static content
app.use(staticFiles);
var swaggerFiles = express.static(env.swagger_files, {
maxAge: maxAge
});
// serve the static content
app.use('/swagger-ui-dist', swaggerFiles);
var tmpFiles = express.static('tmp', {
maxAge: maxAge
});
+2
View File
@@ -20,6 +20,7 @@ function config ( ) {
env.HOSTNAME = readENV('HOSTNAME', null);
env.IMPORT_CONFIG = readENV('IMPORT_CONFIG', null);
env.static_files = readENV('NIGHTSCOUT_STATIC_FILES', __dirname + '/static/');
env.swagger_files = readENV('NIGHTSCOUT_SWAGGER_FILES', __dirname + '/node_modules/swagger-ui-dist/');
env.debug = {
minify: readENVTruthy('DEBUG_MINIFY', true)
};
@@ -105,6 +106,7 @@ function setStorage() {
env.profile_collection = readENV('MONGO_PROFILE_COLLECTION', 'profile');
env.devicestatus_collection = readENV('MONGO_DEVICESTATUS_COLLECTION', 'devicestatus');
env.food_collection = readENV('MONGO_FOOD_COLLECTION', 'food');
env.activity_collection = readENV('MONGO_ACTIVITY_COLLECTION', 'activity');
// TODO: clean up a bit
// Some people prefer to use a json configuration file instead.
+127
View File
@@ -0,0 +1,127 @@
'use strict';
var _ = require('lodash');
var consts = require('../../constants');
var moment = require('moment');
function configure(app, wares, ctx) {
var express = require('express')
, api = express.Router();
api.use(wares.compression());
api.use(wares.bodyParser({
limit: 1048576 * 50
}));
// text body types get handled as raw buffer stream
api.use(wares.bodyParser.raw({
limit: 1048576
}));
// json body types get handled as parsed json
api.use(wares.bodyParser.json({
limit: 1048576
}));
// also support url-encoded content-type
api.use(wares.bodyParser.urlencoded({
limit: 1048576
, extended: true
}));
// invoke common middleware
api.use(wares.sendJSONStatus);
api.use(ctx.authorization.isPermitted('api:activity:read'));
// List activity data available
api.get('/activity', function(req, res) {
var ifModifiedSince = req.get('If-Modified-Since');
ctx.activity.list(req.query, function(err, results) {
var d1 = null;
_.forEach(results, function clean(t) {
var d2 = null;
if (t.hasOwnProperty('created_at')) {
d2 = new Date(t.created_at);
} else {
if (t.hasOwnProperty('timestamp')) {
d2 = new Date(t.timestamp);
}
}
if (d2 == null) { return; }
if (d1 == null || d2.getTime() > d1.getTime()) {
d1 = d2;
}
});
if (!_.isNil(d1)) res.setHeader('Last-Modified', d1.toUTCString());
if (ifModifiedSince && d1.getTime() <= moment(ifModifiedSince).valueOf()) {
res.status(304).send({
status: 304
, message: 'Not modified'
, type: 'internal'
});
return;
} else {
return res.json(results);
}
});
});
function config_authed(app, api, wares, ctx) {
function post_response(req, res) {
var activity = req.body;
if (!_.isArray(activity)) {
activity = [activity];
};
ctx.activity.create(activity, function(err, created) {
if (err) {
console.log('Error adding activity data', err);
res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err);
} else {
console.log('Activity measure created');
res.json(created);
}
});
}
api.post('/activity/', wares.bodyParser({
limit: 1048576 * 50
}), ctx.authorization.isPermitted('api:activity:create'), post_response);
api.delete('/activity/:_id', ctx.authorization.isPermitted('api:activity:delete'), function(req, res) {
ctx.activity.remove(req.params._id, function() {
res.json({});
});
});
// update record
api.put('/activity/', ctx.authorization.isPermitted('api:activity:update'), function(req, res) {
var data = req.body;
ctx.activity.save(data, function(err, created) {
if (err) {
res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err);
console.log('Error saving activity');
console.log(err);
} else {
res.json(created);
console.log('Activity measure saved', data);
}
});
});
}
if (app.enabled('api') && app.enabled('careportal')) {
config_authed(app, api, wares, ctx);
}
return api;
}
module.exports = configure;
+2
View File
@@ -53,6 +53,8 @@ function create (env, ctx) {
app.all('/devicestatus*', require('./devicestatus/')(app, wares, ctx));
app.all('/notifications*', require('./notifications-api')(app, wares, ctx));
app.all('/activity*', require('./activity/')(app, wares, ctx));
app.use('/', wares.sendJSONStatus, require('./verifyauth')(ctx));
app.all('/food*', require('./food/')(app, wares, ctx));
+1
View File
@@ -114,6 +114,7 @@ function init (env, ctx) {
, { name: 'readable', permissions: [ '*:*:read' ] }
, { name: 'careportal', permissions: [ 'api:treatments:create' ] }
, { name: 'devicestatus-upload', permissions: [ 'api:devicestatus:create' ] }
, { name: 'activity', permissions: [ 'api:activity:create' ] }
];
storage.reload = function reload (callback) {
+350 -269
View File
@@ -5,301 +5,382 @@ var async = require('async');
var times = require('../times');
var fitTreatmentsToBGCurve = require('./treatmenttocurve');
var ONE_DAY = 86400000
, TWO_DAYS = 172800000;
var ONE_DAY = 86400000,
TWO_DAYS = 172800000;
function uniq(a) {
var seen = {};
return a.filter(function (item) {
return seen.hasOwnProperty(item.mills) ? false : (seen[item.mills] = true);
});
var seen = {};
return a.filter(function(item) {
return seen.hasOwnProperty(item.mills) ? false : (seen[item.mills] = true);
});
}
function init(env, ctx) {
var dataloader = { };
var dataloader = {};
dataloader.update = function update(ddata, opts, done) {
if (opts && done == null && opts.call) {
done = opts;
opts = { lastUpdated: Date.now( ), frame: false };
}
dataloader.update = function update(ddata, opts, done) {
if (opts.frame) {
ddata.page = {
frame: true
, after: opts.lastUpdated
// , before: opts.
};
}
ddata.lastUpdated = opts.lastUpdated;
// console.log('LOOKING SINCE', (new Date(ddata.lastUpdated)));
//console.log("Database is connected: " + ctx.store.client.isConnected());
function loadComplete (err, result) {
ddata.treatments = _.uniq(ddata.treatments, false, function (item) { return item._id.toString(); });
//sort treatments so the last is the most recent
ddata.treatments = _.sortBy(ddata.treatments, function (item) { return item.mills; });
fitTreatmentsToBGCurve(ddata, env, ctx);
if (err) {
console.error(err);
}
ddata.processTreatments(true);
var counts = [];
_.forIn(ddata, function each (value, key) {
if (_.isArray(value) && value.length > 0) {
counts.push(key + ':' + value.length);
if (opts && done == null && opts.call) {
done = opts;
opts = {
lastUpdated: Date.now(),
frame: false
};
}
});
console.info('Load Complete:\n\t', counts.join(', '));
if (opts.frame) {
ddata.page = {
frame: true,
after: opts.lastUpdated
// , before: opts.
};
}
ddata.lastUpdated = opts.lastUpdated;
// console.log('LOOKING SINCE', (new Date(ddata.lastUpdated)));
done(err, result);
}
// clear treatments, we're going to merge from more queries
ddata.treatments = [];
async.parallel([
loadEntries.bind(null, ddata, ctx)
, loadTreatments.bind(null, ddata, ctx)
, loadProfileSwitchTreatments.bind(null, ddata, ctx)
, loadSensorAndInsulinTreatments.bind(null, ddata, ctx)
, loadProfile.bind(null, ddata, ctx)
, loadFood.bind(null, ddata, ctx)
, loadDeviceStatus.bind(null, ddata, env, ctx)
], loadComplete);
};
return dataloader;
}
function loadEntries (ddata, ctx, callback) {
var dateRange = {
$gte: ddata.lastUpdated - TWO_DAYS
};
if (ddata.page && ddata.page.frame) {
dateRange['$lte'] = ddata.lastUpdated;
}
var q = {
find: {
date: dateRange
}
, sort: {date: 1}
};
ctx.entries.list(q, function (err, results) {
if (err) { console.log("Problem loading entries"); }
if (!err && results) {
var mbgs = [];
var sgvs = [];
var cals = [];
results.forEach(function (element) {
if (element) {
if (element.mbg) {
mbgs.push({
mgdl: Number(element.mbg), mills: element.date, device: element.device
function loadComplete(err, result) {
ddata.treatments = _.uniq(ddata.treatments, false, function(item) {
return item._id.toString();
});
} else if (element.sgv) {
sgvs.push({
mgdl: Number(element.sgv), mills: element.date, device: element.device, direction: element.direction, filtered: element.filtered, unfiltered: element.unfiltered, noise: element.noise, rssi: element.rssi
//sort treatments so the last is the most recent
ddata.treatments = _.sortBy(ddata.treatments, function(item) {
return item.mills;
});
} else if (element.type === 'cal') {
cals.push({
mills: element.date, scale: element.scale, intercept: element.intercept, slope: element.slope
fitTreatmentsToBGCurve(ddata, env, ctx);
if (err) {
console.error(err);
}
ddata.processTreatments(true);
var counts = [];
_.forIn(ddata, function each(value, key) {
if (_.isArray(value) && value.length > 0) {
counts.push(key + ':' + value.length);
}
});
}
console.info('Load Complete:\n\t', counts.join(', '));
done(err, result);
}
});
//stop using uniq for SGVs since we use buckets, also enables more detailed monitoring
ddata.sgvs = sgvs;
// clear treatments, we're going to merge from more queries
ddata.treatments = [];
async.parallel([
loadEntries.bind(null, ddata, ctx)
, loadTreatments.bind(null, ddata, ctx)
, loadProfileSwitchTreatments.bind(null, ddata, ctx)
, loadSensorAndInsulinTreatments.bind(null, ddata, ctx)
, loadProfile.bind(null, ddata, ctx)
, loadFood.bind(null, ddata, ctx)
, loadDeviceStatus.bind(null, ddata, env, ctx)
, loadActivity.bind(null, ddata, ctx)
], loadComplete);
};
return dataloader;
ddata.mbgs = uniq(mbgs);
ddata.cals = uniq(cals);
}
callback();
});
}
function mergeToTreatments (ddata, results) {
var filtered = _.filter(results, function hasId (treatment) {
return !_.isEmpty(treatment._id);
});
var treatments = _.map(filtered, function update (treatment) {
treatment.mills = new Date(treatment.created_at).getTime();
return treatment;
});
//filter out temps older than a day and an hour ago since we don't display them
var oldestAgo = ddata.lastUpdated - TWO_DAYS - times.hour().msecs;
treatments = _.filter(treatments, function noOldTemps (treatment) {
return !treatment.eventType || treatment.eventType.indexOf('Temp Basal') === -1 || treatment.mills > oldestAgo;
});
ddata.treatments = _.unionWith(ddata.treatments, treatments, function (a, b) {
return a._id.toString() == b._id.toString();
});
}
function loadTreatments (ddata, ctx, callback) {
var dateRange = {
$gte: new Date(ddata.lastUpdated - (ONE_DAY * 8)).toISOString()
};
if (ddata.page && ddata.page.frame) {
dateRange['$lte'] = new Date(ddata.lastUpdated).toISOString( );
}
var tq = {
find: {
created_at: dateRange
function loadEntries(ddata, ctx, callback) {
var dateRange = {
$gte: ddata.lastUpdated - TWO_DAYS
};
if (ddata.page && ddata.page.frame) {
dateRange['$lte'] = ddata.lastUpdated;
}
, sort: {created_at: 1}
};
console.log('searching treatments q', tq);
ctx.treatments.list(tq, function (err, results) {
if (!err && results) {
mergeToTreatments(ddata, results);
}
callback();
});
}
function loadProfileSwitchTreatments (ddata, ctx, callback) {
var dateRange = {
$gte: new Date(ddata.lastUpdated - (ONE_DAY * 31 * 12)).toISOString()
};
if (ddata.page && ddata.page.frame) {
dateRange['$lte'] = new Date(ddata.lastUpdated).toISOString( );
}
var tq = {
find: {
eventType: 'Profile Switch'
, created_at: dateRange
}
, sort: {created_at: -1}
};
ctx.treatments.list(tq, function (err, results) {
if (!err && results) {
mergeToTreatments(ddata, results);
}
// Store last profile switch
if (results) {
ddata.lastProfileFromSwitch = null;
var now = new Date().getTime();
for (var p = 0; p < results.length; p++ ) {
var pdate = new Date(results[p].created_at).getTime();
if (pdate < now) {
ddata.lastProfileFromSwitch = results[p].profile;
break;
var q = {
find: {
date: dateRange
},
sort: {
date: 1
}
}
}
};
callback();
});
}
ctx.entries.list(q, function(err, results) {
function loadSensorAndInsulinTreatments (ddata, ctx, callback) {
var dateRange = {
$gte: new Date(ddata.lastUpdated - (ONE_DAY * 32)).toISOString()
};
if (ddata.page && ddata.page.frame) {
dateRange['$lte'] = new Date(ddata.lastUpdated).toISOString( );
}
var tq = {
find: {
eventType: {
$in: [ 'Sensor Start', 'Sensor Change', 'Insulin Change', 'Pump Battery Change']
}
, created_at: dateRange
}
, sort: {created_at: -1}
};
ctx.treatments.list(tq, function (err, results) {
if (!err && results) {
mergeToTreatments(ddata, results);
}
callback();
});
}
function loadProfile (ddata, ctx, callback) {
ctx.profile.last(function (err, results) {
if (!err && results) {
var profiles = [];
results.forEach(function (element) {
if (element) {
profiles[0] = element;
if (err) {
console.log("Problem loading entries");
}
});
ddata.profiles = profiles;
}
callback();
});
}
function loadFood (ddata, ctx, callback) {
ctx.food.list(function (err, results) {
if (!err && results) {
ddata.food = results;
}
callback();
if (!err && results) {
var mbgs = [];
var sgvs = [];
var cals = [];
results.forEach(function(element) {
if (element) {
if (element.mbg) {
mbgs.push({
mgdl: Number(element.mbg),
mills: element.date,
device: element.device
});
} else if (element.sgv) {
sgvs.push({
mgdl: Number(element.sgv),
mills: element.date,
device: element.device,
direction: element.direction,
filtered: element.filtered,
unfiltered: element.unfiltered,
noise: element.noise,
rssi: element.rssi
});
} else if (element.type === 'cal') {
cals.push({
mills: element.date,
scale: element.scale,
intercept: element.intercept,
slope: element.slope
});
}
}
});
//stop using uniq for SGVs since we use buckets, also enables more detailed monitoring
ddata.sgvs = sgvs;
ddata.mbgs = uniq(mbgs);
ddata.cals = uniq(cals);
}
callback();
});
}
function loadDeviceStatus (ddata, env, ctx, callback) {
var dateRange = {
$gte: new Date(ddata.lastUpdated - ONE_DAY).toISOString()
};
if (ddata.page && ddata.page.frame) {
dateRange['$lte'] = new Date(ddata.lastUpdated).toISOString( );
}
var opts = {
find: {
created_at: dateRange
}
, sort: {created_at: -1}
};
if (env.extendedSettings.devicestatus && env.extendedSettings.devicestatus.advanced) {
//not adding count: 1 restriction
} else {
opts.count = 1;
}
ctx.devicestatus.list(opts, function (err, results) {
if (!err && results) {
ddata.devicestatus = _.map(results, function eachStatus (result) {
result.mills = new Date(result.created_at).getTime();
if ('uploaderBattery' in result) {
result.uploader = {
battery: result.uploaderBattery
};
delete result.uploaderBattery;
}
return result;
}).reverse();
} else {
ddata.devicestatus = [];
}
callback();
});
}
module.exports = init;
function mergeToTreatments(ddata, results) {
var filtered = _.filter(results, function hasId(treatment) {
return !_.isEmpty(treatment._id);
});
var treatments = _.map(filtered, function update(treatment) {
treatment.mills = new Date(treatment.created_at).getTime();
return treatment;
});
//filter out temps older than a day and an hour ago since we don't display them
var oldestAgo = ddata.lastUpdated - TWO_DAYS - times.hour().msecs;
treatments = _.filter(treatments, function noOldTemps(treatment) {
return !treatment.eventType || treatment.eventType.indexOf('Temp Basal') === -1 || treatment.mills > oldestAgo;
});
ddata.treatments = _.unionWith(ddata.treatments, treatments, function(a, b) {
return a._id.toString() == b._id.toString();
});
}
function loadActivity(ddata, ctx, callback) {
var dateRange = {
$gte: new Date(ddata.lastUpdated - (ONE_DAY * 2)).toISOString()
};
if (ddata.page && ddata.page.frame) {
dateRange['$lte'] = new Date(ddata.lastUpdated).toISOString();
}
var q = {
find: {
created_at: dateRange
},
sort: {
created_at: 1
}
};
var activity = [];
ctx.activity.list(q, function(err, results) {
if (err) {
console.log("Problem loading activity data");
}
if (!err && results) {
var activity = [];
results.forEach(function(element) {
if (element) {
if (element.created_at) {
var d = new Date(element.created_at);
activity.push({
mills: d,
heartrate: element.heartrate,
steps: element.steps,
activitylevel: element.activitylevel
});
}
}
});
ddata.activity = uniq(activity);
}
callback();
});
}
function loadTreatments(ddata, ctx, callback) {
var dateRange = {
$gte: new Date(ddata.lastUpdated - (ONE_DAY * 8)).toISOString()
};
if (ddata.page && ddata.page.frame) {
dateRange['$lte'] = new Date(ddata.lastUpdated).toISOString();
}
var tq = {
find: {
created_at: dateRange
},
sort: {
created_at: 1
}
};
console.log('searching treatments q', tq);
ctx.treatments.list(tq, function(err, results) {
if (!err && results) {
mergeToTreatments(ddata, results);
}
callback();
});
}
function loadProfileSwitchTreatments(ddata, ctx, callback) {
var dateRange = {
$gte: new Date(ddata.lastUpdated - (ONE_DAY * 31 * 12)).toISOString()
};
if (ddata.page && ddata.page.frame) {
dateRange['$lte'] = new Date(ddata.lastUpdated).toISOString();
}
var tq = {
find: {
eventType: 'Profile Switch',
created_at: dateRange
},
sort: {
created_at: -1
}
};
ctx.treatments.list(tq, function(err, results) {
if (!err && results) {
mergeToTreatments(ddata, results);
}
// Store last profile switch
if (results) {
ddata.lastProfileFromSwitch = null;
var now = new Date().getTime();
for (var p = 0; p < results.length; p++) {
var pdate = new Date(results[p].created_at).getTime();
if (pdate < now) {
ddata.lastProfileFromSwitch = results[p].profile;
break;
}
}
}
callback();
});
}
function loadSensorAndInsulinTreatments(ddata, ctx, callback) {
var dateRange = {
$gte: new Date(ddata.lastUpdated - (ONE_DAY * 32)).toISOString()
};
if (ddata.page && ddata.page.frame) {
dateRange['$lte'] = new Date(ddata.lastUpdated).toISOString();
}
var tq = {
find: {
eventType: {
$in: ['Sensor Start', 'Sensor Change', 'Insulin Change', 'Pump Battery Change']
},
created_at: dateRange
},
sort: {
created_at: -1
}
};
ctx.treatments.list(tq, function(err, results) {
if (!err && results) {
mergeToTreatments(ddata, results);
}
callback();
});
}
function loadProfile(ddata, ctx, callback) {
ctx.profile.last(function(err, results) {
if (!err && results) {
var profiles = [];
results.forEach(function(element) {
if (element) {
profiles[0] = element;
}
});
ddata.profiles = profiles;
}
callback();
});
}
function loadFood(ddata, ctx, callback) {
ctx.food.list(function(err, results) {
if (!err && results) {
ddata.food = results;
}
callback();
});
}
function loadDeviceStatus(ddata, env, ctx, callback) {
var dateRange = {
$gte: new Date(ddata.lastUpdated - ONE_DAY).toISOString()
};
if (ddata.page && ddata.page.frame) {
dateRange['$lte'] = new Date(ddata.lastUpdated).toISOString();
}
var opts = {
find: {
created_at: dateRange
},
sort: {
created_at: -1
}
};
if (env.extendedSettings.devicestatus && env.extendedSettings.devicestatus.advanced) {
//not adding count: 1 restriction
} else {
opts.count = 1;
}
ctx.devicestatus.list(opts, function(err, results) {
if (!err && results) {
ddata.devicestatus = _.map(results, function eachStatus(result) {
result.mills = new Date(result.created_at).getTime();
if ('uploaderBattery' in result) {
result.uploader = {
battery: result.uploaderBattery
};
delete result.uploaderBattery;
}
return result;
}).reverse();
} else {
ddata.devicestatus = [];
}
callback();
});
}
module.exports = init;
+2
View File
@@ -15,6 +15,7 @@ function init( ) {
, profiles: []
, devicestatus: []
, food: []
, activity: []
, lastUpdated: 0
};
@@ -78,6 +79,7 @@ function init( ) {
result.rest.mbgs = ddata.mbgs.filter(filterMax);
result.rest.food = ddata.food;
result.rest.activity = ddata.activity;
console.log('results.first size', JSON.stringify(result.first).length,'bytes');
console.log('results.rest size', JSON.stringify(result.rest).length,'bytes');
+79
View File
@@ -0,0 +1,79 @@
'use strict';
var find_options = require('./query');
function storage (env, ctx) {
var ObjectID = require('mongodb').ObjectID;
function create (obj, fn) {
obj.created_at = (new Date( )).toISOString( );
api().insert(obj, function (err, doc) {
fn(null, doc.ops);
});
}
function save (obj, fn) {
obj._id = new ObjectID(obj._id);
obj.created_at = (new Date( )).toISOString( );
api().save(obj, function (err, doc) {
fn(err, doc);
});
}
function query_for (opts) {
return find_options(opts, storage.queryOpts);
}
function list(opts, fn) {
// these functions, find, sort, and limit, are used to
// dynamically configure the request, based on the options we've
// been given
// determine sort options
function sort ( ) {
return opts && opts.sort || {created_at: -1};
}
// configure the limit portion of the current query
function limit ( ) {
if (opts && opts.count) {
return this.limit(parseInt(opts.count));
}
return this;
}
// handle all the results
function toArray (err, entries) {
fn(err, entries);
}
// now just stitch them all together
limit.call(api( )
.find(query_for(opts))
.sort(sort( ))
).toArray(toArray);
}
function remove (_id, fn) {
return api( ).remove({ '_id': new ObjectID(_id) }, fn);
}
function api ( ) {
return ctx.store.collection(env.activity_collection);
}
api.list = list;
api.create = create;
api.query_for = query_for;
api.save = save;
api.remove = remove;
api.indexedFields = ['created_at'];
return api;
}
module.exports = storage;
storage.queryOpts = {
dateField: 'created_at'
};
+2
View File
@@ -118,6 +118,7 @@ function boot (env, language) {
ctx.maker = require('../plugins/maker')(env);
ctx.pushnotify = require('./pushnotify')(env, ctx);
ctx.activity = require('./activity')(env, ctx);
ctx.entries = require('./entries')(env, ctx);
ctx.treatments = require('./treatments')(env, ctx);
ctx.devicestatus = require('./devicestatus')(env.devicestatus_collection, ctx);
@@ -148,6 +149,7 @@ function boot (env, language) {
ctx.store.ensureIndexes(ctx.devicestatus( ), ctx.devicestatus.indexedFields);
ctx.store.ensureIndexes(ctx.profile( ), ctx.profile.indexedFields);
ctx.store.ensureIndexes(ctx.food( ), ctx.food.indexedFields);
ctx.store.ensureIndexes(ctx.activity( ), ctx.activity.indexedFields);
next( );
}
+4 -3
View File
@@ -27,9 +27,10 @@ function init (env, ctx, server) {
var supportedCollections = {
'treatments' : env.treatments_collection,
'entries': env.entries_collection,
'devicestatus': env.devicestatus_collection,
'profile': env.profile_collection,
'food': env.food_collection
'devicestatus': env.devicestatus_collection,
'profile': env.profile_collection,
'food': env.food_collection,
'activity': env.activity_collection
};
// This is little ugly copy but I was unable to pass testa after making module from status and share with /api/v1/status
+84 -83
View File
@@ -1,9 +1,11 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Nightscout API</title>
<title>Swagger UI: Nightscout API</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="swagger-ui-dist/swagger-ui.css" >
<link rel="apple-touch-icon" sizes="57x57" href="/images/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/images/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/images/apple-touch-icon-72x72.png">
@@ -22,89 +24,88 @@
<meta name="msapplication-TileColor" content="#00a300">
<meta name="msapplication-TileImage" content="/images/mstile-144x144.png">
<meta name="msapplication-config" content="/browserconfig.xml">
<meta name="theme-color" content="#333333">
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
<link href='/bower_components/swagger-ui/dist/css/typography.css' media='screen' rel='stylesheet' type='text/css'/>
<link href='/bower_components/swagger-ui/dist/css/reset.css' media='screen' rel='stylesheet' type='text/css'/>
<link href='/bower_components/swagger-ui/dist/css/screen.css' media='screen' rel='stylesheet' type='text/css'/>
<link href='/bower_components/swagger-ui/dist/css/reset.css' media='print' rel='stylesheet' type='text/css'/>
<link href='/bower_components/swagger-ui/dist/css/print.css' media='print' rel='stylesheet' type='text/css'/>
<script src='/bower_components/swagger-ui/dist/lib/jquery-1.8.0.min.js' type='text/javascript'></script>
<script src='/bower_components/swagger-ui/dist/lib/jquery.slideto.min.js' type='text/javascript'></script>
<script src='/bower_components/swagger-ui/dist/lib/jquery.wiggle.min.js' type='text/javascript'></script>
<script src='/bower_components/swagger-ui/dist/lib/jquery.ba-bbq.min.js' type='text/javascript'></script>
<script src='/bower_components/swagger-ui/dist/lib/handlebars-2.0.0.js' type='text/javascript'></script>
<script src='/bower_components/swagger-ui/dist/lib/underscore-min.js' type='text/javascript'></script>
<script src='/bower_components/swagger-ui/dist/lib/backbone-min.js' type='text/javascript'></script>
<script src='/bower_components/swagger-ui/dist/swagger-ui.js' type='text/javascript'></script>
<script src='/bower_components/swagger-ui/dist/lib/highlight.7.3.pack.js' type='text/javascript'></script>
<script src='/bower_components/swagger-ui/dist/lib/jsoneditor.min.js' type='text/javascript'></script>
<script src='/bower_components/swagger-ui/dist/lib/marked.js' type='text/javascript'></script>
<script src='/bower_components/swagger-ui/dist/lib/swagger-oauth.js' type='text/javascript'></script>
<!-- Some basic translations -->
<!-- <script src='lang/translator.js' type='text/javascript'></script> -->
<!-- <script src='lang/ru.js' type='text/javascript'></script> -->
<!-- <script src='lang/en.js' type='text/javascript'></script> -->
<script type="text/javascript">
$(function () {
// Pre load translate...
if(window.SwaggerTranslator) {
window.SwaggerTranslator.translate();
}
window.swaggerUi = new SwaggerUi({
url: '/swagger.yaml',
dom_id: 'swagger-ui-container',
supportedSubmitMethods: ['get', 'post', 'put', 'delete', 'patch'],
onComplete: function(/*swaggerApi, swaggerUi*/){
if(window.SwaggerTranslator) {
window.SwaggerTranslator.translate();
}
$('pre code').each(function(i, e) {
hljs.highlightBlock(e)
});
addApiKeyAuthorization();
},
onFailure: function(data) {
log('Unable to Load SwaggerUI', data);
},
docExpansion: 'none',
apisSorter: 'alpha',
showRequestHeaders: false
});
function addApiKeyAuthorization(){
var key = encodeURIComponent($('#input_apiKey')[0].value);
if(key && key.trim() !== '') {
var apiKeyAuth = new SwaggerClient.ApiKeyAuthorization('api_secret', key, 'query');
window.swaggerUi.api.clientAuthorizations.add('api_secret', apiKeyAuth);
log('added key ' + key);
}
}
$('#input_apiKey').change(addApiKeyAuthorization);
window.swaggerUi.load();
});
</script>
body {
margin:0;
background: #fafafa;
}
</style>
</head>
<body class="swagger-section">
<div id='header'>
<div class="swagger-ui-wrap">
<a id="logo" href="http://swagger.io">swagger</a>
<form id='api_selector'>
<div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text"/></div>
<div class='input'><input placeholder="api_secret" id="input_apiKey" name="apiKey" type="text"/></div>
<div class='input'><a id="explore" href="#" data-sw-translate>Explore</a></div>
</form>
</div>
</div>
<body>
<div id="message-bar" class="swagger-ui-wrap" data-sw-translate>&nbsp;</div>
<div id="swagger-ui-container" class="swagger-ui-wrap"></div>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="position:absolute;width:0;height:0">
<defs>
<symbol viewBox="0 0 20 20" id="unlocked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
</symbol>
<symbol viewBox="0 0 20 20" id="locked">
<path d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="close">
<path d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow">
<path d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"/>
</symbol>
<symbol viewBox="0 0 20 20" id="large-arrow-down">
<path d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="jump-to">
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"/>
</symbol>
<symbol viewBox="0 0 24 24" id="expand">
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"/>
</symbol>
</defs>
</svg>
<div id="swagger-ui"></div>
<script src="swagger-ui-dist/swagger-ui-bundle.js"> </script>
<script src="swagger-ui-dist/swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "/swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
</script>
</body>
</html>
+952
View File
@@ -0,0 +1,952 @@
{
"openapi": "3.0.0",
"servers": [
{
"url": "/api/v1"
}
],
"info": {
"title": "Nightscout API",
"description": "Own your DData with the Nightscout API",
"version": "0.10.3-dev-20171205",
"license": {
"name": "AGPL 3",
"url": "https://www.gnu.org/licenses/agpl.txt"
}
},
"security": [
{
"api_secret": [],
"token_in_url": [],
"jwtoken": []
}
],
"paths": {
"/entries/{spec}": {
"get": {
"summary": "All Entries matching query",
"description": "The Entries endpoint returns information about the\nNightscout entries.\n",
"parameters": [
{
"name": "spec",
"in": "path",
"description": "entry id, such as `55cf81bc436037528ec75fa5` or a type filter such\nas `sgv`, `mbg`, etc.\n",
"required": true,
"schema": {
"type": "string",
"default": "sgv"
}
},
{
"name": "find",
"in": "query",
"description": "The query used to find entries, support nested query syntax, for\nexample `find[dateString][$gte]=2015-08-27`. All find parameters\nare interpreted as strings.\n",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "count",
"in": "query",
"description": "Number of entries to return.",
"required": false,
"schema": {
"type": "number"
}
}
],
"tags": [
"Entries"
],
"responses": {
"200": {
"description": "An array of entries",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Entries"
}
}
}
},
"default": {
"description": "Entries",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Entries"
}
}
}
}
}
}
},
"/slice/{storage}/{field}/{type}/{prefix}/{regex}": {
"get": {
"summary": "All Entries matching query",
"description": "The Entries endpoint returns information about the Nightscout entries.",
"parameters": [
{
"name": "storage",
"in": "path",
"description": "Prefix to use in constructing a prefix-based regex, default is `entries`.",
"required": true,
"schema": {
"type": "string",
"default": "entries"
}
},
{
"name": "field",
"in": "path",
"description": "Name of the field to use Regex against in query object, default is `dateString`.",
"required": true,
"schema": {
"type": "string",
"default": "dateString"
}
},
{
"name": "type",
"in": "path",
"description": "The type field to search against, default is sgv.",
"required": true,
"schema": {
"type": "string",
"default": "sgv"
}
},
{
"name": "prefix",
"in": "path",
"description": "Prefix to use in constructing a prefix-based regex.",
"required": true,
"schema": {
"type": "string",
"default": "2015"
}
},
{
"name": "regex",
"in": "path",
"description": "Tail part of regexp to use in expanding/construccting a query object.\nRegexp also has bash-style brace and glob expansion applied to it,\ncreating ways to search for modal times of day, perhaps using\nsomething like this syntax: `T{15..17}:.*`, this would search for\nall records from 3pm to 5pm.\n",
"required": true,
"schema": {
"type": "string",
"default": ".*"
}
},
{
"name": "find",
"in": "query",
"description": "The query used to find entries, support nested query syntax, for\nexample `find[dateString][$gte]=2015-08-27`. All find parameters\nare interpreted as strings.\n",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "count",
"in": "query",
"description": "Number of entries to return.",
"required": false,
"schema": {
"type": "number"
}
}
],
"tags": [
"Entries"
],
"responses": {
"200": {
"description": "An array of entries",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Entries"
}
}
}
},
"default": {
"description": "Unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/echo/{storage}/{spec}": {
"get": {
"summary": "View generated Mongo Query object",
"description": "Information about the mongo query object created by the query.\n",
"parameters": [
{
"name": "storage",
"in": "path",
"description": "`entries`, or `treatments` to select the storage layer.\n",
"required": true,
"schema": {
"type": "string",
"default": "sgv"
}
},
{
"name": "spec",
"in": "path",
"description": "entry id, such as `55cf81bc436037528ec75fa5` or a type filter such\nas `sgv`, `mbg`, etc.\nThis parameter is optional.\n",
"required": true,
"schema": {
"type": "string",
"default": "sgv"
}
},
{
"name": "find",
"in": "query",
"description": "The query used to find entries, support nested query syntax, for\nexample `find[dateString][$gte]=2015-08-27`. All find parameters\nare interpreted as strings.\n",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "count",
"in": "query",
"description": "Number of entries to return.",
"required": false,
"schema": {
"type": "number"
}
}
],
"tags": [
"Entries",
"Debug"
],
"responses": {
"200": {
"description": "An array of entries",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MongoQuery"
}
}
}
}
}
}
},
"/times/echo/{prefix}/{regex}": {
"get": {
"summary": "Echo the query object to be used.",
"description": "Echo debug information about the query object constructed.",
"parameters": [
{
"name": "prefix",
"in": "path",
"description": "Prefix to use in constructing a prefix-based regex.",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "regex",
"in": "path",
"description": "Tail part of regexp to use in expanding/construccting a query object.\nRegexp also has bash-style brace and glob expansion applied to it,\ncreating ways to search for modal times of day, perhaps using\nsomething like this syntax: `T{15..17}:.*`, this would search for\nall records from 3pm to 5pm.\n",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "find",
"in": "query",
"description": "The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find parameters are interpreted as strings.",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "count",
"in": "query",
"description": "Number of entries to return.",
"required": false,
"schema": {
"type": "number"
}
}
],
"tags": [
"Entries",
"Debug"
],
"responses": {
"200": {
"description": "An array of entries",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MongoQuery"
}
}
}
},
"default": {
"description": "Unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/times/{prefix}/{regex}": {
"get": {
"summary": "All Entries matching query",
"description": "The Entries endpoint returns information about the Nightscout entries.",
"parameters": [
{
"name": "prefix",
"in": "path",
"description": "Prefix to use in constructing a prefix-based regex.",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "regex",
"in": "path",
"description": "Tail part of regexp to use in expanding/construccting a query object.\nRegexp also has bash-style brace and glob expansion applied to it,\ncreating ways to search for modal times of day, perhaps using\nsomething like this syntax: `T{15..17}:.*`, this would search for\nall records from 3pm to 5pm.\n",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "find",
"in": "query",
"description": "The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find parameters are interpreted as strings.",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "count",
"in": "query",
"description": "Number of entries to return.",
"required": false,
"schema": {
"type": "number"
}
}
],
"tags": [
"Entries"
],
"responses": {
"200": {
"description": "An array of entries",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Entries"
}
}
}
},
"default": {
"description": "Unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/entries": {
"get": {
"summary": "All Entries matching query",
"description": "The Entries endpoint returns information about the Nightscout entries.",
"parameters": [
{
"name": "find",
"in": "query",
"description": "The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find parameters are interpreted as strings.",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "count",
"in": "query",
"description": "Number of entries to return.",
"required": false,
"schema": {
"type": "number"
}
}
],
"tags": [
"Entries"
],
"responses": {
"200": {
"description": "An array of entries",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Entries"
}
}
}
},
"default": {
"description": "Unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"tags": [
"Entries"
],
"summary": "Add new entries.",
"description": "",
"operationId": "addEntries",
"responses": {
"200": {
"description": "Rejected list of entries. Empty list is success."
},
"405": {
"description": "Invalid input"
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Entries"
}
},
"text/plain": {
"schema": {
"$ref": "#/components/schemas/Entries"
}
}
},
"description": "Entries to be uploaded.",
"required": true
}
},
"delete": {
"tags": [
"Entries"
],
"summary": "Delete entries matching query.",
"description": "Remove entries, same search syntax as GET.",
"operationId": "remove",
"parameters": [
{
"name": "find",
"in": "query",
"description": "The query used to find entries, support nested query syntax, for example `find[dateString][$gte]=2015-08-27`. All find parameters are interpreted as strings.",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "count",
"in": "query",
"description": "Number of entries to return.",
"required": false,
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "Empty list is success."
}
}
}
},
"/treatments": {
"get": {
"summary": "Treatments",
"description": "The Treatments endpoint returns information about the Nightscout treatments.",
"tags": [
"Treatments"
],
"parameters": [
{
"name": "find",
"in": "query",
"description": "The query used to find entries, supports nested query syntax. Examples `find[insulin][$gte]=3` `find[carb][$gte]=100` `find[eventType]=Correction+Bolus` All find parameters are interpreted as strings.",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "count",
"in": "query",
"description": "Number of entries to return.",
"required": false,
"schema": {
"type": "number"
}
}
],
"responses": {
"200": {
"description": "An array of treatments",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Treatments"
}
}
}
},
"default": {
"description": "Unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
},
"post": {
"tags": [
"Treatments"
],
"summary": "Add new treatments.",
"description": "",
"operationId": "addTreatments",
"responses": {
"200": {
"description": "Rejected list of treatments. Empty list is success."
},
"405": {
"description": "Invalid input"
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Treatments"
}
}
},
"description": "Treatments to be uploaded.",
"required": true
}
}
},
"/profile": {
"get": {
"summary": "Profile",
"description": "The Profile endpoint returns information about the Nightscout Treatment Profiles.",
"tags": [
"Profile"
],
"responses": {
"200": {
"description": "An array of treatments",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Profile"
}
}
}
},
"default": {
"description": "Unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
},
"/status": {
"get": {
"summary": "Status",
"description": "Server side status, default settings and capabilities.",
"tags": [
"Status"
],
"responses": {
"200": {
"description": "Server capabilities and status.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Status"
}
}
}
},
"default": {
"description": "Unexpected error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"api_secret": {
"type": "apiKey",
"name": "api_secret",
"in": "header",
"description": "The hash of the API_SECRET env var"
},
"token_in_url": {
"type": "apiKey",
"name": "token",
"in": "query",
"description": "Add token as query item in the URL. You can manage access Token in `/admin`. This uses json webtokens."
},
"jwtoken": {
"type": "http",
"scheme": "bearer",
"description": "Use this if you know the temporary json webtoken.",
"bearerFormat": "JWT"
}
},
"schemas": {
"Entry": {
"properties": {
"type": {
"type": "string",
"description": "sgv, mbg, cal, etc"
},
"dateString": {
"type": "string",
"description": "dateString, prefer ISO `8601`"
},
"date": {
"type": "number",
"description": "Epoch"
},
"sgv": {
"type": "number",
"description": "The glucose reading. (only available for sgv types)"
},
"direction": {
"type": "string",
"description": "Direction of glucose trend reported by CGM. (only available for sgv types)"
},
"noise": {
"type": "number",
"description": "Noise level at time of reading. (only available for sgv types)"
},
"filtered": {
"type": "number",
"description": "The raw filtered value directly from CGM transmitter. (only available for sgv types)"
},
"unfiltered": {
"type": "number",
"description": "The raw unfiltered value directly from CGM transmitter. (only available for sgv types)"
},
"rssi": {
"type": "number",
"description": "The signal strength from CGM transmitter. (only available for sgv types)"
}
}
},
"Entries": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Entry"
}
},
"Treatment": {
"properties": {
"_id": {
"type": "string",
"description": "Internally assigned id."
},
"eventType": {
"type": "string",
"description": "The type of treatment event."
},
"created_at": {
"type": "string",
"description": "The date of the event, might be set retroactively ."
},
"glucose": {
"type": "string",
"description": "Current glucose."
},
"glucoseType": {
"type": "string",
"description": "Method used to obtain glucose, Finger or Sensor."
},
"carbs": {
"type": "number",
"description": "Number of carbs."
},
"insulin": {
"type": "number",
"description": "Amount of insulin, if any."
},
"units": {
"type": "string",
"description": "The units for the glucose value, mg/dl or mmol."
},
"notes": {
"type": "string",
"description": "Description/notes of treatment."
},
"enteredBy": {
"type": "string",
"description": "Who entered the treatment."
}
}
},
"Treatments": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Treatment"
}
},
"Profile": {
"properties": {
"sens": {
"type": "integer",
"description": "Internally assigned id"
},
"dia": {
"type": "integer",
"description": "Internally assigned id"
},
"carbratio": {
"type": "integer",
"description": "Internally assigned id"
},
"carbs_hr": {
"type": "integer",
"description": "Internally assigned id"
},
"_id": {
"type": "string",
"description": "Internally assigned id"
}
}
},
"Status": {
"properties": {
"apiEnabled": {
"type": "boolean",
"description": "Whether or not the REST API is enabled."
},
"careportalEnabled": {
"type": "boolean",
"description": "Whether or not the careportal is enabled in the API."
},
"head": {
"type": "string",
"description": "The git identifier for the running instance of the app."
},
"name": {
"type": "string",
"description": "Nightscout (static)"
},
"version": {
"type": "string",
"description": "The version label of the app."
},
"settings": {
"$ref": "#/components/schemas/Settings"
},
"extendedSettings": {
"$ref": "#/components/schemas/ExtendedSettings"
}
}
},
"Settings": {
"properties": {
"units": {
"type": "string",
"description": "Default units for glucose measurements across the server."
},
"timeFormat": {
"type": "string",
"description": "Default time format",
"enum": [
12,
24
]
},
"customTitle": {
"type": "string",
"description": "Default custom title to be displayed system wide."
},
"nightMode": {
"type": "boolean",
"description": "Should Night mode be enabled by default?"
},
"theme": {
"type": "string",
"description": "Default theme to be displayed system wide, `default`, `colors`, `colorblindfriendly`."
},
"language": {
"type": "string",
"description": "Default language code to be used system wide"
},
"showPlugins": {
"type": "string",
"description": "Plugins that should be shown by default"
},
"showRawbg": {
"type": "string",
"description": "If Raw BG is enabled when should it be shown? `never`, `always`, `noise`"
},
"alarmTypes": {
"type": "array",
"items": {
"type": "string"
},
"enum": [
"simple",
"predict"
],
"description": "Enabled alarm types, can be multiple"
},
"alarmUrgentHigh": {
"type": "boolean",
"description": "Enable/Disable client-side Urgent High alarms by default, for use with `simple` alarms."
},
"alarmHigh": {
"type": "boolean",
"description": "Enable/Disable client-side High alarms by default, for use with `simple` alarms."
},
"alarmLow": {
"type": "boolean",
"description": "Enable/Disable client-side Low alarms by default, for use with `simple` alarms."
},
"alarmUrgentLow": {
"type": "boolean",
"description": "Enable/Disable client-side Urgent Low alarms by default, for use with `simple` alarms."
},
"alarmTimeagoWarn": {
"type": "boolean",
"description": "Enable/Disable client-side stale data alarms by default."
},
"alarmTimeagoWarnMins": {
"type": "number",
"description": "Number of minutes before a stale data warning is generated."
},
"alarmTimeagoUrgent": {
"type": "boolean",
"description": "Enable/Disable client-side urgent stale data alarms by default."
},
"alarmTimeagoUrgentMins": {
"type": "number",
"description": "Number of minutes before a stale data warning is generated."
},
"enable": {
"type": "array",
"items": {
"type": "string"
},
"description": "Enabled features"
},
"thresholds": {
"$ref": "#/components/schemas/Threshold"
}
}
},
"Threshold": {
"properties": {
"bg_high": {
"type": "integer",
"description": "High BG range."
},
"bg_target_top": {
"type": "integer",
"description": "Top of target range."
},
"bg_target_bottom": {
"type": "integer",
"description": "Bottom of target range."
},
"bg_low": {
"type": "integer",
"description": "Low BG range."
}
}
},
"ExtendedSettings": {
"description": "Extended settings of client side plugins"
},
"MongoQuery": {
"description": "Mongo Query object."
},
"Error": {
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"fields": {
"type": "object"
}
}
}
}
}
}
+487 -398
View File
File diff suppressed because it is too large Load Diff