waibu-db 1.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/LICENSE +21 -0
- package/README.md +23 -0
- package/bajo/.alias +1 -0
- package/bajo/config.json +17 -0
- package/bajo/method/admin-menu.js +46 -0
- package/bajo/method/get-params.js +18 -0
- package/bajo/method/get-schema-ext.js +133 -0
- package/bajo/method/method-map.js +9 -0
- package/bajo/method/record/create.js +12 -0
- package/bajo/method/record/find-one.js +20 -0
- package/bajo/method/record/find.js +26 -0
- package/bajo/method/record/get.js +14 -0
- package/bajo/method/record/remove.js +10 -0
- package/bajo/method/record/update.js +12 -0
- package/bajo/method/stat/aggregate.js +13 -0
- package/bajo/method/stat/histogram.js +13 -0
- package/bajoI18N/resource/en-US.json +18 -0
- package/bajoI18N/resource/id.json +40 -0
- package/lib/crud/add-handler.js +39 -0
- package/lib/crud/all-handler.js +31 -0
- package/lib/crud/delete-handler.js +37 -0
- package/lib/crud/details-handler.js +18 -0
- package/lib/crud/edit-handler.js +33 -0
- package/lib/crud/export-handler.js +15 -0
- package/lib/crud/helper/add-ons-handler.js +46 -0
- package/lib/crud/helper/build-params.js +14 -0
- package/lib/crud/list-handler.js +27 -0
- package/lib/prep-crud.js +17 -0
- package/package.json +30 -0
- package/waibuBootstrap/theme/component/btn-add.js +15 -0
- package/waibuBootstrap/theme/component/btn-back.js +13 -0
- package/waibuBootstrap/theme/component/btn-clone.js +30 -0
- package/waibuBootstrap/theme/component/btn-columns.js +45 -0
- package/waibuBootstrap/theme/component/btn-delete.js +43 -0
- package/waibuBootstrap/theme/component/btn-details.js +32 -0
- package/waibuBootstrap/theme/component/btn-edit.js +56 -0
- package/waibuBootstrap/theme/component/btn-export.js +129 -0
- package/waibuBootstrap/theme/component/echarts.js +58 -0
- package/waibuBootstrap/theme/component/pagination.js +60 -0
- package/waibuBootstrap/theme/component/query.js +137 -0
- package/waibuBootstrap/theme/component/recs-info.js +50 -0
- package/waibuBootstrap/theme/component/table.js +161 -0
- package/waibuMpa/extend/waibuAdmin/route/@model/@action.js +14 -0
- package/waibuMpa/partial/crud/_addons.html +5 -0
- package/waibuMpa/partial/crud/_form.html +12 -0
- package/waibuMpa/partial/crud/add-handler.html +19 -0
- package/waibuMpa/partial/crud/details-handler.html +21 -0
- package/waibuMpa/partial/crud/echarts-window.html +9 -0
- package/waibuMpa/partial/crud/edit-handler.html +19 -0
- package/waibuMpa/partial/crud/list-handler.html +26 -0
- package/waibuMpa/sumba/route/export/@action.js +14 -0
- package/waibuMpa/template/crud/add.html +2 -0
- package/waibuMpa/template/crud/details.html +2 -0
- package/waibuMpa/template/crud/edit.html +2 -0
- package/waibuMpa/template/crud/list.html +2 -0
- package/waibuMpa/template/disabled.html +8 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Ardhi Lukianto
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# waibu-db
|
|
2
|
+
|
|
3
|
+
Plugin name: **waibuDemo**, alias: **wdb**
|
|
4
|
+
|
|
5
|
+
 
|
|
6
|
+
|
|
7
|
+
> <br />**Attention**: I do NOT accept any pull request at the moment, thanks!<br /><br />
|
|
8
|
+
|
|
9
|
+
DB Helper for [Waibu MPA](https://github.com/ardhi/waibu-mpa)
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Goto your ```<bajo-base-dir>``` and type:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
$ npm install waibu-db
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Now open your ```<bajo-data-dir>/config/.plugins``` and put ```waibu-db``` in it
|
|
20
|
+
|
|
21
|
+
## License
|
|
22
|
+
|
|
23
|
+
[MIT](LICENSE)
|
package/bajo/.alias
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
wdb
|
package/bajo/config.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"waibu": {
|
|
3
|
+
"prefix": "db",
|
|
4
|
+
"title": "Model Database"
|
|
5
|
+
},
|
|
6
|
+
"waibuAdmin": {
|
|
7
|
+
"menuHandler": "waibuDb:adminMenu"
|
|
8
|
+
},
|
|
9
|
+
"waibuMpa": {
|
|
10
|
+
"icon": "database"
|
|
11
|
+
},
|
|
12
|
+
"dbModel": {
|
|
13
|
+
"count": false,
|
|
14
|
+
"patchEnabled": false
|
|
15
|
+
},
|
|
16
|
+
"dependencies": ["waibu", "dobo"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
function modelsMenu (req) {
|
|
2
|
+
const { getPluginPrefix } = this.app.waibu
|
|
3
|
+
const { titleize, pascalCase } = this.app.bajo
|
|
4
|
+
const { getAppTitle } = this.app.waibuMpa
|
|
5
|
+
const { map, pick, groupBy, keys, kebabCase, filter, get } = this.app.bajo.lib._
|
|
6
|
+
|
|
7
|
+
const prefix = getPluginPrefix(this.name)
|
|
8
|
+
const schemas = filter(this.app.dobo.schemas, s => {
|
|
9
|
+
const byModelFind = !s.disabled.includes('find')
|
|
10
|
+
let modelDisabled = get(this, `app.${s.ns}.config.waibuAdmin.modelDisabled`)
|
|
11
|
+
if (modelDisabled) {
|
|
12
|
+
const allModels = map(filter(this.app.dobo.schemas, { ns: s.ns }), 'name')
|
|
13
|
+
if (modelDisabled === 'all') modelDisabled = allModels
|
|
14
|
+
else modelDisabled = map(modelDisabled, m => pascalCase(`${this.app[s.ns].alias} ${m}`))
|
|
15
|
+
} else modelDisabled = []
|
|
16
|
+
const byDbDisabled = !modelDisabled.includes(s.name)
|
|
17
|
+
return byModelFind && byDbDisabled
|
|
18
|
+
})
|
|
19
|
+
const omenu = groupBy(map(schemas, s => {
|
|
20
|
+
const item = pick(s, ['name', 'ns'])
|
|
21
|
+
item.nsTitle = getAppTitle(s.ns)
|
|
22
|
+
return item
|
|
23
|
+
}), 'nsTitle')
|
|
24
|
+
const menu = []
|
|
25
|
+
for (const k of keys(omenu).sort()) {
|
|
26
|
+
const items = omenu[k]
|
|
27
|
+
const plugin = this.app[items[0].ns]
|
|
28
|
+
menu.push({
|
|
29
|
+
name: k,
|
|
30
|
+
children: map(items, item => {
|
|
31
|
+
return {
|
|
32
|
+
name: titleize(item.name.slice(plugin.alias.length)),
|
|
33
|
+
href: `waibuAdmin:/${prefix}/${kebabCase(item.name)}/list`
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
return menu
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function adminMenu (locals, req) {
|
|
42
|
+
const { buildAccordionMenu } = this.app.waibuAdmin
|
|
43
|
+
return buildAccordionMenu(modelsMenu.call(this, locals, req), locals, req)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default adminMenu
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
function getParams (req, ...items) {
|
|
2
|
+
const { map, trim, get } = this.app.bajo.lib._
|
|
3
|
+
let fields
|
|
4
|
+
req.query = req.query ?? {}
|
|
5
|
+
req.params = req.params ?? {}
|
|
6
|
+
if (req.query.fields) fields = map((req.query.fields ?? '').split(','), i => trim(i))
|
|
7
|
+
const params = {
|
|
8
|
+
fields,
|
|
9
|
+
count: get(this, 'config.dbModel.count', false),
|
|
10
|
+
body: req.body
|
|
11
|
+
}
|
|
12
|
+
items.forEach(i => {
|
|
13
|
+
params[i] = req.params[i]
|
|
14
|
+
})
|
|
15
|
+
return params
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default getParams
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
const defReadonly = ['id', 'createdAt', 'updatedAt']
|
|
4
|
+
|
|
5
|
+
function getCommons (action, schema, ext, opts = {}) {
|
|
6
|
+
const { map, get, set, without, uniq } = this.app.bajo.lib._
|
|
7
|
+
const hidden = get(ext, `view.${action}.hidden`, get(ext, 'common.hidden', []))
|
|
8
|
+
hidden.push(...schema.hidden, ...(opts.hidden ?? []))
|
|
9
|
+
const allFields = without(map(schema.properties, 'name'), ...hidden)
|
|
10
|
+
const forFields = get(ext, `view.${action}.fields`, get(ext, 'common.fields', allFields))
|
|
11
|
+
set(schema, 'view.stat.aggregate', get(ext, `view.${action}.stat.aggregate`, get(ext, 'common.stat.aggregate', [])))
|
|
12
|
+
set(schema, 'view.disabled', get(ext, 'disabled', []))
|
|
13
|
+
if (schema.disabled.length > 0) schema.view.disabled.push(...schema.disabled)
|
|
14
|
+
let fields = []
|
|
15
|
+
for (const f of forFields) {
|
|
16
|
+
if (allFields.includes(f)) fields.push(f)
|
|
17
|
+
}
|
|
18
|
+
fields = uniq(without(fields, ...hidden))
|
|
19
|
+
if (action !== 'add' && !fields.includes('id')) fields.unshift('id')
|
|
20
|
+
return { fields, allFields }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function autoLayout ({ action, schema, ext, layout, allWidgets }) {
|
|
24
|
+
const matches = ['id', 'createdAt', 'updatedAt']
|
|
25
|
+
const meta = []
|
|
26
|
+
const general = []
|
|
27
|
+
for (const w of allWidgets) {
|
|
28
|
+
if (matches.includes(w.name)) meta.push(w)
|
|
29
|
+
else general.push(w)
|
|
30
|
+
}
|
|
31
|
+
if (meta.length <= 1) layout.push({ name: '_common', widgets: allWidgets })
|
|
32
|
+
else {
|
|
33
|
+
layout.push({ name: 'Meta', widgets: meta })
|
|
34
|
+
layout.push({ name: 'General', widgets: general })
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function customLayout ({ action, schema, ext, layout, allWidgets, readonly }) {
|
|
39
|
+
const { find, omit, merge, isString } = this.app.bajo.lib._
|
|
40
|
+
const items = [...layout]
|
|
41
|
+
layout.splice(0, layout.length)
|
|
42
|
+
for (const item of items) {
|
|
43
|
+
const widgets = []
|
|
44
|
+
for (let f of item.fields) {
|
|
45
|
+
if (isString(f)) {
|
|
46
|
+
const [name, col, label] = f.split(':')
|
|
47
|
+
f = { name, col, label }
|
|
48
|
+
}
|
|
49
|
+
const widget = find(allWidgets, { name: f.name })
|
|
50
|
+
if (!widget && !f.component) continue
|
|
51
|
+
widget.attr = merge({}, widget.attr, omit(f, ['component']))
|
|
52
|
+
if (f.component && !readonly.includes(f.name) && action !== 'details') widget.component = f.component
|
|
53
|
+
widgets.push(widget)
|
|
54
|
+
}
|
|
55
|
+
if (widgets.length > 0) layout.push({ name: item.name, widgets })
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function applyLayout (action, schema, ext) {
|
|
60
|
+
const { set, get, isEmpty, map, find } = this.app.bajo.lib._
|
|
61
|
+
const { fields } = getCommons.call(this, action, schema, ext)
|
|
62
|
+
const layout = get(ext, `view.${action}.layout`, get(ext, 'common.layout', []))
|
|
63
|
+
const readonly = get(ext, `view.${action}.readonly`, get(ext, 'common.readonly', defReadonly))
|
|
64
|
+
const allWidgets = map(fields, f => {
|
|
65
|
+
const prop = find(schema.properties, { name: f })
|
|
66
|
+
const result = { name: f, component: 'form-input', attr: { col: '4-md' } }
|
|
67
|
+
if (['array', 'object', 'text'].includes(prop.type)) {
|
|
68
|
+
result.attr.col = '12'
|
|
69
|
+
result.component = 'form-textarea'
|
|
70
|
+
result.attr.rows = '3'
|
|
71
|
+
}
|
|
72
|
+
if (action === 'details') {
|
|
73
|
+
result.component = 'form-plaintext'
|
|
74
|
+
} else {
|
|
75
|
+
if (prop.type === 'boolean') {
|
|
76
|
+
result.component = 'form-select'
|
|
77
|
+
result.attr.options = 'false:No true:Yes'
|
|
78
|
+
}
|
|
79
|
+
if (prop.values) {
|
|
80
|
+
result.component = 'form-select'
|
|
81
|
+
result.attr.options = prop.values.join(' ')
|
|
82
|
+
}
|
|
83
|
+
if (['string', 'text'].includes(prop.type) && prop.maxLength) set(result, 'attr.maxlength', prop.maxLength)
|
|
84
|
+
if (readonly.includes(f)) result.component = 'form-plaintext'
|
|
85
|
+
}
|
|
86
|
+
return result
|
|
87
|
+
})
|
|
88
|
+
if (isEmpty(layout)) autoLayout.call(this, { layout, allWidgets, schema, action, ext })
|
|
89
|
+
else customLayout.call(this, { layout, allWidgets, schema, action, ext, readonly })
|
|
90
|
+
set(schema, 'view.layout', layout)
|
|
91
|
+
set(schema, 'view.fields', fields)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const handler = {
|
|
95
|
+
list: async function (schema, ext, opts) {
|
|
96
|
+
const { get, set } = this.app.bajo.lib._
|
|
97
|
+
const { fields } = getCommons.call(this, 'list', schema, ext, opts)
|
|
98
|
+
const qsFields = []
|
|
99
|
+
for (const f of get(schema, 'view.qs.fields', '').split(',')) {
|
|
100
|
+
if (fields.includes(f)) qsFields.push(f)
|
|
101
|
+
}
|
|
102
|
+
let [col, dir] = get(schema, 'view.qs.sort', '').split(':')
|
|
103
|
+
if (!fields.includes(col) || !col) col = 'id'
|
|
104
|
+
if (!['1', '-1'].includes(dir)) dir = '1'
|
|
105
|
+
set(schema, 'view.fields', fields)
|
|
106
|
+
set(schema, 'view.qs.fields', qsFields.join(','))
|
|
107
|
+
set(schema, 'view.qs.sort', `${col}:${dir}`)
|
|
108
|
+
},
|
|
109
|
+
details: async function (schema, ext, opts) {
|
|
110
|
+
applyLayout.call(this, 'details', schema, ext, opts)
|
|
111
|
+
},
|
|
112
|
+
add: async function (schema, ext, opts) {
|
|
113
|
+
applyLayout.call(this, 'add', schema, ext, opts)
|
|
114
|
+
},
|
|
115
|
+
edit: async function (schema, ext, opts) {
|
|
116
|
+
applyLayout.call(this, 'edit', schema, ext, opts)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function getSchemaExt (model, view, opts) {
|
|
121
|
+
const { readConfig } = this.app.bajo
|
|
122
|
+
const { getSchema } = this.app.dobo
|
|
123
|
+
const { pick } = this.app.bajo.lib._
|
|
124
|
+
|
|
125
|
+
let schema = getSchema(model)
|
|
126
|
+
const base = path.basename(schema.file, path.extname(schema.file))
|
|
127
|
+
const ext = await readConfig(`${schema.ns}:/waibuDb/schema/${base}.*`, { ignoreError: true })
|
|
128
|
+
await handler[view].call(this, schema, ext, opts)
|
|
129
|
+
schema = pick(schema, ['name', 'properties', 'indexes', 'disabled', 'attachment', 'sortables', 'view'])
|
|
130
|
+
return { schema, ext }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export default getSchemaExt
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import prepCrud from '../../../lib/prep-crud.js'
|
|
2
|
+
|
|
3
|
+
async function create ({ model, req, reply, body, options = {} }) {
|
|
4
|
+
const { recordCreate, attachmentFind } = this.app.dobo
|
|
5
|
+
const { name, input, opts } = prepCrud.call(this, { model, req, body, options, args: ['model'] })
|
|
6
|
+
const ret = await recordCreate(name, input, opts)
|
|
7
|
+
const { attachment, stats, mimeType } = req.query
|
|
8
|
+
if (attachment) ret.data._attachment = await attachmentFind(name, ret.data.id, { stats, mimeType })
|
|
9
|
+
return ret
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default create
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import prepCrud from '../../../lib/prep-crud.js'
|
|
2
|
+
|
|
3
|
+
async function find ({ model, req, reply, options = {} }) {
|
|
4
|
+
const { recordFindOne, attachmentFind } = this.app.dobo
|
|
5
|
+
const { parseFilter } = this.app.waibu
|
|
6
|
+
const { name, opts } = prepCrud.call(this, { model, req, options, args: ['model'] })
|
|
7
|
+
const cfgWeb = this.app.waibu.config
|
|
8
|
+
opts.bboxLatField = req.query[cfgWeb.qsKey.bboxLatField]
|
|
9
|
+
opts.bboxLngField = req.query[cfgWeb.qsKey.bboxLngField]
|
|
10
|
+
const filter = parseFilter(req)
|
|
11
|
+
const ret = await recordFindOne(name, filter, opts)
|
|
12
|
+
ret.filter = filter
|
|
13
|
+
const { attachment, stats, mimeType } = req.query
|
|
14
|
+
if (attachment) {
|
|
15
|
+
ret.data._attachment = await attachmentFind(name, ret.data.id, { stats, mimeType })
|
|
16
|
+
}
|
|
17
|
+
return ret
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default find
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import prepCrud from '../../../lib/prep-crud.js'
|
|
2
|
+
|
|
3
|
+
async function find ({ model, req, reply, options = {} }) {
|
|
4
|
+
const { recordFind, attachmentFind } = this.app.dobo
|
|
5
|
+
const { cloneDeep } = this.app.bajo.lib._
|
|
6
|
+
const { name, opts } = prepCrud.call(this, { model, req, options, args: ['model'] })
|
|
7
|
+
const { parseFilter } = this.app.waibu
|
|
8
|
+
const cfgWeb = this.app.waibu.config
|
|
9
|
+
opts.bboxLatField = req.query[cfgWeb.qsKey.bboxLatField]
|
|
10
|
+
opts.bboxLngField = req.query[cfgWeb.qsKey.bboxLngField]
|
|
11
|
+
const filter = parseFilter(req)
|
|
12
|
+
if (options.query) {
|
|
13
|
+
filter.query = cloneDeep(options.query)
|
|
14
|
+
delete options.query
|
|
15
|
+
}
|
|
16
|
+
const ret = await recordFind(name, filter, opts)
|
|
17
|
+
const { attachment, stats, mimeType } = req.query
|
|
18
|
+
if (attachment) {
|
|
19
|
+
for (const d of ret.data) {
|
|
20
|
+
d._attachment = await attachmentFind(name, d.id, { stats, mimeType })
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return ret
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default find
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import prepCrud from '../../../lib/prep-crud.js'
|
|
2
|
+
|
|
3
|
+
async function get ({ model, req, reply, id, options = {} }) {
|
|
4
|
+
const { recordGet, attachmentFind } = this.app.dobo
|
|
5
|
+
const { parseFilter } = this.app.waibu
|
|
6
|
+
const { name, recId, opts } = prepCrud.call(this, { model, req, id, options, args: ['model', 'id'] })
|
|
7
|
+
opts.filter = parseFilter(req)
|
|
8
|
+
const ret = await recordGet(name, recId, opts)
|
|
9
|
+
const { attachment, stats, mimeType } = req.query
|
|
10
|
+
if (attachment) ret.data._attachment = await attachmentFind(name, id, { stats, mimeType })
|
|
11
|
+
return ret
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default get
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import prepCrud from '../../../lib/prep-crud.js'
|
|
2
|
+
|
|
3
|
+
async function remove ({ model, req, reply, id, options = {} }) {
|
|
4
|
+
const { recordRemove } = this.app.dobo
|
|
5
|
+
const { name, recId, opts } = prepCrud.call(this, { model, req, id, options, args: ['model', 'id'] })
|
|
6
|
+
const result = await recordRemove(name, recId, opts)
|
|
7
|
+
return result
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default remove
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import prepCrud from '../../../lib/prep-crud.js'
|
|
2
|
+
|
|
3
|
+
async function update ({ model, req, reply, id, body, options = {} }) {
|
|
4
|
+
const { recordUpdate, attachmentFind } = this.app.dobo
|
|
5
|
+
const { name, input, opts, recId } = prepCrud.call(this, { model, req, body, id, options, args: ['model', 'id'] })
|
|
6
|
+
const ret = await recordUpdate(name, recId, input, opts)
|
|
7
|
+
const { attachment, stats, mimeType } = req.query
|
|
8
|
+
if (attachment) ret.data._attachment = await attachmentFind(name, id, { stats, mimeType })
|
|
9
|
+
return ret
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default update
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import prepCrud from '../../../lib/prep-crud.js'
|
|
2
|
+
|
|
3
|
+
async function aggregate ({ model, req, reply, options = {} }) {
|
|
4
|
+
const { statAggregate } = this.app.dobo
|
|
5
|
+
const { parseFilter } = this.app.waibu
|
|
6
|
+
const { name, opts } = prepCrud.call(this, { model, req, options, args: ['model'] })
|
|
7
|
+
for (const item of ['group', 'aggregate']) {
|
|
8
|
+
opts[item] = options[item] ?? req.params[item] ?? req.query[item]
|
|
9
|
+
}
|
|
10
|
+
return await statAggregate(name, parseFilter(req), opts)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default aggregate
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import prepCrud from '../../../lib/prep-crud.js'
|
|
2
|
+
|
|
3
|
+
async function histogram ({ model, req, reply, options = {} }) {
|
|
4
|
+
const { statHistogram } = this.app.dobo
|
|
5
|
+
const { parseFilter } = this.app.waibu
|
|
6
|
+
const { name, opts } = prepCrud.call(this, { model, req, options, args: ['model'] })
|
|
7
|
+
for (const item of ['type', 'group', 'aggregate']) {
|
|
8
|
+
opts[item] = options[item] ?? req.params[item] ?? req.query[item]
|
|
9
|
+
}
|
|
10
|
+
return await statHistogram(name, parseFilter(req), opts)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default histogram
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"op": {
|
|
3
|
+
"equals": "Equals",
|
|
4
|
+
"notEquals": "Not Equals",
|
|
5
|
+
"greaterThan": "Greater Than",
|
|
6
|
+
"greaterThanOrEquals": "Greater Than or Equals",
|
|
7
|
+
"lessThan": "Less Than",
|
|
8
|
+
"lessThanOrEquals": "Less Than or Equals",
|
|
9
|
+
"in": "Includes",
|
|
10
|
+
"notIn": "Not Includes",
|
|
11
|
+
"contains": "Contains",
|
|
12
|
+
"notContains": "Not Contains",
|
|
13
|
+
"startsWith": "Starts With",
|
|
14
|
+
"notStartsWith": "Not Starts With",
|
|
15
|
+
"endsWith": "Ends With",
|
|
16
|
+
"notEndsWith": "Not Ends With"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Page %s of %s pages": "Hal %s dari %s",
|
|
3
|
+
"%s record(s) found": "Ditemukan %s data",
|
|
4
|
+
"recs per page": "data per halaman",
|
|
5
|
+
"Add as New Clone": "Tambah sbg Duplikat",
|
|
6
|
+
"Details": "Detil",
|
|
7
|
+
"Clone": "Gandakan",
|
|
8
|
+
"Delivery": "Penyampaian",
|
|
9
|
+
"Save as File": "Simpan sbg Berkas",
|
|
10
|
+
"Formatted Field": "Kolom Terformat",
|
|
11
|
+
"Formatted Value": "Nilai Terformat",
|
|
12
|
+
"Zipped": "Di kompres",
|
|
13
|
+
"File Type": "Tipe Berkas",
|
|
14
|
+
"You're about to remove one or more records. Are you really sure to do this?": "Anda akan menghapus satu atau lebih data. Anda yakin akan melakukannya?",
|
|
15
|
+
"Columns": "Kolom",
|
|
16
|
+
"No statistic yet, sorry": "Maaf, belum ada statistik",
|
|
17
|
+
"%s - %s": "%s - %s",
|
|
18
|
+
"List": "Daftar",
|
|
19
|
+
"Add": "Tambah",
|
|
20
|
+
"Edit": "Ubah",
|
|
21
|
+
"Yes": "Ya",
|
|
22
|
+
"No": "Tidak",
|
|
23
|
+
"Db": "DB",
|
|
24
|
+
"op": {
|
|
25
|
+
"equals": "Sama Dengan",
|
|
26
|
+
"notEquals": "Tidak Sama Dengan",
|
|
27
|
+
"greaterThan": "Lebih Besar Dari",
|
|
28
|
+
"greaterThanOrEquals": "Lebih Besar Dari atau Sama Dengan",
|
|
29
|
+
"lessThan": "Lebih Kecil Dari",
|
|
30
|
+
"lessThanOrEquals": "Lebih Kecil Dari atau Sama Dengan",
|
|
31
|
+
"in": "Termasuk",
|
|
32
|
+
"notIn": "Tidak Termasuk",
|
|
33
|
+
"contains": "Mengandung",
|
|
34
|
+
"notContains": "Tidak Mengandung",
|
|
35
|
+
"startsWith": "Dimulai Dengan",
|
|
36
|
+
"notStartsWith": "Tidak Dimulai Dengan",
|
|
37
|
+
"endsWith": "Diakhiri Dengan",
|
|
38
|
+
"notEndsWith": "Tidak Diakhiri Dengan"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
async function addHandler ({ req, reply, model, params = {}, template, addOnsHandler, templateDisabled = 'waibuDb.template:/disabled.html' } = {}) {
|
|
2
|
+
const { pascalCase } = this.app.bajo
|
|
3
|
+
const { recordCreate, recordGet, getSchemaExt } = this.app.waibuDb
|
|
4
|
+
const { buildUrl } = this.app.waibuMpa
|
|
5
|
+
const { pick, map, merge, defaultsDeep, omit, isEmpty } = this.app.bajo.lib._
|
|
6
|
+
const options = {}
|
|
7
|
+
model = model ?? pascalCase(req.params.model)
|
|
8
|
+
const { schema } = await getSchemaExt(model, 'add', options)
|
|
9
|
+
if (schema.disabled.includes('create')) return reply.view(templateDisabled, { action: 'add' })
|
|
10
|
+
// req.query.attachment = true
|
|
11
|
+
options.fields = schema.view.fields
|
|
12
|
+
let def = {}
|
|
13
|
+
if (req.method === 'GET' && req.query.mode === 'clone' && req.query.id) {
|
|
14
|
+
const resp = await recordGet({ model, req, id: req.query.id, options: { fields: map(schema.properties, 'name') } })
|
|
15
|
+
def = omit(resp.data, ['id', 'createdAt', 'updatedAt'])
|
|
16
|
+
}
|
|
17
|
+
let form = defaultsDeep(req.body, def)
|
|
18
|
+
let error
|
|
19
|
+
let resp
|
|
20
|
+
if (req.method === 'POST') {
|
|
21
|
+
req.session[`wdb${model}AddMore`] = form._addmore
|
|
22
|
+
req.session[`wdb${model}ClonePrev`] = form._cloneprev
|
|
23
|
+
try {
|
|
24
|
+
resp = await recordCreate({ model, req, reply, options })
|
|
25
|
+
if (isEmpty(form._addmore)) return reply.redirectTo(buildUrl({ url: req.url, base: 'list', params: { page: 1 }, exclude: ['id', 'mode'] }))
|
|
26
|
+
if (isEmpty(form._cloneprev)) form = pick(form, ['_addmore', '_cloneprev'])
|
|
27
|
+
} catch (err) {
|
|
28
|
+
error = err
|
|
29
|
+
}
|
|
30
|
+
} else {
|
|
31
|
+
form._addmore = req.session[`wdb${model}AddMore`]
|
|
32
|
+
form._cloneprev = req.session[`wdb${model}ClonePrev`]
|
|
33
|
+
}
|
|
34
|
+
const addOns = addOnsHandler ? await addOnsHandler.call(this.app[req.ns], { req, reply, params, data: resp, schema, error }) : undefined
|
|
35
|
+
merge(params, { form, schema, error, addOns })
|
|
36
|
+
return reply.view(template, params)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default addHandler
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import buildParams from './helper/build-params.js'
|
|
2
|
+
import addOnsHandler from './helper/add-ons-handler.js'
|
|
3
|
+
|
|
4
|
+
import addHandler from './add-handler.js'
|
|
5
|
+
import deleteHandler from './delete-handler.js'
|
|
6
|
+
import detailsHandler from './details-handler.js'
|
|
7
|
+
import editHandler from './edit-handler.js'
|
|
8
|
+
import exportHandler from './export-handler.js'
|
|
9
|
+
import listHandler from './list-handler.js'
|
|
10
|
+
|
|
11
|
+
const handler = {
|
|
12
|
+
add: addHandler,
|
|
13
|
+
delete: deleteHandler,
|
|
14
|
+
details: detailsHandler,
|
|
15
|
+
edit: editHandler,
|
|
16
|
+
export: exportHandler,
|
|
17
|
+
list: listHandler
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function allHandler ({ model, action, req, reply, template, params = {} }) {
|
|
21
|
+
const { upperFirst, merge, keys } = this.app.bajo.lib._
|
|
22
|
+
if (!keys(handler).includes(action)) throw this.error('notFound')
|
|
23
|
+
if (['delete', 'export'].includes(action)) {
|
|
24
|
+
if (req.method === 'GET') throw this.error('notFound')
|
|
25
|
+
return await handler[action].call(this, { model, req, reply })
|
|
26
|
+
}
|
|
27
|
+
const allParams = merge(buildParams.call(this, { model, req, reply, action: upperFirst(action) }), params)
|
|
28
|
+
return await handler[action].call(this, { model, req, reply, params: allParams, template, addOnsHandler })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default allHandler
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
async function deleteHandler ({ req, reply, model, params = {}, templateDisabled = 'waibuDb.template:/disabled.html' } = {}) {
|
|
2
|
+
const { pascalCase } = this.app.bajo
|
|
3
|
+
const { recordRemove, getSchemaExt } = this.app.waibuDb
|
|
4
|
+
const { buildUrl } = this.app.waibuMpa
|
|
5
|
+
const { reduce } = this.app.bajo.lib._
|
|
6
|
+
const options = {}
|
|
7
|
+
model = model ?? pascalCase(req.params.model)
|
|
8
|
+
const { schema } = await getSchemaExt(model, 'add', options)
|
|
9
|
+
if (schema.disabled.includes('remove')) return reply.view(templateDisabled, { action: 'delete' })
|
|
10
|
+
options.fields = schema.view.fields
|
|
11
|
+
const ids = (req.body.ids ?? '').split(',')
|
|
12
|
+
if (ids.length > 0) {
|
|
13
|
+
const result = []
|
|
14
|
+
const options = { noResult: true, noFlash: true }
|
|
15
|
+
for (const id of ids) {
|
|
16
|
+
try {
|
|
17
|
+
await recordRemove({ model, id, req, reply, options })
|
|
18
|
+
result.push(true)
|
|
19
|
+
} catch (err) {
|
|
20
|
+
result.push(err.message)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const success = reduce(result, (sum, n) => {
|
|
24
|
+
return n === true ? (sum + 1) : sum
|
|
25
|
+
}, 0)
|
|
26
|
+
let type = 'danger'
|
|
27
|
+
if (success > 0) type = 'warning'
|
|
28
|
+
if (success === ids.length) type = 'info'
|
|
29
|
+
req.flash('notify', req.t('%d of %d record(s) successfully removed', success, ids.length) + '\t' + type)
|
|
30
|
+
req.query.page = 1
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const url = buildUrl({ url: req.url, base: 'list', params: { page: 1 } })
|
|
34
|
+
return reply.redirectTo(url)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default deleteHandler
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
async function detailsHandler ({ req, reply, model, params = {}, template, addOnsHandler, templateDisabled = 'waibuDb.template:/disabled.html' } = {}) {
|
|
2
|
+
const { pascalCase } = this.app.bajo
|
|
3
|
+
const { recordGet, getSchemaExt } = this.app.waibuDb
|
|
4
|
+
const { merge } = this.app.bajo.lib._
|
|
5
|
+
const options = {}
|
|
6
|
+
model = model ?? pascalCase(req.params.model)
|
|
7
|
+
const { schema } = await getSchemaExt(model, 'details', options)
|
|
8
|
+
if (schema.disabled.includes('get')) return reply.view(templateDisabled, { action: 'details' })
|
|
9
|
+
// req.query.attachment = true
|
|
10
|
+
options.fields = schema.view.fields
|
|
11
|
+
const resp = await recordGet({ model, req, options })
|
|
12
|
+
const form = resp.data
|
|
13
|
+
const addOns = addOnsHandler ? await addOnsHandler.call(this.app[req.ns], { req, reply, params, data: resp, schema }) : undefined
|
|
14
|
+
merge(params, { form, schema, addOns })
|
|
15
|
+
return reply.view(template, params)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default detailsHandler
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
async function editHandler ({ req, reply, model, params = {}, template, addOnsHandler, templateDisabled = 'waibuDb.template:/disabled.html' } = {}) {
|
|
2
|
+
const { pascalCase } = this.app.bajo
|
|
3
|
+
const { recordUpdate, recordGet, getSchemaExt } = this.app.waibuDb
|
|
4
|
+
const { buildUrl } = this.app.waibuMpa
|
|
5
|
+
const { merge, defaultsDeep } = this.app.bajo.lib._
|
|
6
|
+
const options = {}
|
|
7
|
+
model = model ?? pascalCase(req.params.model)
|
|
8
|
+
const { schema } = await getSchemaExt(model, 'edit', options)
|
|
9
|
+
if (schema.disabled.includes('update')) return reply.view(templateDisabled, { action: 'edit' })
|
|
10
|
+
// req.query.attachment = true
|
|
11
|
+
options.fields = schema.view.fields
|
|
12
|
+
let error
|
|
13
|
+
let resp
|
|
14
|
+
let form
|
|
15
|
+
if (req.method === 'GET') {
|
|
16
|
+
const old = await recordGet({ model, req, id: req.query.id, options })
|
|
17
|
+
form = defaultsDeep(req.body, old.data)
|
|
18
|
+
} else {
|
|
19
|
+
form = req.body
|
|
20
|
+
try {
|
|
21
|
+
resp = await recordUpdate({ model, req, id: req.query.id, reply, options })
|
|
22
|
+
form = resp.data
|
|
23
|
+
return reply.redirectTo(buildUrl({ url: req.url, base: 'list', params: { page: 1 }, exclude: ['id'] }))
|
|
24
|
+
} catch (err) {
|
|
25
|
+
error = err
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const addOns = addOnsHandler ? await addOnsHandler.call(this.app[req.ns], { req, reply, params, data: resp, schema, error }) : undefined
|
|
29
|
+
merge(params, { form, schema, error, addOns })
|
|
30
|
+
return reply.view(template, params)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default editHandler
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
async function exportHandler ({ req, reply, model, params = {}, templateDisabled = 'waibuDb.template:/disabled.html' } = {}) {
|
|
2
|
+
const { pascalCase } = this.app.bajo
|
|
3
|
+
const { getSchemaExt } = this.app.waibuDb
|
|
4
|
+
const { buildUrl } = this.app.waibuMpa
|
|
5
|
+
const options = {}
|
|
6
|
+
model = model ?? pascalCase(req.params.model)
|
|
7
|
+
const { schema } = await getSchemaExt(model, 'add', options)
|
|
8
|
+
if (schema.disabled.includes('find')) return reply.view(templateDisabled, { action: 'list' })
|
|
9
|
+
options.fields = schema.view.fields
|
|
10
|
+
const url = buildUrl({ url: req.url, base: req.body.handler })
|
|
11
|
+
req.flash('notify', req.t('Data export in queue. You\'ll be notified once completed'))
|
|
12
|
+
return reply.redirectTo(url)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default exportHandler
|