Skip to content
This repository has been archived by the owner on Sep 21, 2022. It is now read-only.

Allow inviteShopMember to accept multiple groups #10

Merged
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8746fd3
refactor: rename service to plugin
loan-laux May 2, 2020
f7dd554
chore: add github issue template to repository
kieckhafer Apr 7, 2020
938f6b2
chore: minor change to trigger stuck CI build
kieckhafer Apr 7, 2020
1b8e119
chore: minor change to trigger stuck CI build
kieckhafer Apr 7, 2020
23f1918
feat: move AddressType schema from address-validation to accounts
kieckhafer Apr 8, 2020
7040586
feat: move AddressConnection schema from address-validation to accounts
kieckhafer Apr 8, 2020
06f1e62
feat: move AddressConnection to core
kieckhafer Apr 9, 2020
737378f
chore: remove unused bin folder from ci config
kieckhafer Apr 13, 2020
478ff64
feat: use groupIds on input schema
loan-laux Apr 29, 2020
82234ba
feat: check that groups exist
loan-laux Apr 29, 2020
af4f087
feat: insert groupIds into AccountInvites
loan-laux Apr 29, 2020
8f66d0b
fix: update email sending logic to use new template variable
loan-laux Apr 30, 2020
e3518e9
feat: use hasMultipleGroups for plural form
loan-laux Apr 30, 2020
4138652
fix: reduce groupIds on account creation
loan-laux Apr 30, 2020
1d23744
chore: deprecate groupId while keeping it
loan-laux May 2, 2020
c8ba1b8
Merge branch 'trunk' into outgrow-inviteShopMember-groupIds
loan-laux May 2, 2020
36fd4ae
refactor: add deprecation message for groupId
loan-laux May 8, 2020
d988d57
fix: shopId for permission check
loan-laux May 26, 2020
9b63e7b
fix: check for empty accounts with length === 0
loan-laux May 26, 2020
2f5cb7f
feat: pass groupName to e-mail template for backwards compatibility
loan-laux May 26, 2020
54978ee
refactor: pass groupNames to e-mail template as array
loan-laux May 26, 2020
e0e22f0
refactor: re-order param checks
loan-laux May 26, 2020
7e2c41c
feat: add support for groupId and prevent duplicates
loan-laux May 26, 2020
d37f8d1
Merge branch 'trunk' of https://github.com/reactioncommerce/api-plugi…
loan-laux May 26, 2020
8a398c1
refactor: use Set everywhere + use updateGroupsForAccounts
loan-laux May 26, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/mutations/createAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export default async function createAccount(context, input) {
// find all invites for this email address, for all shops, and add to all groups
const emailAddresses = emails.map((emailRecord) => emailRecord.address.toLowerCase());
invites = await AccountInvites.find({ email: { $in: emailAddresses } }).toArray();
groups = invites.map((invite) => invite.groupId);
groups = invites.reduce((allGroupIds, invite) => [...allGroupIds, ...invite.groupIds], []);
loan-laux marked this conversation as resolved.
Show resolved Hide resolved
}

AccountSchema.validate(account);
Expand Down
66 changes: 54 additions & 12 deletions src/mutations/inviteShopMember.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const { REACTION_ADMIN_PUBLIC_ACCOUNT_REGISTRATION_URL } = config;

const inputSchema = new SimpleSchema({
email: String,
groupId: String,
groupIds: Array,
"groupIds.$": String,
name: String,
shopId: String
});
Expand All @@ -22,7 +23,7 @@ const inputSchema = new SimpleSchema({
* @param {Object} context - GraphQL execution context
* @param {Object} input - Necessary input for mutation. See SimpleSchema.
* @param {String} input.shopId - shop to invite user
* @param {String} input.groupId - groupId to invite user
* @param {String} input.groupIds - groupIds to invite user
* @param {String} input.email - email of invitee
* @param {String} input.name - name of invitee
* @return {Promise<Object>} with boolean of found new account === true || false
Expand All @@ -33,7 +34,7 @@ export default async function inviteShopMember(context, input) {
const { Accounts, AccountInvites, Groups, Shops } = collections;
const {
email,
groupId,
groupIds,
name,
shopId
} = input;
Expand All @@ -45,8 +46,19 @@ export default async function inviteShopMember(context, input) {
const shop = await Shops.findOne({ _id: shopId });
if (!shop) throw new ReactionError("not-found", "No shop found");

const group = await Groups.findOne({ _id: groupId });
if (!group) throw new ReactionError("not-found", "No group found");
const groups = await Groups.find({
_id: {
$in: groupIds
}
}).toArray();

if (!groups) {
loan-laux marked this conversation as resolved.
Show resolved Hide resolved
throw new ReactionError("not-found", "No groups matching the provided IDs were found");
}

if (groups.length !== groupIds.length) {
throw new ReactionError("not-found", `Could not find ${groupIds.length - groups.length} of ${groupIds.length} groups provided`)
}

const lowercaseEmail = email.toLowerCase();

Expand All @@ -55,17 +67,27 @@ export default async function inviteShopMember(context, input) {

if (invitedAccount) {
// Set the account's permission group for this shop
await context.mutations.addAccountToGroup(context, {
accountId: invitedAccount._id,
groupId
await context.mutations.updateGroupsForAccounts(context, {
accountIds: [invitedAccount._id],
groupIds
});

return Accounts.findOne({ _id: invitedAccount._id });
}

// This check is part of `addAccountToGroup` mutation for existing users. For new users,
const groupShopIds = groups.reduce((allShopIds, group) => {
if (!allShopIds.includes(group.shopId)) {
allShopIds.push(group.shopId);
}

return allShopIds;
}, []);

// This check is part of `updateGroupsForAccounts` mutation for existing users. For new users,
// we do it here before creating an invite record and sending the invite email.
await context.validatePermissions("reaction:legacy:groups", "manage:accounts", { shopId: group.shopId });
for (let groupShopId of groupShopIds) {
await context.validatePermissions("reaction:legacy:groups", "manage:accounts", { groupShopId });
loan-laux marked this conversation as resolved.
Show resolved Hide resolved
}

// 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.
Expand All @@ -74,7 +96,7 @@ export default async function inviteShopMember(context, input) {
shopId
}, {
$set: {
groupId,
groupIds,
invitedByUserId: userFromContext._id
},
$setOnInsert: {
Expand All @@ -84,11 +106,31 @@ export default async function inviteShopMember(context, input) {
upsert: true
});

let formattedGroupNames = groups[0].name;

// Generate a human-readable list of group names.
// For example, if we have groups "test1" and "test2", `formattedGroupNames` will be "test1 and test2".
// If we have groups "test1", "test2" and "test3", `formattedGroupNames` will be "test1, test2 and test3".
if (groups.length > 1) {
formattedGroupNames = groups.reduce((sentence, group, index) => {
if (index === groups.length - 1) {
return `${sentence} and ${group.name}`;
}

if (index === 0) {
return group.name;
}

return `${sentence}, ${group.name}`;
}, "");
}
loan-laux marked this conversation as resolved.
Show resolved Hide resolved

// Now send them an invitation email
const dataForEmail = {
contactEmail: _.get(shop, "emails[0].address"),
copyrightDate: new Date().getFullYear(),
groupName: _.startCase(group.name),
loan-laux marked this conversation as resolved.
Show resolved Hide resolved
groupNames: formattedGroupNames,
hasMultipleGroups: groups.length > 1,
legalName: _.get(shop, "addressBook[0].company"),
physicalAddress: {
address: `${_.get(shop, "addressBook[0].address1")} ${_.get(shop, "addressBook[0].address2")}`,
Expand Down
20 changes: 17 additions & 3 deletions src/resolvers/Mutation/inviteShopMember.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ReactionError from "@reactioncommerce/reaction-error";
import { decodeGroupOpaqueId, decodeShopOpaqueId } from "../../xforms/id.js";

/**
Expand All @@ -8,7 +9,8 @@ import { decodeGroupOpaqueId, decodeShopOpaqueId } from "../../xforms/id.js";
* @param {Object} _ - unused
* @param {Object} args.input - an object of all mutation arguments that were sent by the client
* @param {String} args.input.email - The email address of the person to invite
* @param {String} args.input.groupId - The permission group for this person's new account
* @param {String} args.input.groupId - The permission group for this person's new account (deprecated)
* @param {String} args.input.groupIds - The permission groups for this person's new account
* @param {String} args.input.name - The permission group for this person's new account
* @param {String} args.input.shopId - The ID of the shop to which you want to invite this person
* @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call
Expand All @@ -17,12 +19,24 @@ import { decodeGroupOpaqueId, decodeShopOpaqueId } from "../../xforms/id.js";
*/
export default async function inviteShopMember(_, { input }, context) {
const { email, groupId, name, shopId, clientMutationId = null } = input;
const decodedGroupId = decodeGroupOpaqueId(groupId);
let { groupIds } = input;

// If user is using deprecated `groupId` instead of `groupIds`, populate `groupIds`
if (groupId && (!Array.isArray(groupIds) || groupIds.length === 0)) {
groupIds = [groupId];
}

// If user is passing both `groupId` and `groupIds`, throw an error
if (groupId && Array.isArray(groupIds) && groupIds.length > 0) {
throw new ReactionError("invalid-parameter", "Can't specify both groupId and groupIds.");
}
loan-laux marked this conversation as resolved.
Show resolved Hide resolved

const decodedGroupIds = groupIds.map((groupId) => decodeGroupOpaqueId(groupId));
const decodedShopId = decodeShopOpaqueId(shopId);

const account = await context.mutations.inviteShopMember(context, {
email,
groupId: decodedGroupId,
groupIds: decodedGroupIds,
name,
shopId: decodedShopId
});
Expand Down
8 changes: 6 additions & 2 deletions src/schemas/inviteShopMember.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ input InviteShopMemberInput {
"The email address of the person to invite"
email: String!

"The permission group for this person's new account"
groupId: ID!
"The permission group for this person's new account. DEPRECATED. Use `groupIds` field instead."
# @deprecated isn't allowed on input fields yet. See See https://github.com/graphql/graphql-spec/pull/525
groupId: ID

"The permission groups for this person's new account"
groupIds: [ID]

"The invitee's full name"
name: String!
Expand Down