@adaptivestone/framework 5.0.0-beta.7 → 5.0.0-beta.9

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 CHANGED
@@ -1,9 +1,21 @@
1
- ### 5.0.0-beta.7
2
- [UPDATE] update deps
3
- [UPDATE] change vitest shutdown behavior as mongo driver v6.13 change befaviur that affect us (MongoClient.close now closes any outstanding cursors)
1
+ ### 5.0.0-beta.9
4
2
 
3
+ [BREAKING] move email module to separate package @adaptivestone/framework-module-email. Please use it if you want to send emails
4
+ [NEW] app now contains 'frameworkFolder' folder the framework located. Mostly for modules usage
5
+ [BREAKING] remove VIEWS folders at all. Should not afffect any user as this was not used internally
6
+ [UPDATE] update typing
7
+ [UPDATE] change redis -> @redis/client as we are using only client from pakage
8
+ [BREAKING] removed noidemailer-sendmail-transport. Not needed anymore and not recommended to use as well
5
9
 
10
+ ### 5.0.0-beta.8
11
+
12
+ [UPDATE] update deps
13
+ [NEW] Lock model for working locks via mongoDB
6
14
 
15
+ ### 5.0.0-beta.7
16
+
17
+ [UPDATE] update deps
18
+ [UPDATE] change vitest shutdown behavior as mongo driver v6.13 change befaviur that affect us (MongoClient.close now closes any outstanding cursors)
7
19
 
8
20
  ### 5.0.0-beta.5
9
21
 
@@ -107,12 +107,9 @@ class Auth extends AbstractController {
107
107
 
108
108
  const { isAuthWithVefificationFlow } = this.app.getConfig('auth');
109
109
  if (isAuthWithVefificationFlow) {
110
- const answer = await user.sendVerificationEmail(req.i18n).catch((e) => {
110
+ await user.sendVerificationEmail(req.i18n).catch((e) => {
111
111
  this.logger.error(e);
112
112
  });
113
- if (!answer) {
114
- return res.status(500).json();
115
- }
116
113
  }
117
114
  return res.status(201).json();
118
115
  }
package/folderConfig.js CHANGED
@@ -5,7 +5,6 @@ export default {
5
5
  config: path.resolve('./config'),
6
6
  models: path.resolve('./models'),
7
7
  controllers: path.resolve('./controllers'),
8
- views: path.resolve('./views'),
9
8
  locales: path.resolve('./locales'),
10
9
  emails: path.resolve('./services/messaging/email/templates'),
11
10
  commands: path.resolve('./commands'),
package/jsconfig.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "compilerOptions": {
3
3
  "module": "NodeNext",
4
- "target": "ES2022",
4
+ "target": "ES2024",
5
5
  "moduleResolution": "nodenext",
6
6
  "checkJs": true
7
7
  },
package/models/Lock.js ADDED
@@ -0,0 +1,107 @@
1
+ import AbstractModel from '../modules/AbstractModel.js';
2
+
3
+ class Lock extends AbstractModel {
4
+ initHooks() {
5
+ this.mongooseSchema.index({ expiredAt: 1 }, { expireAfterSeconds: 0 });
6
+ }
7
+
8
+ // eslint-disable-next-line class-methods-use-this
9
+ get modelSchema() {
10
+ return {
11
+ _id: { type: String, required: true },
12
+ expiredAt: {
13
+ type: Date,
14
+ },
15
+ };
16
+ }
17
+
18
+ /**
19
+ * acquire lock based on lock name
20
+ * @param {string} name
21
+ * @param {number} [ttlSeconds=30]
22
+ * @returns {Promise<boolean>}
23
+ */
24
+ static async acquireLock(name, ttlSeconds = 30) {
25
+ try {
26
+ await this.create({
27
+ _id: name,
28
+ expiredAt: new Date(Date.now() + ttlSeconds * 1000),
29
+ });
30
+ } catch (error) {
31
+ if (error.code !== 11000) {
32
+ // not a duplicate leys
33
+ throw error;
34
+ }
35
+ return false;
36
+ }
37
+ return true;
38
+ }
39
+
40
+ /**
41
+ * release lock based on lock name
42
+ * @param {string} name
43
+ * @returns {Promise<boolean>}
44
+ */
45
+ static async releaseLock(name) {
46
+ const res = await this.deleteOne({ _id: name });
47
+ if (res.acknowledged && res.deletedCount) {
48
+ return true;
49
+ }
50
+ return false;
51
+ }
52
+
53
+ /**
54
+ * wait lock based on lock name
55
+ * @param {string} name
56
+ * @returns {Promise}
57
+ */
58
+ static async waitForUnlock(name) {
59
+ const res = await this.findOne({ _id: name });
60
+ if (!res) {
61
+ return Promise.resolve();
62
+ }
63
+
64
+ return new Promise((resolve) => {
65
+ const stream = this.watch([
66
+ { $match: { operationType: 'delete', 'documentKey._id': name } },
67
+ ]);
68
+ stream.on('change', () => {
69
+ stream.close();
70
+ resolve();
71
+ });
72
+ });
73
+ }
74
+
75
+ /**
76
+ * get lock remaining time based on lock name
77
+ * @param {string} name
78
+ * @returns {Promise<{ttl: number}>}
79
+ */
80
+ static async getLockData(name) {
81
+ const res = await this.findOne({ _id: name });
82
+ if (!res) {
83
+ return { ttl: 0 };
84
+ }
85
+ return { ttl: res.expiredAt.getTime() - Date.now() };
86
+ }
87
+
88
+ /**
89
+ * get lock remaining time based on lock name
90
+ * @param {string[]} names
91
+ * @returns {Promise<{name: string, ttl: number}[]>}
92
+ */
93
+ static async getLocksData(names) {
94
+ const res = await this.find({ _id: { $in: names } });
95
+ const lockMap = new Map(res.map((lock) => [lock._id, lock]));
96
+
97
+ return names.map((name) => {
98
+ const lock = lockMap.get(name);
99
+ return {
100
+ name,
101
+ ttl: lock ? lock.expiredAt.getTime() - Date.now() : 0,
102
+ };
103
+ });
104
+ }
105
+ }
106
+
107
+ export default Lock;
package/models/User.js CHANGED
@@ -14,6 +14,7 @@ class User extends AbstractModel {
14
14
  initHooks() {
15
15
  this.mongooseSchema.pre('save', async function userPreSaveHook() {
16
16
  if (this.isModified('password')) {
17
+ // @ts-ignore
17
18
  this.password = await this.constructor.hashPassword(this.password);
18
19
  }
19
20
  });
@@ -172,9 +173,18 @@ class User extends AbstractModel {
172
173
  async sendPasswordRecoveryEmail(i18n) {
173
174
  const passwordRecoveryToken =
174
175
  await User.generateUserPasswordRecoveryToken(this);
176
+ let Mailer;
175
177
  // speed optimisation
176
- const Mailer = (await import('../services/messaging/email/index.js'))
177
- .default;
178
+ try {
179
+ // @ts-ignore
180
+ // eslint-disable-next-line import-x/no-unresolved
181
+ Mailer = (await import('@adaptivestone/framework-module-email')).default;
182
+ } catch {
183
+ const error =
184
+ 'Mailer not found. Please install @adaptivestone/framework-module-email in order to use it';
185
+ this.getSuper().logger.error(error);
186
+ return false;
187
+ }
178
188
 
179
189
  const mail = new Mailer(
180
190
  this.getSuper().app,
@@ -243,8 +253,17 @@ class User extends AbstractModel {
243
253
  async sendVerificationEmail(i18n) {
244
254
  const verificationToken = await User.generateUserVerificationToken(this);
245
255
  // speed optimisation
246
- const Mailer = (await import('../services/messaging/email/index.js'))
247
- .default;
256
+ let Mailer;
257
+ try {
258
+ // @ts-ignore
259
+ // eslint-disable-next-line import-x/no-unresolved
260
+ Mailer = (await import('@adaptivestone/framework-module-email')).default;
261
+ } catch {
262
+ const error =
263
+ 'Mailer not found. Please install @adaptivestone/framework-module-email in order to use it';
264
+ this.getSuper().logger.error(error);
265
+ return false;
266
+ }
248
267
  const mail = new Mailer(
249
268
  this.getSuper().app,
250
269
  'verification',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaptivestone/framework",
3
- "version": "5.0.0-beta.7",
3
+ "version": "5.0.0-beta.9",
4
4
  "description": "Adaptive stone node js framework",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -30,20 +30,14 @@
30
30
  "author": "Andrey Logunov",
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
+ "@redis/client": "^1.6.0",
33
34
  "deepmerge": "^4.2.2",
34
35
  "express": "^5.0.1",
35
36
  "formidable": "^3.5.1",
36
- "html-to-text": "^9.0.3",
37
37
  "i18next": "^24.0.0",
38
38
  "i18next-fs-backend": "^2.0.0",
39
- "juice": "^11.0.0",
40
39
  "mongoose": "^8.0.0",
41
- "nodemailer": "^6.6.3",
42
- "nodemailer-sendmail-transport": "^1.0.2",
43
- "nodemailer-stub-transport": "^1.1.0",
44
- "pug": "^3.0.2",
45
40
  "rate-limiter-flexible": "^5.0.0",
46
- "redis": "^4.3.1",
47
41
  "winston": "^3.3.3",
48
42
  "winston-transport-sentry-node": "^3.0.0",
49
43
  "yup": "^1.0.0"
@@ -64,6 +58,14 @@
64
58
  "prettier": "^3.0.0",
65
59
  "vitest": "^3.0.0"
66
60
  },
61
+ "peerDependencies": {
62
+ "@adaptivestone/framework-module-email": "^1.0.0"
63
+ },
64
+ "peerDependenciesMeta": {
65
+ "@adaptivestone/framework-module-email": {
66
+ "optional": true
67
+ }
68
+ },
67
69
  "lint-staged": {
68
70
  "**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
69
71
  "prettier --write"
package/server.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- import TFolderConfig from './types/TFoldersConfig';
2
- import { ExpandDeep } from './types/Expand';
1
+ import TFolderConfig from './types/TFoldersConfig.d.ts';
2
+ import { ExpandDeep } from './types/Expand.d.ts';
3
3
 
4
4
  import EventEmitter from 'events';
5
5
 
6
6
  import { Model as MongooseModel, Schema } from 'mongoose';
7
7
 
8
- import BaseCli from './modules/BaseCli';
9
- import Cache from './services/cache/Cache';
8
+ import BaseCli from './modules/BaseCli.js';
9
+ import Cache from './services/cache/Cache.d.ts';
10
10
  import winston from 'winston';
11
11
 
12
12
  import HttpServer from './services/http/HttpServer.js';
@@ -29,6 +29,7 @@ declare class Server {
29
29
  get logger(): winston.Logger;
30
30
  httpServer: HttpServer | null;
31
31
  controllerManager: ControllerManager | null;
32
+ frameworkFolder: string;
32
33
  };
33
34
  cacheService: Cache;
34
35
 
package/server.js CHANGED
@@ -36,9 +36,10 @@ class Server {
36
36
  * @param {String} config.folders.config path to folder with config files
37
37
  * @param {String} config.folders.models path to folder with moidels files
38
38
  * @param {String} config.folders.controllers path to folder with controllers files
39
- * @param {String} config.folders.views path to folder with view files
40
39
  * @param {String} config.folders.locales path to folder with locales files
41
- * @param {String} config.folders.emails path to folder with emails files
40
+ * @param {String} [config.folders.emails] path to folder with emails files
41
+ * @param {String} config.folders.commands path to folder with commands files
42
+ * @param {String} config.folders.migrations path to folder with migrations files
42
43
  */
43
44
  constructor(config) {
44
45
  this.config = config;
@@ -58,6 +59,7 @@ class Server {
58
59
  },
59
60
  httpServer: null,
60
61
  controllerManager: null,
62
+ frameworkFolder: new URL('.', import.meta.url).pathname,
61
63
  };
62
64
 
63
65
  this.cache = {
@@ -1,4 +1,4 @@
1
- import Base from '../../modules/Base';
1
+ import Base from '../../modules/Base.d.ts';
2
2
  import Server from '../../server.js';
3
3
 
4
4
  declare class Cache extends Base {
@@ -11,9 +11,9 @@ class Cache extends Base {
11
11
  // at least memory and redis drivers should be presented
12
12
  // memory drives should works on master process level
13
13
  // we should support multiple cashe same time
14
- const redis = await import('redis');
14
+ const { createClient } = await import('@redis/client');
15
15
  const conf = this.app.getConfig('redis');
16
- this.redisClient = redis.createClient({
16
+ this.redisClient = createClient({
17
17
  url: conf.url,
18
18
  });
19
19
 
@@ -18,12 +18,6 @@ class HttpServer extends Base {
18
18
  super(app);
19
19
  this.express = express();
20
20
  this.express.disable('x-powered-by');
21
- // const dirname = url.fileURLToPath(new URL('.', import.meta.url));
22
- // this.express.set('views', [
23
- // this.app.foldersConfig.views,
24
- // path.join(dirname, '../../views'),
25
- // ]);
26
- // this.express.set('view engine', 'pug');
27
21
 
28
22
  this.express.use(new RequestLoggerMiddleware(this.app).getMiddleware());
29
23
  this.express.use(new PrepareAppInfoMiddleware(this.app).getMiddleware());
@@ -4,7 +4,7 @@ import {
4
4
  RateLimiterMongo,
5
5
  } from 'rate-limiter-flexible';
6
6
  import merge from 'deepmerge';
7
- import redis from 'redis';
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 = redis.createClient({
49
+ const redisClient = createClient({
50
50
  url: redisConfig.url,
51
51
  });
52
52
 
File without changes
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 redis from 'redis';
6
+ import { createClient } from '@redis/client';
7
7
  import Server from '../server.js';
8
8
 
9
9
  import clearRedisNamespace from '../helpers/redis/clearNamespace.js';
@@ -31,7 +31,6 @@ beforeAll(async () => {
31
31
  config: process.env.TEST_FOLDER_CONFIG || path.resolve('./config'),
32
32
  controllers:
33
33
  process.env.TEST_FOLDER_CONTROLLERS || path.resolve('./controllers'),
34
- views: process.env.TEST_FOLDER_VIEWS || path.resolve('./views'),
35
34
  models: process.env.TEST_FOLDER_MODELS || path.resolve('./models'),
36
35
  emails:
37
36
  process.env.TEST_FOLDER_EMAIL ||
@@ -93,7 +92,7 @@ beforeEach(() => {
93
92
  afterEach(async () => {
94
93
  if (global.server) {
95
94
  const { url, namespace } = global.server.getConfig('redis');
96
- const redisClient = redis.createClient({ url });
95
+ const redisClient = createClient({ url });
97
96
 
98
97
  try {
99
98
  await redisClient.connect();
@@ -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 redis from 'redis';
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 ||
@@ -82,7 +81,7 @@ beforeEach(() => {
82
81
  afterEach(async () => {
83
82
  if (global.server) {
84
83
  const { url, namespace } = global.server.getConfig('redis');
85
- const redisClient = redis.createClient({ url });
84
+ const redisClient = createClient({ url });
86
85
 
87
86
  try {
88
87
  await redisClient.connect();
@@ -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 views path to folder with view files
5
+ * @param migrations path to folder with migrations files
6
6
  * @param locales path to folder with locales files
7
- * @param emails path to folder with emails files
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
- views: string;
14
- emails: string;
14
+ commands: string;
15
+ locales: string;
16
+ migrations: string;
17
+ emails?: string;
15
18
  };
16
19
 
17
20
  export default FolderConfig;
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
- // @ts-ignore
127
- juice.tableElements = ['TABLE'];
128
-
129
- const juiceResourcesAsync = promisify(juice.juiceResources);
130
-
131
- const inlinedHTML = await juiceResourcesAsync(htmlRendered, {
132
- preserveImportant: true,
133
- webResources: mailConfig.webResources,
134
- extraCss,
135
- });
136
- return {
137
- htmlRaw: htmlRendered,
138
- subject: subjectRendered,
139
- text: textRendered,
140
- inlinedHTML,
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 Mail.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,9 +0,0 @@
1
- doctype html
2
- html(lang='en')
3
- head
4
- meta(charset='UTF-8')
5
- title New message
6
- body
7
- h1 Good day
8
- p
9
- | Sorry, message template not found
@@ -1 +0,0 @@
1
- = `New message`
@@ -1,3 +0,0 @@
1
- import email from './email/index.js';
2
-
3
- export { email };