namirasoft-node 1.1.4 → 1.1.5
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/.gitlab-ci.yml +14 -0
- package/package.json +3 -3
- package/public/index.html +109 -109
- package/src/AnomalyDetector.ts +84 -84
- package/src/BaseApplication.ts +186 -186
- package/src/BaseApplicationLink.ts +6 -6
- package/src/BaseController.ts +131 -131
- package/src/BaseDatabase.ts +4 -4
- package/src/BaseMySqlDatabase.ts +7 -7
- package/src/BaseSequelizeDatabase.ts +83 -83
- package/src/BaseSequelizeModel.ts +4 -4
- package/src/BaseSequelizeTable.ts +54 -54
- package/src/BaseTable.ts +21 -21
- package/src/EmailService.ts +96 -96
- package/src/EnvService.ts +21 -21
- package/src/IPOperation.ts +38 -38
- package/src/Meta.ts +34 -34
- package/src/OTPOperation.ts +64 -64
- package/src/RequestHeaderService.ts +22 -22
- package/src/ServerToServerOperation.ts +23 -23
- package/src/index.ts +16 -16
- package/tsconfig.json +29 -29
package/src/BaseApplication.ts
CHANGED
|
@@ -1,187 +1,187 @@
|
|
|
1
|
-
import express, { Router } from 'express';
|
|
2
|
-
import cors from "cors";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import swaggerUi from 'swagger-ui-express';
|
|
5
|
-
import swaggerJSDoc from 'swagger-jsdoc';
|
|
6
|
-
import { BaseDatabase } from './BaseDatabase';
|
|
7
|
-
import { ILogger } from "namirasoft-log";
|
|
8
|
-
import { BaseApplicationLink } from './BaseApplicationLink';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import { PackageService } from 'namirasoft-core';
|
|
11
|
-
|
|
12
|
-
export abstract class BaseApplication<D extends BaseDatabase>
|
|
13
|
-
{
|
|
14
|
-
private title: string;
|
|
15
|
-
private description: string;
|
|
16
|
-
private logo: string;
|
|
17
|
-
private version: string;
|
|
18
|
-
private serverPath: string;
|
|
19
|
-
private swaggerPath: string;
|
|
20
|
-
private linkLoader?: () => Promise<void>;
|
|
21
|
-
private links: BaseApplicationLink[] = [];
|
|
22
|
-
public app!: express.Express;
|
|
23
|
-
public database!: D;
|
|
24
|
-
public logger: ILogger;
|
|
25
|
-
protected abstract getDatabase(): D;
|
|
26
|
-
protected abstract getLogger(): ILogger;
|
|
27
|
-
protected abstract getRouter(): Router;
|
|
28
|
-
protected abstract getPort(): number;
|
|
29
|
-
constructor(serverPath: string, swaggerPath: string | null = null)
|
|
30
|
-
{
|
|
31
|
-
let json = PackageService.getMain();
|
|
32
|
-
this.title = json?.getTitle() ?? "";
|
|
33
|
-
this.description = json?.getDescription() ?? "";
|
|
34
|
-
this.logo = json?.getLogo() ?? "";
|
|
35
|
-
this.version = json?.getVersion() ?? "";
|
|
36
|
-
this.serverPath = serverPath;
|
|
37
|
-
this.swaggerPath = swaggerPath ?? (serverPath + "/swagger");
|
|
38
|
-
this.logger = this.getLogger();
|
|
39
|
-
this.addLink = this.addLink.bind(this);
|
|
40
|
-
this.addSwaggerLink = this.addSwaggerLink.bind(this);
|
|
41
|
-
}
|
|
42
|
-
setLinkLoader(linkLoader: () => Promise<void>)
|
|
43
|
-
{
|
|
44
|
-
this.linkLoader = linkLoader;
|
|
45
|
-
}
|
|
46
|
-
addLink(link: BaseApplicationLink)
|
|
47
|
-
{
|
|
48
|
-
this.links.push(link);
|
|
49
|
-
}
|
|
50
|
-
addSwaggerLink()
|
|
51
|
-
{
|
|
52
|
-
this.addLink({
|
|
53
|
-
name: "Swagger",
|
|
54
|
-
url: this.swaggerPath + "/",
|
|
55
|
-
logo: "https://static.namirasoft.com/logo/swagger/base.png",
|
|
56
|
-
description: this.description + " Swagger"
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
async start()
|
|
60
|
-
{
|
|
61
|
-
await this.startCrashHandler();
|
|
62
|
-
await this.startDatabase();
|
|
63
|
-
await this.startServer();
|
|
64
|
-
await this.startSwagger();
|
|
65
|
-
await this.startHomePage();
|
|
66
|
-
await this.onStart();
|
|
67
|
-
}
|
|
68
|
-
protected async startCrashHandler(): Promise<void>
|
|
69
|
-
{
|
|
70
|
-
process.on('unhandledRejection', (reason) =>
|
|
71
|
-
{
|
|
72
|
-
if (reason instanceof Error)
|
|
73
|
-
this.logger.onCatchCritical(reason);
|
|
74
|
-
else
|
|
75
|
-
this.logger.critical(reason + "");
|
|
76
|
-
});
|
|
77
|
-
process.on('uncaughtException', (error) =>
|
|
78
|
-
{
|
|
79
|
-
this.logger.onCatchFatal(error);
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
protected async startDatabase(): Promise<void>
|
|
83
|
-
{
|
|
84
|
-
this.database = this.getDatabase();
|
|
85
|
-
this.database.init();
|
|
86
|
-
await this.database.sync(false);
|
|
87
|
-
}
|
|
88
|
-
protected async startServer(): Promise<void>
|
|
89
|
-
{
|
|
90
|
-
this.app = express();
|
|
91
|
-
this.app.use((req, res, next) =>
|
|
92
|
-
{
|
|
93
|
-
let excludes: string[] = [];
|
|
94
|
-
if (excludes.includes(req.path))
|
|
95
|
-
next();
|
|
96
|
-
else
|
|
97
|
-
express.json({ limit: '100kb' })(req, res, next);
|
|
98
|
-
});
|
|
99
|
-
// Express
|
|
100
|
-
this.app.use(express.static('static'));
|
|
101
|
-
// Cors
|
|
102
|
-
this.app.use(cors({ exposedHeaders: '*', }));
|
|
103
|
-
// api routes
|
|
104
|
-
this.app.use('/', this.getRouter());
|
|
105
|
-
// start server
|
|
106
|
-
const port = this.getPort();
|
|
107
|
-
this.app.listen(port, async () =>
|
|
108
|
-
{
|
|
109
|
-
this.logger.info(`Server listening on port ${port}`);
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
protected async startSwagger(): Promise<void>
|
|
113
|
-
{
|
|
114
|
-
const joptions = {
|
|
115
|
-
definition: {
|
|
116
|
-
openapi: "3.0.1",
|
|
117
|
-
info: {
|
|
118
|
-
title: this.title,
|
|
119
|
-
description: this.description,
|
|
120
|
-
version: this.version,
|
|
121
|
-
// license: {
|
|
122
|
-
// name: "MIT",
|
|
123
|
-
// url: "https://spdx.org/licenses/MIT.html",
|
|
124
|
-
// },
|
|
125
|
-
// contact: {
|
|
126
|
-
// name: "Amir Abolhasani",
|
|
127
|
-
// url: "https://namirasoft.com",
|
|
128
|
-
// email: "accounts@namirasoft.com",
|
|
129
|
-
// },
|
|
130
|
-
},
|
|
131
|
-
servers: [
|
|
132
|
-
{
|
|
133
|
-
url: this.serverPath,
|
|
134
|
-
},
|
|
135
|
-
],
|
|
136
|
-
},
|
|
137
|
-
apis: ['./src/route/*.ts'],
|
|
138
|
-
};
|
|
139
|
-
const swaggerSpec = swaggerJSDoc(joptions);
|
|
140
|
-
var options = {
|
|
141
|
-
explorer: true
|
|
142
|
-
};
|
|
143
|
-
this.app.use(this.swaggerPath + "/", swaggerUi.serve, swaggerUi.setup(swaggerSpec, options));
|
|
144
|
-
}
|
|
145
|
-
protected async startHomePage(): Promise<void>
|
|
146
|
-
{
|
|
147
|
-
this.app.get(this.serverPath + "/", (_, res) =>
|
|
148
|
-
{
|
|
149
|
-
const htmlFilePath = path.join(__dirname, '../public/index.html');
|
|
150
|
-
fs.readFile(htmlFilePath, 'utf8', async (err: any, data: string) =>
|
|
151
|
-
{
|
|
152
|
-
if (err)
|
|
153
|
-
return res.status(500).send('Error reading HTML file');
|
|
154
|
-
|
|
155
|
-
data = data.replace(/\@title/gm, this.title);
|
|
156
|
-
data = data.replace(/\@description/gm, this.description);
|
|
157
|
-
data = data.replace(/\@logo/gm, this.logo);
|
|
158
|
-
let match = /<\@row>([\w\W]*)<\/\@row>/gm.exec(data);
|
|
159
|
-
if (match)
|
|
160
|
-
{
|
|
161
|
-
let rows: string[] = [];
|
|
162
|
-
if (this.linkLoader)
|
|
163
|
-
{
|
|
164
|
-
this.links = [];
|
|
165
|
-
await this.linkLoader();
|
|
166
|
-
}
|
|
167
|
-
this.links.forEach(link =>
|
|
168
|
-
{
|
|
169
|
-
if (match)
|
|
170
|
-
{
|
|
171
|
-
let row = match[1];
|
|
172
|
-
row = row.replace(/\@row_name/gm, link.name);
|
|
173
|
-
row = row.replace(/\@row_url/gm, link.url);
|
|
174
|
-
row = row.replace(/\@row_logo/gm, link.logo);
|
|
175
|
-
row = row.replace(/\@row_description/gm, link.description);
|
|
176
|
-
rows.push(row);
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
data = data.replace(match[0], rows.join(" "));
|
|
180
|
-
}
|
|
181
|
-
res.send(data);
|
|
182
|
-
return;
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
abstract onStart(): Promise<void>;
|
|
1
|
+
import express, { Router } from 'express';
|
|
2
|
+
import cors from "cors";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import swaggerUi from 'swagger-ui-express';
|
|
5
|
+
import swaggerJSDoc from 'swagger-jsdoc';
|
|
6
|
+
import { BaseDatabase } from './BaseDatabase';
|
|
7
|
+
import { ILogger } from "namirasoft-log";
|
|
8
|
+
import { BaseApplicationLink } from './BaseApplicationLink';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import { PackageService } from 'namirasoft-core';
|
|
11
|
+
|
|
12
|
+
export abstract class BaseApplication<D extends BaseDatabase>
|
|
13
|
+
{
|
|
14
|
+
private title: string;
|
|
15
|
+
private description: string;
|
|
16
|
+
private logo: string;
|
|
17
|
+
private version: string;
|
|
18
|
+
private serverPath: string;
|
|
19
|
+
private swaggerPath: string;
|
|
20
|
+
private linkLoader?: () => Promise<void>;
|
|
21
|
+
private links: BaseApplicationLink[] = [];
|
|
22
|
+
public app!: express.Express;
|
|
23
|
+
public database!: D;
|
|
24
|
+
public logger: ILogger;
|
|
25
|
+
protected abstract getDatabase(): D;
|
|
26
|
+
protected abstract getLogger(): ILogger;
|
|
27
|
+
protected abstract getRouter(): Router;
|
|
28
|
+
protected abstract getPort(): number;
|
|
29
|
+
constructor(serverPath: string, swaggerPath: string | null = null)
|
|
30
|
+
{
|
|
31
|
+
let json = PackageService.getMain();
|
|
32
|
+
this.title = json?.getTitle() ?? "";
|
|
33
|
+
this.description = json?.getDescription() ?? "";
|
|
34
|
+
this.logo = json?.getLogo() ?? "";
|
|
35
|
+
this.version = json?.getVersion() ?? "";
|
|
36
|
+
this.serverPath = serverPath;
|
|
37
|
+
this.swaggerPath = swaggerPath ?? (serverPath + "/swagger");
|
|
38
|
+
this.logger = this.getLogger();
|
|
39
|
+
this.addLink = this.addLink.bind(this);
|
|
40
|
+
this.addSwaggerLink = this.addSwaggerLink.bind(this);
|
|
41
|
+
}
|
|
42
|
+
setLinkLoader(linkLoader: () => Promise<void>)
|
|
43
|
+
{
|
|
44
|
+
this.linkLoader = linkLoader;
|
|
45
|
+
}
|
|
46
|
+
addLink(link: BaseApplicationLink)
|
|
47
|
+
{
|
|
48
|
+
this.links.push(link);
|
|
49
|
+
}
|
|
50
|
+
addSwaggerLink()
|
|
51
|
+
{
|
|
52
|
+
this.addLink({
|
|
53
|
+
name: "Swagger",
|
|
54
|
+
url: this.swaggerPath + "/",
|
|
55
|
+
logo: "https://static.namirasoft.com/logo/swagger/base.png",
|
|
56
|
+
description: this.description + " Swagger"
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
async start()
|
|
60
|
+
{
|
|
61
|
+
await this.startCrashHandler();
|
|
62
|
+
await this.startDatabase();
|
|
63
|
+
await this.startServer();
|
|
64
|
+
await this.startSwagger();
|
|
65
|
+
await this.startHomePage();
|
|
66
|
+
await this.onStart();
|
|
67
|
+
}
|
|
68
|
+
protected async startCrashHandler(): Promise<void>
|
|
69
|
+
{
|
|
70
|
+
process.on('unhandledRejection', (reason) =>
|
|
71
|
+
{
|
|
72
|
+
if (reason instanceof Error)
|
|
73
|
+
this.logger.onCatchCritical(reason);
|
|
74
|
+
else
|
|
75
|
+
this.logger.critical(reason + "");
|
|
76
|
+
});
|
|
77
|
+
process.on('uncaughtException', (error) =>
|
|
78
|
+
{
|
|
79
|
+
this.logger.onCatchFatal(error);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
protected async startDatabase(): Promise<void>
|
|
83
|
+
{
|
|
84
|
+
this.database = this.getDatabase();
|
|
85
|
+
this.database.init();
|
|
86
|
+
await this.database.sync(false);
|
|
87
|
+
}
|
|
88
|
+
protected async startServer(): Promise<void>
|
|
89
|
+
{
|
|
90
|
+
this.app = express();
|
|
91
|
+
this.app.use((req, res, next) =>
|
|
92
|
+
{
|
|
93
|
+
let excludes: string[] = [];
|
|
94
|
+
if (excludes.includes(req.path))
|
|
95
|
+
next();
|
|
96
|
+
else
|
|
97
|
+
express.json({ limit: '100kb' })(req, res, next);
|
|
98
|
+
});
|
|
99
|
+
// Express
|
|
100
|
+
this.app.use(express.static('static'));
|
|
101
|
+
// Cors
|
|
102
|
+
this.app.use(cors({ exposedHeaders: '*', }));
|
|
103
|
+
// api routes
|
|
104
|
+
this.app.use('/', this.getRouter());
|
|
105
|
+
// start server
|
|
106
|
+
const port = this.getPort();
|
|
107
|
+
this.app.listen(port, async () =>
|
|
108
|
+
{
|
|
109
|
+
this.logger.info(`Server listening on port ${port}`);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
protected async startSwagger(): Promise<void>
|
|
113
|
+
{
|
|
114
|
+
const joptions = {
|
|
115
|
+
definition: {
|
|
116
|
+
openapi: "3.0.1",
|
|
117
|
+
info: {
|
|
118
|
+
title: this.title,
|
|
119
|
+
description: this.description,
|
|
120
|
+
version: this.version,
|
|
121
|
+
// license: {
|
|
122
|
+
// name: "MIT",
|
|
123
|
+
// url: "https://spdx.org/licenses/MIT.html",
|
|
124
|
+
// },
|
|
125
|
+
// contact: {
|
|
126
|
+
// name: "Amir Abolhasani",
|
|
127
|
+
// url: "https://namirasoft.com",
|
|
128
|
+
// email: "accounts@namirasoft.com",
|
|
129
|
+
// },
|
|
130
|
+
},
|
|
131
|
+
servers: [
|
|
132
|
+
{
|
|
133
|
+
url: this.serverPath,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
apis: ['./src/route/*.ts'],
|
|
138
|
+
};
|
|
139
|
+
const swaggerSpec = swaggerJSDoc(joptions);
|
|
140
|
+
var options = {
|
|
141
|
+
explorer: true
|
|
142
|
+
};
|
|
143
|
+
this.app.use(this.swaggerPath + "/", swaggerUi.serve, swaggerUi.setup(swaggerSpec, options));
|
|
144
|
+
}
|
|
145
|
+
protected async startHomePage(): Promise<void>
|
|
146
|
+
{
|
|
147
|
+
this.app.get(this.serverPath + "/", (_, res) =>
|
|
148
|
+
{
|
|
149
|
+
const htmlFilePath = path.join(__dirname, '../public/index.html');
|
|
150
|
+
fs.readFile(htmlFilePath, 'utf8', async (err: any, data: string) =>
|
|
151
|
+
{
|
|
152
|
+
if (err)
|
|
153
|
+
return res.status(500).send('Error reading HTML file');
|
|
154
|
+
|
|
155
|
+
data = data.replace(/\@title/gm, this.title);
|
|
156
|
+
data = data.replace(/\@description/gm, this.description);
|
|
157
|
+
data = data.replace(/\@logo/gm, this.logo);
|
|
158
|
+
let match = /<\@row>([\w\W]*)<\/\@row>/gm.exec(data);
|
|
159
|
+
if (match)
|
|
160
|
+
{
|
|
161
|
+
let rows: string[] = [];
|
|
162
|
+
if (this.linkLoader)
|
|
163
|
+
{
|
|
164
|
+
this.links = [];
|
|
165
|
+
await this.linkLoader();
|
|
166
|
+
}
|
|
167
|
+
this.links.forEach(link =>
|
|
168
|
+
{
|
|
169
|
+
if (match)
|
|
170
|
+
{
|
|
171
|
+
let row = match[1];
|
|
172
|
+
row = row.replace(/\@row_name/gm, link.name);
|
|
173
|
+
row = row.replace(/\@row_url/gm, link.url);
|
|
174
|
+
row = row.replace(/\@row_logo/gm, link.logo);
|
|
175
|
+
row = row.replace(/\@row_description/gm, link.description);
|
|
176
|
+
rows.push(row);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
data = data.replace(match[0], rows.join(" "));
|
|
180
|
+
}
|
|
181
|
+
res.send(data);
|
|
182
|
+
return;
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
abstract onStart(): Promise<void>;
|
|
187
187
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export type BaseApplicationLink =
|
|
2
|
-
{
|
|
3
|
-
name: string;
|
|
4
|
-
url: string;
|
|
5
|
-
logo: string;
|
|
6
|
-
description: string;
|
|
1
|
+
export type BaseApplicationLink =
|
|
2
|
+
{
|
|
3
|
+
name: string;
|
|
4
|
+
url: string;
|
|
5
|
+
logo: string;
|
|
6
|
+
description: string;
|
|
7
7
|
};
|
package/src/BaseController.ts
CHANGED
|
@@ -1,132 +1,132 @@
|
|
|
1
|
-
import * as express from 'express';
|
|
2
|
-
import { BaseDatabase } from './BaseDatabase';
|
|
3
|
-
import { SchemaLike } from 'joi';
|
|
4
|
-
import { AnomalyDetector } from './AnomalyDetector';
|
|
5
|
-
import { ILogger } from 'namirasoft-log';
|
|
6
|
-
import { Meta } from './Meta';
|
|
7
|
-
import Joi from 'joi';
|
|
8
|
-
import { ErrorOperation, HTTPError } from 'namirasoft-core';
|
|
9
|
-
|
|
10
|
-
export abstract class BaseController<D extends BaseDatabase, State, Props>
|
|
11
|
-
{
|
|
12
|
-
protected showLogAtTheBeginning: boolean = false;
|
|
13
|
-
protected showLogAtTheEnd: boolean = true;
|
|
14
|
-
protected req: express.Request;
|
|
15
|
-
protected res: express.Response;
|
|
16
|
-
protected logger: ILogger;
|
|
17
|
-
protected meta!: Meta;
|
|
18
|
-
protected result!: {};
|
|
19
|
-
protected database!: D;
|
|
20
|
-
protected state!: State;
|
|
21
|
-
protected props!: Props;
|
|
22
|
-
constructor(req: express.Request, res: express.Response)
|
|
23
|
-
{
|
|
24
|
-
this.req = req;
|
|
25
|
-
this.res = res;
|
|
26
|
-
this.logger = this.getLogger();
|
|
27
|
-
}
|
|
28
|
-
abstract getLogger(): ILogger;
|
|
29
|
-
abstract getDatabase(): D;
|
|
30
|
-
abstract getAnomaly(): AnomalyDetector | null;
|
|
31
|
-
abstract getBodySchema(): SchemaLike | null;
|
|
32
|
-
abstract getQuerySchema(): SchemaLike | null;
|
|
33
|
-
abstract getState(): Promise<State>;
|
|
34
|
-
abstract getProps(): Promise<Props>;
|
|
35
|
-
abstract preHandle(): Promise<void>;
|
|
36
|
-
abstract handle(): Promise<any>;
|
|
37
|
-
abstract postHandle(): Promise<void>;
|
|
38
|
-
|
|
39
|
-
async run()
|
|
40
|
-
{
|
|
41
|
-
this.meta = new Meta(this.req);
|
|
42
|
-
// result
|
|
43
|
-
this.result = {};
|
|
44
|
-
try
|
|
45
|
-
{
|
|
46
|
-
// meta
|
|
47
|
-
this.meta.onStart();
|
|
48
|
-
if (this.showLogAtTheBeginning)
|
|
49
|
-
this.logger.info(JSON.stringify(this.meta));
|
|
50
|
-
// init controller
|
|
51
|
-
this.database = this.getDatabase();
|
|
52
|
-
this.database.init();
|
|
53
|
-
this.state = await this.getState();
|
|
54
|
-
this.props = await this.getProps();
|
|
55
|
-
|
|
56
|
-
// preHandle
|
|
57
|
-
await this.preHandle();
|
|
58
|
-
|
|
59
|
-
// check for anomaly
|
|
60
|
-
let anomaly: AnomalyDetector | null = this.getAnomaly();
|
|
61
|
-
if (anomaly != null)
|
|
62
|
-
if (anomaly.isAnomaly(this.meta.ip, this.meta.url))
|
|
63
|
-
ErrorOperation.throwHTTP(403, 'Suspicious activity detected.');
|
|
64
|
-
|
|
65
|
-
// check body validation
|
|
66
|
-
let bodySchema = this.getBodySchema();
|
|
67
|
-
if (bodySchema != null)
|
|
68
|
-
{
|
|
69
|
-
const validation = await Joi.compile(bodySchema)
|
|
70
|
-
.prefs({ errors: { label: 'key' } })
|
|
71
|
-
.validate(this.req.body);
|
|
72
|
-
if (validation.error)
|
|
73
|
-
{
|
|
74
|
-
let message = validation.error.details.map((details) => details.message).join(', ');
|
|
75
|
-
ErrorOperation.throwHTTP(400, message);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
// check query validation
|
|
79
|
-
let querySchema = this.getQuerySchema();
|
|
80
|
-
if (querySchema != null)
|
|
81
|
-
{
|
|
82
|
-
const validation = await Joi.compile(querySchema)
|
|
83
|
-
.prefs({ errors: { label: 'key' } })
|
|
84
|
-
.validate(this.req.query);
|
|
85
|
-
if (validation.error)
|
|
86
|
-
{
|
|
87
|
-
let message = validation.error.details.map((details) => details.message).join(', ');
|
|
88
|
-
ErrorOperation.throwHTTP(400, message);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// call controller
|
|
93
|
-
this.result = await this.handle();
|
|
94
|
-
if (this.result == null)
|
|
95
|
-
this.result = "Success";
|
|
96
|
-
|
|
97
|
-
// postHandle
|
|
98
|
-
await this.postHandle();
|
|
99
|
-
} catch (error)
|
|
100
|
-
{
|
|
101
|
-
let message: string;
|
|
102
|
-
if (error instanceof Error)
|
|
103
|
-
{
|
|
104
|
-
this.meta.error = error;
|
|
105
|
-
message = error.message;
|
|
106
|
-
}
|
|
107
|
-
else
|
|
108
|
-
message = error + "";
|
|
109
|
-
|
|
110
|
-
if (error instanceof HTTPError)
|
|
111
|
-
{
|
|
112
|
-
this.meta.code = error.code;
|
|
113
|
-
this.logger.error(error.message + "\n" + JSON.stringify(this.meta), undefined, error.stack);
|
|
114
|
-
}
|
|
115
|
-
else
|
|
116
|
-
{
|
|
117
|
-
this.meta.code = 500;
|
|
118
|
-
if (error instanceof Error)
|
|
119
|
-
this.logger.critical(error.message + "\n" + JSON.stringify(this.meta), undefined, error.stack);
|
|
120
|
-
}
|
|
121
|
-
this.meta.message = message;
|
|
122
|
-
if (error instanceof HTTPError)
|
|
123
|
-
this.result = this.meta.message;
|
|
124
|
-
else
|
|
125
|
-
this.result = "Sorry, internl server error.";
|
|
126
|
-
}
|
|
127
|
-
this.meta.onFinish();
|
|
128
|
-
if (this.showLogAtTheEnd)
|
|
129
|
-
this.logger.info(JSON.stringify(this.meta));
|
|
130
|
-
return this.res.status(this.meta.code).send(this.result);
|
|
131
|
-
}
|
|
1
|
+
import * as express from 'express';
|
|
2
|
+
import { BaseDatabase } from './BaseDatabase';
|
|
3
|
+
import { SchemaLike } from 'joi';
|
|
4
|
+
import { AnomalyDetector } from './AnomalyDetector';
|
|
5
|
+
import { ILogger } from 'namirasoft-log';
|
|
6
|
+
import { Meta } from './Meta';
|
|
7
|
+
import Joi from 'joi';
|
|
8
|
+
import { ErrorOperation, HTTPError } from 'namirasoft-core';
|
|
9
|
+
|
|
10
|
+
export abstract class BaseController<D extends BaseDatabase, State, Props>
|
|
11
|
+
{
|
|
12
|
+
protected showLogAtTheBeginning: boolean = false;
|
|
13
|
+
protected showLogAtTheEnd: boolean = true;
|
|
14
|
+
protected req: express.Request;
|
|
15
|
+
protected res: express.Response;
|
|
16
|
+
protected logger: ILogger;
|
|
17
|
+
protected meta!: Meta;
|
|
18
|
+
protected result!: {};
|
|
19
|
+
protected database!: D;
|
|
20
|
+
protected state!: State;
|
|
21
|
+
protected props!: Props;
|
|
22
|
+
constructor(req: express.Request, res: express.Response)
|
|
23
|
+
{
|
|
24
|
+
this.req = req;
|
|
25
|
+
this.res = res;
|
|
26
|
+
this.logger = this.getLogger();
|
|
27
|
+
}
|
|
28
|
+
abstract getLogger(): ILogger;
|
|
29
|
+
abstract getDatabase(): D;
|
|
30
|
+
abstract getAnomaly(): AnomalyDetector | null;
|
|
31
|
+
abstract getBodySchema(): SchemaLike | null;
|
|
32
|
+
abstract getQuerySchema(): SchemaLike | null;
|
|
33
|
+
abstract getState(): Promise<State>;
|
|
34
|
+
abstract getProps(): Promise<Props>;
|
|
35
|
+
abstract preHandle(): Promise<void>;
|
|
36
|
+
abstract handle(): Promise<any>;
|
|
37
|
+
abstract postHandle(): Promise<void>;
|
|
38
|
+
|
|
39
|
+
async run()
|
|
40
|
+
{
|
|
41
|
+
this.meta = new Meta(this.req);
|
|
42
|
+
// result
|
|
43
|
+
this.result = {};
|
|
44
|
+
try
|
|
45
|
+
{
|
|
46
|
+
// meta
|
|
47
|
+
this.meta.onStart();
|
|
48
|
+
if (this.showLogAtTheBeginning)
|
|
49
|
+
this.logger.info(JSON.stringify(this.meta));
|
|
50
|
+
// init controller
|
|
51
|
+
this.database = this.getDatabase();
|
|
52
|
+
this.database.init();
|
|
53
|
+
this.state = await this.getState();
|
|
54
|
+
this.props = await this.getProps();
|
|
55
|
+
|
|
56
|
+
// preHandle
|
|
57
|
+
await this.preHandle();
|
|
58
|
+
|
|
59
|
+
// check for anomaly
|
|
60
|
+
let anomaly: AnomalyDetector | null = this.getAnomaly();
|
|
61
|
+
if (anomaly != null)
|
|
62
|
+
if (anomaly.isAnomaly(this.meta.ip, this.meta.url))
|
|
63
|
+
ErrorOperation.throwHTTP(403, 'Suspicious activity detected.');
|
|
64
|
+
|
|
65
|
+
// check body validation
|
|
66
|
+
let bodySchema = this.getBodySchema();
|
|
67
|
+
if (bodySchema != null)
|
|
68
|
+
{
|
|
69
|
+
const validation = await Joi.compile(bodySchema)
|
|
70
|
+
.prefs({ errors: { label: 'key' } })
|
|
71
|
+
.validate(this.req.body);
|
|
72
|
+
if (validation.error)
|
|
73
|
+
{
|
|
74
|
+
let message = validation.error.details.map((details) => details.message).join(', ');
|
|
75
|
+
ErrorOperation.throwHTTP(400, message);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// check query validation
|
|
79
|
+
let querySchema = this.getQuerySchema();
|
|
80
|
+
if (querySchema != null)
|
|
81
|
+
{
|
|
82
|
+
const validation = await Joi.compile(querySchema)
|
|
83
|
+
.prefs({ errors: { label: 'key' } })
|
|
84
|
+
.validate(this.req.query);
|
|
85
|
+
if (validation.error)
|
|
86
|
+
{
|
|
87
|
+
let message = validation.error.details.map((details) => details.message).join(', ');
|
|
88
|
+
ErrorOperation.throwHTTP(400, message);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// call controller
|
|
93
|
+
this.result = await this.handle();
|
|
94
|
+
if (this.result == null)
|
|
95
|
+
this.result = "Success";
|
|
96
|
+
|
|
97
|
+
// postHandle
|
|
98
|
+
await this.postHandle();
|
|
99
|
+
} catch (error)
|
|
100
|
+
{
|
|
101
|
+
let message: string;
|
|
102
|
+
if (error instanceof Error)
|
|
103
|
+
{
|
|
104
|
+
this.meta.error = error;
|
|
105
|
+
message = error.message;
|
|
106
|
+
}
|
|
107
|
+
else
|
|
108
|
+
message = error + "";
|
|
109
|
+
|
|
110
|
+
if (error instanceof HTTPError)
|
|
111
|
+
{
|
|
112
|
+
this.meta.code = error.code;
|
|
113
|
+
this.logger.error(error.message + "\n" + JSON.stringify(this.meta), undefined, error.stack);
|
|
114
|
+
}
|
|
115
|
+
else
|
|
116
|
+
{
|
|
117
|
+
this.meta.code = 500;
|
|
118
|
+
if (error instanceof Error)
|
|
119
|
+
this.logger.critical(error.message + "\n" + JSON.stringify(this.meta), undefined, error.stack);
|
|
120
|
+
}
|
|
121
|
+
this.meta.message = message;
|
|
122
|
+
if (error instanceof HTTPError)
|
|
123
|
+
this.result = this.meta.message;
|
|
124
|
+
else
|
|
125
|
+
this.result = "Sorry, internl server error.";
|
|
126
|
+
}
|
|
127
|
+
this.meta.onFinish();
|
|
128
|
+
if (this.showLogAtTheEnd)
|
|
129
|
+
this.logger.info(JSON.stringify(this.meta));
|
|
130
|
+
return this.res.status(this.meta.code).send(this.result);
|
|
131
|
+
}
|
|
132
132
|
}
|
package/src/BaseDatabase.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export abstract class BaseDatabase
|
|
2
|
-
{
|
|
3
|
-
abstract init(): void;
|
|
4
|
-
abstract sync(force: boolean): Promise<void>;
|
|
1
|
+
export abstract class BaseDatabase
|
|
2
|
+
{
|
|
3
|
+
abstract init(): void;
|
|
4
|
+
abstract sync(force: boolean): Promise<void>;
|
|
5
5
|
}
|