import app from 'cpb-api';
import { getBinaryFlagValue } from 'cpb-common';
import Product from '../product/index.js';
import Datastore from 'cpb-datastore';
import CPBMiddleware from './cpb.js';
import GetFiles from 'cpb-api/filestore/get.js';
import Shopify from 'cpb-shopify';
import { File } from 'cpb-storage';
import s from 'connect-redis';
/**
* @method module:cpb-api/middleware.product
* @param {Request} req
* @param {string|number} req.params.id
* @param {string|number} req.params.shop_id
* @param {boolean} req.query.fetch
* @param {boolean} req.query.versions
* @param {boolean} req.query.files
* @param {Response} res
* @param {NextFunction} next
* @returns {Promise<ProductList|Product|undefined>}
*/
export default async function ProductMiddleware(req, res, next) {
if (req.method !== 'GET' && req.method !== 'HEAD') {
res.statusCode = req.method === 'OPTIONS' ? 200 : 405;
res.setHeader('Allow', 'GET, HEAD, OPTIONS');
res.setHeader('Content-Length', '0');
res.end();
return next();
}
//const id = req.params.id,
const id = req.params.productIdOrHandle,
//shop_id = res.locals.shopIdOrName,
shop_id = res.locals.shop.id,
bucket = app.get('bucket'), // versions =
fetch = getBinaryFlagValue(req.query.fetch, 0),
versions = getBinaryFlagValue(req.query.versions, 0),
files = getBinaryFlagValue(req.query.files, 0), // req.query.versions || true,
debug = {
params: req.params,
query: req.query,
id,
shop_id,
bucket, // versions,
fetch,
versions,
files,
};
const 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),
};
let data, entries, DSQuery;
const productIdOrHandle = req.params.productIdOrHandle;
const generation = req.params.generation;
// missing prefix means no storeId was passed as the parameter and we return all stores
if (!id) {
// const [ids, misc, legacy] = await getShopifyStoreIds(bucket);
try {
if (fetch) {
entries = await productGet({shop : res.locals.shop, generation, exclusions});
} else {
DSQuery = {
kind: 'shopify_products',
filter: {
'shopName': `${res.locals.shop.name}`,
},
};
console.log('query in');
data = await Datastore.query(DSQuery);
console.log('query out');
entries = data.map(entry => {
const template = {
"node": {
"id": entry.admin_graphql_api_id,
"title": entry.title,
"description": entry.body_html,
"featuredImage": {
"id": entry.image ? entry.image.admin_graphql_api_id : null,
"src": entry.image ? entry.image.src : null,
"transformedSrc": entry.image ? entry.image.src : null /// !!! CHECK what difference between src and transformedSrc
},
"handle": entry.handle
}
}
return template;
})
// cacheKey = stringifyJSON(DSQuery);
// data = await Product.list({ bucket, shop_id, files, fetch, versions });
// title('[api/product/middleware.list::data', { ...data.stats, debug });
}
} catch (error) {
console.error(`ProductListError::[gs://${bucket}/${shop_id}`, error);
throw error;
}
} else {
try {
if (fetch){
//const productIdOrHandle = req.params.productIdOrHandle;
const generation = req.params.generation;
entries = await productGet({shop : res.locals.shop, generation, exclusions});
//entries = await productGet({shop : res.locals.shop, productIdOrHandle: id, generation, exclusions});
} else {
DSQuery = {
kind: 'shopify_products',
filter: {
'shopName': `${res.locals.shop.name}`,
'id': +id
},
};
//cacheKey = stringifyJSON(DSQuery);
entries = await Datastore.query(DSQuery);
console.log(id, ' ', shop_id, ' ', bucket, ' ', files, ' ', fetch, ' ', versions);
data = await Product.get({ id, shop_id, bucket, files, fetch, versions });
// title('ProductMiddleware.ProductGet', { ...data.stats, debug });
}
} catch (error) {
console.error(`ProductGetError::[gs://${bucket}/${shop_id}/${id}]`, error);
throw error;
}
}
//res.json({ ...data, debug});
res.json({ products: {edges: entries}, debug});
res.end();
}
async function productGet({shop, productIdOrHandle, generation, exclusions = {}}) {
const promises = [];
let result;
const shopName = shop.name;
const shopID = shop.id;
const cachedFile = `${shopID}/cpb.json`;
const errorMessageShopify = 'errorMessageShopify';
const errorMessageShopifyPromise = 'errorMessageShopifyPromise';
const errorMessageShopifyPromiseAll = 'errorMessageShopifyPromiseAll';
const fetch = exclusions.fetch;
const needDatastoreSave = true;
result = result || { shopName, shopID };
// requesting all the data at once
promises.push(
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) {
const params = productIdOrHandle ? { shopName, id: productIdOrHandle} : { shopName, ids: files.productIds }
try {
const [products, error] = await Shopify.Product.list(params).catch(errorMessageShopify);
if (error) throw error;
result.products = products;
} catch (error) {
error.originator = 'Shopify.Product.list';
console.error( error );
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(errorMessageShopifyPromise),
);
try {
await Promise.all(promises).catch(errorMessageShopifyPromiseAll);
} catch (error) {
console.log(error);
}
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);
}
const entries = productIdOrHandle ? result.product : result.products;
return entries;
}