dobo 1.0.22 → 1.1.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/dobo/feature/removed-at.js +85 -0
- package/lib/exec-feature-hook.js +3 -2
- package/package.json +1 -1
- package/plugin/factory.js +317 -0
- package/{bajo → plugin}/method/record/count.js +12 -7
- package/{bajo → plugin}/method/record/create.js +8 -7
- package/{bajo → plugin}/method/record/find-one.js +10 -3
- package/{bajo → plugin}/method/record/find.js +9 -3
- package/{bajo → plugin}/method/record/get.js +9 -3
- package/{bajo → plugin}/method/record/remove.js +8 -3
- package/{bajo → plugin}/method/record/update.js +5 -3
- package/waibuMpa/route/attachment/@model/@id/@field/@file.js +8 -1
- package/bajo/.alias +0 -1
- package/bajo/config.json +0 -36
- package/bajo/init.js +0 -29
- package/bajo/method/aggregate-types.js +0 -1
- package/bajo/method/build-match.js +0 -34
- package/bajo/method/build-query.js +0 -14
- package/bajo/method/get-connection.js +0 -6
- package/bajo/method/get-info.js +0 -14
- package/bajo/method/get-schema.js +0 -10
- package/bajo/method/pick-record.js +0 -36
- package/bajo/method/prep-pagination.js +0 -63
- package/bajo/method/prop-type.js +0 -43
- package/bajo/method/validation-error-message.js +0 -12
- /package/{bajo → plugin}/method/attachment/copy-uploaded.js +0 -0
- /package/{bajo → plugin}/method/attachment/create.js +0 -0
- /package/{bajo → plugin}/method/attachment/find.js +0 -0
- /package/{bajo → plugin}/method/attachment/get-path.js +0 -0
- /package/{bajo → plugin}/method/attachment/get.js +0 -0
- /package/{bajo → plugin}/method/attachment/pre-check.js +0 -0
- /package/{bajo → plugin}/method/attachment/remove.js +0 -0
- /package/{bajo → plugin}/method/attachment/update.js +0 -0
- /package/{bajo → plugin}/method/bulk/create.js +0 -0
- /package/{bajo → plugin}/method/model/clear.js +0 -0
- /package/{bajo → plugin}/method/model/create.js +0 -0
- /package/{bajo → plugin}/method/model/drop.js +0 -0
- /package/{bajo → plugin}/method/model/exists.js +0 -0
- /package/{bajo → plugin}/method/record/clear.js +0 -0
- /package/{bajo → plugin}/method/record/find-all.js +0 -0
- /package/{bajo → plugin}/method/record/upsert.js +0 -0
- /package/{bajo → plugin}/method/sanitize/body.js +0 -0
- /package/{bajo → plugin}/method/sanitize/date.js +0 -0
- /package/{bajo → plugin}/method/sanitize/id.js +0 -0
- /package/{bajo → plugin}/method/stat/aggregate.js +0 -0
- /package/{bajo → plugin}/method/stat/histogram.js +0 -0
- /package/{bajo → plugin}/method/validate.js +0 -0
- /package/{bajo → plugin}/start.js +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
async function beforeFind ({ filter = {}, options }, opts) {
|
|
2
|
+
filter.query = filter.query ?? {}
|
|
3
|
+
const { isEmpty, set } = this.app.bajo.lib._
|
|
4
|
+
const q = { $and: [] }
|
|
5
|
+
if (!isEmpty(filter.query)) {
|
|
6
|
+
if (filter.query.$and) q.$and.push(...filter.query.$and)
|
|
7
|
+
else q.$and.push(filter.query)
|
|
8
|
+
}
|
|
9
|
+
q.$and.push(set({}, opts.fieldName, null))
|
|
10
|
+
filter.query = q
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function afterFind ({ records }, opts) {
|
|
14
|
+
for (const rec of records.data) {
|
|
15
|
+
delete rec[opts.fieldName]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function afterGet ({ schema, id, record }, opts) {
|
|
20
|
+
const { isEmpty } = this.app.bajo.lib._
|
|
21
|
+
if (!isEmpty(record.data[opts.fieldName])) throw this.error('recordNotFound%s%s', id, schema.name, { statusCode: 404 })
|
|
22
|
+
delete record.data[opts.fieldName]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function beforeCreate ({ body }, opts) {
|
|
26
|
+
delete body[opts.fieldName]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function afterCreate ({ record }, opts) {
|
|
30
|
+
delete record.data[opts.fieldName]
|
|
31
|
+
if (record.oldData) delete record.oldData[opts.fieldName]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function removedAt (opts = {}) {
|
|
35
|
+
opts.fieldName = opts.fieldName ?? 'removedAt'
|
|
36
|
+
return {
|
|
37
|
+
properties: {
|
|
38
|
+
name: opts.fieldName,
|
|
39
|
+
type: 'datetime',
|
|
40
|
+
index: true
|
|
41
|
+
},
|
|
42
|
+
hook: {
|
|
43
|
+
beforeFind: async function ({ filter, options }) {
|
|
44
|
+
await beforeFind.call(this, { filter, options }, opts)
|
|
45
|
+
},
|
|
46
|
+
beforeFindOne: async function ({ filter, options }) {
|
|
47
|
+
await beforeFind.call(this, { filter, options }, opts)
|
|
48
|
+
},
|
|
49
|
+
afterFind: async function ({ records }) {
|
|
50
|
+
await afterFind.call(this, { records }, opts)
|
|
51
|
+
},
|
|
52
|
+
afterFindOne: async function ({ record }) {
|
|
53
|
+
await afterGet.call(this, { record }, opts)
|
|
54
|
+
},
|
|
55
|
+
afterGet: async function ({ record }) {
|
|
56
|
+
await afterGet.call(this, { record }, opts)
|
|
57
|
+
},
|
|
58
|
+
beforeCreate: async function ({ body }) {
|
|
59
|
+
await beforeCreate.call(this, { body }, opts)
|
|
60
|
+
},
|
|
61
|
+
afterCreate: async function ({ record }) {
|
|
62
|
+
await afterCreate.call(this, { record }, opts)
|
|
63
|
+
},
|
|
64
|
+
beforeUpdate: async function ({ schema, id, body, options }) {
|
|
65
|
+
await beforeCreate.call(this, { body }, opts)
|
|
66
|
+
},
|
|
67
|
+
afterUpdate: async function ({ record }) {
|
|
68
|
+
await afterCreate.call(this, { record }, opts)
|
|
69
|
+
},
|
|
70
|
+
beforeRemove: async function ({ schema, id, options }) {
|
|
71
|
+
const { recordUpdate, recordGet } = this.app.dobo
|
|
72
|
+
await recordGet(schema.name, id, options)
|
|
73
|
+
const { set } = this.app.bajo.lib._
|
|
74
|
+
const body = set({}, opts.fieldName, new Date())
|
|
75
|
+
const record = await recordUpdate(schema.name, id, body, { dataOnly: false, noValidation: true, noFeatureHook: true })
|
|
76
|
+
options.record = { oldData: record.oldData }
|
|
77
|
+
},
|
|
78
|
+
afterRemove: async function ({ record }) {
|
|
79
|
+
delete record.oldData[opts.fieldName]
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default removedAt
|
package/lib/exec-feature-hook.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
async function execFeatureHook (name,
|
|
1
|
+
async function execFeatureHook (name, params = {}) {
|
|
2
2
|
const { get } = this.app.bajo.lib._
|
|
3
|
+
const { schema } = params
|
|
3
4
|
for (const f of schema.feature) {
|
|
4
5
|
const fn = get(this.feature, f.name)
|
|
5
6
|
if (!fn) continue
|
|
6
7
|
const input = await fn.call(this, f)
|
|
7
8
|
const hook = get(input, 'hook.' + name)
|
|
8
|
-
if (hook) await hook.call(this,
|
|
9
|
+
if (hook) await hook.call(this, params)
|
|
9
10
|
}
|
|
10
11
|
}
|
|
11
12
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,317 @@
|
|
|
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
|
+
import memDbStart from '../lib/mem-db/start.js'
|
|
6
|
+
import memDbInstantiate from '../lib/mem-db/instantiate.js'
|
|
7
|
+
import nql from '@tryghost/nql'
|
|
8
|
+
|
|
9
|
+
async function factory (pkgName) {
|
|
10
|
+
const me = this
|
|
11
|
+
|
|
12
|
+
return class Dobo extends this.lib.BajoPlugin {
|
|
13
|
+
constructor () {
|
|
14
|
+
super(pkgName, me.app)
|
|
15
|
+
this.alias = 'db'
|
|
16
|
+
this.config = {
|
|
17
|
+
connections: [],
|
|
18
|
+
mergeProps: ['connections'],
|
|
19
|
+
validationParams: {
|
|
20
|
+
abortEarly: false,
|
|
21
|
+
convert: false,
|
|
22
|
+
allowUnknown: true
|
|
23
|
+
},
|
|
24
|
+
default: {
|
|
25
|
+
property: {
|
|
26
|
+
text: {
|
|
27
|
+
kind: 'text'
|
|
28
|
+
},
|
|
29
|
+
string: {
|
|
30
|
+
length: 50
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
filter: {
|
|
34
|
+
limit: 25,
|
|
35
|
+
maxLimit: 200,
|
|
36
|
+
sort: ['dt:-1', 'updatedAt:-1', 'updated_at:-1', 'createdAt:-1', 'createdAt:-1', 'ts:-1', 'username', 'name']
|
|
37
|
+
},
|
|
38
|
+
idField: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
maxLength: 50,
|
|
41
|
+
required: true,
|
|
42
|
+
index: { type: 'primary' }
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
memDb: {
|
|
46
|
+
createDefConnAtStart: true,
|
|
47
|
+
persistence: {
|
|
48
|
+
syncPeriod: 1
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
this.aggregateTypes = ['count', 'avg', 'min', 'max', 'sum']
|
|
53
|
+
this.propType = {
|
|
54
|
+
integer: {
|
|
55
|
+
validator: 'number'
|
|
56
|
+
},
|
|
57
|
+
smallint: {
|
|
58
|
+
validator: 'number'
|
|
59
|
+
},
|
|
60
|
+
text: {
|
|
61
|
+
validator: 'string',
|
|
62
|
+
kind: 'text',
|
|
63
|
+
choices: ['text', 'mediumtext', 'longtext']
|
|
64
|
+
},
|
|
65
|
+
string: {
|
|
66
|
+
validator: 'string',
|
|
67
|
+
maxLength: 255,
|
|
68
|
+
minLength: 0
|
|
69
|
+
},
|
|
70
|
+
float: {
|
|
71
|
+
validator: 'number'
|
|
72
|
+
},
|
|
73
|
+
double: {
|
|
74
|
+
validator: 'number'
|
|
75
|
+
},
|
|
76
|
+
boolean: {
|
|
77
|
+
validator: 'boolean'
|
|
78
|
+
},
|
|
79
|
+
date: {
|
|
80
|
+
validator: 'date'
|
|
81
|
+
},
|
|
82
|
+
datetime: {
|
|
83
|
+
validator: 'date'
|
|
84
|
+
},
|
|
85
|
+
time: {
|
|
86
|
+
validator: 'date'
|
|
87
|
+
},
|
|
88
|
+
timestamp: {
|
|
89
|
+
validator: 'timestamp'
|
|
90
|
+
},
|
|
91
|
+
object: {},
|
|
92
|
+
array: {}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
init = async () => {
|
|
97
|
+
const { buildCollections } = this.app.bajo
|
|
98
|
+
const { fs } = this.app.bajo.lib
|
|
99
|
+
const checkType = async (item, items) => {
|
|
100
|
+
const { filter } = this.app.bajo.lib._
|
|
101
|
+
const existing = filter(items, { type: 'dobo:memory' })
|
|
102
|
+
if (existing.length > 1) this.fatal('onlyOneConnType%s', item.type)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fs.ensureDirSync(`${this.dir.data}/attachment`)
|
|
106
|
+
await collectDrivers.call(this)
|
|
107
|
+
if (this.config.memDb.createDefConnAtStart) {
|
|
108
|
+
this.config.connections.push({
|
|
109
|
+
type: 'dobo:memory',
|
|
110
|
+
name: 'memory'
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
this.connections = await buildCollections({ ns: this.name, container: 'connections', handler: collectConnections, dupChecks: ['name', checkType] })
|
|
114
|
+
if (this.connections.length === 0) this.log.warn('notFound%s', this.print.write('connection'))
|
|
115
|
+
await collectFeature.call(this)
|
|
116
|
+
await collectSchemas.call(this)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
start = async (conns = 'all', noRebuild = true) => {
|
|
120
|
+
const { importModule, breakNsPath } = this.app.bajo
|
|
121
|
+
const { find, filter, isString, map } = this.app.bajo.lib._
|
|
122
|
+
if (conns === 'all') conns = this.connections
|
|
123
|
+
else if (isString(conns)) conns = filter(this.connections, { name: conns })
|
|
124
|
+
else conns = map(conns, c => find(this.connections, { name: c }))
|
|
125
|
+
for (const c of conns) {
|
|
126
|
+
const { ns } = breakNsPath(c.type)
|
|
127
|
+
const schemas = filter(this.schemas, { connection: c.name })
|
|
128
|
+
const mod = c.type === 'dobo:memory' ? memDbInstantiate : await importModule(`${ns}:/${this.name}/boot/instantiate.js`)
|
|
129
|
+
await mod.call(this.app[ns], { connection: c, noRebuild, schemas })
|
|
130
|
+
this.log.trace('driverInstantiated%s%s', c.driver, c.name)
|
|
131
|
+
}
|
|
132
|
+
await memDbStart.call(this)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
pickRecord = async ({ record, fields, schema = {}, hidden = [], forceNoHidden } = {}) => {
|
|
136
|
+
const { isArray, pick, clone, isEmpty, omit } = this.app.bajo.lib._
|
|
137
|
+
const { dayjs } = this.app.bajo.lib
|
|
138
|
+
|
|
139
|
+
const transform = async ({ record, schema, hidden = [], forceNoHidden } = {}) => {
|
|
140
|
+
if (record._id) {
|
|
141
|
+
record.id = record._id
|
|
142
|
+
delete record._id
|
|
143
|
+
}
|
|
144
|
+
const defHidden = [...schema.hidden, ...hidden]
|
|
145
|
+
let result = {}
|
|
146
|
+
for (const p of schema.properties) {
|
|
147
|
+
if (!forceNoHidden && defHidden.includes(p.name)) continue
|
|
148
|
+
result[p.name] = record[p.name] ?? null
|
|
149
|
+
if (record[p.name] === null) continue
|
|
150
|
+
switch (p.type) {
|
|
151
|
+
case 'boolean': result[p.name] = !!result[p.name]; break
|
|
152
|
+
case 'time': result[p.name] = dayjs(record[p.name]).format('HH:mm:ss'); break
|
|
153
|
+
case 'date': result[p.name] = dayjs(record[p.name]).format('YYYY-MM-DD'); break
|
|
154
|
+
case 'datetime': result[p.name] = dayjs(record[p.name]).toISOString(); break
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
result = await this.sanitizeBody({ body: result, schema, partial: true, ignoreNull: true })
|
|
158
|
+
if (record._rel) result._rel = record._rel
|
|
159
|
+
return result
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (isEmpty(record)) return record
|
|
163
|
+
if (hidden.length > 0) record = omit(record, hidden)
|
|
164
|
+
if (!isArray(fields)) return await transform.call(this, { record, schema, hidden, forceNoHidden })
|
|
165
|
+
const fl = clone(fields)
|
|
166
|
+
if (!fl.includes('id')) fl.unshift('id')
|
|
167
|
+
if (record._rel) fl.push('_rel')
|
|
168
|
+
return pick(await transform.call(this, { record, schema, hidden, forceNoHidden }), fl)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
prepPagination = async (filter = {}, schema, options = {}) => {
|
|
172
|
+
const buildPageSkipLimit = (filter) => {
|
|
173
|
+
let limit = parseInt(filter.limit) || this.config.default.filter.limit
|
|
174
|
+
if (limit === -1) limit = this.config.default.filter.maxLimit
|
|
175
|
+
if (limit > this.config.default.filter.maxLimit) limit = this.config.default.filter.maxLimit
|
|
176
|
+
if (limit < 1) limit = 1
|
|
177
|
+
let page = parseInt(filter.page) || 1
|
|
178
|
+
if (page < 1) page = 1
|
|
179
|
+
let skip = (page - 1) * limit
|
|
180
|
+
if (filter.skip) {
|
|
181
|
+
skip = parseInt(filter.skip) || skip
|
|
182
|
+
page = undefined
|
|
183
|
+
}
|
|
184
|
+
if (skip < 0) skip = 0
|
|
185
|
+
return { page, skip, limit }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const buildSort = (input, schema, allowSortUnindexed) => {
|
|
189
|
+
const { isEmpty, map, each, isPlainObject, isString, trim, keys } = this.app.bajo.lib._
|
|
190
|
+
let sort
|
|
191
|
+
if (schema && isEmpty(input)) {
|
|
192
|
+
const columns = map(schema.properties, 'name')
|
|
193
|
+
each(this.config.default.filter.sort, s => {
|
|
194
|
+
const [col] = s.split(':')
|
|
195
|
+
if (columns.includes(col)) {
|
|
196
|
+
input = s
|
|
197
|
+
return false
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
if (!isEmpty(input)) {
|
|
202
|
+
if (isPlainObject(input)) sort = input
|
|
203
|
+
else if (isString(input)) {
|
|
204
|
+
const item = {}
|
|
205
|
+
each(input.split('+'), text => {
|
|
206
|
+
let [col, dir] = map(trim(text).split(':'), i => trim(i))
|
|
207
|
+
dir = (dir ?? '').toUpperCase()
|
|
208
|
+
dir = dir === 'DESC' ? -1 : parseInt(dir) || 1
|
|
209
|
+
item[col] = dir / Math.abs(dir)
|
|
210
|
+
})
|
|
211
|
+
sort = item
|
|
212
|
+
}
|
|
213
|
+
if (schema) {
|
|
214
|
+
const items = keys(sort)
|
|
215
|
+
each(items, i => {
|
|
216
|
+
if (!schema.sortables.includes(i) && !allowSortUnindexed) throw this.error('sortOnUnindexedField%s%s', i, schema.name)
|
|
217
|
+
// if (schema.fullText.fields.includes(i)) throw this.error('Can\'t sort on full-text index: \'%s@%s\'', i, schema.name)
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return sort
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const { page, skip, limit } = buildPageSkipLimit.call(this, filter)
|
|
225
|
+
let sortInput = filter.sort
|
|
226
|
+
try {
|
|
227
|
+
sortInput = JSON.parse(sortInput)
|
|
228
|
+
} catch (err) {}
|
|
229
|
+
const sort = buildSort.call(this, sortInput, schema, options.allowSortUnindexed)
|
|
230
|
+
return { limit, page, skip, sort }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
buildMatch = ({ input = '', schema, options }) => {
|
|
234
|
+
const { isPlainObject, trim } = this.app.bajo.lib._
|
|
235
|
+
const split = (value, schema) => {
|
|
236
|
+
let [field, val] = value.split(':').map(i => i.trim())
|
|
237
|
+
if (!val) {
|
|
238
|
+
val = field
|
|
239
|
+
field = '*'
|
|
240
|
+
}
|
|
241
|
+
return { field, value: val }
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
input = trim(input)
|
|
245
|
+
let items = {}
|
|
246
|
+
if (isPlainObject(input)) items = input
|
|
247
|
+
else if (input[0] === '{') items = JSON.parse(input)
|
|
248
|
+
else {
|
|
249
|
+
for (const item of input.split('+').map(i => i.trim())) {
|
|
250
|
+
const part = split(item, schema)
|
|
251
|
+
if (!items[part.field]) items[part.field] = []
|
|
252
|
+
items[part.field].push(...part.value.split(' ').filter(v => ![''].includes(v)))
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const matcher = {}
|
|
256
|
+
for (const f of schema.fullText.fields) {
|
|
257
|
+
const value = []
|
|
258
|
+
if (typeof items[f] === 'string') items[f] = [items[f]]
|
|
259
|
+
if (Object.prototype.hasOwnProperty.call(items, f)) value.push(...items[f])
|
|
260
|
+
matcher[f] = value
|
|
261
|
+
}
|
|
262
|
+
if (Object.prototype.hasOwnProperty.call(items, '*')) matcher['*'] = items['*']
|
|
263
|
+
return matcher
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
buildQuery = async ({ filter, schema, options = {} } = {}) => {
|
|
267
|
+
const { trim, isString, isPlainObject } = this.app.bajo.lib._
|
|
268
|
+
let query = {}
|
|
269
|
+
if (isString(filter.query)) {
|
|
270
|
+
filter.oquery = filter.query
|
|
271
|
+
if (trim(filter.query).startsWith('{')) query = JSON.parse(filter.query)
|
|
272
|
+
else query = nql(filter.query).parse()
|
|
273
|
+
} else if (isPlainObject(filter.query)) query = filter.query
|
|
274
|
+
return query
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
validationErrorMessage = (err) => {
|
|
278
|
+
let text = err.message
|
|
279
|
+
if (err.details) {
|
|
280
|
+
text += ' -> '
|
|
281
|
+
text += this.app.bajo.join(err.details.map((d, idx) => {
|
|
282
|
+
return `${d.field}@${err.model}: ${d.error} (${d.value})`
|
|
283
|
+
}))
|
|
284
|
+
}
|
|
285
|
+
return text
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
getConnection = (name) => {
|
|
289
|
+
const { find } = this.app.bajo.lib._
|
|
290
|
+
return find(this.connections, { name })
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
getInfo = (name) => {
|
|
294
|
+
const { breakNsPath } = this.app.bajo
|
|
295
|
+
const { find, map } = this.app.bajo.lib._
|
|
296
|
+
const schema = this.getSchema(name)
|
|
297
|
+
const conn = this.getConnection(schema.connection)
|
|
298
|
+
const { ns, path: type } = breakNsPath(conn.type)
|
|
299
|
+
const driver = find(this.drivers, { type, ns, driver: conn.driver })
|
|
300
|
+
const instance = find(this.app[driver.ns].instances, { name: schema.connection })
|
|
301
|
+
const opts = conn.type === 'mssql' ? { includeTriggerModifications: true } : undefined
|
|
302
|
+
const returning = [map(schema.properties, 'name'), opts]
|
|
303
|
+
return { instance, driver, connection: conn, returning, schema }
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
getSchema = (input, cloned = true) => {
|
|
307
|
+
const { find, isPlainObject, cloneDeep } = this.app.bajo.lib._
|
|
308
|
+
let name = isPlainObject(input) ? input.name : input
|
|
309
|
+
name = this.app.bajo.pascalCase(name)
|
|
310
|
+
const schema = find(this.schemas, { name })
|
|
311
|
+
if (!schema) throw this.error('unknownModelSchema%s', name)
|
|
312
|
+
return cloned ? cloneDeep(schema) : schema
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export default factory
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import resolveMethod from '../../../lib/resolve-method.js'
|
|
2
|
+
import execFeatureHook from '../../../lib/exec-feature-hook.js'
|
|
2
3
|
|
|
3
4
|
async function count (name, filter = {}, opts = {}) {
|
|
4
5
|
const { runHook } = this.app.bajo
|
|
5
6
|
const { get, set } = this.cache ?? {}
|
|
6
7
|
const { cloneDeep, camelCase, omit } = this.app.bajo.lib._
|
|
8
|
+
delete opts.record
|
|
7
9
|
const options = cloneDeep(omit(opts, ['req', 'reply']))
|
|
8
10
|
options.req = opts.req
|
|
9
11
|
options.reply = opts.reply
|
|
10
12
|
options.dataOnly = options.dataOnly ?? true
|
|
11
|
-
let { dataOnly, noHook, noCache } = options
|
|
13
|
+
let { dataOnly, noHook, noCache, noFeatureHook } = options
|
|
12
14
|
options.dataOnly = false
|
|
13
15
|
await this.modelExists(name, true)
|
|
14
16
|
const { handler, schema, driver } = await resolveMethod.call(this, name, 'record-count', options)
|
|
@@ -20,20 +22,23 @@ async function count (name, filter = {}, opts = {}) {
|
|
|
20
22
|
await runHook(`${this.name}:beforeRecordCount`, name, filter, options)
|
|
21
23
|
await runHook(`${this.name}.${camelCase(name)}:beforeRecordCount`, filter, options)
|
|
22
24
|
}
|
|
23
|
-
if (
|
|
25
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'beforeCount', { schema, filter, options })
|
|
26
|
+
if (get && !noCache && !options.record) {
|
|
24
27
|
const cachedResult = await get({ model: name, filter, options })
|
|
25
28
|
if (cachedResult) {
|
|
26
29
|
cachedResult.cached = true
|
|
27
30
|
return dataOnly ? cachedResult.data : cachedResult
|
|
28
31
|
}
|
|
29
32
|
}
|
|
30
|
-
const
|
|
33
|
+
const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, filter, options }))
|
|
34
|
+
delete options.record
|
|
31
35
|
if (!noHook) {
|
|
32
|
-
await runHook(`${this.name}.${camelCase(name)}:afterRecordCount`, filter, options,
|
|
33
|
-
await runHook(`${this.name}:afterRecordCount`, name, filter, options,
|
|
36
|
+
await runHook(`${this.name}.${camelCase(name)}:afterRecordCount`, filter, options, record)
|
|
37
|
+
await runHook(`${this.name}:afterRecordCount`, name, filter, options, record)
|
|
34
38
|
}
|
|
35
|
-
if (set && !noCache) await set({ model: name, filter, options,
|
|
36
|
-
|
|
39
|
+
if (set && !noCache) await set({ model: name, filter, options, record })
|
|
40
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'afterCount', { schema, filter, options, record })
|
|
41
|
+
return dataOnly ? record.data : record
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
export default count
|
|
@@ -10,6 +10,7 @@ async function create (name, input, opts = {}) {
|
|
|
10
10
|
const { generateId, runHook, isSet } = this.app.bajo
|
|
11
11
|
const { clearModel } = this.cache ?? {}
|
|
12
12
|
const { find, forOwn, cloneDeep, camelCase, omit, get, pick } = this.app.bajo.lib._
|
|
13
|
+
delete opts.record
|
|
13
14
|
const options = cloneDeep(omit(opts, ['req', 'reply']))
|
|
14
15
|
options.req = opts.req
|
|
15
16
|
options.reply = opts.reply
|
|
@@ -37,10 +38,8 @@ async function create (name, input, opts = {}) {
|
|
|
37
38
|
}
|
|
38
39
|
} else if (['integer', 'smallint'].includes(idField.type) && !idField.autoInc) input.id = generateId('int')
|
|
39
40
|
}
|
|
40
|
-
if (!noFeatureHook) await execFeatureHook.call(this, 'beforeCreate', { schema, body })
|
|
41
41
|
if (!noValidation) body = await execValidation.call(this, { name, body, options })
|
|
42
42
|
if (isSet(body.id) && !noCheckUnique) await checkUnique.call(this, { schema, body })
|
|
43
|
-
let record = {}
|
|
44
43
|
const nbody = {}
|
|
45
44
|
forOwn(body, (v, k) => {
|
|
46
45
|
if (v === undefined) return undefined
|
|
@@ -49,20 +48,22 @@ async function create (name, input, opts = {}) {
|
|
|
49
48
|
if (options.truncateString && isSet(v) && ['string', 'text'].includes(prop.type)) v = v.slice(0, prop.maxLength)
|
|
50
49
|
nbody[k] = v
|
|
51
50
|
})
|
|
52
|
-
|
|
51
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'beforeCreate', { schema, body: nbody, options })
|
|
52
|
+
const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, body: nbody, options }))
|
|
53
|
+
delete options.record
|
|
53
54
|
if (isSet(options.rels)) await singleRelRows.call(this, { schema, record: record.data, options })
|
|
54
55
|
if (options.req) {
|
|
55
56
|
if (options.req.file) await handleAttachmentUpload.call(this, { name: schema.name, id: body.id, body, options, action: 'create' })
|
|
56
57
|
if (options.req.flash && !options.noFlash) options.req.flash('notify', options.req.t('recordCreated'))
|
|
57
58
|
}
|
|
58
|
-
if (!noFeatureHook) await execFeatureHook.call(this, 'afterCreate', { schema, body, record })
|
|
59
59
|
if (!noHook) {
|
|
60
|
-
await runHook(`${this.name}.${camelCase(name)}:afterRecordCreate`,
|
|
61
|
-
await runHook(`${this.name}:afterRecordCreate`, name,
|
|
60
|
+
await runHook(`${this.name}.${camelCase(name)}:afterRecordCreate`, nbody, options, record)
|
|
61
|
+
await runHook(`${this.name}:afterRecordCreate`, name, nbody, options, record)
|
|
62
62
|
}
|
|
63
|
-
if (clearModel) await clearModel({ model: name, body, options, record })
|
|
63
|
+
if (clearModel) await clearModel({ model: name, body: nbody, options, record })
|
|
64
64
|
if (noResult) return
|
|
65
65
|
record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
|
|
66
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'afterCreate', { schema, body: nbody, options, record })
|
|
66
67
|
return dataOnly ? record.data : record
|
|
67
68
|
}
|
|
68
69
|
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import resolveMethod from '../../../lib/resolve-method.js'
|
|
2
2
|
import singleRelRows from '../../../lib/single-rel-rows.js'
|
|
3
|
+
import execFeatureHook from '../../../lib/exec-feature-hook.js'
|
|
3
4
|
|
|
4
5
|
async function findOne (name, filter = {}, opts = {}) {
|
|
5
6
|
const { runHook, isSet } = this.app.bajo
|
|
6
7
|
const { get, set } = this.cache ?? {}
|
|
7
8
|
const { cloneDeep, camelCase, omit } = this.app.bajo.lib._
|
|
9
|
+
delete opts.record
|
|
8
10
|
const options = cloneDeep(omit(opts, ['req', 'reply']))
|
|
9
11
|
options.req = opts.req
|
|
10
12
|
options.reply = opts.reply
|
|
11
13
|
options.dataOnly = options.dataOnly ?? true
|
|
12
|
-
let { fields, dataOnly, noHook, noCache, hidden, forceNoHidden } = options
|
|
14
|
+
let { fields, dataOnly, noHook, noCache, noFeatureHook, hidden, forceNoHidden } = options
|
|
13
15
|
options.count = false
|
|
14
16
|
options.dataOnly = false
|
|
15
17
|
await this.modelExists(name, true)
|
|
@@ -23,15 +25,19 @@ async function findOne (name, filter = {}, opts = {}) {
|
|
|
23
25
|
await runHook(`${this.name}:beforeRecordFindOne`, name, filter, options)
|
|
24
26
|
await runHook(`${this.name}.${camelCase(name)}:beforeRecordFindOne`, filter, options)
|
|
25
27
|
}
|
|
26
|
-
if (
|
|
28
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'beforeFindOne', { schema, filter, options })
|
|
29
|
+
if (get && !noCache && !options.record) {
|
|
27
30
|
const cachedResult = await get({ model: name, filter, options })
|
|
28
31
|
if (cachedResult) {
|
|
29
32
|
cachedResult.cached = true
|
|
30
33
|
return dataOnly ? cachedResult.data : cachedResult
|
|
31
34
|
}
|
|
32
35
|
}
|
|
33
|
-
|
|
36
|
+
filter.limit = 1
|
|
37
|
+
const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, filter, options }))
|
|
38
|
+
delete options.record
|
|
34
39
|
record.data = record.data[0]
|
|
40
|
+
|
|
35
41
|
if (isSet(options.rels)) await singleRelRows.call(this, { schema, record: record.data, options })
|
|
36
42
|
if (!noHook) {
|
|
37
43
|
await runHook(`${this.name}.${camelCase(name)}:afterRecordFindOne`, filter, options, record)
|
|
@@ -39,6 +45,7 @@ async function findOne (name, filter = {}, opts = {}) {
|
|
|
39
45
|
}
|
|
40
46
|
record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
|
|
41
47
|
if (set && !noCache) await set({ model: name, filter, options, record })
|
|
48
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'afterFindOne', { schema, filter, options, record })
|
|
42
49
|
return dataOnly ? record.data : record
|
|
43
50
|
}
|
|
44
51
|
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import resolveMethod from '../../../lib/resolve-method.js'
|
|
2
2
|
import multiRelRows from '../../../lib/multi-rel-rows.js'
|
|
3
|
+
import execFeatureHook from '../../../lib/exec-feature-hook.js'
|
|
3
4
|
|
|
4
5
|
async function find (name, filter = {}, opts = {}) {
|
|
5
6
|
const { runHook, isSet } = this.app.bajo
|
|
6
7
|
const { get, set } = this.cache ?? {}
|
|
7
8
|
const { cloneDeep, camelCase, omit } = this.app.bajo.lib._
|
|
9
|
+
delete opts.records
|
|
8
10
|
const options = cloneDeep(omit(opts, ['req', 'reply']))
|
|
9
11
|
options.req = opts.req
|
|
10
12
|
options.reply = opts.reply
|
|
11
13
|
options.dataOnly = options.dataOnly ?? true
|
|
12
|
-
let { fields, dataOnly, noHook, noCache, hidden, forceNoHidden } = options
|
|
14
|
+
let { fields, dataOnly, noHook, noCache, noFeatureHook, hidden, forceNoHidden } = options
|
|
13
15
|
options.count = options.count ?? false
|
|
14
16
|
options.dataOnly = false
|
|
15
17
|
await this.modelExists(name, true)
|
|
@@ -22,14 +24,17 @@ async function find (name, filter = {}, opts = {}) {
|
|
|
22
24
|
await runHook(`${this.name}:beforeRecordFind`, name, filter, options)
|
|
23
25
|
await runHook(`${this.name}.${camelCase(name)}:beforeRecordFind`, filter, options)
|
|
24
26
|
}
|
|
25
|
-
if (
|
|
27
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'beforeFind', { schema, filter, options })
|
|
28
|
+
if (get && !noCache && !options.records) {
|
|
26
29
|
const cachedResult = await get({ model: name, filter, options })
|
|
27
30
|
if (cachedResult) {
|
|
28
31
|
cachedResult.cached = true
|
|
32
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'afterFind', { schema, filter, options, records: cachedResult })
|
|
29
33
|
return dataOnly ? cachedResult.data : cachedResult
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
|
-
const records = await handler.call(this.app[driver.ns], { schema, filter, options })
|
|
36
|
+
const records = options.records ?? (await handler.call(this.app[driver.ns], { schema, filter, options }))
|
|
37
|
+
delete options.records
|
|
33
38
|
if (isSet(options.rels)) await multiRelRows.call(this, { schema, records: records.data, options })
|
|
34
39
|
if (!noHook) {
|
|
35
40
|
await runHook(`${this.name}.${camelCase(name)}:afterRecordFind`, filter, options, records)
|
|
@@ -39,6 +44,7 @@ async function find (name, filter = {}, opts = {}) {
|
|
|
39
44
|
records.data[idx] = await this.pickRecord({ record: records.data[idx], fields, schema, hidden, forceNoHidden })
|
|
40
45
|
}
|
|
41
46
|
if (set && !noCache) await set({ model: name, filter, options, records })
|
|
47
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'afterFind', { schema, filter, options, records })
|
|
42
48
|
return dataOnly ? records.data : records
|
|
43
49
|
}
|
|
44
50
|
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import resolveMethod from '../../../lib/resolve-method.js'
|
|
2
2
|
import singleRelRows from '../../../lib/single-rel-rows.js'
|
|
3
|
+
import execFeatureHook from '../../../lib/exec-feature-hook.js'
|
|
3
4
|
|
|
4
5
|
async function get (name, id, opts = {}) {
|
|
5
6
|
const { runHook, isSet } = this.app.bajo
|
|
6
7
|
const { get, set } = this.cache ?? {}
|
|
7
8
|
const { cloneDeep, camelCase, omit } = this.app.bajo.lib._
|
|
9
|
+
delete opts.record
|
|
8
10
|
const options = cloneDeep(omit(opts, ['req', 'reply']))
|
|
9
11
|
options.req = opts.req
|
|
10
12
|
options.reply = opts.reply
|
|
11
13
|
options.dataOnly = options.dataOnly ?? true
|
|
12
|
-
let { fields, dataOnly, noHook, noCache, hidden = [], forceNoHidden } = options
|
|
14
|
+
let { fields, dataOnly, noHook, noCache, noFeatureHook, hidden = [], forceNoHidden } = options
|
|
13
15
|
await this.modelExists(name, true)
|
|
14
16
|
const { handler, schema, driver } = await resolveMethod.call(this, name, 'record-get', options)
|
|
15
17
|
if (!schema.cacheable) noCache = true
|
|
@@ -19,14 +21,17 @@ async function get (name, id, opts = {}) {
|
|
|
19
21
|
await runHook(`${this.name}:beforeRecordGet`, name, id, options)
|
|
20
22
|
await runHook(`${this.name}.${camelCase(name)}:beforeRecordGet`, id, options)
|
|
21
23
|
}
|
|
22
|
-
if (
|
|
24
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'beforeGet', { schema, id, options })
|
|
25
|
+
if (get && !noCache && !options.record) {
|
|
23
26
|
const cachedResult = await get({ model: name, id, options })
|
|
24
27
|
if (cachedResult) {
|
|
25
28
|
cachedResult.cached = true
|
|
29
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'afterGet', { schema, id, options, record: cachedResult })
|
|
26
30
|
return dataOnly ? cachedResult.data : cachedResult
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
|
-
const record = await handler.call(this.app[driver.ns], { schema, id, options })
|
|
33
|
+
const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, id, options }))
|
|
34
|
+
delete options.record
|
|
30
35
|
if (isSet(options.rels)) await singleRelRows.call(this, { schema, record: record.data, options })
|
|
31
36
|
if (!noHook) {
|
|
32
37
|
await runHook(`${this.name}.${camelCase(name)}:afterRecordGet`, id, options, record)
|
|
@@ -35,6 +40,7 @@ async function get (name, id, opts = {}) {
|
|
|
35
40
|
record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
|
|
36
41
|
|
|
37
42
|
if (set && !noCache) await set({ model: name, id, options, record })
|
|
43
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'afterGet', { schema, id, options, record })
|
|
38
44
|
return dataOnly ? record.data : record
|
|
39
45
|
}
|
|
40
46
|
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import resolveMethod from '../../../lib/resolve-method.js'
|
|
2
2
|
import handleAttachmentUpload from '../../../lib/handle-attachment-upload.js'
|
|
3
|
+
import execFeatureHook from '../../../lib/exec-feature-hook.js'
|
|
3
4
|
|
|
4
5
|
async function remove (name, id, opts = {}) {
|
|
5
6
|
const { runHook } = this.app.bajo
|
|
6
7
|
const { clearModel } = this.cache ?? {}
|
|
7
8
|
const { cloneDeep, camelCase, omit } = this.app.bajo.lib._
|
|
9
|
+
delete opts.record
|
|
8
10
|
const options = cloneDeep(omit(opts, ['req', 'reply']))
|
|
9
11
|
options.req = opts.req
|
|
10
12
|
options.reply = opts.reply
|
|
11
13
|
options.dataOnly = options.dataOnly ?? true
|
|
12
|
-
const { fields, dataOnly, noHook, noResult, hidden, forceNoHidden } = options
|
|
14
|
+
const { fields, dataOnly, noHook, noResult, noFeatureHook, hidden, forceNoHidden } = options
|
|
13
15
|
options.dataOnly = false
|
|
14
16
|
await this.modelExists(name, true)
|
|
15
17
|
const { handler, schema, driver } = await resolveMethod.call(this, name, 'record-remove', options)
|
|
@@ -18,7 +20,9 @@ async function remove (name, id, opts = {}) {
|
|
|
18
20
|
await runHook(`${this.name}:beforeRecordRemove`, name, id, options)
|
|
19
21
|
await runHook(`${this.name}.${camelCase(name)}:beforeRecordRemove`, id, options)
|
|
20
22
|
}
|
|
21
|
-
|
|
23
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'beforeRemove', { schema, id, options })
|
|
24
|
+
const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, id, options }))
|
|
25
|
+
delete options.record
|
|
22
26
|
if (options.req) {
|
|
23
27
|
if (options.req.file) await handleAttachmentUpload.call(this, { name: schema.name, id, options, action: 'remove' })
|
|
24
28
|
if (options.req.flash && !options.noFlash) options.req.flash('notify', options.req.t('recordRemoved'))
|
|
@@ -29,7 +33,8 @@ async function remove (name, id, opts = {}) {
|
|
|
29
33
|
}
|
|
30
34
|
if (clearModel) await clearModel({ model: name, id, options, record })
|
|
31
35
|
if (noResult) return
|
|
32
|
-
record.oldData = await this.pickRecord({ record: record.oldData, fields, schema, hidden, forceNoHidden })
|
|
36
|
+
record.oldData = options.record ? options.record.oldData : (await this.pickRecord({ record: record.oldData, fields, schema, hidden, forceNoHidden }))
|
|
37
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'afterRemove', { schema, id, options, record })
|
|
33
38
|
return dataOnly ? record.oldData : record
|
|
34
39
|
}
|
|
35
40
|
|
|
@@ -9,6 +9,7 @@ async function update (name, id, input, opts = {}) {
|
|
|
9
9
|
const { runHook, isSet } = this.app.bajo
|
|
10
10
|
const { clearModel } = this.cache ?? {}
|
|
11
11
|
const { forOwn, find, cloneDeep, camelCase, omit, get } = this.app.bajo.lib._
|
|
12
|
+
delete opts.record
|
|
12
13
|
const options = cloneDeep(omit(opts, ['req', 'reply']))
|
|
13
14
|
options.req = opts.req
|
|
14
15
|
options.reply = opts.reply
|
|
@@ -27,7 +28,6 @@ async function update (name, id, input, opts = {}) {
|
|
|
27
28
|
await runHook(`${this.name}:beforeRecordUpdate`, name, id, body, options)
|
|
28
29
|
await runHook(`${this.name}.${camelCase(name)}:beforeRecordUpdate`, id, body, options)
|
|
29
30
|
}
|
|
30
|
-
if (!noFeatureHook) await execFeatureHook.call(this, 'beforeUpdate', { schema, body })
|
|
31
31
|
if (!noValidation) body = await execValidation.call(this, { name, body, options, partial })
|
|
32
32
|
if (!noCheckUnique) await checkUnique.call(this, { schema, body, id })
|
|
33
33
|
const nbody = {}
|
|
@@ -39,13 +39,14 @@ async function update (name, id, input, opts = {}) {
|
|
|
39
39
|
nbody[k] = v
|
|
40
40
|
})
|
|
41
41
|
delete nbody.id
|
|
42
|
-
|
|
42
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'beforeUpdate', { schema, body: nbody, options })
|
|
43
|
+
const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, id, body: nbody, options }))
|
|
44
|
+
delete options.record
|
|
43
45
|
if (isSet(options.rels)) await singleRelRows.call(this, { schema, record: record.data, options })
|
|
44
46
|
if (options.req) {
|
|
45
47
|
if (options.req.file) await handleAttachmentUpload.call(this, { name: schema.name, id, body, options, action: 'update' })
|
|
46
48
|
if (options.req.flash && !options.noFlash) options.req.flash('notify', options.req.t('recordUpdated'))
|
|
47
49
|
}
|
|
48
|
-
if (!noFeatureHook) await execFeatureHook.call(this, 'afterUpdate', { schema, body: nbody, record })
|
|
49
50
|
if (!noHook) {
|
|
50
51
|
await runHook(`${this.name}.${camelCase(name)}:afterRecordUpdate`, id, nbody, options, record)
|
|
51
52
|
await runHook(`${this.name}:afterRecordUpdate`, name, id, nbody, options, record)
|
|
@@ -54,6 +55,7 @@ async function update (name, id, input, opts = {}) {
|
|
|
54
55
|
if (noResult) return
|
|
55
56
|
record.oldData = await this.pickRecord({ record: record.oldData, fields, schema, hidden, forceNoHidden })
|
|
56
57
|
record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
|
|
58
|
+
if (!noFeatureHook) await execFeatureHook.call(this, 'afterUpdate', { schema, body: nbody, record })
|
|
57
59
|
return dataOnly ? record.data : record
|
|
58
60
|
}
|
|
59
61
|
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
|
|
3
3
|
async function attachment (req, reply) {
|
|
4
|
+
const { isString } = this.app.bajo.lib._
|
|
4
5
|
const { importPkg, getPluginDataDir, pascalCase } = this.app.bajo
|
|
6
|
+
const { routePath } = this.app.waibu
|
|
5
7
|
const mime = await importPkg('waibu:mime')
|
|
6
8
|
const { fs } = this.app.bajo.lib
|
|
7
9
|
const file = `${getPluginDataDir('dobo')}/attachment/${pascalCase(req.params.model)}/${req.params.id}/${req.params.field}/${req.params.file}`
|
|
8
|
-
if (!fs.existsSync(file)) throw this.error('_notFound', { noView: true })
|
|
9
10
|
const mimeType = mime.getType(path.extname(file))
|
|
11
|
+
if (!fs.existsSync(file)) {
|
|
12
|
+
if (!req.query.notfound) throw this.error('_notFound', { noView: true })
|
|
13
|
+
const [, ext] = mimeType.split('/')
|
|
14
|
+
const replacer = isString(req.query.notfound) ? req.query.notfound : `waibuStatic.asset:/not-found.${ext}`
|
|
15
|
+
return reply.redirectTo(routePath(replacer))
|
|
16
|
+
}
|
|
10
17
|
reply.header('Content-Type', mimeType)
|
|
11
18
|
const stream = fs.createReadStream(file)
|
|
12
19
|
reply.send(stream)
|
package/bajo/.alias
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
db
|
package/bajo/config.json
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"connections": [],
|
|
3
|
-
"mergeProps": ["connections"],
|
|
4
|
-
"validationParams": {
|
|
5
|
-
"abortEarly": false,
|
|
6
|
-
"convert": false,
|
|
7
|
-
"allowUnknown": true
|
|
8
|
-
},
|
|
9
|
-
"default": {
|
|
10
|
-
"property": {
|
|
11
|
-
"text": {
|
|
12
|
-
"kind": "text"
|
|
13
|
-
},
|
|
14
|
-
"string": {
|
|
15
|
-
"length": 50
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
"filter": {
|
|
19
|
-
"limit": 25,
|
|
20
|
-
"maxLimit": 200,
|
|
21
|
-
"sort": ["dt:-1", "updatedAt:-1", "updated_at:-1", "createdAt:-1", "createdAt:-1", "ts:-1", "username", "name"]
|
|
22
|
-
},
|
|
23
|
-
"idField": {
|
|
24
|
-
"type": "string",
|
|
25
|
-
"maxLength": 50,
|
|
26
|
-
"required": true,
|
|
27
|
-
"index": { "type": "primary" }
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
"memDb": {
|
|
31
|
-
"createDefConnAtStart": true,
|
|
32
|
-
"persistence": {
|
|
33
|
-
"syncPeriod": 1
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
package/bajo/init.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
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 checkType (item, items) {
|
|
7
|
-
const { filter } = this.app.bajo.lib._
|
|
8
|
-
const existing = filter(items, { type: 'dobo:memory' })
|
|
9
|
-
if (existing.length > 1) this.fatal('onlyOneConnType%s', item.type)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async function init () {
|
|
13
|
-
const { buildCollections } = this.app.bajo
|
|
14
|
-
const { fs } = this.app.bajo.lib
|
|
15
|
-
fs.ensureDirSync(`${this.dir.data}/attachment`)
|
|
16
|
-
await collectDrivers.call(this)
|
|
17
|
-
if (this.config.memDb.createDefConnAtStart) {
|
|
18
|
-
this.config.connections.push({
|
|
19
|
-
type: 'dobo:memory',
|
|
20
|
-
name: 'memory'
|
|
21
|
-
})
|
|
22
|
-
}
|
|
23
|
-
this.connections = await buildCollections({ ns: this.name, container: 'connections', handler: collectConnections, dupChecks: ['name', checkType] })
|
|
24
|
-
if (this.connections.length === 0) this.log.warn('notFound%s', this.print.write('connection'))
|
|
25
|
-
await collectFeature.call(this)
|
|
26
|
-
await collectSchemas.call(this)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export default init
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export default ['count', 'avg', 'min', 'max', 'sum']
|
|
@@ -1,34 +0,0 @@
|
|
|
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
|
|
@@ -1,14 +0,0 @@
|
|
|
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
|
-
filter.oquery = filter.query
|
|
8
|
-
if (trim(filter.query).startsWith('{')) query = JSON.parse(filter.query)
|
|
9
|
-
else query = nql(filter.query).parse()
|
|
10
|
-
} else if (isPlainObject(filter.query)) query = filter.query
|
|
11
|
-
return query
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export default buildQuery
|
package/bajo/method/get-info.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
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 = this.getConnection(schema.connection)
|
|
6
|
-
const { ns, path: 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
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
function getSchema (input, cloned = true) {
|
|
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('unknownModelSchema%s', name)
|
|
7
|
-
return cloned ? cloneDeep(schema) : schema
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export default getSchema
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
async function transform ({ record, schema, hidden = [], forceNoHidden } = {}) {
|
|
2
|
-
const { dayjs } = this.app.bajo.lib
|
|
3
|
-
if (record._id) {
|
|
4
|
-
record.id = record._id
|
|
5
|
-
delete record._id
|
|
6
|
-
}
|
|
7
|
-
const defHidden = [...schema.hidden, ...hidden]
|
|
8
|
-
let result = {}
|
|
9
|
-
for (const p of schema.properties) {
|
|
10
|
-
if (!forceNoHidden && defHidden.includes(p.name)) continue
|
|
11
|
-
result[p.name] = record[p.name] ?? null
|
|
12
|
-
if (record[p.name] === null) continue
|
|
13
|
-
switch (p.type) {
|
|
14
|
-
case 'boolean': result[p.name] = !!result[p.name]; break
|
|
15
|
-
case 'time': result[p.name] = dayjs(record[p.name]).format('HH:mm:ss'); break
|
|
16
|
-
case 'date': result[p.name] = dayjs(record[p.name]).format('YYYY-MM-DD'); break
|
|
17
|
-
case 'datetime': result[p.name] = dayjs(record[p.name]).toISOString(); break
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
result = await this.sanitizeBody({ body: result, schema, partial: true, ignoreNull: true })
|
|
21
|
-
if (record._rel) result._rel = record._rel
|
|
22
|
-
return result
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function pickRecord ({ record, fields, schema = {}, hidden = [], forceNoHidden } = {}) {
|
|
26
|
-
const { isArray, pick, clone, isEmpty, omit } = this.app.bajo.lib._
|
|
27
|
-
if (isEmpty(record)) return record
|
|
28
|
-
if (hidden.length > 0) record = omit(record, hidden)
|
|
29
|
-
if (!isArray(fields)) return await transform.call(this, { record, schema, hidden, forceNoHidden })
|
|
30
|
-
const fl = clone(fields)
|
|
31
|
-
if (!fl.includes('id')) fl.unshift('id')
|
|
32
|
-
if (record._rel) fl.push('_rel')
|
|
33
|
-
return pick(await transform.call(this, { record, schema, hidden, forceNoHidden }), fl)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export default pickRecord
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
function buildPageSkipLimit (filter) {
|
|
2
|
-
let limit = parseInt(filter.limit) || this.config.default.filter.limit
|
|
3
|
-
if (limit === -1) limit = this.config.default.filter.maxLimit
|
|
4
|
-
if (limit > this.config.default.filter.maxLimit) limit = this.config.default.filter.maxLimit
|
|
5
|
-
if (limit < 1) limit = 1
|
|
6
|
-
let page = parseInt(filter.page) || 1
|
|
7
|
-
if (page < 1) page = 1
|
|
8
|
-
let skip = (page - 1) * limit
|
|
9
|
-
if (filter.skip) {
|
|
10
|
-
skip = parseInt(filter.skip) || skip
|
|
11
|
-
page = undefined
|
|
12
|
-
}
|
|
13
|
-
if (skip < 0) skip = 0
|
|
14
|
-
return { page, skip, limit }
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function buildSort (input, schema, allowSortUnindexed) {
|
|
18
|
-
const { isEmpty, map, each, isPlainObject, isString, trim, keys } = this.app.bajo.lib._
|
|
19
|
-
let sort
|
|
20
|
-
if (schema && isEmpty(input)) {
|
|
21
|
-
const columns = map(schema.properties, 'name')
|
|
22
|
-
each(this.config.default.filter.sort, s => {
|
|
23
|
-
const [col] = s.split(':')
|
|
24
|
-
if (columns.includes(col)) {
|
|
25
|
-
input = s
|
|
26
|
-
return false
|
|
27
|
-
}
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
if (!isEmpty(input)) {
|
|
31
|
-
if (isPlainObject(input)) sort = input
|
|
32
|
-
else if (isString(input)) {
|
|
33
|
-
const item = {}
|
|
34
|
-
each(input.split('+'), text => {
|
|
35
|
-
let [col, dir] = map(trim(text).split(':'), i => trim(i))
|
|
36
|
-
dir = (dir ?? '').toUpperCase()
|
|
37
|
-
dir = dir === 'DESC' ? -1 : parseInt(dir) || 1
|
|
38
|
-
item[col] = dir / Math.abs(dir)
|
|
39
|
-
})
|
|
40
|
-
sort = item
|
|
41
|
-
}
|
|
42
|
-
if (schema) {
|
|
43
|
-
const items = keys(sort)
|
|
44
|
-
each(items, i => {
|
|
45
|
-
if (!schema.sortables.includes(i) && !allowSortUnindexed) throw this.error('sortOnUnindexedField%s%s', i, schema.name)
|
|
46
|
-
// if (schema.fullText.fields.includes(i)) throw this.error('Can\'t sort on full-text index: \'%s@%s\'', i, schema.name)
|
|
47
|
-
})
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return sort
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async function prepPagination (filter = {}, schema, options = {}) {
|
|
54
|
-
const { page, skip, limit } = buildPageSkipLimit.call(this, filter)
|
|
55
|
-
let sortInput = filter.sort
|
|
56
|
-
try {
|
|
57
|
-
sortInput = JSON.parse(sortInput)
|
|
58
|
-
} catch (err) {}
|
|
59
|
-
const sort = buildSort.call(this, sortInput, schema, options.allowSortUnindexed)
|
|
60
|
-
return { limit, page, skip, sort }
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export default prepPagination
|
package/bajo/method/prop-type.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
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
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
function validationErrorMessage (err) {
|
|
2
|
-
let text = err.message
|
|
3
|
-
if (err.details) {
|
|
4
|
-
text += ' -> '
|
|
5
|
-
text += this.app.bajo.join(err.details.map((d, idx) => {
|
|
6
|
-
return `${d.field}@${err.model}: ${d.error} (${d.value})`
|
|
7
|
-
}))
|
|
8
|
-
}
|
|
9
|
-
return text
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export default validationErrorMessage
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|