-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathapp.js
More file actions
209 lines (174 loc) · 5.84 KB
/
Copy pathapp.js
File metadata and controls
209 lines (174 loc) · 5.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
'use strict'
// dependencies
const config = require('./config')
const express = require('express')
const bodyParser = require('body-parser')
const http = require('http')
const path = require('path')
const passport = require('passport')
const mongoose = require('mongoose')
const helmet = require('helmet')
const cors = require('cors')
const mongoSanitize = require('express-mongo-sanitize')
const basicAuth = require('express-basic-auth')
const swaggerUi = require('swagger-ui-express')
const swaggerSpec = require('./swagger')
// create express app
const app = express()
// keep reference to config
app.config = config
// setup the web server
app.server = http.createServer(app)
// setup mongoose
app.db = mongoose.connection
app.db.on('error', function (error) {
console.error('Error in MongoDb connection: ' + error)
})
app.db.on('disconnected', function () {
console.log('MongoDB disconnected!')
})
// config data models
require('./models')(app, mongoose)
// settings
app.disable('x-powered-by')
app.set('port', config.port)
app.set('views', path.join(__dirname, 'controllers'))
app.set('view engine', 'pug')
// middleware
app.use(require('morgan')('dev'))
app.use(require('compression')())
app.use(require('serve-static')(path.join(__dirname, 'build')))
app.use(require('serve-static')(path.join(__dirname, 'public')))
app.use(require('method-override')())
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.use(passport.initialize())
const allowedOrigins = config.allowedOrigins
? config.allowedOrigins.split(',')
: ['http://localhost:3000', 'http://localhost:3001']
app.use(cors({ origin: allowedOrigins, credentials: true }))
app.use(helmet())
// 防止 NoSQL Injection:過濾 $ / . 等 MongoDB 運算子,避免惡意 payload 竄改查詢條件。
// sanitize() 深度遍歷物件,大型 payload 會造成效能瓶頸。
// 根本解法:在 Mongoose Schema 層定義嚴格型別與 required,
// 搭配 Joi / Zod 做輸入驗證,從源頭拒絕非預期欄位,可取代此中介層。
// 注意:新版 Node.js 將 req.query 定義為 getter-only,不能直接賦值,
// 因此對 query 採用 in-place 修改而非替換參考。
app.use(function (req, res, next) {
if (req.body) req.body = mongoSanitize.sanitize(req.body)
if (req.params) req.params = mongoSanitize.sanitize(req.params)
if (req.query) {
const clean = mongoSanitize.sanitize(Object.assign({}, req.query))
Object.keys(req.query).forEach(function (k) {
if (k in clean) req.query[k] = clean[k]
else delete req.query[k]
})
}
next()
})
// response locals
app.use(function (req, res, next) {
res.locals.user = {}
res.locals.user.defaultReturnUrl = req.user && req.user.defaultReturnUrl()
res.locals.user.username = req.user && req.user.username
next()
})
// global locals
app.locals.projectName = app.config.projectName
app.locals.copyrightYear = new Date().getFullYear()
app.locals.copyrightName = app.config.companyName
app.locals.cacheBreaker = 'br34k-01'
// setup passport
require('./passport')(app, passport)
// setup routes
require('./routes')(app, passport)
// api docs(需設定 SWAGGER_USER 與 SWAGGER_PASSWORD 環境變數才會啟用)
if (config.swaggerAuth.user && config.swaggerAuth.password) {
app.use(
'/api-docs',
basicAuth({ users: { [config.swaggerAuth.user]: config.swaggerAuth.password }, challenge: true }),
swaggerUi.serve,
swaggerUi.setup(swaggerSpec)
)
}
// setup react
app.get('*path', function (req, res) {
res.sendFile(path.join(__dirname, './build/index.html'))
})
// 全域錯誤攔截:防止 stack trace 或內部錯誤訊息外洩給客戶端
// 開發模式(NODE_ENV=development)下保留完整錯誤訊息以利 trace
app.use(function (err, req, res, next) { // eslint-disable-line no-unused-vars
const status = err.status || err.statusCode || 500
if (status >= 500) {
console.error(err)
}
const isDev = app.get('env') === 'development'
res.status(status).json({
errors: [{ message: isDev || status < 500 ? err.message : 'An error occurred' }]
})
})
// setup utilities
app.utility = {}
app.utility.sendmail = require('./util/sendmail')
app.utility.slugify = require('./util/slugify')
app.utility.workflow = require('./util/workflow')
app.utility.ODMService = require('./util/odm-service')
const shutdownTimeoutMs = 10000
let isShuttingDown = false
const startServer = async function () {
await mongoose.connect(config.mongodb.uri)
await new Promise(function (resolve, reject) {
app.server.once('error', reject)
app.server.listen(app.config.port, function () {
app.server.off('error', reject)
// and... we're live
console.log('Server is running on port ' + config.port)
resolve()
})
})
}
const closeServer = function () {
return new Promise(function (resolve, reject) {
app.server.close(function (err) {
if (err) {
reject(err)
return
}
resolve()
})
})
}
const shutdown = async function (signal) {
if (isShuttingDown) {
console.log('Shutdown already in progress, ignoring ' + signal)
return
}
isShuttingDown = true
console.log('Received ' + signal + ', shutting down gracefully')
const timeout = setTimeout(function () {
console.error('Graceful shutdown timed out after ' + shutdownTimeoutMs + 'ms')
process.exit(1)
}, shutdownTimeoutMs)
try {
await closeServer()
console.log('HTTP server closed')
await mongoose.disconnect()
console.log('MongoDB disconnected')
clearTimeout(timeout)
process.exit(0)
} catch (err) {
clearTimeout(timeout)
console.error('Failed to shutdown gracefully:', err)
process.exit(1)
}
}
process.on('SIGTERM', function () {
shutdown('SIGTERM')
})
process.on('SIGINT', function () {
shutdown('SIGINT')
})
startServer().catch(function (err) {
console.error('Failed to start server:', err)
process.exit(1)
})