dobo 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -1
- package/bajo/config.json +27 -0
- package/bajo/hook/bajoI18N.db@before-resource-merge.js +10 -0
- package/bajo/hook/bajoI18N@before-init.js +6 -0
- package/bajo/init.js +18 -0
- package/bajo/method/aggregate-types.js +1 -0
- package/bajo/method/attachment/copy-uploaded.js +32 -0
- package/bajo/method/attachment/create.js +27 -0
- package/bajo/method/attachment/find.js +27 -0
- package/bajo/method/attachment/get-path.js +11 -0
- package/bajo/method/attachment/get.js +12 -0
- package/bajo/method/attachment/pre-check.js +8 -0
- package/bajo/method/attachment/remove.js +11 -0
- package/bajo/method/attachment/update.js +7 -0
- package/bajo/method/build-match.js +34 -0
- package/bajo/method/build-query.js +13 -0
- package/bajo/method/bulk/create.js +45 -0
- package/bajo/method/get-info.js +14 -0
- package/bajo/method/get-schema.js +10 -0
- package/bajo/method/model/clear.js +20 -0
- package/bajo/method/model/create.js +11 -0
- package/bajo/method/model/drop.js +11 -0
- package/bajo/method/model/exists.js +17 -0
- package/bajo/method/pick-record.js +30 -0
- package/bajo/method/prep-pagination.js +66 -0
- package/bajo/method/prop-type.js +43 -0
- package/bajo/method/record/clear.js +22 -0
- package/bajo/method/record/create.js +61 -0
- package/bajo/method/record/find-all.js +15 -0
- package/bajo/method/record/find-one.js +39 -0
- package/bajo/method/record/find.js +41 -0
- package/bajo/method/record/get.js +38 -0
- package/bajo/method/record/remove.js +34 -0
- package/bajo/method/record/update.js +60 -0
- package/bajo/method/record/upsert.js +19 -0
- package/bajo/method/sanitize/body.js +67 -0
- package/bajo/method/sanitize/date.js +14 -0
- package/bajo/method/sanitize/id.js +7 -0
- package/bajo/method/stat/aggregate.js +23 -0
- package/bajo/method/stat/histogram.js +26 -0
- package/bajo/method/validate.js +154 -0
- package/bajo/method/validation-error-message.js +12 -0
- package/bajo/start.js +16 -0
- package/bajoCli/applet/connection.js +22 -0
- package/bajoCli/applet/lib/post-process.js +47 -0
- package/bajoCli/applet/model-clear.js +11 -0
- package/bajoCli/applet/model-rebuild.js +77 -0
- package/bajoCli/applet/record-create.js +41 -0
- package/bajoCli/applet/record-find.js +27 -0
- package/bajoCli/applet/record-get.js +24 -0
- package/bajoCli/applet/record-remove.js +24 -0
- package/bajoCli/applet/record-update.js +47 -0
- package/bajoCli/applet/schema.js +22 -0
- package/bajoCli/applet/shell.js +48 -0
- package/bajoCli/applet/stat-count.js +24 -0
- package/bajoCli/applet.js +1 -0
- package/bajoI18N/resource/en-US.json +82 -0
- package/bajoI18N/resource/id.json +143 -0
- package/dobo/feature/created-at.js +18 -0
- package/dobo/feature/dt.js +13 -0
- package/dobo/feature/int-id.js +13 -0
- package/dobo/feature/updated-at.js +23 -0
- package/lib/add-fixtures.js +53 -0
- package/lib/build-bulk-action.js +12 -0
- package/lib/check-unique.js +39 -0
- package/lib/collect-connections.js +25 -0
- package/lib/collect-drivers.js +32 -0
- package/lib/collect-feature.js +23 -0
- package/lib/collect-schemas.js +77 -0
- package/lib/exec-feature-hook.js +12 -0
- package/lib/exec-validation.js +27 -0
- package/lib/generic-prop-sanitizer.js +38 -0
- package/lib/handle-attachment-upload.js +16 -0
- package/lib/merge-attachment-info.js +16 -0
- package/lib/multi-rel-rows.js +34 -0
- package/lib/resolve-method.js +15 -0
- package/lib/sanitize-schema.js +180 -0
- package/lib/single-rel-rows.js +32 -0
- package/package.json +1 -1
- package/waibuStatic/virtual.json +4 -0
package/README.md
CHANGED
|
@@ -1 +1,23 @@
|
|
|
1
|
-
# dobo
|
|
1
|
+
# dobo
|
|
2
|
+
|
|
3
|
+
Plugin name: **bajoDb**, alias: **db**
|
|
4
|
+
|
|
5
|
+
 
|
|
6
|
+
|
|
7
|
+
> <br />**Attention**: I do NOT accept any pull request at the moment, thanks!<br /><br />
|
|
8
|
+
|
|
9
|
+
Database connectivity, tools, ORM/ODM support for [Bajo](https://github.com/ardhi/bajo)
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Goto your ```<bajo-base-dir>``` and type:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
$ npm install dobo
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Now open your ```<bajo-data-dir>/config/.plugins``` and put ```dobo``` in it
|
|
20
|
+
|
|
21
|
+
## License
|
|
22
|
+
|
|
23
|
+
[MIT](LICENSE)
|
package/bajo/config.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"alias": "db",
|
|
3
|
+
"connections": [],
|
|
4
|
+
"dependencies": [],
|
|
5
|
+
"mergeProps": ["connections"],
|
|
6
|
+
"defaults": {
|
|
7
|
+
"property": {
|
|
8
|
+
"text": {
|
|
9
|
+
"kind": "text"
|
|
10
|
+
},
|
|
11
|
+
"string": {
|
|
12
|
+
"length": 50
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"filter": {
|
|
16
|
+
"limit": 25,
|
|
17
|
+
"maxLimit": 200,
|
|
18
|
+
"sort": ["dt:-1", "updatedAt:-1", "updated_at:-1", "createdAt:-1", "createdAt:-1", "ts:-1", "username", "name"]
|
|
19
|
+
},
|
|
20
|
+
"idField": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"maxLength": 50,
|
|
23
|
+
"required": true,
|
|
24
|
+
"index": { "type": "primary" }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
async function beforeResourceMerge (lng, content) {
|
|
2
|
+
const { eachPlugins, readConfig } = this.app.bajo
|
|
3
|
+
const { merge } = this.app.bajo.lib._
|
|
4
|
+
await eachPlugins(async function ({ file, ns }) {
|
|
5
|
+
const item = await readConfig(file, { ns })
|
|
6
|
+
merge(content, item)
|
|
7
|
+
}, { glob: `i18n/${lng}.json`, ns: this.name })
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default beforeResourceMerge
|
package/bajo/init.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import collectConnections from '../lib/collect-connections.js'
|
|
2
|
+
import collectDrivers from '../lib/collect-drivers.js'
|
|
3
|
+
import collectFeature from '../lib/collect-feature.js'
|
|
4
|
+
import collectSchemas from '../lib/collect-schemas.js'
|
|
5
|
+
|
|
6
|
+
async function init () {
|
|
7
|
+
const { buildCollections } = this.app.bajo
|
|
8
|
+
const { fs } = this.app.bajo.lib
|
|
9
|
+
const cfg = this.config
|
|
10
|
+
fs.ensureDirSync(`${cfg.dir.data}/attachment`)
|
|
11
|
+
await collectDrivers.call(this)
|
|
12
|
+
this.connections = await buildCollections({ ns: this.name, handler: collectConnections, dupChecks: ['name'] })
|
|
13
|
+
if (this.connections.length === 0) this.log.warn('No %s found!', this.print.write('connection'))
|
|
14
|
+
await collectFeature.call(this)
|
|
15
|
+
await collectSchemas.call(this)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default init
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default ['count', 'avg', 'min', 'max', 'sum']
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
async function copyUploaded (name, id, { req, setField, setFile, mimeType, stats, silent = true } = {}) {
|
|
4
|
+
const { fs } = this.app.bajo.lib
|
|
5
|
+
name = this.attachmentPreCheck(name)
|
|
6
|
+
if (!name) {
|
|
7
|
+
if (silent) return
|
|
8
|
+
throw this.error('Name must be provided')
|
|
9
|
+
}
|
|
10
|
+
if (!this.bajoWeb) {
|
|
11
|
+
if (silent) return
|
|
12
|
+
throw this.error('Plugin \'%s\' is missing')
|
|
13
|
+
}
|
|
14
|
+
const { dir, files } = await this.bajoWeb.getUploadedFiles(req.id, false, true)
|
|
15
|
+
const result = []
|
|
16
|
+
if (files.length === 0) return result
|
|
17
|
+
for (const f of files) {
|
|
18
|
+
let [field, ...parts] = path.basename(f).split('@')
|
|
19
|
+
if (parts.length === 0) continue
|
|
20
|
+
field = setField ?? field
|
|
21
|
+
const file = setFile ?? parts.join('@')
|
|
22
|
+
const opts = { source: f, field, file, mimeType, stats, req }
|
|
23
|
+
const rec = await this.attachmentCreate(name, id, opts)
|
|
24
|
+
delete rec.dir
|
|
25
|
+
result.push(rec)
|
|
26
|
+
if (setField || setFile) break
|
|
27
|
+
}
|
|
28
|
+
fs.removeSync(dir)
|
|
29
|
+
return result
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default copyUploaded
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import mergeAttachmentInfo from '../../../lib/merge-attachment-info.js'
|
|
2
|
+
|
|
3
|
+
async function create (name, id, options = {}) {
|
|
4
|
+
const { fs } = this.app.bajo.lib
|
|
5
|
+
name = this.attachmentPreCheck(name)
|
|
6
|
+
if (!name) return
|
|
7
|
+
const { source, field, file } = options
|
|
8
|
+
if (!source) throw this.error('Invalid source')
|
|
9
|
+
const baseDir = await this.attachmentGetPath(name, id, field, file, { dirOnly: true })
|
|
10
|
+
const { fullPath, stats, mimeType, req } = options
|
|
11
|
+
|
|
12
|
+
let dir = `${baseDir}/${field}`
|
|
13
|
+
if ((field || '').endsWith('[]')) dir = `${baseDir}/${field.replace('[]', '')}`
|
|
14
|
+
const dest = `${dir}/${file}`.replaceAll('//', '/')
|
|
15
|
+
await fs.ensureDir(dir)
|
|
16
|
+
await fs.copy(source, dest)
|
|
17
|
+
const rec = {
|
|
18
|
+
field: field === '' ? undefined : field,
|
|
19
|
+
dir,
|
|
20
|
+
file
|
|
21
|
+
}
|
|
22
|
+
await mergeAttachmentInfo.call(this, rec, dest, { mimeType, fullPath, stats })
|
|
23
|
+
if (req && req.flash) req.flash('dbsuccess', { message: req.i18n.t('File successfully uploaded') })
|
|
24
|
+
return rec
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default create
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import mergeAttachmentInfo from '../../../lib/merge-attachment-info.js'
|
|
2
|
+
|
|
3
|
+
async function find (name, id, options = {}) {
|
|
4
|
+
const { fastGlob, fs } = this.app.bajo.lib
|
|
5
|
+
const { getPluginDataDir } = this.app.bajo
|
|
6
|
+
name = this.attachmentPreCheck(name)
|
|
7
|
+
if (!name) return
|
|
8
|
+
const dir = `${getPluginDataDir(this.name)}/attachment/${name}/${id}`
|
|
9
|
+
if (!fs.existsSync(dir)) return []
|
|
10
|
+
const files = await fastGlob(`${dir}/**/*`)
|
|
11
|
+
const { fullPath, stats, mimeType } = options
|
|
12
|
+
const recs = []
|
|
13
|
+
for (const f of files) {
|
|
14
|
+
const item = f.replace(dir, '')
|
|
15
|
+
let [, field, file] = item.split('/')
|
|
16
|
+
if (!file) {
|
|
17
|
+
file = field
|
|
18
|
+
field = null
|
|
19
|
+
}
|
|
20
|
+
const rec = { field, file }
|
|
21
|
+
await mergeAttachmentInfo.call(this, rec, f, { mimeType, fullPath, stats })
|
|
22
|
+
recs.push(rec)
|
|
23
|
+
}
|
|
24
|
+
return recs
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default find
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
async function getPath (name, id, field, file, options = {}) {
|
|
2
|
+
const { pascalCase, getPluginDataDir } = this.app.bajo
|
|
3
|
+
const { fs } = this.app.bajo.lib
|
|
4
|
+
const dir = `${getPluginDataDir(this.name)}/attachment/${pascalCase(name)}/${id}`
|
|
5
|
+
if (options.dirOnly) return dir
|
|
6
|
+
const path = field ? `${dir}/${field}/${file}` : `${dir}/${file}`
|
|
7
|
+
if (!fs.existsSync(path)) throw this.error('notfound')
|
|
8
|
+
return path
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default getPath
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
async function get (name, id, field, file, options = {}) {
|
|
2
|
+
name = this.attachmentPreCheck(name)
|
|
3
|
+
if (!name) return
|
|
4
|
+
const { find } = this.app.bajo.lib._
|
|
5
|
+
const all = await this.attachmentFind(name, id, options)
|
|
6
|
+
if (field === 'null') field = null
|
|
7
|
+
const data = find(all, { field, file })
|
|
8
|
+
if (!data) throw this.error('notfound', { statusCode: 404 })
|
|
9
|
+
return data
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default get
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
async function remove (name, id, field, file, options = {}) {
|
|
2
|
+
const { fs } = this.app.bajo.lib
|
|
3
|
+
name = this.attachmentPreCheck(name)
|
|
4
|
+
if (!name) return
|
|
5
|
+
const path = await this.attachmentGetPath(name, id, field, file)
|
|
6
|
+
const { req } = options
|
|
7
|
+
await fs.remove(path)
|
|
8
|
+
if (req && req.flash) req.flash('dbsuccess', { message: req.i18n.t('File successfully removed') })
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default remove
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
function split (value, schema) {
|
|
2
|
+
let [field, val] = value.split(':').map(i => i.trim())
|
|
3
|
+
if (!val) {
|
|
4
|
+
val = field
|
|
5
|
+
field = '*'
|
|
6
|
+
}
|
|
7
|
+
return { field, value: val }
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function buildMatch ({ input = '', schema, options }) {
|
|
11
|
+
const { isPlainObject, trim } = this.app.bajo.lib._
|
|
12
|
+
input = trim(input)
|
|
13
|
+
let items = {}
|
|
14
|
+
if (isPlainObject(input)) items = input
|
|
15
|
+
else if (input[0] === '{') items = JSON.parse(input)
|
|
16
|
+
else {
|
|
17
|
+
for (const item of input.split('+').map(i => i.trim())) {
|
|
18
|
+
const part = split.call(this, item, schema)
|
|
19
|
+
if (!items[part.field]) items[part.field] = []
|
|
20
|
+
items[part.field].push(...part.value.split(' ').filter(v => ![''].includes(v)))
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const matcher = {}
|
|
24
|
+
for (const f of schema.fullText.fields) {
|
|
25
|
+
const value = []
|
|
26
|
+
if (typeof items[f] === 'string') items[f] = [items[f]]
|
|
27
|
+
if (Object.prototype.hasOwnProperty.call(items, f)) value.push(...items[f])
|
|
28
|
+
matcher[f] = value
|
|
29
|
+
}
|
|
30
|
+
if (Object.prototype.hasOwnProperty.call(items, '*')) matcher['*'] = items['*']
|
|
31
|
+
return matcher
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default buildMatch
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import nql from '@tryghost/nql'
|
|
2
|
+
|
|
3
|
+
async function buildQuery ({ filter, schema, options = {} } = {}) {
|
|
4
|
+
const { trim, isString, isPlainObject } = this.app.bajo.lib._
|
|
5
|
+
let query = {}
|
|
6
|
+
if (isString(filter.query)) {
|
|
7
|
+
if (trim(filter.query).startsWith('{')) query = JSON.parse(filter.query)
|
|
8
|
+
else query = nql(filter.query).parse()
|
|
9
|
+
} else if (isPlainObject(filter.query)) query = filter.query
|
|
10
|
+
return query
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default buildQuery
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import buildBulkAction from '../../../lib/build-bulk-action.js'
|
|
2
|
+
import execValidation from '../../../lib/exec-validation.js'
|
|
3
|
+
import execFeatureHook from '../../../lib/exec-feature-hook.js'
|
|
4
|
+
|
|
5
|
+
async function create (name, inputs, options) {
|
|
6
|
+
const { generateId, runHook, isSet } = this.app.bajo
|
|
7
|
+
const { clearColl } = this.cache ?? {}
|
|
8
|
+
const { find } = this.app.bajo.lib._
|
|
9
|
+
options.dataOnly = options.dataOnly ?? true
|
|
10
|
+
options.truncateString = options.truncateString ?? true
|
|
11
|
+
const { noHook, noValidation } = options
|
|
12
|
+
await this.modelExists(name, true)
|
|
13
|
+
const { handler, schema } = await buildBulkAction.call(this, name, 'create', options)
|
|
14
|
+
const idField = find(schema.properties, { name: 'id' })
|
|
15
|
+
const bodies = [...inputs]
|
|
16
|
+
for (let b of bodies) {
|
|
17
|
+
b.id = b.id ?? generateId(idField.type === 'integer' ? 'int' : undefined)
|
|
18
|
+
b = await this.sanitizeBody({ body: b, schema, strict: true })
|
|
19
|
+
if (!noValidation) b = await execValidation.call(this, { noHook, name, b, options })
|
|
20
|
+
}
|
|
21
|
+
if (!noHook) {
|
|
22
|
+
await runHook(`${this.name}:onBeforeBulkCreate`, name, bodies, options)
|
|
23
|
+
await runHook(`${this.name}.${name}:onBeforeBulkCreate`, bodies, options)
|
|
24
|
+
}
|
|
25
|
+
for (const idx in bodies) {
|
|
26
|
+
await execFeatureHook.call(this, 'beforeCreate', { schema, body: bodies[idx] })
|
|
27
|
+
// TODO: check unique?
|
|
28
|
+
for (const k in bodies[idx]) {
|
|
29
|
+
if (bodies[idx][k] === undefined) continue
|
|
30
|
+
const prop = find(schema.properties, { name: k })
|
|
31
|
+
if (options.truncateString && isSet(bodies[idx][k]) && prop && ['string', 'text'].includes(prop.type)) bodies[idx][k] = bodies[idx][k].slice(0, prop.maxLength)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
await handler.call(this, { schema, bodies, options })
|
|
35
|
+
for (const idx in bodies) {
|
|
36
|
+
await execFeatureHook.call(this, 'afterCreate', { schema, body: bodies[idx] })
|
|
37
|
+
}
|
|
38
|
+
if (!noHook) {
|
|
39
|
+
await runHook(`${this.name}.${name}:onAfterBulkCreate`, bodies, options)
|
|
40
|
+
await runHook(`${this.name}:onAfterBulkCreate`, name, bodies, options)
|
|
41
|
+
}
|
|
42
|
+
if (clearColl) await clearColl({ model: name })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default create
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function getInfo (name) {
|
|
2
|
+
const { breakNsPath } = this.app.bajo
|
|
3
|
+
const { find, map } = this.app.bajo.lib._
|
|
4
|
+
const schema = this.getSchema(name)
|
|
5
|
+
const conn = find(this.connections, { name: schema.connection })
|
|
6
|
+
const [ns, type] = breakNsPath(conn.type)
|
|
7
|
+
const driver = find(this.drivers, { type, ns, driver: conn.driver })
|
|
8
|
+
const instance = find(this.app[driver.ns].instances, { name: schema.connection })
|
|
9
|
+
const opts = conn.type === 'mssql' ? { includeTriggerModifications: true } : undefined
|
|
10
|
+
const returning = [map(schema.properties, 'name'), opts]
|
|
11
|
+
return { instance, driver, connection: conn, returning, schema }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default getInfo
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
function getSchema (input) {
|
|
2
|
+
const { find, isPlainObject, cloneDeep } = this.app.bajo.lib._
|
|
3
|
+
let name = isPlainObject(input) ? input.name : input
|
|
4
|
+
name = this.app.bajo.pascalCase(name)
|
|
5
|
+
const schema = find(this.schemas, { name })
|
|
6
|
+
if (!schema) throw this.error('Unknown model/schema \'%s\'', name)
|
|
7
|
+
return cloneDeep(schema)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default getSchema
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import resolveMethod from '../../../lib/resolve-method.js'
|
|
2
|
+
|
|
3
|
+
async function clear (name, options = {}) {
|
|
4
|
+
const { runHook } = this.app.bajo
|
|
5
|
+
await this.modelExists(name, true)
|
|
6
|
+
const { noHook } = options
|
|
7
|
+
const { handler, schema } = await resolveMethod.call(this, name, 'model-clear')
|
|
8
|
+
if (!noHook) {
|
|
9
|
+
await runHook(`${this.name}:onBeforeCollClear`, name, options)
|
|
10
|
+
await runHook(`${this.name}.${name}:onBeforeCollClear`, options)
|
|
11
|
+
}
|
|
12
|
+
const resp = await handler.call(this, { schema, options })
|
|
13
|
+
if (!noHook) {
|
|
14
|
+
await runHook(`${this.name}.${name}:onAfterCollClear`, options, resp)
|
|
15
|
+
await runHook(`${this.name}:onAfterCollClear`, name, options, resp)
|
|
16
|
+
}
|
|
17
|
+
return resp
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export default clear
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import resolveMethod from '../../../lib/resolve-method.js'
|
|
2
|
+
|
|
3
|
+
async function create (name, options = {}) {
|
|
4
|
+
const { runHook } = this.app.bajo
|
|
5
|
+
const { handler, schema } = await resolveMethod.call(this, name, 'model-create', options)
|
|
6
|
+
await runHook(`${this.name}:beforeCollCreate` + name, schema)
|
|
7
|
+
await handler.call(this, { schema, options })
|
|
8
|
+
await runHook(`${this.name}:afterCollCreate` + name, schema)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default create
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import resolveMethod from '../../../lib/resolve-method.js'
|
|
2
|
+
|
|
3
|
+
async function drop (name, options = {}) {
|
|
4
|
+
const { runHook } = this.app.bajo
|
|
5
|
+
const { handler, schema } = await resolveMethod.call(this, name, 'model-drop', options)
|
|
6
|
+
await runHook(`${this.name}:beforeCollDrop` + name, schema)
|
|
7
|
+
await handler.call(this, { schema, options })
|
|
8
|
+
await runHook(`${this.name}:afterCollDrop` + name, schema)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default drop
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import resolveMethod from '../../../lib/resolve-method.js'
|
|
2
|
+
|
|
3
|
+
const cache = {}
|
|
4
|
+
|
|
5
|
+
async function exists (name, thrown, options = {}) {
|
|
6
|
+
if (cache[name]) return cache[name]
|
|
7
|
+
const { runHook } = this.app.bajo
|
|
8
|
+
const { handler, schema } = await resolveMethod.call(this, name, 'model-exists', options)
|
|
9
|
+
await runHook(`${this.name}:beforeCollExists` + name, schema)
|
|
10
|
+
const exist = await handler.call(this, { schema, options })
|
|
11
|
+
await runHook(`${this.name}:afterCollExists` + name, schema, exist)
|
|
12
|
+
if (!exist && thrown) throw this.error('Model doesn\'t exist yet. Please do model rebuild first')
|
|
13
|
+
cache[name] = exist
|
|
14
|
+
return exist
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default exists
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
async function transform ({ record, schema, hidden = [] } = {}) {
|
|
2
|
+
const { dayjs } = this.app.bajo
|
|
3
|
+
if (record._id) {
|
|
4
|
+
record.id = record._id
|
|
5
|
+
delete record._id
|
|
6
|
+
}
|
|
7
|
+
const result = {}
|
|
8
|
+
for (const p of schema.properties) {
|
|
9
|
+
if (hidden.includes(p.name)) continue
|
|
10
|
+
result[p.name] = record[p.name] ?? null
|
|
11
|
+
if (record[p.name] === null) continue
|
|
12
|
+
switch (p.type) {
|
|
13
|
+
case 'time': result[p.name] = dayjs(record[p.name]).format('HH:mm:ss'); break
|
|
14
|
+
case 'date': result[p.name] = dayjs(record[p.name]).format('YYYY-MM-DD'); break
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return await this.sanitizeBody({ body: result, schema, partial: true, ignoreNull: true })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function pickRecord ({ record, fields, schema = {}, hidden = [] } = {}) {
|
|
21
|
+
const { isArray, pick, clone, isEmpty, omit } = this.app.bajo.lib._
|
|
22
|
+
if (isEmpty(record)) return record
|
|
23
|
+
if (hidden.length > 0) record = omit(record, hidden)
|
|
24
|
+
if (!isArray(fields)) return await transform.call(this, { record, schema, hidden })
|
|
25
|
+
const fl = clone(fields)
|
|
26
|
+
if (!fl.includes('id')) fl.unshift('id')
|
|
27
|
+
return pick(await transform.call(this, { record, schema, hidden }), fl)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default pickRecord
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
function buildPageSkipLimit (filter) {
|
|
2
|
+
let limit = parseInt(filter.limit) || this.config.defaults.filter.limit
|
|
3
|
+
if (limit > this.config.defaults.filter.maxLimit) limit = this.config.defaults.filter.maxLimit
|
|
4
|
+
if (limit < 1) limit = 1
|
|
5
|
+
let page = parseInt(filter.page) || 1
|
|
6
|
+
if (page < 1) page = 1
|
|
7
|
+
let skip = (page - 1) * limit
|
|
8
|
+
if (filter.skip) {
|
|
9
|
+
skip = parseInt(filter.skip) || skip
|
|
10
|
+
page = undefined
|
|
11
|
+
}
|
|
12
|
+
if (skip < 0) skip = 0
|
|
13
|
+
return { page, skip, limit }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function buildSort (input, schema, allowSortUnindexed) {
|
|
17
|
+
const { isEmpty, map, each, isPlainObject, isString, trim, filter, keys } = this.app.bajo.lib._
|
|
18
|
+
let sort
|
|
19
|
+
if (schema && isEmpty(input)) {
|
|
20
|
+
const columns = map(schema.properties, 'name')
|
|
21
|
+
each(this.config.defaults.filter.sort, s => {
|
|
22
|
+
const [col] = s.split(':')
|
|
23
|
+
if (columns.includes(col)) {
|
|
24
|
+
input = s
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
if (!isEmpty(input)) {
|
|
30
|
+
if (isPlainObject(input)) sort = input
|
|
31
|
+
else if (isString(input)) {
|
|
32
|
+
const item = {}
|
|
33
|
+
each(input.split('+'), text => {
|
|
34
|
+
let [col, dir] = map(trim(text).split(':'), i => trim(i))
|
|
35
|
+
dir = (dir ?? '').toUpperCase()
|
|
36
|
+
dir = dir === 'DESC' ? -1 : parseInt(dir) || 1
|
|
37
|
+
item[col] = dir / Math.abs(dir)
|
|
38
|
+
})
|
|
39
|
+
sort = item
|
|
40
|
+
}
|
|
41
|
+
if (schema) {
|
|
42
|
+
const indexes = map(filter(schema.properties, p => !!p.index), 'name')
|
|
43
|
+
each(schema.indexes, item => {
|
|
44
|
+
indexes.push(...item.fields)
|
|
45
|
+
})
|
|
46
|
+
const items = keys(sort)
|
|
47
|
+
each(items, i => {
|
|
48
|
+
if (!indexes.includes(i) && !allowSortUnindexed) throw this.error('Sort on unindexed field: \'%s@%s\'', i, schema.name)
|
|
49
|
+
// if (schema.fullText.fields.includes(i)) throw this.error('Can\'t sort on full-text index: \'%s@%s\'', i, schema.name)
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return sort
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function prepPagination (filter = {}, schema, options = {}) {
|
|
57
|
+
const { page, skip, limit } = buildPageSkipLimit.call(this, filter)
|
|
58
|
+
let sortInput = filter.sort
|
|
59
|
+
try {
|
|
60
|
+
sortInput = JSON.parse(sortInput)
|
|
61
|
+
} catch (err) {}
|
|
62
|
+
const sort = buildSort.call(this, sortInput, schema, options.allowSortUnindexed)
|
|
63
|
+
return { limit, page, skip, sort }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default prepPagination
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const propType = {
|
|
2
|
+
integer: {
|
|
3
|
+
validator: 'number'
|
|
4
|
+
},
|
|
5
|
+
smallint: {
|
|
6
|
+
validator: 'number'
|
|
7
|
+
},
|
|
8
|
+
text: {
|
|
9
|
+
validator: 'string',
|
|
10
|
+
kind: 'text',
|
|
11
|
+
choices: ['text', 'mediumtext', 'longtext']
|
|
12
|
+
},
|
|
13
|
+
string: {
|
|
14
|
+
validator: 'string',
|
|
15
|
+
maxLength: 255,
|
|
16
|
+
minLength: 0
|
|
17
|
+
},
|
|
18
|
+
float: {
|
|
19
|
+
validator: 'number'
|
|
20
|
+
},
|
|
21
|
+
double: {
|
|
22
|
+
validator: 'number'
|
|
23
|
+
},
|
|
24
|
+
boolean: {
|
|
25
|
+
validator: 'boolean'
|
|
26
|
+
},
|
|
27
|
+
date: {
|
|
28
|
+
validator: 'date'
|
|
29
|
+
},
|
|
30
|
+
datetime: {
|
|
31
|
+
validator: 'date'
|
|
32
|
+
},
|
|
33
|
+
time: {
|
|
34
|
+
validator: 'date'
|
|
35
|
+
},
|
|
36
|
+
timestamp: {
|
|
37
|
+
validator: 'timestamp'
|
|
38
|
+
},
|
|
39
|
+
object: {},
|
|
40
|
+
array: {}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default propType
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import resolveMethod from '../../../lib/resolve-method.js'
|
|
2
|
+
|
|
3
|
+
async function clear (name, opts = {}) {
|
|
4
|
+
const { runHook } = this.app.bajo
|
|
5
|
+
await this.modelExists(name, true)
|
|
6
|
+
const { cloneDeep } = this.app.bajo.lib._
|
|
7
|
+
const options = cloneDeep(opts)
|
|
8
|
+
const { noHook } = options
|
|
9
|
+
const { handler, schema, driver } = await resolveMethod.call(this, name, 'record-clear')
|
|
10
|
+
if (!noHook) {
|
|
11
|
+
await runHook(`${this.name}:onBeforeRecordClear`, name, options)
|
|
12
|
+
await runHook(`${this.name}.${name}:onBeforeRecordClear`, options)
|
|
13
|
+
}
|
|
14
|
+
const resp = await handler.call(this.app[driver.ns], { schema, options })
|
|
15
|
+
if (!noHook) {
|
|
16
|
+
await runHook(`${this.name}.${name}:onAfterRecordClear`, options, resp)
|
|
17
|
+
await runHook(`${this.name}:onAfterRecordClear`, name, options, resp)
|
|
18
|
+
}
|
|
19
|
+
return resp
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export default clear
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import resolveMethod from '../../../lib/resolve-method.js'
|
|
2
|
+
import checkUnique from '../../../lib/check-unique.js'
|
|
3
|
+
import handleAttachmentUpload from '../../../lib/handle-attachment-upload.js'
|
|
4
|
+
import execValidation from '../../../lib/exec-validation.js'
|
|
5
|
+
import execFeatureHook from '../../../lib/exec-feature-hook.js'
|
|
6
|
+
|
|
7
|
+
async function create (name, input, opts = {}) {
|
|
8
|
+
const { generateId, runHook, isSet } = this.app.bajo
|
|
9
|
+
const { clearColl } = this.cache ?? {}
|
|
10
|
+
const { get, find, forOwn, cloneDeep } = this.app.bajo.lib._
|
|
11
|
+
const options = cloneDeep(opts)
|
|
12
|
+
options.dataOnly = options.dataOnly ?? true
|
|
13
|
+
input = cloneDeep(input)
|
|
14
|
+
const { fields, dataOnly, noHook, noValidation, noCheckUnique, noFeatureHook, noResult, noSanitize, hidden } = options
|
|
15
|
+
options.truncateString = options.truncateString ?? true
|
|
16
|
+
options.dataOnly = false
|
|
17
|
+
await this.modelExists(name, true)
|
|
18
|
+
const { handler, schema, driver } = await resolveMethod.call(this, name, 'record-create', options)
|
|
19
|
+
const idField = find(schema.properties, { name: 'id' })
|
|
20
|
+
if (!isSet(input.id)) {
|
|
21
|
+
if (idField.type === 'string') input.id = generateId()
|
|
22
|
+
else if (['integer', 'smallint'].includes(idField.type) && !idField.autoInc) input.id = generateId('int')
|
|
23
|
+
}
|
|
24
|
+
let body = noSanitize ? input : await this.sanitizeBody({ body: input, schema, strict: true })
|
|
25
|
+
if (!noHook) {
|
|
26
|
+
await runHook(`${this.name}:onBeforeRecordCreate`, name, body, options)
|
|
27
|
+
await runHook(`${this.name}.${name}:onBeforeRecordCreate`, body, options)
|
|
28
|
+
}
|
|
29
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'beforeCreate', { schema, body })
|
|
30
|
+
if (!noValidation) body = await execValidation.call(this, { noHook, name, body, options })
|
|
31
|
+
if (isSet(body.id) && !noCheckUnique) await checkUnique.call(this, { schema, body })
|
|
32
|
+
let record = {}
|
|
33
|
+
try {
|
|
34
|
+
const nbody = {}
|
|
35
|
+
forOwn(body, (v, k) => {
|
|
36
|
+
if (v === undefined) return undefined
|
|
37
|
+
const prop = find(schema.properties, { name: k })
|
|
38
|
+
if (options.truncateString && isSet(v) && prop && ['string', 'text'].includes(prop.type)) v = v.slice(0, prop.maxLength)
|
|
39
|
+
nbody[k] = v
|
|
40
|
+
})
|
|
41
|
+
record = await handler.call(this.app[driver.ns], { schema, body: nbody, options })
|
|
42
|
+
if (options.req) {
|
|
43
|
+
if (options.req.file) await handleAttachmentUpload.call(this, { name: schema.name, id: body.id, body, options, action: 'create' })
|
|
44
|
+
if (options.req.flash) options.req.flash('dbsuccess', { message: this.print.write('Record successfully created'), record })
|
|
45
|
+
}
|
|
46
|
+
} catch (err) {
|
|
47
|
+
if (get(options, 'req.flash')) options.req.flash('dberr', err)
|
|
48
|
+
throw err
|
|
49
|
+
}
|
|
50
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'afterCreate', { schema, body, record })
|
|
51
|
+
if (!noHook) {
|
|
52
|
+
await runHook(`${this.name}.${name}:onAfterRecordCreate`, body, options, record)
|
|
53
|
+
await runHook(`${this.name}:onAfterRecordCreate`, name, body, options, record)
|
|
54
|
+
}
|
|
55
|
+
if (clearColl) await clearColl({ model: name, body, options, record })
|
|
56
|
+
if (noResult) return
|
|
57
|
+
record.data = await this.pickRecord({ record: record.data, fields, schema, hidden })
|
|
58
|
+
return dataOnly ? record.data : record
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default create
|