Source: api/middleware/product.js

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;
}