Skip to content

Commit 03d7a23

Browse files
authored
Merge pull request #2 from mtrezza/add-locale-to-payload
Add locale to payload
2 parents 429c1ec + c628f76 commit 03d7a23

File tree

6 files changed

+147
-51
lines changed

6 files changed

+147
-51
lines changed

.github/workflows/ci.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: ci
2+
on:
3+
push:
4+
branches:
5+
- main
6+
pull_request:
7+
branches:
8+
- '**'
9+
jobs:
10+
build:
11+
runs-on: ubuntu-18.04
12+
timeout-minutes: 30
13+
steps:
14+
- uses: actions/checkout@v2
15+
- name: Use Node.js
16+
uses: actions/setup-node@v1
17+
with:
18+
node-version: '10.14'
19+
- name: Cache Node.js modules
20+
uses: actions/cache@v2
21+
with:
22+
path: ~/.npm
23+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
24+
restore-keys: |
25+
${{ runner.os }}-
26+
- name: Install dependencies
27+
run: npm ci
28+
- run: npm test
29+
env:
30+
CI: true
31+
- run: bash <(curl -s https://codecov.io/bash)

.github/workflows/release.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: release
2+
on:
3+
release:
4+
types: [published]
5+
jobs:
6+
publish-npm:
7+
runs-on: ubuntu-18.04
8+
steps:
9+
- uses: actions/checkout@v2
10+
- uses: actions/setup-node@v1
11+
with:
12+
node-version: '10.14'
13+
registry-url: https://registry.npmjs.org/
14+
- name: Cache Node.js modules
15+
uses: actions/cache@v2
16+
with:
17+
path: ~/.npm
18+
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
19+
restore-keys: |
20+
${{ runner.os }}-node-
21+
- run: npm ci
22+
- run: npm publish
23+
env:
24+
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[Full Changelog](https://github.com/mtrezza/parse-server-api-mail-adapter/compare/1.0.0...master)
55

66
- FIX: Removed unused config parameter from docs. [#1](https://github.com/mtrezza/parse-server-api-mail-adapter/pull/1). Thanks to [mtrezza](https://github.com/mtrezza)
7+
- NEW: Added locale to API callback. [#2](https://github.com/mtrezza/parse-server-api-mail-adapter/pull/2). Thanks to [mtrezza](https://github.com/mtrezza)
78

89
### 1.0.0
910
- NEW: Initial commit.

README.md

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ The Parse Server API Mail Adapter enables Parse Server to send emails using any
77

88
# Content
99

10-
- [Getting Started](#getting-started)
10+
- [Installation](#Installation)
1111
- [Demo](#demo)
1212
- [Configuration](#configuration)
1313
- [Templates](#templates)
1414
- [Localization](#localization)
1515
- [Need help?](#need-help)
1616

17-
# Getting Started
17+
# Installation
1818

1919
1. Install adapter:
2020
```
@@ -61,45 +61,59 @@ const server = new ParseServer({
6161
sender: '[email protected]',
6262
// The email templates.
6363
templates: {
64-
// The template used by Parse Server to send an email for password reset; this is a reserved template name.
64+
// The template used by Parse Server to send an email for password
65+
// reset; this is a reserved template name.
6566
passwordResetEmail: {
6667
subjectPath: './files/password_reset_email_subject.txt'),
6768
textPath: './files/password_reset_email.txt'),
6869
htmlPath: './files/password_reset_email.html')
6970
},
70-
// The template used by Parse Server to send an email for email address verification; this is a reserved template name.
71+
// The template used by Parse Server to send an email for email
72+
// address verification; this is a reserved template name.
7173
verificationEmail: {
7274
subjectPath: './files/verification_email_subject.txt'),
7375
textPath: './files/verification_email.txt'),
7476
htmlPath: './files/verification_email.html')
7577
},
76-
// A custom email template that can be used when sending emails from Cloud Code; the template name can be choosen freely; it is possible to add various custom templates.
78+
// A custom email template that can be used when sending emails
79+
// from Cloud Code; the template name can be choosen freely; it
80+
// is possible to add various custom templates.
7781
customEmail: {
7882
subjectPath: './files/custom_email_subject.txt'),
7983
textPath: './files/custom_email.txt'),
8084
htmlPath: './files/custom_email.html'),
81-
// Placeholders contain the values to be filled into the placeholder keys in the file content. A placeholder `{{appName}}` in the email will be replaced the value defined here.
85+
// Placeholders are filled into the template file contents.
86+
// For example, the placeholder `{{appName}}` in the email
87+
// will be replaced the value defined here.
8288
placeholders: {
8389
appName: "ExampleApp"
8490
},
85-
// Extras to add to the email payload that is accessible in the `apiCallback`.
91+
// Extras to add to the email payload that is accessible in the
92+
// `apiCallback`.
8693
extra: {
8794
replyTo: '[email protected]'
8895
},
89-
// A callback that makes the Parse User accessible and allows to return user-customized placeholders that will override the default template placeholders.
96+
// A callback that makes the Parse User accessible and allows
97+
// to return user-customized placeholders that will override
98+
// the default template placeholders.
9099
placeholderCallback: async (user) => {
91100
return {
92101
phone: user.get('phone')
93102
};
94103
},
95-
// A callback that makes the Parse User accessible and allows to return the locale of the user for template localization.
104+
// A callback that makes the Parse User accessible and allows
105+
// to return the locale of the user for template localization.
96106
localeCallback: async (user) => {
97107
return user.get('locale');
98108
}
99109
}
100110
},
101-
// The asynronous callback that contains the composed email payload to be passed on to an 3rd party API. The payload may need to be convert specifically for the API; conversion for common APIs is conveniently available in the `ApiPayloadConverter`. Below is an example for the Mailgun client.
102-
apiCallback: async (payload) => {
111+
// The asynronous callback that contains the composed email payload to
112+
// be passed on to an 3rd party API and optional meta data. The payload
113+
// may need to be converted specifically for the API; conversion for
114+
// common APIs is conveniently available in the `ApiPayloadConverter`.
115+
// Below is an example for the Mailgun client.
116+
apiCallback: async ({ payload, locale }) => {
103117
const mailgunPayload = ApiPayloadConverter.mailgun(payload);
104118
await mailgunClient.messages.create(mailgunDomain, mailgunPayload);
105119
}
@@ -108,7 +122,7 @@ const server = new ParseServer({
108122
});
109123
```
110124

111-
## Templates
125+
# Templates
112126

113127
Emails are composed using templates. A template defines the paths to its content files, for example:
114128

@@ -127,6 +141,17 @@ There are different files for different parts of the email:
127141
- plain-text content (`textPath`)
128142
- HTML content (`htmlPath`)
129143

144+
# Placeholders
145+
Placeholders allow to dynamically insert text into the template file content. The placeholders are filled in according to the key-value definitions returned by the placeholder callback in the adapter configuration.
146+
147+
This is using the [mustache](http://mustache.github.io/mustache.5.html) template syntax. The most commonly used tags are:
148+
- `{{double-mustache}}`
149+
150+
The most basic form of tag; inserts text as HTML escaped by default.
151+
- `{{{triple-mustache}}}`
152+
153+
Inserts text with unescaped HTML, which is required to insert a URL for example.
154+
130155
# Localization
131156

132157
Localization allows to use a specific template depending on the user locale. To turn on localization for a template, add a `localeCallback` to the template configuration.
@@ -143,7 +168,7 @@ path/
143168
│ └── example.html // de localized file
144169
└── de-AT/ // de-AT locale folder
145170
│ └── example.html // de-AT localized file
146-
````
171+
```
147172

148173
Files are matched with the user locale in the following order:
149174
1. Locale match, e.g. locale `de-AT` matches file in folder `de-AT`.
@@ -152,5 +177,5 @@ Files are matched with the user locale in the following order:
152177

153178
# Need help?
154179

155-
- Search through existing issues or open a new issue.
156-
- Ask on StackOverflow using the tag `parse-server` and `api-mail-adapter`.
180+
- Ask on StackOverflow using the tag [parse-server](https://stackoverflow.com/questions/tagged/parse-server).
181+
- Search through existing issues or open a new issue.

spec/ApiMailAdapter.spec.js

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,11 @@ describe('ApiMailAdapter', () => {
147147

148148
await adapter.sendPasswordResetEmail(options);
149149
expect(_sendMail.calls.all()[0].args[0]).toEqual(expectedArguments);
150-
expect(apiCallback.calls.all()[0].args[0].from).toEqual(config.sender);
151-
expect(apiCallback.calls.all()[0].args[0].to).toEqual(user.get('email'));
152-
expect(apiCallback.calls.all()[0].args[0].subject).toMatch("Reset");
153-
expect(apiCallback.calls.all()[0].args[0].text).toMatch("reset");
154-
expect(apiCallback.calls.all()[0].args[0].html).toMatch("reset");
150+
expect(apiCallback.calls.all()[0].args[0].payload.from).toEqual(config.sender);
151+
expect(apiCallback.calls.all()[0].args[0].payload.to).toEqual(user.get('email'));
152+
expect(apiCallback.calls.all()[0].args[0].payload.subject).toMatch("Reset");
153+
expect(apiCallback.calls.all()[0].args[0].payload.text).toMatch("reset");
154+
expect(apiCallback.calls.all()[0].args[0].payload.html).toMatch("reset");
155155
});
156156
});
157157

@@ -175,11 +175,11 @@ describe('ApiMailAdapter', () => {
175175

176176
await adapter.sendVerificationEmail(options);
177177
expect(_sendMail.calls.all()[0].args[0]).toEqual(expectedArguments);
178-
expect(apiCallback.calls.all()[0].args[0].from).toEqual(config.sender);
179-
expect(apiCallback.calls.all()[0].args[0].to).toEqual(user.get('email'));
180-
expect(apiCallback.calls.all()[0].args[0].subject).toMatch("Verification");
181-
expect(apiCallback.calls.all()[0].args[0].text).toMatch("verify");
182-
expect(apiCallback.calls.all()[0].args[0].html).toMatch("verify");
178+
expect(apiCallback.calls.all()[0].args[0].payload.from).toEqual(config.sender);
179+
expect(apiCallback.calls.all()[0].args[0].payload.to).toEqual(user.get('email'));
180+
expect(apiCallback.calls.all()[0].args[0].payload.subject).toMatch("Verification");
181+
expect(apiCallback.calls.all()[0].args[0].payload.text).toMatch("verify");
182+
expect(apiCallback.calls.all()[0].args[0].payload.html).toMatch("verify");
183183
});
184184
});
185185

@@ -216,7 +216,7 @@ describe('ApiMailAdapter', () => {
216216
it('creates payload with correct properties', async () => {
217217
const adapter = new ApiMailAdapter(config);
218218
spyOn(adapter, 'apiCallback').and.callFake(apiResponseSuccess);
219-
const _createPayload = spyOn(adapter, '_createPayload').and.callThrough();
219+
const _createApiData = spyOn(adapter, '_createApiData').and.callThrough();
220220
const options = {
221221
sender: config.sender,
222222
recipient: '[email protected]',
@@ -235,7 +235,7 @@ describe('ApiMailAdapter', () => {
235235

236236
await expectAsync(adapter.sendMail(options)).toBeResolved();
237237

238-
const payload = (await _createPayload.calls.all()[0].returnValue);
238+
const { payload } = (await _createApiData.calls.all()[0].returnValue);
239239
expect(payload.from).toMatch(options.sender);
240240
expect(payload.to).toMatch(options.recipient);
241241
expect(payload.subject).toMatch(options.subject);
@@ -247,7 +247,7 @@ describe('ApiMailAdapter', () => {
247247
it('creates payload with correct properties when overriding extras', async () => {
248248
const adapter = new ApiMailAdapter(config);
249249
spyOn(adapter, 'apiCallback').and.callFake(apiResponseSuccess);
250-
const _createPayload = spyOn(adapter, '_createPayload').and.callThrough();
250+
const _createApiData = spyOn(adapter, '_createApiData').and.callThrough();
251251
const options = {
252252
sender: config.sender,
253253
recipient: '[email protected]',
@@ -266,7 +266,7 @@ describe('ApiMailAdapter', () => {
266266

267267
await expectAsync(adapter.sendMail(options)).toBeResolved();
268268

269-
const payload = (await _createPayload.calls.all()[0].returnValue);
269+
const { payload } = (await _createApiData.calls.all()[0].returnValue);
270270
expect(payload.from).toMatch(options.sender);
271271
expect(payload.to).toMatch(options.recipient);
272272
expect(payload.subject).toMatch(options.subject);
@@ -349,7 +349,7 @@ describe('ApiMailAdapter', () => {
349349
const _loadFile = spyOn(adapter, '_loadFile').and.callThrough();
350350
const options = { message: {}, template: config.templates.customEmail };
351351

352-
await adapter._createPayload(options);
352+
await adapter._createApiData(options);
353353
const subjectFileData = await fs.readFile(options.template.subjectPath);
354354
const textFileData = await fs.readFile(options.template.textPath);
355355
const htmlFileData = await fs.readFile(options.template.htmlPath);
@@ -371,7 +371,7 @@ describe('ApiMailAdapter', () => {
371371
placeholders: { appName: 'ExampleApp' }
372372
};
373373

374-
await adapter._createPayload(options);
374+
await adapter._createApiData(options);
375375

376376
expect(_fillPlaceholders.calls.all()[0].args[0]).not.toContain('ExampleApp');
377377
expect(_fillPlaceholders.calls.all()[1].args[0]).not.toContain('ExampleApp');
@@ -418,10 +418,11 @@ describe('ApiMailAdapter', () => {
418418
describe('localization', () => {
419419
let adapter;
420420
let options;
421+
let apiCallback;
421422

422423
beforeEach(async () => {
423424
adapter = new ApiMailAdapter(config);
424-
spyOn(adapter, 'apiCallback').and.callFake(apiResponseSuccess);
425+
apiCallback = spyOn(adapter, 'apiCallback').and.callFake(apiResponseSuccess);
425426

426427
options = {
427428
message: {},
@@ -432,7 +433,7 @@ describe('ApiMailAdapter', () => {
432433

433434
it('uses user locale variable from user locale callback', async () => {
434435
const _loadFile = spyOn(adapter, '_loadFile').and.callThrough();
435-
await adapter._createPayload(options);
436+
await adapter._createApiData(options);
436437

437438
const subjectFileData = await fs.readFile(path.join(__dirname, 'templates/de-AT/custom_email_subject.txt'));
438439
const textFileData = await fs.readFile(path.join(__dirname, 'templates/de-AT/custom_email.txt'));
@@ -452,7 +453,7 @@ describe('ApiMailAdapter', () => {
452453
return !/\/templates\/de-AT\//.test(path);
453454
});
454455
const _loadFile = spyOn(adapter, '_loadFile').and.callThrough();
455-
await adapter._createPayload(options);
456+
await adapter._createApiData(options);
456457

457458
const subjectFileData = await fs.readFile(path.join(__dirname, 'templates/de/custom_email_subject.txt'));
458459
const textFileData = await fs.readFile(path.join(__dirname, 'templates/de/custom_email.txt'));
@@ -472,7 +473,7 @@ describe('ApiMailAdapter', () => {
472473
return !/\/templates\/de(-AT)?\//.test(path);
473474
});
474475
const _loadFile = spyOn(adapter, '_loadFile').and.callThrough();
475-
await adapter._createPayload(options);
476+
await adapter._createApiData(options);
476477

477478
const subjectFileData = await fs.readFile(path.join(__dirname, 'templates/custom_email_subject.txt'));
478479
const textFileData = await fs.readFile(path.join(__dirname, 'templates/custom_email.txt'));
@@ -485,5 +486,16 @@ describe('ApiMailAdapter', () => {
485486
expect(textSpyData.toString('utf8')).toEqual(textFileData.toString('utf8'));
486487
expect(htmlSpyData.toString('utf8')).toEqual(htmlFileData.toString('utf8'));
487488
});
489+
490+
it('makes user locale available in API callback', async () => {
491+
const locale = await options.template.localeCallback();
492+
const email = {
493+
templateName: 'customEmailWithLocaleCallback',
494+
recipient: '[email protected]',
495+
direct: true
496+
}
497+
await adapter._sendMail(email);
498+
expect(apiCallback.calls.all()[0].args[0].locale).toContain(locale);
499+
});
488500
});
489501
});

0 commit comments

Comments
 (0)