@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.
Files changed (50) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/Cli.js +3 -4
  3. package/cluster.js +0 -2
  4. package/commands/CreateUser.js +31 -0
  5. package/commands/DropIndex.js +14 -5
  6. package/commands/GenerateRandomBytes.js +2 -0
  7. package/commands/GetOpenApiJson.js +13 -2
  8. package/commands/SyncIndexes.js +1 -0
  9. package/commands/migration/Create.js +21 -10
  10. package/commands/migration/Migrate.js +1 -0
  11. package/controllers/Auth.js +20 -29
  12. package/eslint.config.js +68 -0
  13. package/folderConfig.js +0 -1
  14. package/helpers/files.js +1 -4
  15. package/helpers/logger.js +0 -1
  16. package/helpers/yup.js +2 -4
  17. package/jsconfig.json +4 -4
  18. package/models/Lock.js +107 -0
  19. package/models/User.js +23 -6
  20. package/modules/AbstractCommand.js +25 -1
  21. package/modules/AbstractController.js +0 -7
  22. package/modules/AbstractModel.js +21 -9
  23. package/modules/Base.js +3 -2
  24. package/modules/BaseCli.js +82 -9
  25. package/package.json +20 -16
  26. package/server.d.ts +9 -7
  27. package/server.js +44 -10
  28. package/services/cache/Cache.d.ts +1 -1
  29. package/services/cache/Cache.js +3 -3
  30. package/services/http/HttpServer.js +6 -13
  31. package/services/http/middleware/AbstractMiddleware.js +3 -3
  32. package/services/http/middleware/I18n.js +1 -1
  33. package/services/http/middleware/Pagination.js +4 -4
  34. package/services/http/middleware/RateLimiter.js +2 -2
  35. package/services/messaging/email/templates/.gitkeep +0 -0
  36. package/services/validate/ValidateService.js +4 -4
  37. package/services/validate/drivers/AbstractValidator.js +2 -2
  38. package/services/validate/drivers/CustomValidator.js +2 -2
  39. package/services/validate/drivers/YupValidator.js +3 -3
  40. package/tests/setup.js +8 -6
  41. package/tests/setupVitest.js +9 -7
  42. package/types/ICommandArguments.d.ts +41 -0
  43. package/types/TFoldersConfig.d.ts +7 -4
  44. package/vitest.config.js +4 -3
  45. package/config/mail.js +0 -29
  46. package/services/messaging/email/index.js +0 -217
  47. package/services/messaging/email/templates/emptyTemplate/html.pug +0 -9
  48. package/services/messaging/email/templates/emptyTemplate/subject.pug +0 -1
  49. package/services/messaging/email/templates/emptyTemplate/text.pug +0 -1
  50. 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 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
@@ -1,4 +1,4 @@
1
- import yup from 'yup';
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
- ? this.constructor.getDriverByValidatorBody(app, validator)
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 (yup.isSchema(body)) {
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 = this.constructor.getDriverByValidatorBody(
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 yup from 'yup';
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 yup.ValidationError({
22
+ throw new ValidationError({
23
23
  [e.path]: e.message,
24
24
  });
25
25
  }
@@ -1,9 +1,9 @@
1
- import yup from 'yup';
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 this.constructor.convertFieldsToJson(this.body);
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 yup.ValidationError({
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 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';
@@ -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 = redis.createClient({ url });
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 (err) {
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();
@@ -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 ||
@@ -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 = redis.createClient({ url });
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 (err) {
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
- await mongoose.connection.db.dropDatabase(); // clean database after test
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 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/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', 'cobertura'],
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,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 };