diff --git a/imports/plugins/core/dashboard/client/containers/toolbarContainer.js b/imports/plugins/core/dashboard/client/containers/toolbarContainer.js index 8c235e40f00..0a5ecbc4775 100644 --- a/imports/plugins/core/dashboard/client/containers/toolbarContainer.js +++ b/imports/plugins/core/dashboard/client/containers/toolbarContainer.js @@ -51,7 +51,12 @@ function composer(props, onData) { if (user && user.roles) { // Get all shops for which user has roles - shops = Shops.find({ _id: { $in: Object.keys(user.roles) } }).fetch(); + shops = Shops.find({ + $and: [ + { _id: { $in: Object.keys(user.roles) } }, + { $or: [{ "workflow.status": "active" }, { _id: Reaction.getPrimaryShopId() }] } + ] + }).fetch(); } // Standard variables diff --git a/imports/plugins/included/product-variant/containers/productsContainer.js b/imports/plugins/included/product-variant/containers/productsContainer.js index c833551b1e0..dd027fb8fdb 100644 --- a/imports/plugins/included/product-variant/containers/productsContainer.js +++ b/imports/plugins/included/product-variant/containers/productsContainer.js @@ -8,7 +8,7 @@ import { Reaction } from "/client/api"; import { ITEMS_INCREMENT } from "/client/config/defaults"; import { ReactionProduct } from "/lib/api"; import { applyProductRevision } from "/lib/api/products"; -import { Products, Tags } from "/lib/collections"; +import { Products, Tags, Shops } from "/lib/collections"; import ProductsComponent from "../components/products"; /** @@ -166,9 +166,17 @@ function composer(props, onData) { window.prerenderReady = true; } + const activeShopsIds = Shops.find({ + $or: [ + { "workflow.status": "active" }, + { _id: Reaction.getPrimaryShopId() } + ] + }).fetch().map(activeShop => activeShop._id); + const productCursor = Products.find({ ancestors: [], - type: { $in: ["simple"] } + type: { $in: ["simple"] }, + shopId: { $in: activeShopsIds } }); const products = productCursor.map((product) => { diff --git a/server/imports/fixtures/shops.js b/server/imports/fixtures/shops.js index ffb5ba8403a..9a320ff661e 100755 --- a/server/imports/fixtures/shops.js +++ b/server/imports/fixtures/shops.js @@ -1,6 +1,7 @@ import faker from "faker"; import _ from "lodash"; import { Factory } from "meteor/dburles:factory"; +import { Random } from "meteor/random"; import { Shops } from "/lib/collections"; export function getShop() { @@ -33,116 +34,147 @@ export function getAddress(options = {}) { return _.defaults(options, defaults); } - -export function createShopFactory() { - Factory.define("shop", Shops, { - name: faker.internet.domainName(), - description: faker.company.catchPhrase(), - keywords: faker.company.bsAdjective(), - addressBook: [ getAddress() ], - domains: ["localhost"], - emails: [ - { - address: faker.internet.email(), - verified: faker.random.boolean() - } - ], - currency: "USD", // could use faker.finance.currencyCode() - currencies: { - USD: { - format: "%s%v", - symbol: "$" - }, - EUR: { - format: "%v %s", - symbol: "€", - decimal: ",", - thousand: "." - } +const shop = { + name: faker.internet.domainName(), + description: faker.company.catchPhrase(), + keywords: faker.company.bsAdjective(), + addressBook: [ getAddress() ], + domains: ["localhost"], + emails: [ + { + address: faker.internet.email(), + verified: faker.random.boolean() + } + ], + currency: "USD", // could use faker.finance.currencyCode() + currencies: { + USD: { + format: "%s%v", + symbol: "$" }, - locale: "en", - locales: { - continents: { - NA: "North America" - }, - countries: { - US: { - name: "United States", - native: "United States", - phone: "1", - continent: "NA", - capital: "Washington D.C.", - currency: "USD,USN,USS", - languages: "en" - } - } + EUR: { + format: "%v %s", + symbol: "€", + decimal: ",", + thousand: "." + } + }, + locale: "en", + locales: { + continents: { + NA: "North America" }, - baseUOL: "in", - unitsOfLength: [{ - uol: "in", - label: "Inches", - default: true - }, { - uol: "cm", - label: "Centimeters" - }, { - uol: "ft", - label: "Feet" - }], - baseUOM: "oz", - unitsOfMeasure: [{ - uom: "oz", - label: "Ounces", - default: true - }, { - uom: "lb", - label: "Pounds" - }, { - uom: "g", - label: "Grams" - }, { - uom: "kg", - label: "Kilograms" - }], - layout: [{ - layout: "coreLayout", - workflow: "coreLayout", - theme: "default", - enabled: true - }, { - layout: "coreLayout", - workflow: "coreCartWorkflow", - collection: "Cart", - theme: "default", - enabled: true - }, { - layout: "coreLayout", - workflow: "coreOrderWorkflow", - collection: "Orders", - theme: "default", - enabled: true - }, { - layout: "coreLayout", - workflow: "coreOrderShipmentWorkflow", - collection: "Orders", - theme: "default", - enabled: true - }], - public: true, - brandAssets: [ - { - mediaId: "J8Bhq3uTtdgwZx3rz", - type: "navbarBrandImage" + countries: { + US: { + name: "United States", + native: "United States", + phone: "1", + continent: "NA", + capital: "Washington D.C.", + currency: "USD,USN,USS", + languages: "en" } - ], - timezone: "US/Pacific", - metafields: [], - // one shop in the marketplace is required as default shop. This is used to control marketplace settings. - // Not having a primary shop will cause test failures - shopType: "primary", - createdAt: new Date, - updatedAt: new Date() - }); + } + }, + baseUOL: "in", + unitsOfLength: [{ + uol: "in", + label: "Inches", + default: true + }, { + uol: "cm", + label: "Centimeters" + }, { + uol: "ft", + label: "Feet" + }], + baseUOM: "oz", + unitsOfMeasure: [{ + uom: "oz", + label: "Ounces", + default: true + }, { + uom: "lb", + label: "Pounds" + }, { + uom: "g", + label: "Grams" + }, { + uom: "kg", + label: "Kilograms" + }], + layout: [{ + layout: "coreLayout", + workflow: "coreLayout", + theme: "default", + enabled: true + }, { + layout: "coreLayout", + workflow: "coreCartWorkflow", + collection: "Cart", + theme: "default", + enabled: true + }, { + layout: "coreLayout", + workflow: "coreOrderWorkflow", + collection: "Orders", + theme: "default", + enabled: true + }, { + layout: "coreLayout", + workflow: "coreOrderShipmentWorkflow", + collection: "Orders", + theme: "default", + enabled: true + }], + workflow: { + status: "active" + }, + public: true, + brandAssets: [ + { + mediaId: "J8Bhq3uTtdgwZx3rz", + type: "navbarBrandImage" + } + ], + timezone: "US/Pacific", + metafields: [], + // one shop in the marketplace is required as default shop. This is used to control marketplace settings. + // Not having a primary shop will cause test failures + shopType: "primary", + createdAt: new Date, + updatedAt: new Date() +}; + +const activeShop = { + workflow: { + status: "active" + }, + _id: Random.id() +}; + +export function createActiveShop(options = {}) { + const existingActiveShop = Shops.findOne({ "workflow.status": "active", ...options }); + + // If we found an existingActiveShop, return it + if (existingActiveShop) { + return existingActiveShop; + } + // Otherwise, we need to create a new shop from the factory + + // Setup the activeShop factory definition + createActiveShopFactory(); + + // Create a new shop from the factory with the provided options + return Factory.create("activeShop", options); +} + +export function createShopFactory() { + Factory.define("shop", Shops, shop); +} + +export function createActiveShopFactory() { + Factory.define("activeShop", Shops, Object.assign({}, shop, activeShop)); } @@ -152,4 +184,5 @@ export default function () { * @summary define shop Factory */ createShopFactory(); + createActiveShopFactory(); } diff --git a/server/publications/collections/product-publications.app-test.js b/server/publications/collections/product-publications.app-test.js index 7049ef16e77..55991db2d1b 100644 --- a/server/publications/collections/product-publications.app-test.js +++ b/server/publications/collections/product-publications.app-test.js @@ -3,7 +3,7 @@ import { Random } from "meteor/random"; import { expect } from "meteor/practicalmeteor:chai"; import { sinon } from "meteor/practicalmeteor:sinon"; import { Roles } from "meteor/alanning:roles"; -import { getShop } from "/server/imports/fixtures/shops"; +import { createActiveShop } from "/server/imports/fixtures/shops"; import { Reaction } from "/server/api"; import * as Collections from "/lib/collections"; import Fixtures from "/server/imports/fixtures"; @@ -13,10 +13,12 @@ import { RevisionApi } from "/imports/plugins/core/revisions/lib/api/revisions"; Fixtures(); describe("Publication", function () { - const shop = getShop(); + const shopId = Random.id(); let sandbox; beforeEach(function () { + Collections.Shops.remove({}); + createActiveShop({ _id: shopId }); sandbox = sinon.sandbox.create(); sandbox.stub(RevisionApi, "isRevisionControlEnabled", () => true); }); @@ -45,7 +47,7 @@ describe("Publication", function () { Collections.Products.insert({ ancestors: [], title: "My Little Pony", - shopId: shop._id, + shopId: shopId, type: "simple", price: priceRangeA, isVisible: false, @@ -57,7 +59,7 @@ describe("Publication", function () { Collections.Products.insert({ ancestors: [], title: "Shopkins - Peachy", - shopId: shop._id, + shopId: shopId, price: priceRangeB, type: "simple", isVisible: true, @@ -69,7 +71,7 @@ describe("Publication", function () { Collections.Products.insert({ ancestors: [], title: "Fresh Tomatoes", - shopId: shop._id, + shopId: shopId, price: priceRangeA, type: "simple", isVisible: true, @@ -82,10 +84,10 @@ describe("Publication", function () { describe("Products", function () { it("should return all products to admins", function (done) { // setup - sandbox.stub(Reaction, "getShopId", () => shop._id); + sandbox.stub(Reaction, "getShopId", () => shopId); sandbox.stub(Roles, "userIsInRole", () => true); sandbox.stub(Reaction, "hasPermission", () => true); - sandbox.stub(Reaction, "getShopsWithRoles", () => [shop._id]); + sandbox.stub(Reaction, "getShopsWithRoles", () => [shopId]); const collector = new PublicationCollector({ userId: Random.id() }); let isDone = false; @@ -103,10 +105,10 @@ describe("Publication", function () { it("should have an expected product title", function (done) { // setup - sandbox.stub(Reaction, "getShopId", () => shop._id); + sandbox.stub(Reaction, "getShopId", () => shopId); sandbox.stub(Roles, "userIsInRole", () => true); sandbox.stub(Reaction, "hasPermission", () => true); - sandbox.stub(Reaction, "getShopsWithRoles", () => [shop._id]); + sandbox.stub(Reaction, "getShopsWithRoles", () => [shopId]); const collector = new PublicationCollector({ userId: Random.id() }); let isDone = false; @@ -126,7 +128,7 @@ describe("Publication", function () { }); it("should return only visible products to visitors", function (done) { - sandbox.stub(Reaction, "getShopId", () => shop._id); + sandbox.stub(Reaction, "getShopId", () => shopId); sandbox.stub(Roles, "userIsInRole", () => false); const collector = new PublicationCollector({ userId: Random.id() }); @@ -150,7 +152,7 @@ describe("Publication", function () { it("should return only products matching query", function (done) { const productScrollLimit = 24; const filters = { query: "Shopkins" }; - sandbox.stub(Reaction, "getShopId", () => shop._id); + sandbox.stub(Reaction, "getShopId", () => shopId); sandbox.stub(Roles, "userIsInRole", () => false); const collector = new PublicationCollector({ userId: Random.id() }); @@ -168,7 +170,7 @@ describe("Publication", function () { it("should not return products not matching query", function (done) { const productScrollLimit = 24; const filters = { query: "random search" }; - sandbox.stub(Reaction, "getShopId", () => shop._id); + sandbox.stub(Reaction, "getShopId", () => shopId); sandbox.stub(Roles, "userIsInRole", () => false); const collector = new PublicationCollector({ userId: Random.id() }); @@ -185,7 +187,7 @@ describe("Publication", function () { it("should return products in price.min query", function (done) { const productScrollLimit = 24; const filters = { "price.min": "2.00" }; - sandbox.stub(Reaction, "getShopId", () => shop._id); + sandbox.stub(Reaction, "getShopId", () => shopId); sandbox.stub(Roles, "userIsInRole", () => false); const collector = new PublicationCollector({ userId: Random.id() }); @@ -202,7 +204,7 @@ describe("Publication", function () { it("should return products in price.max query", function (done) { const productScrollLimit = 24; const filters = { "price.max": "24.00" }; - sandbox.stub(Reaction, "getShopId", () => shop._id); + sandbox.stub(Reaction, "getShopId", () => shopId); sandbox.stub(Roles, "userIsInRole", () => false); const collector = new PublicationCollector({ userId: Random.id() }); @@ -219,7 +221,7 @@ describe("Publication", function () { it("should return products in price.min - price.max range query", function (done) { const productScrollLimit = 24; const filters = { "price.min": "12.00", "price.max": "19.98" }; - sandbox.stub(Reaction, "getShopId", () => shop._id); + sandbox.stub(Reaction, "getShopId", () => shopId); sandbox.stub(Roles, "userIsInRole", () => false); const collector = new PublicationCollector({ userId: Random.id() }); @@ -236,7 +238,7 @@ describe("Publication", function () { it("should return products where value is in price set query", function (done) { const productScrollLimit = 24; const filters = { "price.min": "13.00", "price.max": "24.00" }; - sandbox.stub(Reaction, "getShopId", () => shop._id); + sandbox.stub(Reaction, "getShopId", () => shopId); sandbox.stub(Roles, "userIsInRole", () => false); const collector = new PublicationCollector({ userId: Random.id() }); @@ -251,12 +253,12 @@ describe("Publication", function () { }); it("should return products from all shops when multiple shops are provided", function (done) { - const filters = { shops: [shop._id] }; + const filters = { shops: [shopId] }; const productScrollLimit = 24; sandbox.stub(Reaction, "getCurrentShop", function () {return { _id: "123" };}); sandbox.stub(Roles, "userIsInRole", () => true); sandbox.stub(Reaction, "hasPermission", () => true); - sandbox.stub(Reaction, "getShopsWithRoles", () => [shop._id]); + sandbox.stub(Reaction, "getShopsWithRoles", () => [shopId]); const collector = new PublicationCollector({ userId: Random.id() }); let isDone = false; @@ -281,7 +283,7 @@ describe("Publication", function () { const product = Collections.Products.findOne({ isVisible: true }); - sandbox.stub(Reaction, "getShopId", () => shop._id); + sandbox.stub(Reaction, "getShopId", () => shopId); const collector = new PublicationCollector({ userId: Random.id() }); @@ -296,7 +298,7 @@ describe("Publication", function () { }); it("should return a product based on a regex", function (done) { - sandbox.stub(Reaction, "getShopId", () => shop._id); + sandbox.stub(Reaction, "getShopId", () => shopId); const collector = new PublicationCollector({ userId: Random.id() }); @@ -311,7 +313,7 @@ describe("Publication", function () { }); it("should not return a product based on a regex if it isn't visible", function (done) { - sandbox.stub(Reaction, "getShopId", () => shop._id); + sandbox.stub(Reaction, "getShopId", () => shopId); sandbox.stub(Roles, "userIsInRole", () => false); const collector = new PublicationCollector({ userId: Random.id() }); @@ -334,7 +336,7 @@ describe("Publication", function () { }); it("should return a product based on a regex to admin even if it isn't visible", function (done) { - sandbox.stub(Reaction, "getShopId", () => shop._id); + sandbox.stub(Reaction, "getShopId", () => shopId); sandbox.stub(Roles, "userIsInRole", () => true); sandbox.stub(Reaction, "hasPermission", () => true); diff --git a/server/publications/collections/products.js b/server/publications/collections/products.js index ee52500a466..b6f269432e1 100644 --- a/server/publications/collections/products.js +++ b/server/publications/collections/products.js @@ -95,6 +95,14 @@ Meteor.publish("Products", function (productScrollLimit = 24, productFilters, so return this.ready(); } + // Get active shop id's to use for filtering + const activeShopsIds = Shops.find({ + $or: [ + { "workflow.status": "active" }, + { _id: Reaction.getPrimaryShopId() } + ] + }).fetch().map(activeShop => activeShop._id); + // if there are filter/params that don't match the schema // validate, catch except but return no results try { @@ -278,7 +286,7 @@ Meteor.publish("Products", function (productScrollLimit = 24, productFilters, so $in: [true, false, null, undefined] }; selector.shopId = { - $in: userAdminShopIds + $in: activeShopsIds }; // Get _ids of top-level products @@ -517,20 +525,11 @@ Meteor.publish("Products", function (productScrollLimit = 24, productFilters, so }); } - // Get disabled shop id's to use for filtering - const disabledShopIds = Shops.find({ - "workflow.status": { - $in: ["disabled", "archived"] - } - }, { - fields: { _id: 1 } - }).map((shop) => shop._id); - - // Adjust the selector to exclude all disabled shops + // Adjust the selector to include only active shops newSelector = { ...newSelector, shopId: { - $nin: disabledShopIds + $in: activeShopsIds } };