miolo 3.0.0-beta.13 → 3.0.0-beta.131
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/bin/{dev.mjs → dev/dev.mjs} +19 -5
- package/bin/{dev_start.mjs → dev/dev_start.mjs} +3 -2
- package/bin/index.mjs +47 -33
- package/bin/prod-bin/create-bin.mjs +33 -0
- package/bin/prod-bin/run.mjs +35 -0
- package/bin/{build-client.mjs → prod-build/build-client.mjs} +17 -5
- package/bin/{build-server.mjs → prod-build/build-server.mjs} +12 -10
- package/bin/prod-run/pid.mjs +13 -0
- package/bin/{restart.mjs → prod-run/restart.mjs} +3 -4
- package/bin/prod-run/start.mjs +14 -0
- package/bin/{stop.mjs → prod-run/stop.mjs} +4 -3
- package/bin/util.mjs +2 -29
- package/package.json +25 -26
- package/{bin → src/config}/.env +17 -6
- package/src/config/defaults.mjs +443 -422
- package/src/config/env.mjs +52 -0
- package/src/config/index.mjs +17 -10
- package/src/config/util.mjs +41 -0
- package/src/engines/emailer/queue.mjs +3 -2
- package/src/engines/emailer/transporter.mjs +10 -9
- package/src/engines/http/index.mjs +1 -1
- package/src/engines/parser/Parser.mjs +25 -7
- package/src/engines/schema/index.mjs +40 -0
- package/src/index.mjs +3 -1
- package/src/middleware/auth/basic.mjs +5 -2
- package/src/middleware/auth/credentials/index.mjs +75 -43
- package/src/middleware/auth/credentials/session/index.mjs +7 -0
- package/src/middleware/auth/credentials/session/store.mjs +9 -0
- package/src/middleware/context/index.mjs +1 -1
- package/src/middleware/http/catcher.mjs +78 -2
- package/src/middleware/http/catcher.old.mjs +82 -0
- package/src/middleware/http/custom_blacklist.mjs +3 -1
- package/src/middleware/routes/catch_js_error.mjs +14 -2
- package/src/middleware/routes/router/crud/attachCrudRoutes.mjs +24 -12
- package/src/middleware/routes/router/crud/getCrudConfig.mjs +4 -6
- package/src/middleware/routes/router/defaults.mjs +1 -11
- package/src/middleware/routes/router/index.mjs +0 -1
- package/src/middleware/routes/router/queries/attachQueriesRoutes.mjs +70 -13
- package/src/middleware/routes/router/queries/getQueriesConfig.mjs +21 -21
- package/src/middleware/routes/router/utils.mjs +43 -25
- package/src/middleware/ssr/context.mjs +1 -1
- package/src/middleware/ssr/fallbackIndex.mjs +2 -8
- package/src/middleware/ssr/html.mjs +39 -31
- package/src/middleware/vite/devserver.mjs +22 -8
- package/src/server-cron.mjs +3 -4
- package/src/server-dev.mjs +3 -3
- package/src/server.mjs +3 -5
- package/bin/create-bin.mjs +0 -38
- package/bin/env.mjs +0 -39
- package/bin/prod_start.mjs +0 -9
- package/bin/start.mjs +0 -17
- package/src/engines/logger/verify.mjs +0 -22
|
@@ -3,17 +3,92 @@
|
|
|
3
3
|
* @param ctx
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import http from 'http'
|
|
6
|
+
// import http from 'http'
|
|
7
|
+
import statuses from 'statuses'
|
|
8
|
+
import util from 'node:util'
|
|
9
|
+
|
|
7
10
|
const _ONLY_WARN= [401, 403]
|
|
8
11
|
|
|
9
12
|
function init_catcher_middleware(app) {
|
|
10
13
|
const logger= app.context.miolo.logger
|
|
11
14
|
|
|
12
15
|
async function catcher_middleware(err) {
|
|
16
|
+
// don't do anything if there is no error.
|
|
17
|
+
// this allows you to pass `this.onerror`
|
|
18
|
+
// to node-style callbacks.
|
|
19
|
+
if (err == null) return
|
|
20
|
+
|
|
21
|
+
// Ignore this error
|
|
22
|
+
// Appearing since Koa 3.1 + koa-static 6.x + koa-send 7.x
|
|
23
|
+
if (err.code === 'ERR_STREAM_PREMATURE_CLOSE') return
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
// When dealing with cross-globals a normal `instanceof` check doesn't work properly.
|
|
27
|
+
// See https://github.com/koajs/koa/issues/1466
|
|
28
|
+
// We can probably remove it once jest fixes https://github.com/facebook/jest/issues/2549.
|
|
29
|
+
const isNativeError =
|
|
30
|
+
Object.prototype.toString.call(err) === '[object Error]' ||
|
|
31
|
+
err instanceof Error
|
|
32
|
+
if (!isNativeError) err = new Error(util.format('non-error thrown: %j', err))
|
|
33
|
+
|
|
34
|
+
let headerSent = false
|
|
35
|
+
if (this.headerSent || !this.writable) {
|
|
36
|
+
headerSent = err.headerSent = true
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// delegate
|
|
40
|
+
this.app.emit('error', err, this)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
let statusCode = err.status || err.statusCode
|
|
44
|
+
// default to 500
|
|
45
|
+
if (typeof statusCode !== 'number' || !statuses.message[statusCode]) statusCode = 500
|
|
46
|
+
|
|
47
|
+
// Log the error depending on the status
|
|
48
|
+
const ip = this.headers["x-real-ip"] || this.headers["x-orig-ip"] || this.ip || '127.0.0.1'
|
|
49
|
+
if ((_ONLY_WARN.indexOf(statusCode)>=0) || (err.message.indexOf('Premature close')>=0)) {
|
|
50
|
+
logger.warn(`[${ip}] ${this.method} ${this.url} - ${statusCode}: ${err.message}`)
|
|
51
|
+
} else {
|
|
52
|
+
logger.error(`[${ip}] ${this.method} ${this.url} - ${statusCode}: ${err.message}`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// nothing we can do here other
|
|
56
|
+
// than delegate to the app-level
|
|
57
|
+
// handler and log.
|
|
58
|
+
if (headerSent) {
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { res } = this
|
|
63
|
+
|
|
64
|
+
// first unset all headers
|
|
65
|
+
/* istanbul ignore else */
|
|
66
|
+
if (typeof res.getHeaderNames === 'function') {
|
|
67
|
+
res.getHeaderNames().forEach(name => res.removeHeader(name))
|
|
68
|
+
} else {
|
|
69
|
+
res._headers = {} // Node < 7.7
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// then set those specified
|
|
73
|
+
this.set(err.headers)
|
|
74
|
+
|
|
75
|
+
// force text/plain
|
|
76
|
+
this.type = 'text'
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
// respond
|
|
80
|
+
const code = statuses.message[statusCode]
|
|
81
|
+
const msg = err.expose ? err.message : code
|
|
82
|
+
this.status = err.status = statusCode
|
|
83
|
+
this.length = Buffer.byteLength(msg)
|
|
84
|
+
res.end(msg)
|
|
85
|
+
|
|
86
|
+
/*
|
|
13
87
|
if (!err) return
|
|
14
88
|
|
|
15
89
|
// wrap non-error object
|
|
16
|
-
|
|
90
|
+
const isNativeError = (Object.prototype.toString.call(err) === '[object Error]') || (err instanceof Error)
|
|
91
|
+
if (!isNativeError) {
|
|
17
92
|
let errMsg = err;
|
|
18
93
|
if (typeof err === 'object') {
|
|
19
94
|
try {
|
|
@@ -73,6 +148,7 @@ function init_catcher_middleware(app) {
|
|
|
73
148
|
this.body = JSON.stringify(this.body || '', null, 2)
|
|
74
149
|
this.length = Buffer.byteLength(this.body)
|
|
75
150
|
this.res.end(this.body)
|
|
151
|
+
*/
|
|
76
152
|
}
|
|
77
153
|
|
|
78
154
|
app.context.onerror = catcher_middleware
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware for catching errors thrown in routes
|
|
3
|
+
* @param ctx
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import http from 'http'
|
|
7
|
+
const _ONLY_WARN= [401, 403]
|
|
8
|
+
|
|
9
|
+
function init_catcher_middleware(app) {
|
|
10
|
+
const logger= app.context.miolo.logger
|
|
11
|
+
|
|
12
|
+
async function catcher_middleware(err) {
|
|
13
|
+
if (!err) return
|
|
14
|
+
|
|
15
|
+
// wrap non-error object
|
|
16
|
+
const isNativeError = (Object.prototype.toString.call(err) === '[object Error]') || (err instanceof Error)
|
|
17
|
+
if (!isNativeError) {
|
|
18
|
+
let errMsg = err;
|
|
19
|
+
if (typeof err === 'object') {
|
|
20
|
+
try {
|
|
21
|
+
errMsg = JSON.stringify(err);
|
|
22
|
+
// eslint-disable-next-line no-empty
|
|
23
|
+
} catch (e) {}
|
|
24
|
+
}
|
|
25
|
+
const newError = new Error('non-error thrown: ' + errMsg);
|
|
26
|
+
// err maybe an object, try to copy the name, message and stack to the new error instance
|
|
27
|
+
if (err) {
|
|
28
|
+
if (err.name) newError.name = err.name;
|
|
29
|
+
if (err.message) newError.message = err.message;
|
|
30
|
+
if (err.stack) newError.stack = err.stack;
|
|
31
|
+
if (err.status) newError.status = err.status;
|
|
32
|
+
if (err.headers) newError.headers = err.headers;
|
|
33
|
+
}
|
|
34
|
+
err = newError;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const headerSent = this.headerSent || !this.writable;
|
|
38
|
+
if (headerSent) err.headerSent = true;
|
|
39
|
+
|
|
40
|
+
// delegate
|
|
41
|
+
this.app.emit('error', err, this);
|
|
42
|
+
|
|
43
|
+
// ENOENT support
|
|
44
|
+
if (err.code === 'ENOENT') err.status = 404;
|
|
45
|
+
|
|
46
|
+
// Get HTML status code
|
|
47
|
+
let status = err.status || 400
|
|
48
|
+
const type = this.accepts(['text', 'json', 'html'])
|
|
49
|
+
if (!type) {
|
|
50
|
+
status = 406
|
|
51
|
+
err.message = 'Unsupported type'
|
|
52
|
+
} else if (typeof err.status !== 'number' || !http.STATUS_CODES[err.status]) {
|
|
53
|
+
err.status = 500
|
|
54
|
+
}
|
|
55
|
+
this.status = status
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
// Log the error depending on the status
|
|
59
|
+
const ip = this.headers["x-real-ip"] || this.headers["x-orig-ip"] || this.ip || '127.0.0.1'
|
|
60
|
+
if (_ONLY_WARN.indexOf(status)>=0) {
|
|
61
|
+
logger.warn(`[${ip}] ${this.method} ${this.url} - ${status}: ${err.message}`)
|
|
62
|
+
} else {
|
|
63
|
+
logger.error(err)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Nothing we can do here other than delegate to the app-level handler and log.
|
|
67
|
+
if (err.headerSent ) {
|
|
68
|
+
logger.warn('headers were already sent, returning early')
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Prepare response a bit
|
|
73
|
+
this.type = 'json'
|
|
74
|
+
this.body = JSON.stringify(this.body || '', null, 2)
|
|
75
|
+
this.length = Buffer.byteLength(this.body)
|
|
76
|
+
this.res.end(this.body)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
app.context.onerror = catcher_middleware
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export {init_catcher_middleware}
|
|
@@ -13,5 +13,7 @@ export const CUSTOM_BLACKLIST_IPS= [
|
|
|
13
13
|
'198.12.222.107', // GoDaddy.com, LLC (GODAD)
|
|
14
14
|
'212.128.118.10', // RIPE Network Coordination Centre (RIPE)
|
|
15
15
|
'103.218.240.46', // Asia Pacific Network Information Centre (APNIC)
|
|
16
|
-
'45.15.167.150'
|
|
16
|
+
'45.15.167.150', // ??
|
|
17
|
+
'165.227.173.41', // Frankfurt am Main, DE
|
|
18
|
+
'147.135.220.53', //
|
|
17
19
|
]
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import { blue, red, yellow } from 'tinguir'
|
|
2
2
|
import Router from '@koa/router'
|
|
3
3
|
|
|
4
|
+
const ERRORS_AS_WARNINGS = [
|
|
5
|
+
'Minified React error'
|
|
6
|
+
]
|
|
7
|
+
|
|
4
8
|
function init_route_catch_js_error(app, route) {
|
|
5
9
|
|
|
6
10
|
async function catch_js_error(ctx) {
|
|
7
|
-
|
|
11
|
+
let { error, warning, path, agent } = ctx.request.body
|
|
8
12
|
const logger = ctx.miolo.logger
|
|
9
13
|
|
|
14
|
+
if (error) {
|
|
15
|
+
ERRORS_AS_WARNINGS.map(e => {
|
|
16
|
+
if (error.msg.indexOf(e) >= 0) {
|
|
17
|
+
warning = error
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
}
|
|
21
|
+
|
|
10
22
|
if (warning) {
|
|
11
23
|
const msg=
|
|
12
24
|
`${yellow('[JS Warning]')} on ${blue(path)}: ${JSON.stringify(warning.msg)}\n` +
|
|
@@ -28,7 +40,7 @@ function init_route_catch_js_error(app, route) {
|
|
|
28
40
|
logger.error(msg)
|
|
29
41
|
}
|
|
30
42
|
|
|
31
|
-
ctx.body = {
|
|
43
|
+
ctx.body = {ok: true, data: 1}
|
|
32
44
|
}
|
|
33
45
|
|
|
34
46
|
const catch_js_error_router = new Router()
|
|
@@ -11,9 +11,10 @@ function attachCrudRoutes(router, crudConfigs, logger) {
|
|
|
11
11
|
|
|
12
12
|
const _pack_body_field = (data) => {
|
|
13
13
|
if (route.bodyField == undefined) {
|
|
14
|
-
return data
|
|
14
|
+
return {data}
|
|
15
15
|
}
|
|
16
16
|
return {
|
|
17
|
+
bodyField: route.bodyField,
|
|
17
18
|
[route.bodyField]: data
|
|
18
19
|
}
|
|
19
20
|
}
|
|
@@ -32,15 +33,22 @@ function attachCrudRoutes(router, crudConfigs, logger) {
|
|
|
32
33
|
ctx.miolo.logger.error(`[router] Unauthorized access. Throwing error ${auth.error_code}`)
|
|
33
34
|
ctx.throw(
|
|
34
35
|
auth.error_code,
|
|
35
|
-
'Unauthorized',
|
|
36
|
+
new Error('Unauthorized'),
|
|
36
37
|
{}
|
|
37
38
|
)
|
|
38
39
|
} else if (auth.action=='redirect') {
|
|
39
40
|
ctx.miolo.logger.warn(`[router] Unauthorized access. Redirecting to ${auth.redirect_url}`)
|
|
41
|
+
ctx.body= {
|
|
42
|
+
ok: false,
|
|
43
|
+
error: 'Unathorized'
|
|
44
|
+
}
|
|
40
45
|
ctx.redirect(auth.redirect_url)
|
|
41
46
|
} else {
|
|
42
47
|
ctx.miolo.logger.error(`[router] Crud path ${route.url} specified auth but no action`)
|
|
43
|
-
ctx.body= {
|
|
48
|
+
ctx.body= {
|
|
49
|
+
ok: false,
|
|
50
|
+
error: 'Unathorized'
|
|
51
|
+
}
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
54
|
return authenticated
|
|
@@ -57,12 +65,15 @@ function attachCrudRoutes(router, crudConfigs, logger) {
|
|
|
57
65
|
throw new Error(`[router] Could not get model for ${route.name}`)
|
|
58
66
|
}
|
|
59
67
|
|
|
60
|
-
let
|
|
68
|
+
let data = {}
|
|
61
69
|
try {
|
|
62
70
|
const authenticated = await _crud_auth_callback(ctx, op)
|
|
63
71
|
|
|
64
72
|
if (! authenticated) {
|
|
65
|
-
ctx.body= {
|
|
73
|
+
ctx.body= {
|
|
74
|
+
ok: false,
|
|
75
|
+
error: 'Unathorized'
|
|
76
|
+
}
|
|
66
77
|
return
|
|
67
78
|
}
|
|
68
79
|
|
|
@@ -72,7 +83,10 @@ function attachCrudRoutes(router, crudConfigs, logger) {
|
|
|
72
83
|
}
|
|
73
84
|
|
|
74
85
|
if (! goon) {
|
|
75
|
-
ctx.body= {
|
|
86
|
+
ctx.body= {
|
|
87
|
+
ok: false,
|
|
88
|
+
error: 'Not allowd'
|
|
89
|
+
}
|
|
76
90
|
return
|
|
77
91
|
}
|
|
78
92
|
|
|
@@ -87,19 +101,17 @@ function attachCrudRoutes(router, crudConfigs, logger) {
|
|
|
87
101
|
fieldNames,
|
|
88
102
|
}
|
|
89
103
|
|
|
90
|
-
|
|
104
|
+
data= await callback(model, uinfo)
|
|
91
105
|
|
|
92
106
|
if (route?.after) {
|
|
93
|
-
|
|
107
|
+
data= await route.after(ctx, data)
|
|
94
108
|
}
|
|
95
109
|
} catch(error) {
|
|
96
110
|
ctx.miolo.logger.error(`[router] Unexpected error on CRUD ${route.name}-${op}`)
|
|
97
111
|
ctx.miolo.logger.error(error)
|
|
98
112
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
ctx.body= result
|
|
113
|
+
|
|
114
|
+
ctx.body= {ok: true, ..._pack_body_field(data)}
|
|
103
115
|
}
|
|
104
116
|
|
|
105
117
|
const route_read = async (ctx) => {
|
|
@@ -3,8 +3,6 @@ import merge from 'deepmerge'
|
|
|
3
3
|
import {
|
|
4
4
|
DEFAULT_AUTH_USER,
|
|
5
5
|
DEFAULT_USE_USER_FIELDS,
|
|
6
|
-
DEFAULT_BEFORE_CALLBACK,
|
|
7
|
-
DEFAULT_AFTER_CALLBACK
|
|
8
6
|
} from "../defaults.mjs"
|
|
9
7
|
|
|
10
8
|
/**
|
|
@@ -14,7 +12,7 @@ import {
|
|
|
14
12
|
auth,
|
|
15
13
|
bodyField,
|
|
16
14
|
before: (ctx) => {return goon/!goon},
|
|
17
|
-
after : (ctx,
|
|
15
|
+
after : (ctx, data) => {return data},
|
|
18
16
|
|
|
19
17
|
routes: an array of tables config, where each config can be:
|
|
20
18
|
- a simple string with the table name
|
|
@@ -36,7 +34,7 @@ import {
|
|
|
36
34
|
auth,
|
|
37
35
|
bodyField,
|
|
38
36
|
before: (ctx) => {return goon/!goon},
|
|
39
|
-
after : (ctx,
|
|
37
|
+
after : (ctx, data) => {return data},
|
|
40
38
|
|
|
41
39
|
}
|
|
42
40
|
}]
|
|
@@ -69,8 +67,8 @@ const getCrudConfig = (config) => {
|
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
const comm_bodyField = crud?.bodyField || config?.bodyField
|
|
72
|
-
const comm_before = crud?.before || config?.before
|
|
73
|
-
const comm_after = crud?.after || config?.after
|
|
70
|
+
const comm_before = crud?.before || config?.before
|
|
71
|
+
const comm_after = crud?.after || config?.after
|
|
74
72
|
|
|
75
73
|
|
|
76
74
|
const comm_auth= merge.all([
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
const DEFAULT_BEFORE_CALLBACK = async (ctx) => {
|
|
2
|
-
return true
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
const DEFAULT_AFTER_CALLBACK = async (ctx, result) => {
|
|
6
|
-
return result
|
|
7
|
-
}
|
|
8
|
-
|
|
9
1
|
const DEFAULT_AUTH_USER = {
|
|
10
2
|
require: false, // true / false / 'read-only'
|
|
11
3
|
action: 'redirect', // 'error'
|
|
@@ -23,7 +15,5 @@ const DEFAULT_USE_USER_FIELDS = {
|
|
|
23
15
|
|
|
24
16
|
export {
|
|
25
17
|
DEFAULT_AUTH_USER,
|
|
26
|
-
DEFAULT_USE_USER_FIELDS
|
|
27
|
-
DEFAULT_BEFORE_CALLBACK,
|
|
28
|
-
DEFAULT_AFTER_CALLBACK
|
|
18
|
+
DEFAULT_USE_USER_FIELDS
|
|
29
19
|
}
|
|
@@ -4,7 +4,6 @@ import attachCrudRoutes from './crud/attachCrudRoutes.mjs'
|
|
|
4
4
|
import getQueriesConfig from './queries/getQueriesConfig.mjs'
|
|
5
5
|
import attachQueriesRoutes from './queries/attachQueriesRoutes.mjs'
|
|
6
6
|
|
|
7
|
-
|
|
8
7
|
function init_router(app, routes) {
|
|
9
8
|
const logger = app.context.miolo.logger
|
|
10
9
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Joi from 'joi'
|
|
2
|
+
import {
|
|
3
|
+
query_string_to_json,
|
|
4
|
+
ensure_response_is_ok_data} from '../utils.mjs'
|
|
2
5
|
|
|
3
6
|
function attachQueriesRoutes(router, queriesConfigs, logger) {
|
|
4
7
|
|
|
@@ -21,7 +24,6 @@ function attachQueriesRoutes(router, queriesConfigs, logger) {
|
|
|
21
24
|
logger.debug(`[router] Routing ${route.callback?.name || 'callback'} to ${route.method} ${url}${checkAuth ? ' (auth)' : ''}`)
|
|
22
25
|
|
|
23
26
|
const _route_auth_callback = async (ctx) => {
|
|
24
|
-
|
|
25
27
|
if (checkAuth) {
|
|
26
28
|
const authenticated= ctx?.session?.authenticated === true
|
|
27
29
|
if (!authenticated) {
|
|
@@ -29,7 +31,7 @@ function attachQueriesRoutes(router, queriesConfigs, logger) {
|
|
|
29
31
|
ctx.miolo.logger.error(`Unauthorized access. Throwing error ${routeAuth.error_code}`)
|
|
30
32
|
ctx.throw(
|
|
31
33
|
routeAuth.error_code,
|
|
32
|
-
'Unauthorized',
|
|
34
|
+
new Error('Unauthorized'),
|
|
33
35
|
{}
|
|
34
36
|
)
|
|
35
37
|
} else if (routeAuth.action=='redirect') {
|
|
@@ -37,7 +39,6 @@ function attachQueriesRoutes(router, queriesConfigs, logger) {
|
|
|
37
39
|
ctx.redirect(routeAuth.redirect_url)
|
|
38
40
|
} else {
|
|
39
41
|
ctx.miolo.logger.error(`Route path ${route.url} specified auth but no action`)
|
|
40
|
-
ctx.body= {}
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -48,7 +49,6 @@ function attachQueriesRoutes(router, queriesConfigs, logger) {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
const _route_callback = async (ctx) => {
|
|
51
|
-
let result = {}
|
|
52
52
|
try {
|
|
53
53
|
try {
|
|
54
54
|
if ((route.method == 'GET') && (!ctx.request?.body)) {
|
|
@@ -59,35 +59,92 @@ function attachQueriesRoutes(router, queriesConfigs, logger) {
|
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
} catch(
|
|
63
|
-
ctx.miolo.logger.error(`[router] Error while trying to qet query params for ${ctx.request.url}`)
|
|
62
|
+
} catch(error) {
|
|
63
|
+
ctx.miolo.logger.error(`[router] Error while trying to qet query params for ${ctx.request.url}: ${error?.message || '?'}`)
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const authenticated = await _route_auth_callback(ctx)
|
|
67
67
|
if (! authenticated) {
|
|
68
|
+
ctx.body= {
|
|
69
|
+
ok: false,
|
|
70
|
+
error: 'Not authorized'
|
|
71
|
+
}
|
|
68
72
|
return
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
let goon= true
|
|
72
76
|
if (route?.before) {
|
|
73
77
|
goon= await route.before(ctx)
|
|
78
|
+
|
|
79
|
+
if (! goon) {
|
|
80
|
+
ctx.body= {
|
|
81
|
+
ok: false,
|
|
82
|
+
error: `Route was aborted by a <before> callback (${route.before?.name})`
|
|
83
|
+
}
|
|
84
|
+
return
|
|
85
|
+
}
|
|
74
86
|
}
|
|
75
87
|
|
|
76
|
-
if (
|
|
77
|
-
|
|
88
|
+
if (route?.schema) {
|
|
89
|
+
// Check schema is actually a schema
|
|
90
|
+
if (! Joi.isSchema(route.schema)) {
|
|
91
|
+
ctx.miolo.logger.error(`[router] Expecting schema at ${url} but something else was found (${typeof route.schema})`)
|
|
92
|
+
ctx.body = {
|
|
93
|
+
ok: false,
|
|
94
|
+
error: 'Invalid schema'
|
|
95
|
+
}
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const v = route.schema.validate(ctx.request.body)
|
|
101
|
+
|
|
102
|
+
if (v?.error) {
|
|
103
|
+
ctx.miolo.logger.warn(`[router] Schema invalidated data for ${url}: ${v.error}\n${v.error.annotate(true)}`)
|
|
104
|
+
ctx.body = {
|
|
105
|
+
ok: false,
|
|
106
|
+
error: v.error.toString()
|
|
107
|
+
}
|
|
108
|
+
return
|
|
109
|
+
} else if (v?.value) {
|
|
110
|
+
ctx.miolo.logger.silly(`[router] Schema validated data for ${url} successfully`)
|
|
111
|
+
ctx.request.body = v.value
|
|
112
|
+
} else {
|
|
113
|
+
ctx.miolo.logger.warn(`[router] Schema returned unknown result for ${url}: ${JSON.stringify(v)}. Let's ignore it.`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
} catch(error) {
|
|
117
|
+
ctx.miolo.logger.error(`[router] Error validating schema at ${url}: ${error?.message || error}`)
|
|
118
|
+
ctx.body = {
|
|
119
|
+
ok: false,
|
|
120
|
+
error: error?.message || error
|
|
121
|
+
}
|
|
122
|
+
return
|
|
123
|
+
}
|
|
78
124
|
}
|
|
79
125
|
|
|
80
|
-
result= await route.callback(ctx)
|
|
126
|
+
const result = await route.callback(ctx, ctx.request.body)
|
|
127
|
+
|
|
128
|
+
if (! route.keep_body) {
|
|
129
|
+
if (ctx.body!==undefined) {
|
|
130
|
+
ctx.body = ensure_response_is_ok_data(ctx, ctx.body)
|
|
131
|
+
} else {
|
|
132
|
+
ctx.body = ensure_response_is_ok_data(ctx, result)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
81
135
|
|
|
82
136
|
if (route?.after) {
|
|
83
|
-
result= await route.after(ctx,
|
|
137
|
+
const result = await route.after(ctx, ctx.body)
|
|
138
|
+
ctx.body = ensure_response_is_ok_data(ctx, result)
|
|
84
139
|
}
|
|
85
140
|
} catch(error) {
|
|
86
141
|
ctx.miolo.logger.error(`[router] Unexpected error on Query ${route.callback?.name} at ${url}`)
|
|
87
142
|
ctx.miolo.logger.error(error)
|
|
143
|
+
ctx.body = {
|
|
144
|
+
ok: false,
|
|
145
|
+
error: error?.message || error
|
|
146
|
+
}
|
|
88
147
|
}
|
|
89
|
-
|
|
90
|
-
return result
|
|
91
148
|
}
|
|
92
149
|
|
|
93
150
|
const router_method = route.method.toLowerCase()
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import merge from 'deepmerge'
|
|
2
|
-
|
|
3
2
|
import {
|
|
4
|
-
DEFAULT_AUTH_USER
|
|
5
|
-
DEFAULT_BEFORE_CALLBACK,
|
|
6
|
-
DEFAULT_AFTER_CALLBACK
|
|
3
|
+
DEFAULT_AUTH_USER
|
|
7
4
|
} from "../defaults.mjs"
|
|
8
|
-
import { make_endpoint_from_fn } from '../utils.mjs'
|
|
9
5
|
|
|
10
6
|
/**
|
|
11
7
|
{
|
|
@@ -13,7 +9,7 @@ import { make_endpoint_from_fn } from '../utils.mjs'
|
|
|
13
9
|
|
|
14
10
|
auth,
|
|
15
11
|
before: (ctx) => {return goon/!goon},
|
|
16
|
-
after : (ctx,
|
|
12
|
+
after : (ctx, data) => {return data},
|
|
17
13
|
|
|
18
14
|
|
|
19
15
|
routes: [
|
|
@@ -21,13 +17,20 @@ import { make_endpoint_from_fn } from '../utils.mjs'
|
|
|
21
17
|
{
|
|
22
18
|
url: '/crud/todos/fake',
|
|
23
19
|
method: 'GET', // 'POST'
|
|
24
|
-
callback: async (ctx) => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
callback: async (ctx, params) => {
|
|
21
|
+
return {ok: true/false, data|error}
|
|
22
|
+
// or:
|
|
23
|
+
// return <anything>
|
|
24
|
+
// and milo will wrap into {ok: true, data: <anything>}
|
|
25
|
+
// or by yourself:
|
|
26
|
+
// ctx.body = {ok: true/false, data|error}
|
|
27
|
+
// (you may want {keep_body: true})
|
|
28
|
+
} ,
|
|
28
29
|
auth,
|
|
29
|
-
before: (ctx) => {return
|
|
30
|
-
after : (ctx,
|
|
30
|
+
before: async (ctx) => { return true/false },
|
|
31
|
+
after : async (ctx, data) => { return data },
|
|
32
|
+
schema: a Joi schema,
|
|
33
|
+
keep_body: false by default. If true, miolo wont wnsure ctx.body after callback.
|
|
31
34
|
}
|
|
32
35
|
]
|
|
33
36
|
}
|
|
@@ -61,8 +64,8 @@ const getQueriesConfig = (config) => {
|
|
|
61
64
|
return
|
|
62
65
|
}
|
|
63
66
|
|
|
64
|
-
const comm_before = instance?.before || config?.before
|
|
65
|
-
const comm_after = instance?.after || config?.after
|
|
67
|
+
const comm_before = instance?.before || config?.before
|
|
68
|
+
const comm_after = instance?.after || config?.after
|
|
66
69
|
|
|
67
70
|
const comm_auth= merge.all([
|
|
68
71
|
DEFAULT_AUTH_USER,
|
|
@@ -77,12 +80,7 @@ const getQueriesConfig = (config) => {
|
|
|
77
80
|
continue
|
|
78
81
|
}
|
|
79
82
|
|
|
80
|
-
let cb=
|
|
81
|
-
if ((! route.callback) && (! route.callback_fn)) {
|
|
82
|
-
continue
|
|
83
|
-
} else {
|
|
84
|
-
cb = route.callback || make_endpoint_from_fn(route.callback_fn)
|
|
85
|
-
}
|
|
83
|
+
let cb=route?.callback
|
|
86
84
|
|
|
87
85
|
const parsed_route= {
|
|
88
86
|
url: route.url,
|
|
@@ -95,7 +93,9 @@ const getQueriesConfig = (config) => {
|
|
|
95
93
|
]),
|
|
96
94
|
|
|
97
95
|
before: route?.before || comm_before,
|
|
98
|
-
after: route?.after || comm_after
|
|
96
|
+
after: route?.after || comm_after,
|
|
97
|
+
schema: route?.schema,
|
|
98
|
+
keep_body: route?.keep_body === true
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
parsed_routes.push(parsed_route)
|