Martin Donnelly 57bd6c8e6a init
2018-06-24 21:15:03 +01:00

549 lines
17 KiB
JavaScript

/**
* Controller to manage the addresses functions
*/
'use strict';
var _ = require('lodash');
var Q = require('q');
var httpStatus = require('http-status-codes');
var mongodb = require('mongodb');
var debug = require('debug')('webconsole-api:controllers:addresses');
var mainDB = require(global.pathPrefix + 'mainDB.js');
var swaggerUtils = require(global.pathPrefix + '../utils/swaggerUtils.js');
var anon = require(global.pathPrefix + '../utils/anon.js');
var config = require(global.configFile);
module.exports = {
getAddresses: getAddresses,
getAddress: getAddress,
addAddress: addAddress,
deleteAddress: deleteAddress
};
/**
* Get the address list
*
* @param {Object} req - Express request object
* @param {Object} res - Express response object
*/
function getAddresses(req, res) {
//
// Get the query params from the request and the session
//
var clientID = req.session.data.clientID;
var limit = req.swagger.params.limit.value;
var skip = req.swagger.params.skip.value;
var minDate = req.swagger.params.minDate.value;
var maxDate = req.swagger.params.maxDate.value;
var query = {
ClientID: clientID
};
//
// Add date limits if included
//
if (minDate || maxDate) {
query.LastUpdate = {};
if (minDate) {
query.LastUpdate.$gte = minDate;
}
if (maxDate) {
query.LastUpdate.$lte = maxDate;
}
}
//
// Define the projection based on the Swagger definition
//
var projection = swaggerUtils.swaggerToMongoProjection(
req.swagger.operation,
true // include _id so we know how to select an individual address.
);
//
// Make the query. Note limit & skip have defaults defined in the
// swagger definition, so will always exist even if not requested
//
mainDB.collectionAddresses.find(query, projection)
.skip(skip)
.limit(limit)
.sort({LastUpdate: -1}) // Hard-coded reverse sort by time
.toArray(function(err, items) {
if (err) {
debug('- failed to getAddresses', err);
res.status(httpStatus.BAD_GATEWAY).json({
code: 377,
info: 'Database offline'
});
} else {
//
// Anonymise all the addresses before sending them out
//
_.forEach(
items,
function(value, index, collection) {
anon.anonymiseAddress(value);
//
// Rename _id to AddressID
//
value.AddressID = value._id;
delete value._id;
//
// Rename PhoneNumber to PhoneNumberAnon.
// (we anonymise the response, but obviously not the set)
//
value.PhoneNumberAnon = value.PhoneNumber;
delete value.PhoneNumber;
});
//
// Null any nullable fields
//
swaggerUtils.getAndApplyNullableFields(req.swagger.operation, items);
res.status(httpStatus.OK).json(items);
}
});
}
/**
* Gets the address details for a specific address.
*
* @param {Object} req - Express request object
* @param {Object} res - Express response object
*/
function getAddress(req, res) {
//
// Get the query params from the request and the session
//
var clientID = req.session.data.clientID;
var addressId = req.swagger.params.objectId.value;
//
// Build the query. The limits are:
// - Must match the id of the item we are looking for
// - Current user must be the address owner (to protect against Insecure
// Direct Object References).
//
var query = {
_id: mongodb.ObjectID(addressId),
ClientID: clientID
};
//
// Define the fields based on the Swagger definition.
//
var projection = swaggerUtils.swaggerToMongoProjection(
req.swagger.operation,
true
);
//
// Build the options to encapsulate the projection
//
var options = {
fields: projection,
comment: 'WebConsole:getAddress' // For profiler logs use
};
//
// Make the request
//
mainDB.findOneObject(mainDB.collectionAddresses, query, options, false,
function(err, item) {
if (err) {
debug('- failed to getAddress', err);
res.status(httpStatus.BAD_GATEWAY).json({
code: 377,
info: 'Database offline'
});
} else if (item === null) {
//
// Nothing found
//
res.status(httpStatus.NOT_FOUND).json({
code: 393,
info: 'Not found'
});
} else {
//
// Anonymise all the address before sending them out
//
anon.anonymiseAddress(item);
//
// Rename PhoneNumber to PhoneNumberAnon.
// (we anonymise the response, but obviously not the set)
//
item.PhoneNumberAnon = item.PhoneNumber;
delete item.PhoneNumber;
//
// Rename _id to AddressId
//
item.AddressID = item._id;
delete item._id;
//
// Null any nullable fields
//
swaggerUtils.getAndApplyNullableFields(req.swagger.operation, item);
res.status(httpStatus.OK).json(item);
}
});
}
/**
* Adds a new address
*
* @param {Object} req - Express request object
* @param {Object} res - Express response object
*/
function addAddress(req, res) {
var clientID = req.session.data.clientID;
var validatedBody = req.swagger.params.body.value;
//
// Step 1: Find all existing addresses
//
var findQuery = {
ClientID: clientID
};
var findPromise = Q.ninvoke(mainDB.collectionAddresses, 'find', findQuery)
.ninvoke('toArray');
//
// Step 2: Check existing addresses and confirm that we:
// 1) don't have too many already
// 2) don't already have one with this description
//
const MAX_ADDR = 'BRIDGE: MAX ADDRESSES';
const DESC_IN_USE = 'BRIDGE: DESCRIPTION ALREADY IN USE';
var checkPromise = findPromise.then(function(results) {
if (results.length > config.maxAddresses) {
return Q.reject({name: MAX_ADDR});
}
for (var i = 0; i < results.length; ++i) {
if (results[i].AddressDescription === validatedBody.AddressDescription) {
return Q.reject({name: DESC_IN_USE});
}
}
return true;
});
//
// Step 3: Add the new address
//
var timestamp = new Date();
var newAddress = mainDB.blankAddress();
var required = ['AddressDescription', 'Address1', 'Town', 'PostCode', 'Country'];
var optional = ['BuildingNameFlat', 'Address2', 'County', 'PhoneNumber'];
var idx = 0;
for (idx = 0; idx < required.length; ++idx) {
newAddress[required[idx]] = validatedBody[required[idx]];
}
for (idx = 0; idx < optional.length; ++idx) {
var key = optional[idx];
if (validatedBody.hasOwnProperty(key) && validatedBody[key] !== null) {
newAddress[key] = validatedBody[key];
} else {
newAddress[key] = '';
}
}
newAddress.ClientID = clientID;
newAddress.DateAdded = timestamp;
newAddress.LastUpdate = timestamp;
newAddress.LastVersion = 1;
var addPromise = checkPromise.then(function() {
return Q.nfcall(
mainDB.addObject,
mainDB.collectionAddresses,
newAddress,
undefined,
false
);
});
//
// Step 4. Run all the promises and wait for the result
//
Q.all([findPromise, checkPromise, addPromise])
.then(function success(result) {
//
// Succeeded
// The _id is in result[2][0] because:
// Result is an array of results from the 3 promises in .all()
// Thus result[2] is the result of addPromise
// This is an addObject() which returns an array itself. But we
// are only adding one so we know it is result[2][0].
//
res.status(201).json({
AddressID: result[2][0]._id
});
})
.catch(function fail(error) {
debug('-- error adding address: ', error);
if (
error &&
error.hasOwnProperty('name')
) {
switch (error.name) {
case MAX_ADDR:
//
// User already has max addresses
//
res.status(httpStatus.CONFLICT).json({
code: 380,
info: 'Max addresses reached'
});
break;
case DESC_IN_USE:
//
// Device is barred
//
res.status(httpStatus.CONFLICT).json({
code: 381,
info: 'An address with the same description already exists'
});
break;
case 'MongoError':
//
// Mongo Error
//
res.status(httpStatus.BAD_GATEWAY).json({
code: 365,
info: 'Database Unavailable'
});
break;
default:
//
// Unknown error
//
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({
code: -1,
info: 'Unexpected error'
});
break;
}
} else {
//
// Unknown error
//
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({
code: -1,
info: 'Unexpected error'
});
}
})
.done(); // Catch all
}
/**
* Deletes a Address such that it can no longer be used in the system.
* What it actually does is copies the document to the AddressArchive collection
* then deletes it from the Addresses collection.
*
* @param {Object} req - Express request object
* @param {Object} res - Express response object
*/
function deleteAddress(req, res) {
//
// Get the query params from the request and the session
//
var clientID = req.session.data.clientID;
var addressId = req.swagger.params.objectId.value;
//
// Find the Address we want to delete
//
var query = {
_id: mongodb.ObjectID(addressId), // The Address to update
ClientID: clientID // Must be *my* Address
};
var options = {
comment: 'WebConsole: find for deleteAddress'
};
//
// Step 1: Find the Address. This includes checking ownership by the
// the user with the current session.
//
var findOriginalPromise = Q.nfcall(
mainDB.findOneObject,
mainDB.collectionAddresses,
query,
options,
false
);
//
// Step 2: Find any accounts that are using it
//
var findInUseQuery = {
ClientID: clientID,
BillingAddress: addressId
};
var findInUsePromise = Q.ninvoke(
mainDB.collectionAddresses,
'find',
findInUseQuery)
.ninvoke('toArray');
//
// Step 3: Check that we didn't find any accounts using it
// We can only delete addresses that are not currently in use.
//
const IN_USE = 'BRIDGE: ADDRESS IN USE';
var ensureNotInUsePromise = findInUsePromise.then(function(results) {
if (results && results.length > 0) {
return Q.reject({name: IN_USE});
}
return true;
});
//
// Step 4: Once we've found that we own it and it's not in use, we can
// go and back it up
//
const NOT_FOUND = 'BRIDGE: NOT FOUND';
var oldId = null;
var addArchivePromise =
Q.all([findOriginalPromise, findInUsePromise, ensureNotInUsePromise])
.then(function(results) {
var item = results[0]; // The find original result is the first one.
//
// DB query ran ok, but need to check if there were any results
//
if (!item) {
return Q.reject({name: NOT_FOUND});
} else {
var insertObject = _.clone(item);
oldId = item._id;
insertObject.AddressID = item._id.toString(); // Backup the old index
delete insertObject._id; // Then delete it to get a new one
//
// Set LastUpdate to the current date
//
insertObject.LastUpdate = new Date();
//
// And insert into the archive
//
return Q.nfcall(
mainDB.addObject,
mainDB.collectionAddressArchive,
insertObject,
undefined,
false
);
}
});
//
// Step 5: Delete the original
//
const NOT_ARCHIVED = 'BRIDGE: NOT ARCHIVED';
var deleteOriginalPromise = addArchivePromise.then(function(result) {
if (!_.isObject(result)) {
return Q.reject({name: NOT_ARCHIVED});
} else {
debug('Deleting orginal: ', oldId);
var deleteQuery = {
_id: oldId
};
return Q.nfcall(
mainDB.removeObject,
mainDB.collectionAddresses,
deleteQuery,
undefined,
false
);
}
});
//
// Run them all in sequence and check the result
//
Q.all([
findOriginalPromise, findInUsePromise, ensureNotInUsePromise,
addArchivePromise, deleteOriginalPromise
])
.then(function success() {
//
// Succeeded
//
res.status(200).json();
})
.catch(function fail(error) {
debug('-- error deleting Address: ', error);
if (
error &&
error.hasOwnProperty('name')
) {
switch (error.name) {
case NOT_FOUND:
//
// Address not found in the DB (or doesn't belong to
// this user)
//
res.status(httpStatus.NOT_FOUND).json({
code: 388,
info: 'Address not found'
});
break;
case NOT_ARCHIVED:
//
// Item failed to archive
//
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({
code: 389,
info: 'Failed to archive Address'
});
break;
case 'MongoError':
//
// Mongo Error
//
res.status(httpStatus.BAD_GATEWAY).json({
code: 385,
info: 'Database Unavailable'
});
break;
default:
//
// Unknown error
//
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({
code: -1,
info: 'Unexpected error'
});
break;
}
} else {
//
// Unknown error
//
res.status(httpStatus.INTERNAL_SERVER_ERROR).json({
code: -1,
info: 'Unexpected error'
});
}
})
.done(); // Catch all
}