Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
84ec3e7
(feat): add language field to profile schema
Apr 25, 2019
1542e76
(feat): update graphql to make use of profile.language field in accounts
Apr 25, 2019
e37c469
(feat): update getSubject to accept language as optional second param…
Apr 25, 2019
82562cf
(feat): update getTemplate to accept language as optional second para…
Apr 25, 2019
0157736
(feat): update functions to properly handle optional language param
Apr 26, 2019
0f6b0b5
(feat): update locations where getTemplate or getSubject are used to…
Apr 26, 2019
62a4ae5
(fix): add missing import statement for Accounts collection
Apr 26, 2019
766f321
(fix): remove leftover jsdoc parameter that is not used in function
Apr 26, 2019
7d1abbc
(style): reformat code to follow eslint
Apr 26, 2019
47a38af
(fix): type error template to subject
Apr 26, 2019
45010d1
(fix): mongodb query search for account based on email
Apr 26, 2019
9140167
(feat): add language field to order document schema
May 6, 2019
43b9c75
(feat): Move language field from OrderItem to Order
May 7, 2019
3706674
(feat): add language field orderInputSchema
May 7, 2019
30ba9c7
(feat(): add language field to graphql type Order and input OrderInput
May 7, 2019
e56e3f3
(feat): use newly added language field in placeOrder
May 7, 2019
5e322e8
(feat): modify account findOne query to look at _id instead of emails
May 7, 2019
e285900
(fix): change language field schema from default to optional
May 7, 2019
1a845f6
Add field enabled true to template search
May 7, 2019
5fb7bea
(fix): add enabled requirement for shopLanguage when search for match…
May 7, 2019
1635406
(fix): add functionality to check if language is correct and enabled …
May 7, 2019
3dd2e46
(fix): fix eslint and tests
May 7, 2019
9faa6b3
(feat): add function to receive array of languages from primaryShop
May 7, 2019
75c1977
Modify account to use primaryShopLanguages instead of shop specific l…
May 7, 2019
cd04f12
Update comment
May 7, 2019
6230e58
(fix): properly search for language in array of primary shop languages
May 7, 2019
db7c60b
(feat): modify function to get language from primary shop instead of …
May 7, 2019
5ec0d7d
Fix eslint errors
May 8, 2019
916d7b2
Remove core/server/Reaction import and use collection find to pass tests
May 8, 2019
2fdf126
Fix eslint error
May 8, 2019
cca2c5b
Remove not needed function which was added in previous commits
May 9, 2019
10bf8a6
Modify functions to first check user shop and secondly primary shop f…
May 9, 2019
9cddfb6
Merge branch 'master' into feat-#63-accounts-language-email
jmaver-plume May 9, 2019
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
5 changes: 5 additions & 0 deletions imports/collections/schemas/accounts.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ export const Profile = new SimpleSchema({
optional: true,
mockValue: null
},
"language": {
label: "User Language",
type: String,
optional: true
},
"preferences": {
label: "User preferences",
type: Object,
Expand Down
4 changes: 4 additions & 0 deletions imports/collections/schemas/orders.js
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,10 @@ export const Order = new SimpleSchema({
optional: true
},
"history.$": History,
"language": {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be more specifically named, maybe ordererPreferredLanguage?

type: String,
optional: true
},
"notes": {
type: Array,
optional: true
Expand Down
2 changes: 2 additions & 0 deletions imports/plugins/core/accounts/server/methods/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import removeEmailAddress from "./removeEmailAddress";
import removeUserPermissions from "./removeUserPermissions";
import sendResetPasswordEmail from "./sendResetPasswordEmail";
import setProfileCurrency from "./setProfileCurrency";
import setProfileLanguage from "./setProfileLanguage";
import setUserPermissions from "./setUserPermissions";
import updateEmailAddress from "./updateEmailAddress";
import updateServiceConfiguration from "./updateServiceConfiguration";
Expand Down Expand Up @@ -47,6 +48,7 @@ export default {
"accounts/removeUserPermissions": removeUserPermissions,
"accounts/sendResetPasswordEmail": sendResetPasswordEmail,
"accounts/setProfileCurrency": setProfileCurrency,
"accounts/setProfileLanguage": setProfileLanguage,
"accounts/setUserPermissions": setUserPermissions,
"accounts/updateEmailAddress": updateEmailAddress,
"accounts/updateServiceConfiguration": updateServiceConfiguration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,12 @@ export default function inviteShopMember(options) {

dataForEmail.groupName = _.startCase(group.name);

const account = Accounts.findOne({ userId });
const language = account && account.profile && account.profile.language;

// Compile Email with SSR
SSR.compileTemplate(tpl, Reaction.Email.getTemplate(tpl));
SSR.compileTemplate(subject, Reaction.Email.getSubject(tpl));
SSR.compileTemplate(tpl, Reaction.Email.getTemplate(tpl, language));
SSR.compileTemplate(subject, Reaction.Email.getSubject(tpl, language));

// send invitation email from primary shop email
Reaction.Email.send({
Expand All @@ -128,5 +131,5 @@ export default function inviteShopMember(options) {
html: SSR.render(tpl, dataForEmail)
});

return Accounts.findOne({ userId });
return account;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Meteor } from "meteor/meteor";
import { Accounts as MeteorAccounts } from "meteor/accounts-base";
import { check, Match } from "meteor/check";
import { SSR } from "meteor/meteorhacks:ssr";
import { Accounts } from "/lib/collections";
import Reaction from "/imports/plugins/core/core/server/Reaction";
import ReactionError from "@reactioncommerce/reaction-error";
import getCurrentUserName from "../no-meteor/util/getCurrentUserName";
Expand Down Expand Up @@ -55,8 +56,11 @@ export default function inviteShopOwner(options, shopData) {
const tpl = "accounts/inviteShopOwner";
const subject = "accounts/inviteShopOwner/subject";

SSR.compileTemplate(tpl, Reaction.Email.getTemplate(tpl));
SSR.compileTemplate(subject, Reaction.Email.getSubject(tpl));
const account = Accounts.findOne({ userId }, { _id: 0, profile: 1 });
const language = account && account.profile && account.profile.language;

SSR.compileTemplate(tpl, Reaction.Email.getTemplate(tpl, language));
SSR.compileTemplate(subject, Reaction.Email.getSubject(tpl, language));

const emailLogo = Reaction.Email.getShopLogo(primaryShop);
const token = Random.id();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import _ from "lodash";
import Logger from "@reactioncommerce/logger";
import Random from "@reactioncommerce/random";
import { Accounts } from "meteor/accounts-base";
import { Accounts as MeteorAccounts } from "meteor/accounts-base";
import { check } from "meteor/check";
import { Meteor } from "meteor/meteor";
import { SSR } from "meteor/meteorhacks:ssr";
import { Shops } from "/lib/collections";
import { Accounts, Shops } from "/lib/collections";
import Reaction from "/imports/plugins/core/core/server/Reaction";
import ReactionError from "@reactioncommerce/reaction-error";

Accounts.urls.resetPassword = function reset(token) {
MeteorAccounts.urls.resetPassword = function reset(token) {
return Meteor.absoluteUrl(`reset-password/${token}`);
};

Expand Down Expand Up @@ -97,15 +97,19 @@ async function sendResetEmail(userId, optionalEmail) {
}
},
// Account Data
passwordResetUrl: Accounts.urls.resetPassword(token),
passwordResetUrl: MeteorAccounts.urls.resetPassword(token),
user
};

// Compile Email with SSR
const tpl = "accounts/resetPassword";
const subject = "accounts/resetPassword/subject";
SSR.compileTemplate(tpl, Reaction.Email.getTemplate(tpl));
SSR.compileTemplate(subject, Reaction.Email.getSubject(tpl));

const account = Accounts.findOne({ userId }, { _id: 0, profile: 1 });
const language = account && account.profile && account.profile.language;

SSR.compileTemplate(tpl, Reaction.Email.getTemplate(tpl, language));
SSR.compileTemplate(subject, Reaction.Email.getSubject(tpl, language));

return Reaction.Email.send({
to: email,
Expand All @@ -130,7 +134,7 @@ export default function sendResetPasswordEmail(options) {
email: String
});

const user = Accounts.findUserByEmail(options.email);
const user = MeteorAccounts.findUserByEmail(options.email);

if (!user) {
Logger.error("accounts/sendResetPasswordEmail - User not found");
Expand Down
57 changes: 57 additions & 0 deletions imports/plugins/core/accounts/server/methods/setProfileLanguage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { check, Match } from "meteor/check";
import R from "ramda";
import { Accounts, Shops } from "/lib/collections";
import appEvents from "/imports/node-app/core/util/appEvents";
import Reaction from "/imports/plugins/core/core/server/Reaction";
import ReactionError from "@reactioncommerce/reaction-error";

/**
* @name accounts/setProfileLanguage
* @memberof Accounts/Methods
* @method
* @param {String} languageCode - i18n language code
* @param {String} [accountId] - accountId of user to set language of. Defaults to current user ID
* @summary Sets users profile language
* @returns {Object} Account document
*/
export default function setProfileLanguage(languageCode, accountId) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to a non-Meteor mutation function in /imports/plugins/core/accounts/server/no-meteor/mutations/setProfileLanguage.js and call it as const updatedAccount = await context.mutations.setProfileLanguage(context, languageCode, dbAccountId); in the resolver. Using callMeteorMethod is deprecated and soon going away.

check(languageCode, String);
check(accountId, Match.Maybe(String));

const currentUserId = this.userId;
const userId = accountId || currentUserId;
if (!userId) throw new ReactionError("access-denied", "You must be logged in to set profile language");

const account = Accounts.findOne({ userId }, { fields: { shopId: 1 } });
if (!account) throw new ReactionError("not-found", "Account not found");

if (userId !== currentUserId && !Reaction.hasPermission("reaction-accounts", currentUserId, account.shopId)) {
throw new ReactionError("access-denied", "Access denied");
}

// first search for shop based on account id
let shop = Shops.findOne({ _id: account.shopId }, { languages: 1 });

if (!shop || !shop.languages || shop.languages.length === 0) {
// if non-primary shop does not have any languages use primary shop
shop = Shops.findOne({ shopType: "primary" }, { languages: 1 });
}

const shopLanguages = (shop && shop.languages) || [];
const shopLanguage = R.find(R.whereEq({ enabled: true, i18n: languageCode }))(shopLanguages);

if (!shopLanguage) {
throw new ReactionError("invalid-argument", `Shop does not define any enabled language with code "${languageCode}"`);
}

Accounts.update({ userId }, { $set: { "profile.language": languageCode } });

const updatedAccount = Accounts.findOne({ userId });
Promise.await(appEvents.emit("afterAccountUpdate", {
account: updatedAccount,
updatedBy: currentUserId,
updatedFields: ["profile.language"]
}));

return updatedAccount;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export default {
_id: (account) => encodeAccountOpaqueId(account._id),
addressBook,
currency: (account) => getXformedCurrencyByCode(account.profile && account.profile.currency),
language: (account) => account.profile && account.profile.language,
emailRecords: (account) => account.emails,
preferences: (account) => get(account, "profile.preferences"),
primaryEmailAddress: (account) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import inviteShopMember from "./inviteShopMember";
import removeAccountAddressBookEntry from "./removeAccountAddressBookEntry";
import removeAccountFromGroup from "./removeAccountFromGroup";
import setAccountProfileCurrency from "./setAccountProfileCurrency";
import setAccountProfileLanguage from "./setAccountProfileLanguage";
import updateAccountAddressBookEntry from "./updateAccountAddressBookEntry";

export default {
Expand All @@ -13,5 +14,6 @@ export default {
removeAccountAddressBookEntry,
removeAccountFromGroup,
setAccountProfileCurrency,
setAccountProfileLanguage,
updateAccountAddressBookEntry
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { decodeAccountOpaqueId } from "@reactioncommerce/reaction-graphql-xforms/account";

/**
* @name "Mutation.setAccountProfileLanguage"
* @method
* @memberof Accounts/GraphQL
* @summary resolver for the setAccountProfileLanguage GraphQL mutation
* @param {Object} _ - unused
* @param {Object} args.input - an object of all mutation arguments that were sent by the client
* @param {String} [args.input.accoundId] - The account ID, which defaults to the viewer account
* @param {String} args.input.languageCode - The languageCode to add to user profile
* @param {String} [args.input.clientMutationId] - An optional string identifying the mutation call
* @param {Object} context - an object containing the per-request state
* @return {Object} setAccountProfileLanguage
*/
export default function setAccountProfileLanguage(_, { input }, context) {
const { accountId, languageCode, clientMutationId = null } = input;
const dbAccountId = decodeAccountOpaqueId(accountId);
const updatedAccount = context.callMeteorMethod("accounts/setProfileLanguage", languageCode, dbAccountId);

return {
account: updatedAccount,
clientMutationId
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ input SetAccountProfileCurrencyInput {
currencyCode: String!
}

"Describes an account profile language change"
input SetAccountProfileLanguageInput {
"The account ID, which defaults to the viewer account"
accountId: ID

"An optional string identifying the mutation call, which will be returned in the response payload"
clientMutationId: String

"The language code"
languageCode: String!
}

"Defines a new Email and the account to which it should be added"
input AddAccountEmailRecordInput {
"The account ID, which defaults to the viewer account"
Expand Down Expand Up @@ -124,6 +136,9 @@ type Account implements Node {
"The preferred currency used by this account"
currency: Currency

"The preferred language used by this account"
language: String

"A list of email records associated with this account"
emailRecords: [EmailRecord]

Expand Down Expand Up @@ -228,6 +243,15 @@ type SetAccountProfileCurrencyPayload {
clientMutationId: String
}

"The response from the `setAccountProfileLanguage` mutation"
type SetAccountProfileLanguagePayload {
"The updated account"
account: Account

"The same string you sent with the mutation params, for matching mutation calls with their responses"
clientMutationId: String
}

extend type Mutation {
"Add a new address to the `addressBook` field for an account"
addAccountAddressBookEntry(input: AddAccountAddressBookEntryInput!): AddAccountAddressBookEntryPayload
Expand All @@ -244,6 +268,9 @@ extend type Mutation {
"Set the preferred currency for an account"
setAccountProfileCurrency(input: SetAccountProfileCurrencyInput!): SetAccountProfileCurrencyPayload

"Set the preferred language for an account"
setAccountProfileLanguage(input: SetAccountProfileLanguageInput!): SetAccountProfileLanguagePayload

"Remove an address that exists in the `addressBook` field for an account"
updateAccountAddressBookEntry(input: UpdateAccountAddressBookEntryInput!): UpdateAccountAddressBookEntryPayload
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import _ from "lodash";
import Logger from "@reactioncommerce/logger";
import Random from "@reactioncommerce/random";
import { Meteor } from "meteor/meteor";
import { Accounts } from "meteor/accounts-base";
import { Accounts as MeteorAccounts } from "meteor/accounts-base";
import { Accounts } from "/lib/collections";
import { SSR } from "meteor/meteorhacks:ssr";
import Reaction from "/imports/plugins/core/core/server/Reaction";
import ReactionError from "@reactioncommerce/reaction-error";
Expand Down Expand Up @@ -66,7 +67,7 @@ export default async function sendVerificationEmail({
});

const shopName = Reaction.getShopName();
const url = Accounts.urls.verifyEmail(token);
const url = MeteorAccounts.urls.verifyEmail(token);
const copyrightDate = new Date().getFullYear();

const dataForEmail = {
Expand Down Expand Up @@ -117,8 +118,11 @@ export default async function sendVerificationEmail({
`);
}

SSR.compileTemplate(bodyTemplate, Reaction.Email.getTemplate(bodyTemplate));
SSR.compileTemplate(subjectTemplate, Reaction.Email.getSubject(bodyTemplate));
const account = Accounts.findOne({ userId }, { _id: 0, profile: 1 });
const language = account && account.profile && account.profile.language;

SSR.compileTemplate(bodyTemplate, Reaction.Email.getTemplate(bodyTemplate, language));
SSR.compileTemplate(subjectTemplate, Reaction.Email.getSubject(bodyTemplate, language));

return Reaction.Email.send({
to: address,
Expand Down
5 changes: 3 additions & 2 deletions imports/plugins/core/accounts/server/util/sendWelcomeEmail.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ export default function sendWelcomeEmail(shopId, userId, token) {

const tpl = "accounts/sendWelcomeEmail";
const subject = "accounts/sendWelcomeEmail/subject";
SSR.compileTemplate(tpl, Reaction.Email.getTemplate(tpl));
SSR.compileTemplate(subject, Reaction.Email.getSubject(tpl));
const language = account && account.profile && account.profile.language;
SSR.compileTemplate(tpl, Reaction.Email.getTemplate(tpl, language));
SSR.compileTemplate(subject, Reaction.Email.getSubject(tpl, language));

Reaction.Email.send({
to: userEmail,
Expand Down
28 changes: 23 additions & 5 deletions imports/plugins/core/core/server/Reaction/Email/getSubject.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,40 @@ import ReactionError from "@reactioncommerce/reaction-error";
* layout must be defined + template
* @example SSR.compileTemplate(subject, Reaction.Email.getSubject(tpl));
* @param {String} template name of the template in either Layouts or fs
* @param {String} [language] i18n language of a template
* @returns {Object} returns source
*/
export default function getSubject(template) {
export default function getSubject(template, language) {
if (typeof template !== "string") {
const msg = "Reaction.Email.getSubject() requires a template name";
Logger.error(msg);
throw new ReactionError("invalid-parameter", msg);
}

// set default
const language = Reaction.getShopLanguage();
if (language !== undefined && typeof language !== "string") {
const msg = "Reaction.Email.getSubject() requires optional language code that is a string";
Logger.error(msg);
throw new ReactionError("invalid-parameter", msg);
}

if (language !== undefined) {
// check database for a matching template using language param
const tmpl = Templates.findOne({
enabled: true,
name: template,
language
});

// use that template if found
if (tmpl && tmpl.template) {
return tmpl.subject;
}
}

// check database for a matching template
// check database for a matching template using shop language
const tmpl = Templates.findOne({
name: template,
language
language: Reaction.getShopLanguage()
});

// use that template if found
Expand Down
Loading