dobo 1.1.14 → 1.1.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dobo",
3
- "version": "1.1.14",
3
+ "version": "1.1.16",
4
4
  "description": "Database ORM/ODM for Bajo Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/plugin/factory.js CHANGED
@@ -5,6 +5,7 @@ import collectSchemas from '../lib/collect-schemas.js'
5
5
  import memDbStart from '../lib/mem-db/start.js'
6
6
  import memDbInstantiate from '../lib/mem-db/instantiate.js'
7
7
  import nql from '@tryghost/nql'
8
+ import path from 'path'
8
9
 
9
10
  async function factory (pkgName) {
10
11
  const me = this
@@ -360,6 +361,34 @@ async function factory (pkgName) {
360
361
  if (fields.length === 0) return all
361
362
  return map(all, item => pick(item, fields))
362
363
  }
364
+
365
+ listAttachments = async ({ model, id = '*', field = '*', file = '*' } = {}, { uriEncoded = true } = {}) => {
366
+ const { map, kebabCase } = this.lib._
367
+ const { importPkg, getPluginDataDir, pascalCase } = this.app.bajo
368
+ const mime = await importPkg('waibu:mime')
369
+ const { fastGlob } = this.lib
370
+ const root = `${getPluginDataDir('dobo')}/attachment`
371
+ model = pascalCase(model)
372
+ let pattern = `${root}/${model}/${id}/${field}/${file}`
373
+ if (uriEncoded) pattern = pattern.split('/').map(p => decodeURI(p)).join('/')
374
+ return map(await fastGlob(pattern), f => {
375
+ const mimeType = mime.getType(path.extname(f)) ?? ''
376
+ const fullPath = f.replace(root, '')
377
+ const row = {
378
+ file: f,
379
+ fileName: path.basename(fullPath),
380
+ fullPath,
381
+ mimeType,
382
+ params: { model, id, field, file }
383
+ }
384
+ if (this.app.waibuMpa) {
385
+ const { routePath } = this.app.waibu
386
+ const [, _model, _id, _field, _file] = fullPath.split('/')
387
+ row.url = routePath(`dobo:/attachment/${kebabCase(_model)}/${_id}/${_field}/${_file}`)
388
+ }
389
+ return row
390
+ })
391
+ }
363
392
  }
364
393
  }
365
394
 
@@ -20,8 +20,9 @@ async function copyUploaded (name, id, options = {}) {
20
20
  if (parts.length === 0) continue
21
21
  field = setField ?? field
22
22
  const file = setFile ?? parts.join('@')
23
- const opts = { source: f, field, file, mimeType, stats, req }
23
+ const opts = { source: f, field, file, mimeType, stats, req, silent: true }
24
24
  const rec = await this.attachmentCreate(name, id, opts)
25
+ if (!rec) continue
25
26
  delete rec.dir
26
27
  result.push(rec)
27
28
  if (setField || setFile) break
@@ -2,9 +2,11 @@ import mergeAttachmentInfo from '../../../lib/merge-attachment-info.js'
2
2
 
3
3
  async function create (name, id, options = {}) {
4
4
  const { fs } = this.lib
5
+ const { isEmpty } = this.lib._
5
6
  name = this.attachmentPreCheck(name)
6
7
  if (!name) return
7
- const { source, field, file } = options
8
+ const { source, field = 'file', file } = options
9
+ if (isEmpty(file)) return
8
10
  if (!source) throw this.error('isMissing%s', this.print.write('field.source'))
9
11
  const baseDir = await this.attachmentGetPath(name, id, field, file, { dirOnly: true })
10
12
  const { fullPath, stats, mimeType, req } = options
@@ -20,7 +22,7 @@ async function create (name, id, options = {}) {
20
22
  file
21
23
  }
22
24
  await mergeAttachmentInfo.call(this, rec, dest, { mimeType, fullPath, stats })
23
- if (req && req.flash) req.flash('notify', req.t('attachmentUploaded'))
25
+ if (!options.silent && req && req.flash) req.flash('notify', req.t('attachmentUploaded'))
24
26
  return rec
25
27
  }
26
28
 
@@ -56,13 +56,13 @@ async function create (name, input, opts = {}) {
56
56
  if (options.req.file) await handleAttachmentUpload.call(this, { name: schema.name, id: body.id, body, options, action: 'create' })
57
57
  if (options.req.flash && !options.noFlash) options.req.flash('notify', options.req.t('recordCreated'))
58
58
  }
59
+ if (clearModel) await clearModel({ model: name, body: nbody, options, record })
60
+ if (noResult) return
61
+ record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
59
62
  if (!noHook) {
60
63
  await runHook(`${this.name}.${camelCase(name)}:afterRecordCreate`, nbody, options, record)
61
64
  await runHook(`${this.name}:afterRecordCreate`, name, nbody, options, record)
62
65
  }
63
- if (clearModel) await clearModel({ model: name, body: nbody, options, record })
64
- if (noResult) return
65
- record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
66
66
  if (!noFeatureHook) await execFeatureHook.call(this, 'afterCreate', { schema, body: nbody, options, record })
67
67
  return dataOnly ? record.data : record
68
68
  }
@@ -39,11 +39,11 @@ async function findOne (name, filter = {}, opts = {}) {
39
39
  record.data = record.data[0]
40
40
 
41
41
  if (isSet(options.rels)) await singleRelRows.call(this, { schema, record: record.data, options })
42
+ record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
42
43
  if (!noHook) {
43
44
  await runHook(`${this.name}.${camelCase(name)}:afterRecordFindOne`, filter, options, record)
44
45
  await runHook(`${this.name}:afterRecordFindOne`, name, filter, options, record)
45
46
  }
46
- record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
47
47
  if (set && !noCache) await set({ model: name, filter, options, record })
48
48
  if (!noFeatureHook) await execFeatureHook.call(this, 'afterFindOne', { schema, filter, options, record })
49
49
  return dataOnly ? record.data : record
@@ -36,13 +36,13 @@ async function find (name, filter = {}, opts = {}) {
36
36
  const records = options.records ?? (await handler.call(this.app[driver.ns], { schema, filter, options }))
37
37
  delete options.records
38
38
  if (isSet(options.rels)) await multiRelRows.call(this, { schema, records: records.data, options })
39
+ for (const idx in records.data) {
40
+ records.data[idx] = await this.pickRecord({ record: records.data[idx], fields, schema, hidden, forceNoHidden })
41
+ }
39
42
  if (!noHook) {
40
43
  await runHook(`${this.name}.${camelCase(name)}:afterRecordFind`, filter, options, records)
41
44
  await runHook(`${this.name}:afterRecordFind`, name, filter, options, records)
42
45
  }
43
- for (const idx in records.data) {
44
- records.data[idx] = await this.pickRecord({ record: records.data[idx], fields, schema, hidden, forceNoHidden })
45
- }
46
46
  if (set && !noCache) await set({ model: name, filter, options, records })
47
47
  if (!noFeatureHook) await execFeatureHook.call(this, 'afterFind', { schema, filter, options, records })
48
48
  return dataOnly ? records.data : records
@@ -33,12 +33,11 @@ async function get (name, id, opts = {}) {
33
33
  const record = options.record ?? (await handler.call(this.app[driver.ns], { schema, id, options }))
34
34
  delete options.record
35
35
  if (isSet(options.rels)) await singleRelRows.call(this, { schema, record: record.data, options })
36
+ record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
36
37
  if (!noHook) {
37
38
  await runHook(`${this.name}.${camelCase(name)}:afterRecordGet`, id, options, record)
38
39
  await runHook(`${this.name}:afterRecordGet`, name, id, options, record)
39
40
  }
40
- record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
41
-
42
41
  if (set && !noCache) await set({ model: name, id, options, record })
43
42
  if (!noFeatureHook) await execFeatureHook.call(this, 'afterGet', { schema, id, options, record })
44
43
  return dataOnly ? record.data : record
@@ -27,13 +27,13 @@ async function remove (name, id, opts = {}) {
27
27
  if (options.req.file) await handleAttachmentUpload.call(this, { name: schema.name, id, options, action: 'remove' })
28
28
  if (options.req.flash && !options.noFlash) options.req.flash('notify', options.req.t('recordRemoved'))
29
29
  }
30
+ if (clearModel) await clearModel({ model: name, id, options, record })
31
+ if (noResult) return
32
+ record.oldData = options.record ? options.record.oldData : (await this.pickRecord({ record: record.oldData, fields, schema, hidden, forceNoHidden }))
30
33
  if (!noHook) {
31
34
  await runHook(`${this.name}.${camelCase(name)}:afterRecordRemove`, id, options, record)
32
35
  await runHook(`${this.name}:afterRecordRemove`, name, id, options, record)
33
36
  }
34
- if (clearModel) await clearModel({ model: name, id, options, record })
35
- if (noResult) return
36
- record.oldData = options.record ? options.record.oldData : (await this.pickRecord({ record: record.oldData, fields, schema, hidden, forceNoHidden }))
37
37
  if (!noFeatureHook) await execFeatureHook.call(this, 'afterRemove', { schema, id, options, record })
38
38
  return dataOnly ? record.oldData : record
39
39
  }
@@ -47,14 +47,14 @@ async function update (name, id, input, opts = {}) {
47
47
  if (options.req.file) await handleAttachmentUpload.call(this, { name: schema.name, id, body, options, action: 'update' })
48
48
  if (options.req.flash && !options.noFlash) options.req.flash('notify', options.req.t('recordUpdated'))
49
49
  }
50
- if (!noHook) {
51
- await runHook(`${this.name}.${camelCase(name)}:afterRecordUpdate`, id, nbody, options, record)
52
- await runHook(`${this.name}:afterRecordUpdate`, name, id, nbody, options, record)
53
- }
54
50
  if (clearModel) await clearModel({ model: name, id, body: nbody, options, record })
55
51
  if (noResult) return
56
52
  record.oldData = await this.pickRecord({ record: record.oldData, fields, schema, hidden, forceNoHidden })
57
53
  record.data = await this.pickRecord({ record: record.data, fields, schema, hidden, forceNoHidden })
54
+ if (!noHook) {
55
+ await runHook(`${this.name}.${camelCase(name)}:afterRecordUpdate`, id, nbody, options, record)
56
+ await runHook(`${this.name}:afterRecordUpdate`, name, id, nbody, options, record)
57
+ }
58
58
  if (!noFeatureHook) await execFeatureHook.call(this, 'afterUpdate', { schema, body: nbody, record })
59
59
  return dataOnly ? record.data : record
60
60
  }
@@ -1,25 +1,34 @@
1
1
  import path from 'path'
2
2
 
3
3
  async function attachment (req, reply) {
4
- const { isString, isEmpty } = this.lib._
5
- const { importPkg, getPluginDataDir, pascalCase } = this.app.bajo
4
+ const { isString, isEmpty, find } = this.lib._
5
+ const { pascalCase } = this.app.bajo
6
6
  const { routePath } = this.app.waibu
7
- const mime = await importPkg('waibu:mime')
8
- const { fs, fastGlob } = this.lib
9
- let file = `${getPluginDataDir('dobo')}/attachment/${pascalCase(req.params.model)}/${req.params.id}/${req.params.field}/${req.params.file}`
10
- if (path.basename(file) === '_first') {
11
- const files = await fastGlob(`${path.dirname(file)}/*`)
12
- if (files.length > 0) file = files[0]
7
+ const { fs } = this.lib
8
+ const items = await this.listAttachments({
9
+ model: req.params.model,
10
+ id: req.params.id,
11
+ field: req.params.field,
12
+ file: '*'
13
+ })
14
+ let item = req.params.file === '_first' ? items[0] : undefined
15
+ if (!item) {
16
+ item = find(items, i => {
17
+ const [, model, id, field, file] = i.fullPath.split('/')
18
+ return model === pascalCase(req.params.model) &&
19
+ id === decodeURI(req.params.id) &&
20
+ field === decodeURI(req.params.field) &&
21
+ file === decodeURI(req.params.file)
22
+ })
13
23
  }
14
- const mimeType = mime.getType(path.extname(file)) ?? ''
15
- if (!fs.existsSync(file)) {
24
+ if (!item) {
16
25
  if (!req.query.notfound) throw this.error('_notFound', { noContent: true })
17
- const [, ext] = mimeType.split('/')
18
- const replacer = isString(req.query.notfound) ? req.query.notfound : `waibuStatic.asset:/not-found.${ext ?? 'png'}`
26
+ const ext = path.extname(req.params.file)
27
+ const replacer = isString(req.query.notfound) ? req.query.notfound : `waibuStatic.asset:/not-found${isEmpty(ext) ? '.png' : ext}`
19
28
  return reply.redirectTo(routePath(replacer))
20
29
  }
21
- if (!isEmpty(mimeType)) reply.header('Content-Type', mimeType)
22
- const stream = fs.createReadStream(file)
30
+ if (!isEmpty(item.mimeType)) reply.header('Content-Type', item.mimeType)
31
+ const stream = fs.createReadStream(item.file)
23
32
  reply.send(stream)
24
33
  return reply
25
34
  }