dobo 2.17.0 → 2.18.0
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/extend/bajo/intl/en-US.json +1 -0
- package/extend/bajo/intl/id.json +1 -0
- package/extend/dobo/feature/dt.js +24 -6
- package/index.js +1 -1
- package/lib/factory/model/create-record.js +2 -2
- package/lib/factory/model/sanitize-body.js +24 -15
- package/lib/factory/model/sanitize-record.js +5 -0
- package/lib/factory/model/update-record.js +1 -1
- package/lib/factory/model/upsert-record.js +1 -1
- package/package.json +1 -1
- package/wiki/CHANGES.md +10 -0
|
@@ -149,6 +149,7 @@
|
|
|
149
149
|
"hardCapWarning%s%s": "Max records returned (%s rows) above the allowed threshold (%s rows)",
|
|
150
150
|
"maxPageError%s%s": "Page number (%s) above the allowed threshold (%s)",
|
|
151
151
|
"duplicateRefKeys%s%s": "Duplicate reference keys found in '%s' (%s)",
|
|
152
|
+
"sanitizeBodyError": "Error sanitizing body",
|
|
152
153
|
"field": {
|
|
153
154
|
"id": "ID",
|
|
154
155
|
"code": "Kode",
|
package/extend/bajo/intl/id.json
CHANGED
|
@@ -147,6 +147,7 @@
|
|
|
147
147
|
"hardCapWarning%s%s": "Maksimum data yang dihasilkan (%s baris) melampaui batas yang diijinkan (%s baris)",
|
|
148
148
|
"maxPageError%s%s": "Nomor halaman (%s) melampaui batas yang diijinkan (%s)",
|
|
149
149
|
"duplicateRefKeys%s%s": "Ditemukan kunci referensi duplikat di '%s' (%s)",
|
|
150
|
+
"sanitizeBodyError": "Kesalahan saat sanitasi body",
|
|
150
151
|
"field": {
|
|
151
152
|
"id": "ID",
|
|
152
153
|
"code": "Kode",
|
|
@@ -1,12 +1,30 @@
|
|
|
1
1
|
async function dt (opts = {}) {
|
|
2
2
|
opts.field = opts.field ?? 'dt'
|
|
3
|
+
opts.type = opts.type ??'datetime'
|
|
4
|
+
opts.formatInt = opts.formatInt ?? false
|
|
5
|
+
opts.formatValueInt = opts.formatValueInt ?? false
|
|
6
|
+
const prop = {
|
|
7
|
+
name: opts.field ?? 'dt',
|
|
8
|
+
type: opts.type,
|
|
9
|
+
required: opts.required ?? true,
|
|
10
|
+
index: opts.index ?? true
|
|
11
|
+
}
|
|
12
|
+
if (opts.type === 'integer') {
|
|
13
|
+
if (opts.formatInt) {
|
|
14
|
+
prop.format = async function (val, data, { req } = {}) {
|
|
15
|
+
const dt = new Date(data._orig[opts.field])
|
|
16
|
+
return req ? req.format(dt, 'datetime') : this.app.bajo.format(dt, 'datetime', { lang: req.lang })
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (opts.formatValueInt) {
|
|
20
|
+
prop.format = async function (val, data, { req } = {}) {
|
|
21
|
+
return new Date(data._orig[opts.field])
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
3
26
|
return {
|
|
4
|
-
properties: [
|
|
5
|
-
name: opts.field ?? 'dt',
|
|
6
|
-
type: 'datetime',
|
|
7
|
-
required: opts.required ?? true,
|
|
8
|
-
index: opts.index ?? true
|
|
9
|
-
}]
|
|
27
|
+
properties: [prop]
|
|
10
28
|
}
|
|
11
29
|
}
|
|
12
30
|
|
package/index.js
CHANGED
|
@@ -390,7 +390,7 @@ async function factory (pkgName) {
|
|
|
390
390
|
return parseInt(value) || null
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
sanitizeObject = (value, { strict = false } = {}) => {
|
|
393
|
+
sanitizeObject = (value, { strict = false, action, model } = {}) => {
|
|
394
394
|
const { isString } = this.app.lib._
|
|
395
395
|
let result = null
|
|
396
396
|
if (isString(value)) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getFilterAndOptions, execHook, execValidation, execModelHook, execDynHook, getSingleRef, handleReq } from './_util.js'
|
|
2
2
|
|
|
3
|
-
export const onlyTypes = ['datetime', 'date', 'time', 'timestamp']
|
|
3
|
+
export const onlyTypes = ['datetime', 'date', 'time', 'timestamp', 'array', 'object']
|
|
4
4
|
const action = 'createRecord'
|
|
5
5
|
|
|
6
6
|
async function createRecord (...args) {
|
|
@@ -14,7 +14,7 @@ async function createRecord (...args) {
|
|
|
14
14
|
const { options } = await getFilterAndOptions.call(this, null, opts, action)
|
|
15
15
|
const { truncateString, noResult, noBodySanitizer, noResultSanitizer, noValidation } = options
|
|
16
16
|
const extFields = get(options, 'validation.extFields', [])
|
|
17
|
-
const input = noBodySanitizer ? cloneDeep(body) : await this.sanitizeBody({ body, extFields, strict: true, truncateString, onlyTypes })
|
|
17
|
+
const input = noBodySanitizer ? cloneDeep(body) : await this.sanitizeBody({ body, extFields, strict: true, truncateString, onlyTypes, action })
|
|
18
18
|
await execHook.call(this, 'beforeCreateRecord', input, options)
|
|
19
19
|
await execModelHook.call(this, 'beforeCreateRecord', input, options)
|
|
20
20
|
await execDynHook.call(this, 'beforeCreateRecord', input, options)
|
|
@@ -21,7 +21,7 @@ async function sanitizeBody ({ body = {}, partial, strict, extFields = [], noDef
|
|
|
21
21
|
|
|
22
22
|
const sanitize = (name, type) => {
|
|
23
23
|
if (onlyTypes.length > 0 && !onlyTypes.includes(type)) return
|
|
24
|
-
if (['object', 'array'].includes(type)) result[name] = sanitizeObject(result[name], { strict })
|
|
24
|
+
if (['object', 'array'].includes(type)) result[name] = sanitizeObject(result[name], { strict, action, model: this.name })
|
|
25
25
|
else if (type === 'boolean') result[name] = sanitizeBoolean(result[name])
|
|
26
26
|
else if (['float', 'double'].includes(type)) result[name] = sanitizeFloat(result[name], { strict })
|
|
27
27
|
else if (['integer', 'smallint'].includes(type)) result[name] = sanitizeInt(result[name], { strict })
|
|
@@ -32,23 +32,32 @@ async function sanitizeBody ({ body = {}, partial, strict, extFields = [], noDef
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
const omitted = []
|
|
35
|
+
const details = []
|
|
35
36
|
for (const prop of [...this.properties, ...extFields]) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
try {
|
|
38
|
+
if (partial && !has(body, prop.name)) continue
|
|
39
|
+
result[prop.name] = body[prop.name]
|
|
40
|
+
if (body[prop.name] === null) continue
|
|
41
|
+
if (isSet(body[prop.name])) sanitize(prop.name, prop.type)
|
|
42
|
+
else {
|
|
43
|
+
if (isSet(prop.default) && !noDefault) {
|
|
44
|
+
result[prop.name] = prop.default
|
|
45
|
+
if (isString(prop.default) && prop.default.startsWith('handler:')) {
|
|
46
|
+
const [, ...args] = prop.default.split(':')
|
|
47
|
+
if (args.length > 0) result[prop.name] = await callHandler(args.join(':'))
|
|
48
|
+
} else sanitize(prop.name, prop.type)
|
|
49
|
+
}
|
|
47
50
|
}
|
|
51
|
+
if (truncateString && isSet(result[prop.name]) && ['string', 'text'].includes(prop.type)) result[prop.name] = result[prop.name].slice(0, prop.maxLength)
|
|
52
|
+
if (prop.name.endsWith('Id') && prop.type === 'string' && ['smallint', 'integer'].includes(this.driver.idField.type)) result[prop.name] = result[prop.name] + ''
|
|
53
|
+
if (body[prop.name] === undefined) omitted.push(prop.name)
|
|
54
|
+
} catch (err) {
|
|
55
|
+
details.push({ field: prop.name, error: err.message, value: body[prop.name], ext: { type: prop.type } })
|
|
48
56
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
}
|
|
58
|
+
if (details.length > 0) {
|
|
59
|
+
const payload = { details, statusCode: 422, code: 'BODY_SANITIZATION', model: this.name }
|
|
60
|
+
throw this.plugin.error('sanitizeBodyError', payload)
|
|
52
61
|
}
|
|
53
62
|
return omit(result, omitted)
|
|
54
63
|
}
|
|
@@ -29,6 +29,11 @@ async function sanitizeRecord (record = {}, opts = {}) {
|
|
|
29
29
|
const prop = this.getProperty(key)
|
|
30
30
|
if (!prop) continue
|
|
31
31
|
const value = ['object', 'array'].includes(prop.type) ? cloneDeep(newRecord[key]) : newRecord[key]
|
|
32
|
+
if (prop.formatValue && opts.retainOriginalValue) {
|
|
33
|
+
const value = ['object', 'array'].includes(prop.type) ? cloneDeep(newRecord._orig[key]) : newRecord._orig[key]
|
|
34
|
+
if (isFunction(prop.formatValue)) newRecord._orig[key] = await prop.formatValue.call(this, value, newRecord._orig, opts)
|
|
35
|
+
else if (isString(prop.formatValue)) newRecord._orig[key] = await callHandler(this.plugin, this, value, newRecord._orig, opts)
|
|
36
|
+
}
|
|
32
37
|
if (prop.format) {
|
|
33
38
|
if (isFunction(prop.format)) newRecord[key] = await prop.format.call(this, value, newRecord, opts)
|
|
34
39
|
else if (isString(prop.format)) newRecord[key] = await callHandler(this.plugin, this, value, newRecord, opts)
|
|
@@ -58,7 +58,7 @@ async function updateRecord (...args) {
|
|
|
58
58
|
const extFields = get(options, 'validation.extFields', [])
|
|
59
59
|
id = this.sanitizeId(id)
|
|
60
60
|
|
|
61
|
-
let input = noBodySanitizer ? cloneDeep(body) : await this.sanitizeBody({ body, extFields, strict: true, truncateString, partial, onlyTypes })
|
|
61
|
+
let input = noBodySanitizer ? cloneDeep(body) : await this.sanitizeBody({ body, extFields, strict: true, truncateString, partial, onlyTypes, action })
|
|
62
62
|
const immutables = this.properties.filter(prop => prop.immutable)
|
|
63
63
|
if (immutables.length > 0) input = omit(input, immutables)
|
|
64
64
|
delete input.id
|
|
@@ -10,7 +10,7 @@ async function native (body = {}, opts = {}) {
|
|
|
10
10
|
const { options } = await getFilterAndOptions.call(this, null, opts, action)
|
|
11
11
|
const { truncateString, noResult, noBodySanitizer, noResultSanitizer, noValidation } = options
|
|
12
12
|
const extFields = get(options, 'validation.extFields', [])
|
|
13
|
-
let input = noBodySanitizer ? cloneDeep(body) : await this.sanitizeBody({ body, extFields, strict: true, truncateString })
|
|
13
|
+
let input = noBodySanitizer ? cloneDeep(body) : await this.sanitizeBody({ body, extFields, strict: true, truncateString, action })
|
|
14
14
|
if (!noValidation) input = await execValidation.call(this, input, options)
|
|
15
15
|
await execHook.call(this, 'beforeUpsertRecord', input, options)
|
|
16
16
|
await execModelHook.call(this, 'beforeUpsertRecord', input, options)
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-04-16
|
|
4
|
+
|
|
5
|
+
- [2.18.0] Add parameter options to ```sanitizeObject()```
|
|
6
|
+
- [2.18.0] Rewrite ```dobo:dt``` feature to support customization
|
|
7
|
+
- [2.18.0] Bug fix in ```model.createRecord()```
|
|
8
|
+
- [2.18.0] Changes in ```model.sanitizeBody()``` to throw error with payload details
|
|
9
|
+
- [2.18.0] Changes in ```model.sanitizeRecord()``` to support ```options.retainOriginalValue```
|
|
10
|
+
- [2.18.0] Bug fix in ```model.updateRecord()```
|
|
11
|
+
- [2.18.0] Bug fix in ```model.upsertRecord()```
|
|
12
|
+
|
|
3
13
|
## 2026-04-13
|
|
4
14
|
|
|
5
15
|
- [2.17.0] Add ```{ strict }``` as parameter to ```sanitizeObject()```
|