Skip to content

Commit

Permalink
Activity data collection baseline implementation & swagger fixes (#3442)
Browse files Browse the repository at this point in the history
* 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 #3345

* use nightscout icon for swagger ui

* Comment out deprecated API call
  • Loading branch information
sulkaharo authored May 7, 2018
1 parent 9dda299 commit 57beae5
Show file tree
Hide file tree
Showing 14 changed files with 2,070 additions and 723 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 10 additions & 3 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});

/*
Expand Down Expand Up @@ -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
});
Expand Down
2 changes: 2 additions & 0 deletions env.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
};
Expand Down Expand Up @@ -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.
Expand Down
127 changes: 127 additions & 0 deletions lib/api/activity/index.js
Original file line number Diff line number Diff line change
@@ -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 changes: 2 additions & 0 deletions lib/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down
1 change: 1 addition & 0 deletions lib/authorization/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 57beae5

Please sign in to comment.