waibu-db 1.2.3 → 1.2.5

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.
@@ -32,6 +32,9 @@
32
32
  "checkAll": "Check All",
33
33
  "uncheckAll": "Uncheck All",
34
34
  "attachment": "Attachment",
35
+ "exportInQueue": "Data export in queue. Please check your download list for progress",
36
+ "tsv": "TSV",
37
+ "ndjson": "NDJSON",
35
38
  "op": {
36
39
  "equals": "Equals",
37
40
  "notEquals": "Not Equals",
package/bajo/intl/id.json CHANGED
@@ -32,6 +32,9 @@
32
32
  "checkAll": "Cek Semua",
33
33
  "uncheckAll": "Uncheck Semua",
34
34
  "attachment": "Attachment",
35
+ "exportInQueue": "Ekspor data sedang dalam antrian. Silahkan cek daftar unduh Anda untuk perkembangan terakhir",
36
+ "tsv": "TSV",
37
+ "ndjson": "NDJSON",
35
38
  "op": {
36
39
  "equals": "Sama Dengan",
37
40
  "notEquals": "Tidak Sama Dengan",
@@ -16,7 +16,7 @@
16
16
  <c:wdb-btn-clone href="<%= schema.view.control.cloneHref ?? 'undefined' %>" />
17
17
  <% } %>
18
18
  <% if (!schema.view.control.noExportBtn) { %>
19
- <c:wdb-btn-export selector="#main-form" handler="details" launch-on-end/>
19
+ <c:wdb-btn-export selector="#main-form" handler="details" launch-on-end no-save/>
20
20
  <% } %>
21
21
  </c:btn-group>
22
22
  <% } %>
@@ -10,7 +10,7 @@
10
10
  <% if (!schema.view.control.noCloneBtn) { %>
11
11
  <c:wdb-btn-clone href="<%= schema.view.control.cloneHref ?? 'undefined' %>" />
12
12
  <% } %>
13
- <c:wdb-btn-export selector="#main-form" handler="edit" launch-on-end/>
13
+ <c:wdb-btn-export selector="#main-form" handler="edit" launch-on-end no-save />
14
14
  </c:btn-group>
15
15
  </c:grid-col>
16
16
  <c:grid-col col="6-lg" flex="justify-content:end-lg align-items:center">
package/index.js CHANGED
@@ -5,7 +5,7 @@ async function factory (pkgName) {
5
5
  constructor () {
6
6
  super(pkgName, me.app)
7
7
  this.alias = 'wdb'
8
- this.dependencies = ['dobo', 'waibu']
8
+ this.dependencies = ['dobo', 'waibu', 'bajo-queue', 'dobo-extra']
9
9
  this.config = {
10
10
  waibu: {
11
11
  prefix: 'db',
@@ -25,6 +25,33 @@ async function factory (pkgName) {
25
25
  modelRestApi: false
26
26
  }
27
27
  }
28
+
29
+ exportData = async (params) => {
30
+ const { getPlugin } = this.app.bajo
31
+ const { get } = this.lib._
32
+ const { fs } = this.lib
33
+ const { recordUpdate } = this.app.dobo
34
+ const { exportTo } = this.app.doboExtra
35
+ const { downloadDir } = getPlugin('sumba')
36
+ const model = get(params, 'payload.data.name')
37
+ const fields = get(params, 'payload.data.opts.fields')
38
+ const { id, file } = get(params, 'payload.data.download', {})
39
+ const dest = `${downloadDir}/${file}`
40
+ const options = {
41
+ filter: get(params, 'payload.data.filter', {}),
42
+ ensureDir: true,
43
+ fields
44
+ }
45
+ const dmodel = 'SumbaDownload'
46
+ try {
47
+ await recordUpdate(dmodel, id, { status: 'PROCESSING' })
48
+ await exportTo(model, dest, options)
49
+ const { size } = fs.statSync(dest)
50
+ await recordUpdate(dmodel, id, { size, status: 'COMPLETE' })
51
+ } catch (err) {
52
+ await recordUpdate(dmodel, id, { status: 'FAIL' })
53
+ }
54
+ }
28
55
  }
29
56
  }
30
57
 
@@ -34,6 +34,8 @@ async function addHandler ({ req, reply, model, params = {}, template, addOnsHan
34
34
  }
35
35
  const addOns = addOnsHandler ? await addOnsHandler.call(this.app[req.ns], { req, reply, params, data: resp, schema, error }) : undefined
36
36
  merge(params, { form, schema, error, addOns })
37
+ if (schema.template) template = schema.template
38
+ if (schema.layout) params.page.layout = schema.layout
37
39
  return await reply.view(template, params)
38
40
  }
39
41
 
@@ -16,6 +16,8 @@ async function detailsHandler ({ req, reply, model, params = {}, id, template, a
16
16
  const addOns = addOnsHandler ? await addOnsHandler.call(this.app[req.ns], { req, reply, params, data: resp, schema }) : undefined
17
17
  const attachments = await attachmentHandler.call(this, { schema, id })
18
18
  merge(params, { form, schema, addOns, attachments })
19
+ if (schema.template) template = schema.template
20
+ if (schema.layout) params.page.layout = schema.layout
19
21
  return await reply.view(template, params)
20
22
  }
21
23
 
@@ -45,6 +45,8 @@ async function editHandler ({ req, reply, model, id, params = {}, template, addO
45
45
  const addOns = addOnsHandler ? await addOnsHandler.call(this.app[req.ns], { req, reply, params, data: resp, schema, error }) : undefined
46
46
  const attachments = await attachmentHandler.call(this, { schema, id })
47
47
  merge(params, { form, schema, error, addOns, attachments })
48
+ if (schema.template) template = schema.template
49
+ if (schema.layout) params.page.layout = schema.layout
48
50
  return await reply.view(template, params)
49
51
  }
50
52
 
@@ -1,14 +1,27 @@
1
+ import prepCrud from '../prep-crud.js'
2
+
1
3
  async function exportHandler ({ req, reply, model, params = {}, templateDisabled = 'waibuDb.template:/disabled.html' } = {}) {
4
+ const { getPlugin } = this.app.bajo
5
+ const { dayjs } = this.lib
6
+ const { omit, kebabCase, get } = this.lib._
2
7
  const { pascalCase } = this.lib.aneka
3
8
  const { getSchemaExt } = this.app.waibuDb
4
9
  const { buildUrl } = this.app.waibuMpa
5
- const options = {}
10
+ const { pushDownload } = getPlugin('sumba')
6
11
  model = model ?? pascalCase(req.params.model)
7
- const { schema } = await getSchemaExt(model, 'add', options, { params })
12
+ const { schema } = await getSchemaExt(model, 'add', { params })
8
13
  if (schema.disabled.includes('find')) return await reply.view(templateDisabled, { action: 'list' })
9
- options.fields = schema.view.fields
14
+ const data = prepCrud.call(getPlugin('waibuDb'), { model, req, reply, args: ['model'] })
15
+ data.opts = omit(data.opts, ['req', 'reply'])
16
+ const source = `${this.name}:/export-handler`
17
+ const worker = 'waibuDb:exportData'
18
+ const type = get(data, 'input.ftype', 'json')
19
+ const settings = get(data, 'input.options', '').split(',')
20
+ const ext = settings.includes('zip') ? `${type}.gz` : type
21
+ const file = `${kebabCase(model)}_${dayjs().format('YYYYMMDDhhmmss')}.${ext}`
22
+ await pushDownload({ file, type, worker, source, data, req })
23
+ req.flash('notify', req.t('exportInQueue'))
10
24
  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
25
  return reply.redirectTo(url)
13
26
  }
14
27
 
@@ -21,6 +21,8 @@ async function listHandler ({ req, reply, model, template, params = {}, addOnsHa
21
21
  if (!isArray(addOns)) addOns = [addOns]
22
22
  }
23
23
  merge(params, { list, schema, addOns })
24
+ if (schema.template) template = schema.template
25
+ if (schema.layout) params.page.layout = schema.layout
24
26
  return await reply.view(template, params)
25
27
  }
26
28
 
package/lib/prep-crud.js CHANGED
@@ -1,5 +1,6 @@
1
1
  function prepCrud ({ model, body, id, req, reply, options = {}, args }) {
2
2
  const { parseFilter } = this.app.waibu
3
+ const { buildQuery, getInfo } = this.app.dobo
3
4
  const { pascalCase } = this.lib.aneka
4
5
  const { cloneDeep, has } = this.lib._
5
6
  const cfgWeb = this.app.waibu.getConfig()
@@ -20,6 +21,7 @@ function prepCrud ({ model, body, id, req, reply, options = {}, args }) {
20
21
 
21
22
  const recId = id ?? params.id ?? req.query.id
22
23
  const name = pascalCase(model ?? params.model)
24
+ const { schema } = getInfo(name)
23
25
  const input = body ?? params.body
24
26
  opts.bboxLatField = req.query[cfgWeb.qsKey.bboxLatField]
25
27
  opts.bboxLngField = req.query[cfgWeb.qsKey.bboxLngField]
@@ -28,6 +30,7 @@ function prepCrud ({ model, body, id, req, reply, options = {}, args }) {
28
30
  if (options.limit) filter.limit = options.limit
29
31
  if (options.sort) filter.sort = options.sort
30
32
  if (options.page) filter.page = options.page
33
+ filter.query = buildQuery({ filter, schema })
31
34
  return { name, recId, input, opts, filter, attachment, stats, mimeType }
32
35
  }
33
36
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waibu-db",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "DB Helper",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,4 +1,4 @@
1
- async function formatRow ({ data, req, component, schema, options = {} }) {
1
+ async function formatRow ({ data, req, schema, options = {} }) {
2
2
  const { get, find, isFunction, cloneDeep } = this.lib._
3
3
  const { format, callHandler } = this.app.bajo
4
4
  const fields = get(schema, 'view.fields', Object.keys(schema.properties))
@@ -19,25 +19,19 @@ async function formatRow ({ data, req, component, schema, options = {} }) {
19
19
  rec[f] = format(data[f], prop.type, opts)
20
20
  const vf = get(schema, `view.valueFormatter.${f}`)
21
21
  if (vf) {
22
- if (isFunction(vf)) rec[f] = await vf.call(req ?? this, data[f], data)
23
- else rec[f] = await callHandler(vf, req, data[f], data)
24
- }
25
- const formatter = get(schema, `view.formatter.${f}`)
26
- if (formatter && component) {
27
- if (isFunction(formatter)) rec[f] = await formatter.call(req ?? this, data[f], data)
28
- else rec[f] = await callHandler(formatter, req, data[f], data)
29
- rec[f] = await component.buildSentence(rec[f])
22
+ if (isFunction(vf)) rec[f] = await vf.call(this, data[f], data)
23
+ else rec[f] = await callHandler(vf, { req, value: data[f], data })
30
24
  }
31
25
  }
32
26
  return rec
33
27
  }
34
28
 
35
- async function formatRecord ({ data, req, schema, component, options = {} }) {
29
+ async function formatRecord ({ data, req, schema, options = {} }) {
36
30
  const { isArray } = this.lib._
37
- if (!isArray(data)) return await formatRow.call(this, { data, req, schema, component, options })
31
+ if (!isArray(data)) return await formatRow.call(this, { data, req, schema, options })
38
32
  const items = []
39
33
  for (const d of data) {
40
- const item = await formatRow.call(this, { data: d, req, schema, component, options })
34
+ const item = await formatRow.call(this, { data: d, req, schema, options })
41
35
  items.push(item)
42
36
  }
43
37
  return items
@@ -29,15 +29,22 @@ async function btnExport () {
29
29
  ftype: 'json',
30
30
  toggle (val) {
31
31
  if (val === 'clipboard') {
32
+ $refs.fkey.removeAttribute('disabled')
33
+ $refs.fvalue.removeAttribute('disabled')
32
34
  $refs.zip.setAttribute('disabled', '')
33
35
  $refs.xlsx.setAttribute('disabled', '')
34
- $refs.xml.setAttribute('disabled', '')
36
+ $refs.tsv.setAttribute('disabled', '')
37
+ $refs.ndjson.setAttribute('disabled', '')
35
38
  _.pull(this.options, 'zip')
36
39
  if (!['json', 'csv'].includes(this.ftype)) this.ftype = 'json'
37
40
  } else {
41
+ $refs.fkey.setAttribute('disabled', '')
42
+ $refs.fvalue.setAttribute('disabled', '')
38
43
  $refs.zip.removeAttribute('disabled')
39
44
  $refs.xlsx.removeAttribute('disabled')
40
- $refs.xml.removeAttribute('disabled')
45
+ $refs.tsv.removeAttribute('disabled')
46
+ $refs.ndjson.removeAttribute('disabled')
47
+ _.pull(this.options, 'fkey', 'fvalue')
41
48
  }
42
49
  },
43
50
  extractForm (selector) {
@@ -117,8 +124,8 @@ async function btnExport () {
117
124
  <c:grid-row gutter="2">
118
125
  <c:grid-col col="6-md">
119
126
  <c:fieldset t:legend="delivery" legend-type="6">
120
- <c:form-radio x-model="delivery" value="file" t:label="saveAsFile" />
121
127
  <c:form-radio x-model="delivery" value="clipboard" t:label="copyClipboard" />
128
+ <c:form-radio x-model="delivery" value="file" t:label="saveAsFile" ${this.params.attr.noSave ? 'disabled' : ''} />
122
129
  </c:fieldset>
123
130
  <c:fieldset t:legend="options" legend-type="6" margin="top-2">
124
131
  <c:form-check x-ref="fkey" x-model="options" value="fkey" t:label="formattedField" />
@@ -130,8 +137,9 @@ async function btnExport () {
130
137
  <c:fieldset t:legend="fileType" legend-type="6">
131
138
  <c:form-radio x-ref="xlsx" x-model="ftype" value="xlsx" t:label="excelXlsx" />
132
139
  <c:form-radio x-ref="csv" x-model="ftype" value="csv" t:label="csv" />
133
- <c:form-radio x-ref="xml" x-model="ftype" value="xml" t:label="xml" />
140
+ <c:form-radio x-ref="tsv" x-model="ftype" value="tsv" t:label="tsv" />
134
141
  <c:form-radio x-ref="json" x-model="ftype" value="json" t:label="json" />
142
+ <c:form-radio x-ref="ndjson" x-model="ftype" value="ndjson" t:label="ndjson" />
135
143
  </c:fieldset />
136
144
  </c:grid-col>
137
145
  </c:grid-row>
@@ -10,7 +10,6 @@ async function query () {
10
10
  const { find, get, without, isEmpty, filter, upperFirst } = this.plugin.app.bajo.lib._
11
11
  const qsKey = this.plugin.app.waibu.config.qsKey
12
12
  const schema = get(this, 'component.locals.schema', {})
13
- const count = get(this, 'component.locals.list.count', 0)
14
13
  if (schema.view.disabled.includes('find')) {
15
14
  this.params.html = ''
16
15
  return
@@ -21,7 +20,6 @@ async function query () {
21
20
  const id = generateId('alpha')
22
21
  const columns = []
23
22
  const models = []
24
- const selects = ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'in', 'contains', 'starts', 'ends', '!in', '!contains', '!starts', '!ends']
25
23
  for (const f of schema.view.fields) {
26
24
  if (!fields.includes(f)) continue
27
25
  const prop = find(schema.properties, { name: f })
@@ -29,7 +27,7 @@ async function query () {
29
27
  if (['float', 'double', 'integer', 'smallint'].includes(prop.type)) ops.push('eq', 'neq', 'gt', 'gte', 'lt', 'lte')
30
28
  else if (['datetime', 'date', 'time'].includes(prop.type)) ops.push('eq', 'neq', 'gt', 'gte', 'lt', 'lte')
31
29
  else if (['boolean'].includes(prop.type)) ops.push('eq', 'neq')
32
- else ops.push(...selects)
30
+ else ops.push('eq', 'neq', 'in', 'contains', 'starts', 'ends', '!in', '!contains', '!starts', '!ends')
33
31
  if (ops.length === 0) continue
34
32
  const sels = ops.map(o => `<c:option>${o}</c:option>`)
35
33
  models.push(`${f}Op: 'eq'`, `${f}Val: ''`)
@@ -51,12 +49,12 @@ async function query () {
51
49
  this.params.noTag = true
52
50
  const container = this.params.attr.modal ? 'modal' : 'drawer'
53
51
  this.params.html = await this.component.buildSentence(`
54
- <c:form-input ${count === 0 ? 'disabled' : ''} type="search" t:placeholder="query" id="${id}" x-data="{ query: '' }" x-init="
52
+ <c:form-input type="search" t:placeholder="query" id="${id}" x-data="{ query: '' }" x-init="
55
53
  const url = new URL(window.location.href)
56
54
  query = url.searchParams.get('${qsKey.query}') ?? ''
57
55
  " x-model="query" @on-query.window="query = $event.detail ?? ''" @keyup.enter="$dispatch('on-submit')">
58
56
  <c:form-input-addon>
59
- <c:${container} ${count === 0 ? 'trigger-disabled' : ''} trigger-icon="${this.params.attr.icon ?? 'dotsThree'}" trigger-on-end t:title="queryBuilder" x-ref="query" x-data="{
57
+ <c:${container} trigger-icon="${this.params.attr.icon ?? 'dotsThree'}" trigger-on-end t:title="queryBuilder" x-ref="query" x-data="{
60
58
  fields: ${jsonStringify(fields, true)},
61
59
  builder: '',
62
60
  selected: [],
@@ -70,6 +68,7 @@ async function query () {
70
68
  },
71
69
  initBuilder () {
72
70
  this.builder = document.getElementById('${id}').value
71
+ if (!this.builder.includes(':')) this.builder = ''
73
72
  if (_.isEmpty(this.builder)) return
74
73
  const tokens = _.merge({}, this.ops, {
75
74
  in: ':[',
@@ -158,7 +157,7 @@ async function query () {
158
157
  </c:${container}>
159
158
  </c:form-input-addon>
160
159
  <c:form-input-addon>
161
- <c:btn ${count === 0 ? 'disabled' : ''} t:content="submit" x-data="{
160
+ <c:btn t:content="submit" x-data="{
162
161
  submit () {
163
162
  const val = document.getElementById('${id}').value ?? ''
164
163
  const url = new URL(window.location.href)
@@ -19,19 +19,33 @@ async function table () {
19
19
  return get(schema, 'view.noWrap', []).includes(field)
20
20
  }
21
21
 
22
+ _defFormatter = async ({ req, key, value, data, schema }) => {
23
+ const { get, find } = this.plugin.lib._
24
+ const { escape } = this.plugin.app.waibu
25
+ const prop = find(schema.properties, { name: key })
26
+ if (!prop) return value
27
+ if (prop.type === 'boolean') {
28
+ value = (await this.component.buildTag({ tag: 'icon', attr: { name: `circle${data[key] ? 'Check' : ''}` } })) +
29
+ ' ' + (req.t(data[key] ? 'Yes' : 'No'))
30
+ } else if (['string', 'text'].includes(prop.type)) {
31
+ if (!get(schema, 'view.noEscape', []).includes(key)) value = escape(value)
32
+ }
33
+ return value
34
+ }
35
+
22
36
  build = async () => {
23
37
  const { req } = this.component
24
38
  const { escape } = this.plugin.app.waibu
25
39
  const { formatRecord } = this.plugin.app.waibuDb
26
40
  const { attrToArray, groupAttrs } = this.plugin.app.waibuMpa
27
- const { get, omit, set, find, isEmpty, without, merge } = this.plugin.app.bajo.lib._
41
+ const { get, omit, set, find, isEmpty, without, merge } = this.plugin.lib._
28
42
  const group = groupAttrs(this.params.attr, ['body', 'head', 'foot'])
29
43
  this.params.attr = group._
30
44
  const prettyUrl = this.params.attr.prettyUrl
31
45
 
32
46
  const schema = get(this, 'component.locals.schema', {})
33
47
  const data = get(this, 'component.locals.list.data', [])
34
- const fdata = await formatRecord.call(this.plugin, { data, req, schema, component: this.component })
48
+ const fdata = await formatRecord.call(this.plugin, { data, req, schema })
35
49
  const filter = get(this, 'component.locals.list.filter', {})
36
50
  const count = get(this, 'component.locals.list.count', 0)
37
51
  if (count === 0) {
@@ -136,16 +150,10 @@ async function table () {
136
150
  if (['array', 'object'].includes(prop.type)) dataValue = escape(JSON.stringify(d[f]))
137
151
  }
138
152
  let value = fd[f]
139
- if (prop.type === 'boolean') {
140
- value = (await this.component.buildTag({ tag: 'icon', attr: { name: `circle${d[f] ? 'Check' : ''}` } })) +
141
- ' ' + (req.t(d[f] ? 'Yes' : 'No'))
142
- } else {
143
- if (!get(schema, 'view.noEscape', []).includes(f)) value = escape(value)
144
- }
145
153
  const attr = { dataValue, dataKey: prop.name, dataType: prop.type }
146
154
  if (!disableds.includes('get')) attr.style = { cursor: 'pointer' }
147
155
  const cellFormatter = get(schema, `view.cellFormatter.${f}`)
148
- if (cellFormatter) merge(attr, await cellFormatter.call(req, dataValue, d))
156
+ if (cellFormatter) merge(attr, await cellFormatter.call(this, dataValue, d))
149
157
  const noWrap = this.isNoWrap(f, schema, group.body.nowrap) ? 'nowrap' : ''
150
158
  if (this.isRightAligned(f, schema)) attr.text = `align:end ${noWrap}`
151
159
  else attr.text = noWrap
@@ -154,6 +162,9 @@ async function table () {
154
162
  const item = find(lookup.values, set({}, lookup.id ?? 'id', d[f]))
155
163
  if (item) value = req.t(item[lookup.field ?? 'name'])
156
164
  }
165
+ const formatter = get(schema, `view.formatter.${f}`)
166
+ if (formatter) value = await formatter.call(this, value, d)
167
+ else value = await this._defFormatter({ req, key: f, schema, value, data: d })
157
168
  const line = await this.component.buildTag({ tag: 'td', attr, html: value })
158
169
  lines.push(line)
159
170
  }
@@ -1,14 +0,0 @@
1
- const action = {
2
- method: ['GET', 'POST'],
3
- title: 'Database Export',
4
- handler: async function (req, reply) {
5
- const { importModule } = this.app.bajo
6
- const handler = await importModule('waibuDb:/lib/crud/all-handler.js')
7
- const model = 'SumbaUser'
8
- const { action } = req.params
9
- const template = `sumba.template:/crud/${action}.html`
10
- return handler.call(this, { model, req, reply, action, template })
11
- }
12
- }
13
-
14
- export default action