import callerId from 'caller-id';
import { ShopIdService } from 'cpb-api';
import GetFiles from 'cpb-api/filestore/get.js';
import { get as GetShop } from 'cpb-api/middleware/shop.js';
import { getBinaryFlagValue, stringifyJSON, title } from 'cpb-common';
import Shopify from 'cpb-shopify';
import Webhooks from 'cpb-shopify/webhook/index.js';
import { File } from 'cpb-storage';
/**
* ### Get all shop instance data
* @param {!string|number} shopNameOrShopID
* @param {?string|number} productIdOrHandle
* @param {?number} generation
* @param {?object} exclusions
* @returns {Promise<{products}>}
*/
export async function get(shop, productIdOrHandle, generation, exclusions = {}) {
// if (!shopNameOrShopID) throw new TypeError('!shopNameOrShopID');
const fetch = exclusions.fetch,
excProducts = exclusions.products,
promises = [],
{ id: shopID, name: shopName } = shop, //await ShopIdService.getIdName(shopNameOrShopID, fetch),
cachedFile = `${shopID}/cpb.json`,
onGetShopError = function onGetShopError(error, msg = 'middleware') {
console.error(`[api/middleware/cpb.get][${shopID},${shopName}] Error: ${msg}`, error, callerId.getDetailedString());
return { error };
};
let result;
if (!fetch) {
try {
result = await File.read({ name: cachedFile, generation, json: true });
} catch (error) {
title(`[cpb-api/middleware/get][${shopID},${shopName}] ERROR.CachedFileNotFound`, error, callerId.getDetailedString());
}
}
let needDatastoreSave;
if (fetch || !result) {
needDatastoreSave = true;
result = result || { shopName, shopID };
// requesting all the data at once
promises.push(
GetShop({ shopName }, fetch)
.then(shop => (result.shop = shop))
.catch(onGetShopError),
GetFiles({ id: shopID, fetch, files: 1, versions: 1 })
.then(async files => {
files.productIds = Object.keys(files.productConfigs).map(a => +a);
// listing product id;s existing on storage buckets
if (files.productIds && files.productIds.length) {
try {
const fields = [];
if (excProducts) fields.push('images');
const [products, error] = await Shopify.Product.list({ shopName, ids: files.productIds, fields: fields }).catch(onGetShopError);
if (error) throw error;
result.products = products;
} catch (error) {
error.originator = 'Shopify.Product.list';
console.error({ error }, callerId.getDetailedString());
result.products = [];
result.errors = result.errors || [];
delete error.timings;
result.errors.push(error);
}
for (const product of result.products) product.config = files.productConfigs[product.id];
}
// this contains the configs that need to be deleted since the Shopify Product is gone.
const toRemove = [];
for (const fileId of files.productIds) if (!result.products.find(product => product.id == fileId)) toRemove.push(files.productConfigs[fileId]);
result.storage = { files, toRemove };
// for (const { path } of toRemove) File.delete({ path }).catch(console.error);
})
.catch(onGetShopError),
Shopify.Charge.getActiveCharge(shopName || shopID, fetch)
.then(charge => (result.charge = charge))
.catch(onGetShopError),
// Webhooks.list(shopName, fetch)
// .then(([webhooks, error]) => {
// if (error) throw error;
// result.webhooks = webhooks;
// })
// .catch(onGetShopError),
);
try {
await Promise.all(promises).catch(onGetShopError);
} catch (error) {
onGetShopError(error, 'GetShop', { ...arguments });
}
}
for (const product of result.products || []) {
const currentVersion = product.config.versions.find(v => v.isCurrent) || product.config.versions.pop();
product.config = { ...product.config, ...currentVersion };
// restoring accidentally deleted configs
if (product.config.isDeleted) {
console.warn('[cpb-api/middleware/cpb] FOUND_DELETED_CONFIG', product.config);
await File.restore({ path: `${shopID}/${product.id}.json`, generation: currentVersion.generation }).catch(console.error);
}
}
if (fetch || needDatastoreSave)
File.upload({
name: cachedFile,
data: result,
}).catch(console.error);
for (const [key, val] of Object.entries(exclusions)) if (!val) delete result[key];
if (productIdOrHandle) {
result.product = result.products.find(p => p.id == productIdOrHandle || p.handle === productIdOrHandle);
const configPath = result.product.config.path;
if (generation) {
const version = result.product.config.versions.find(value => value.generation === generation);
console.debug({ generation, configPath, version });
if (version) result.product.config = { ...result.product.config, ...version };
}
delete result.products;
result.productData = await File.read({ name: configPath, generation }).catch(console.error);
}
return result;
}
/**
* ### [/cpb:${shopNameOrShopID] middleware
* @param {Request} req
* @param {Response} res
*/
export async function CPBWare(req, res) {
if (!res.locals.shop) {
console.error(`[CPBWare] NO_RES_LOCALS_SHOP`, res.locals, req.params, req.query);
// throw new TypeError('!res.locals.shop');
}
const fetch = getBinaryFlagValue(req.query.fetch, 0),
generation = req.params.generation;
let exclusions = {
fetch,
storage: getBinaryFlagValue(req.query.files, 0),
versions: getBinaryFlagValue(req.query.versions, 1),
charge: getBinaryFlagValue(req.query.charges, 0),
shop: getBinaryFlagValue(req.query.shop, 1),
products: getBinaryFlagValue(req.query.products, 1),
webhooks: getBinaryFlagValue(req.query.webhooks, 1),
};
if (!isEmptyObj(req.query) && !fetch || fetch && Object.keys(req.query).length > 1) {
exclusions = {
fetch,
storage: getBinaryFlagValue(req.query.files, 0),
versions: getBinaryFlagValue(req.query.versions, 0),
charge: getBinaryFlagValue(req.query.charges, 0),
shop: getBinaryFlagValue(req.query.shop, 0),
products: getBinaryFlagValue(req.query.products, 0),
webhooks: getBinaryFlagValue(req.query.webhooks, 0),
};
}
// shopIdOrName = res.locals.shopIdOrName || req.params.shopIdOrName,
// { id: shopID, name: shopName } = res.locals.shop || (await ShopIdService.getIdName(shopIdOrName)),
const productIdOrHandle = req.params.productIdOrHandle;
console.log(productIdOrHandle, '+++++++++++++++++++++++++++++++=');
const debug = {
// shopID,
// shopName,
// shopIdOrName,
productIdOrHandle,
generation,
exclusions,
request: {
rid: res.locals.rid,
query: req.query,
params: req.params,
locals: res.locals,
},
timestamp: new Date(),
};
// console.info(`[api/middleware/cpb]`, );
try {
const data = await get(res.locals.shop, productIdOrHandle, generation, exclusions);
res
.type('json')
.status(200)
.end(stringifyJSON({ ...data, debug }));
} catch (error) {
res.type('json').status(500).end(stringifyJSON({ error, debug }));
}
}
const isEmptyObj = (obj) => {
return obj // 👈 null and undefined check
&& Object.keys(obj).length === 0
&& Object.getPrototypeOf(obj) === Object.prototype
}
export default CPBWare;