UNPKG

9.71 kBJavaScriptView Raw
1"use strict"
2
3const path = require("path")
4const glob = require("glob")
5const fs = require("fs")
6const favicon = require("serve-favicon")
7const _ = require("lodash")
8const debug = require("debug")("motif:process")
9
10const Macro = require("node-macro")
11
12const http = require("http")
13const express = require("express")
14
15const session = require("express-session")
16const cookieParser = require("cookie-parser")
17const bodyParser = require("body-parser")
18const helmet = require("helmet")
19const cors = require("cors")
20const formidable = require("formidable")
21const contextService = require('request-context')
22const userAgent = require('express-useragent')
23
24const Context = require("./Context")
25const Swagger = require("./Swagger")
26
27class 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", // or urlencoded
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
378module.exports = Process