UNPKG

auction

Version:
660 lines (552 loc) 17.2 kB
'use strict'; /** * Module dependencies. */ Object.defineProperty(exports, '__esModule', { value: true }); var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); var _get = function get(_x11, _x12, _x13) { var _again = true; _function: while (_again) { var object = _x11, property = _x12, receiver = _x13; desc = parent = getter = undefined; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x11 = parent; _x12 = property; _x13 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var _errors = require('./errors'); var _eventemitter3 = require('eventemitter3'); var _eventemitter32 = _interopRequireDefault(_eventemitter3); var _predefine = require('predefine'); var _predefine2 = _interopRequireDefault(_predefine); var _debug = require('debug'); var _debug2 = _interopRequireDefault(_debug); var _bid = require('./bid'); var _bid2 = _interopRequireDefault(_bid); var _lodash = require('lodash'); var _lodash2 = _interopRequireDefault(_lodash); /** * Module variables. */ var debug = (0, _debug2['default'])('auction'); var noop = function noop() {}; var READ_ONLY_FIELDS = { authorization: true, auctionStatus: true, currentPrice: true, _events: true, outBid: true, bestBid: true, started: true, ended: true, bids: true }; var _default = (function (_Emitter) { _inherits(_default, _Emitter); /** * Auction constructor method. * * @param {Object} data * @param {Function} fn * @return {Auction} this * @api private */ function _default(data, fn) { _classCallCheck(this, _default); _get(Object.getPrototypeOf(_default.prototype), 'constructor', this).call(this); this.writable = (0, _predefine2['default'])(this, _predefine2['default'].WRITABLE); this.readable = (0, _predefine2['default'])(this, _predefine2['default'].READABLE); this.predefineProperties(); this.init(data, fn); } /** * Initialize `auction` object. * * @param {Object} data * @param {Function} fn * @return {Auction} this * @api private */ _createClass(_default, [{ key: 'init', value: function init(data, fn) { if (data === undefined) data = {}; var error = this.check(data); if (error) { if (!this.listeners('error', true) && !fn) throw error; if (fn) setImmediate(fn.bind(null, error)); this.emit('error', error); return this; } this.id = data.id; this.reset(); this.mergeProperties(data); this.normalize(data); debug('auction initialized %j', this.data); if (fn) setImmediate(fn.bind(null, null, this.data)); return this; } /** * Set predefined properties. * * @api private */ }, { key: 'predefineProperties', value: function predefineProperties() { this.writable('_events', {}); } /** * Merge 'auction' properties. * * @param {Object} data * @type {Auction} this * @api private */ }, { key: 'mergeProperties', value: function mergeProperties(data) { for (var key in data) { if ('undefined' !== typeof data[key]) { this.mergeProperty(key, data[key]); } } } /** * Merge 'auction' property. * * @param {String} key * @param {Mixed} value * @type {Auction} this * @api private */ }, { key: 'mergeProperty', value: function mergeProperty(key, value) { if (READ_ONLY_FIELDS[key]) return; this[key] = value; } /** * Merge data to auction. * * @return {Object} data * @return {Boolean} * @api private */ }, { key: 'merge', value: function merge(data) { this.mergeProperties(this.deff(data)); return this.data; } /** * Diff auction data. * * @return {Object} data * @return {Object} diff * @api private */ }, { key: 'diff', value: function diff(data) { var res = {}; for (var key in data) { if (_lodash2['default'].isEqual(this[key], data[key])) continue; if (!Order.READ_ONLY_FIELDS[key]) { res[key] = data[key]; } } return res; } /** * Validate auction data. * * @param {Object} data * @return {Error|Boolean} * @api private */ }, { key: 'validate', value: function validate(data) { // Extend this method return false; } /** * Normalize auction data. * * @param {Object} data * @api private */ }, { key: 'normalize', value: function normalize(data) { // Extend this method return data; } /** * Check validation data. * * @param {Object} data * @return {Error|Undefined} * @api private */ }, { key: 'check', value: function check(data) { var message = null; if (!data.id) { message = 'Invalid auction ID.'; } else if (!_lodash2['default'].isNumber(data.openPrice)) { message = 'Invalid open price.'; } else if ('minPrice' in data && !_lodash2['default'].isNumber(data.minPrice)) { message = 'Invalid minimum price.'; } else { message = this.validate(data); } if (message) { var error = new _errors.AuctionError(message); debug('auction error %s', message); this.emit('error', error); return error; } } /** * Update `auction`. * * @param {Object} data * @param {Function} fn * @return {Order} this * @api public */ }, { key: 'update', value: function update(data) { var fn = arguments.length <= 1 || arguments[1] === undefined ? noop : arguments[1]; data = this.normalize(data); var error = this.validate(data); if (error) { error = new OrderError(error); this.emit('error', error); return fn(error); } fn(null, this.merge(data)); } /** * Destroy `auction`. * * @param {Function} fn * @return {Auction} this * @api public */ }, { key: 'destroy', value: function destroy() { var fn = arguments.length <= 0 || arguments[0] === undefined ? noop : arguments[0]; this.destroyed = true; this.removeAllListeners(); fn(); } /** * Reset `auction`. * * @return {Auction} this * @api private */ }, { key: 'reset', value: function reset() { this.bids = []; this.outBid = {}; this.bestBid = {}; this.started = {}; this.ended = {}; this.saleId = null; this.saleDate = null; this.minPrice = 0; this.openPrice = 0; this.increment = 1; this.minIncrement = 1; this.destroyed = false; this.initialized = true; this.auctionStatus = 'created'; return this; } /** * Start `auction`. * * @param {Object} data * @param {Function} fn * @return {Auction} this * @api public */ }, { key: 'start', value: function start() { var data = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; var fn = arguments.length <= 1 || arguments[1] === undefined ? noop : arguments[1]; var error = null; var agentId = data.agentId; var status = this.auctionStatus; this.openPrice = data.openPrice || this.openPrice; if (!agentId) { error = 'Invalid agent.'; } else if ('ending' === status) { error = 'Auction is ending.'; } else if ('ended' === status) { error = 'Auction already ended.'; } else if ('started' === status) { error = 'Auction already started.'; } else if (!this.openPrice || !_lodash2['default'].isNumber(this.openPrice)) { error = 'Invalid opening price.'; } if (error) { debug('auction %d error %s', this.id, error); error = new _errors.AuctionError(error); this.emit('error', error); return setImmediate(fn.bind(null, error)); } this.auctionStatus = 'started'; this.started = { agentId: agentId, timestamp: Date.now() }; data = this.data; debug('started auction %d', this.id, data); this.emit('started', data); setImmediate(function () { fn(null, data); }); return this; } /** * Place `bid`. * * @param {Object} data * @param {Function} fn * @return {Auction} this * @api public */ }, { key: 'bid', value: function bid() { var data = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; var fn = arguments.length <= 1 || arguments[1] === undefined ? noop : arguments[1]; var bid = null; var error = null; var id = this.id; var bestBid = this.bestBid; var openPrice = this.openPrice; var agentId = data.agentId; var plus = this.increment; var status = this.auctionStatus; var omit = ['auctionId', 'saleId']; // Ensure that the first bid is always openPrice + 1 if (!this.bids.length) { plus = this.minIncrement; } bestBid = this.bestBid = _lodash2['default'].omit(this.bestBid, omit); debug('creating bid %j', data); try { bid = new _bid2['default']({ price: data.price, auctionId: this.id, agentId: data.agentId, saleId: this.saleId }); } catch (e) { debug('auction ' + id + ' error ' + e.messsage); return process.nextTick(fn.bind(null, e)); } debug('placing bid ' + bid.id); if (!agentId) { error = 'Invalid agent.'; } else if ('object' !== typeof bid) { error = 'Invalid bid.'; } else if ('function' !== typeof bid.place) { error = 'Invalid bid object.'; } else if ('created' === status) { error = 'Auction not started.'; } else if ('ending' === status && this.ending && this.ending.agentId !== agentId) { error = 'Auction has ended, waiting for Auctioneer to confirm and end auction.'; } else if ('ended' === status) { error = 'Auction already ended.'; } else if (bid.price < bestBid.price + plus) { error = 'Bid price ' + bid.price + ' must be ' + plus + ' higher than the current bid price $' + bestBid.price + '.'; } else if (bid.price <= this.openPrice) { error = 'Bid price ' + bid.price + ' must be at least ' + plus + ' higher than the current bid price $' + openPrice + '.'; } else if (bestBid.price > bid.price) { error = 'Invalid bid price'; } if (!error) { bid.accept(); this.outBid = bestBid; this.bestBid = _lodash2['default'].omit(bid.data, omit); this.bids.push(bid); debug('saving bid ' + bid.id + ' to auction bid list'); } else { bid.reject(error); } data = this.data; if (error) { debug('auction %d error %s', this.id, error); error = new _errors.AuctionError(error); this.emit('error', error); return setImmediate(fn.bind(null, error)); } debug('bid placed ' + bid.data); this.emit('changed', data); setImmediate(function () { fn(null, bid.data); }); return this; } /** * Ending `auction`. * * @param {Object} data * @param {Function} fn * @return {Auction} this * @api public */ }, { key: 'ending', value: function ending() { var data = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; var fn = arguments.length <= 1 || arguments[1] === undefined ? noop : arguments[1]; var error = null; var agentId = data.agentId; var status = this.auctionStatus; if (!agentId) { error = 'Invalid agent.'; } else if ('ending' === status) { error = 'Auction already ending.'; } else if ('ended' === status) { error = 'Auction already ended.'; } else if ('started' !== status) { error = 'Auction not started.'; } if (error) { debug('auction %d error %s', this.id, error); error = new _errors.AuctionError(error); this.emit('error', error); return setImmediate(fn.bind(null, error)); } this.auctionStatus = 'ending'; this.isending = { agentId: agentId, timestamp: Date.now() }; data = this.data; debug('ending auction %d', this.id, data); this.emit('ending', data); setImmediate(function () { fn(null, data); }); return this; } /** * End `auction`. * * @param {Object} data * @param {Function} fn * @return {Auction} this * @api public */ }, { key: 'end', value: function end() { var data = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; var fn = arguments.length <= 1 || arguments[1] === undefined ? noop : arguments[1]; var error = null; var agentId = data.agentId; var status = this.auctionStatus; if (!agentId) { error = 'Invalid agent.'; } else if ('ended' === status) { error = 'Auction already ended.'; } else if ('started' !== status && 'ending' !== status) { error = 'Auction not started.'; } if (error) { debug('auction %d error %s', this.id, error); error = new _errors.AuctionError(error); this.emit('error', error); return setImmediate(fn.bind(null, error)); } this.auctionStatus = 'ended'; this.ended = { agentId: agentId, timestamp: Date.now() }; data = this.data; debug('ended auction %d', this.id, data); this.emit('ended', data); setImmediate(function () { fn(null, data); }); return this; } /** * Method to extend object. * * @param {Function} fn * @param {Object} options * @return {Auction} this * @api public */ }, { key: 'use', value: function use(fn, options) { fn(this, options); return this; } /** * Lazy get bid data. * * @type {Object} * @api public */ }, { key: 'currentPrice', get: function get() { return _lodash2['default'].isEmpty(this.bestBid) ? this.openPrice : this.bestBid.price; } /** * Lazy get auctioneer. * * @type {Object} * @api public */ }, { key: 'auctioneer', get: function get() { this.started.agentId; } /** * Lazy get bid data. * * @type {Object} * @api public */ }, { key: 'data', get: function get() { return { id: this.id, agents: this.agents, auctioneer: this.auctioneer, started: this.started, ended: this.ended, saleId: this.saleId, saleDate: this.saleDate, bids: this.bids, outBid: this.outBid, bestBid: this.bestBid, minPrice: this.minPrice, openPrice: this.openPrice, currentPrice: this.currentPrice, increment: this.increment, auctionStatus: this.auctionStatus }; } }]); return _default; })(_eventemitter32['default']); exports['default'] = _default; module.exports = exports['default'];