@adaptivestone/framework 5.0.0-beta.1 → 5.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -1
- package/Cli.js +3 -4
- package/cluster.js +0 -2
- package/commands/CreateUser.js +31 -0
- package/commands/DropIndex.js +14 -5
- package/commands/GenerateRandomBytes.js +2 -0
- package/commands/GetOpenApiJson.js +13 -2
- package/commands/SyncIndexes.js +1 -0
- package/commands/migration/Create.js +21 -10
- package/commands/migration/Migrate.js +1 -0
- package/controllers/Auth.js +20 -29
- package/eslint.config.js +68 -0
- package/folderConfig.js +0 -1
- package/helpers/files.js +1 -4
- package/helpers/logger.js +0 -1
- package/helpers/yup.js +2 -4
- package/jsconfig.json +4 -4
- package/models/Lock.js +107 -0
- package/models/User.js +23 -6
- package/modules/AbstractCommand.js +25 -1
- package/modules/AbstractController.js +0 -7
- package/modules/AbstractModel.js +21 -9
- package/modules/Base.js +3 -2
- package/modules/BaseCli.js +82 -9
- package/package.json +20 -16
- package/server.d.ts +9 -7
- package/server.js +44 -10
- package/services/cache/Cache.d.ts +1 -1
- package/services/cache/Cache.js +3 -3
- package/services/http/HttpServer.js +6 -13
- package/services/http/middleware/AbstractMiddleware.js +3 -3
- package/services/http/middleware/I18n.js +1 -1
- package/services/http/middleware/Pagination.js +4 -4
- package/services/http/middleware/RateLimiter.js +2 -2
- package/services/messaging/email/templates/.gitkeep +0 -0
- package/services/validate/ValidateService.js +4 -4
- package/services/validate/drivers/AbstractValidator.js +2 -2
- package/services/validate/drivers/CustomValidator.js +2 -2
- package/services/validate/drivers/YupValidator.js +3 -3
- package/tests/setup.js +8 -6
- package/tests/setupVitest.js +9 -7
- package/types/ICommandArguments.d.ts +41 -0
- package/types/TFoldersConfig.d.ts +7 -4
- package/vitest.config.js +4 -3
- package/config/mail.js +0 -29
- package/services/messaging/email/index.js +0 -217
- package/services/messaging/email/templates/emptyTemplate/html.pug +0 -9
- package/services/messaging/email/templates/emptyTemplate/subject.pug +0 -1
- package/services/messaging/email/templates/emptyTemplate/text.pug +0 -1
- package/services/messaging/index.js +0 -3
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
RateLimiterMongo,
|
|
5
5
|
} from 'rate-limiter-flexible';
|
|
6
6
|
import merge from 'deepmerge';
|
|
7
|
-
import
|
|
7
|
+
import { createClient } from '@redis/client';
|
|
8
8
|
import mongoose from 'mongoose';
|
|
9
9
|
import AbstractMiddleware from './AbstractMiddleware.js';
|
|
10
10
|
|
|
@@ -46,7 +46,7 @@ class RateLimiter extends AbstractMiddleware {
|
|
|
46
46
|
|
|
47
47
|
initRedisLimiter() {
|
|
48
48
|
const redisConfig = this.app.getConfig('redis');
|
|
49
|
-
const redisClient =
|
|
49
|
+
const redisClient = createClient({
|
|
50
50
|
url: redisConfig.url,
|
|
51
51
|
});
|
|
52
52
|
|
|
File without changes
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { isSchema } from 'yup';
|
|
2
2
|
import YupValidator from './drivers/YupValidator.js';
|
|
3
3
|
import CustomValidator from './drivers/CustomValidator.js';
|
|
4
4
|
import Base from '../../modules/Base.js';
|
|
@@ -7,7 +7,7 @@ class ValidateService extends Base {
|
|
|
7
7
|
constructor(app, validator) {
|
|
8
8
|
super(app);
|
|
9
9
|
this.validator = validator
|
|
10
|
-
?
|
|
10
|
+
? ValidateService.getDriverByValidatorBody(app, validator)
|
|
11
11
|
: null;
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -30,7 +30,7 @@ class ValidateService extends Base {
|
|
|
30
30
|
if (this.isValidatorExists(body)) {
|
|
31
31
|
return body;
|
|
32
32
|
}
|
|
33
|
-
if (
|
|
33
|
+
if (isSchema(body)) {
|
|
34
34
|
const yupValidator = new YupValidator(app, body);
|
|
35
35
|
return yupValidator;
|
|
36
36
|
}
|
|
@@ -104,7 +104,7 @@ class ValidateService extends Base {
|
|
|
104
104
|
const result = [];
|
|
105
105
|
|
|
106
106
|
for (const validator of validators) {
|
|
107
|
-
const formatedValidator =
|
|
107
|
+
const formatedValidator = ValidateService.getDriverByValidatorBody(
|
|
108
108
|
this.app,
|
|
109
109
|
validator,
|
|
110
110
|
);
|
|
@@ -18,8 +18,8 @@ class AbstractValidator extends Base {
|
|
|
18
18
|
return {};
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
// eslint-disable-next-line class-methods-use-this
|
|
22
|
-
async validateFields() {
|
|
21
|
+
// eslint-disable-next-line class-methods-use-this, no-unused-vars
|
|
22
|
+
async validateFields(data, { query, body, appInfo }) {
|
|
23
23
|
// IMPLENT;
|
|
24
24
|
return true;
|
|
25
25
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ValidationError } from 'yup';
|
|
2
2
|
import AbstractValidator from './AbstractValidator.js';
|
|
3
3
|
|
|
4
4
|
class CustomValidator extends AbstractValidator {
|
|
@@ -19,7 +19,7 @@ class CustomValidator extends AbstractValidator {
|
|
|
19
19
|
} catch (e) {
|
|
20
20
|
this.logger.warn(`CustomValidator validateFields ${e}`);
|
|
21
21
|
if (e.path) {
|
|
22
|
-
throw new
|
|
22
|
+
throw new ValidationError({
|
|
23
23
|
[e.path]: e.message,
|
|
24
24
|
});
|
|
25
25
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ValidationError } from 'yup';
|
|
2
2
|
import AbstractValidator from './AbstractValidator.js';
|
|
3
3
|
|
|
4
4
|
class YupValidator extends AbstractValidator {
|
|
5
5
|
get fieldsInJsonFormat() {
|
|
6
|
-
return
|
|
6
|
+
return YupValidator.convertFieldsToJson(this.body);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
static convertFieldsToJson(fields) {
|
|
@@ -76,7 +76,7 @@ class YupValidator extends AbstractValidator {
|
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
throw new
|
|
79
|
+
throw new ValidationError({
|
|
80
80
|
...errorAnswer,
|
|
81
81
|
});
|
|
82
82
|
}
|
package/tests/setup.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { randomBytes } from 'node:crypto';
|
|
4
4
|
import { MongoMemoryReplSet } from 'mongodb-memory-server';
|
|
5
5
|
import mongoose from 'mongoose';
|
|
6
|
-
import
|
|
6
|
+
import { createClient } from '@redis/client';
|
|
7
7
|
import Server from '../server.js';
|
|
8
8
|
|
|
9
9
|
import clearRedisNamespace from '../helpers/redis/clearNamespace.js';
|
|
@@ -12,7 +12,9 @@ mongoose.set('autoIndex', false);
|
|
|
12
12
|
|
|
13
13
|
let mongoMemoryServerInstance;
|
|
14
14
|
|
|
15
|
+
// @ts-ignore
|
|
15
16
|
jest.setTimeout(1000000);
|
|
17
|
+
// @ts-ignore
|
|
16
18
|
beforeAll(async () => {
|
|
17
19
|
mongoMemoryServerInstance = await MongoMemoryReplSet.create({
|
|
18
20
|
// binary: { version: '4.4.6' },
|
|
@@ -29,7 +31,6 @@ beforeAll(async () => {
|
|
|
29
31
|
config: process.env.TEST_FOLDER_CONFIG || path.resolve('./config'),
|
|
30
32
|
controllers:
|
|
31
33
|
process.env.TEST_FOLDER_CONTROLLERS || path.resolve('./controllers'),
|
|
32
|
-
views: process.env.TEST_FOLDER_VIEWS || path.resolve('./views'),
|
|
33
34
|
models: process.env.TEST_FOLDER_MODELS || path.resolve('./models'),
|
|
34
35
|
emails:
|
|
35
36
|
process.env.TEST_FOLDER_EMAIL ||
|
|
@@ -64,9 +65,7 @@ beforeAll(async () => {
|
|
|
64
65
|
nick: 'testUserNickName',
|
|
65
66
|
},
|
|
66
67
|
}).catch((e) => {
|
|
67
|
-
// eslint-disable-next-line no-console
|
|
68
68
|
console.error(e);
|
|
69
|
-
// eslint-disable-next-line no-console
|
|
70
69
|
console.info(
|
|
71
70
|
'That error can happens in case you have custom user model. Please use global.testSetup.disableUserCreate flag to skip user creating',
|
|
72
71
|
);
|
|
@@ -79,6 +78,7 @@ beforeAll(async () => {
|
|
|
79
78
|
await global.server.startServer();
|
|
80
79
|
});
|
|
81
80
|
|
|
81
|
+
// @ts-ignore
|
|
82
82
|
beforeEach(() => {
|
|
83
83
|
if (global.server) {
|
|
84
84
|
const key = `test-${Math.random().toString(36).substring(7)}`;
|
|
@@ -88,21 +88,23 @@ beforeEach(() => {
|
|
|
88
88
|
}
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
+
// @ts-ignore
|
|
91
92
|
afterEach(async () => {
|
|
92
93
|
if (global.server) {
|
|
93
94
|
const { url, namespace } = global.server.getConfig('redis');
|
|
94
|
-
const redisClient =
|
|
95
|
+
const redisClient = createClient({ url });
|
|
95
96
|
|
|
96
97
|
try {
|
|
97
98
|
await redisClient.connect();
|
|
98
99
|
await clearRedisNamespace(redisClient, namespace);
|
|
99
100
|
await redisClient.disconnect();
|
|
100
|
-
} catch
|
|
101
|
+
} catch {
|
|
101
102
|
// that ok. No redis connection
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
});
|
|
105
106
|
|
|
107
|
+
// @ts-ignore
|
|
106
108
|
afterAll(async () => {
|
|
107
109
|
if (global.server) {
|
|
108
110
|
global.server.app.httpServer.shutdown();
|
package/tests/setupVitest.js
CHANGED
|
@@ -4,7 +4,7 @@ import { beforeAll, beforeEach, afterEach, afterAll } from 'vitest';
|
|
|
4
4
|
|
|
5
5
|
import mongoose from 'mongoose'; // we do not need create indexes on tests
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import { createClient } from '@redis/client';
|
|
8
8
|
import clearRedisNamespace from '../helpers/redis/clearNamespace.js';
|
|
9
9
|
import Server from '../server.js';
|
|
10
10
|
|
|
@@ -18,7 +18,6 @@ beforeAll(async () => {
|
|
|
18
18
|
config: process.env.TEST_FOLDER_CONFIG || path.resolve('./config'),
|
|
19
19
|
controllers:
|
|
20
20
|
process.env.TEST_FOLDER_CONTROLLERS || path.resolve('./controllers'),
|
|
21
|
-
views: process.env.TEST_FOLDER_VIEWS || path.resolve('./views'),
|
|
22
21
|
models: process.env.TEST_FOLDER_MODELS || path.resolve('./models'),
|
|
23
22
|
emails:
|
|
24
23
|
process.env.TEST_FOLDER_EMAIL ||
|
|
@@ -57,9 +56,7 @@ beforeAll(async () => {
|
|
|
57
56
|
nick: 'testUserNickName',
|
|
58
57
|
},
|
|
59
58
|
}).catch((e) => {
|
|
60
|
-
// eslint-disable-next-line no-console
|
|
61
59
|
console.error(e);
|
|
62
|
-
// eslint-disable-next-line no-console
|
|
63
60
|
console.info(
|
|
64
61
|
'That error can happens in case you have custom user model. Please use global.testSetup.disableUserCreate flag to skip user creating',
|
|
65
62
|
);
|
|
@@ -84,13 +81,13 @@ beforeEach(() => {
|
|
|
84
81
|
afterEach(async () => {
|
|
85
82
|
if (global.server) {
|
|
86
83
|
const { url, namespace } = global.server.getConfig('redis');
|
|
87
|
-
const redisClient =
|
|
84
|
+
const redisClient = createClient({ url });
|
|
88
85
|
|
|
89
86
|
try {
|
|
90
87
|
await redisClient.connect();
|
|
91
88
|
await clearRedisNamespace(redisClient, namespace);
|
|
92
89
|
await redisClient.disconnect();
|
|
93
|
-
} catch
|
|
90
|
+
} catch {
|
|
94
91
|
// that ok. No redis connection
|
|
95
92
|
}
|
|
96
93
|
}
|
|
@@ -104,6 +101,11 @@ afterAll(async () => {
|
|
|
104
101
|
if (typeof global.testSetup.afterAll === 'function') {
|
|
105
102
|
await global.testSetup.afterAll();
|
|
106
103
|
}
|
|
107
|
-
|
|
104
|
+
try {
|
|
105
|
+
await mongoose.connection.db.dropDatabase(); // clean database after test
|
|
106
|
+
} catch {
|
|
107
|
+
// that ok. No mongoose connection
|
|
108
|
+
}
|
|
109
|
+
|
|
108
110
|
await mongoose.disconnect();
|
|
109
111
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// this is from nodejs types
|
|
2
|
+
interface ParseArgsOptionConfig {
|
|
3
|
+
/**
|
|
4
|
+
* Type of argument.
|
|
5
|
+
*/
|
|
6
|
+
type: 'string' | 'boolean';
|
|
7
|
+
/**
|
|
8
|
+
* Whether this option can be provided multiple times.
|
|
9
|
+
* If `true`, all values will be collected in an array.
|
|
10
|
+
* If `false`, values for the option are last-wins.
|
|
11
|
+
* @default false.
|
|
12
|
+
*/
|
|
13
|
+
multiple?: boolean | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* A single character alias for the option.
|
|
16
|
+
*/
|
|
17
|
+
short?: string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* The default option value when it is not set by args.
|
|
20
|
+
* It must be of the same type as the the `type` property.
|
|
21
|
+
* When `multiple` is `true`, it must be an array.
|
|
22
|
+
* @since v18.11.0
|
|
23
|
+
*/
|
|
24
|
+
default?: string | boolean | string[] | boolean[] | undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ParseArgsOptionsConfigExtended extends ParseArgsOptionConfig {
|
|
28
|
+
/**
|
|
29
|
+
* A description of the option.
|
|
30
|
+
*/
|
|
31
|
+
description?: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Is it required?
|
|
35
|
+
*/
|
|
36
|
+
required?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ICommandArguments {
|
|
40
|
+
[longOption: string]: ParseArgsOptionsConfigExtended;
|
|
41
|
+
}
|
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
* @param config path to folder with config files
|
|
3
3
|
* @param models path to folder with moidels files
|
|
4
4
|
* @param controllers path to folder with controllers files
|
|
5
|
-
* @param
|
|
5
|
+
* @param migrations path to folder with migrations files
|
|
6
6
|
* @param locales path to folder with locales files
|
|
7
|
-
* @param
|
|
7
|
+
* @param migrations path to folder with migrations files
|
|
8
|
+
* @param [emails] path to folder with emails files. Optional
|
|
8
9
|
*/
|
|
9
10
|
type FolderConfig = {
|
|
10
11
|
config: string;
|
|
11
12
|
models: string;
|
|
12
13
|
controllers: string;
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
commands: string;
|
|
15
|
+
locales: string;
|
|
16
|
+
migrations: string;
|
|
17
|
+
emails?: string;
|
|
15
18
|
};
|
|
16
19
|
|
|
17
20
|
export default FolderConfig;
|
package/vitest.config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// eslint-disable-next-line import/no-unresolved
|
|
1
|
+
// eslint-disable-next-line import-x/no-unresolved
|
|
2
2
|
import { defineConfig } from 'vitest/config';
|
|
3
3
|
|
|
4
4
|
export default defineConfig({
|
|
@@ -6,11 +6,12 @@ export default defineConfig({
|
|
|
6
6
|
globalSetup: './tests/globalSetupVitest.js',
|
|
7
7
|
setupFiles: './tests/setupVitest.js',
|
|
8
8
|
testTimeout: 10000,
|
|
9
|
-
outputFile: './coverage/rspec.xml',
|
|
9
|
+
outputFile: './coverage/junit.rspec.xml',
|
|
10
10
|
reporters: ['default', 'junit'],
|
|
11
11
|
coverage: {
|
|
12
|
+
provider: 'v8',
|
|
12
13
|
enabled: true,
|
|
13
|
-
reporter: ['text', 'html', 'clover', 'json'
|
|
14
|
+
reporter: ['text', 'html', 'clover', 'json'],
|
|
14
15
|
},
|
|
15
16
|
},
|
|
16
17
|
});
|
package/config/mail.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
|
|
3
|
-
export default {
|
|
4
|
-
from: 'Localhost <info@localhost>',
|
|
5
|
-
transports: {
|
|
6
|
-
sendMail: {
|
|
7
|
-
// path: "path to the sendmail command (defaults to 'sendmail')"
|
|
8
|
-
// args: 'an array of extra command line options to pass to the sendmail command (ie. ["-f", "foo@blurdybloop.com"]).'
|
|
9
|
-
},
|
|
10
|
-
stub: {},
|
|
11
|
-
smtp: {
|
|
12
|
-
// https://github.com/nodemailer/nodemailer#set-up-smtp
|
|
13
|
-
host: process.env.EMAIL_HOST || 'smtp.mailtrap.io',
|
|
14
|
-
port: process.env.EMAIL_PORT || 2525,
|
|
15
|
-
auth: {
|
|
16
|
-
user: process.env.EMAIL_USER,
|
|
17
|
-
pass: process.env.EMAIL_PASSWORD,
|
|
18
|
-
},
|
|
19
|
-
connectionTimeout: 10000, // timeout to 10 seconds
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
transport: process.env.EMAIL_TRANSPORT || 'smtp',
|
|
23
|
-
webResources: {
|
|
24
|
-
// https://github.com/jrit/web-resource-inliner path to find resources
|
|
25
|
-
relativeTo: path.resolve('src/services/messaging/email/resources'),
|
|
26
|
-
images: false,
|
|
27
|
-
},
|
|
28
|
-
globalVariablesToTemplates: {},
|
|
29
|
-
};
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import * as url from 'node:url';
|
|
4
|
-
import { promisify } from 'node:util';
|
|
5
|
-
import nodemailer from 'nodemailer';
|
|
6
|
-
import sendMail from 'nodemailer-sendmail-transport';
|
|
7
|
-
import stub from 'nodemailer-stub-transport';
|
|
8
|
-
import pug from 'pug';
|
|
9
|
-
import juice from 'juice';
|
|
10
|
-
import { convert } from 'html-to-text';
|
|
11
|
-
import Base from '../../../modules/Base.js';
|
|
12
|
-
|
|
13
|
-
const mailTransports = {
|
|
14
|
-
sendMail,
|
|
15
|
-
stub,
|
|
16
|
-
smtp: (data) => data,
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
class Mail extends Base {
|
|
20
|
-
/**
|
|
21
|
-
* Construct mail class
|
|
22
|
-
* @param {object} app
|
|
23
|
-
* @param {string} template template name
|
|
24
|
-
* @param {object} [templateData={}] data to render in template. Object with value that available inside template
|
|
25
|
-
* @param {object} [i18n] data to render in template
|
|
26
|
-
*/
|
|
27
|
-
constructor(app, template, templateData = {}, i18n = null) {
|
|
28
|
-
super(app);
|
|
29
|
-
const dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
|
30
|
-
if (!path.isAbsolute(template)) {
|
|
31
|
-
if (
|
|
32
|
-
fs.existsSync(
|
|
33
|
-
`${this.app.foldersConfig.emails}/${path.basename(template)}`,
|
|
34
|
-
)
|
|
35
|
-
) {
|
|
36
|
-
this.template = `${this.app.foldersConfig.emails}/${path.basename(
|
|
37
|
-
template,
|
|
38
|
-
)}`;
|
|
39
|
-
} else if (
|
|
40
|
-
fs.existsSync(
|
|
41
|
-
path.join(dirname, `/templates/${path.basename(template)}`),
|
|
42
|
-
)
|
|
43
|
-
) {
|
|
44
|
-
this.template = path.join(
|
|
45
|
-
dirname,
|
|
46
|
-
`/templates/${path.basename(template)}`,
|
|
47
|
-
);
|
|
48
|
-
} else {
|
|
49
|
-
this.template = path.join(dirname, `/templates/emptyTemplate`);
|
|
50
|
-
this.logger.error(
|
|
51
|
-
`Template '${template}' not found. Using 'emptyTemplate' as a fallback`,
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
this.templateData = templateData;
|
|
56
|
-
this.i18n = i18n ?? {
|
|
57
|
-
t: (str) => str,
|
|
58
|
-
locale: 'en', // todo change it to config
|
|
59
|
-
};
|
|
60
|
-
this.locale = this.i18n?.language;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Render template
|
|
65
|
-
* @param {object} type and fullpath
|
|
66
|
-
* @param {object} templateData
|
|
67
|
-
* @returns string
|
|
68
|
-
*/
|
|
69
|
-
// eslint-disable-next-line class-methods-use-this
|
|
70
|
-
async #renderTemplateFile({ type, fullPath } = {}, templateData = {}) {
|
|
71
|
-
if (!type) {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
switch (type) {
|
|
76
|
-
case 'html':
|
|
77
|
-
case 'text':
|
|
78
|
-
case 'css':
|
|
79
|
-
return fs.promises.readFile(fullPath, { encoding: 'utf8' });
|
|
80
|
-
case 'pug': {
|
|
81
|
-
const compiledFunction = pug.compileFile(fullPath);
|
|
82
|
-
return compiledFunction(templateData);
|
|
83
|
-
}
|
|
84
|
-
default:
|
|
85
|
-
throw new Error(`Template type ${type} is not supported`);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Render template
|
|
91
|
-
* @return {Promise}
|
|
92
|
-
*/
|
|
93
|
-
async renderTemplate() {
|
|
94
|
-
const files = await fs.promises.readdir(this.template);
|
|
95
|
-
const templates = {};
|
|
96
|
-
for (const file of files) {
|
|
97
|
-
const [name, extension] = file.split('.');
|
|
98
|
-
templates[name] = {
|
|
99
|
-
type: extension,
|
|
100
|
-
fullPath: path.join(this.template, file),
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (!templates.html || !templates.subject) {
|
|
105
|
-
throw new Error(
|
|
106
|
-
'Template HTML and Subject must be provided. Please follow documentation for details https://framework.adaptivestone.com/docs/email',
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
const mailConfig = this.app.getConfig('mail');
|
|
110
|
-
|
|
111
|
-
const templateDataToRender = {
|
|
112
|
-
locale: this.locale,
|
|
113
|
-
t: this.i18n.t.bind(this.i18n),
|
|
114
|
-
...mailConfig.globalVariablesToTemplates,
|
|
115
|
-
...this.templateData,
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const [htmlRendered, subjectRendered, textRendered, extraCss] =
|
|
119
|
-
await Promise.all([
|
|
120
|
-
this.#renderTemplateFile(templates.html, templateDataToRender),
|
|
121
|
-
this.#renderTemplateFile(templates.subject, templateDataToRender),
|
|
122
|
-
this.#renderTemplateFile(templates.text, templateDataToRender),
|
|
123
|
-
this.#renderTemplateFile(templates.style),
|
|
124
|
-
]);
|
|
125
|
-
|
|
126
|
-
juice.tableElements = ['TABLE'];
|
|
127
|
-
|
|
128
|
-
const juiceResourcesAsync = promisify(juice.juiceResources);
|
|
129
|
-
|
|
130
|
-
const inlinedHTML = await juiceResourcesAsync(htmlRendered, {
|
|
131
|
-
preserveImportant: true,
|
|
132
|
-
webResources: mailConfig.webResources,
|
|
133
|
-
extraCss,
|
|
134
|
-
});
|
|
135
|
-
return {
|
|
136
|
-
htmlRaw: htmlRendered,
|
|
137
|
-
subject: subjectRendered,
|
|
138
|
-
text: textRendered,
|
|
139
|
-
inlinedHTML,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Send email
|
|
145
|
-
* @param {string} to email send to
|
|
146
|
-
* @param {string} [from = mailConfig.from]
|
|
147
|
-
* @param {object} [aditionalNodemailerOptions = {}] additional option to nodemailer
|
|
148
|
-
* @return {Promise}
|
|
149
|
-
*/
|
|
150
|
-
async send(to, from = null, aditionalNodemailerOptions = {}) {
|
|
151
|
-
const { subject, text, inlinedHTML } = await this.renderTemplate();
|
|
152
|
-
|
|
153
|
-
return this.constructor.sendRaw(
|
|
154
|
-
this.app,
|
|
155
|
-
to,
|
|
156
|
-
subject,
|
|
157
|
-
inlinedHTML,
|
|
158
|
-
text,
|
|
159
|
-
from,
|
|
160
|
-
aditionalNodemailerOptions,
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Send provided text (html) to email. Low level function. All data should be prepared before sending (like inline styles)
|
|
166
|
-
* @param {import('../../../server.js').default['app']} app application
|
|
167
|
-
* @param {string} to send to
|
|
168
|
-
* @param {string} subject email topic
|
|
169
|
-
* @param {string} html hmlt body of emain
|
|
170
|
-
* @param {string} [text] if not provided will be generated from html string
|
|
171
|
-
* @param {string} [from = mailConfig.from] from. If not provided will be grabbed from config
|
|
172
|
-
* @param {object} [additionalNodeMailerOption = {}] any otipns to pass to nodemailer https://nodemailer.com/message/
|
|
173
|
-
*/
|
|
174
|
-
static async sendRaw(
|
|
175
|
-
app,
|
|
176
|
-
to,
|
|
177
|
-
subject,
|
|
178
|
-
html,
|
|
179
|
-
text = null,
|
|
180
|
-
from = null,
|
|
181
|
-
additionalNodeMailerOption = {},
|
|
182
|
-
) {
|
|
183
|
-
if (!app || !to || !subject || !html) {
|
|
184
|
-
throw new Error('App, to, subject and html is required fields.');
|
|
185
|
-
}
|
|
186
|
-
const mailConfig = app.getConfig('mail');
|
|
187
|
-
if (!from) {
|
|
188
|
-
// eslint-disable-next-line no-param-reassign
|
|
189
|
-
from = mailConfig.from;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (!text) {
|
|
193
|
-
// eslint-disable-next-line no-param-reassign
|
|
194
|
-
text = convert(html, {
|
|
195
|
-
selectors: [{ selector: 'img', format: 'skip' }],
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
const transportConfig = mailConfig.transports[mailConfig.transport];
|
|
199
|
-
const transport = mailTransports[mailConfig.transport];
|
|
200
|
-
const transporter = nodemailer.createTransport(transport(transportConfig));
|
|
201
|
-
|
|
202
|
-
return transporter.sendMail({
|
|
203
|
-
from,
|
|
204
|
-
to,
|
|
205
|
-
subject,
|
|
206
|
-
text,
|
|
207
|
-
html,
|
|
208
|
-
...additionalNodeMailerOption,
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
static get loggerGroup() {
|
|
213
|
-
return 'email_';
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export default Mail;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
= `New message`
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
= `Good day`
|