Skip to content

Commit a61c2d4

Browse files
move getData logic to measurements model
1 parent 58f3787 commit a61c2d4

File tree

2 files changed

+65
-66
lines changed

2 files changed

+65
-66
lines changed

lib/controllers/measurementsController.js

Lines changed: 9 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@ const
44
restifyErrors = require('restify-errors'),
55
{ Measurement, Box } = require('../models'),
66
csvstringify = require('csv-stringify'),
7-
streamTransform = require('stream-transform'),
8-
jsonstringify = require('stringify-stream'),
97
{ checkContentType } = require('../helpers/apiUtils'),
108
{
119
retrieveParameters,
1210
validateFromToTimeParams
1311
} = require('../helpers/userParamHelpers'),
14-
{ outlierTransformer } = require('../statistics'),
1512
handleError = require('../helpers/errorHandler');
1613

1714
const { BadRequestError, UnsupportedMediaTypeError } = restifyErrors;
@@ -33,12 +30,6 @@ const getLatestMeasurements = function getLatestMeasurements (req, res, next) {
3330
});
3431
};
3532

36-
const getDataTransformerFunction = function getDataTransformerFunction (data) {
37-
data.createdAt = data.createdAt.toISOString();
38-
39-
return data;
40-
};
41-
4233
/**
4334
* @apiDefine SeparatorParam
4435
*
@@ -60,70 +51,23 @@ const getDataTransformerFunction = function getDataTransformerFunction (data) {
6051
* @apiUse SeparatorParam
6152
*/
6253
const getData = function getData (req, res, next) {
63-
let stringifier;
64-
65-
const { format, delimiter, outliers, outlierWindow } = req._userParams;
66-
67-
// validate window
68-
if (outlierWindow < 1 || outlierWindow >= 50) {
69-
return next(new BadRequestError('parameter outlier-window must be between 1 and 50, default is 15'));
70-
}
54+
const { sensorId, format, download } = req._userParams;
7155

7256
// IDEA: add geojson point featurecollection format
73-
if (format === 'csv') {
57+
if (format === 'csv' || (download === 'true')) {
7458
res.header('Content-Type', 'text/csv');
75-
const csvColumns = ['createdAt', 'value'];
76-
if (outliers) {
77-
csvColumns.push('isOutlier');
78-
}
79-
80-
stringifier = csvstringify({ columns: csvColumns, header: 1, delimiter });
59+
res.header('Content-Disposition', `attachment; filename=${sensorId}.${format}`);
8160
} else if (format === 'json') {
8261
res.header('Content-Type', 'application/json; charset=utf-8');
83-
stringifier = jsonstringify({ open: '[', close: ']' }, function replacer (k, v) {
84-
// dont send unnecessary nested location
85-
return (k === 'location') ? v.coordinates : v;
86-
});
8762
}
8863

89-
// offer download to browser
90-
if (format === 'csv' || (req._userParams.download === 'true')) {
91-
res.header('Content-Disposition', `attachment; filename=${req.params.sensorId}.${format}`);
92-
}
64+
Measurement.getMeasurementsStream(req._userParams)
65+
.on('error', function (err) {
66+
res.end(`Error: ${err.message}`);
9367

94-
// finally execute the query
95-
const queryLimit = 10000;
96-
97-
const qry = {
98-
sensor_id: req._userParams.sensorId,
99-
createdAt: {
100-
$gte: req._userParams.fromDate.toDate(),
101-
$lte: req._userParams.toDate.toDate()
102-
}
103-
};
104-
105-
const measurementsCursor = Measurement
106-
.find(qry, { 'createdAt': 1, 'value': 1, 'location': 1, '_id': 0 })
107-
.cursor({ lean: true, limit: queryLimit });
108-
109-
if (outliers) {
110-
measurementsCursor
111-
.pipe(streamTransform(getDataTransformerFunction))
112-
.pipe(outlierTransformer({
113-
window: Math.trunc(outlierWindow), // only allow integer values
114-
replaceOutlier: (outliers === 'replace')
115-
}))
116-
.on('error', function (err) {
117-
res.end(`Error: ${err.message}`);
118-
})
119-
.pipe(stringifier)
120-
.pipe(res);
121-
} else {
122-
measurementsCursor
123-
.pipe(streamTransform(getDataTransformerFunction))
124-
.pipe(stringifier)
125-
.pipe(res);
126-
}
68+
return next(err);
69+
})
70+
.pipe(res);
12771
};
12872

12973
/**

lib/models/measurement.js

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ const { mongoose } = require('../db'),
44
Schema = mongoose.Schema,
55
moment = require('moment'),
66
decodeHandlers = require('../decoding'),
7-
ModelError = require('./modelError');
7+
ModelError = require('./modelError'),
8+
csvstringify = require('csv-stringify'),
9+
streamTransform = require('stream-transform'),
10+
jsonstringify = require('stringify-stream'),
11+
{ outlierTransformer } = require('../statistics');
812

913
const measurementSchema = new Schema({
1014
value: {
@@ -123,6 +127,57 @@ measurementSchema.statics.findLatestMeasurementsForSensors = function findLatest
123127
.exec();
124128
};
125129

130+
const csvColumns = ['createdAt', 'value'];
131+
132+
const jsonLocationReplacer = function jsonLocationReplacer (k, v) {
133+
// dont send unnecessary nested location
134+
return (k === 'location') ? v.coordinates : v;
135+
};
136+
137+
const getDataTransformerFunction = function getDataTransformerFunction (data) {
138+
data.createdAt = data.createdAt.toISOString();
139+
140+
return data;
141+
};
142+
143+
measurementSchema.statics.getMeasurementsStream = function getMeasurementsStream ({ format, delimiter, fromDate, toDate, sensorId, outliers, outlierWindow }) {
144+
let stringifier;
145+
// IDEA: add geojson point featurecollection format
146+
if (format === 'csv') {
147+
stringifier = csvstringify({ columns: csvColumns, header: 1, delimiter });
148+
} else if (format === 'json') {
149+
stringifier = jsonstringify({ open: '[', close: ']' }, jsonLocationReplacer);
150+
}
151+
152+
// finally execute the query
153+
const queryLimit = 10000;
154+
155+
const qry = {
156+
sensor_id: sensorId,
157+
createdAt: {
158+
$gte: fromDate.toDate(),
159+
$lte: toDate.toDate()
160+
}
161+
};
162+
163+
let measurementsCursor = this
164+
.find(qry, { 'createdAt': 1, 'value': 1, 'location': 1, '_id': 0 })
165+
.cursor({ lean: true, limit: queryLimit })
166+
.pipe(streamTransform(getDataTransformerFunction));
167+
168+
if (outliers) {
169+
measurementsCursor = measurementsCursor
170+
.pipe(outlierTransformer({
171+
window: Math.trunc(outlierWindow), // only allow integer values
172+
replaceOutlier: (outliers === 'replace')
173+
}))
174+
.pipe(stringifier);
175+
}
176+
177+
return measurementsCursor
178+
.pipe(stringifier);
179+
};
180+
126181
const measurementModel = mongoose.model('Measurement', measurementSchema);
127182

128183
module.exports = {

0 commit comments

Comments
 (0)