import { CACHE, ShopIdService } from 'cpb-api';
import { isIterable, isObject, stringifyJSON, title } from 'cpb-common';
import Datastore from 'cpb-datastore';
import Shopify from 'cpb-shopify';
/**
* @module cpb-api/subscription.shopify
* @description
* ### Shopify Subscription Management
*/
/**
* @exports module:cpb-api/subscription/shopify
*/
export default {
/**
* @property {CACHE} CACHE
*/
process,
CACHE,
// settings : {
// kind: process.env.CPB_TOKENS_KIND,// || 'custom-product-builder-production-tokens',
// autoLimit: true,
// },
/**
* get uninstalled stores data
* @returns {Promise<Map>}
*/
async getUninstalledStores() {
const data = await this.getTokens({
filter: { uninstalled: true },
});
return this.fillUninstalledStoresCache(data);
},
/**
* populates cache for Uninstalled stores with given data
* @param {object[]} data
* @return {*}
*/
fillUninstalledStoresCache(data) {
const name = 'uninstalledStores',
key = 'shopName';
return this.fillCache({ name, data, key });
},
/**
* populates cache for installed stores with given data
* @param {object[]} data
* @return {*}
*/
fillInstalledStoresCache(data) {
return this.fillCache({ name: 'installedStores', data, key: 'shopName' });
},
/**
* Updates the CACHE[name][entry[key]] = entry;
* @param {string} name - cached entity name
* @param {object|object[]} data - data to stor by the `key`
* @param {string} key - object key to use for lookup (usually **shopName**)
* @return {*} CACHE[name] - cached entity map
*/
fillCache({ name, data, key }) {
if (!key) throw new TypeError('!key');
if (!data) throw new TypeError('!data');
if (!name) throw new TypeError('!name');
CACHE[name] = CACHE[name] || new Map();
if (isIterable(data)) {
for (const entry of data) CACHE[name][entry[key]] = entry;
} else if (isObject(data)) {
for (const [, entry] of Object.entries(data)) CACHE[name][entry[key]] = entry;
} else console.error(`CACHE_IS_NOT_ITERABLE_OR_OBJECT[${name}]`, data);
return CACHE[name];
},
/**
* @description
* ### Get installed shopify shops information from the datastore
* If the `shopName` is given - retrieves a particular record. Otherwise, returns a full list.
* @param {?string} [shopName]
* @return {Promise<module:cpb-api/subscription/shopify.CACHE.installedStores|Map>}
*/
async getInstalledStores(shopName) {
const settings = {
kind: process.env.CPB_TOKENS_KIND || 'custom-product-builder-production-tokens',
autoLimit: true,
};
const key = 'shopName',
params = {
filter: [
['uninstalled', false],
// ['paymentProblem', false],
],
};
if (shopName) params.filter.push([key, shopName]);
const data = await this.getTokens({ ...settings, ...params });
return this.fillCache({ name: 'installedStores', data, key });
},
// createQueryFilter: Datastore.createQueryFilter,
/**
* ### Get Shopify Authentication Token record from the datastore.
* @param {object} [filterObject={}]
* @param {*} fetch
* @return {Promise<object>} tpken
* @example
* const token = Shopify.getToken({shopName: '02bp2018'}, true);
* console.info(token);
* {
* shopName: '02bp2018',
* shopID: null,
* paymentProblem: false,
* datastore_inserted_at: 2021-12-30T07:51:27.128Z,
* uninstalled: false,
* info: '!fetch',
* datastore_updated_at: 2022-01-13T04:34:56.816Z,
* planName: 'default',
* token: '42b71e125874a649225b191f23780d93',
* error: 'GET_SHOP_ID',
* [Symbol(KEY)]: Key {
* namespace: undefined,
* name: '02bp2018',
* kind: 'custom-product-builder-production-tokens',
* path: [Getter]
* }
*/
async getToken(filter = {}, fetch) {
CACHE.tokens = CACHE.tokens || new Map();
const querySettings = { orderBy: 'shopName', filter },
cacheKey = stringifyJSON(querySettings);
// title('getToken', { cacheKey, arguments });
if (!fetch && CACHE.tokens.has(cacheKey)) return CACHE.tokens.get(cacheKey);
const tokens = await this.getTokens(querySettings, fetch);
if (tokens.length > 1) {
const keys = Datastore.Keys.get(tokens);
// proper record is one which key has the name attribute instead of id
const properKey = keys.find(k => k.name);
console.warn(`[api/subscription/getToken][${filter.shopName}] MULTIPLE_TOKENS_FOUND: ${tokens.length}`, keys, properKey);
if (properKey) {
const forRemoval = [];
for (const key of keys) if (!Datastore.Keys.compare(key, properKey)) forRemoval.push(key);
Datastore.delete({ keys: forRemoval }).catch(console.error);
}
}
if (!tokens.length) console.error(`[api/subscription/getToken][${filter.shopName}] NO_TOKENS_FOUND`);
return tokens.length ? tokens[0] : undefined;
},
/**
* get the shops authentication tokens records from the datastore
* @param opts
* @param fetch
* @return {Promise<Array()>}
*/
async getTokens(opts = {}, fetch) {
CACHE.tokens = CACHE.tokens || new Map();
const settings = {
kind: process.env.CPB_TOKENS_KIND || 'custom-product-builder-production-tokens',
autoLimit: true,
};
const tokens = [],
stats = {
errors: 0,
shops: 0,
},
querySettings = { ...settings, ...opts },
cacheKey = stringifyJSON(querySettings);
// title('Subscriptions.Shopify.getTokens', { opts, fetch, cacheKey, querySettings });
if (!fetch && CACHE.tokens.has(cacheKey)) return CACHE.tokens.get(cacheKey);
// const datastoreTokens = await Datastore.query(querySettings);
// const storesFromStatsMap = ShopIdService.ids;
// console.debug({ storesFromTokens });
for (const tokenInfo of await Datastore.query(querySettings)) {
if (!tokenInfo.shopName) {
console.error('[cpb-api/subscription/shopify/getTokens] ERROR_NO_TOKEN_SHOPNAME', { ...arguments });
continue;
}
if (!tokenInfo.token) {
console.error(`[cpb-api/subscription/shopify/getTokens][${tokenInfo.shopName}] ERROR_NO_TOKEN`);
continue;
}
// const shopName = tokenInfo.shopName;
let { id: shopId, name: shopName } = await ShopIdService.getIdName(tokenInfo.shopName);
// let shopId = tokenInfo.shopID || storesFromStatsMap.get(shopName);
if (shopId && !tokenInfo.shopID) {
stats.shops++;
tokenInfo.shopID = shopId;
tokenInfo.uninstalled = false;
// if (tokenInfo.error) {
delete tokenInfo.error;
delete tokenInfo.info;
delete tokenInfo._DEBUG;
Shopify.saveToken(tokenInfo).catch(console.error);
// }
tokens.push(tokenInfo);
continue;
}
if (fetch) {
// paymentProblem = (tokenInfo.paymentProblem || false).toString(),
try {
if (!shopId) shopId = await Shopify.getShopData(shopName, fetch)?.id;
console.debug(`[cpb-api/subscription/shopify/getTokens][${shopName}] fetched shopify.shopData.shopId`, {
fetch,
shopName,
shopId,
});
} catch (error) {
console.error(`[cpb-api/subscription/shopify/getTokens][${shopName}]`, { error });
tokenInfo.error = error;
tokens.push(tokenInfo);
stats.errors++;
await Shopify.saveToken(tokenInfo).catch(console.error);
continue;
}
}
// else {
// shopId = storesFromStatsMap.get(shopName);
// tokenInfo.info = '!fetch.storesFromStatsMap';
// }
if (!shopId) {
tokenInfo.error = `[${tokenInfo.shopName}] ERROR_GET_SHOP_ID`;
tokenInfo.shopID = null;
stats.errors++;
console.warn(tokenInfo.error, tokenInfo.shopName, { stats });
continue;
}
if (shopId) {
delete tokenInfo.error;
tokenInfo.uninstalled = false;
tokenInfo.shopID = +shopId;
stats.shops++;
console.info(`[${tokenInfo.shopName}] found shopID: ${shopId}`, { stats });
}
// need to update the datastore record with shopID
// await Shopify.saveToken(tokenInfo).catch(console.error);
tokens.push(tokenInfo);
}
this.fillCache({
name: 'tokens',
data: tokens,
key: cacheKey,
});
return tokens;
},
};