Skip to content

Commit 500a70a

Browse files
refactor deleting sensor data
1 parent 0d3e08f commit 500a70a

File tree

3 files changed

+93
-127
lines changed

3 files changed

+93
-127
lines changed

lib/controllers/sensorsController.js

Lines changed: 9 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
'use strict';
22

3-
const { BadRequestError, InternalServerError, NotFoundError } = require('restify-errors'),
4-
models = require('../models'),
3+
const { Box } = require('../models'),
54
{ checkContentType } = require('../helpers/apiUtils'),
6-
{ retrieveParameters, validateFromToTimeParams, checkBoxIdOwner } = require('../helpers/userParamHelpers');
7-
8-
const { Measurement, Box } = models;
9-
10-
const CONTACT_ADMIN_MSG = 'If you feel your box may be in inconsistent state, please contact the administrator.';
11-
const DELETE_UNSUCCESSFUL_ERROR = 'Delete operation partially unsuccessful. This usually means some criteria you specified didn\'t yield measurements to delete or the sensor had no measurements. This can happen, if you send the same request twice.';
12-
const UPDATE_BOX_UNSUCCESSFUL_ERROR = 'Couldn\'t find the sensor for updating the lastMeasurement.';
5+
{ retrieveParameters, validateFromToTimeParams, checkBoxIdOwner } = require('../helpers/userParamHelpers'),
6+
handleError = require('../helpers/errorHandler');
137

148
/**
159
* @api {delete} /boxes/:senseBoxId/:sensorId/measurements Delete measurements of a sensor
@@ -26,78 +20,16 @@ const UPDATE_BOX_UNSUCCESSFUL_ERROR = 'Couldn\'t find the sensor for updating th
2620
* @apiParam (RequestBody) {Boolean=true,false} [deleteAllMeasurements=false] Specify `deleteAllMeasurements` with a value of `true` to delete all measurements of this sensor
2721
*/
2822
const deleteSensorData = function deleteSensorData (req, res, next) {
29-
const { boxId, sensorId, deleteAllMeasurements, timestamps, fromDate, toDate } = req._userParams;
30-
31-
const reallyDeleteAllMeasurements = (deleteAllMeasurements === 'true');
32-
// check for instruction exclusivity
33-
if (reallyDeleteAllMeasurements === true && (timestamps || (fromDate && toDate))) {
34-
return next(new BadRequestError('deleteAllMeasurements can only be used by itself'));
35-
} else if (reallyDeleteAllMeasurements === false && timestamps && fromDate && toDate) {
36-
return next(new BadRequestError('please specify only timestamps or a range with from-date and to-date'));
37-
} else if (reallyDeleteAllMeasurements === false && !timestamps && !fromDate && !toDate) {
38-
return next(new BadRequestError('deleteAllMeasurements not true. deleting nothing'));
39-
}
40-
41-
let successMsg = 'all measurements',
42-
mode = 'all';
43-
let createdAt;
44-
45-
if (timestamps) {
46-
createdAt = {
47-
$in: timestamps.map(t => t.toDate())
48-
};
49-
successMsg = `${timestamps.length} measurements`;
50-
mode = 'timestamps';
51-
}
52-
53-
if (fromDate && toDate) {
54-
createdAt = {
55-
$gt: fromDate.toDate(),
56-
$lt: toDate.toDate()
57-
};
58-
successMsg = `measurements between ${fromDate.format()} and ${toDate.format()}`;
59-
mode = 'range';
60-
}
61-
62-
let deleteError = false;
63-
64-
Measurement.deleteMeasurementsOfSensor(sensorId, createdAt)
65-
.then(function (deleteErrors) {
66-
deleteError = deleteErrors.includes('DELETE_ERROR');
67-
68-
// nothing removed -> lastMeasurement should not be updated
69-
if (deleteErrors.includes('NO_MATCHING_MEASUREMENTS')) {
70-
throw new NotFoundError('no matching measurements for specified query');
71-
}
72-
73-
return Box.findByIdAndUpdateLastMeasurementOfSensor(boxId, sensorId, (mode !== 'all'));
23+
Box.findBoxById(req._userParams.boxId, { lean: false })
24+
.then(function (box) {
25+
return box.deleteMeasurementsOfSensor(req._userParams);
7426
})
75-
.then(function () {
76-
let responseMessage = `Successfully deleted ${successMsg} of sensor ${sensorId} of senseBox ${boxId}`;
77-
78-
if (deleteError === true) {
79-
responseMessage = `${DELETE_UNSUCCESSFUL_ERROR} ${CONTACT_ADMIN_MSG}`;
80-
}
81-
82-
res.send(200, { code: 'Ok', message: responseMessage });
27+
.then(function (message) {
28+
res.send({ code: 'Ok', message });
8329
})
8430
.catch(function (err) {
85-
if (err.message === 'SENSOR_NOT_FOUND') {
86-
let responseMessage = UPDATE_BOX_UNSUCCESSFUL_ERROR;
87-
if (deleteError === true) {
88-
responseMessage = `${DELETE_UNSUCCESSFUL_ERROR} ${responseMessage}`;
89-
}
90-
91-
return res.send(200, { code: 'Ok', message: `${responseMessage} ${CONTACT_ADMIN_MSG}` });
92-
}
93-
94-
if (err.name === 'NotFoundError') {
95-
return next(err);
96-
}
97-
98-
return next(new InternalServerError(err));
31+
handleError(err, next);
9932
});
100-
10133
};
10234

10335
module.exports = {

lib/models/box.js

Lines changed: 78 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const { mongoose } = require('../db'),
2424
integrations = require('./integrations'),
2525
sensorLayouts = require('../sensorLayouts'),
2626
mqttClient = require('../mqtt'),
27-
Measurement = require('./measurement').model,
27+
{ model: Measurement } = require('./measurement'),
2828
{
2929
config: { api_measurements_post_domain, imageFolder },
3030
parseTimestamp,
@@ -315,36 +315,94 @@ boxSchema.statics.findBoxById = function findBoxById (id, { lean = true, populat
315315
});
316316
};
317317

318-
boxSchema.statics.findByIdAndUpdateLastMeasurementOfSensor = function findByIdAndUpdateLastMeasurementOfSensor (id, sensorId, shouldQueryLatestMeasurement) {
319-
let newLastMeasurementPromise = Promise.resolve();
320-
if (typeof shouldQueryLatestMeasurement !== 'undefined' && shouldQueryLatestMeasurement === true) {
321-
newLastMeasurementPromise = Measurement.findLastMeasurementOfSensor(sensorId);
318+
const DELETE_MEASUREMENTS_CONTACT_ADMIN_MSG = 'If you feel your box may be in inconsistent state, please contact the administrator.';
319+
const DELETE_MEASUREMENTS_UNSUCCESSFUL_ERROR = 'Delete operation partially unsuccessful. This usually means some criteria you specified didn\'t yield measurements to delete or the sensor had no measurements. This can happen, if you send the same request twice.';
320+
321+
boxSchema.methods.deleteMeasurementsOfSensor = function deleteMeasurementsOfSensor ({ sensorId, deleteAllMeasurements, timestamps, fromDate, toDate }) {
322+
const box = this;
323+
324+
const sensorIndex = box.sensors.findIndex(s => s._id.equals(sensorId));
325+
if (sensorIndex === -1) {
326+
throw new ModelError(`Sensor with id ${sensorId} not found.`, { type: 'NotFoundError' });
327+
}
328+
329+
const reallyDeleteAllMeasurements = (deleteAllMeasurements === 'true');
330+
// check for instruction exclusivity
331+
if (reallyDeleteAllMeasurements === true && (timestamps || (fromDate && toDate))) {
332+
return Promise.reject(new ModelError('Parameter deleteAllMeasurements can only be used by itself'));
333+
} else if (reallyDeleteAllMeasurements === false && timestamps && fromDate && toDate) {
334+
return Promise.reject(new ModelError('Please specify only timestamps or a range with from-date and to-date'));
335+
} else if (reallyDeleteAllMeasurements === false && !timestamps && !fromDate && !toDate) {
336+
return Promise.reject(new ModelError('DeleteAllMeasurements not true. deleting nothing'));
322337
}
323338

324-
return newLastMeasurementPromise
325-
.then((newLastMeasurement) => { // arrow function for scope
326-
if (newLastMeasurement && Array.isArray(newLastMeasurement) && newLastMeasurement.length !== 0) {
327-
newLastMeasurement = newLastMeasurement[0]._id;
328-
} else { // reply was just an empty array
329-
newLastMeasurement = undefined;
339+
let successMsg = 'all measurements',
340+
mode = 'all';
341+
let createdAt;
342+
343+
if (timestamps) {
344+
createdAt = {
345+
$in: timestamps.map(t => t.toDate())
346+
};
347+
successMsg = `${timestamps.length} measurements`;
348+
mode = 'timestamps';
349+
}
350+
351+
if (fromDate && toDate) {
352+
createdAt = {
353+
$gt: fromDate.toDate(),
354+
$lt: toDate.toDate()
355+
};
356+
successMsg = `measurements between ${fromDate.format()} and ${toDate.format()}`;
357+
mode = 'range';
358+
}
359+
360+
const query = {
361+
sensor_id: sensorId
362+
};
363+
364+
if (createdAt) {
365+
query.createdAt = createdAt;
366+
}
367+
368+
let deleteUnsuccessful = false;
369+
370+
// delete measurements
371+
return Measurement.find(query)
372+
.remove()
373+
.exec()
374+
.then(function (removeResult) {
375+
if (removeResult && removeResult.result && removeResult.result.n === 0) {
376+
throw new ModelError('No matching measurements for specified query', { type: 'NotFoundError' });
377+
}
378+
// check for not ok deletion, this is not a failure but should generate a warning for the user
379+
if (removeResult.result.ok !== 1) {
380+
deleteUnsuccessful = true;
330381
}
331382

332-
return this.findById(id) // scope is used here
333-
.populate('sensor.lastMeasurement')
334-
.exec()
335-
.then(function (box) {
336-
const sensorIndex = box.sensors.findIndex(s => s._id.equals(sensorId));
337-
if (sensorIndex === -1) {
338-
throw new Error('SENSOR_NOT_FOUND');
339-
}
383+
let newLastMeasurementPromise = Promise.resolve();
384+
if (mode !== 'all') {
385+
newLastMeasurementPromise = Measurement.findLastMeasurementOfSensor(sensorId);
386+
}
340387

388+
return newLastMeasurementPromise
389+
.then(function (newLastMeasurement) {
341390
box.set(`sensors.${sensorIndex}.lastMeasurement`, newLastMeasurement);
342391

343392
return box.save();
344393
});
394+
})
395+
.then(function () {
396+
let responseMessage = `Successfully deleted ${successMsg} of sensor ${sensorId}`;
397+
398+
if (deleteUnsuccessful === true) {
399+
responseMessage = `${DELETE_MEASUREMENTS_UNSUCCESSFUL_ERROR} ${DELETE_MEASUREMENTS_CONTACT_ADMIN_MSG}`;
400+
}
401+
402+
return responseMessage;
345403
});
346-
};
347404

405+
};
348406

349407
/**
350408
* updates a boxes location at a given time, and performs housekeeping logic.

lib/models/measurement.js

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -53,40 +53,16 @@ measurementSchema.set('toJSON', {
5353
}
5454
});
5555

56-
measurementSchema.statics.deleteMeasurementsOfSensor = function deleteMeasurementsOfSensor (sensorId, createdAt) {
57-
const query = {
58-
sensor_id: sensorId
59-
};
60-
61-
if (createdAt) {
62-
query.createdAt = createdAt;
63-
}
64-
65-
// delete measurements
66-
return this.find(query)
67-
.remove()
68-
.exec()
69-
.then(function (removeResult) {
70-
const errors = [];
71-
// nothing removed -> lastMeasurement should not be updated
72-
if (removeResult && removeResult.result && removeResult.result.n === 0) {
73-
errors.push('NO_MATCHING_MEASUREMENTS');
74-
//throw new BadRequestError('no matching measurements for specified query');
75-
}
76-
// check for not ok deletion, this is not a failure but should generate a warning for the user
77-
if (removeResult.result.ok !== 1) {
78-
errors.push('DELETE_ERROR');
79-
}
80-
81-
return errors;
82-
});
83-
};
84-
8556
measurementSchema.statics.findLastMeasurementOfSensor = function findLastMeasurementOfSensor (sensorId) {
8657
return this.find({ sensor_id: sensorId })
8758
.sort({ createdAt: -1 })
8859
.limit(1)
89-
.exec();
60+
.exec()
61+
.then(function (lastMeasurement) {
62+
if (lastMeasurement && Array.isArray(lastMeasurement) && lastMeasurement.length !== 0) {
63+
return lastMeasurement[0];
64+
}
65+
});
9066
};
9167

9268
measurementSchema.statics.decodeMeasurements = function decodeMeasurements (measurements, { contentType = 'json', sensors }) {

0 commit comments

Comments
 (0)