diff --git a/.babelrc.js b/.babelrc.js index bde709c495..39c0fa3e45 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -1,13 +1,13 @@ -let ignore = [`**/dist`] +let ignore = [`**/dist`]; // Jest needs to compile this code, but generally we don't want this copied // to output folders if (process.env.NODE_ENV !== `test`) { - ignore.push(`**/__tests__`) + ignore.push(`**/__tests__`); } module.exports = { sourceMaps: true, presets: ["babel-preset-medusa-package"], ignore, -} +}; diff --git a/packages/medusa-plugin-contentful/src/services/contentful.js b/packages/medusa-plugin-contentful/src/services/contentful.js index f78d714073..a1498f2695 100644 --- a/packages/medusa-plugin-contentful/src/services/contentful.js +++ b/packages/medusa-plugin-contentful/src/services/contentful.js @@ -2,9 +2,17 @@ import _ from "lodash" import { BaseService } from "medusa-interfaces" import { createClient } from "contentful-management" +const IGNORE_THRESHOLD = 2 // seconds + class ContentfulService extends BaseService { constructor( - { productService, redisClient, productVariantService, eventBusService }, + { + regionService, + productService, + redisClient, + productVariantService, + eventBusService, + }, options ) { super() @@ -13,6 +21,8 @@ class ContentfulService extends BaseService { this.productVariantService_ = productVariantService + this.regionService_ = regionService + this.eventBus_ = eventBusService this.options_ = options @@ -24,16 +34,19 @@ class ContentfulService extends BaseService { this.redis_ = redisClient } - async getIgnoreIds_(type) { - return new Promise((resolve, reject) => { - this.redis_.get(`${type}_ignore_ids`, (err, reply) => { - if (err) { - return reject(err) - } + async addIgnore_(id, side) { + const key = `${id}_ignore_${side}` + return await this.redis_.set( + key, + 1, + "EX", + this.options_.ignore_threshold || IGNORE_THRESHOLD + ) + } - return resolve(JSON.parse(reply)) - }) - }) + async shouldIgnore_(id, side) { + const key = `${id}_ignore_${side}` + return await this.redis_.get(key) } async getContentfulEnvironment_() { @@ -46,12 +59,17 @@ class ContentfulService extends BaseService { } } - async getVariantEntries_(variants) { + async getVariantEntries_(variants, config = { publish: false }) { try { const contentfulVariants = await Promise.all( - variants.map((variant) => - this.updateProductVariantInContentful(variant) - ) + variants.map(async (variant) => { + let updated = await this.updateProductVariantInContentful(variant) + if (config.publish) { + updated = updated.publish() + } + + return updated + }) ) return contentfulVariants @@ -137,13 +155,21 @@ class ContentfulService extends BaseService { }) const environment = await this.getContentfulEnvironment_() - const variantEntries = await this.getVariantEntries_(p.variants) + const variantEntries = await this.getVariantEntries_(p.variants, { + publish: true, + }) const variantLinks = this.getVariantLinks_(variantEntries) const fields = { [this.getCustomField("title", "product")]: { "en-US": p.title, }, + [this.getCustomField("subtitle", "product")]: { + "en-US": p.subtitle, + }, + [this.getCustomField("description", "product")]: { + "en-US": p.description, + }, [this.getCustomField("variants", "product")]: { "en-US": variantLinks, }, @@ -157,7 +183,14 @@ class ContentfulService extends BaseService { if (p.images.length > 0) { const imageLinks = await this.createImageAssets(product) + if (imageLinks) { + fields.images = { + "en-US": imageLinks, + } + } + } + if (p.thumbnail) { const thumbnailAsset = await environment.createAsset({ fields: { title: { @@ -176,7 +209,7 @@ class ContentfulService extends BaseService { }, }) - await thumbnailAsset.processForAllLocales() + await thumbnailAsset.processForAllLocales().then((a) => a.publish()) const thumbnailLink = { sys: { @@ -189,12 +222,6 @@ class ContentfulService extends BaseService { fields.thumbnail = { "en-US": thumbnailLink, } - - if (imageLinks) { - fields.images = { - "en-US": imageLinks, - } - } } if (p.type) { @@ -233,9 +260,6 @@ class ContentfulService extends BaseService { fields, }) - // const ignoreIds = (await this.getIgnoreIds_("product")) || [] - // ignoreIds.push(product.id) - // this.redis_.set("product_ignore_ids", JSON.stringify(ignoreIds)) return result } catch (error) { throw error @@ -282,12 +306,125 @@ class ContentfulService extends BaseService { } } + async createRegionInContentful(region) { + const hasType = await this.getType("region") + .then(() => true) + .catch(() => false) + if (!hasType) { + return + } + try { + const r = await this.regionService_.retrieve(region.id, { + relations: ["countries", "payment_providers", "fulfillment_providers"], + }) + + const environment = await this.getContentfulEnvironment_() + + const fields = { + [this.getCustomField("medusaId", "product")]: { + "en-US": r.id, + }, + [this.getCustomField("name", "region")]: { + "en-US": r.name, + }, + [this.getCustomField("countries", "region")]: { + "en-US": r.countries, + }, + [this.getCustomField("paymentProviders", "region")]: { + "en-US": r.payment_providers, + }, + [this.getCustomField("fulfillmentProviders", "region")]: { + "en-US": r.fulfillment_providers, + }, + } + + const result = await environment.createEntryWithId("region", r.id, { + fields, + }) + + return result + } catch (error) { + throw error + } + } + + async updateRegionInContentful(data) { + const hasType = await this.getType("region") + .then(() => true) + .catch(() => false) + if (!hasType) { + return + } + + const updateFields = [ + "name", + "currency_code", + "countries", + "payment_providers", + "fulfillment_providers", + ] + + const found = data.fields.find((f) => updateFields.includes(f)) + if (!found) { + return + } + + try { + const ignore = await this.shouldIgnore_(data.id, "contentful") + if (ignore) { + return + } + + const r = await this.regionService_.retrieve(data.id, { + relations: ["countries", "payment_providers", "fulfillment_providers"], + }) + + const environment = await this.getContentfulEnvironment_() + + // check if region exists + let regionEntry = undefined + try { + regionEntry = await environment.getEntry(data.id) + } catch (error) { + return this.createRegionInContentful(r) + } + + const regionEntryFields = { + ...regionEntry.fields, + [this.getCustomField("name", "region")]: { + "en-US": r.name, + }, + [this.getCustomField("countries", "region")]: { + "en-US": r.countries, + }, + [this.getCustomField("paymentProviders", "region")]: { + "en-US": r.payment_providers, + }, + [this.getCustomField("fulfillmentProviders", "region")]: { + "en-US": r.fulfillment_providers, + }, + } + + regionEntry.fields = regionEntryFields + + const updatedEntry = await regionEntry.update() + const publishedEntry = await updatedEntry.publish() + + await this.addIgnore_(data.id, "medusa") + + return publishedEntry + } catch (error) { + throw error + } + } + async updateProductInContentful(data) { const updateFields = [ "variants", "options", "tags", "title", + "subtitle", "tags", "type", "type_id", @@ -302,16 +439,10 @@ class ContentfulService extends BaseService { } try { - // const ignoreIds = (await this.getIgnoreIds_("product")) || [] - - // if (ignoreIds.includes(product.id)) { - // const newIgnoreIds = ignoreIds.filter((id) => id !== product.id) - // this.redis_.set("product_ignore_ids", JSON.stringify(newIgnoreIds)) - // return - // } else { - // ignoreIds.push(product.id) - // this.redis_.set("product_ignore_ids", JSON.stringify(ignoreIds)) - // } + const ignore = await this.shouldIgnore_(data.id, "contentful") + if (ignore) { + return + } const p = await this.productService_.retrieve(data.id, { relations: [ @@ -341,6 +472,12 @@ class ContentfulService extends BaseService { [this.getCustomField("title", "product")]: { "en-US": p.title, }, + [this.getCustomField("subtitle", "product")]: { + "en-US": p.subtitle, + }, + [this.getCustomField("description", "product")]: { + "en-US": p.description, + }, [this.getCustomField("options", "product")]: { "en-US": p.options, }, @@ -376,7 +513,7 @@ class ContentfulService extends BaseService { }, }) - await thumbnailAsset.processForAllLocales() + await thumbnailAsset.processForAllLocales().then((a) => a.publish()) const thumbnailLink = { sys: { @@ -430,6 +567,8 @@ class ContentfulService extends BaseService { const updatedEntry = await productEntry.update() const publishedEntry = await updatedEntry.publish() + await this.addIgnore_(data.id, "medusa") + return publishedEntry } catch (error) { throw error @@ -460,19 +599,10 @@ class ContentfulService extends BaseService { } try { - // const ignoreIds = (await this.getIgnoreIds_("product_variant")) || [] - - //if (ignoreIds.includes(variant.id)) { - // const newIgnoreIds = ignoreIds.filter((id) => id !== variant.id) - // this.redis_.set( - // "product_variant_ignore_ids", - // JSON.stringify(newIgnoreIds) - // ) - // return - //} else { - // ignoreIds.push(variant.id) - // this.redis_.set("product_variant_ignore_ids", JSON.stringify(ignoreIds)) - //} + const ignore = await this.shouldIgnore_(variant.id, "contentful") + if (ignore) { + return + } const environment = await this.getContentfulEnvironment_() // check if product exists @@ -512,6 +642,8 @@ class ContentfulService extends BaseService { const updatedEntry = await variantEntry.update() const publishedEntry = await updatedEntry.publish() + await this.addIgnore_(variant.id, "medusa") + return publishedEntry } catch (error) { throw error @@ -519,29 +651,42 @@ class ContentfulService extends BaseService { } async sendContentfulProductToAdmin(productId) { + const ignore = await this.shouldIgnore_(productId, "medusa") + if (ignore) { + return + } + try { const environment = await this.getContentfulEnvironment_() const productEntry = await environment.getEntry(productId) const product = await this.productService_.retrieve(productId) - //const ignoreIds = (await this.getIgnoreIds_("product")) || [] - //if (ignoreIds.includes(productId)) { - // const newIgnoreIds = ignoreIds.filter((id) => id !== productId) - // this.redis_.set("product_ignore_ids", JSON.stringify(newIgnoreIds)) - // return - //} else { - // ignoreIds.push(productId) - // this.redis_.set("product_ignore_ids", JSON.stringify(ignoreIds)) - //} - let update = {} + const title = productEntry.fields[this.getCustomField("title", "product")]["en-US"] + + const subtitle = + productEntry.fields[this.getCustomField("subtitle", "product")]["en-US"] + + const description = + productEntry.fields[this.getCustomField("description", "product")][ + "en-US" + ] + if (product.title !== title) { update.title = title } + if (product.subtitle !== subtitle) { + update.subtitle = subtitle + } + + if (product.description !== description) { + update.description = description + } + // Get the thumbnail, if present if (productEntry.fields.thumbnail) { const thumb = await environment.getAsset( @@ -556,7 +701,9 @@ class ContentfulService extends BaseService { } if (!_.isEmpty(update)) { - await this.productService_.update(productId, update) + await this.productService_.update(productId, update).then(async () => { + return await this.addIgnore_(productId, "contentful") + }) } } catch (error) { throw error @@ -564,32 +711,25 @@ class ContentfulService extends BaseService { } async sendContentfulProductVariantToAdmin(variantId) { + const ignore = this.shouldIgnore_(variantId, "medusa") + if (ignore) { + return + } + try { const environment = await this.getContentfulEnvironment_() const variantEntry = await environment.getEntry(variantId) - // const ignoreIds = (await this.getIgnoreIds_("product_variant")) || [] - // if (ignoreIds.includes(variantId)) { - // const newIgnoreIds = ignoreIds.filter((id) => id !== variantId) - // this.redis_.set( - // "product_variant_ignore_ids", - // JSON.stringify(newIgnoreIds) - // ) - // return - // } else { - // ignoreIds.push(variantId) - // this.redis_.set("product_variant_ignore_ids", JSON.stringify(ignoreIds)) - // } - - const updatedVariant = await this.productVariantService_.update( - variantId, - { + const updatedVariant = await this.productVariantService_ + .update(variantId, { title: variantEntry.fields[this.getCustomField("title", "variant")][ "en-US" ], - } - ) + }) + .then(async () => { + return await this.addIgnore_(variantId, "contentful") + }) return updatedVariant } catch (error) { diff --git a/packages/medusa-plugin-contentful/src/subscribers/contentful.js b/packages/medusa-plugin-contentful/src/subscribers/contentful.js index 301ac00160..6431ea60c4 100644 --- a/packages/medusa-plugin-contentful/src/subscribers/contentful.js +++ b/packages/medusa-plugin-contentful/src/subscribers/contentful.js @@ -10,6 +10,14 @@ class ContentfulSubscriber { this.contentfulService_ = contentfulService this.eventBus_ = eventBusService + this.eventBus_.subscribe("region.created", async (data) => { + await this.contentfulService_.createRegionInContentful(data) + }) + + this.eventBus_.subscribe("region.updated", async (data) => { + await this.contentfulService_.updateRegionInContentful(data) + }) + this.eventBus_.subscribe("product-variant.updated", async (data) => { await this.contentfulService_.updateProductVariantInContentful(data) }) diff --git a/packages/medusa/src/services/__tests__/region.js b/packages/medusa/src/services/__tests__/region.js index bd23d97285..4ea0efa171 100644 --- a/packages/medusa/src/services/__tests__/region.js +++ b/packages/medusa/src/services/__tests__/region.js @@ -1,6 +1,14 @@ import { IdMap, MockManager, MockRepository } from "medusa-test-utils" import RegionService from "../region" +const eventBusService = { + emit: jest.fn(), + withTransaction: function() { + return this + }, +} + + describe("RegionService", () => { describe("create", () => { const regionRepository = MockRepository({}) @@ -58,6 +66,7 @@ describe("RegionService", () => { const regionService = new RegionService({ manager: MockManager, + eventBusService, fulfillmentProviderRepository: fpRepository, paymentProviderRepository: ppRepository, currencyRepository, @@ -168,6 +177,7 @@ describe("RegionService", () => { const regionService = new RegionService({ manager: MockManager, + eventBusService, regionRepository, }) @@ -237,6 +247,7 @@ describe("RegionService", () => { const regionService = new RegionService({ manager: MockManager, + eventBusService, fulfillmentProviderRepository: fpRepository, paymentProviderRepository: ppRepository, regionRepository, @@ -335,6 +346,7 @@ describe("RegionService", () => { const regionService = new RegionService({ manager: MockManager, + eventBusService, fulfillmentProviderRepository: fpRepository, paymentProviderRepository: ppRepository, regionRepository, @@ -380,6 +392,7 @@ describe("RegionService", () => { const regionService = new RegionService({ manager: MockManager, + eventBusService, regionRepository, }) @@ -429,6 +442,7 @@ describe("RegionService", () => { const regionService = new RegionService({ manager: MockManager, + eventBusService, regionRepository, countryRepository, }) @@ -473,6 +487,7 @@ describe("RegionService", () => { const regionService = new RegionService({ manager: MockManager, + eventBusService, regionRepository, }) @@ -522,6 +537,7 @@ describe("RegionService", () => { const regionService = new RegionService({ manager: MockManager, + eventBusService, fulfillmentProviderRepository: fpRepository, paymentProviderRepository: ppRepository, regionRepository, @@ -582,6 +598,7 @@ describe("RegionService", () => { const regionService = new RegionService({ manager: MockManager, + eventBusService, fulfillmentProviderRepository: fpRepository, regionRepository, }) @@ -631,6 +648,7 @@ describe("RegionService", () => { const regionService = new RegionService({ manager: MockManager, + eventBusService, regionRepository, }) @@ -665,6 +683,7 @@ describe("RegionService", () => { const regionService = new RegionService({ manager: MockManager, + eventBusService, regionRepository, }) diff --git a/packages/medusa/src/services/region.js b/packages/medusa/src/services/region.js index cec4c6690a..33a56ff3c7 100644 --- a/packages/medusa/src/services/region.js +++ b/packages/medusa/src/services/region.js @@ -8,11 +8,17 @@ import { countries } from "../utils/countries" * @implements BaseService */ class RegionService extends BaseService { + static Events = { + UPDATED: "region.updated", + CREATED: "region.created", + } + constructor({ manager, regionRepository, countryRepository, storeService, + eventBusService, currencyRepository, paymentProviderRepository, fulfillmentProviderRepository, @@ -33,6 +39,9 @@ class RegionService extends BaseService { /** @private @const {StoreService} */ this.storeService_ = storeService + /** @private @const {EventBus} */ + this.eventBus_ = eventBusService + /** @private @const {CurrencyRepository} */ this.currencyRepository_ = currencyRepository @@ -60,6 +69,7 @@ class RegionService extends BaseService { currencyRepository: this.currencyRepository_, countryRepository: this.countryRepository_, storeService: this.storeService_, + eventBusService: this.eventBus_, paymentProviderRepository: this.paymentProviderRepository_, paymentProviderService: this.paymentProviderService_, fulfillmentProviderRepository: this.fulfillmentProviderRepository_, @@ -117,6 +127,13 @@ class RegionService extends BaseService { const created = regionRepository.create(regionObject) const result = await regionRepository.save(created) + + await this.eventBus_ + .withTransaction(manager) + .emit(RegionService.Events.CREATED, { + id: result.id, + }) + return result }) } @@ -168,6 +185,14 @@ class RegionService extends BaseService { } const result = await regionRepository.save(region) + + await this.eventBus_ + .withTransaction(manager) + .emit(RegionService.Events.UPDATED, { + id: result.id, + fields: Object.keys(update), + }) + return result }) } @@ -390,6 +415,14 @@ class RegionService extends BaseService { region.countries = [...(region.countries || []), country] const updated = await regionRepo.save(region) + + await this.eventBus_ + .withTransaction(manager) + .emit(RegionService.Events.UPDATED, { + id: updated.id, + fields: ["countries"], + }) + return updated }) } @@ -419,6 +452,12 @@ class RegionService extends BaseService { ) const updated = await regionRepo.save(region) + await this.eventBus_ + .withTransaction(manager) + .emit(RegionService.Events.UPDATED, { + id: updated.id, + fields: ["countries"], + }) return updated }) } @@ -458,6 +497,14 @@ class RegionService extends BaseService { region.payment_providers = [...region.payment_providers, pp] const updated = await regionRepo.save(region) + + await this.eventBus_ + .withTransaction(manager) + .emit(RegionService.Events.UPDATED, { + id: updated.id, + fields: ["payment_providers"], + }) + return updated }) } @@ -497,6 +544,12 @@ class RegionService extends BaseService { region.fulfillment_providers = [...region.fulfillment_providers, fp] const updated = await regionRepo.save(region) + await this.eventBus_ + .withTransaction(manager) + .emit(RegionService.Events.UPDATED, { + id: updated.id, + fields: ["fulfillment_providers"], + }) return updated }) } @@ -525,6 +578,12 @@ class RegionService extends BaseService { ) const updated = await regionRepo.save(region) + await this.eventBus_ + .withTransaction(manager) + .emit(RegionService.Events.UPDATED, { + id: updated.id, + fields: ["payment_providers"], + }) return updated }) } @@ -553,23 +612,15 @@ class RegionService extends BaseService { ) const updated = await regionRepo.save(region) + await this.eventBus_ + .withTransaction(manager) + .emit(RegionService.Events.UPDATED, { + id: updated.id, + fields: ["fulfillment_providers"], + }) return updated }) } - - /** - * Decorates a region - * @param {object} region - the region to decorate - * @param {[string]} fields - the fields to include - * @param {[string]} expandFields - the fields to expand - * @return {Region} the region - */ - async decorate(region, fields, expandFields = []) { - const requiredFields = ["id", "metadata"] - const decorated = _.pick(region, fields.concat(requiredFields)) - const final = await this.runDecorators_(decorated) - return final - } } export default RegionService