Skip to content

Commit

Permalink
Merge pull request #6053 from reactioncommerce/fix-aldeed-product-fixes
Browse files Browse the repository at this point in the history
[3.0.0] Various product GQL fixes
  • Loading branch information
mikemurray authored Jan 27, 2020
2 parents 3164969 + 9497cbb commit ba3cb38
Show file tree
Hide file tree
Showing 19 changed files with 241 additions and 382 deletions.
2 changes: 1 addition & 1 deletion src/core-services/account/mutations/createAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default async function createAccount(context, input) {

// If we didn't already upgrade them to the "owner" group, see if they're been invited to any groups
if (groupSlug === "customer") {
const emailAddresses = emails.map((emailRecord) => emailRecord.address);
const emailAddresses = emails.map((emailRecord) => emailRecord.address.toLowerCase());
// Find all invites for all shops and add to all groups
invites = await AccountInvites.find({ email: { $in: emailAddresses } }).toArray();
groups = invites.map((invite) => invite.groupId);
Expand Down
16 changes: 7 additions & 9 deletions src/core-services/account/mutations/inviteShopMember.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const inputSchema = new SimpleSchema({
export default async function inviteShopMember(context, input) {
inputSchema.validate(input);
const { collections, user: userFromContext } = context;
const { Accounts, AccountInvites, Groups, Shops, users } = collections;
const { Accounts, AccountInvites, Groups, Shops } = collections;
const {
email,
groupId,
Expand All @@ -54,14 +54,12 @@ export default async function inviteShopMember(context, input) {
throw new ReactionError("bad-request", "Cannot directly invite owner");
}

// check to see if invited user has an account
const invitedUser = await users.findOne({ "emails.address": email });
const lowercaseEmail = email.toLowerCase();

if (invitedUser) {
// make sure user has an account
const invitedAccount = await Accounts.findOne({ userId: invitedUser._id }, { projection: { _id: 1 } });
if (!invitedAccount) throw new ReactionError("not-found", "User found but matching account not found");
// check to see if invited email has an account
const invitedAccount = await Accounts.findOne({ "emails.address": lowercaseEmail }, { projection: { _id: 1 } });

if (invitedAccount) {
// Set the account's permission group for this shop
await context.mutations.addAccountToGroup(context, {
accountId: invitedAccount._id,
Expand All @@ -79,7 +77,7 @@ export default async function inviteShopMember(context, input) {
// Create an AccountInvites document. If a person eventually creates an account with this email address,
// it will be automatically added to this group instead of the default group for this shop.
await AccountInvites.updateOne({
email,
email: lowercaseEmail,
shopId
}, {
$set: {
Expand Down Expand Up @@ -115,7 +113,7 @@ export default async function inviteShopMember(context, input) {
data: dataForEmail,
fromShop: shop,
templateName: "accounts/inviteNewShopMember",
to: email
to: lowercaseEmail
});

return null;
Expand Down
60 changes: 24 additions & 36 deletions src/core-services/catalog/startup.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,6 @@
import Logger from "@reactioncommerce/logger";
import hashProduct from "./mutations/hashProduct.js";

/**
* @summary Recalculate the currentProductHash for the related product
* @param {Object} productId The product to hash
* @param {Object} collections Map of MongoDB collections
* @returns {Promise<null>} Null
*/
async function hashRelatedProduct(productId, collections) {
if (productId) {
hashProduct(productId, collections, false)
.catch((error) => {
Logger.error(`Error updating currentProductHash for product with ID ${productId}`, error);
});
}

return null;
}

/**
* @summary Called on startup
* @param {Object} context Startup context
Expand All @@ -29,23 +12,29 @@ export default async function catalogStartup(context) {

appEvents.on("afterMediaInsert", ({ mediaRecord }) => {
const { productId } = mediaRecord.metadata || {};
hashRelatedProduct(productId, collections).catch((error) => {
Logger.error("Error in afterMediaInsert", error);
});
if (productId) {
hashProduct(productId, collections, false).catch((error) => {
Logger.error(`Error updating currentProductHash for product with ID ${productId}`, error);
});
}
});

appEvents.on("afterMediaUpdate", ({ mediaRecord }) => {
const { productId } = mediaRecord.metadata || {};
hashRelatedProduct(productId, collections).catch((error) => {
Logger.error("Error in afterMediaUpdate", error);
});
if (productId) {
hashProduct(productId, collections, false).catch((error) => {
Logger.error(`Error updating currentProductHash for product with ID ${productId}`, error);
});
}
});

appEvents.on("afterMediaRemove", ({ mediaRecord }) => {
const { productId } = mediaRecord.metadata || {};
hashRelatedProduct(productId, collections).catch((error) => {
Logger.error("Error in afterMediaRemove", error);
});
if (productId) {
hashProduct(productId, collections, false).catch((error) => {
Logger.error(`Error updating currentProductHash for product with ID ${productId}`, error);
});
}
});

appEvents.on("afterProductSoftDelete", ({ product }) => {
Expand All @@ -58,15 +47,14 @@ export default async function catalogStartup(context) {
});
});

appEvents.on("afterProductUpdate", ({ productId }) => {
hashRelatedProduct(productId, collections).catch((error) => {
Logger.error("Error in afterProductUpdate", error);
});
});
const productOrVariantUpdateHandler = ({ productId }) => {
if (productId) {
hashProduct(productId, collections, false).catch((error) => {
Logger.error(`Error updating currentProductHash for product with ID ${productId}`, error);
});
}
};

appEvents.on("afterVariantUpdate", async ({ productId }) => {
hashRelatedProduct(productId, collections).catch((error) => {
Logger.error("Error in afterVariantUpdate", error);
});
});
appEvents.on("afterProductUpdate", productOrVariantUpdateHandler);
appEvents.on("afterVariantUpdate", productOrVariantUpdateHandler);
}
54 changes: 31 additions & 23 deletions src/core-services/product/mutations/createProduct.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import SimpleSchema from "simpl-schema";
import Random from "@reactioncommerce/random";
import ReactionError from "@reactioncommerce/reaction-error";
import createProductOrVariant from "../utils/createProductOrVariant.js";
import { Product } from "../simpleSchemas.js";

const inputSchema = new SimpleSchema({
shopId: String
Expand All @@ -25,34 +24,43 @@ export default async function createProduct(context, input) {
await context.validatePermissions("reaction:legacy:products", "create", { shopId });

const newProductId = Random.id();
const createdAt = new Date();
const newProduct = {
_id: newProductId,
ancestors: [],
createdAt,
handle: "",
isDeleted: false,
isVisible: false,
shopId,
type: "simple"
shouldAppearInSitemap: true,
supportedFulfillmentTypes: ["shipping"],
title: "",
type: "simple",
updatedAt: createdAt,
workflow: {
status: "new"
}
};

// Create a product
const createdProductId = await createProductOrVariant(context, newProduct);

// Get full product document to create variant
const createdProduct = await Products.findOne({ _id: createdProductId });

if (!createdProduct) {
throw new ReactionError("server-error", "Unable to find created product");
// Apply custom transformations from plugins.
for (const customFunc of context.getFunctionsOfType("mutateNewProductBeforeCreate")) {
// Functions of type "mutateNewProductBeforeCreate" are expected to mutate the provided variant.
// We need to run each of these functions in a series, rather than in parallel, because
// we are mutating the same object on each pass.
// eslint-disable-next-line no-await-in-loop
await customFunc(newProduct, { context });
}

// Create a product variant
const newVariantId = Random.id();
const createdVariantId = await createProductOrVariant(context, {
_id: newVariantId,
ancestors: [createdProductId],
shopId,
type: "variant" // needed for multi-schema
}, { product: createdProduct, parentVariant: null, isOption: false });
Product.validate(newProduct);

if (!createdVariantId) {
throw new ReactionError("server-error", "Unable to create product variant");
}
await Products.insertOne(newProduct);

// Create one initial product variant for it
await context.mutations.createProductVariant(context.getInternalContext(), {
productId: newProductId,
shopId
});

return createdProduct;
return newProduct;
}
43 changes: 28 additions & 15 deletions src/core-services/product/mutations/createProductVariant.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import SimpleSchema from "simpl-schema";
import Logger from "@reactioncommerce/logger";
import Random from "@reactioncommerce/random";
import ReactionError from "@reactioncommerce/reaction-error";
import createProductOrVariant from "../utils/createProductOrVariant.js";
import { ProductVariant } from "../simpleSchemas.js";
import isAncestorDeleted from "../utils/isAncestorDeleted.js";

const inputSchema = new SimpleSchema({
Expand Down Expand Up @@ -52,32 +52,45 @@ export default async function createProductVariant(context, input) {
}

// get ancestors to build new ancestors array
const { ancestors } = parentProduct;
Array.isArray(ancestors) && ancestors.push(productId);
let { ancestors } = parentProduct;
if (Array.isArray(ancestors)) {
ancestors.push(productId);
} else {
ancestors = [productId];
}

const newVariantId = Random.id();
const createdAt = new Date();
const newVariant = {
_id: newVariantId,
ancestors,
shopId: product.shopId,
type: "variant"
createdAt,
isDeleted: false,
isVisible: false,
shopId,
type: "variant",
updatedAt: createdAt,
workflow: {
status: "new"
}
};

const isOption = ancestors.length > 1;

const createdVariantId = await createProductOrVariant(context, newVariant, { product, parentVariant, isOption });

if (!createdVariantId) {
throw new ReactionError("server-error", "Unable to create product variant");
// Apply custom transformations from plugins.
for (const customFunc of context.getFunctionsOfType("mutateNewVariantBeforeCreate")) {
// Functions of type "mutateNewVariantBeforeCreate" are expected to mutate the provided variant.
// We need to run each of these functions in a series, rather than in parallel, because
// we are mutating the same object on each pass.
// eslint-disable-next-line no-await-in-loop
await customFunc(newVariant, { context, isOption, parentVariant, product });
}

Logger.debug(`createProductVariant: created variant: ${createdVariantId} for ${productId}`);
ProductVariant.validate(newVariant);

const createdVariant = await Products.findOne({ _id: createdVariantId });
await Products.insertOne(newVariant);

if (!createdVariant) {
throw new ReactionError("server-error", "Unable to retrieve newly created product variant");
}
Logger.debug(`createProductVariant: created variant: ${newVariantId} for ${productId}`);

return createdVariant;
return newVariant;
}
24 changes: 8 additions & 16 deletions src/core-services/product/mutations/updateProduct.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ const metafieldInputSchema = new SimpleSchema({
});

const inputSchema = new SimpleSchema({
"_id": {
type: String,
optional: true
},
"description": {
type: String,
optional: true
Expand All @@ -55,14 +51,6 @@ const inputSchema = new SimpleSchema({
type: String,
optional: true
},
"hashtags": {
type: Array,
optional: true
},
"hashtags.$": {
type: String,
optional: true
},
"isDeleted": {
type: Boolean,
optional: true
Expand Down Expand Up @@ -161,9 +149,15 @@ export default async function updateProduct(context, input) {
updateDocument.handle = await createHandle(context, getSlug(productInput.title), productId, shopId);
}

if (Object.keys(updateDocument).length === 0) {
throw new ReactionError("invalid-param", "At least one field to update must be provided");
}

inputSchema.validate(updateDocument);

await Products.updateOne(
updateDocument.updatedAt = new Date();

const { value: updatedProduct } = await Products.findOneAndUpdate(
{
_id: productId,
shopId
Expand All @@ -176,9 +170,7 @@ export default async function updateProduct(context, input) {
}
);

const updatedProduct = await Products.findOne({ _id: productId, shopId });

appEvents.emit("afterProductUpdate", { productId, product: updatedProduct });
await appEvents.emit("afterProductUpdate", { productId, product: updatedProduct });

return updatedProduct;
}
Loading

0 comments on commit ba3cb38

Please sign in to comment.