@adaptivestone/framework 5.0.0-alpha.9 → 5.0.0-beta.2
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 +90 -0
- package/Cli.js +1 -1
- package/commands/CreateUser.js +4 -0
- package/commands/Documentation.js +4 -0
- package/commands/GenerateRandomBytes.js +21 -0
- package/commands/GetOpenApiJson.js +4 -0
- package/config/auth.js +4 -1
- package/config/ipDetector.js +14 -0
- package/controllers/Auth.js +2 -2
- package/controllers/Home.js +1 -1
- package/helpers/files.js +8 -8
- package/models/User.js +7 -1
- package/modules/AbstractCommand.js +16 -1
- package/modules/AbstractController.js +19 -19
- package/modules/AbstractModel.d.ts +48 -0
- package/modules/AbstractModel.js +31 -10
- package/modules/Base.d.ts +4 -4
- package/modules/Base.js +11 -1
- package/modules/BaseCli.js +36 -8
- package/package.json +10 -11
- package/server.js +29 -5
- package/services/cache/Cache.js +6 -4
- package/services/http/HttpServer.js +11 -9
- package/services/http/middleware/GetUserByToken.js +3 -2
- package/services/http/middleware/I18n.js +21 -22
- package/services/http/middleware/IpDetector.js +59 -0
- package/services/http/middleware/Pagination.js +3 -2
- package/services/http/middleware/RateLimiter.js +8 -2
- package/services/http/middleware/RequestLogger.js +3 -3
- package/services/http/middleware/RequestParser.js +1 -1
- package/services/validate/ValidateService.js +1 -1
- package/.eslintrc.cjs +0 -41
- package/commands/Generate.js +0 -14
- package/controllers/Auth.test.js +0 -451
- package/controllers/Home.test.js +0 -12
- package/models/Migration.test.js +0 -20
- package/models/Sequence.test.js +0 -43
- package/models/User.test.js +0 -143
- package/modules/Modules.test.js +0 -18
- package/services/cache/Cache.test.js +0 -81
- package/services/http/middleware/Auth.test.js +0 -57
- package/services/http/middleware/Cors.test.js +0 -147
- package/services/http/middleware/GetUserByToken.test.js +0 -108
- package/services/http/middleware/I18n.test.js +0 -96
- package/services/http/middleware/PrepareAppInfo.test.js +0 -26
- package/services/http/middleware/RateLimiter.test.js +0 -233
- package/services/http/middleware/RequestParser.test.js +0 -121
- package/services/http/middleware/Role.test.js +0 -93
- package/services/validate/ValidateService.test.js +0 -107
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,93 @@
|
|
|
1
|
+
### 5.0.0-beta.2
|
|
2
|
+
|
|
3
|
+
[UPDATE] update deps
|
|
4
|
+
[NEW] add ability to skip mongo model init in CLI env
|
|
5
|
+
[NEW] now each mongo connection on CLI have own name and inslude command name there too (getMongoConnectionName in command)
|
|
6
|
+
|
|
7
|
+
### 5.0.0-beta.1
|
|
8
|
+
|
|
9
|
+
[UPDATE] update deps
|
|
10
|
+
[BREAKING] vitest v3 https://vitest.dev/guide/migration.html
|
|
11
|
+
|
|
12
|
+
### 5.0.0-alpha.26
|
|
13
|
+
|
|
14
|
+
[UPDATE] update deps
|
|
15
|
+
[UPDATE] new commands view in CLI
|
|
16
|
+
|
|
17
|
+
### 5.0.0-alpha.24
|
|
18
|
+
|
|
19
|
+
[UPDATE] update deps
|
|
20
|
+
[BREAKING] i18next v24 https://www.i18next.com/misc/migration-guide#v23.x.x-to-v24.0.0
|
|
21
|
+
|
|
22
|
+
### 5.0.0-alpha.23
|
|
23
|
+
|
|
24
|
+
[UPDATE] update deps
|
|
25
|
+
|
|
26
|
+
### 5.0.0-alpha.22
|
|
27
|
+
|
|
28
|
+
[UPDATE] update deps
|
|
29
|
+
[FIX] fix optional routing parameters
|
|
30
|
+
|
|
31
|
+
### 5.0.0-alpha.21
|
|
32
|
+
|
|
33
|
+
[BREAKING] possible breaking. Framework start using express 5 instead of express 4. Please follow express migration guide too https://expressjs.com/en/guide/migrating-5.html.
|
|
34
|
+
[BREAKING] AS part of express 5 migration _ in rotes (middlewares) should have perameter. please replace _ to \*splat
|
|
35
|
+
[UPDATE] update deps
|
|
36
|
+
[UPDATE] Mailer uses await import() for startup speedup
|
|
37
|
+
|
|
38
|
+
### 5.0.0-alpha.20
|
|
39
|
+
|
|
40
|
+
[UPDATE] update deps
|
|
41
|
+
[UPDATE] #realLogger do not throw error in a scecific cases (model toJSON({virtual:true}))
|
|
42
|
+
|
|
43
|
+
### 5.0.0-alpha.19
|
|
44
|
+
|
|
45
|
+
[NEW] added modelSchemaOptions for models
|
|
46
|
+
|
|
47
|
+
### 5.0.0-alpha.18
|
|
48
|
+
|
|
49
|
+
[BREAKING] default auth responce changed to be unified. {token, user} = > {data:{token, user}}
|
|
50
|
+
[UPDATE] RateLimiter updae key generation
|
|
51
|
+
|
|
52
|
+
### 5.0.0-alpha.17
|
|
53
|
+
|
|
54
|
+
[NEW] generateRandomBytes command
|
|
55
|
+
[UPDATE] update deps
|
|
56
|
+
|
|
57
|
+
### 5.0.0-alpha.16
|
|
58
|
+
|
|
59
|
+
[UPDATE] no warning of direct usage body and query
|
|
60
|
+
[UPDATE] update deps
|
|
61
|
+
|
|
62
|
+
### 5.0.0-alpha.15
|
|
63
|
+
|
|
64
|
+
[BUG] fix bug with pagination
|
|
65
|
+
[UPDATE] update deps
|
|
66
|
+
|
|
67
|
+
### 5.0.0-alpha.14
|
|
68
|
+
|
|
69
|
+
[NEW] add types for Abstract model (wip)
|
|
70
|
+
|
|
71
|
+
### 5.0.0-alpha.13
|
|
72
|
+
|
|
73
|
+
[UPDATE] update deps
|
|
74
|
+
[UPDATE] update i18n internal implementation
|
|
75
|
+
[CHANGE] disable https server view
|
|
76
|
+
|
|
77
|
+
### 5.0.0-alpha.12
|
|
78
|
+
|
|
79
|
+
[UPDATE] update deps
|
|
80
|
+
|
|
81
|
+
### 5.0.0-alpha.11
|
|
82
|
+
|
|
83
|
+
[UPDATE] update deps
|
|
84
|
+
|
|
85
|
+
### 5.0.0-alpha.10
|
|
86
|
+
|
|
87
|
+
[UPDATE] update deps
|
|
88
|
+
[NEW] IpDetector middleware that support detecting proxy and X-Forwarded-For header
|
|
89
|
+
[BREAKING] RateLimiter now need to have IpDetector middleware before
|
|
90
|
+
|
|
1
91
|
### 5.0.0-alpha.9
|
|
2
92
|
|
|
3
93
|
[UPDATE] update deps
|
package/Cli.js
CHANGED
|
@@ -12,7 +12,7 @@ class Cli extends BaseCli {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
async run() {
|
|
15
|
-
await this.server.init();
|
|
15
|
+
await this.server.init({ isSkipModelInit: true, isSkipModelLoading: true });
|
|
16
16
|
const command = process.argv[2]?.toLowerCase();
|
|
17
17
|
await super.run(command, this.args);
|
|
18
18
|
this.app.events.emit('shutdown');
|
package/commands/CreateUser.js
CHANGED
|
@@ -2,6 +2,10 @@ import AbstractCommand from '../modules/AbstractCommand.js';
|
|
|
2
2
|
|
|
3
3
|
// Example: node src/cli createuser --email=somemail@gmail.com --password=somePassword --roles=user,admin,someOtherRoles
|
|
4
4
|
class CreateUser extends AbstractCommand {
|
|
5
|
+
static get description() {
|
|
6
|
+
return 'Create user in a database';
|
|
7
|
+
}
|
|
8
|
+
|
|
5
9
|
async run() {
|
|
6
10
|
const User = this.app.getModel('User');
|
|
7
11
|
const { id, email, password, roles, update } = this.args;
|
|
@@ -2,6 +2,10 @@ import AbstractCommand from '../modules/AbstractCommand.js';
|
|
|
2
2
|
import ControllerManager from '../controllers/index.js';
|
|
3
3
|
|
|
4
4
|
class Documentation extends AbstractCommand {
|
|
5
|
+
static get description() {
|
|
6
|
+
return 'Generate documentation (internal)';
|
|
7
|
+
}
|
|
8
|
+
|
|
5
9
|
async run() {
|
|
6
10
|
const CM = new ControllerManager(this.app);
|
|
7
11
|
this.app.documentation = [];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import AbstractCommand from '../modules/AbstractCommand.js';
|
|
3
|
+
|
|
4
|
+
class GenerateRandomBytes extends AbstractCommand {
|
|
5
|
+
static get description() {
|
|
6
|
+
return 'Generate random bytes ising randomBytes from node:crypto';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
static isShouldInitModels = false;
|
|
10
|
+
|
|
11
|
+
async run() {
|
|
12
|
+
const sizes = [16, 32, 64, 128, 256];
|
|
13
|
+
for (const size of sizes) {
|
|
14
|
+
const bytes = randomBytes(size).toString('hex');
|
|
15
|
+
this.logger.info(`${size} bytes: ${bytes}`);
|
|
16
|
+
}
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default GenerateRandomBytes;
|
|
@@ -5,6 +5,10 @@ import AbstractCommand from '../modules/AbstractCommand.js';
|
|
|
5
5
|
* Command for generate documentation json file openApi
|
|
6
6
|
*/
|
|
7
7
|
class GetOpenApiJson extends AbstractCommand {
|
|
8
|
+
static get description() {
|
|
9
|
+
return 'Generate documentation (openApi) ';
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
async run() {
|
|
9
13
|
const { myDomain } = this.app.getConfig('http');
|
|
10
14
|
let jsonFile = process.env.npm_package_json;
|
package/config/auth.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
hashRounds: 64,
|
|
3
3
|
saltSecret:
|
|
4
|
-
process.env.AUTH_SALT ||
|
|
4
|
+
process.env.AUTH_SALT ||
|
|
5
|
+
console.error(
|
|
6
|
+
'AUTH_SALT is not defined. You can "npm run cli generateRandomBytes" and use it',
|
|
7
|
+
),
|
|
5
8
|
isAuthWithVefificationFlow: true,
|
|
6
9
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
headers: ['X-Forwarded-For'],
|
|
3
|
+
trustedProxy: [
|
|
4
|
+
// list of trusted proxies.
|
|
5
|
+
'169.254.0.0/16', // linklocal
|
|
6
|
+
'fe80::/10', // linklocal
|
|
7
|
+
'127.0.0.1/8', // loopback
|
|
8
|
+
'::1/128', // loopback
|
|
9
|
+
'10.0.0.0/8', // uniquelocal
|
|
10
|
+
'172.16.0.0/12', // uniquelocal
|
|
11
|
+
'192.168.0.0/16', // uniquelocal
|
|
12
|
+
'fc00::/7', // uniquelocal
|
|
13
|
+
],
|
|
14
|
+
};
|
package/controllers/Auth.js
CHANGED
|
@@ -83,7 +83,7 @@ class Auth extends AbstractController {
|
|
|
83
83
|
}
|
|
84
84
|
const token = await user.generateToken();
|
|
85
85
|
|
|
86
|
-
return res.status(200).json({ token, user: user.getPublic() });
|
|
86
|
+
return res.status(200).json({ data: { token, user: user.getPublic() } });
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
async postRegister(req, res) {
|
|
@@ -203,7 +203,7 @@ class Auth extends AbstractController {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
static get middleware() {
|
|
206
|
-
return new Map([['
|
|
206
|
+
return new Map([['/{*splat}', [GetUserByToken, RateLimiter]]]);
|
|
207
207
|
}
|
|
208
208
|
}
|
|
209
209
|
|
package/controllers/Home.js
CHANGED
package/helpers/files.js
CHANGED
|
@@ -8,7 +8,7 @@ const getFilesPathWithInheritance = async ({
|
|
|
8
8
|
loggerFileType = '',
|
|
9
9
|
filter: { startWithCapital = true, notTests = true, notHidden = true } = {},
|
|
10
10
|
}) => {
|
|
11
|
-
|
|
11
|
+
const [internalFiles, externalFiles] = await Promise.all([
|
|
12
12
|
fs.readdir(internalFolder, { recursive: true, withFileTypes: true }),
|
|
13
13
|
fs.readdir(externalFolder, { recursive: true, withFileTypes: true }),
|
|
14
14
|
]);
|
|
@@ -33,24 +33,24 @@ const getFilesPathWithInheritance = async ({
|
|
|
33
33
|
return true;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
const internalFilesString = internalFiles
|
|
37
37
|
.filter(filterIndexFile)
|
|
38
38
|
.map((fileDirent) =>
|
|
39
|
-
join(fileDirent.
|
|
39
|
+
join(fileDirent.parentPath, fileDirent.name)
|
|
40
40
|
.replace(`${internalFolder}/`, '')
|
|
41
41
|
.replace(`${internalFolder}`, ''),
|
|
42
42
|
);
|
|
43
|
-
|
|
43
|
+
const externalFilesString = externalFiles
|
|
44
44
|
.filter(filterIndexFile)
|
|
45
45
|
.map((fileDirent) =>
|
|
46
|
-
join(fileDirent.
|
|
46
|
+
join(fileDirent.parentPath, fileDirent.name)
|
|
47
47
|
.replace(`${externalFolder}/`, '')
|
|
48
48
|
.replace(`${externalFolder}`, ''),
|
|
49
49
|
);
|
|
50
50
|
|
|
51
51
|
const filesToLoad = [];
|
|
52
|
-
for (const file of
|
|
53
|
-
if (
|
|
52
|
+
for (const file of internalFilesString) {
|
|
53
|
+
if (externalFilesString.includes(file)) {
|
|
54
54
|
logger(
|
|
55
55
|
`Skipping register INTERNAL file '${file}' ${
|
|
56
56
|
loggerFileType ? `of type ${loggerFileType}` : ''
|
|
@@ -64,7 +64,7 @@ const getFilesPathWithInheritance = async ({
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
for (const file of
|
|
67
|
+
for (const file of externalFilesString) {
|
|
68
68
|
filesToLoad.push({
|
|
69
69
|
path: join(externalFolder, file),
|
|
70
70
|
file,
|
package/models/User.js
CHANGED
|
@@ -4,7 +4,6 @@ import { scrypt } from 'node:crypto';
|
|
|
4
4
|
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import AbstractModel from '../modules/AbstractModel.js';
|
|
7
|
-
import Mailer from '../services/messaging/email/index.js';
|
|
8
7
|
|
|
9
8
|
class User extends AbstractModel {
|
|
10
9
|
constructor(app) {
|
|
@@ -175,6 +174,10 @@ class User extends AbstractModel {
|
|
|
175
174
|
async sendPasswordRecoveryEmail(i18n) {
|
|
176
175
|
const passwordRecoveryToken =
|
|
177
176
|
await User.generateUserPasswordRecoveryToken(this);
|
|
177
|
+
// speed optimisation
|
|
178
|
+
const Mailer = (await import('../services/messaging/email/index.js'))
|
|
179
|
+
.default;
|
|
180
|
+
|
|
178
181
|
const mail = new Mailer(
|
|
179
182
|
this.getSuper().app,
|
|
180
183
|
'recovery',
|
|
@@ -241,6 +244,9 @@ class User extends AbstractModel {
|
|
|
241
244
|
|
|
242
245
|
async sendVerificationEmail(i18n) {
|
|
243
246
|
const verificationToken = await User.generateUserVerificationToken(this);
|
|
247
|
+
// speed optimisation
|
|
248
|
+
const Mailer = (await import('../services/messaging/email/index.js'))
|
|
249
|
+
.default;
|
|
244
250
|
const mail = new Mailer(
|
|
245
251
|
this.getSuper().app,
|
|
246
252
|
'verification',
|
|
@@ -8,7 +8,22 @@ class AbstractCommand extends Base {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
static get description() {
|
|
11
|
-
return 'Command description';
|
|
11
|
+
return 'Command description. PLEASE PROVIDE IT';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* If true, then this command will load models and init mongo connection
|
|
16
|
+
*/
|
|
17
|
+
static isShouldInitModels = true;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get mongo connection name
|
|
21
|
+
* @param {String} commandName
|
|
22
|
+
* @param {array} args
|
|
23
|
+
* @returns string
|
|
24
|
+
*/
|
|
25
|
+
static getMongoConnectionName(commandName, args) {
|
|
26
|
+
return `CLI: ${commandName} ${args.join(' ')}`;
|
|
12
27
|
}
|
|
13
28
|
|
|
14
29
|
/**
|
|
@@ -196,22 +196,22 @@ class AbstractController extends Base {
|
|
|
196
196
|
errors: err.message,
|
|
197
197
|
});
|
|
198
198
|
}
|
|
199
|
-
req.body = new Proxy(req.body, {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
});
|
|
207
|
-
req.query = new Proxy(req.query, {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
});
|
|
199
|
+
// req.body = new Proxy(req.body, {
|
|
200
|
+
// get: (target, prop) => {
|
|
201
|
+
// this.logger.warn(
|
|
202
|
+
// 'Please not use "req.body" directly. Implement "request" and use "req.appInfo.request" ',
|
|
203
|
+
// );
|
|
204
|
+
// return target[prop];
|
|
205
|
+
// },
|
|
206
|
+
// });
|
|
207
|
+
// req.query = new Proxy(req.query, {
|
|
208
|
+
// get: (target, prop) => {
|
|
209
|
+
// this.logger.warn(
|
|
210
|
+
// 'Please not use "req.query" directly. Implement "query" and use "req.appInfo.query" ',
|
|
211
|
+
// );
|
|
212
|
+
// return target[prop];
|
|
213
|
+
// },
|
|
214
|
+
// });
|
|
215
215
|
|
|
216
216
|
if (!routeObject.handler) {
|
|
217
217
|
this.logger.error(`Route object have no handler defined`);
|
|
@@ -373,17 +373,17 @@ class AbstractController extends Base {
|
|
|
373
373
|
* You should provide path relative to controller and then array of middlewares to apply.
|
|
374
374
|
* Order is matter.
|
|
375
375
|
* Be default path apply to ANY' method, but you can preattach 'METHOD' into patch to scope patch to this METHOD
|
|
376
|
-
* @returns {Map<string, Array<AbstractMiddleware | [Function, ...any]>>}
|
|
376
|
+
* @returns {Map<string, Array<typeof import('../services/http/middleware/AbstractMiddleware.js').default | [Function, ...any]>>}
|
|
377
377
|
* @example
|
|
378
378
|
* return new Map([
|
|
379
|
-
* ['
|
|
379
|
+
* ['/{*splat}', [GetUserByToken]] // for any method for this controller
|
|
380
380
|
* ['POST/', [Auth]] // for POST method
|
|
381
381
|
* ['/superSecretMethod', [OnlySuperSecretUsers]] // route with ANY method
|
|
382
382
|
* ['PUT/superSecretMathod', [OnlySuperSecretAdmin]] // route with PUT method
|
|
383
383
|
* ]);
|
|
384
384
|
*/
|
|
385
385
|
static get middleware() {
|
|
386
|
-
return new Map([['
|
|
386
|
+
return new Map([['/{*splat}', [GetUserByToken, Auth]]]);
|
|
387
387
|
}
|
|
388
388
|
|
|
389
389
|
/**
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type Base from './Base.js';
|
|
2
|
+
import { Model, Schema } from 'mongoose';
|
|
3
|
+
import type Server from '../server.js';
|
|
4
|
+
|
|
5
|
+
interface AbstractModel<T extends Document> extends Model, Base {
|
|
6
|
+
constructor(app: Server['app'], callback?: () => void);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Return itself for internal methods.
|
|
10
|
+
*/
|
|
11
|
+
getSuper(): this;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Model schema in Js object (not a mongoose schema).
|
|
15
|
+
*/
|
|
16
|
+
get modelSchema(): Object;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Mongoose schema.
|
|
20
|
+
*/
|
|
21
|
+
mongooseSchema: Schema<T>;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Acces to mongoose model too
|
|
25
|
+
*/
|
|
26
|
+
mongooseModel: Model<T>;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Init custom hooks before model
|
|
30
|
+
*/
|
|
31
|
+
initHooks(): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
abstract class AbstractModel<T extends Document>
|
|
35
|
+
extends Model
|
|
36
|
+
implements AbstractModel
|
|
37
|
+
{
|
|
38
|
+
abstract get modelSchema(): Object;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Return itself for internal methods.
|
|
42
|
+
*/
|
|
43
|
+
static abstract getSuper(): this;
|
|
44
|
+
|
|
45
|
+
mongooseSchema: Schema<T> = new Schema<T>(this.modelSchema);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default AbstractModel;
|
package/modules/AbstractModel.js
CHANGED
|
@@ -2,13 +2,20 @@ import mongoose from 'mongoose';
|
|
|
2
2
|
import Base from './Base.js';
|
|
3
3
|
|
|
4
4
|
class AbstractModel extends Base {
|
|
5
|
+
mongooseSchema = null;
|
|
6
|
+
|
|
7
|
+
mongooseModel = null;
|
|
8
|
+
|
|
5
9
|
/**
|
|
6
10
|
* @param {import('../server.js').default['app']} app //TODO change to *.d.ts as this is a Server, not app
|
|
7
11
|
* @param function callback optional callback when connection ready
|
|
8
12
|
*/
|
|
9
13
|
constructor(app, callback = () => {}) {
|
|
10
14
|
super(app);
|
|
11
|
-
this.mongooseSchema = new mongoose.Schema(
|
|
15
|
+
this.mongooseSchema = new mongoose.Schema(
|
|
16
|
+
this.modelSchema,
|
|
17
|
+
this.modelSchemaOptions,
|
|
18
|
+
);
|
|
12
19
|
mongoose.set('strictQuery', true);
|
|
13
20
|
this.mongooseSchema.set('timestamps', true);
|
|
14
21
|
this.mongooseSchema.set('minimize', false);
|
|
@@ -30,17 +37,23 @@ class AbstractModel extends Base {
|
|
|
30
37
|
}
|
|
31
38
|
// await mongoose.disconnect(); // TODO it have problems with replica-set
|
|
32
39
|
});
|
|
40
|
+
const connectionParams = {};
|
|
41
|
+
if (process.env.MONGO_APP_NAME) {
|
|
42
|
+
connectionParams.appName = process.env.MONGO_APP_NAME;
|
|
43
|
+
}
|
|
33
44
|
// do not connect on test
|
|
34
|
-
mongoose
|
|
35
|
-
()
|
|
36
|
-
|
|
45
|
+
mongoose
|
|
46
|
+
.connect(this.app.getConfig('mongo').connectionString, connectionParams)
|
|
47
|
+
.then(
|
|
48
|
+
() => {
|
|
49
|
+
this.logger.info('Mongo connection success');
|
|
37
50
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
callback();
|
|
52
|
+
},
|
|
53
|
+
(error) => {
|
|
54
|
+
this.logger.error("Can't install mongodb connection", error);
|
|
55
|
+
},
|
|
56
|
+
);
|
|
44
57
|
} else {
|
|
45
58
|
callback();
|
|
46
59
|
}
|
|
@@ -54,6 +67,14 @@ class AbstractModel extends Base {
|
|
|
54
67
|
return {};
|
|
55
68
|
}
|
|
56
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Mongoose schema options
|
|
72
|
+
*/
|
|
73
|
+
// eslint-disable-next-line class-methods-use-this
|
|
74
|
+
get modelSchemaOptions() {
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
|
|
57
78
|
static get loggerGroup() {
|
|
58
79
|
return 'model';
|
|
59
80
|
}
|
package/modules/Base.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import winston from 'winston';
|
|
2
|
-
import Server from '../server.js';
|
|
3
|
-
import type { Dirent } from 'fs';
|
|
1
|
+
import type winston from 'winston';
|
|
2
|
+
import type Server from '../server.js';
|
|
3
|
+
import type { Dirent } from 'node.fs';
|
|
4
4
|
|
|
5
5
|
declare class Base {
|
|
6
6
|
app: Server['app'];
|
|
@@ -27,7 +27,7 @@ declare class Base {
|
|
|
27
27
|
getFilesPathWithInheritance(
|
|
28
28
|
internalFolder: string,
|
|
29
29
|
externalFolder: string,
|
|
30
|
-
): Promise<
|
|
30
|
+
): Promise<{ path: string; file: string }[]>;
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Return logger group. Just to have all logs groupped logically
|
package/modules/Base.js
CHANGED
|
@@ -18,7 +18,17 @@ class Base {
|
|
|
18
18
|
* Optimzation to lazy load logger. It will be inited only on request
|
|
19
19
|
*/
|
|
20
20
|
get logger() {
|
|
21
|
-
|
|
21
|
+
let l;
|
|
22
|
+
try {
|
|
23
|
+
l = this.#realLogger;
|
|
24
|
+
} catch (e) {
|
|
25
|
+
console.warn(
|
|
26
|
+
`You try to accees logger not from class. that can be ok in case of models.`,
|
|
27
|
+
);
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!l) {
|
|
22
32
|
this.#realLogger = this.getLogger(
|
|
23
33
|
this.constructor.loggerGroup + this.getConstructorName(),
|
|
24
34
|
);
|
package/modules/BaseCli.js
CHANGED
|
@@ -29,28 +29,56 @@ class Cli extends Base {
|
|
|
29
29
|
return true;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
async printCommandTable() {
|
|
33
|
+
const commands = Object.keys(this.commands).sort();
|
|
34
|
+
const maxLength = commands.reduce((max, c) => Math.max(max, c.length), 0);
|
|
35
|
+
console.log('Available commands:');
|
|
36
|
+
let commandsClasses = [];
|
|
37
|
+
for (const c of commands) {
|
|
38
|
+
// eslint-disable-next-line no-await-in-loop
|
|
39
|
+
commandsClasses.push(import(this.commands[c]));
|
|
40
|
+
// console.log(
|
|
41
|
+
// ` \x1b[36m${c.padEnd(maxLength)}\x1b[0m - ${f.default.description}`,
|
|
42
|
+
// );
|
|
43
|
+
}
|
|
44
|
+
commandsClasses = await Promise.all(commandsClasses);
|
|
45
|
+
for (const [key, c] of Object.entries(commands)) {
|
|
46
|
+
// eslint-disable-next-line no-await-in-loop
|
|
47
|
+
console.log(
|
|
48
|
+
` \x1b[36m${c.padEnd(maxLength)}\x1b[0m - ${commandsClasses[key].default.description}`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
32
53
|
async run(command, args) {
|
|
33
54
|
await this.loadCommands();
|
|
34
55
|
|
|
35
56
|
if (!command) {
|
|
36
57
|
console.log('Please provide command name');
|
|
37
|
-
|
|
38
|
-
'Availalble commands:',
|
|
39
|
-
Object.keys(this.commands).join(', '),
|
|
40
|
-
);
|
|
58
|
+
await this.printCommandTable();
|
|
41
59
|
return false;
|
|
42
60
|
}
|
|
43
61
|
|
|
44
62
|
if (!this.commands[command]) {
|
|
45
63
|
console.log(`Command ${command} not found `);
|
|
46
|
-
|
|
47
|
-
'Availalble commands:',
|
|
48
|
-
Object.keys(this.commands).join(', '),
|
|
49
|
-
);
|
|
64
|
+
await this.printCommandTable();
|
|
50
65
|
return false;
|
|
51
66
|
}
|
|
52
67
|
const { default: Command } = await import(this.commands[command]);
|
|
53
68
|
|
|
69
|
+
if (Command.isShouldInitModels) {
|
|
70
|
+
this.logger.debug(
|
|
71
|
+
`Command ${command} isShouldInitModels called. If you want to skip loading and init models, please set isShouldInitModels to false in tyou command`,
|
|
72
|
+
);
|
|
73
|
+
process.env.MONGO_APP_NAME = Command.getMongoConnectionName(
|
|
74
|
+
command,
|
|
75
|
+
args,
|
|
76
|
+
);
|
|
77
|
+
await this.server.initAllModels();
|
|
78
|
+
} else {
|
|
79
|
+
this.logger.debug(`Command ${command} NOT need to isShouldInitModels`);
|
|
80
|
+
}
|
|
81
|
+
|
|
54
82
|
const c = new Command(this.app, this.commands, args);
|
|
55
83
|
let result = false;
|
|
56
84
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adaptivestone/framework",
|
|
3
|
-
"version": "5.0.0-
|
|
3
|
+
"version": "5.0.0-beta.2",
|
|
4
4
|
"description": "Adaptive stone node js framework",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"dev": "node --watch ./index.js",
|
|
17
17
|
"prod": "node --watch ./cluster.js",
|
|
18
18
|
"test": "vitest run",
|
|
19
|
+
"t": "vitest --coverage=false --reporter=default",
|
|
19
20
|
"prettier": "prettier --check '**/*.(js|jsx|ts|tsx|json|css|scss|md)'",
|
|
20
21
|
"lint": "eslint '**/*.js'",
|
|
21
22
|
"lint:fix": "eslint '**/*.js' --fix",
|
|
@@ -30,14 +31,12 @@
|
|
|
30
31
|
"license": "MIT",
|
|
31
32
|
"dependencies": {
|
|
32
33
|
"deepmerge": "^4.2.2",
|
|
33
|
-
"express": "^
|
|
34
|
+
"express": "^5.0.1",
|
|
34
35
|
"formidable": "^3.5.1",
|
|
35
36
|
"html-to-text": "^9.0.3",
|
|
36
|
-
"i18next": "^
|
|
37
|
-
"i18next-chained-backend": "^4.0.0",
|
|
37
|
+
"i18next": "^24.0.0",
|
|
38
38
|
"i18next-fs-backend": "^2.0.0",
|
|
39
|
-
"juice": "^
|
|
40
|
-
"mime": "^4.0.0",
|
|
39
|
+
"juice": "^11.0.0",
|
|
41
40
|
"minimist": "^1.2.5",
|
|
42
41
|
"mongoose": "^8.0.0",
|
|
43
42
|
"nodemailer": "^6.6.3",
|
|
@@ -47,21 +46,21 @@
|
|
|
47
46
|
"rate-limiter-flexible": "^5.0.0",
|
|
48
47
|
"redis": "^4.3.1",
|
|
49
48
|
"winston": "^3.3.3",
|
|
50
|
-
"winston-transport-sentry-node": "^
|
|
49
|
+
"winston-transport-sentry-node": "^3.0.0",
|
|
51
50
|
"yup": "^1.0.0"
|
|
52
51
|
},
|
|
53
52
|
"devDependencies": {
|
|
54
|
-
"@vitest/coverage-v8": "^
|
|
53
|
+
"@vitest/coverage-v8": "^3.0.0",
|
|
55
54
|
"eslint": "^8.0.0",
|
|
56
55
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
57
|
-
"eslint-config-prettier": "^
|
|
56
|
+
"eslint-config-prettier": "^10.0.0",
|
|
58
57
|
"eslint-plugin-prettier": "^5.0.0",
|
|
59
58
|
"eslint-plugin-vitest": "^0.4.0",
|
|
60
59
|
"husky": "^9.0.0",
|
|
61
60
|
"lint-staged": "^15.0.0",
|
|
62
|
-
"mongodb-memory-server": "^
|
|
61
|
+
"mongodb-memory-server": "^10.0.0",
|
|
63
62
|
"prettier": "^3.0.0",
|
|
64
|
-
"vitest": "^
|
|
63
|
+
"vitest": "^3.0.0"
|
|
65
64
|
},
|
|
66
65
|
"lint-staged": {
|
|
67
66
|
"**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
|