dobo 2.0.1 → 2.2.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/.github/FUNDING.yml +0 -0
- package/.github/workflows/repo-lockdown.yml +0 -0
- package/.jsdoc.conf.json +0 -0
- package/LICENSE +0 -0
- package/README.md +2 -2
- package/docs/Dobo.html +0 -0
- package/docs/data/search.json +0 -0
- package/docs/fonts/Inconsolata-Regular.ttf +0 -0
- package/docs/fonts/OpenSans-Regular.ttf +0 -0
- package/docs/fonts/WorkSans-Bold.ttf +0 -0
- package/docs/global.html +0 -0
- package/docs/index.html +0 -0
- package/docs/index.js.html +0 -0
- package/docs/lib_collect-connections.js.html +0 -0
- package/docs/lib_collect-drivers.js.html +0 -0
- package/docs/lib_collect-features.js.html +0 -0
- package/docs/lib_collect-schemas.js.html +0 -0
- package/docs/lib_index.js.html +0 -0
- package/docs/method_model_create.js.html +0 -0
- package/docs/method_model_drop.js.html +0 -0
- package/docs/method_model_exists.js.html +0 -0
- package/docs/method_record_count.js.html +0 -0
- package/docs/method_record_create.js.html +0 -0
- package/docs/method_record_find-all.js.html +0 -0
- package/docs/method_record_find-one.js.html +0 -0
- package/docs/method_record_find.js.html +0 -0
- package/docs/method_record_get.js.html +0 -0
- package/docs/method_record_remove.js.html +0 -0
- package/docs/method_record_update.js.html +0 -0
- package/docs/method_record_upsert.js.html +0 -0
- package/docs/method_sanitize_body.js.html +0 -0
- package/docs/method_sanitize_date.js.html +0 -0
- package/docs/method_sanitize_id.js.html +0 -0
- package/docs/method_validate.js.html +0 -0
- package/docs/module-Lib.html +0 -0
- package/docs/scripts/core.js +476 -477
- package/docs/scripts/core.min.js +0 -0
- package/docs/scripts/resize.js +36 -36
- package/docs/scripts/search.js +105 -105
- package/docs/scripts/search.min.js +0 -0
- package/docs/scripts/third-party/Apache-License-2.0.txt +0 -0
- package/docs/scripts/third-party/fuse.js +1 -1
- package/docs/scripts/third-party/hljs-line-num-original.js +282 -285
- package/docs/scripts/third-party/hljs-line-num.js +1 -1
- package/docs/scripts/third-party/hljs-original.js +1195 -1202
- package/docs/scripts/third-party/hljs.js +1 -1
- package/docs/scripts/third-party/popper.js +1 -1
- package/docs/scripts/third-party/tippy.js +1 -1
- package/docs/scripts/third-party/tocbot.js +508 -509
- package/docs/scripts/third-party/tocbot.min.js +0 -0
- package/docs/static/bitcoin.jpeg +0 -0
- package/docs/static/home.md +0 -0
- package/docs/static/logo-ecosystem.png +0 -0
- package/docs/static/logo.png +0 -0
- package/docs/styles/clean-jsdoc-theme-base.css +0 -0
- package/docs/styles/clean-jsdoc-theme-dark.css +0 -0
- package/docs/styles/clean-jsdoc-theme-light.css +0 -0
- package/docs/styles/clean-jsdoc-theme-scrollbar.css +0 -0
- package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +0 -0
- package/docs/styles/clean-jsdoc-theme.min.css +0 -0
- package/extend/bajo/intl/en-US.json +66 -28
- package/extend/bajo/intl/id.json +55 -27
- package/extend/bajoCli/applet/clear-record.js +22 -0
- package/extend/bajoCli/applet/connection.js +0 -0
- package/extend/bajoCli/applet/count-record.js +27 -0
- package/extend/bajoCli/applet/create-aggregate.js +33 -0
- package/extend/bajoCli/applet/create-histogram.js +33 -0
- package/extend/bajoCli/applet/create-record.js +39 -0
- package/extend/bajoCli/applet/find-record.js +27 -0
- package/extend/bajoCli/applet/get-record.js +27 -0
- package/extend/bajoCli/applet/lib/post-process.js +10 -17
- package/extend/bajoCli/applet/model.js +22 -0
- package/extend/bajoCli/applet/rebuild-model.js +91 -0
- package/extend/bajoCli/applet/remove-record.js +27 -0
- package/extend/bajoCli/applet/update-record.js +44 -0
- package/extend/bajoCli/applet.js +0 -0
- package/extend/dobo/driver/memory.js +170 -0
- package/extend/dobo/feature/created-at.js +9 -7
- package/extend/dobo/feature/dt.js +0 -0
- package/extend/dobo/feature/immutable.js +30 -0
- package/extend/dobo/feature/int-id.js +0 -0
- package/extend/dobo/feature/removed-at.js +32 -54
- package/extend/dobo/feature/updated-at.js +14 -12
- package/extend/waibuMpa/route/attachment/@model/@id/@field/@file.js +2 -6
- package/extend/waibuStatic/virtual.json +0 -0
- package/index.js +291 -366
- package/lib/collect-connections.js +49 -21
- package/lib/collect-drivers.js +19 -33
- package/lib/collect-features.js +24 -17
- package/lib/collect-models.js +319 -0
- package/lib/factory/action.js +161 -0
- package/lib/factory/connection.js +62 -0
- package/lib/factory/driver.js +358 -0
- package/lib/factory/feature.js +33 -0
- package/lib/factory/model/_util.js +402 -0
- package/lib/factory/model/build.js +15 -0
- package/lib/factory/model/clear-record.js +17 -0
- package/lib/factory/model/count-record.js +17 -0
- package/lib/factory/model/create-aggregate.js +17 -0
- package/lib/factory/model/create-attachment.js +29 -0
- package/lib/factory/model/create-histogram.js +17 -0
- package/lib/factory/model/create-record.js +35 -0
- package/lib/factory/model/drop.js +15 -0
- package/lib/factory/model/exists.js +21 -0
- package/lib/factory/model/find-all-record.js +71 -0
- package/lib/factory/model/find-attachment.js +29 -0
- package/lib/factory/model/find-one-record.js +19 -0
- package/{method/record/find.js → lib/factory/model/find-record.js} +103 -115
- package/lib/factory/model/get-attachment.js +15 -0
- package/lib/factory/model/get-record.js +79 -0
- package/lib/factory/model/list-attachment.js +37 -0
- package/lib/{add-fixtures.js → factory/model/load-fixtures.js} +69 -67
- package/lib/factory/model/remove-attachment.js +15 -0
- package/lib/factory/model/remove-record.js +59 -0
- package/lib/factory/model/sanitize-body.js +62 -0
- package/lib/factory/model/sanitize-id.js +7 -0
- package/lib/factory/model/sanitize-record.js +26 -0
- package/lib/factory/model/update-attachment.js +9 -0
- package/lib/factory/model/update-record.js +81 -0
- package/lib/factory/model/upsert-record.js +95 -0
- package/{method → lib/factory/model}/validate.js +38 -52
- package/lib/factory/model.js +150 -0
- package/lib/index.js +0 -0
- package/package.json +8 -4
- package/wiki/APPLETS.md +0 -0
- package/wiki/CHANGES.md +46 -0
- package/wiki/CONFIG.md +0 -0
- package/wiki/CONTRIBUTING.md +0 -0
- package/wiki/DEV-GUIDE.md +0 -0
- package/wiki/ECOSYSTEM.md +0 -0
- package/wiki/GETTING-STARTED.md +10 -10
- package/wiki/QUERY-LANGUAGE.md +0 -0
- package/wiki/USER-GUIDE.md +0 -0
- package/extend/bajoCli/applet/model-clear.js +0 -11
- package/extend/bajoCli/applet/model-rebuild.js +0 -101
- package/extend/bajoCli/applet/record-create.js +0 -43
- package/extend/bajoCli/applet/record-find.js +0 -28
- package/extend/bajoCli/applet/record-get.js +0 -24
- package/extend/bajoCli/applet/record-remove.js +0 -24
- package/extend/bajoCli/applet/record-update.js +0 -47
- package/extend/bajoCli/applet/schema.js +0 -22
- package/extend/bajoCli/applet/stat-count.js +0 -24
- package/lib/build-bulk-action.js +0 -12
- package/lib/check-unique.js +0 -39
- package/lib/collect-schemas.js +0 -91
- package/lib/exec-feature-hook.js +0 -13
- package/lib/exec-validation.js +0 -21
- package/lib/generic-prop-sanitizer.js +0 -32
- package/lib/handle-attachment-upload.js +0 -16
- package/lib/mem-db/conn-sanitizer.js +0 -8
- package/lib/mem-db/instantiate.js +0 -41
- package/lib/mem-db/method/model/clear.js +0 -6
- package/lib/mem-db/method/model/create.js +0 -5
- package/lib/mem-db/method/model/drop.js +0 -5
- package/lib/mem-db/method/model/exists.js +0 -5
- package/lib/mem-db/method/record/create.js +0 -12
- package/lib/mem-db/method/record/find.js +0 -20
- package/lib/mem-db/method/record/get.js +0 -9
- package/lib/mem-db/method/record/remove.js +0 -13
- package/lib/mem-db/method/record/update.js +0 -15
- package/lib/mem-db/method/stat/count.js +0 -11
- package/lib/mem-db/start.js +0 -25
- package/lib/merge-attachment-info.js +0 -16
- package/lib/multi-rel-rows.js +0 -42
- package/lib/resolve-method.js +0 -16
- package/lib/sanitize-schema.js +0 -198
- package/lib/single-rel-rows.js +0 -38
- package/method/attachment/copy-uploaded.js +0 -34
- package/method/attachment/create.js +0 -29
- package/method/attachment/find.js +0 -27
- package/method/attachment/get-path.js +0 -12
- package/method/attachment/get.js +0 -12
- package/method/attachment/pre-check.js +0 -9
- package/method/attachment/remove.js +0 -11
- package/method/attachment/update.js +0 -7
- package/method/bulk/create.js +0 -46
- package/method/model/clear.js +0 -22
- package/method/model/create.js +0 -32
- package/method/model/drop.js +0 -31
- package/method/model/exists.js +0 -37
- package/method/record/clear.js +0 -24
- package/method/record/count.js +0 -66
- package/method/record/create.js +0 -111
- package/method/record/find-all.js +0 -41
- package/method/record/find-one.js +0 -70
- package/method/record/get.js +0 -89
- package/method/record/remove.js +0 -72
- package/method/record/update.js +0 -104
- package/method/record/upsert.js +0 -51
- package/method/sanitize/body.js +0 -85
- package/method/sanitize/date.js +0 -27
- package/method/sanitize/id.js +0 -17
- package/method/stat/aggregate.js +0 -23
- package/method/stat/histogram.js +0 -26
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import nql from '@tryghost/nql'
|
|
3
|
+
|
|
4
|
+
export async function execHook (name, ...args) {
|
|
5
|
+
const { runHook } = this.app.bajo
|
|
6
|
+
const { camelCase, last } = this.app.lib._
|
|
7
|
+
const { noHook } = last(args)
|
|
8
|
+
const { ns } = this.app.dobo
|
|
9
|
+
if (!noHook) {
|
|
10
|
+
await runHook(`${ns}:${name}`, this.name, ...args)
|
|
11
|
+
await runHook(`${ns}.${camelCase(this.name)}:${name}`, ...args)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function execModelHook (name, ...args) {
|
|
16
|
+
const { last } = this.app.lib._
|
|
17
|
+
const { noModelHook } = last(args)
|
|
18
|
+
const results = []
|
|
19
|
+
if (!noModelHook) {
|
|
20
|
+
const hooks = this.hooks.filter(hook => hook.name === name)
|
|
21
|
+
for (const hook of hooks) {
|
|
22
|
+
await hook.handler.call(this, ...args)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return results
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function execValidation (body, options = {}) {
|
|
29
|
+
const { uniq } = this.app.lib._
|
|
30
|
+
const { validation = {}, extFields = [], partial, req } = options
|
|
31
|
+
const fields = uniq([...Object.keys(body), ...(options.fields ?? [])])
|
|
32
|
+
await execHook.call(this, 'beforeRecordValidation', body, options)
|
|
33
|
+
const result = await this.validate(body, validation, { fields, extFields, partial, req })
|
|
34
|
+
await execHook.call(this, 'afterRecordValidation', body, result, options)
|
|
35
|
+
return result
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Break any reference to the original and get the new options
|
|
40
|
+
*
|
|
41
|
+
* @param {Object} options
|
|
42
|
+
* @returns {Object}
|
|
43
|
+
*/
|
|
44
|
+
export async function getFilterAndOptions (filter = {}, options = {}, action) {
|
|
45
|
+
const { cloneDeep, omit } = this.app.lib._
|
|
46
|
+
const keys = ['req', 'reply']
|
|
47
|
+
const nFilter = cloneDeep(omit(filter, keys))
|
|
48
|
+
const nOptions = cloneDeep(omit(options, keys))
|
|
49
|
+
nOptions.action = action
|
|
50
|
+
nOptions.dataOnly = false
|
|
51
|
+
nOptions.truncateString = nOptions.truncateString ?? false
|
|
52
|
+
nOptions.throwNotFound = nOptions.throwNotFound ?? true
|
|
53
|
+
for (const key of keys) {
|
|
54
|
+
nOptions[key] = options[key]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
nFilter.query = buildFilterQuery.call(this, nFilter, nOptions) ?? {}
|
|
58
|
+
nFilter.match = buildFilterMatch.call(this, nFilter, nOptions) ?? {}
|
|
59
|
+
handleRegexInQuery.call(this, nFilter)
|
|
60
|
+
const { limit, page, skip, sort } = preparePagination.call(this, nFilter, nOptions)
|
|
61
|
+
nFilter.limit = limit
|
|
62
|
+
nFilter.page = page
|
|
63
|
+
nFilter.skip = skip
|
|
64
|
+
nFilter.sort = sort
|
|
65
|
+
if (nOptions.queryHandler) {
|
|
66
|
+
const scope = nOptions.req ? this.app[nOptions.req.ns] : this.plugin
|
|
67
|
+
nFilter.query = await options.queryHandler.call(scope, nFilter.query, nOptions.req)
|
|
68
|
+
}
|
|
69
|
+
return { filter: nFilter, options: nOptions }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function handleReq (id, trigger, options = {}) {
|
|
73
|
+
const { upperFirst } = this.app.lib._
|
|
74
|
+
if (options.req) {
|
|
75
|
+
if (options.req.file && trigger !== 'removed') await handleAttachmentUpload.call(this, id, trigger, options)
|
|
76
|
+
if (options.req.flash && !options.noFlash) options.req.flash('notify', options.req.t(`record${upperFirst(trigger)}`))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function mergeAttachmentInfo (rec, source, options = {}) {
|
|
81
|
+
if (!this.app.waibu) return
|
|
82
|
+
const { mimeType, stats, fullPath } = options
|
|
83
|
+
const { importPkg } = this.app.bajo
|
|
84
|
+
const { fs } = this.app.lib
|
|
85
|
+
const { pick } = this.app.lib._
|
|
86
|
+
const mime = await importPkg('waibu:mime')
|
|
87
|
+
|
|
88
|
+
if (mimeType) rec.mimeType = mime.getType(rec.file)
|
|
89
|
+
if (fullPath) rec.fullPath = source
|
|
90
|
+
if (stats) {
|
|
91
|
+
const s = fs.statSync(source)
|
|
92
|
+
rec.stats = pick(s, ['size', 'atime', 'ctime', 'mtime'])
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function getAttachmentPath (id, fieldName, file, options = {}) {
|
|
97
|
+
const { getPluginDataDir } = this.app.bajo
|
|
98
|
+
const { fs } = this.app.lib
|
|
99
|
+
const dir = `${getPluginDataDir(this.app.dobo.ns)}/attachment/${this.name}/${id}`
|
|
100
|
+
if (options.dirOnly) return dir
|
|
101
|
+
const path = fieldName ? `${dir}/${fieldName}/${file}` : `${dir}/${file}`
|
|
102
|
+
if (!fs.existsSync(path)) throw this.app.dobo.error('notFound')
|
|
103
|
+
return path
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function copyAttachment (id, options = {}) {
|
|
107
|
+
if (!this.app.waibu) return
|
|
108
|
+
if (!this.attachment) return
|
|
109
|
+
const { fs } = this.app.lib
|
|
110
|
+
const { req, setField, setFile, mimeType, stats } = options
|
|
111
|
+
const { dir, files } = await this.app.waibu.getUploadedFiles(req.id, false, true)
|
|
112
|
+
const result = []
|
|
113
|
+
if (files.length === 0) return result
|
|
114
|
+
for (const f of files) {
|
|
115
|
+
let [fieldName, ...parts] = path.basename(f).split('@')
|
|
116
|
+
if (parts.length === 0) continue
|
|
117
|
+
fieldName = setField ?? fieldName
|
|
118
|
+
const file = setFile ?? parts.join('@')
|
|
119
|
+
const opts = { source: f, fieldName, file, mimeType, stats, req, silent: true }
|
|
120
|
+
const rec = await this.createAttachment(id, opts)
|
|
121
|
+
if (!rec) continue
|
|
122
|
+
delete rec.dir
|
|
123
|
+
result.push(rec)
|
|
124
|
+
if (setField || setFile) break
|
|
125
|
+
}
|
|
126
|
+
fs.removeSync(dir)
|
|
127
|
+
return result
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function handleAttachmentUpload (id, trigger, options = {}) {
|
|
131
|
+
if (!this.attachment) return
|
|
132
|
+
const { getPluginDataDir } = this.app.bajo
|
|
133
|
+
const { fs } = this.app.lib
|
|
134
|
+
const { req, mimeType, stats, setFile, setField } = options
|
|
135
|
+
if (trigger === 'removed') {
|
|
136
|
+
const dir = `${getPluginDataDir(this.app.dobo.ns)}/attachment/${this.name}/${id}`
|
|
137
|
+
await fs.remove(dir)
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
return copyAttachment.call(this, id, { req, mimeType, stats, setFile, setField })
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function getSingleRef (record = {}, options = {}) {
|
|
144
|
+
const { isSet } = this.app.lib.aneka
|
|
145
|
+
const props = this.properties.filter(p => isSet(p.ref) && !(options.hidden ?? []).includes(p.name))
|
|
146
|
+
const refs = {}
|
|
147
|
+
options.refs = options.refs ?? []
|
|
148
|
+
if (props.length > 0) {
|
|
149
|
+
for (const prop of props) {
|
|
150
|
+
for (const key in prop.ref) {
|
|
151
|
+
if (!((typeof options.refs === 'string' && ['*', 'all'].includes(options.refs)) || options.refs.includes(key))) continue
|
|
152
|
+
const ref = prop.ref[key]
|
|
153
|
+
if (ref.fields.length === 0) continue
|
|
154
|
+
const rModel = this.plugin.getModel(key)
|
|
155
|
+
const query = {}
|
|
156
|
+
query[ref.propName] = record[prop.name]
|
|
157
|
+
if (ref.propName === 'id') query[ref.propName] = this.sanitizeId(query[ref.propName])
|
|
158
|
+
const rFilter = { query }
|
|
159
|
+
const rOptions = { dataOnly: true, refs: [] }
|
|
160
|
+
const results = await rModel.findRecord(rFilter, rOptions)
|
|
161
|
+
const fields = [...ref.fields]
|
|
162
|
+
const data = []
|
|
163
|
+
for (const res of results) {
|
|
164
|
+
data.push(await rModel.sanitizeRecord(res, { fields }))
|
|
165
|
+
}
|
|
166
|
+
refs[key] = ['1:1'].includes(ref.type) ? data[0] : data
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
record._ref = refs
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function getMultiRefs (records = [], options = {}) {
|
|
174
|
+
const { isSet } = this.app.lib.aneka
|
|
175
|
+
const { uniq, map } = this.app.lib._
|
|
176
|
+
const props = this.properties.filter(p => isSet(p.ref) && !(options.hidden ?? []).includes(p.name))
|
|
177
|
+
options.refs = options.refs ?? []
|
|
178
|
+
if (props.length > 0) {
|
|
179
|
+
for (const prop of props) {
|
|
180
|
+
for (const key in prop.ref) {
|
|
181
|
+
if (!((typeof options.refs === 'string' && ['*', 'all'].includes(options.refs)) || options.refs.includes(key))) continue
|
|
182
|
+
const ref = prop.ref[key]
|
|
183
|
+
if (ref.fields.length === 0) continue
|
|
184
|
+
if (ref.type !== '1:1') continue
|
|
185
|
+
const rModel = this.plugin.getModel(key)
|
|
186
|
+
const matches = uniq(map(records, r => {
|
|
187
|
+
let v = r[prop.name]
|
|
188
|
+
if (ref.propName === 'id') v = this.sanitizeId(v)
|
|
189
|
+
return v
|
|
190
|
+
}))
|
|
191
|
+
const query = {}
|
|
192
|
+
query[ref.propName] = { $in: matches }
|
|
193
|
+
const rFilter = { query, limit: matches.length }
|
|
194
|
+
const rOptions = { dataOnly: true, refs: [] }
|
|
195
|
+
const results = await rModel.findRecord(rFilter, rOptions)
|
|
196
|
+
const fields = [...ref.fields]
|
|
197
|
+
if (!fields.includes(prop.name)) fields.push(prop.name)
|
|
198
|
+
for (const i in records) {
|
|
199
|
+
records[i]._ref = records[i]._ref ?? {}
|
|
200
|
+
const rec = records[i]
|
|
201
|
+
const res = results.find(res => (res[ref.propName] + '') === rec[prop.name] + '')
|
|
202
|
+
if (res) records[i]._ref[key] = await rModel.sanitizeRecord(res, { fields })
|
|
203
|
+
else records[i]._ref[key] = {}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function buildFilterQuery (filter = {}, options = {}) {
|
|
211
|
+
const { trim, find, isString, isPlainObject } = this.app.lib._
|
|
212
|
+
let query = {}
|
|
213
|
+
if (isString(filter.query)) {
|
|
214
|
+
try {
|
|
215
|
+
filter.query = trim(filter.query)
|
|
216
|
+
filter.orgQuery = filter.query
|
|
217
|
+
if (trim(filter.query).startsWith('{')) query = JSON.parse(filter.query)
|
|
218
|
+
else if (filter.query.includes(':')) query = nql(filter.query).parse()
|
|
219
|
+
else {
|
|
220
|
+
const fields = this.sortables.filter(f => {
|
|
221
|
+
const field = find(this.properties, { name: f, type: 'string' })
|
|
222
|
+
return !!field
|
|
223
|
+
})
|
|
224
|
+
const parts = fields.map(f => {
|
|
225
|
+
if (filter.query[0] === '*') return `${f}:~$'${filter.query.replaceAll('*', '')}'`
|
|
226
|
+
if (filter.query[filter.length - 1] === '*') return `${f}:~^'${filter.query.replaceAll('*', '')}'`
|
|
227
|
+
return `${f}:~'${filter.query.replaceAll('*', '')}'`
|
|
228
|
+
})
|
|
229
|
+
if (parts.length === 1) query = nql(parts[0]).parse()
|
|
230
|
+
else if (parts.length > 1) query = nql(parts.join(',')).parse()
|
|
231
|
+
}
|
|
232
|
+
} catch (err) {
|
|
233
|
+
this.app.dobo.error('invalidQuery', { orgMessage: err.message })
|
|
234
|
+
}
|
|
235
|
+
} else if (isPlainObject(filter.query)) query = filter.query
|
|
236
|
+
return sanitizeQuery.call(this, query)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function sanitizeQuery (query = {}, parent) {
|
|
240
|
+
const { cloneDeep, isPlainObject, isArray, find } = this.app.lib._
|
|
241
|
+
const { isSet } = this.app.lib.aneka
|
|
242
|
+
const { dayjs } = this.app.lib
|
|
243
|
+
const obj = cloneDeep(query)
|
|
244
|
+
const keys = Object.keys(obj)
|
|
245
|
+
const sanitize = (key, val, p) => {
|
|
246
|
+
if (!isSet(val)) return val
|
|
247
|
+
const prop = find(this.properties, { name: key.startsWith('$') ? p : key })
|
|
248
|
+
if (!prop) return val
|
|
249
|
+
if (['datetime', 'date', 'time'].includes(prop.type)) {
|
|
250
|
+
const dt = dayjs(val)
|
|
251
|
+
return dt.isValid() ? dt.toDate() : val
|
|
252
|
+
} else if (['smallint', 'integer'].includes(prop.type)) return parseInt(val) || val
|
|
253
|
+
else if (['float', 'double'].includes(prop.type)) return parseFloat(val) || val
|
|
254
|
+
else if (['boolean'].includes(prop.type)) return !!val
|
|
255
|
+
return val
|
|
256
|
+
}
|
|
257
|
+
keys.forEach(k => {
|
|
258
|
+
const v = obj[k]
|
|
259
|
+
if (isPlainObject(v)) obj[k] = sanitizeQuery.call(this, v, k)
|
|
260
|
+
else if (isArray(v)) {
|
|
261
|
+
v.forEach((i, idx) => {
|
|
262
|
+
if (isPlainObject(i)) obj[k][idx] = sanitizeQuery.call(this, i, k)
|
|
263
|
+
})
|
|
264
|
+
} else obj[k] = sanitize(k, v, parent)
|
|
265
|
+
})
|
|
266
|
+
return obj
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function buildFilterMatch (filter = {}, options = {}) {
|
|
270
|
+
const { isPlainObject, trim, has, uniq } = this.app.lib._
|
|
271
|
+
let input = filter.match
|
|
272
|
+
if (isPlainObject(input)) return input
|
|
273
|
+
const split = (value) => {
|
|
274
|
+
let [field, val] = value.split(':').map(i => i.trim())
|
|
275
|
+
if (!val) {
|
|
276
|
+
val = field
|
|
277
|
+
field = '*'
|
|
278
|
+
}
|
|
279
|
+
return { field, value: val }
|
|
280
|
+
}
|
|
281
|
+
input = trim(input)
|
|
282
|
+
let items = {}
|
|
283
|
+
if (isPlainObject(input)) items = input
|
|
284
|
+
else if (input[0] === '{') {
|
|
285
|
+
try {
|
|
286
|
+
items = JSON.parse(input)
|
|
287
|
+
} catch (err) {}
|
|
288
|
+
} else {
|
|
289
|
+
for (const item of input.split('+').map(i => i.trim())) {
|
|
290
|
+
const part = split(item, ' ')
|
|
291
|
+
if (!items[part.field]) items[part.field] = []
|
|
292
|
+
items[part.field].push(...part.value.split(' ').filter(v => ![''].includes(v)))
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const matcher = {}
|
|
296
|
+
for (const index of this.indexes.filter(i => i.type === 'fulltext')) {
|
|
297
|
+
for (const f of index.fields) {
|
|
298
|
+
const value = []
|
|
299
|
+
if (typeof items[f] === 'string') items[f] = [items[f]]
|
|
300
|
+
if (has(items, f)) value.push(...items[f])
|
|
301
|
+
if (!matcher[f]) matcher[f] = []
|
|
302
|
+
matcher[f] = uniq([...matcher[f], ...value])
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (has(items, '*')) matcher['*'] = items['*']
|
|
306
|
+
return matcher
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function handleRegexInQuery (filter) {
|
|
310
|
+
if (this.driver.idField.name !== 'id') {
|
|
311
|
+
const query = JSON.stringify(filter.query ?? {}, (key, value) => {
|
|
312
|
+
if (value instanceof RegExp) return ['__REGEXP__', value.source, value.flags]
|
|
313
|
+
return value
|
|
314
|
+
}).replaceAll('"id"', `"${this.driver.idField.name}"`)
|
|
315
|
+
try {
|
|
316
|
+
filter.query = JSON.parse(query, (key, value) => {
|
|
317
|
+
if (Array.isArray(value) && value[0] === '__REGEXP__') return new RegExp(value[1], value[2])
|
|
318
|
+
return value
|
|
319
|
+
})
|
|
320
|
+
} catch (err) {}
|
|
321
|
+
const match = JSON.stringify(filter.match ?? {}).replaceAll('"id"', `"${this.driver.idField.name}"`)
|
|
322
|
+
try {
|
|
323
|
+
filter.match = JSON.parse(match)
|
|
324
|
+
} catch (err) {}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Prepare records pagination:
|
|
330
|
+
* - making sure records limit is obeyed
|
|
331
|
+
* - making sure page is a positive value
|
|
332
|
+
* - if skip is given, recalculate limit to use skip instead of page number
|
|
333
|
+
* - Build sort info
|
|
334
|
+
*
|
|
335
|
+
* @method
|
|
336
|
+
* @async
|
|
337
|
+
* @param {Object} [filter={}] - Filter object
|
|
338
|
+
* @param {Object} options - Options
|
|
339
|
+
* @returns {TRecordPagination}
|
|
340
|
+
*/
|
|
341
|
+
export function preparePagination (filter = {}, options = {}) {
|
|
342
|
+
const { isEmpty, map, each, isPlainObject, isString, trim, keys } = this.app.lib._
|
|
343
|
+
const config = this.app.dobo.config
|
|
344
|
+
|
|
345
|
+
const buildPageSkipLimit = (filter) => {
|
|
346
|
+
let limit = parseInt(filter.limit) || config.default.filter.limit
|
|
347
|
+
if (limit === -1) limit = config.default.filter.maxLimit
|
|
348
|
+
if (limit > config.default.filter.maxLimit) limit = config.default.filter.maxLimit
|
|
349
|
+
if (limit < 1) limit = 1
|
|
350
|
+
let page = parseInt(filter.page) || 1
|
|
351
|
+
if (page < 1) page = 1
|
|
352
|
+
let skip = (page - 1) * limit
|
|
353
|
+
if (filter.skip) {
|
|
354
|
+
skip = parseInt(filter.skip) || skip
|
|
355
|
+
page = undefined
|
|
356
|
+
}
|
|
357
|
+
if (skip < 0) skip = 0
|
|
358
|
+
return { page, skip, limit }
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const buildSort = (input, allowSortUnindexed) => {
|
|
362
|
+
let sort
|
|
363
|
+
if (isEmpty(input)) {
|
|
364
|
+
const columns = map(this.properties ?? [], 'name')
|
|
365
|
+
each(config.default.filter.sort, s => {
|
|
366
|
+
const [col] = s.split(':')
|
|
367
|
+
if (columns.includes(col)) {
|
|
368
|
+
input = s
|
|
369
|
+
return false
|
|
370
|
+
}
|
|
371
|
+
})
|
|
372
|
+
}
|
|
373
|
+
if (!isEmpty(input)) {
|
|
374
|
+
if (isPlainObject(input)) sort = input
|
|
375
|
+
else if (isString(input)) {
|
|
376
|
+
const item = {}
|
|
377
|
+
each(input.split('+'), text => {
|
|
378
|
+
let [col, dir] = map(trim(text).split(':'), i => trim(i))
|
|
379
|
+
dir = (dir ?? '').toUpperCase()
|
|
380
|
+
dir = dir === 'DESC' ? -1 : parseInt(dir) || 1
|
|
381
|
+
item[col] = dir / Math.abs(dir)
|
|
382
|
+
})
|
|
383
|
+
sort = item
|
|
384
|
+
}
|
|
385
|
+
const items = keys(sort)
|
|
386
|
+
each(items, i => {
|
|
387
|
+
if (!this.sortables.includes(i) && !allowSortUnindexed) throw this.app.dobo.error('sortOnUnindexedField%s%s', i, this.name)
|
|
388
|
+
// if (model.fullText.fields.includes(i)) throw this.error('Can\'t sort on full-text index: \'%s@%s\'', i, model.name)
|
|
389
|
+
})
|
|
390
|
+
}
|
|
391
|
+
return sort
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const { page, skip, limit } = buildPageSkipLimit(filter)
|
|
395
|
+
let sortInput = filter.sort
|
|
396
|
+
try {
|
|
397
|
+
sortInput = JSON.parse(sortInput)
|
|
398
|
+
} catch (err) {
|
|
399
|
+
}
|
|
400
|
+
const sort = buildSort(sortInput, options.allowSortUnindexed)
|
|
401
|
+
return { limit, page, skip, sort }
|
|
402
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getFilterAndOptions, execHook, execModelHook } from './_util.js'
|
|
2
|
+
const action = 'build'
|
|
3
|
+
|
|
4
|
+
async function build (opts = {}) {
|
|
5
|
+
const { dataOnly = true } = opts
|
|
6
|
+
const { options } = await getFilterAndOptions.call(this, null, opts, action)
|
|
7
|
+
await execHook.call(this, 'beforeBuildModel', options)
|
|
8
|
+
await execModelHook.call(this, 'beforeBuildModel', options)
|
|
9
|
+
const result = (await this.driver._buildModel(this, options)) ?? {}
|
|
10
|
+
await execModelHook.call(this, 'afterBuildModel', result, options)
|
|
11
|
+
await execHook.call(this, 'afterBuildModel', result, options)
|
|
12
|
+
return dataOnly ? result.data : result
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default build
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getFilterAndOptions, execHook, execModelHook } from './_util.js'
|
|
2
|
+
const action = 'clearRecord'
|
|
3
|
+
|
|
4
|
+
async function clearRecord (...args) {
|
|
5
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
6
|
+
const [opts = {}] = args
|
|
7
|
+
const { dataOnly = true } = opts
|
|
8
|
+
const { options } = await getFilterAndOptions.call(this, null, opts, action)
|
|
9
|
+
await execHook.call(this, 'beforeClearRecord', options)
|
|
10
|
+
await execModelHook.call(this, 'beforeClearRecord', options)
|
|
11
|
+
const result = (await this.driver._clearRecord(this, options)) ?? {}
|
|
12
|
+
await execModelHook.call(this, 'afterClearRecord', result, options)
|
|
13
|
+
await execHook.call(this, 'afterClearRecord', result, options)
|
|
14
|
+
return dataOnly ? result.data : result
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default clearRecord
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getFilterAndOptions, execHook, execModelHook } from './_util.js'
|
|
2
|
+
const action = 'countRecord'
|
|
3
|
+
|
|
4
|
+
async function countRecord (...args) {
|
|
5
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
6
|
+
const [params = {}, opts = {}] = args
|
|
7
|
+
const { dataOnly = true } = opts
|
|
8
|
+
const { filter, options } = await getFilterAndOptions.call(this, params, opts, action)
|
|
9
|
+
await execHook.call(this, 'beforeCountRecord', options)
|
|
10
|
+
await execModelHook.call(this, 'beforeCountRecord', filter, options)
|
|
11
|
+
const result = (await this.driver._countRecord(this, filter, options)) ?? {}
|
|
12
|
+
await execModelHook.call(this, 'afterCountRecord', filter, result, options)
|
|
13
|
+
await execHook.call(this, 'afterCountRecord', filter, result, options)
|
|
14
|
+
return dataOnly ? result.data : result
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default countRecord
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getFilterAndOptions, execHook, execModelHook } from './_util.js'
|
|
2
|
+
const action = 'createAggregate'
|
|
3
|
+
|
|
4
|
+
async function createAggregate (...args) {
|
|
5
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
6
|
+
const [_filter = {}, params = {}, opts = {}] = args
|
|
7
|
+
const { dataOnly = true } = opts
|
|
8
|
+
const { filter, options } = await getFilterAndOptions.call(this, _filter, opts, action)
|
|
9
|
+
await execHook.call(this, 'beforeCreateAggregate', filter, params, options)
|
|
10
|
+
await execModelHook.call(this, 'beforeCreateAggregate', filter, params, options)
|
|
11
|
+
const result = (await this.driver._createAggregate(this, filter, params, options)) ?? {}
|
|
12
|
+
await execModelHook.call(this, 'afterCreateAggregate', filter, params, result, options)
|
|
13
|
+
await execHook.call(this, 'afterCreateAggregate', filter, params, result, options)
|
|
14
|
+
return dataOnly ? result.data : result
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default createAggregate
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { mergeAttachmentInfo, getAttachmentPath } from './_util.js'
|
|
2
|
+
const action = 'createAttachment'
|
|
3
|
+
|
|
4
|
+
async function createAttachment (...args) {
|
|
5
|
+
if (!this.attachment) return
|
|
6
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
7
|
+
const [id, opts = {}] = args
|
|
8
|
+
const { fs } = this.app.lib
|
|
9
|
+
const { isEmpty } = this.app.lib._
|
|
10
|
+
const { source, fieldName = 'file', file, fullPath, stats, mimeType, req } = opts
|
|
11
|
+
if (isEmpty(file)) return
|
|
12
|
+
if (!source) throw this.plugin.error('isMissing%s', this.plugin.t('field.source'))
|
|
13
|
+
const baseDir = await getAttachmentPath.call(this, id, fieldName, file, { dirOnly: true })
|
|
14
|
+
let dir = `${baseDir}/${fieldName}`
|
|
15
|
+
if ((fieldName || '').endsWith('[]')) dir = `${baseDir}/${fieldName.replace('[]', '')}`
|
|
16
|
+
const dest = `${dir}/${file}`.replaceAll('//', '/')
|
|
17
|
+
await fs.ensureDir(dir)
|
|
18
|
+
await fs.copy(source, dest)
|
|
19
|
+
const rec = {
|
|
20
|
+
field: fieldName === '' ? undefined : fieldName,
|
|
21
|
+
dir,
|
|
22
|
+
file
|
|
23
|
+
}
|
|
24
|
+
await mergeAttachmentInfo.call(this, rec, dest, { mimeType, fullPath, stats })
|
|
25
|
+
if (!opts.noFlash && req && req.flash) req.flash('notify', req.t('attachmentUploaded'))
|
|
26
|
+
return rec
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default createAttachment
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getFilterAndOptions, execHook, execModelHook } from './_util.js'
|
|
2
|
+
const action = 'createHistogram'
|
|
3
|
+
|
|
4
|
+
async function createHistogram (...args) {
|
|
5
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
6
|
+
const [_filter = {}, params = {}, opts = {}] = args
|
|
7
|
+
const { dataOnly = true } = opts
|
|
8
|
+
const { filter, options } = await getFilterAndOptions.call(this, _filter, opts, action)
|
|
9
|
+
await execHook.call(this, 'beforeCreateHistogram', filter, params, options)
|
|
10
|
+
await execModelHook.call(this, 'beforeCreateHistogram', filter, params, options)
|
|
11
|
+
const result = (await this.driver._createHistogram(this, filter, params, options)) ?? {}
|
|
12
|
+
await execModelHook.call(this, 'afterCreateHistogram', filter, params, result, options)
|
|
13
|
+
await execHook.call(this, 'afterCreateHistogram', filter, params, result, options)
|
|
14
|
+
return dataOnly ? result.data : result
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default createHistogram
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { getFilterAndOptions, execHook, execValidation, execModelHook, getSingleRef, handleReq } from './_util.js'
|
|
2
|
+
|
|
3
|
+
export const onlyTypes = ['datetime', 'date', 'time', 'timestamp']
|
|
4
|
+
const action = 'createRecord'
|
|
5
|
+
|
|
6
|
+
async function createRecord (...args) {
|
|
7
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
8
|
+
const [body = {}, opts = {}] = args
|
|
9
|
+
const { isSet } = this.app.lib.aneka
|
|
10
|
+
const { runHook } = this.app.bajo
|
|
11
|
+
const { cloneDeep, get } = this.app.lib._
|
|
12
|
+
const { dataOnly = true } = opts
|
|
13
|
+
const { options } = await getFilterAndOptions.call(this, null, opts, action)
|
|
14
|
+
const { truncateString, noResult, noBodySanitizer, noResultSanitizer, noValidation } = options
|
|
15
|
+
const extFields = get(options, 'validation.extFields', [])
|
|
16
|
+
const input = noBodySanitizer ? cloneDeep(body) : await this.sanitizeBody({ body, extFields, strict: true, truncateString, onlyTypes })
|
|
17
|
+
await execHook.call(this, 'beforeCreateRecord', input, options)
|
|
18
|
+
await execModelHook.call(this, 'beforeCreateRecord', input, options)
|
|
19
|
+
if (!noValidation) await execValidation.call(this, input, options)
|
|
20
|
+
let result = options.record ?? (await this.driver._createRecord(this, input, options)) ?? {}
|
|
21
|
+
if (noResult) {
|
|
22
|
+
await runHook('cache:clear', this, 'create', body)
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
result = result ?? {}
|
|
26
|
+
if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
|
|
27
|
+
if (isSet(options.refs)) await getSingleRef.call(this, { record: result.data, options })
|
|
28
|
+
await handleReq.call(this, result.data.id, 'created', options)
|
|
29
|
+
await execModelHook.call(this, 'afterCreateRecord', input, result, options)
|
|
30
|
+
await execHook.call(this, 'afterCreateRecord', input, result, options)
|
|
31
|
+
await runHook('cache:clear', this, 'create', body, result)
|
|
32
|
+
return dataOnly ? result.data : result
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default createRecord
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getFilterAndOptions, execHook, execModelHook } from './_util.js'
|
|
2
|
+
const action = 'drop'
|
|
3
|
+
|
|
4
|
+
async function drop (opts = {}) {
|
|
5
|
+
const { dataOnly = true } = opts
|
|
6
|
+
const { options } = await getFilterAndOptions.call(this, null, opts, action)
|
|
7
|
+
await execHook.call(this, 'beforeDropModel', options)
|
|
8
|
+
await execModelHook.call(this, 'beforeDropModel', options)
|
|
9
|
+
const result = (await this.driver._dropModel(this, options)) ?? {}
|
|
10
|
+
await execModelHook.call(this, 'afterDropModel', result, options)
|
|
11
|
+
await execHook.call(this, 'afterModelDrop', result, options)
|
|
12
|
+
return dataOnly ? result.data : result
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default drop
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { getFilterAndOptions, execHook, execModelHook } from './_util.js'
|
|
2
|
+
const action = 'modelExists'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Method to check if the underlaying table/collection exists already
|
|
6
|
+
*
|
|
7
|
+
* @param {Object} [options]
|
|
8
|
+
* @returns {Object}
|
|
9
|
+
*/
|
|
10
|
+
async function isExists (opts = {}) {
|
|
11
|
+
const { dataOnly = true } = opts
|
|
12
|
+
const { options } = await getFilterAndOptions.call(this, null, opts, action)
|
|
13
|
+
await execHook.call(this, 'beforeModelExists', options)
|
|
14
|
+
await execModelHook.call(this, 'beforeModelExists', options)
|
|
15
|
+
const result = (await this.driver._modelExists(this, options)) ?? {}
|
|
16
|
+
await execModelHook.call(this, 'afterModelExists', result, options)
|
|
17
|
+
await execHook.call(this, 'afterModelExists', result, options)
|
|
18
|
+
return dataOnly ? result.data : result
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default isExists
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { getMultiRefs, execHook, execModelHook, getFilterAndOptions } from './_util.js'
|
|
2
|
+
const action = 'findAllRecord'
|
|
3
|
+
|
|
4
|
+
async function native (filter, options, dataOnly) {
|
|
5
|
+
const { isSet } = this.app.lib.aneka
|
|
6
|
+
const { get, set } = this.plugin.cache ?? {}
|
|
7
|
+
if (dataOnly) options.count = false
|
|
8
|
+
let { noResultSanitizer, noCache } = options
|
|
9
|
+
if (!this.cacheable) noCache = true
|
|
10
|
+
await execHook.call(this, 'beforeFindAllRecord', filter, options)
|
|
11
|
+
await execModelHook.call(this, 'beforeFindAllRecord', filter, options)
|
|
12
|
+
if (get && !noCache && !options.record) {
|
|
13
|
+
const cachedResult = await get({ model: this.name, filter, options })
|
|
14
|
+
if (cachedResult) {
|
|
15
|
+
cachedResult.cached = true
|
|
16
|
+
await execModelHook.call(this, 'afterFindAllRecord', filter, cachedResult, options)
|
|
17
|
+
return dataOnly ? cachedResult.data : cachedResult
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const result = options.record ?? (await this.driver._findAllRecord(this, filter, options)) ?? {}
|
|
21
|
+
if (!noResultSanitizer) {
|
|
22
|
+
for (const idx in result.data) {
|
|
23
|
+
result.data[idx] = await this.sanitizeRecord(result.data[idx], options)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (isSet(options.refs)) await getMultiRefs.call(this, { records: result.data, options })
|
|
27
|
+
await execModelHook.call(this, 'afterFindAllRecord', filter, result, options)
|
|
28
|
+
await execHook.call(this, 'afterFindAllRecord', filter, result, options)
|
|
29
|
+
if (set && !noCache) await set({ model: this.name, filter, options, result })
|
|
30
|
+
return dataOnly ? result.data : result
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function loop (params, opts, dataOnly) {
|
|
34
|
+
const { cloneDeep } = this.app.lib._
|
|
35
|
+
const { filter, options } = await getFilterAndOptions.call(this, params, opts, action)
|
|
36
|
+
const { maxLimit, hardLimit } = this.app.dobo.config.default.filter
|
|
37
|
+
const nFilter = cloneDeep(filter)
|
|
38
|
+
const nOptions = cloneDeep(options)
|
|
39
|
+
nOptions.count = false
|
|
40
|
+
nOptions.dataOnly = false
|
|
41
|
+
nFilter.limit = maxLimit
|
|
42
|
+
nFilter.page = 1
|
|
43
|
+
let count = 0
|
|
44
|
+
const data = []
|
|
45
|
+
for (;;) {
|
|
46
|
+
const result = await this.findRecord(nFilter, nOptions)
|
|
47
|
+
if (result.data.length === 0) break
|
|
48
|
+
if (count + result.data.length > hardLimit) {
|
|
49
|
+
const sliced = result.data.slice(0, hardLimit - count)
|
|
50
|
+
data.push(...sliced)
|
|
51
|
+
break
|
|
52
|
+
}
|
|
53
|
+
data.push(...result.data)
|
|
54
|
+
count = count + result.data.length
|
|
55
|
+
nFilter.page++
|
|
56
|
+
}
|
|
57
|
+
return dataOnly ? data : { data, count }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function findAllRecord (...args) {
|
|
61
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
62
|
+
const [params = {}, opts = {}] = args
|
|
63
|
+
const { dataOnly = true } = opts
|
|
64
|
+
if (this.driver.findAllRecord) {
|
|
65
|
+
const { filter, options } = await getFilterAndOptions.call(this, params, opts, action)
|
|
66
|
+
return await native.call(this, filter, options, dataOnly)
|
|
67
|
+
}
|
|
68
|
+
return await loop.call(this, params, opts, dataOnly)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default findAllRecord
|