@adaptivestone/framework 2.15.4 → 3.0.1
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 +72 -0
- package/README.md +2 -0
- package/commands/Documentation.js +14 -0
- package/commands/DropIndex.js +4 -0
- package/commands/migration/Create.js +4 -0
- package/commands/migration/Migrate.js +4 -0
- package/config/http.js +1 -1
- package/config/i18n.js +1 -1
- package/config/log.js +4 -3
- package/controllers/Auth.js +19 -28
- package/controllers/Auth.test.js +4 -9
- package/controllers/Home.js +1 -2
- package/models/User.js +1 -10
- package/modules/AbstractCommand.js +4 -0
- package/modules/AbstractController.js +251 -218
- package/modules/AbstractModel.js +15 -22
- package/modules/Base.d.ts +36 -0
- package/modules/Base.js +0 -12
- package/package.json +10 -16
- package/server.d.ts +75 -0
- package/server.js +8 -1
- package/services/cache/Cache.d.ts +22 -0
- package/services/cache/Cache.js +4 -0
- package/services/http/HttpServer.js +5 -3
- package/services/http/middleware/AbstractMiddleware.js +14 -0
- package/services/http/middleware/Auth.js +4 -0
- package/services/http/middleware/GetUserByToken.js +9 -0
- package/services/http/middleware/PrepareAppInfo.js +4 -0
- package/services/http/middleware/RateLimiter.js +5 -1
- package/services/http/middleware/Role.js +29 -0
- package/tests/setup.js +1 -1
- package/types/Expand.d.ts +11 -0
- package/types/TFoldersConfig.d.ts +19 -0
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adaptivestone/framework",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Adaptive stone node js framework",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://gitlab.com/adaptivestone/framework"
|
|
9
9
|
},
|
|
10
|
+
"homepage": "https://framework.adaptivestone.com/",
|
|
10
11
|
"scripts": {
|
|
11
12
|
"dev": "nodemon ./index.js",
|
|
12
13
|
"prod": "nodemon ./cluster.js",
|
|
@@ -25,18 +26,17 @@
|
|
|
25
26
|
"license": "MIT",
|
|
26
27
|
"dependencies": {
|
|
27
28
|
"bcrypt": "^5.0.1",
|
|
28
|
-
"body-parser": "^1.19.0",
|
|
29
29
|
"cors": "^2.8.5",
|
|
30
30
|
"deepmerge": "^4.2.2",
|
|
31
31
|
"dotenv": "^10.0.0",
|
|
32
32
|
"email-templates": "^8.0.7",
|
|
33
33
|
"express": "^4.17.1",
|
|
34
|
-
"i18next": "^
|
|
34
|
+
"i18next": "^21.2.4",
|
|
35
35
|
"i18next-chained-backend": "^3.0.2",
|
|
36
36
|
"i18next-fs-backend": "^1.1.1",
|
|
37
37
|
"i18next-http-middleware": "^3.1.4",
|
|
38
38
|
"minimist": "^1.2.5",
|
|
39
|
-
"mongoose": "^
|
|
39
|
+
"mongoose": "^6.0.0",
|
|
40
40
|
"nodemailer": "^6.6.3",
|
|
41
41
|
"nodemailer-sendmail-transport": "^1.0.2",
|
|
42
42
|
"nodemailer-stub-transport": "^1.1.0",
|
|
@@ -44,21 +44,20 @@
|
|
|
44
44
|
"pug": "^3.0.2",
|
|
45
45
|
"rate-limiter-flexible": "^2.2.4",
|
|
46
46
|
"redis": "^3.1.2",
|
|
47
|
-
"validator": "^13.6.0",
|
|
48
47
|
"winston": "^3.3.3",
|
|
49
|
-
"winston-transport-sentry-node": "^
|
|
48
|
+
"winston-transport-sentry-node": "^2.0.0",
|
|
50
49
|
"yup": "^0.32.9"
|
|
51
50
|
},
|
|
52
51
|
"devDependencies": {
|
|
53
|
-
"eslint": "^
|
|
54
|
-
"eslint-config-airbnb-base": "^
|
|
52
|
+
"eslint": "^8.0.0",
|
|
53
|
+
"eslint-config-airbnb-base": "^15.0.0",
|
|
55
54
|
"eslint-config-prettier": "^8.3.0",
|
|
56
55
|
"eslint-plugin-import": "^2.23.4",
|
|
57
|
-
"eslint-plugin-jest": "^
|
|
56
|
+
"eslint-plugin-jest": "^25.0.0",
|
|
58
57
|
"husky": "^7.0.0",
|
|
59
58
|
"jest": "^27.0.6",
|
|
60
|
-
"lint-staged": "^
|
|
61
|
-
"mongodb-memory-server": "^
|
|
59
|
+
"lint-staged": "^12.0.0",
|
|
60
|
+
"mongodb-memory-server": "^8.0.2",
|
|
62
61
|
"nodemon": "^2.0.12",
|
|
63
62
|
"supertest": "^6.1.4"
|
|
64
63
|
},
|
|
@@ -66,10 +65,5 @@
|
|
|
66
65
|
"**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
|
|
67
66
|
"prettier --write"
|
|
68
67
|
]
|
|
69
|
-
},
|
|
70
|
-
"config": {
|
|
71
|
-
"mongodbMemoryServer": {
|
|
72
|
-
"version": "4.4.6"
|
|
73
|
-
}
|
|
74
68
|
}
|
|
75
69
|
}
|
package/server.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import TFolderConfig from './types/TFoldersConfig';
|
|
2
|
+
import { ExpandDeep } from './types/Expand';
|
|
3
|
+
|
|
4
|
+
import EventEmitter from 'events';
|
|
5
|
+
|
|
6
|
+
import { Model as MongooseModel, Schema } from 'mongoose';
|
|
7
|
+
|
|
8
|
+
import BaseCli from './modules/BaseCli';
|
|
9
|
+
import Cache from './services/cache/Cache';
|
|
10
|
+
|
|
11
|
+
type ServerConfig = {
|
|
12
|
+
folders: ExpandDeep<TFolderConfig>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
declare class Server {
|
|
16
|
+
config: ServerConfig;
|
|
17
|
+
app: {
|
|
18
|
+
getConfig: Server['getConfig'];
|
|
19
|
+
getModel: Server['getModel'];
|
|
20
|
+
runCliCommand: Server['runCliCommand'];
|
|
21
|
+
updateConfig: Server['updateConfig'];
|
|
22
|
+
foldersConfig: Server['config'];
|
|
23
|
+
events: EventEmitter;
|
|
24
|
+
get cache(): Server['cacheService'];
|
|
25
|
+
httpServer: null;
|
|
26
|
+
controllerManager: null;
|
|
27
|
+
};
|
|
28
|
+
cacheService: Cache;
|
|
29
|
+
|
|
30
|
+
cache: {
|
|
31
|
+
configs: Map<string, {}>;
|
|
32
|
+
models: Map<string, MongooseModel<any>>;
|
|
33
|
+
};
|
|
34
|
+
cli: boolean;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Construct new server
|
|
38
|
+
*/
|
|
39
|
+
constructor(config: ExpandDeep<ServerConfig>);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Start server (http + websocket + init all http and websocet ralated functions)
|
|
43
|
+
*/
|
|
44
|
+
startServer(callbackBefore404?: Promise<null>): Promise<null>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Return config from {configName} (file name) on config folder.
|
|
48
|
+
* Support cache and updating confing into cache
|
|
49
|
+
* Also will update config based on NODE_ENV. If config.js and config.production.js
|
|
50
|
+
* and NODE_ENV is production then we will load base config (config.js) and the load
|
|
51
|
+
* environment config (config.production.js) and overwrite base config options
|
|
52
|
+
* @see updateConfig
|
|
53
|
+
* @TODO generate that based on real data
|
|
54
|
+
*/
|
|
55
|
+
getConfig(configName: string): {};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Primary designed for tests when we need to update some configs before start testing
|
|
59
|
+
* Should be called before any initialization was done
|
|
60
|
+
*/
|
|
61
|
+
updateConfig(configName: string, config: {}): {};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Return model from {modelName} (file name) on model folder.
|
|
65
|
+
* Support cache
|
|
66
|
+
*/
|
|
67
|
+
getModel(modelName: string): MongooseModel<any>;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Run cli command into framework (http, ws, etc)
|
|
71
|
+
*/
|
|
72
|
+
runCliCommand(commandName: string, args: {}): Promise<BaseCli['run']>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export = Server;
|
package/server.js
CHANGED
|
@@ -32,6 +32,8 @@ class Server {
|
|
|
32
32
|
get cache() {
|
|
33
33
|
return that.getCache();
|
|
34
34
|
},
|
|
35
|
+
httpServer: null,
|
|
36
|
+
controllerManager: null,
|
|
35
37
|
};
|
|
36
38
|
|
|
37
39
|
this.cache = {
|
|
@@ -122,7 +124,7 @@ class Server {
|
|
|
122
124
|
updateConfig(configName, config) {
|
|
123
125
|
// const confName = configName.charAt(0).toUpperCase() + configName.slice(1);
|
|
124
126
|
const conf = this.getConfig(configName);
|
|
125
|
-
const newConf = Object.assign(conf, config);
|
|
127
|
+
const newConf = Object.assign(conf, config); // TODO deep clone
|
|
126
128
|
this.cache.configs.set(configName, newConf);
|
|
127
129
|
return newConf;
|
|
128
130
|
}
|
|
@@ -134,6 +136,11 @@ class Server {
|
|
|
134
136
|
* @returns {import('mongoose').Model}
|
|
135
137
|
*/
|
|
136
138
|
getModel(modelName) {
|
|
139
|
+
if (modelName.endsWith('s')) {
|
|
140
|
+
console.warn(
|
|
141
|
+
`Probably your model name '${modelName}' in plural from. Try to avoid plural form`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
137
144
|
if (!this.cache.models.has(modelName)) {
|
|
138
145
|
const Model = this.getFileWithExtendingInhirence('models', modelName);
|
|
139
146
|
if (!Model) {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import Base from '../../modules/Base';
|
|
2
|
+
import Server from '../../server';
|
|
3
|
+
|
|
4
|
+
declare class Cache extends Base {
|
|
5
|
+
app: Server['app'];
|
|
6
|
+
|
|
7
|
+
constructor(app: Server['app']);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get value from cache. Set and get if not eists
|
|
11
|
+
* @param key key to check
|
|
12
|
+
* @param onNotFound callback that will be executed if value not found on cahce
|
|
13
|
+
* @param storeTime how long we should store value on cache
|
|
14
|
+
*/
|
|
15
|
+
getSetValue(
|
|
16
|
+
key: String,
|
|
17
|
+
onNotFound: () => Promise<any>,
|
|
18
|
+
storeTime: number,
|
|
19
|
+
): Promise<any>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export = Cache;
|
package/services/cache/Cache.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const http = require('http');
|
|
3
3
|
const path = require('path');
|
|
4
|
-
const bodyParser = require('body-parser');
|
|
5
4
|
const cors = require('cors');
|
|
6
5
|
|
|
7
6
|
const i18next = require('i18next');
|
|
8
7
|
const i18nextMiddleware = require('i18next-http-middleware');
|
|
9
8
|
const BackendFS = require('i18next-fs-backend');
|
|
10
9
|
const Backend = require('i18next-chained-backend');
|
|
10
|
+
const PrepareAppInfoMiddleware = require('./middleware/PrepareAppInfo');
|
|
11
11
|
|
|
12
12
|
const Base = require('../../modules/Base');
|
|
13
13
|
|
|
@@ -41,11 +41,13 @@ class HttpServer extends Base {
|
|
|
41
41
|
origin: httpConfig.corsDomains,
|
|
42
42
|
}),
|
|
43
43
|
); // todo whitelist
|
|
44
|
-
this.express.use(
|
|
45
|
-
this.express.use(
|
|
44
|
+
this.express.use(express.urlencoded({ limit: '50mb', extended: true }));
|
|
45
|
+
this.express.use(express.json({ limit: '50mb' }));
|
|
46
46
|
this.express.use(express.static(folderConfig.folders.public));
|
|
47
47
|
this.express.use(express.static('./public'));
|
|
48
48
|
|
|
49
|
+
this.express.use(new PrepareAppInfoMiddleware(this.app).getMiddleware());
|
|
50
|
+
|
|
49
51
|
// As exprress will check numbersof arguments
|
|
50
52
|
// eslint-disable-next-line no-unused-vars
|
|
51
53
|
this.express.use((err, req, res, next) => {
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
const Base = require('../../../modules/Base');
|
|
2
2
|
|
|
3
3
|
class AbstractMiddleware extends Base {
|
|
4
|
+
constructor(app, params) {
|
|
5
|
+
super(app);
|
|
6
|
+
this.params = params;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
static get description() {
|
|
10
|
+
return ' Middleware description. Please provide own';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async middleware(req, res, next) {
|
|
14
|
+
this.logger.warn('Middleware is not implemented');
|
|
15
|
+
next();
|
|
16
|
+
}
|
|
17
|
+
|
|
4
18
|
getMiddleware() {
|
|
5
19
|
return this.middleware.bind(this);
|
|
6
20
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
const AbstractMiddleware = require('./AbstractMiddleware');
|
|
2
2
|
|
|
3
3
|
class AuthMiddleware extends AbstractMiddleware {
|
|
4
|
+
static get description() {
|
|
5
|
+
return 'Allow to pass only if the user provided. Please use any middleware that provide user instance before';
|
|
6
|
+
}
|
|
7
|
+
|
|
4
8
|
async middleware(req, res, next) {
|
|
5
9
|
if (!req.appInfo.user) {
|
|
6
10
|
this.logger.info('User try to access resource without credentials');
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
const AbstractMiddleware = require('./AbstractMiddleware');
|
|
2
2
|
|
|
3
3
|
class GetUserByToken extends AbstractMiddleware {
|
|
4
|
+
static get description() {
|
|
5
|
+
return 'Grab a token and try to parse the user from it. It user exist will add req.appInfo.user variable';
|
|
6
|
+
}
|
|
7
|
+
|
|
4
8
|
async middleware(req, res, next) {
|
|
5
9
|
let { token } = req.body;
|
|
10
|
+
this.logger.verbose(
|
|
11
|
+
`GetUserByToken token in BODY ${token}. Token if Authorization header ${req.get(
|
|
12
|
+
'Authorization',
|
|
13
|
+
)}`,
|
|
14
|
+
);
|
|
6
15
|
if (!token) {
|
|
7
16
|
token = req.get('Authorization');
|
|
8
17
|
if (!token || token === 'null') {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
const AbstractMiddleware = require('./AbstractMiddleware');
|
|
2
2
|
|
|
3
3
|
class PrepareAppInfo extends AbstractMiddleware {
|
|
4
|
+
static get description() {
|
|
5
|
+
return 'Basic middleware that creates "req.appInfo" object';
|
|
6
|
+
}
|
|
7
|
+
|
|
4
8
|
async middleware(req, res, next) {
|
|
5
9
|
if (!req.appInfo) {
|
|
6
10
|
req.appInfo = {
|
|
@@ -10,8 +10,12 @@ const mongoose = require('mongoose');
|
|
|
10
10
|
const AbstractMiddleware = require('./AbstractMiddleware');
|
|
11
11
|
|
|
12
12
|
class RateLimiter extends AbstractMiddleware {
|
|
13
|
+
static get description() {
|
|
14
|
+
return 'Rate limiter middleware. Limit amount of request. Please refer to documentation';
|
|
15
|
+
}
|
|
16
|
+
|
|
13
17
|
constructor(app, params) {
|
|
14
|
-
super(app);
|
|
18
|
+
super(app, params);
|
|
15
19
|
const limiterOptions = this.app.getConfig('rateLimiter');
|
|
16
20
|
this.finalOptions = merge(limiterOptions, params);
|
|
17
21
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const AbstractMiddleware = require('./AbstractMiddleware');
|
|
2
|
+
|
|
3
|
+
class RoleMiddleware extends AbstractMiddleware {
|
|
4
|
+
static get description() {
|
|
5
|
+
return 'Check user role (user.roles property). If the user has no role then stop request and return error. OR logic (any role will pass user)';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async middleware(req, res, next) {
|
|
9
|
+
const { user } = req.appInfo;
|
|
10
|
+
|
|
11
|
+
if (!user) {
|
|
12
|
+
return res.status(401).send();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let hasRole = false;
|
|
16
|
+
user.roles.forEach((role) => {
|
|
17
|
+
if (this.params.roles.includes(role)) {
|
|
18
|
+
hasRole = true;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (!hasRole) {
|
|
23
|
+
return res.status(403).json({ message: 'You do not have access' });
|
|
24
|
+
}
|
|
25
|
+
return next();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = RoleMiddleware;
|
package/tests/setup.js
CHANGED
|
@@ -9,7 +9,7 @@ const Server = require('../server');
|
|
|
9
9
|
jest.setTimeout(1000000);
|
|
10
10
|
beforeAll(async () => {
|
|
11
11
|
mongoMemoryServerInstance = await MongoMemoryReplSet.create({
|
|
12
|
-
binary: { version: '4.4.6' },
|
|
12
|
+
// binary: { version: '4.4.6' },
|
|
13
13
|
replSet: { storageEngine: 'wiredTiger' },
|
|
14
14
|
});
|
|
15
15
|
await mongoMemoryServerInstance.waitUntilRunning();
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param config path to folder with config files
|
|
3
|
+
* @param models path to folder with moidels files
|
|
4
|
+
* @param controllers path to folder with controllers files
|
|
5
|
+
* @param views path to folder with view files
|
|
6
|
+
* @param public path to folder with public files
|
|
7
|
+
* @param locales path to folder with locales files
|
|
8
|
+
* @param emails path to folder with emails files
|
|
9
|
+
*/
|
|
10
|
+
type FolderConfig = {
|
|
11
|
+
config: string;
|
|
12
|
+
models: string;
|
|
13
|
+
controllers: string;
|
|
14
|
+
views: string;
|
|
15
|
+
public: string;
|
|
16
|
+
emails: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default FolderConfig;
|