mirror of
https://github.com/bckelley/cgm-remote-monitor.git
synced 2026-08-24 03:14:12 -05:00
* APIv3: isolating documents from tests (not allowing clashes of calculated identifiers) * removing unused async keyword * fixing api v3 swagger and moving it to /api3-docs * APIv3: adding cachedCollection stub of cachedCollection storage implementation * APIv3: mongo cachedCollection storage implementation * APIv3: testing and debugging cache updates * APIv3: more testing on cache updates * APIv3: fixing bad async functions * APIv3: finishing cache invalidation tests Co-authored-by: Petr Ondrusek <petr.ondrusek@seznam.cz> Co-authored-by: Petr Ondrůšek <petr.ondrusek@okin.eu> Co-authored-by: Sulka Haro <sulka@sulka.net>
154 lines
3.8 KiB
JavaScript
154 lines
3.8 KiB
JavaScript
'use strict';
|
|
|
|
const dateTools = require('../../shared/dateTools')
|
|
, renderer = require('../../shared/renderer')
|
|
, apiConst = require('../../const.json')
|
|
, security = require('../../security')
|
|
, opTools = require('../../shared/operationTools')
|
|
, FieldsProjector = require('../../shared/fieldsProjector')
|
|
, _ = require('lodash')
|
|
;
|
|
|
|
/**
|
|
* HISTORY: Retrieves incremental changes since timestamp
|
|
*/
|
|
async function history (opCtx, fieldsProjector) {
|
|
|
|
const { req, res, col } = opCtx;
|
|
|
|
let filter = parseFilter(opCtx)
|
|
, limit = col.parseLimit(req, res)
|
|
, projection = fieldsProjector.storageProjection()
|
|
, sort = prepareSort()
|
|
, skip = 0
|
|
, onlyValid = false
|
|
, logicalOperator = 'or'
|
|
;
|
|
|
|
if (filter !== null && limit !== null && projection !== null) {
|
|
|
|
const result = await col.storage.findMany({ filter
|
|
, sort
|
|
, limit
|
|
, skip
|
|
, projection
|
|
, onlyValid
|
|
, logicalOperator });
|
|
|
|
if (!result)
|
|
throw new Error('empty result');
|
|
|
|
if (result.length === 0) {
|
|
return res.status(apiConst.HTTP.NO_CONTENT).end();
|
|
}
|
|
|
|
_.each(result, col.resolveDates);
|
|
|
|
const srvModifiedValues = _.map(result, function mapSrvModified (item) {
|
|
return item.srvModified;
|
|
})
|
|
, maxSrvModified = _.max(srvModifiedValues);
|
|
|
|
res.setHeader('Last-Modified', (new Date(maxSrvModified)).toUTCString());
|
|
res.setHeader('ETag', 'W/"' + maxSrvModified + '"');
|
|
|
|
_.each(result, fieldsProjector.applyProjection);
|
|
|
|
res.status(apiConst.HTTP.OK);
|
|
renderer.render(res, result);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Parse history filtering criteria from Last-Modified header
|
|
*/
|
|
function parseFilter (opCtx) {
|
|
|
|
const { req, res } = opCtx;
|
|
|
|
let lastModified = null
|
|
, lastModifiedParam = req.params.lastModified
|
|
, operator = null;
|
|
|
|
if (lastModifiedParam) {
|
|
|
|
// using param in URL as a source of timestamp
|
|
const m = dateTools.parseToMoment(lastModifiedParam);
|
|
|
|
if (m === null || !m.isValid()) {
|
|
opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_LAST_MODIFIED);
|
|
return null;
|
|
}
|
|
|
|
lastModified = m.toDate();
|
|
operator = 'gt';
|
|
}
|
|
else {
|
|
// using request HTTP header as a source of timestamp
|
|
const lastModifiedHeader = req.get('Last-Modified');
|
|
if (!lastModifiedHeader) {
|
|
opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_LAST_MODIFIED);
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
lastModified = dateTools.floorSeconds(new Date(lastModifiedHeader));
|
|
} catch (err) {
|
|
opTools.sendJSONStatus(res, apiConst.HTTP.BAD_REQUEST, apiConst.MSG.HTTP_400_BAD_LAST_MODIFIED);
|
|
return null;
|
|
}
|
|
operator = 'gte';
|
|
}
|
|
|
|
return [
|
|
{ field: 'srvModified', operator: operator, value: lastModified.getTime() },
|
|
{ field: 'created_at', operator: operator, value: lastModified.toISOString() },
|
|
{ field: 'date', operator: operator, value: lastModified.getTime() }
|
|
];
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Prepare sorting for storage query
|
|
*/
|
|
function prepareSort () {
|
|
return {
|
|
srvModified: 1,
|
|
created_at: 1,
|
|
date: 1
|
|
};
|
|
}
|
|
|
|
|
|
function historyOperation (ctx, env, app, col) {
|
|
|
|
return async function operation (req, res) {
|
|
|
|
const opCtx = { app, ctx, env, col, req, res };
|
|
|
|
try {
|
|
opCtx.auth = await security.authenticate(opCtx);
|
|
|
|
if (col.colName === 'settings') {
|
|
await security.demandPermission(opCtx, `api:${col.colName}:admin`);
|
|
} else {
|
|
await security.demandPermission(opCtx, `api:${col.colName}:read`);
|
|
}
|
|
|
|
const fieldsProjector = new FieldsProjector(req.query.fields);
|
|
|
|
await history(opCtx, fieldsProjector);
|
|
|
|
} catch (err) {
|
|
console.error(err);
|
|
if (!res.headersSent) {
|
|
return opTools.sendJSONStatus(res, apiConst.HTTP.INTERNAL_ERROR, apiConst.MSG.STORAGE_ERROR);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
module.exports = historyOperation;
|