Skip to content

Commit

Permalink
introduce sgvdata, use it in api and storage
Browse files Browse the repository at this point in the history
Clean up and comment a bunch of code.
  • Loading branch information
bewest committed Jul 15, 2014
1 parent b35853b commit b1ecbe3
Show file tree
Hide file tree
Showing 4 changed files with 202 additions and 161 deletions.
6 changes: 3 additions & 3 deletions bin/create_svg.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ UNIX=$(date +%s $OPTS)

(
cat <<EOF
{ "sgv": $SGV,
"device": "test",
{ "sgv": "$SGV",
"device": "xxx-test-device",
"direction": "$DIRECTION",
"dateString": "$ISO",
"date": "$UNIX"
"date": $UNIX
}
EOF
) | tr -d '\n'
Expand Down
269 changes: 143 additions & 126 deletions lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,49 @@

var consts = require('./constants');
var es = require('event-stream');
var sgvdata = require('sgvdata');

/*
* API - Expose Nightscout HTTP API
* This api is designed to work with express.
*/
function api (env, entries, settings) {

// our globals
var express = require('express'),
api = express(),
bodyParser = require('body-parser');
api = express()
;

var verifyAuthorization = require('./middleware/verify-token')(env);
var sendJSONStatus = require('./middleware/send-json-status')( );
// some middleware
var verifyAuthorization = require('./middleware/verify-token')(env),
sendJSONStatus = require('./middleware/send-json-status')( ),
bodyParser = require('body-parser')
;

// set up express basics
api.set('title', 'Nightscout API v1');

// invoke common middleware
api.use(sendJSONStatus);
// text body types get handled as raw buffer stream
api.use(bodyParser.raw());
// json body types get handled as parsed json
api.use(bodyParser.json());
// shortcut to use extension to specify output content-type
api.use(require('express-extension-to-accept')([
'json', 'svg', 'csv', 'txt', 'png', 'html', 'tsv'
]));

// also support url-encoded content-type
api.use(bodyParser.urlencoded({
extended: true
}));

/*
* Start setting up routes
*/

// Some experiments
api.get('/authorized/:secret/test', verifyAuthorization, function (req, res, next) {
return res.json({status: 'ok'});
});
Expand All @@ -30,6 +53,7 @@ function api (env, entries, settings) {
return res.json({status: 'ok'});
});

// Status badge/text/json
api.get('/status', function (req, res, next) {
var status = {status: 'ok'};
var badge = 'http://img.shields.io/badge/Nightscout-OK-green';
Expand All @@ -52,134 +76,126 @@ function api (env, entries, settings) {
});
});

api.get('/entries', function(req, res) {
console.log('body', req.body);
console.log('params', req.params);
console.log('query', req.query);
// If "?count=" is present, use that number to decided how many to return.
var count = parseInt(req.query.count, 0) || consts.ENTRIES_DEFAULT_COUNT;
var query = { count: count };
entries.list(query, function(err, entries) {
if (err)
res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err);
else
return res.json(entries);
});
return;
});

api.get('/entries/current', function(req, res) {
entries.list({count: 1}, function(err, entries) {
if (err)
res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err);
else
return res.json(entries);
});
return;
});
/**********\
* Entries
\**********/

// Middleware to format any response involving entries.
function format_entries (req, res, next) {
var output = es.readArray(res.entries || [ ]);
if (res.entries_err) return res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err);
return res.format({
text: function ( ) {
es.pipeline(output, sgvdata.format( ), res);
},
json: function ( ) {
es.pipeline(output, sgvdata.lint({strict: false}), es.writeArray(function (err, out) {
res.json(out);
}));
}
});
}

// middleware to process "uploads" of sgv data
function insert_entries (req, res, next) {
var incoming = [ ];
if ('sgv' in req.body) {
incoming.push(req.body);
// list of incoming records
var incoming = [ ];
// Potentially a single json encoded body.
// This can happen from either an url-encoded or json content-type.
if ('sgv' in req.body) {
// add it to the incoming list
incoming.push(req.body);
}
// potentially a list of json entries
if (req.body.length) {
// add them to the list
incoming = incoming.concat(req.body);
}

/*
* inputs -> <ReadableStream>
* in node, pipe is the most interoperable interface
* inputs returns a readable stream representing all the potential
* records from the HTTP body.
* Most content-types are handled by express middeware.
* However, text/* types are given to us as a raw buffer, this
* function switches between these two variants to find the
* correct input stream.
* stream, so use svgdata to handle those.
* The inputs stream always emits sgv json objects.
*/
function inputs ( ) {
var input;
// handle all text types
if (req.is('text/*')) {
// re-use the svgdata parsing stream
input = es.pipeline(req, sgvdata.parse( ));
return input;
}
if (req.body.length) {
incoming = incoming.concat(req.body);
// use established list
return es.readArray(incoming);
}

// return a writable persistent storage stream
function persist (fn) {
if (req.persist_entries) {
// store everything
return entries.persist(fn);
}
// support a preview mode, just lint everything
return es.pipeline(entries.map( ), es.writeArray(fn));
}

// store results and move to the next middleware
function done (err, result) {
res.entries = result;
res.entries_err = err;
return next( );
}

// pipe everything to persistent storage
// when finished, pass to the next piece of middleware
es.pipeline(inputs( ), persist(done));
}

function inputs ( ) {
var rec_sep = ',';
var input;
if (req.is('text/*')) {
if (req.is('text/tsv')) {
rec_sep = '\t';
}
input = es.pipeline(req,
es.split('\n'),
es.map(function (str, next) {
var p = str.split(rec_sep);
if (p.length < 4) { return next(null); }
var rec = {
dateString: p[0]
, date: parseInt(p[1])
, sgv: p[2]
, direction: p[3]
, device: p[4]
};
console.log(p);
next(null, rec);
})
);
return input;
}
return es.readArray(incoming);
}
api.get('/entries', function(req, res, next) {
// If "?count=" is present, use that number to decided how many to return.
var query = req.query;
entries.list(query, function(err, entries) {
res.entries = entries;
res.entries_err = err;
return next( );
});
return;
}, format_entries);

api.get('/entries/current', function(req, res, next) {
entries.list({count: 1}, function(err, entries) {
res.entries = entries;
res.entries_err = err;
return next( );
});
return;
}, format_entries);

es.pipeline(inputs( ),
entries.map( ),
es.writeArray(function (err, result) {
// incoming = incoming.concat(result);
// var records = incoming.concat(result);
console.log(result);
if (req.persist_entries) {
entries.create(result, function ( ) {
console.log("WEB DONE", arguments);
res.json(result);
});
return;
}
return res.format({
/*
html: function ( ) {
res.send("<h1>STATUS OK</h1>");
},
png: function ( ) {
res.redirect(302, badge + '.png');
},
svg: function ( ) {
res.redirect(302, badge + '.svg');
},
text: function ( ) {
res.send("STATUS OK");
},
*/
json: function ( ) {
res.json(result);
}
});
}) // ;
);

}

// Allow previewing your post content, just echos everything you
// posted back out.
api.post('/entries/preview', function (req, res, next) {
var status = {
params: req.params,
body: req.body,
query: req.query
};
req.persist_entries = false;
console.log(status);
next( );
return;
}, insert_entries);

api.post('/entries', function (req, res, next) {
var status = {
params: req.params,
body: req.body,
query: req.query
};
req.persist_entries = true;
console.log(status);
next( );
return;
}, insert_entries);

req.persist_entries = false;
next( );
return;
}, insert_entries, format_entries);

// Create and store new sgv entries
api.post('/entries', verifyAuthorization, function (req, res, next) {
req.persist_entries = true;
next( );
return;
}, insert_entries, format_entries);

// Fetch one entry by id
api.get('/entries/:id', function(req, res) {
console.log('body', req.body);
console.log('params', req.params);
console.log('query', req.query);
entries.getEntry(function(err, entry) {
if (err)
res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err);
Expand All @@ -188,10 +204,8 @@ function api (env, entries, settings) {
}, req.params.id);
});

// Fetch settings
api.get('/settings', function(req, res) {
console.log('body', req.body);
console.log('params', req.params);
console.log('query', req.query);
settings.getSettings(function(err, settings) {
if (err)
res.sendJSONStatus(res, consts.HTTP_INTERNAL_ERROR, 'Mongo Error', err);
Expand All @@ -200,11 +214,14 @@ function api (env, entries, settings) {
});
});

// Delete settings
api.delete('/settings', verifyAuthorization, function(req, res) {
settings.remove(function ( ) {
res.json({ });
});
});

// Replace settings
api.put('/settings', verifyAuthorization, function(req, res) {
// Retrieve the JSON formatted record.
var json = req.body;
Expand Down
Loading

0 comments on commit b1ecbe3

Please sign in to comment.