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,29 @@
|
|
|
1
|
+
import { mergeAttachmentInfo } from './_util.js'
|
|
2
|
+
const action = 'findAttachment'
|
|
3
|
+
|
|
4
|
+
async function findAttachment (...args) {
|
|
5
|
+
if (!this.attachment) return
|
|
6
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
7
|
+
const [id, opts = {}] = args
|
|
8
|
+
const { fastGlob, fs } = this.app.lib
|
|
9
|
+
const { getPluginDataDir } = this.app.bajo
|
|
10
|
+
const dir = `${getPluginDataDir(this.ns)}/attachment/${this.name}/${id}`
|
|
11
|
+
if (!fs.existsSync(dir)) return []
|
|
12
|
+
const files = await fastGlob(`${dir}/**/*`)
|
|
13
|
+
const { fullPath, stats, mimeType } = opts
|
|
14
|
+
const recs = []
|
|
15
|
+
for (const f of files) {
|
|
16
|
+
const item = f.replace(dir, '')
|
|
17
|
+
let [, fieldName, file] = item.split('/')
|
|
18
|
+
if (!file) {
|
|
19
|
+
file = fieldName
|
|
20
|
+
fieldName = null
|
|
21
|
+
}
|
|
22
|
+
const rec = { fieldName, file }
|
|
23
|
+
await mergeAttachmentInfo.call(this, rec, f, { mimeType, fullPath, stats })
|
|
24
|
+
recs.push(rec)
|
|
25
|
+
}
|
|
26
|
+
return recs
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default findAttachment
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const action = 'findOneRecord'
|
|
2
|
+
|
|
3
|
+
async function findOneRecord (...args) {
|
|
4
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
5
|
+
const [params = {}, opts = {}] = args
|
|
6
|
+
const { cloneDeep } = this.app.lib._
|
|
7
|
+
const { dataOnly = true } = opts
|
|
8
|
+
if (dataOnly) opts.count = false
|
|
9
|
+
const nFilter = cloneDeep(params)
|
|
10
|
+
const nOptions = cloneDeep(opts)
|
|
11
|
+
nOptions.count = false
|
|
12
|
+
nOptions.dataOnly = false
|
|
13
|
+
nFilter.limit = 1
|
|
14
|
+
const result = await this.findRecord(nFilter, nOptions)
|
|
15
|
+
const data = result.data[0]
|
|
16
|
+
return dataOnly ? data : { data }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default findOneRecord
|
|
@@ -1,115 +1,103 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* @
|
|
7
|
-
* @see Dobo#
|
|
8
|
-
* @see Dobo#
|
|
9
|
-
* @
|
|
10
|
-
* @property {
|
|
11
|
-
* @property {number}
|
|
12
|
-
* @property {number}
|
|
13
|
-
* @property {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* @
|
|
19
|
-
* @see Dobo#
|
|
20
|
-
* @see Dobo#
|
|
21
|
-
* @
|
|
22
|
-
* @property {
|
|
23
|
-
* @property {
|
|
24
|
-
* @property {number}
|
|
25
|
-
* @property {number}
|
|
26
|
-
* @property {number}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* @
|
|
32
|
-
* @see Dobo#
|
|
33
|
-
* @see Dobo#
|
|
34
|
-
* @
|
|
35
|
-
* @property {boolean} [
|
|
36
|
-
* @property {boolean} [
|
|
37
|
-
* @property {boolean} [
|
|
38
|
-
* @property {boolean} [
|
|
39
|
-
* @property {boolean} [
|
|
40
|
-
* @property {boolean} [
|
|
41
|
-
* @property {boolean} [
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* const {
|
|
52
|
-
* const
|
|
53
|
-
* const
|
|
54
|
-
* const
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* @
|
|
59
|
-
* @
|
|
60
|
-
* @
|
|
61
|
-
* @
|
|
62
|
-
* @name
|
|
63
|
-
* @param {
|
|
64
|
-
* @param {
|
|
65
|
-
* @
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const {
|
|
70
|
-
const {
|
|
71
|
-
const {
|
|
72
|
-
const {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
options.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
if (!
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
records.data[idx] = await this.pickRecord({ record: records.data[idx], fields, schema, hidden, forceNoHidden })
|
|
105
|
-
}
|
|
106
|
-
if (!noHook) {
|
|
107
|
-
await runHook(`${this.ns}.${camelCase(name)}:afterRecordFind`, filter, options, records)
|
|
108
|
-
await runHook(`${this.ns}:afterRecordFind`, name, filter, options, records)
|
|
109
|
-
}
|
|
110
|
-
if (set && !noCache) await set({ model: name, filter, options, records })
|
|
111
|
-
if (!noFeatureHook) await execFeatureHook.call(this, 'afterFind', { schema, filter, options, records })
|
|
112
|
-
return dataOnly ? records.data : records
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export default find
|
|
1
|
+
import { getFilterAndOptions, execHook, execModelHook, getMultiRefs } from './_util.js'
|
|
2
|
+
const action = 'findRecord'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Object} TRecordFilter
|
|
6
|
+
* @see Dobo#recordFind
|
|
7
|
+
* @see Dobo#recordFindOne
|
|
8
|
+
* @see Dobo#recordFindAll
|
|
9
|
+
* @property {(string|Object)} [query={}] - Query definition. See {@tutorial query-language} for more
|
|
10
|
+
* @property {number} limit - Max number of records per page
|
|
11
|
+
* @property {number} page - Which page is the returned records currently at
|
|
12
|
+
* @property {number} skip - Records to skip
|
|
13
|
+
* @property {TRecordSort} sort - Sort order info
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} TRecordFindResult
|
|
18
|
+
* @see Dobo#recordFind
|
|
19
|
+
* @see Dobo#recordFindAll
|
|
20
|
+
* @see Dobo#recordGet
|
|
21
|
+
* @property {Array.<Object>} data - Array of returned records
|
|
22
|
+
* @property {boolean} success - Whether operation is successfull or failed
|
|
23
|
+
* @property {number} page - Which page is the returned records currently at
|
|
24
|
+
* @property {number} limit - Max number of records per page
|
|
25
|
+
* @property {number} count - Total number of records returned
|
|
26
|
+
* @property {number} pages - Total number of pages returned
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} TRecordFindOptions
|
|
31
|
+
* @see Dobo#recordFind
|
|
32
|
+
* @see Dobo#recordFindOne
|
|
33
|
+
* @see Dobo#recordFindAll
|
|
34
|
+
* @property {boolean} [dataOnly=true] - If ```true``` (default) returns array of records. Otherwise {@link TFindRecordResult}
|
|
35
|
+
* @property {boolean} [count=false] - If ```true``` and ```dataOnly``` is also ```true```, the total number of records found will be returned
|
|
36
|
+
* @property {boolean} [noCache=true] - If ```true``` (default), result set won't be cached. This will overwrite model's ```cacheable``` property. Only applicable if {@link https://github.com/ardhi/bajo-cache|bajo-cache} is loaded
|
|
37
|
+
* @property {boolean} [noHook=false] - If ```true```, no model's hook will be executed
|
|
38
|
+
* @property {boolean} [noFeatureHook=false] - If ```true```, no model's feature hook will be executed
|
|
39
|
+
* @property {boolean} [fields=[]] - If not empty, return only these fields EXCLUDING hidden fields
|
|
40
|
+
* @property {boolean} [hidden=[]] - Additional fields to hide, in addition the one set in model's model
|
|
41
|
+
* @property {boolean} [forceNoHidden=false] - If ```true```, hidden fields will be ignored and ALL fields will be returned
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Find records by model's name and given filter
|
|
46
|
+
*
|
|
47
|
+
* Example: find records from model **CdbCountry** where its id is 'ID' or 'MY',
|
|
48
|
+
* sorted by ```name``` in ascending order and return only its ```id```, ```name``` and ```iso3```
|
|
49
|
+
* ```javascript
|
|
50
|
+
* const { recordFind } = this.app.dobo
|
|
51
|
+
* const query = { id: { $in: ['ID', 'MY'] } }
|
|
52
|
+
* const sort = { name: 1 }
|
|
53
|
+
* const fields = ['id', 'name', 'iso3']
|
|
54
|
+
* const result = await recordFind('CdbCountry', { query, sort }, { fields })
|
|
55
|
+
* ```
|
|
56
|
+
*
|
|
57
|
+
* @method
|
|
58
|
+
* @memberof Dobo
|
|
59
|
+
* @async
|
|
60
|
+
* @instance
|
|
61
|
+
* @name recordFind
|
|
62
|
+
* @param {string} name - Model's name
|
|
63
|
+
* @param {Object} [filter={}] - Filter object
|
|
64
|
+
* @param {TRecordFindOptions} [options={}]
|
|
65
|
+
* @returns {(TRecordFindResult|Array.<Object>)} Return ```array``` of records if ```options.dataOnly``` is set. {@link TRecordFindResult} otherwise
|
|
66
|
+
*/
|
|
67
|
+
async function findRecord (...args) {
|
|
68
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
69
|
+
const [params = {}, opts = {}] = args
|
|
70
|
+
const { isSet } = this.app.lib.aneka
|
|
71
|
+
const { runHook } = this.app.bajo
|
|
72
|
+
const { dataOnly = true } = opts
|
|
73
|
+
const { filter, options } = await getFilterAndOptions.call(this, params, opts, action)
|
|
74
|
+
if (dataOnly) options.count = false
|
|
75
|
+
let { noResultSanitizer, noCache } = options
|
|
76
|
+
if (!this.cacheable || !options.record) noCache = true
|
|
77
|
+
if (!noCache) {
|
|
78
|
+
try {
|
|
79
|
+
await runHook('cache:getByFilter', this, filter)
|
|
80
|
+
} catch (err) {
|
|
81
|
+
if (err.code === 'CACHED_RESULT') {
|
|
82
|
+
const result = err.payload
|
|
83
|
+
result.cache = true
|
|
84
|
+
return dataOnly ? result.data : result
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
await execHook.call(this, 'beforeFindRecord', filter, options)
|
|
89
|
+
await execModelHook.call(this, 'beforeFindRecord', filter, options)
|
|
90
|
+
const result = options.record ?? (await this.driver._findRecord(this, filter, options)) ?? {}
|
|
91
|
+
if (!noResultSanitizer) {
|
|
92
|
+
for (const idx in result.data) {
|
|
93
|
+
result.data[idx] = await this.sanitizeRecord(result.data[idx], options)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (isSet(options.refs)) await getMultiRefs.call(this, { records: result.data, options })
|
|
97
|
+
await execModelHook.call(this, 'afterFindRecord', filter, result, options)
|
|
98
|
+
await execHook.call(this, 'afterFindRecord', filter, result, options)
|
|
99
|
+
if (!noCache) await runHook('cache:setByFilter', this, filter, result)
|
|
100
|
+
return dataOnly ? result.data : result
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export default findRecord
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const action = 'getAttachment'
|
|
2
|
+
|
|
3
|
+
async function getAttachment (...args) {
|
|
4
|
+
if (!this.attachment) return
|
|
5
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
6
|
+
let [id, fieldName, file, opts = {}] = args
|
|
7
|
+
const { find } = this.app.lib._
|
|
8
|
+
const all = await this.findAttachment(id, opts)
|
|
9
|
+
if (fieldName === 'null') fieldName = null
|
|
10
|
+
const data = find(all, { fieldName, file })
|
|
11
|
+
if (!data) throw this.error('notFound', { statusCode: 404 })
|
|
12
|
+
return data
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default getAttachment
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { getFilterAndOptions, execHook, execModelHook, getSingleRef } from './_util.js'
|
|
2
|
+
const action = 'getRecord'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Object} TRecordGetResult
|
|
6
|
+
* @see Dobo#recordGet
|
|
7
|
+
* @see Dobo#recordFindOne
|
|
8
|
+
* @property {Object} data - Returned record
|
|
9
|
+
* @property {boolean} success - Whether operation is successfull or failed
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {Object} TRecordgetFilterAndOptions
|
|
14
|
+
* @see Dobo#recordGet
|
|
15
|
+
* @property {boolean} [dataOnly=true] - If ```true``` (default) returns array of records. Otherwise {@link TFindRecordResult}
|
|
16
|
+
* @property {boolean} [count=false] - If ```true``` and ```dataOnly``` is also ```true```, the total number of records found will be returned
|
|
17
|
+
* @property {boolean} [noCache=true] - If ```true``` (default), result set won't be cached. This will overwrite model's ```cacheable``` property. Only applicable if {@link https://github.com/ardhi/bajo-cache|bajo-cache} is loaded
|
|
18
|
+
* @property {boolean} [noHook=false] - If ```true```, no model's hook will be executed
|
|
19
|
+
* @property {boolean} [noFeatureHook=false] - If ```true```, no model's feature hook will be executed
|
|
20
|
+
* @property {boolean} [fields=[]] - If not empty, return only these fields EXCLUDING hidden fields
|
|
21
|
+
* @property {boolean} [hidden=[]] - Additional fields to hide, in addition the one set in model's model
|
|
22
|
+
* @property {boolean} [forceNoHidden=false] - If ```true```, hidden fields will be ignored and ALL fields will be returned
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get record by model's name and record ID
|
|
27
|
+
*
|
|
28
|
+
* Example:
|
|
29
|
+
* ```javascript
|
|
30
|
+
* const { recordGet } = this.app.dobo
|
|
31
|
+
* const fields = ['id', 'name', 'iso3']
|
|
32
|
+
* const result = await recordGet('CdbCountry', 'ID', { fields })
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @method
|
|
36
|
+
* @memberof Dobo
|
|
37
|
+
* @async
|
|
38
|
+
* @instance
|
|
39
|
+
* @name recordGet
|
|
40
|
+
* @param {string} name - Model's name
|
|
41
|
+
* @param {(string|number)} - Record's ID
|
|
42
|
+
* @param {TRecordgetFilterAndOptions} [options={}]
|
|
43
|
+
* @returns {(TRecordGetResult|Object)} Return record's ```object``` if ```options.dataOnly``` is set. {@link TRecordGetResult} otherwise
|
|
44
|
+
*/
|
|
45
|
+
async function getRecord (...args) {
|
|
46
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
47
|
+
let [id, opts = {}] = args
|
|
48
|
+
const { isEmpty } = this.app.lib._
|
|
49
|
+
const { isSet } = this.app.lib.aneka
|
|
50
|
+
const { runHook } = this.app.bajo
|
|
51
|
+
const { dataOnly = true } = opts
|
|
52
|
+
const { options } = await getFilterAndOptions.call(this, null, opts, action)
|
|
53
|
+
let { noResultSanitizer, noCache } = options
|
|
54
|
+
if (!this.cacheable || !options.record) noCache = true
|
|
55
|
+
id = this.sanitizeId(id)
|
|
56
|
+
await execHook.call(this, 'beforeGetRecord', id, options)
|
|
57
|
+
await execModelHook.call(this, 'beforeGetRecord', id, options)
|
|
58
|
+
if (!noCache) {
|
|
59
|
+
try {
|
|
60
|
+
await runHook('cache:getById', this, id)
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if (err.code === 'CACHED_RESULT') {
|
|
63
|
+
const result = err.payload
|
|
64
|
+
result.cache = true
|
|
65
|
+
return dataOnly ? result.data : result
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const result = options.record ?? (await this.driver._getRecord(this, id, options)) ?? {}
|
|
70
|
+
if (isEmpty(result.data) && !options.throwNotFound) return dataOnly ? undefined : { data: undefined }
|
|
71
|
+
if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
|
|
72
|
+
if (isSet(options.refs)) await getSingleRef.call(this, { record: result.data, options })
|
|
73
|
+
await execModelHook.call(this, 'afterGetRecord', id, result.data, options)
|
|
74
|
+
await execHook.call(this, 'afterGetRecord', id, result, options)
|
|
75
|
+
if (!noCache) await runHook('cache:setById', this, id, result)
|
|
76
|
+
return dataOnly ? result.data : result
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export default getRecord
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
const action = 'listAttachment'
|
|
3
|
+
|
|
4
|
+
async function listAttachment (...args) {
|
|
5
|
+
if (!this.attachment) return
|
|
6
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
7
|
+
const [params = {}, opts = {}] = args
|
|
8
|
+
const { map, kebabCase } = this.app.lib._
|
|
9
|
+
const { importPkg, getPluginDataDir } = this.app.bajo
|
|
10
|
+
const mime = await importPkg('waibu:mime')
|
|
11
|
+
const { fastGlob } = this.app.lib
|
|
12
|
+
|
|
13
|
+
const { id = '*', fieldName = '*', file = '*' } = params
|
|
14
|
+
const { uriEncoded = true } = opts
|
|
15
|
+
const root = `${getPluginDataDir('dobo')}/attachment`
|
|
16
|
+
let pattern = `${root}/${this.name}/${id}/${fieldName}/${file}`
|
|
17
|
+
if (uriEncoded) pattern = pattern.split('/').map(p => decodeURI(p)).join('/')
|
|
18
|
+
return map(await fastGlob(pattern), f => {
|
|
19
|
+
const mimeType = mime.getType(path.extname(f)) ?? ''
|
|
20
|
+
const fullPath = f.replace(root, '')
|
|
21
|
+
const row = {
|
|
22
|
+
file: f,
|
|
23
|
+
fileName: path.basename(fullPath),
|
|
24
|
+
fullPath,
|
|
25
|
+
mimeType,
|
|
26
|
+
params: { model: this.name, id, fieldName, file }
|
|
27
|
+
}
|
|
28
|
+
if (this.app.waibuMpa) {
|
|
29
|
+
const { routePath } = this.app.waibu
|
|
30
|
+
const [, _model, _id, _fieldName, _file] = fullPath.split('/')
|
|
31
|
+
row.url = routePath(`dobo:/attachment/${kebabCase(_model)}/${_id}/${_fieldName}/${_file}`)
|
|
32
|
+
}
|
|
33
|
+
return row
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default listAttachment
|
|
@@ -1,67 +1,69 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
|
|
3
|
-
async function
|
|
4
|
-
const {
|
|
5
|
-
const {
|
|
6
|
-
const {
|
|
7
|
-
const {
|
|
8
|
-
if (connection.proxy) {
|
|
9
|
-
this.log.warn('proxiedConnBound%s',
|
|
10
|
-
return
|
|
11
|
-
}
|
|
12
|
-
const result = { success: 0, failed: 0 }
|
|
13
|
-
const base = path.basename(
|
|
14
|
-
// original
|
|
15
|
-
const pattern = resolvePath(`${path.dirname(
|
|
16
|
-
let items = await readConfig(pattern, { ns:
|
|
17
|
-
if (isEmpty(items)) items = []
|
|
18
|
-
// override
|
|
19
|
-
const overrides = await readConfig(`${this.app.main.dir.pkg}/extend/dobo/override/${
|
|
20
|
-
|
|
21
|
-
if (isArray(overrides) && !isEmpty(overrides)) items = overrides
|
|
22
|
-
// extend
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
async function loadFixtures ({ spinner } = {}) {
|
|
4
|
+
const { readConfig, eachPlugins, getPluginDataDir } = this.app.bajo
|
|
5
|
+
const { resolvePath } = this.app.lib.aneka
|
|
6
|
+
const { isEmpty, isArray, isString } = this.app.lib._
|
|
7
|
+
const { fs } = this.app.lib
|
|
8
|
+
if (this.connection.proxy) {
|
|
9
|
+
this.log.warn('proxiedConnBound%s', this.name)
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
const result = { success: 0, failed: 0 }
|
|
13
|
+
const base = path.basename(this.file, path.extname(this.file))
|
|
14
|
+
// original
|
|
15
|
+
const pattern = resolvePath(`${path.dirname(this.file)}/../fixture/${base}.*`)
|
|
16
|
+
let items = await readConfig(pattern, { ns: this.plugin.ns, ignoreError: true })
|
|
17
|
+
if (isEmpty(items)) items = []
|
|
18
|
+
// override
|
|
19
|
+
const overrides = await readConfig(`${this.app.main.dir.pkg}/extend/dobo/override/${this.plugin.ns}/fixture/${base}.*`, { ns: this.plugin.ns, ignoreError: true })
|
|
20
|
+
|
|
21
|
+
if (isArray(overrides) && !isEmpty(overrides)) items = overrides
|
|
22
|
+
// extend
|
|
23
|
+
const me = this
|
|
24
|
+
await eachPlugins(async function ({ dir }) {
|
|
25
|
+
const { ns } = this
|
|
26
|
+
const extend = await readConfig(`${dir}/extend/dobo/extend/${me.plugin.ns}/fixture/${base}.*`, { ns, ignoreError: true })
|
|
27
|
+
if (isArray(extend) && !isEmpty(extend)) items.push(...extend)
|
|
28
|
+
})
|
|
29
|
+
if (isEmpty(items)) return result
|
|
30
|
+
const opts = { noHook: true, noCache: true }
|
|
31
|
+
for (const item of items) {
|
|
32
|
+
try {
|
|
33
|
+
for (const k in item) {
|
|
34
|
+
const v = item[k]
|
|
35
|
+
if (typeof v === 'string' && v.slice(0, 2) === '?:') {
|
|
36
|
+
let [, model, field, ...query] = v.split(':')
|
|
37
|
+
if (!field) field = 'id'
|
|
38
|
+
const ref = this.app.dobo.getModel(model)
|
|
39
|
+
const recs = await ref.findRecord({ query: query.join(':') }, opts)
|
|
40
|
+
item[k] = (recs[0] ?? {})[field]
|
|
41
|
+
}
|
|
42
|
+
if (v === null) item[k] = undefined
|
|
43
|
+
}
|
|
44
|
+
const resp = await this.createRecord(item, { force: true })
|
|
45
|
+
if (isArray(item._attachments) && item._attachments.length > 0) {
|
|
46
|
+
for (let att of item._attachments) {
|
|
47
|
+
if (isString(att)) att = { field: 'file', file: att }
|
|
48
|
+
const fname = path.basename(att.file)
|
|
49
|
+
if (fs.existsSync(att.file)) {
|
|
50
|
+
const dest = `${getPluginDataDir(this.plugin.ns)}/${resp.id}/${att.field}/${fname}`
|
|
51
|
+
try {
|
|
52
|
+
fs.copySync(att.file, dest)
|
|
53
|
+
} catch (err) {}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
result.success++
|
|
58
|
+
if (spinner) spinner.setText('recordsAdded%s%d%d', this.name, result.success, items.length)
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.log(err)
|
|
61
|
+
err.model = this.name
|
|
62
|
+
if (this.app.applet) this.plugin.print.fail(this.app.dobo.validationErrorMessage(err))
|
|
63
|
+
result.failed++
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return result
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default loadFixtures
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getAttachmentPath } from './_util.js'
|
|
2
|
+
const action = 'removeAttachment'
|
|
3
|
+
|
|
4
|
+
async function removeAttachment (...args) {
|
|
5
|
+
if (!this.attachment) return
|
|
6
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
7
|
+
const [id, fieldName, file, opts = {}] = args
|
|
8
|
+
const { fs } = this.app.lib
|
|
9
|
+
const path = await getAttachmentPath.call(this, id, fieldName, file)
|
|
10
|
+
const { req } = opts
|
|
11
|
+
await fs.remove(path)
|
|
12
|
+
if (!opts.noFlash && req && req.flash) req.flash('notify', req.t('attachmentRemoved'))
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default removeAttachment
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { getFilterAndOptions, execHook, execModelHook, getSingleRef, handleReq } from './_util.js'
|
|
2
|
+
const action = 'removeRecord'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Object} TRecordRemoveOptions
|
|
6
|
+
* @see Dobo#recordRemove
|
|
7
|
+
* @property {boolean} [dataOnly=true] - If ```true``` (default) returns deleted record. Otherwise {@link TRecordRemoveResult}
|
|
8
|
+
* @property {boolean} [noHook=false] - If ```true```, no model's hook will be executed
|
|
9
|
+
* @property {boolean} [noModelHook=false] - If ```true```, no model's feature hook will be executed
|
|
10
|
+
* @property {boolean} [noResult=false] - If ```true```, returns nothing
|
|
11
|
+
* @property {boolean} [fields=[]] - If not empty, return only these fields EXCLUDING hidden fields
|
|
12
|
+
* @property {boolean} [hidden=[]] - Additional fields to hide, in addition the one set in model's model
|
|
13
|
+
* @property {boolean} [forceNoHidden=false] - If ```true```, hidden fields will be ignored and ALL fields will be returned
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Remove existing record by it's ID. All attachments bound to this record will also be removed forever.
|
|
18
|
+
*
|
|
19
|
+
* Example:
|
|
20
|
+
* ```javascript
|
|
21
|
+
* const { recordRemove } = this.app.dobo
|
|
22
|
+
* const result = await recordRemove('CdbCountry', 'ID')
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @method
|
|
26
|
+
* @memberof Model
|
|
27
|
+
* @async
|
|
28
|
+
* @instance
|
|
29
|
+
* @name removeRecord
|
|
30
|
+
* @param {(string|number)} id - Record's ID
|
|
31
|
+
* @param {TRecordRemoveOptions} [options={}]
|
|
32
|
+
* @returns {(TRecordRemoveResult|Object)} Return the removed record if ```options.dataOnly``` is set. {@link TRecordRemoveResult} otherwise
|
|
33
|
+
*/
|
|
34
|
+
async function removeRecord (...args) {
|
|
35
|
+
if (args.length === 0) return this.action(action, ...args)
|
|
36
|
+
let [id, opts = {}] = args
|
|
37
|
+
const { isSet } = this.app.lib.aneka
|
|
38
|
+
const { runHook } = this.app.bajo
|
|
39
|
+
const { dataOnly = true } = opts
|
|
40
|
+
const { options } = await getFilterAndOptions.call(this, null, opts, action)
|
|
41
|
+
const { noResult, noResultSanitizer } = options
|
|
42
|
+
id = this.sanitizeId(id)
|
|
43
|
+
await execHook.call(this, 'beforeRemoveRecord', id, options)
|
|
44
|
+
await execModelHook.call(this, 'beforeRemoveRecord', id, options)
|
|
45
|
+
const result = options.record ?? (await this.driver._removeRecord(this, id, options)) ?? {}
|
|
46
|
+
if (noResult) {
|
|
47
|
+
await runHook('cache:clear', this, 'remove', id)
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
if (!noResultSanitizer) result.oldData = await this.sanitizeRecord(result.oldData, options)
|
|
51
|
+
if (isSet(options.refs)) await getSingleRef.call(this, { record: result.data, options })
|
|
52
|
+
await handleReq.call(this, result.oldData.id, 'removed', options)
|
|
53
|
+
await execModelHook.call(this, 'afterRemoveRecord', id, result, options)
|
|
54
|
+
await execHook.call(this, 'afterRemoveRecord', id, result, options)
|
|
55
|
+
await runHook('cache:clear', this, 'remove', id, result)
|
|
56
|
+
return dataOnly ? result.oldData : result
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default removeRecord
|