@gameroom/cli
Version:
A command line tool for Gameroom
197 lines (192 loc) • 10.6 kB
JavaScript
const cosmetic = require('cosmetic'),
{ join } = require('path'),
{
enums: { sale_status },
models: { Address, Container, Image, Line, Payment, Price, Product, Sale, Store, Unit, Unit_Extended }
} = require('@gameroom/kit'),
{ componentString, getAll, grGreen, timeout, writeCSVFile } = require('../../helpers'),
{ Shopify } = require('../../models'),
{ config, conversions, google_product_categories, spinner } = require('../../refs'),
LIMIT = 500,
SHOPIFY_LIMIT = 250
module.exports = async ({ verbose }) => {
// catch all errors that arent 429 (too many requests) or 404 (not found)
try {
config.exit_gracefully = true
const dev = process.env.NODE_ENV === 'development'
const SHOPIFY_STORE_URL = dev ? process.env.SHOPIFY_DEV_STORE_URL : process.env.SHOPIFY_STORE_URL
if (!SHOPIFY_STORE_URL) throw new Error('missing shopify env keys')
spinner.info(`updating ${cosmetic.green(SHOPIFY_STORE_URL)} @ ${new Date().toLocaleString()}`)
// stores
spinner.text = `getting ${grGreen('stores')}`
const stores = await Store.getAll()
spinner.info(`got ${stores.length} ${grGreen('stores')}`)
// store addresses
spinner.text = `getting ${grGreen('addresses')}`
const address_filters = []
for (const store of stores) address_filters.push({ key: 'addressable_id', value: store.id })
const addresses = await Address.getAll({ filter: { or: address_filters } })
spinner.info(`got ${addresses.length} ${grGreen('addresses')}`)
// containers
spinner.text = `getting ${grGreen('containers')}`
const containers = await Container.getAll()
spinner.info(`got ${containers.length} ${grGreen('containers')}`)
// shopify locations
spinner.text = `getting ${cosmetic.green('locations')}`
const locations = await Shopify.Location.get()
spinner.info(`got ${locations.length} ${cosmetic.green('locations')}`)
// Gameroom units >> Shopify products
// get units and operate in chunks of 500
// let api manage updated_at
// units will be recieved again for updates
// save units in an object to reference for updates when updating shopify
// just save id: updated_at in objects
// compare updated_at dates
// could delete object[id] if i want to
// date filter
return
let filter
if (config.last_shopify_push) {
spinner.info(`last update ${new Date(config.last_shopify_push).toLocaleString()}`)
const date = new Date(config.last_shopify_push)
// just in case...
// date.setTime(date.getTime() - 1)
filter = { key: 'updated_at', comparison: '>', value: new Date(config.last_shopify_push) }
} else {
spinner.info(`first shopify update`)
}
// Get updated units and add or update or remove them on shopify
let offset = 0, done = false, processed = {}
// update shopify
let added = 0, removed = 0, skipped = 0, updated = 0, not_offered = 0, current = 0, total = 0
spinner.text = `0 / 0 processed, 0 ${cosmetic.green('added')}, 0 ${cosmetic.red('removed')}, 0 ${cosmetic.yellow('updated')}, 0 skipped, 0 not offered`
while (!done) {
if (config.should_exit) break
// get batch of units
const units = await Unit_Extended.get({ filter, limit: LIMIT, offset, sort: [{ updated_at: 1 }] })
if (units.length < LIMIT) done = true
offset += LIMIT
total += units.length
if (verbose) spinner.info(`got ${units.length} ${grGreen('units')}`)
// iterate through batch
for (let [i, unit] of units.entries()) {
try {
if (config.should_exit) break
// snapshot unit in case it needs updated
const shopified = unit.shopified
// orphan and shopifyable checks
if (!unit.store_id && verbose) spinner.warn(`${grGreen('unit')} ${unit.id} has no ${grGreen('store')}`)
if (!unit.container_id && verbose) spinner.warn(`${grGreen('unit')} ${unit.id} has no ${grGreen('container')}`)
if (!unit.product_id && verbose) spinner.warn(`${grGreen('unit')} ${unit.id} has no ${grGreen('product')}`)
if (!unit.price_id && verbose) spinner.warn(`${grGreen('unit')} ${unit.id} has no ${grGreen('price')}`)
if (!unit.image && verbose) spinner.warn(`${grGreen('unit')} ${unit.id} has no ${grGreen('image')}`)
// find unit's store on shipify 'location'
const store = unit.store_id ? stores.find(s => s.id === unit.store_id) : null
const store_address = store ? addresses.find(a => a.addressable_id === store.id) : null
const street_number = store_address ? store_address.street1.split(' ')[0] : null
const location = street_number ? locations.find(l => l.address1.includes(street_number)) : null
if (!location && verbose) spinner.warn(`${grGreen('unit')} ${unit.id} has no ${cosmetic.green('location')}`)
// find unit's container
const container = unit.container_id ? containers.find(i => i.id === unit.container_id) : null
// values required to be shopified
const shopifyable = store && store.offered && container && container.offered && unit.offered && unit.image && unit.amount > 0 && unit.quantity > 0 && location
// get shopify product by handle
let product = await Shopify.Product.getWithHandle(unit.id)
// conditions
const add = !product && shopifyable
const remove = product && !shopifyable
const update = product && shopifyable
// snapshot unit last updated_at
const { updated_at } = unit
if (add) {
// create shopify product, variant, and image
product = await Shopify.Product.fromUnitExtended(unit)
product = await product.save()
// if (verbose) spinner.succeed(`created shopify ${cosmetic.green('product')} for ${grGreen('unit')} ${unit.id}`)
// create shopify inventory
const inventory_level = new Shopify.Inventory_Level({
available: unit.quantity,
inventory_item_id: product.variants[0].inventory_item_id,
location_id: location.id
})
await inventory_level.save()
// if (verbose) spinner.succeed(`created shopify ${cosmetic.green('iventory_level')} for ${grGreen('unit')} ${unit.id}`)
// update gameroom unit
unit.shopified = true
// updated units go to the end of the pagination line and mess up the offset...
offset--
if (verbose) spinner.succeed(`created shopify ${cosmetic.green('product')} and ${cosmetic.green('iventory_level')} for ${grGreen('unit')} ${unit.id}`)
added++
} else if (remove) {
// delete shopify product
await Shopify.Product.delete(product.id)
// update gameroom unit
unit.shopified = false
// updated units go to the end of the pagination line and mess up the offset...
offset--
if (verbose) spinner.succeed(`removed shopify ${cosmetic.green('product')} for ${grGreen('unit')} ${unit.id}`)
removed++
} else if (update) {
// check updated_at
if (processed[unit.id] === unit.updated_at) {
config.last_shopify_push = new Date(unit.updated_at * 1000)
delete processed[unit.id]
continue
}
// update shopify product
product = await product.updateFromUnitExtended(unit)
// update shopify inventory
const inventory_levels = await Shopify.Inventory_Level.get(product.variants[0].inventory_item_id)
// move or create inventory item
let inventory_level = inventory_levels.find(l => l.location_id === location.id)
if (!inventory_level) inventory_level = new Shopify.Inventory_Level({ inventory_item_id: product.variants[0].inventory_item_id, location_id: location.id })
if (inventory_level.available !== unit.quantity) {
// all new and updated
inventory_level.available = unit.quantity
await inventory_level.save()
}
// more than 1 inventory level shouldnt be possible
// delete old inventory levels
for (const l of inventory_levels.filter(l => l.location_id !== location.id)) await Shopify.Inventory_Level.delete(l)
// update gameroom unit if not shopified
unit.shopified = true
// if (verbose) spinner.succeed(`${grGreen('unit')} ${unit.id} updated`)
if (verbose) spinner.succeed(`updated shopify ${cosmetic.green('product')} and ${cosmetic.green('inventory_level')} for ${grGreen('unit')} ${unit.id}`)
updated++
} else {
if (unit.offered) {
if (verbose) spinner.warn(`${grGreen('unit')} ${unit.id} skipped`)
skipped++
} else {
not_offered++
}
// check for other mistakes
// unshopify unit if it has no shopify product
if (!product && unit.shopified) {
unit.shopified = false
if (verbose) spinner.succeed(`${grGreen('unit')} ${unit.id} updated`)
}
}
if (unit.shopified !== shopified) {
// update unit if need be
unit = await Unit.update({ id: unit.id, shopified: unit.shopified })
if (verbose) spinner.succeed(`updated ${grGreen('unit')} ${unit.id}`)
}
config.last_shopify_push = new Date(updated_at * 1000)
// spinner.info(new Date(unit.updated_at * 1000).toLocaleString())
processed[unit.id] = unit.updated_at
current++
spinner.text = `${current} / ${total} processed, ${added} ${cosmetic.green('added')}, ${removed} ${cosmetic.red('removed')}, ${updated} ${cosmetic.yellow('updated')}, ${skipped} skipped, ${not_offered} not offered`
} catch (err) {
current++
skipped++
spinner.fail(`failed on ${grGreen('unit')} ${unit.id}: ${err}`)
}
}
}
spinner.succeed(`${total} processed, ${added} ${cosmetic.green('added')}, ${removed} ${cosmetic.red('removed')}, ${updated} ${cosmetic.yellow('updated')}, ${skipped} skipped, ${not_offered} not offered`)
spinner.succeed(`completed push to ${cosmetic.green('shopify')} @ ${new Date().toLocaleString()}`).stop()
} catch(err) {
spinner.fail(`error pushing to ${cosmetic.green('shopify')}: ${err}`).stop()
}
}