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