mirror of
https://github.com/bckelley/cgm-remote-monitor.git
synced 2026-08-24 03:14:12 -05:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
@@ -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');
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
@@ -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( );
|
||||
}
|
||||
|
||||
@@ -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
@@ -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> </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
@@ -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
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user