UNPKG

@fabrix/spool-cart

Version:

Spool - eCommerce Spool for Fabrix

661 lines (660 loc) 23.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const common_1 = require("@fabrix/fabrix/dist/common"); const _ = require("lodash"); const errors_1 = require("@fabrix/fabrix/dist/errors"); const address_4 = require("../../schemas/address"); const joi = require("joi"); const sharp = require("sharp"); const geolib = require("geolib"); const currencyFormatter = require("currency-formatter"); const removeMd = require("remove-markdown"); const stripTags = require("striptags"); const request = require("request"); const initialize_1 = require("../../middleware/initialize"); const authenticate_1 = require("../../middleware/authenticate"); const cart_1 = require("../../middleware/cart"); const customer_1 = require("../../middleware/customer"); const shop_1 = require("../../middleware/shop"); class ProxyCartService extends common_1.FabrixService { constructor(app) { super(app); this._key = 'cart'; this.initialize = initialize_1.initialize; this.authenticate = authenticate_1.authenticate; this.cart = cart_1.cart; this.customer = customer_1.customer; this.shop = shop_1.shop; } notifyAdmins(preNotification, options = {}) { const User = this.app.models.User; if (!preNotification) { return Promise.reject('No Notification to send to Admins'); } return User.findAll({ include: [ { model: this.app.models['Role'].instance, as: 'roles', where: { name: 'admin' } } ] }) .then(users => { if (!users || users.length === 0) { throw new Error('No Admins to send emails to'); } return this.app.services.NotificationService.create(preNotification, users, { transaction: options.transaction || null }) .then(notes => { this.app.log.debug('NOTIFY ADMINS', users.map(u => u.id), preNotification.send_email, notes.users.map(u => u.id)); return notes; }); }); } downloadImage(url) { return new Promise((resolve, reject) => { const req = request.defaults({ encoding: null }); req.get(url, (err, res, body) => { if (err) { this.app.log.error(err); return reject(err); } return resolve(body); }); }); } buildImages(imageUrl, options) { return new Promise((resolve, reject) => { const images = { full: imageUrl, thumbnail: imageUrl, small: imageUrl, medium: imageUrl, large: imageUrl }; let buffer; this.downloadImage(imageUrl) .then(resBuffer => { buffer = resBuffer; return sharp(buffer) .resize(200) .toBuffer(); }) .then(thumbnailBuffer => { return this.uploadImage(thumbnailBuffer, images.thumbnail); }) .then(thumbnail => { images.thumbnail = thumbnail.url; return sharp(buffer) .resize(300) .toBuffer(); }) .then(smallBuffer => { return this.uploadImage(smallBuffer, images.small); }) .then(small => { images.small = small.url; return sharp(buffer) .resize(400) .toBuffer(); }) .then(mediumBuffer => { return this.uploadImage(mediumBuffer, images.medium); }) .then(medium => { images.medium = medium.url; return sharp(buffer) .resize(500) .toBuffer(); }) .then(largeBuffer => { return this.uploadImage(largeBuffer, images.large); }) .then((large) => { images.large = large.url; return resolve(images); }) .catch((err) => { this.app.log.error(err); return resolve(images); }); }); } uploadImage(image, orgUrl) { return this.app.services.DataStoreGenericService.upload(image) .catch(err => { return { url: orgUrl }; }); } handle(text) { if (!text) { return null; } return text.toString().trim() .replace(/\s+/g, '-') .replace(/&/g, '-and-') .replace(/[^\w\-]+/g, '') .replace(/\-\-+/g, '-') .toLowerCase() .substring(0, 255); } splitHandle(text) { if (!text) { return null; } return text.toString().trim() .replace(/\s+/g, '-') .replace(/&/g, '-and-') .replace(/[^\w:\-]+/g, '') .replace(/\-\-+/g, '-') .toLowerCase(); } sku(text) { if (!text) { return null; } text = text.toString().trim() .replace(/[^\w:\-]+/g, ''); return removeMd(stripTags(text)).toString(); } title(text) { if (!text) { return null; } text = text.toString().trim(); return removeMd(stripTags(text)).toString().substring(0, 255); } name(text) { if (!text) { return null; } text = text.toString().trim(); return removeMd(stripTags(text)).toString().toLowerCase().substring(0, 255); } description(text) { if (!text) { return null; } text = text.toString().trim(); return removeMd(stripTags(text)).toString(); } ouncesToGrams(ounces) { return ounces * 28.3495231; } poundsToGrams(pounds) { return pounds * 16 * 28.3495231; } kilogramsToGrams(kilogram) { return kilogram / 1000; } resolveConversion(weight, weightUnit) { switch (weightUnit) { case 'kg': return this.kilogramsToGrams(weight); case 'oz': return this.ouncesToGrams(weight); case 'lb': return this.poundsToGrams(weight); default: return weight; } } isJson(str) { try { JSON.parse(str); } catch (e) { return false; } return true; } formatCurrency(num, currency) { currency = currency || this.app.config.get('cart.default_currency'); return currencyFormatter.format(num / 100, { code: currency.toUpperCase() }); } validateAddress(address) { try { joi.validate(address, address_4.address); } catch (err) { throw new errors_1.ValidationError(err); } try { address = this.normalizeAddress(address); } catch (err) { throw new Error(err); } return address; } normalizeAddress(address = {}) { const CartCountryService = this.app.services.CartCountryService; const Address = this.app.models['Address']; const countryNorm = address.country_code || address.country || address.country_name; const provinceNorm = address.province_code || address.province; let normalizedProvince, ext; if (!provinceNorm || !countryNorm) { return address; } normalizedProvince = CartCountryService.province(countryNorm, provinceNorm); if (!normalizedProvince) { throw new Error(`Unable to normalize ${provinceNorm}, ${countryNorm}`); } ext = { country: normalizedProvince.country.name, country_name: normalizedProvince.country.name, country_code: normalizedProvince.country.ISO.alpha2, province: normalizedProvince.name, province_code: normalizedProvince.code }; if (address instanceof Address.instance) { address = address.merge(ext); } else { address = _.merge(address, ext); } return address; } normalizeCurrency(amount) { if (!_.isNumber(amount) || _.isNil(amount)) { throw new Error('Amount must be a number'); } const amountString = amount.toString().split('.'); if (amountString[1]) { const exponent = amountString[1].length; amount = amount * Math.pow(10, exponent); } return amount; } resolveSendFromTo(obj, shippingAddress, options) { options = options || {}; return new Promise((resolve, reject) => { const Cart = this.app.models['Cart']; const Subscription = this.app.models['Subscription']; const Customer = this.app.models['Customer']; const Shop = this.app.models['Shop']; const Address = this.app.models['Address']; if (!(obj instanceof Cart.instance) && !(obj instanceof Subscription.instance)) { const err = new Error('Object must be an instance!'); return reject(err); } Shop.findById(obj.shop_id, { include: [ { model: Address.instance, as: 'address' }, ], transaction: options.transaction || null }) .then(shop => { if (!shop || !shop.address) { return resolve(null); } const from = { shop_id: shop.id, name: shop.name, address_1: shop.address.address_1, address_2: shop.address.address_2, address_3: shop.address.address_3, company: shop.address.company, city: shop.address.city, province: shop.address.province, province_code: shop.address.province_code, country: shop.address.country, country_name: shop.address.country_name, country_code: shop.address.country_code }; if (shippingAddress && this.app.services.ProxyCartService.validateAddress(shippingAddress)) { const res = { to: shippingAddress, from: from }; return resolve(res); } else if (obj.shipping_address_id) { Address.findById(obj.shipping_address_id) .then(address => { const to = address.get({ plain: true }); const res = { to: to, from: from }; return resolve(res); }) .catch(err => { return reject(err); }); } else if (obj.customer_id) { Customer.findById(obj.customer_id, { attributes: ['id'], include: [ { model: Address.instance, as: 'default_address' }, { model: Address.instance, as: 'shipping_address' } ], transaction: options.transaction || null }) .then(customer => { if (customer.shipping_address instanceof Address.instance) { customer.shipping_address = customer.shipping_address.get({ plain: true }); } if (customer.default_address instanceof Address.instance) { customer.default_address = customer.default_address.get({ plain: true }); } const to = customer.shipping_address ? customer.shipping_address : customer.default_address; const res = { to: to, from: from }; return resolve(res); }) .catch(err => { return reject(err); }); } else { return resolve(null); } }) .catch(err => { return reject(err); }); }); } resolveItemsFromTo(obj, lineItems, shippingAddress, options = {}) { const Cart = this.app.models['Cart']; const Order = this.app.models['Order']; const Subscription = this.app.models['Subscription']; const Customer = this.app.models['Customer']; const Shop = this.app.models['Shop']; const Address = this.app.models['Address']; if (!(obj instanceof Cart.instance) && !(obj instanceof Subscription.instance) && !(obj instanceof Order.instance)) { throw new Error('Object must be an instance of a Cart, Subscription or Order!'); } let nexuses, to; return Promise.resolve() .then(() => { if (shippingAddress && this.app.services.ProxyCartService.validateAddress(shippingAddress)) { return shippingAddress; } else if (obj.shipping_address_id) { return Address.findById(obj.shipping_address_id) .then(address => { return address.get({ plain: true }); }); } else if (obj.customer_id) { return Customer.findById(obj.customer_id, { attributes: ['id'], include: [ { model: Address.instance, as: 'default_address' }, { model: Address.instance, as: 'shipping_address' } ], transaction: options.transaction || null }) .then(customer => { if (customer.shipping_address instanceof Address.instance) { customer.shipping_address = customer.shipping_address.get({ plain: true }); } if (customer.default_address instanceof Address.instance) { customer.default_address = customer.default_address.get({ plain: true }); } return customer.shipping_address ? customer.shipping_address : customer.default_address; }); } else { return; } }) .then(_to => { if (!_to) { return; } else { to = _to; return Shop.sequelize.Promise.mapSeries(lineItems, item => { return this.resolveItemNexusTo(item, to, { transaction: options.transaction || null }); }); } }) .then(_nexuses => { if (!_nexuses) { return _nexuses; } else if (_nexuses.filter(n => n).length === 0) { return Shop.findById(obj.shop_id, { attributes: ['id', 'name', 'address_id'], include: [ { model: Address.instance, as: 'address' }, ], transaction: options.transaction || null }) .then(_shop => { if (!_shop || !_shop.address) { return null; } _nexuses[0] = { shop_id: _shop.id, name: _shop.name, address_1: _shop.address.address_1, address_2: _shop.address.address_2, address_3: _shop.address.address_3, company: _shop.address.company, city: _shop.address.city, province: _shop.address.province, province_code: _shop.address.province_code, postal_code: _shop.address.postal_code, country: _shop.address.country, country_name: _shop.address.country_name, country_code: _shop.address.country_code }; return _nexuses.filter(n => n); }) .catch(err => { this.app.log.error(err); return; }); } else { return _.uniqBy(_nexuses, 'shop_id').filter(n => n); } }) .then(_nexuses => { if (_nexuses) { nexuses = _nexuses; } if (!nexuses || _nexuses.length === 0 || !to) { return null; } else { return { nexus_addresses: nexuses, to_address: to }; } }); } resolveItemNexusTo(item, to, options = {}) { const Shop = this.app.models['Shop']; const Address = this.app.models['Address']; if (!item.shop_id) { return Promise.resolve(null); } else if (!this.app.config.get('scott.awesome')) { return Promise.resolve(null); } return Shop.findById(item.shop_id, { include: [ { model: Address.instance, as: 'address' } ], transaction: options.transaction || null }) .then(_shop => { if (!_shop || !_shop.address) { return null; } return { shop_id: _shop.id, item_id: item.id, name: _shop.name, address_1: _shop.address.address_1, address_2: _shop.address.address_2, address_3: _shop.address.address_3, company: _shop.address.company, city: _shop.address.city, province: _shop.address.province, province_code: _shop.address.province_code, country: _shop.address.country, country_name: _shop.address.country_name, country_code: _shop.address.country_code }; }) .catch(err => { return null; }); } nearestToAddress(shops, address) { shops = _.map(shops, shop => { shop.distance = geolib.getDistance({ latitude: shop.latitude, longitude: shop.longitude }, { latitude: address.latitude, longitude: address.longitude }); return shop; }); shops = _.sortBy(shops, 'distance'); return shops; } afterUserCreate(user, options = {}) { const Customer = this.app.models['Customer']; const Cart = this.app.models['Cart']; return Customer.resolve({ id: user.current_customer_id, email: user.email, phone: user.phone, first_name: user.first_name, last_name: user.last_name, accepts_marketing: user.accepts_marketing, users: [user] }, { transaction: options.transaction || null, create: true }) .then(customer => { if (!customer) { return { id: null }; } user.current_customer_id = customer.id; if (!customer.email && user.email) { customer.email = user.email; } return this.app.services.CustomerService.addUser(customer, user, { transaction: options.transaction || null }) .then(() => { return customer.save({ transaction: options.transaction || null }); }); }) .then(customer => { return Cart.resolve({ id: user.current_cart_id, customer: customer.id }, { transaction: options.transaction || null }); }) .then(cart => { user.current_cart_id = cart.id; return user.save({ fields: [ 'current_cart_id', 'current_customer_id' ], transaction: options.transaction || null }); }); } serializeCart(cart, next) { if (typeof next !== 'function') { throw new Error('instance#serializeCart requires a callback function'); } next(null, cart.id); } deserializeCart(id, options, next) { options = options || {}; if (typeof next !== 'function') { throw new Error('instance#deserializeCart requires a callback function'); } this.app.models['Cart'].findById(id, { transaction: options.transaction || null }) .then(cart => { next(null, cart); }) .catch(err => { next(err); }); } serializeCustomer(customer, next) { if (typeof next !== 'function') { throw new Error('instance#serializeCustomer requires a callback function'); } next(null, customer.id); } deserializeCustomer(id, options, next) { options = options || {}; if (typeof next !== 'function') { throw new Error('instance#deserializeCustomer requires a callback function'); } this.app.models['Customer'].findById(id, { transaction: options.transaction || null }) .then(customer => { next(null, customer); }) .catch(err => { next(err); }); } serializeShop(shop, next) { if (typeof next !== 'function') { throw new Error('instance#serializeShop requires a callback function'); } next(null, shop.id); } deserializeShop(id, options, next) { options = options || {}; if (typeof next !== 'function') { throw new Error('instance#deserializeShop requires a callback function'); } this.app.models['Shop'].findById(id, { transaction: options.transaction || null }) .then(shop => { next(null, shop); }) .catch(err => { next(err); }); } } exports.ProxyCartService = ProxyCartService;