1 | const path = require("path");
|
2 | const glob = require("glob");
|
3 | const favicon = require("serve-favicon");
|
4 | const _ = require("lodash");
|
5 | const debug = require("debug")("motif:process");
|
6 |
|
7 | const Macro = require("node-macro");
|
8 |
|
9 | const http = require("http");
|
10 | const express = require("express");
|
11 |
|
12 | const session = require("express-session");
|
13 | const cookieParser = require("cookie-parser");
|
14 | const bodyParser = require("body-parser");
|
15 | const helmet = require("helmet");
|
16 | const cors = require("cors");
|
17 | const formidable = require("formidable");
|
18 |
|
19 | const Context = require("./Context");
|
20 | const Swagger = require("./Swagger");
|
21 |
|
22 | class Process {
|
23 |
|
24 | get app() {
|
25 | return this._app;
|
26 | }
|
27 |
|
28 | get context() {
|
29 | return this._context;
|
30 | }
|
31 |
|
32 | get models() {
|
33 | return this._models;
|
34 | }
|
35 |
|
36 | _importClassFile(file) {
|
37 | debug("_importClassFile", file);
|
38 | delete require.cache[
|
39 | require.resolve(path.resolve(this._context.appPath, file))
|
40 | ];
|
41 | return require(path.resolve(this._context.appPath, file));
|
42 | }
|
43 |
|
44 | _loadServices() {
|
45 | debug("_loadServices");
|
46 |
|
47 | const macro = new Macro();
|
48 |
|
49 | let modelFiles = [],
|
50 | serviceFiles = [];
|
51 |
|
52 | macro.add("CREATE-APP", action => {
|
53 | let app = express();
|
54 | app.use(helmet());
|
55 | app.use(bodyParser.urlencoded({ extended: false }));
|
56 | app.use(bodyParser.json());
|
57 | app.use(cors());
|
58 |
|
59 | if(this._context.useCookie === true) {
|
60 | app.use(cookieParser());
|
61 | }
|
62 |
|
63 | if(this._context.useFavicon === true) {
|
64 | app.use(
|
65 | favicon(path.join(this._context.appPath, "public", "favicon.ico"))
|
66 | );
|
67 | }
|
68 |
|
69 | if(this._context.useProxy === true) {
|
70 | app.set("trust proxy", true);
|
71 | }
|
72 |
|
73 | app.on("uncaughtException", this.onException);
|
74 |
|
75 |
|
76 | app.use(function (req, res, next) {
|
77 | if(
|
78 | req.headers["content-type"] &&
|
79 | req.headers["content-type"].indexOf("multipart/form-data") > -1
|
80 | ) {
|
81 |
|
82 | const form = new formidable.IncomingForm({
|
83 | encoding: "utf-8",
|
84 | multiples: true,
|
85 | type: "multipart",
|
86 | maxFieldsSize: 20 * 1024 * 1024,
|
87 | maxFileSize: 200 * 1024 * 1024,
|
88 | maxFields: 1000,
|
89 | hash: true,
|
90 | keepExtensions: false
|
91 | });
|
92 | form.parse(req, function (err, fields) {
|
93 | _.merge(req.body, fields);
|
94 | req.openedFiles = [];
|
95 | });
|
96 | form.on("file", function (name, file) {
|
97 | if(!req.files) {
|
98 | req.files = {};
|
99 | }
|
100 | req.files[name] = file;
|
101 | req.body[name] = file;
|
102 | });
|
103 | form.on("end", function () {
|
104 | req.openedFiles = this.openedFiles;
|
105 | next();
|
106 | });
|
107 | } else {
|
108 | next();
|
109 | }
|
110 | });
|
111 |
|
112 | this._app = app;
|
113 | action.complete();
|
114 | });
|
115 |
|
116 | macro.add("LOAD-MODEL-FILES", action => {
|
117 | glob(
|
118 | "services/*/models/*.js", { cwd: this._context.appPath },
|
119 | (err, files) => {
|
120 | if(err) {
|
121 | action.error(err);
|
122 | return;
|
123 | }
|
124 |
|
125 | modelFiles = files;
|
126 | action.complete();
|
127 | }
|
128 | );
|
129 | });
|
130 |
|
131 | macro.add("LOAD-MODELS", async action => {
|
132 | for(var i = 0, max = modelFiles.length; i < max; i++) {
|
133 | let modelFile = modelFiles[i];
|
134 | let modelClass = this._importClassFile(modelFile);
|
135 | let className = modelClass.prototype.constructor.name;
|
136 | let key = className.replace("Model", "");
|
137 | let model = new modelClass();
|
138 | let design = model.createDesign();
|
139 |
|
140 | if(this._models[key]) {
|
141 | console.warn(`${key} model with the same name is already created.`);
|
142 | }
|
143 |
|
144 | this._models[key] = {
|
145 | name: key,
|
146 | constructor: modelClass,
|
147 | design: design
|
148 | };
|
149 |
|
150 | try {
|
151 | console.log("design initialize", className);
|
152 | await design.initialize();
|
153 | } catch (e) {
|
154 | console.error("className", className, e);
|
155 | }
|
156 | }
|
157 |
|
158 | action.complete();
|
159 | });
|
160 |
|
161 | macro.add("LOAD-SERVICE-FILES", action => {
|
162 | glob(
|
163 | "services/*/*Service.js", { cwd: this._context.appPath },
|
164 | (err, files) => {
|
165 | if(err) {
|
166 | action.error(err);
|
167 | return;
|
168 | }
|
169 |
|
170 | serviceFiles = files;
|
171 | action.complete();
|
172 | }
|
173 | );
|
174 | });
|
175 |
|
176 | macro.add("LOAD-SERVICES", action => {
|
177 | _.each(serviceFiles, file => {
|
178 | let service = this._importClassFile(file);
|
179 |
|
180 | this._services.push(service);
|
181 | });
|
182 |
|
183 | action.complete();
|
184 | });
|
185 |
|
186 | if(this._context.createDoc === true) {
|
187 | macro.add("CRAETE-SWAGGER-DOCS", async action => {
|
188 | try {
|
189 | let app = this._app;
|
190 | let swagger = new Swagger(this._context);
|
191 |
|
192 | let json = swagger.buildJSON(this._routerPaths, false);
|
193 | let swaggerPath = path.join(this._context.appPath, "public/swagger");
|
194 |
|
195 | await swagger.jsonWriteAsync(
|
196 | path.join(swaggerPath, "/data/swag.json"),
|
197 | json
|
198 | );
|
199 |
|
200 | app.use(
|
201 | express.static(
|
202 | path.join(this._context.appPath, "public", "swagger")
|
203 | )
|
204 | );
|
205 |
|
206 | app.get("/doc/data", (req, res) => {
|
207 | res.sendFile("data/swag.json", { root: swaggerPath });
|
208 | });
|
209 |
|
210 | app.get("/doc", (req, res) => {
|
211 | res.sendFile("index.html", { root: swaggerPath });
|
212 | });
|
213 | } catch (e) {
|
214 | console.log(e);
|
215 | }
|
216 |
|
217 | action.complete();
|
218 | });
|
219 |
|
220 | macro.add("CRAETE-SWAGGER-ADMIN-DOCS", async action => {
|
221 | try {
|
222 | let app = this._app;
|
223 | let swagger = new Swagger(this._context);
|
224 |
|
225 | let json = swagger.buildJSON(this._routerPaths, true);
|
226 | let swaggerPath = path.join(this._context.appPath, "public/swagger");
|
227 |
|
228 | await swagger.jsonWriteAsync(
|
229 | path.join(swaggerPath, "/data/swag.admin.json"),
|
230 | json
|
231 | );
|
232 |
|
233 | app.use(
|
234 | express.static(
|
235 | path.join(this._context.appPath, "public", "swagger")
|
236 | )
|
237 | );
|
238 |
|
239 | app.get("/doc/data-admin", (req, res) => {
|
240 | res.sendFile("data/swag.admin.json", { root: swaggerPath });
|
241 | });
|
242 |
|
243 | app.get("/doc-admin", (req, res) => {
|
244 | res.sendFile("index.admin.html", { root: swaggerPath });
|
245 | });
|
246 | } catch (e) {
|
247 | console.log(e);
|
248 | }
|
249 |
|
250 | action.complete();
|
251 | });
|
252 | }
|
253 |
|
254 | macro.add("RUN-SERVER", action => {
|
255 | let server = http.createServer(this._app);
|
256 | server.listen(this.context.port, () => {
|
257 | debug(`Server is running on port ${this.context.port}`);
|
258 | });
|
259 | server.on("error", this.onError);
|
260 | server.on("listening", this.onListening);
|
261 |
|
262 | this._server = server;
|
263 |
|
264 | action.complete();
|
265 | });
|
266 |
|
267 | macro.start(error => {
|
268 | if(error) {
|
269 | debug(error);
|
270 | }
|
271 | });
|
272 | }
|
273 |
|
274 | _attachExceptionHandlers() {
|
275 | process.on("uncaughtException", async function (err) {
|
276 | console.error(err.stack);
|
277 | process.exit(1);
|
278 | });
|
279 |
|
280 | process.on("unhandledRejection", async function (err) {
|
281 | console.error(err.stack);
|
282 | process.exit(1);
|
283 | });
|
284 | }
|
285 |
|
286 | constructor(options) {
|
287 |
|
288 | process.motif = this;
|
289 |
|
290 | this._context = new Context({
|
291 | appPath: path.resolve(process.env.PWD, "."),
|
292 | ...options
|
293 | });
|
294 |
|
295 | this._app = null;
|
296 | this._server = null;
|
297 | this._models = {};
|
298 | this._services = [];
|
299 | this._routerPaths = [];
|
300 | }
|
301 |
|
302 | run() {
|
303 | this._attachExceptionHandlers();
|
304 |
|
305 | this._loadServices();
|
306 | }
|
307 |
|
308 | onListening() {
|
309 | debug("onListening");
|
310 | }
|
311 |
|
312 | onException(req, res, route, err) {
|
313 | debug("onException", err);
|
314 | }
|
315 |
|
316 | onError(error) {
|
317 | debug("onError", error);
|
318 | }
|
319 | }
|
320 |
|
321 | module.exports = Process;
|