import { ShopIdService } from 'cpb-api';
import { isArray } from 'cpb-common';
import Datastore from 'cpb-datastore';
import Shopify from 'cpb-shopify';
/**
* @module cpb-shopify/product
*/
/**
* ### Product Attributes to request from Shopify
* @readonly DEFAULT_PRODUCT_FIELDS
*/
const DEFAULT_PRODUCT_FIELDS = [
'admin_graphql_api_id',
'body_html', // body_html is displayed as 64characters on the frontend
'created_at',
'handle',
'id',
'image',
'images',
'options',
'product_type',
'published_at',
'status',
'tags',
'title',
'updated_at',
'variants',
'vendor',
],
DEFAULT_SHOPIFY_PAGE_SIZE = 200,
EXCLUDED_PRODUCT_FIELDS = ['options', 'variants', 'admin_graphql_api_id'];
/**
* ### Product Data from Shopify
*/
export const Product = {
settings: {
kind: 'shopify_products',
fields: DEFAULT_PRODUCT_FIELDS,
excludedFields: EXCLUDED_PRODUCT_FIELDS,
},
/**
* ### Save products to the datastore
* @param {!object|object[]} data - product data to save
* @param {!string} shopName - shopify bottom-level domain of the filestore lowercased
* @param {!number} shopId
* @return {Promise<void|*>}
*/
async save({ data, shopName, shopId } = {}) {
if (!data) throw new TypeError('!data');
if (!shopId) throw new TypeError('!shopId');
if (!shopName) throw new TypeError('!shopName');
const promises = [];
if (!isArray(data)) data = [data];
for (const product of data) {
product.shopID = shopId;
product.shopName = shopName;
product.datastore_updated_at = new Date();
// resolving Error: 3 INVALID_ARGUMENT: The value of property "body_html" is longer than 1500 bytes.
// https://stackoverflow.com/questions/44373051/google-datastore-1500-byte-property-limit-for-embedded-entities
product.body_html = (product.body_html || '').substring(0, 1450);
const productData = { ...product };
delete productData.config;
// console.debug({ SAVE: productData });
promises.push(
Datastore.save({
kind: this.settings.kind,
data: productData,
key: Datastore.datastore.key([this.settings.kind, [productData.shopName, productData.id].join()]),
useEmulator: process.env.USE_EMULATOR_DATASTORE,
}),
);
}
return Promise.all(promises);
},
/**
* ### Get Shopify Product List Iteratively
* Shopify Page Size is in `env.DEFAULT_SHOPIFY_PAGE_SIZE`. The default value is `250`.
* @param {!string} shopName
* @param {?number} id - product id
* @param {?number[]|string} [ids] - product ids
* @param {?string[]} [fields=DEFAULT_PRODUCT_FIELDS] - product attributes to include
* @param {?object} settings - shopify api call settings
* @property {?number} [settings.limit=DEFAULT_SHOPIFY_PAGE_SIZE] - pagination size
* returns {Promise<*[][]|(*[]|*)[]>} [products, ?error] - [Shopify Products, Error]
* @returns {Promise<Array()|*>} [products, ?error] - [Shopify Products, Error]
* @async
*/
async list({ shopName, id, ids = [], fields = [] } = {}, settings = { limit: DEFAULT_SHOPIFY_PAGE_SIZE }) {
if (!shopName) throw new TypeError('!shopName');
if (typeof ids === 'string') ids = ids.split(',');
if (id) ids.push(id);
if (isArray(ids) && ids.length) settings.ids = ids.join(',');
settings.fields = [...DEFAULT_PRODUCT_FIELDS, ...fields].join(',');
let pagination = {
limit: (settings.limit || DEFAULT_SHOPIFY_PAGE_SIZE) > DEFAULT_SHOPIFY_PAGE_SIZE ? DEFAULT_SHOPIFY_PAGE_SIZE : settings.limit,
};
if (settings.ids) pagination.ids = settings.ids;
pagination.fields = [...DEFAULT_PRODUCT_FIELDS, ...fields].join(',');
const products = [],
{ id: shopId, name: shopNameDb } = await ShopIdService.getIdName(shopName),
connection = await Shopify.connect({ shopName: shopNameDb });
try {
do {
const page = await connection.product.list(pagination);
pagination = page.nextPageParameters;
products.push(...page);
} while (pagination);
if (products.length) this.save({ data: products, shopId, shopName: shopNameDb }).catch(console.error);
for (const product of products) for (const attr of EXCLUDED_PRODUCT_FIELDS) {
const variant = product.variants[0];
if(!fields.includes(attr)) delete product[attr];
if(attr === 'variants') product.variants = [variant];
};
return [products, undefined];
} catch (error) {
console.error(`[shopify/product/list][${shopNameDb}][${error.code}] NO_SHOPIFY_CONNECTION`, { error });
return [[], error];
}
},
/**
* ### Shopify Products Count
* @param {string} shopName
* @returns {Promise<number>} - products count
*/
async count({ shopName }) {
let connection;
try {
connection = await Shopify.connect({ shopName });
} catch (error) {
console.error(`[${shopName}] ERROR_CONNECT [${error.code}] `, { error });
return 0;
}
return await connection.product.count();
},
};
export default Product;