const {DB, DEFAULT_QUERY_OPTIONS} = require('../constants');
const Manager = require("../../pdm-dsu-toolkit/managers/Manager");
const {Notification, Batch, Stock} = require('../model');
const getStockManager = require('./StockManager');
const {functionCallIterator} = require('../services').utils;
/**
* Batch Manager Class
*
* Manager Classes in this context should do the bridge between the controllers
* and the services exposing only the necessary api to the controllers while encapsulating <strong>all</strong> business logic.
*
* All Manager Classes should be singletons.
*
* This complete separation of concerts is very beneficial for 2 reasons:
* <ul>
* <li>Allows for testing since there's no browser dependent code (i think) since the DSUStorage can be 'mocked'</li>
* <li>Allows for different controllers access different business logic when necessary (while benefiting from the singleton behaviour)</li>
* </ul>
*
* @param {ParticipantManager} participantManager
* @param {function(err, Manager)} [callback] optional callback for when the assurance that the table has already been indexed is required.
* @class NotificationManager
* @extends Manager
* @memberOf Managers
*/
class NotificationManager extends Manager{
constructor(participantManager, callback) {
super(participantManager, DB.notifications, ['senderId', 'subject'], (err, manager) => {
if (err)
return callback ? callback(err) : console.log(err);
manager.registerMessageListener((message, cb) => {
manager.processMessageRecord(message, (err) => {
manager.refreshController(message.message);
cb(err);
});
});
manager.stockManager = manager.stockManager || getStockManager(participantManager);
if (callback)
callback(undefined, manager);
});
this.stockManager = this.stockManager || getStockManager(participantManager);
}
/**
* generates the db's key for the batch
* @param {string} senderId
* @param {string} subject
* @return {string}
* @private
*/
_genCompostKey(senderId, subject){
return `${senderId}-${subject}-${Date.now()}`;
}
/**
* Must wrap the entry in an object like:
* <pre>
* {
* index1: ...
* index2: ...
* value: item
* }
* </pre>
* so the DB can be queried by each of the indexes and still allow for lazy loading
* @param {string} key
* @param {Notification} item
* @param {string|object} record
* @return {object} the indexed object to be stored in the db
* @protected
* @override
*/
_indexItem(key, item, record){
return {
senderId: item.senderId,
subject: item.subject,
body: item.body
}
};
/**
* Processes the received messages, saves them to the the table and deletes the message
* @param {*} message
* @param {function(err)} callback
* @protected
* @override
*/
_processMessageRecord(message, callback) {
let self = this;
if (!message || typeof message !== 'object')
return callback(`Invalid Message: ${message} does not have a valid notification`);
const notification = new Notification(message);
const err = notification.validate()
if (err)
return callback(err);
const key = self._genCompostKey(notification.senderId, notification.subject);
self.insertRecord(key, notification, (err, record) => {
if (err)
return callback(err);
self._handleNotification(notification, (err) => {
if (err)
console.log(`Could not process notification`, err);
callback(undefined);
})
});
};
_handleBatch(body, callback){
const {gtin, batch} = body;
const {batchNumber, batchStatus, expiry} = batch;
const self = this;
self.stockManager.getOne(gtin, true, (err, stock) => {
if (err)
return callback(err);
const batch = stock.batches.find(b => b.batchNumber === batchNumber);
if (!batch)
return callback(`No stock of such batch... why were we notified of this??`);
if (batch.expiry !== expiry){
console.log(`Updating batch expiry to ${expiry}`);
batch.expiry = expiry;
}
if (batch.batchStatus !== batchStatus){
console.log(`Updating batch status to ${batchStatus}`);
batch.batchStatus = batchStatus;
}
self.stockManager.update(gtin, stock, (err) => {
if (err)
return callback(err);
self.stockManager.refreshController();
callback();
});
});
}
_handleNotification(notification, callback){
switch (notification.subject){
case DB.batches:
return this._handleBatch(notification.body, callback);
default:
return callback(`Cannot handle such notification - ${notification}`);
}
}
/**
* Sends a {@link Notification}
* @param {string} receiverId
* @param {Notification} notification
* @param {function(err, keySSI, string)} callback first keySSI if for the batch, the second for its' product dsu
* @override
*/
push(receiverId, notification, callback){
if (!notification.senderId)
notification.senderId = this.getIdentity().id;
this.sendMessage(receiverId, this.tableName, notification, err =>
this._messageCallback(err, `Notification sent to ${receiverId} regarding ${notification.subject}`));
callback(undefined);
}
pushToAll(receivers, notification, callback){
const self = this;
const func = function(receiver, callback){
self.push.call(self, receiver, notification, callback)
}
functionCallIterator(func, receivers, (err, ...results) => {
if (err)
return callback(err);
console.log(`All notifications regarding ${notification.subject} sent`);
callback(undefined, ...results)
})
}
/**
* Creates a {@link Notification}
* @param {Notification} notification
* @param {function(err, keySSI, string)} callback first keySSI if for the batch, the second for its' product dsu
* @override
*/
create(notification, callback) {
callback(`Notification cannot be created`); }
/**
* reads the specific Notification
*
* @param {string} key
* @param {boolean} [readDSU] defaults to true. decides if the manager loads and reads from the dsu or not
* @param {function(err, Notification|KeySSI, Archive)} callback returns the batch if readDSU, the keySSI otherwise
* @override
*/
getOne(key, readDSU, callback){
if (!callback){
callback = readDSU;
readDSU = true;
}
let self = this;
self.getRecord(key, (err, notification) => {
if (err)
return self._err(`Could not load record with key ${key} on table ${self._getTableName()}`, err, callback);
callback(undefined, new Notification(notification));
});
}
/**
* Lists all registered items according to query options provided
* @param {boolean} [readDSU] defaults to true. decides if the manager loads and reads from the dsu's {@link INFO_PATH} or not
* @param {object} [options] query options. defaults to {@link DEFAULT_QUERY_OPTIONS}
* @param {function(err, object[])} callback
* @override
*/
getAll(readDSU, options, callback){
const defaultOptions = () => Object.assign({}, DEFAULT_QUERY_OPTIONS, {
query: ['__timestamp > 0']
});
if (!callback){
if (!options){
callback = readDSU;
options = defaultOptions();
readDSU = true;
}
if (typeof readDSU === 'boolean'){
callback = options;
options = defaultOptions();
}
if (typeof readDSU === 'object'){
callback = options;
options = readDSU;
readDSU = true;
}
}
options = options || defaultOptions();
let self = this;
self.query(options.query, options.sort, options.limit, (err, records) => {
if (err)
return self._err(`Could not perform query`, err, callback);
if (!readDSU)
return callback(undefined, records.map(r => r.pk));
callback(undefined, records.map(r => new Notification(r)));
});
}
/**
* Removes a Notification from the list (does not delete/invalidate DSU, simply 'forgets' the reference)
* @param {string|number} key
* @param {function(err)} callback
* @override
*/
remove(key, callback) {
super.remove(key, callback);
}
/**
* updates a Batch from the list
* @param {string|number} gtin
* @param {Notification} newBatch
* @param {function(err, Batch, Archive)} callback
* @override
*/
update(gtin, newBatch, callback){
return callback(`Notification cannot be updated`);
}
}
/**
* @param {BaseManager} participantManager
* @param {function(err, Manager)} [callback] optional callback for when the assurance that the table has already been indexed is required.
* @returns {NotificationManager}
* @memberOf Managers
*/
const getNotificationManager = function (participantManager, callback) {
let manager;
try {
manager = participantManager.getManager(NotificationManager);
if (callback)
return callback(undefined, manager);
} catch (e){
manager = new NotificationManager(participantManager, callback);
}
return manager;
}
module.exports = getNotificationManager;
Source