I use parse-server-amazon-ses-email-adapter and would like to know how to localize the verification email and reset password email.
My clue is to check user's fields and then assign the correct template on server.js or AmazonSESAdapter.js. The problem is that user properties are empty, besides email, username.
For instance, at the example below, firstName is undefined.
Thanks.
emailAdapter: {
module: 'parse-server-amazon-ses-email-adapter',
options: {
// The address that your emails come from
fromAddress: 'Iron rr',
accessKeyId: 'gg',
secretAccessKey: 'gg',
region: 'eu-west-1',
// The template section
templates: {
passwordResetEmail: {
subject: 'Redefinir sua senha Iron Trainers',
pathPlainText: '/opt/bitnami/apps/parse/htdocs/node_modules/parse-server/node_modules/parse-server-amazon-ses-email-adapter/test/email-templates/password_reset_email.txt',
pathHtml: '/opt/bitnami/apps/parse/htdocs/node_modules/parse-server/node_modules/parse-server-amazon-ses-email-adapter/test/email-templates/password_reset_email.html',
callback: (user) => {
return {
firstName: user.get('firstName')
}
}
// Now you can use {{firstName}} in your templates
},
verificationEmail: {
subject: 'Confirmar email no Iron Trainers',
pathPlainText: '/opt/bitnami/apps/parse/htdocs/node_modules/parse-server/node_modules/parse-server-amazon-ses-email-adapter/test/email-templates/verification_email.txt',
pathHtml: '/opt/bitnami/apps/parse/htdocs/node_modules/parse-server/node_modules/parse-server-amazon-ses-email-adapter/test/email-templates/resendEmailVerification.html',
callback: (user) => {
return {
firstName: user.get('firstName')
}
}
// Now you can use {{firstName}} in your templates
},
customEmailAlert: {
subject: 'Urgent notification!',
pathPlainText: '/opt/bitnami/apps/parse/htdocs/node_modules/parse-server/node_modules/parse-server-amazon-ses-email-adapter/test/email-templates/custom_alert.txt',
pathHtml: '/opt/bitnami/apps/parse/htdocs/node_modules/parse-server/node_modules/parse-server-amazon-ses-email-adapter/test/email-templates/custom_alert.html',
}
}
As per my understanding this can be done but through a code change in the plugin.
There is a if-else condition at below line in code
https://github.com/ecohealthalliance/parse-server-amazon-ses-email-adapter/blob/0bce9b6c81681c3829a17b208d839d23c846ab05/src/AmazonSESAdapter.js#L90
Since you have not provided any feedback and I have not way to setup this, I have assume that the else part is what gets executed
const {
link,
appName,
user,
templateConfig
} = options;
const {
callback
} = templateConfig;
let userVars;
if (callback && typeof callback === 'function') {
userVars = callback(user);
// If custom user variables are not packaged in an object, ignore it
const validUserVars = userVars && userVars.constructor && userVars.constructor.name === 'Object';
userVars = validUserVars ? userVars : {};
}
pathPlainText = templateConfig.pathPlainText;
pathHtml = templateConfig.pathHtml;
templateVars = Object.assign({
link,
appName,
username: user.get('username'),
email: user.get('email')
}, userVars);
message = {
from: this.fromAddress,
to: user.get('email'),
subject: templateConfig.subject
};
}
Now in this part, 2 lines decide the template to be used
pathPlainText = templateConfig.pathPlainText;
pathHtml = templateConfig.pathHtml;
By this time, the callback you have provided has been called. Now in the callback you can set a variable, let assume it is name locale. So you can update the code like below
pathPlainText = templateConfig.pathPlainText + (userVars["locale"] || "en");
pathHtml = templateConfig.pathHtml + (userVars["locale"] || "en");
And then you will create templates which have the locale in the file path and with updated code the correct template will be picked.
You can also look at #bgran answer, at first look I do believe that should work as well
You'll need to do your localization in the template callback.
The callback is synchronous, so all of your localization will also need to be synchronous.
emailTemplate.html
<div>
{{localizedText}}
</div>
Other templates for each locale:
emailTemplate.en.html
<p>
Hi {{nome}}...
</p>
The emailer logic:
// The same templater used by parse-server-amazon-ses-email-adapter
import template from 'lodash.template'
/* ... */
const TemplatesByLocale = {
en: fs.readFileSync('./emailTemplate.en.html'),
}
verificationEmail: {
/* ... */
pathHtml: './path/to/emailTemplate.html',
callback: (user) => {
const locale = getLocaleSomehow(user) // needs to be synchronous
const localizedTemplate = TemplatesByLocale[locale]
const compiled = template(localizedTemplate, {
// same interpolation as parse-server-amazon-ses-email-adapter
interpolate: /{{([\s\S]+?)}}/g
})
const localizedText = compiled({
nome: user.get('nome'), /* ... */
})
return {
localizedText: localizedText,
}
},
/* ... */
}
It's worth noting that parse-server-amazon-ses-email-adapter will use the HTML template (the one specified via pathHtml) before it uses the plain text template, so if you've specified an HTML template you can just leave off the pathPlainText property.
Related
I am trying to utilize the library #gitgraph/js in my application (Note: I cannot use the React or NodeJS version, only the plain JS):
https://github.com/nicoespeon/gitgraph.js/tree/master/packages/gitgraph-js
Here is an example of what I am trying to do:
https://jsfiddle.net/Ben_Vins/fwcah5s0/7/
var myTemplateConfig = {
// … any specific template configuration
commit: {
shouldDisplayTooltipsInCompactMode: true, // default = true
tooltipHTMLFormatter: function(commit) {
return "<b>BV" + commit.sha1 + "</b>" + ": " + commit.message;
},
}
};
// Instantiate the graph.
const gitgraph = GitgraphJS.createGitgraph(graphContainer, {
mode: GitgraphJS.Mode.Compact,
template: new GitgraphJS.templateExtend(GitgraphJS.TemplateName.Metro, myTemplateConfig ),
});
// Simulate git commands with Gitgraph API.
const master = gitgraph.branch("master");
master.commit("Initial commit");
const develop = gitgraph.branch("develop");
develop.commit("Add TypeScript");
const aFeature = gitgraph.branch("a-feature");
aFeature
.commit("Make it work")
.commit({ subject: "Make it right", hash: "test" })
.commit("Make it fast");
develop.merge(aFeature);
develop.commit("Prepare v1");
master.merge(develop).tag("v1.0.0");
By default, the result were too big, so I have applied a css to scale down the graph (the graph is an inline SVG without a cropbox property, so this is the only trick I could find).
What I would like to do:
Customize the tooltip of the onhover of the commits node (making it larger, change the text, change its css if possible)
Add a onclick event to capture the commit (in particular the commit hash to be used elsewhere in my application)
Extra points:
The documentation is limited and the examples from
https://github.com/nicoespeon/gitgraph.js/tree/master/packages/stories/src/gitgraph-js/
are in typescript. Are they also applicable for the JS version of gitgraph-js?
Note that the documentation of gitgraph.js seemed more detailed i.e. https://github.com/nicoespeon/gitgraph.js/blob/master/packages/gitgraph-js/MIGRATE_FROM_GITGRAPH.JS.md but I was trying to use the next version i.e. #gitgraph/js
Thanks for reaching out. I can add more colors to this as the author of the lib.
To customize the tooltip of a commit, you can provide a renderTooltip() custom function. It will give you the reference to the commit object, so you can customize at your will.
Same, you can pass a onClick() function that will give you the reference to the commit object
The examples are in TypeScript, but that's valid JS if you simply remove the types. Also, the gitgraph-js stories looks like they are React code, but they're not. They're simply wrapped in a React component so we could run them in Storybook along with the gitgraph-react ones.
With the latest version of the lib, you could try the following:
const graphContainer = document.getElementById("graph-container");
// Instantiate the graph.
const withoutBranchLabels = GitgraphJS.templateExtend(GitgraphJS.TemplateName.Metro, {
branch: { label: { display: false } },
});
const gitgraph = GitgraphJS.createGitgraph(graphContainer, {
mode: GitgraphJS.Mode.Compact,
template: withoutBranchLabels,
});
// Simulate git commands with Gitgraph API.
let storedCommit;
gitgraph
.commit({
subject: "Initial commit",
onClick: (commit) => storedCommit = commit
})
.commit({
subject: "Another commit",
onClick: (commit) => storedCommit = commit
})
.commit({
subject: "Do something crazy",
renderTooltip,
onClick: (commit) => storedCommit = commit
});
gitgraph
.branch("dev")
.commit({
subject: "Oh my god",
renderTooltip,
})
.commit({
subject: "This is a saxo!",
renderTooltip,
});
// Logs in console the sha1 of the clicked commit every 3s (for master branch only)
setInterval(() => storedCommit && console.log(`stored commit sha1: ${storedCommit.hashAbbrev}`), 3000)
// Custom tooltip renderer
function renderTooltip (commit) {
const commitSize = commit.style.dot.size * 2;
return createG({
translate: { x: commitSize + 10, y: commitSize / 2 },
children: [
createText({
fill: commit.style.dot.color,
content: `BV${commit.hashAbbrev}: ${commit.subject}`
})
],
});
}
// Helper functions to create SVGs
const SVG_NAMESPACE = "http://www.w3.org/2000/svg";
function createText(options) {
const text = document.createElementNS(SVG_NAMESPACE, "text");
text.setAttribute("alignment-baseline", "central");
text.setAttribute("dominant-baseline", "central");
text.textContent = options.content;
if (options.bold) {
text.setAttribute("font-weight", "bold");
}
if (options.fill) {
text.setAttribute("fill", options.fill);
}
if (options.font) {
text.setAttribute("style", `font: ${options.font}`);
}
if (options.anchor) {
text.setAttribute("text-anchor", options.anchor);
}
if (options.translate) {
text.setAttribute("x", options.translate.x.toString());
text.setAttribute("y", options.translate.y.toString());
}
if (options.onClick) {
text.addEventListener("click", options.onClick);
}
return text;
}
function createG(options) {
const g = document.createElementNS(SVG_NAMESPACE, "g");
options.children.forEach((child) => child && g.appendChild(child));
if (options.translate) {
g.setAttribute(
"transform",
`translate(${options.translate.x}, ${options.translate.y})`,
);
}
if (options.fill) {
g.setAttribute("fill", options.fill);
}
if (options.stroke) {
g.setAttribute("stroke", options.stroke);
}
if (options.strokeWidth) {
g.setAttribute("stroke-width", options.strokeWidth.toString());
}
if (options.onClick) {
g.addEventListener("click", options.onClick);
}
if (options.onMouseOver) {
g.addEventListener("mouseover", options.onMouseOver);
}
if (options.onMouseOut) {
g.addEventListener("mouseout", options.onMouseOut);
}
return g;
}
You can't just return HTML though, it must be SVG because the current renderer only handles SVG. That is surely less convenient than before, thus I encourage you to build helper functions like I did here. You can find helpers used in the stories too.
I hope that will be helpful. You can play with the new online playground too: https://codepen.io/nicoespeon/pen/arqPWb?editors=1010
Finally, I'm not maintaining the library much and I'm still looking for active maintainers: https://github.com/nicoespeon/gitgraph.js/issues/328
I have a very basic feathers service which stores data in mongoose using the feathers-mongoose package. The issue is with the get functionality. My model is as follows:
module.exports = function (app) {
const mongooseClient = app.get('mongooseClient');
const { Schema } = mongooseClient;
const messages = new Schema({
message: { type: String, required: true }
}, {
timestamps: true
});
return mongooseClient.model('messages', messages);
};
When the a user runs a GET command :
curl http://localhost:3030/messages/test
I have the following requirements
This essentially tries to convert test to ObjectID. What i would
like it to do is to run a query against the message attribute
{message : "test"} , i am not sure how i can achieve this. There is
not enough documentation for to understand to write or change this
in the hooks. Can some one please help
I want to return a custom error code (http) when a row is not found or does not match some of my criterias. How can i achive this?
Thanks
In a Feathers before hook you can set context.result in which case the original database call will be skipped. So the flow is
In a before get hook, try to find the message by name
If it exists set context.result to what was found
Otherwise do nothing which will return the original get by id
This is how it looks:
async context => {
const messages = context.service.find({
...context.params,
query: {
$limit: 1,
name: context.id
}
});
if (messages.total > 0) {
context.result = messages.data[0];
}
return context;
}
How to create custom errors and set the error code is documented in the Errors API.
I'm setting up a Booking router in NodeJS, and I have many params in.
Now when I forgot params I return an error like :
500: Need more information
I wonder if it's possible to know which params are missing when I return the error code.
This is for a new API made in NodeJS
Here are the params that I want to retrieve from the front ( made in ReactJS )
let body = {
agentDutyCode: "STRING",
RatePlanCode: params.rateCode,
RoomCode: params.roomCode,
AmountAfterTax: params.amountTax,
Start: params.fromDate,
End: params.toDate,
CardCode: params.cardCode,
CardNumber: params.cardNumber,
ExpireDate: params.expireDate,
SeriesCode: params.cvv,
CardHolderName: params.nameCard,
ChainCode: params.chainCode,
HotelCode: params.hotelCode,
RoomQuantities: params.roomQuantities,
GuestQuantitie: params.numberGuest,
GuestPerRoom: params.guestPerRoom,
LastName: params.lastName,
FirstName: params.firstName,
PhoneNumber: params.phoneNumber,
email: params.email,
FVL_SUBUNIT_7: params.walletAddress
}
And this is my promise :
cdsJson.bookResource(req.body)
.then((response) => {
if (response !== null) {
res.response = {
...response
}
} if (response.hotel.length === 0) {
res.respStatus = 500
res.response = {
sendMsg: "Need more informations"
}
next('route')
}
return response
})
If the request succeeds I got a reservation ID otherwise I got :
Error 500: Need more information
Read the documentation or the source code.
Seriously. If the API response doesn't tell you in the error message, then there is no way to know what parameters it expects programmatically.
try it for a for ... in loop like this:
cdsJson.bookResource(req.body)
.then((response) => {
if (response !== null) {
res.response = {
...response
}
} if (response.hotel.length === 0) {
res.respStatus = 500
let errorStr = "Need more informations"
for(var key in req.body) { // Get all parameters that are not set
if(objects[key] == undefined)
errorStr += "\nParameter ["+key+"] is missing!"
}
res.response = {
sendMsg: errorStr
}
next('route')
}
return response
})
You're trying to do server side validation. In Node a good approach would be to define a JSON Schema for the expected parameters and then in your route handler validate the data sent in the request with a JSON Schema validator. This would help you work out whether a request was valid and help you generate error messages automatically. As a rule it's much better (i.e. simpler and more maintainable) to use tools that enable you to declaratively declare your validation (via a schema) than imperatively write code to manually validate objects.
JSON Schema spec https://json-schema.org/
A validator https://github.com/epoberezkin/ajv
I have the following code in my Model.js file.
Model.observe('loaded', (ctx, next) => {
const {
data,
options: {
user
}
} = ctx;
const owner = (user && data && user.uid === data.userId) || false;
console.log(
`${data.id}: loaded - access by ${user && user.name}, owner:${owner}`
);
if (!owner) {
delete data.testProp1;
}
console.log('returning: ', ctx.data);
next();
});
When I make a request, I see the following log output (server logs):
f3f9ffd6-14dc-42e5-94ba-503aa3426faa: loaded - access by User1, owner:false
returning:
{
testProp2: true,
id: 'f3f9ffd6-14dc-42e5-94ba-503aa3426faa',
userId: 'sfeywkKSuBTlf0DwE4ZOFd8RX5E3'
}
But then in the actual response the browser receives I actually get:
{
testProp1: true,
testProp2: true,
id: 'f3f9ffd6-14dc-42e5-94ba-503aa3426faa',
userId: 'sfeywkKSuBTlf0DwE4ZOFd8RX5E3'
}
Is there something in the documentation I am missing? Deleting the property is exactly what it shows in the Loopback docs here. Also, I actually see the modified data as the data property on the ctx object before calling next(). Anyone run into this issue or know some caveat to the docs that isn't explicitly stated?
In a project I m working on now I have some web models to represent form submission etc. For instance the login form (a pretty bad example) is as follows:
var revalidator = require('revalidator'); // A node.js module for validation
LoginForm = function(email, password) {
this.Email = email;
this.Password = password;
};
LoginForm.prototype.validate = function() {
return revalidator.validate(this, {
properties: {
Email: {
description: 'The email address provided is not valid',
type: 'string',
format: 'email',
required: true
},
Password: {
description: 'The password is not provided',
type: 'string',
required: true
}
}
})
};
exports.LoginForm = LoginForm;
This works great currently in my current node.js setup, but I was wondering if there is any way I can reuse those models on the client-side javascript code. Any help would be really appreciated.
Javascript is Javascript, you can share the code. You have to keep in mind that there are some differences between browsers and Node. If you want to share you code you must not use require and have to check if the export object exists before using it:
if(typeof exports === 'object'){
exports.LoginForm = LoginForm;
} else {
// your browser export
}