549 lines
17 KiB
JavaScript
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
|
|
}
|