nitro-web 0.0.1
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/.editorconfig +9 -0
- package/.eslintrc.json +86 -0
- package/_example/.env-example +16 -0
- package/_example/client/config.ts +5 -0
- package/_example/client/css/index.css +35 -0
- package/_example/client/fonts/Roboto-Bold.ttf +0 -0
- package/_example/client/fonts/Roboto-BoldItalic.ttf +0 -0
- package/_example/client/fonts/Roboto-Italic.ttf +0 -0
- package/_example/client/fonts/Roboto-Medium.ttf +0 -0
- package/_example/client/fonts/Roboto-MediumItalic.ttf +0 -0
- package/_example/client/fonts/Roboto-Regular.ttf +0 -0
- package/_example/client/fonts/inter-v13-latin-300.woff2 +0 -0
- package/_example/client/fonts/inter-v13-latin-500.woff2 +0 -0
- package/_example/client/fonts/inter-v13-latin-600.woff2 +0 -0
- package/_example/client/fonts/inter-v13-latin-700.woff2 +0 -0
- package/_example/client/fonts/inter-v13-latin-800.woff2 +0 -0
- package/_example/client/fonts/inter-v13-latin-900.woff2 +0 -0
- package/_example/client/fonts/inter-v13-latin-regular.woff2 +0 -0
- package/_example/client/imgs/android-chrome-512x512.png +0 -0
- package/_example/client/imgs/favicon.png +0 -0
- package/_example/client/imgs/icons/calendar.svg +3 -0
- package/_example/client/imgs/icons/email.svg +6 -0
- package/_example/client/imgs/icons/eye-open.svg +4 -0
- package/_example/client/imgs/icons/eye.svg +5 -0
- package/_example/client/imgs/icons/filter.svg +7 -0
- package/_example/client/imgs/icons/left-circle.svg +3 -0
- package/_example/client/imgs/icons/left.svg +3 -0
- package/_example/client/imgs/icons/line-options.svg +5 -0
- package/_example/client/imgs/icons/line.svg +3 -0
- package/_example/client/imgs/icons/person.svg +7 -0
- package/_example/client/imgs/icons/plus-circle.svg +5 -0
- package/_example/client/imgs/icons/plus.svg +5 -0
- package/_example/client/imgs/icons/right-circle.svg +3 -0
- package/_example/client/imgs/icons/right.svg +3 -0
- package/_example/client/imgs/icons/search.svg +3 -0
- package/_example/client/imgs/icons/shield.svg +6 -0
- package/_example/client/imgs/icons/tick-circle-solid.svg +8 -0
- package/_example/client/imgs/icons/tick-circle.svg +6 -0
- package/_example/client/imgs/icons/tick.svg +5 -0
- package/_example/client/imgs/icons/up2-small.svg +4 -0
- package/_example/client/imgs/icons/up2.svg +4 -0
- package/_example/client/imgs/icons/updown.svg +6 -0
- package/_example/client/imgs/icons/v-big-dark.svg +3 -0
- package/_example/client/imgs/icons/v-dark.svg +3 -0
- package/_example/client/imgs/icons/v.svg +3 -0
- package/_example/client/imgs/icons/v2-active.svg +6 -0
- package/_example/client/imgs/icons/x1.svg +4 -0
- package/_example/client/imgs/logo/logo-white.svg +20 -0
- package/_example/client/imgs/logo/logo.svg +20 -0
- package/_example/client/imgs/no-image.jpg +0 -0
- package/_example/client/imgs/user.jpg +0 -0
- package/_example/client/index.html +12 -0
- package/_example/client/index.ts +47 -0
- package/_example/components/auth.api.js +1 -0
- package/_example/components/index.tsx +225 -0
- package/_example/components/partials/layouts.tsx +5 -0
- package/_example/components/settings.api.js +1 -0
- package/_example/server/config.js +120 -0
- package/_example/server/email/welcome.html +27 -0
- package/_example/server/index.js +32 -0
- package/_example/tailwind.config.js +84 -0
- package/_example/tsconfig.json +32 -0
- package/_example/types.d.ts +7 -0
- package/_example/webpack.config.js +4 -0
- package/client/app.js +300 -0
- package/client/css/components.css +84 -0
- package/client/css/fonts.css +67 -0
- package/client/imgs/icons/calendar.svg +3 -0
- package/client/imgs/icons/email.svg +6 -0
- package/client/imgs/icons/eye-open.svg +4 -0
- package/client/imgs/icons/eye.svg +5 -0
- package/client/imgs/icons/filter.svg +7 -0
- package/client/imgs/icons/left-circle.svg +3 -0
- package/client/imgs/icons/left.svg +3 -0
- package/client/imgs/icons/line-options.svg +5 -0
- package/client/imgs/icons/line.svg +3 -0
- package/client/imgs/icons/person.svg +7 -0
- package/client/imgs/icons/plus-circle.svg +5 -0
- package/client/imgs/icons/plus.svg +5 -0
- package/client/imgs/icons/right-circle.svg +3 -0
- package/client/imgs/icons/right.svg +3 -0
- package/client/imgs/icons/search.svg +3 -0
- package/client/imgs/icons/shield.svg +6 -0
- package/client/imgs/icons/tick-circle-solid.svg +8 -0
- package/client/imgs/icons/tick-circle.svg +6 -0
- package/client/imgs/icons/tick.svg +5 -0
- package/client/imgs/icons/up2-small.svg +4 -0
- package/client/imgs/icons/up2.svg +4 -0
- package/client/imgs/icons/updown.svg +6 -0
- package/client/imgs/icons/v-big-dark.svg +3 -0
- package/client/imgs/icons/v-dark.svg +3 -0
- package/client/imgs/icons/v.svg +3 -0
- package/client/imgs/icons/v2-active.svg +6 -0
- package/client/imgs/icons/x1.svg +4 -0
- package/client.js +42 -0
- package/components/auth/auth.api.js +419 -0
- package/components/auth/reset.jsx +88 -0
- package/components/auth/signin.jsx +74 -0
- package/components/auth/signup.jsx +62 -0
- package/components/billing/stripe.api.js +267 -0
- package/components/partials/element/accordion.jsx +82 -0
- package/components/partials/element/avatar.jsx +28 -0
- package/components/partials/element/button.jsx +66 -0
- package/components/partials/element/dropdown.jsx +185 -0
- package/components/partials/element/initials.jsx +56 -0
- package/components/partials/element/message.jsx +124 -0
- package/components/partials/element/modal.jsx +229 -0
- package/components/partials/element/sidebar.jsx +166 -0
- package/components/partials/element/tooltip.jsx +146 -0
- package/components/partials/element/topbar.jsx +25 -0
- package/components/partials/form/checkbox.jsx +74 -0
- package/components/partials/form/drop-handler.jsx +62 -0
- package/components/partials/form/drop.jsx +125 -0
- package/components/partials/form/form-error.jsx +21 -0
- package/components/partials/form/input-color.jsx +77 -0
- package/components/partials/form/input-currency.jsx +133 -0
- package/components/partials/form/input-date.jsx +223 -0
- package/components/partials/form/input.jsx +131 -0
- package/components/partials/form/location.jsx +212 -0
- package/components/partials/form/select.jsx +369 -0
- package/components/partials/form/toggle.jsx +46 -0
- package/components/partials/is-first-render.js +15 -0
- package/components/partials/layout/layout1.jsx +32 -0
- package/components/partials/layout/layout2.jsx +47 -0
- package/components/partials/not-found.jsx +7 -0
- package/components/partials/styleguide.jsx +252 -0
- package/components/settings/settings-account.jsx +143 -0
- package/components/settings/settings-business.jsx +121 -0
- package/components/settings/settings-team--member.jsx +108 -0
- package/components/settings/settings-team.jsx +76 -0
- package/components/settings/settings.api.js +54 -0
- package/package.json +175 -0
- package/readme.md +43 -0
- package/server/email/index.js +192 -0
- package/server/email/partials/email.css +153 -0
- package/server/email/partials/layout1.swig +92 -0
- package/server/email/partials/line.swig +8 -0
- package/server/email/partials/vert-10.swig +8 -0
- package/server/email/partials/vert-15.swig +8 -0
- package/server/email/partials/vert-20.swig +8 -0
- package/server/email/partials/vert-25.swig +8 -0
- package/server/email/partials/vert-30.swig +8 -0
- package/server/email/partials/vert-35.swig +8 -0
- package/server/email/partials/vert-50.swig +8 -0
- package/server/email/reset-password.html +21 -0
- package/server/email/welcome.html +21 -0
- package/server/models/company.js +76 -0
- package/server/models/user.js +45 -0
- package/server/router.js +355 -0
- package/server.js +20 -0
- package/util.js +1145 -0
- package/webpack.config.js +302 -0
package/server/router.js
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path, { dirname } from 'path'
|
|
3
|
+
import http from 'http'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
import compression from 'compression'
|
|
6
|
+
import expressFileUpload from 'express-fileupload'
|
|
7
|
+
import express from 'express'
|
|
8
|
+
import bodyParser from 'body-parser'
|
|
9
|
+
import sortRouteAddressesNodeps from 'sort-route-addresses-nodeps'
|
|
10
|
+
|
|
11
|
+
import { sendEmail } from './email/index.js'
|
|
12
|
+
import * as util from '../util.js'
|
|
13
|
+
|
|
14
|
+
const _dirname = dirname(fileURLToPath(import.meta.url)) + '/'
|
|
15
|
+
|
|
16
|
+
export async function setupRouter (config) {
|
|
17
|
+
const { componentsDir, distDir, emailTemplateDir, env, middleware, version } = config
|
|
18
|
+
const expressApp = express()
|
|
19
|
+
const server = http.createServer(expressApp)
|
|
20
|
+
const apiRoutes = {}
|
|
21
|
+
const controllers = {}
|
|
22
|
+
const allMiddleware = { ...defaultMiddleware, ...(middleware || {}) }
|
|
23
|
+
|
|
24
|
+
if (!componentsDir) {
|
|
25
|
+
throw new Error('setupRouter: `config.componentsDir` missing')
|
|
26
|
+
} else if (!env) {
|
|
27
|
+
throw new Error('setupRouter: `config.env` missing')
|
|
28
|
+
} else if (!emailTemplateDir) {
|
|
29
|
+
throw new Error('setupRouter: `config.emailTemplateDir` missing')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Extend request/response with our custom error responses
|
|
33
|
+
setupErrorResponses(expressApp)
|
|
34
|
+
|
|
35
|
+
// Extend request with version
|
|
36
|
+
expressApp.use((req, res, next) => {
|
|
37
|
+
req.version = version
|
|
38
|
+
next()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
// Load in API routes & controllers
|
|
42
|
+
let filepaths = getFiles(componentsDir, /\.api\.js$/)
|
|
43
|
+
// console.log(filepaths, componentsDir)
|
|
44
|
+
for (let filepath of filepaths) {
|
|
45
|
+
let file = (await import(filepath)).default
|
|
46
|
+
let name = filepath.replace(/^.*[\\\/]|\.api\.js$/g, '') // eslint-disable-line
|
|
47
|
+
controllers[name] = file
|
|
48
|
+
|
|
49
|
+
if (file.setup) file.setup(allMiddleware, config)
|
|
50
|
+
if (file.routes) {
|
|
51
|
+
util.each(file.routes, (_middleware, key) => {
|
|
52
|
+
apiRoutes[key] = {
|
|
53
|
+
middleware: util.toArray(_middleware),
|
|
54
|
+
filename: name,
|
|
55
|
+
path: key.replace(/^[a-z]+\s+/, ''),
|
|
56
|
+
verb: key.match(/^[a-z]+/)? key.match(/^[a-z]+/)[0] : 'get',
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Register the base middleware
|
|
63
|
+
for (let name of allMiddleware.order) {
|
|
64
|
+
if (name == 'loadAssets') {
|
|
65
|
+
expressApp.use('/favicon.png', express.static(distDir + 'favicon.png', { maxage: '7d' }))
|
|
66
|
+
expressApp.use('/assets', compression()) // gzip
|
|
67
|
+
expressApp.use('/assets', express.static(distDir + 'assets/', { maxage: '365d' }))
|
|
68
|
+
} else if (!allMiddleware[name]) {
|
|
69
|
+
continue
|
|
70
|
+
} else {
|
|
71
|
+
expressApp.use(allMiddleware[name])
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Register the API routes
|
|
76
|
+
expressApp.use('/api', compression()) // gzip
|
|
77
|
+
for (let key of sortRouteAddressesNodeps(Object.keys(apiRoutes))) {
|
|
78
|
+
let route = apiRoutes[key]
|
|
79
|
+
if (!route.verb.match(/get|post|put|delete/)) throw Error(`The API route verb '${route.verb}' is invalid`)
|
|
80
|
+
route.middleware = route.middleware
|
|
81
|
+
.map((o, i) => resolveMiddleware(controllers, allMiddleware, route, o, i+1==route.middleware.length))
|
|
82
|
+
.filter(o => o)
|
|
83
|
+
// express uses path-to-regex for URL interpretation
|
|
84
|
+
// console.log(route.verb, route.path, route.middleware)
|
|
85
|
+
expressApp[route.verb](route.path, [ ...route.middleware])
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// If the index file is missing that means webpack-dev-server is being used which
|
|
89
|
+
// stores the bundled files in memory
|
|
90
|
+
let indexExists = !!fs.existsSync(distDir + 'index.html')
|
|
91
|
+
|
|
92
|
+
// Register email routes for development
|
|
93
|
+
// E.g. http://localhost:3001/email/welcome
|
|
94
|
+
if (env == 'development') {
|
|
95
|
+
expressApp.get('/email/partials/email.css', (req, res) => {
|
|
96
|
+
// first check if there is a custom email.css in the emailTemplateDir
|
|
97
|
+
// if not, return the default nitro email.css
|
|
98
|
+
console.log(_dirname)
|
|
99
|
+
if (fs.existsSync(emailTemplateDir + '/partials/email.css')) res.sendFile(emailTemplateDir + '/partials/email.css')
|
|
100
|
+
else res.sendFile(_dirname + 'email/partials/email.css')
|
|
101
|
+
})
|
|
102
|
+
expressApp.get('/email/:name', async (req, res) => {
|
|
103
|
+
try {
|
|
104
|
+
const html = await sendEmail({
|
|
105
|
+
config: config,
|
|
106
|
+
subject: 'Development email',
|
|
107
|
+
template: req.params.name,
|
|
108
|
+
test: true,
|
|
109
|
+
skipCssInline: true,
|
|
110
|
+
to: 'Ricky<test@gmail.com>',
|
|
111
|
+
})
|
|
112
|
+
res.send(html)
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.error(e)
|
|
115
|
+
res.error(e.message)
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Ping, pong, useful for webpack
|
|
121
|
+
expressApp.get('/ping', (req, res) => {
|
|
122
|
+
res.send('pong')
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// Catch all remaining routes, i.e 404
|
|
126
|
+
expressApp.get('*', (req, res) => {
|
|
127
|
+
if (indexExists) res.sendFile(distDir + 'index.html')
|
|
128
|
+
else { res.status(404); res.notFound() }
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
return server
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function setupErrorResponses (expressApp) {
|
|
135
|
+
/**
|
|
136
|
+
* Extend the express response object with custom formatted error responses
|
|
137
|
+
* @param {object} express - expressApp to extend
|
|
138
|
+
*/
|
|
139
|
+
|
|
140
|
+
Object.assign(expressApp.response, {
|
|
141
|
+
error: function(a, b) { error.call(this, a, b, 400) },
|
|
142
|
+
unauthorized: function(a, b) { error.call(this, a, b, 401) },
|
|
143
|
+
forbidden: function(a, b) { error.call(this, a, b, 403) },
|
|
144
|
+
notFound: function(a, b) { error.call(this, a, b, 404) },
|
|
145
|
+
serverError: function(a, b) { error.call(this, a, b, 500) },
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
function duplicateKeyIndexAndValue(error) {
|
|
149
|
+
// https://github.com/Automattic/mongoose/issues/2129#issuecomment-280507821
|
|
150
|
+
// E.g. E11000 duplicate key error collection: anamata-production.person index:
|
|
151
|
+
// email_1 dup key: { email: "person1@gmail.com" }
|
|
152
|
+
let regex = /index: (?:.*\.)?\$?(?:([_a-z0-9]*)(?:_\d*)|([_a-z0-9]*))\s*dup key/i
|
|
153
|
+
let match = error.message.match(regex)
|
|
154
|
+
let index = match[1] || match[2]
|
|
155
|
+
let value = (error.message.match(/.*{.*?: (.*) }/i)[1]||'').replace(/"/g, '')
|
|
156
|
+
return [index, value]
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function error(error, detail, status) {
|
|
160
|
+
/**
|
|
161
|
+
* Returns a formatted error
|
|
162
|
+
* @this = res
|
|
163
|
+
* @param {string | Error | Error[]} error - { code, title, detail }, or title
|
|
164
|
+
* @param {string} detail - used when error is a string
|
|
165
|
+
* @param {number} status
|
|
166
|
+
*/
|
|
167
|
+
|
|
168
|
+
const res = this
|
|
169
|
+
const req = this.req
|
|
170
|
+
let errors = []
|
|
171
|
+
let _detail
|
|
172
|
+
|
|
173
|
+
status = parseInt(error && error.status || status) // parseInt until monastery removes or udpates status?
|
|
174
|
+
res.status(status)
|
|
175
|
+
|
|
176
|
+
// Default detail
|
|
177
|
+
if (status == 400) _detail = 'Bad request made.'
|
|
178
|
+
else if (status == 401) _detail = 'You are unauthorised to make this request.'
|
|
179
|
+
else if (status == 403) _detail = 'You are unauthorised to make this request.'
|
|
180
|
+
else if (status == 404) _detail = 'Sorry, nothing found here.'
|
|
181
|
+
else if (status == 500) _detail = 'Internal server error, please contact the admin.'
|
|
182
|
+
|
|
183
|
+
// Single error string
|
|
184
|
+
if (util.isString(error) || !error) {
|
|
185
|
+
if (detail) errors = [{ title: error, detail: detail }]
|
|
186
|
+
else errors = [{ detail: error || _detail }]
|
|
187
|
+
|
|
188
|
+
// Mongo error
|
|
189
|
+
} else if (util.isObject(error) && (error.name||'').match(/Mongo|BulkWriteError/)) {
|
|
190
|
+
if (error.code == 11000) {
|
|
191
|
+
let [name] = duplicateKeyIndexAndValue(error)
|
|
192
|
+
if (name == 'email') errors = [{ title: 'email', detail: 'That email is already linked to an account.' }]
|
|
193
|
+
else errors = [{ title: name, detail: `Cannot insert duplicate values for "${name}".` }]
|
|
194
|
+
} else {
|
|
195
|
+
errors = [{ title: 'mongo', detail: error.message }]
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Stripe error object
|
|
199
|
+
} else if (error instanceof Error && error.type?.match(/Stripe/)) {
|
|
200
|
+
errors = [{ title: 'error', detail: 'Stripe: ' + error.message }]
|
|
201
|
+
|
|
202
|
+
// Error object
|
|
203
|
+
} else if (error instanceof Error) {
|
|
204
|
+
if (error.response) console.log('Error:', error.response.data)
|
|
205
|
+
else console.error(error) // and stack
|
|
206
|
+
errors = [{ title: 'error', detail: error.message }]
|
|
207
|
+
|
|
208
|
+
// Mutliple errors passed
|
|
209
|
+
} else if (util.isObject(error) || util.isArray(error)) {
|
|
210
|
+
errors = error.errors? error.errors : util.toArray(error)
|
|
211
|
+
for (let o of errors) {
|
|
212
|
+
// detail can be an error object
|
|
213
|
+
if (o.detail instanceof Error) {
|
|
214
|
+
console.error(o.detail) // and stack
|
|
215
|
+
o.detail = o.detail.message
|
|
216
|
+
}
|
|
217
|
+
// Remove _ prefixed keys
|
|
218
|
+
for (let key in o) {
|
|
219
|
+
if (o.hasOwnProperty(key) && key.match(/^_/)) delete o[key]
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Invalid data
|
|
224
|
+
} else {
|
|
225
|
+
console.error('Invalid data parsed into response()')
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Add status to all errors.
|
|
229
|
+
for (let o of errors) {
|
|
230
|
+
if (!o.status) o.status = status
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Log error
|
|
234
|
+
let type = status == 500 ? 'error' : 'log'
|
|
235
|
+
console[type]('Sending ' + status + ' response: \n', errors)
|
|
236
|
+
|
|
237
|
+
// Display error json/html
|
|
238
|
+
if (req.json) res.json({ errors: errors })
|
|
239
|
+
else res.send('<p>' + errors.map(e => e.detail).join('<br>') + '</p>')
|
|
240
|
+
return new Error({ errors: errors })
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function getFiles (dir, regexp) {
|
|
245
|
+
/**
|
|
246
|
+
* Recursivaly retreive all files
|
|
247
|
+
* @param {string} dir - directory to search (IS NOW FULL PATH)
|
|
248
|
+
* @return [path, ..]
|
|
249
|
+
*/
|
|
250
|
+
let paths = []
|
|
251
|
+
// let dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
252
|
+
// if (dir.match(/^\./)) dir = path.join(dirname, dir)
|
|
253
|
+
|
|
254
|
+
for (let filename of fs.readdirSync(dir)) {
|
|
255
|
+
let filepath = path.join(dir, '/', filename)
|
|
256
|
+
let stat = fs.statSync(filepath)
|
|
257
|
+
if (stat && stat.isDirectory()) {
|
|
258
|
+
paths = paths.concat(getFiles(filepath, regexp))
|
|
259
|
+
} else if (filepath.match(regexp)) {
|
|
260
|
+
paths.push(filepath)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return paths
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function resolveMiddleware (controllers, middleware, route, item, last) {
|
|
267
|
+
/**
|
|
268
|
+
* Resolves a placeholder string into a function
|
|
269
|
+
* @param {object} route
|
|
270
|
+
* @param {fn|string} item
|
|
271
|
+
* @param {boolean} last - last item
|
|
272
|
+
* @return function(req, res){..}
|
|
273
|
+
*/
|
|
274
|
+
if (util.isFunction(item)) {
|
|
275
|
+
return item
|
|
276
|
+
|
|
277
|
+
} else if (!util.isString(item)) {
|
|
278
|
+
console.error('Invalid middleware item:', item)
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
} else if (item.match(/\./) || last) { // e.g. user.read
|
|
282
|
+
let arr = item.split('.')
|
|
283
|
+
let controllerGroup = controllers[arr[1]? arr[0] : route.filename]
|
|
284
|
+
let controllerName = arr[1] || arr[0]
|
|
285
|
+
if (controllerGroup && controllerGroup[controllerName]) {
|
|
286
|
+
if (middleware.endpointSwitcher && controllerGroup[controllerName + 'Desktop']) {
|
|
287
|
+
return middleware.endpointSwitcher.bind(
|
|
288
|
+
null,
|
|
289
|
+
controllerGroup[controllerName].bind(controllerGroup),
|
|
290
|
+
controllerGroup[controllerName + 'Desktop'].bind(controllerGroup)
|
|
291
|
+
)
|
|
292
|
+
} else {
|
|
293
|
+
return controllerGroup[controllerName].bind(controllerGroup)
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
console.error(`The controller '${item}' defined in '${route.filename}.api' doesn't exist.`)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
} else if (middleware[item]) {
|
|
300
|
+
return middleware[item]
|
|
301
|
+
|
|
302
|
+
} else {
|
|
303
|
+
console.error(`The middleware '${item}' defined in '${route.filename}.api' doesn't exist.`)
|
|
304
|
+
return
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const defaultMiddleware = {
|
|
309
|
+
beforeAPIRoute: (req, res, next) => {
|
|
310
|
+
res.set('version', req.version)
|
|
311
|
+
next()
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
modifyRequest: (req, res, next) => {
|
|
315
|
+
// Handy boolean denoting that the request wants JSON returned
|
|
316
|
+
req.json = req.xhr || req.accepts(['html', 'json']) == 'json'
|
|
317
|
+
next()
|
|
318
|
+
},
|
|
319
|
+
|
|
320
|
+
// parse application/x-www-form-urlencoded (rawbody for stripe webhooks)
|
|
321
|
+
parseUrlEncoded: bodyParser.urlencoded({ extended: false, verify: (req, res, buf, encoding) => {
|
|
322
|
+
if (!buf || !buf.length) return
|
|
323
|
+
req.rawHeaders = req.headers
|
|
324
|
+
req.rawBody = buf.toString(encoding || 'utf8')
|
|
325
|
+
}}),
|
|
326
|
+
|
|
327
|
+
// parse application/json
|
|
328
|
+
parseJson: bodyParser.json({
|
|
329
|
+
verify: (req, res, buf, encoding) => {
|
|
330
|
+
if (!buf || !buf.length) return
|
|
331
|
+
req.rawHeaders = req.headers
|
|
332
|
+
req.rawBody = buf.toString(encoding || 'utf8')
|
|
333
|
+
},
|
|
334
|
+
limit: '40mb',
|
|
335
|
+
}),
|
|
336
|
+
|
|
337
|
+
// parse multipart/form-data
|
|
338
|
+
parseFile: (req, res, next) => {
|
|
339
|
+
req.files = {} // always ensure req.files is defined
|
|
340
|
+
// console.time('upload middleware')
|
|
341
|
+
expressFileUpload({
|
|
342
|
+
limits: { fileSize: 1000 * 1000 * 90, files: 10 }, // 90mb
|
|
343
|
+
})(req, res, () => { /*console.timeEnd('upload middleware'); */next() })
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
order: [
|
|
347
|
+
// Express middleware runtime order
|
|
348
|
+
'loadAssets',
|
|
349
|
+
'modifyRequest',
|
|
350
|
+
'parseUrlEncoded',
|
|
351
|
+
'parseJson',
|
|
352
|
+
'parseFile',
|
|
353
|
+
'beforeAPIRoute',
|
|
354
|
+
],
|
|
355
|
+
}
|
package/server.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Export models
|
|
2
|
+
import userModel from './server/models/user.js'
|
|
3
|
+
import companyModel from './server/models/company.js'
|
|
4
|
+
async function setupDefaultModels(db) {
|
|
5
|
+
// Load default nitro models, if they don't exist already
|
|
6
|
+
if (!db.models.user) await db.model('user', userModel)
|
|
7
|
+
if (!db.models.company) await db.model('company', companyModel)
|
|
8
|
+
}
|
|
9
|
+
export { userModel, companyModel, setupDefaultModels }
|
|
10
|
+
|
|
11
|
+
// Export router
|
|
12
|
+
export { setupRouter } from './server/router.js'
|
|
13
|
+
|
|
14
|
+
// Export email util
|
|
15
|
+
export { sendEmail } from './server/email/index.js'
|
|
16
|
+
|
|
17
|
+
// Export api default controllers
|
|
18
|
+
export { default as auth } from './components/auth/auth.api.js'
|
|
19
|
+
export { default as settings } from './components/settings/settings.api.js'
|
|
20
|
+
export { default as stripe } from './components/billing/stripe.api.js'
|