dobo 2.21.1 → 2.22.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/extend/bajo/intl/en-US.json +3 -1
- package/extend/bajo/intl/id.json +3 -1
- package/extend/dobo/feature/image.js +32 -0
- package/extend/waibuMpa/route/attachment/@model/@id/@field/@file.js +29 -7
- package/index.js +4 -1
- package/lib/collect-models.js +1 -1
- package/lib/factory/model/create-attachment.js +5 -0
- package/lib/factory/model/create-record.js +1 -1
- package/lib/factory/model/remove-attachment.js +12 -3
- package/lib/factory/model/remove-record.js +1 -1
- package/lib/factory/model/update-record.js +1 -1
- package/lib/factory/model/upsert-record.js +1 -1
- package/package.json +1 -1
- package/wiki/CHANGES.md +15 -0
package/extend/bajo/intl/id.json
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
async function handler (val, rec, opts) {
|
|
4
|
+
const atts = await this.listAttachment({ id: rec.id })
|
|
5
|
+
if (atts.length === 0) return
|
|
6
|
+
let items = atts.filter(att => att.mimeType.startsWith('image/'))
|
|
7
|
+
if (opts.asLink && this.app.waibu) items = items.map(f => `<a href="${f.url}">${opts.baseName ? path.basename(f.file) : f.file}</a>`)
|
|
8
|
+
else if (opts.baseName) items = items.map(f => path.basename(f.file))
|
|
9
|
+
if (opts.single) return items[0]
|
|
10
|
+
return opts.returnAsArray ? items : items.join(', ')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function image (opts = {}) {
|
|
14
|
+
opts.field = opts.field ?? 'image'
|
|
15
|
+
opts.baseName = opts.baseName ?? true
|
|
16
|
+
opts.single = opts.single ?? true
|
|
17
|
+
return {
|
|
18
|
+
properties: {
|
|
19
|
+
name: opts.field,
|
|
20
|
+
type: 'string',
|
|
21
|
+
virtual: true,
|
|
22
|
+
getValue: async function (val, rec) {
|
|
23
|
+
return await handler.call(this, val, rec, { ...opts, asLink: false })
|
|
24
|
+
},
|
|
25
|
+
format: async function (val, rec) {
|
|
26
|
+
return await handler.call(this, val, rec, { ...opts, asLink: true })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default image
|
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
|
|
3
|
+
async function handleNotFound (req, reply) {
|
|
4
|
+
const { isEmpty, isString } = this.app.lib._
|
|
5
|
+
const { routePath } = this.app.waibu
|
|
6
|
+
if (!req.query.notfound) throw this.error('_notFound', { noContent: true })
|
|
7
|
+
const ext = path.extname(req.params.file)
|
|
8
|
+
const replacer = isString(req.query.notfound) ? req.query.notfound : `waibuStatic.asset:/not-found${isEmpty(ext) ? '.png' : ext}`
|
|
9
|
+
return reply.redirectTo(routePath(replacer))
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
async function attachment (req, reply) {
|
|
4
|
-
const {
|
|
13
|
+
const { isEmpty, find, last } = this.app.lib._
|
|
5
14
|
const { pascalCase } = this.app.lib.aneka
|
|
6
|
-
const {
|
|
15
|
+
const { createThumbnail } = this.app.bajoExtra ?? {}
|
|
7
16
|
const { fs } = this.app.lib
|
|
8
17
|
const mdl = this.app.dobo.getModel(req.params.model)
|
|
9
18
|
const type = req.query.type
|
|
@@ -20,11 +29,24 @@ async function attachment (req, reply) {
|
|
|
20
29
|
file === decodeURI(req.params.file)
|
|
21
30
|
})
|
|
22
31
|
}
|
|
23
|
-
if (!item)
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
32
|
+
if (!item) return await handleNotFound.call(this, req, reply)
|
|
33
|
+
if (req.query.thumbnail) {
|
|
34
|
+
const dir = path.dirname(item.file)
|
|
35
|
+
const ext = path.extname(item.file)
|
|
36
|
+
const base = path.basename(item.file, ext)
|
|
37
|
+
const dest = `${dir}/_tn/${base}-${req.query.thumbnail}.png`
|
|
38
|
+
if (createThumbnail && !fs.existsSync(dest)) {
|
|
39
|
+
const opts = {
|
|
40
|
+
dir: `${dir}/_tn`,
|
|
41
|
+
size: req.query.thumbnail,
|
|
42
|
+
silent: true,
|
|
43
|
+
format: '.png'
|
|
44
|
+
}
|
|
45
|
+
await createThumbnail(item.file, opts)
|
|
46
|
+
}
|
|
47
|
+
if (!fs.existsSync(dest)) await handleNotFound.call(this, req, reply)
|
|
48
|
+
item.mimeType = 'image/png'
|
|
49
|
+
item.file = dest
|
|
28
50
|
}
|
|
29
51
|
if (!isEmpty(item.mimeType)) reply.header('Content-Type', item.mimeType)
|
|
30
52
|
const stream = fs.createReadStream(item.file)
|
package/index.js
CHANGED
package/lib/collect-models.js
CHANGED
|
@@ -33,7 +33,7 @@ async function sanitizeProp (model, prop, indexes) {
|
|
|
33
33
|
if (prop.virtual) {
|
|
34
34
|
const keys = Object.keys(propType)
|
|
35
35
|
if (!keys.includes(prop.type)) this.fatal('unknownPropType%s%s', `${prop.name}:${prop.type}`, model.name)
|
|
36
|
-
for (const key of ['required', 'rules', 'index', 'validator', 'ref', 'rulesMsg', 'immutable'
|
|
36
|
+
for (const key of ['required', 'rules', 'index', 'validator', 'ref', 'rulesMsg', 'immutable']) {
|
|
37
37
|
delete prop[key]
|
|
38
38
|
}
|
|
39
39
|
model.properties.push(prop)
|
|
@@ -2,6 +2,8 @@ import { mergeAttachmentInfo, getAttachmentPath } from './_util.js'
|
|
|
2
2
|
const action = 'createAttachment'
|
|
3
3
|
|
|
4
4
|
async function createAttachment (...args) {
|
|
5
|
+
const { createThumbnail } = this.app.bajoExtra
|
|
6
|
+
const { thumbSizes: size } = this.app.dobo.config.default.attachment
|
|
5
7
|
if (!this.attachment) return
|
|
6
8
|
if (args.length === 0) return this.action(action, ...args)
|
|
7
9
|
const [id, opts = {}] = args
|
|
@@ -18,6 +20,9 @@ async function createAttachment (...args) {
|
|
|
18
20
|
const dest = `${dir}/${file}`.replaceAll('//', '/')
|
|
19
21
|
await fs.ensureDir(dir)
|
|
20
22
|
await fs.copy(source, dest)
|
|
23
|
+
try {
|
|
24
|
+
if (createThumbnail) await createThumbnail(dest, { dir: `${dir}/_tn`, size, format: ['.png'] })
|
|
25
|
+
} catch (err) {}
|
|
21
26
|
const rec = {
|
|
22
27
|
field: field === '' ? undefined : field,
|
|
23
28
|
dir,
|
|
@@ -20,13 +20,13 @@ async function createRecord (...args) {
|
|
|
20
20
|
await execDynHook.call(this, 'beforeCreateRecord', input, options)
|
|
21
21
|
if (!noValidation) await execValidation.call(this, input, options)
|
|
22
22
|
let result = options.record ?? (await this.driver._createRecord(this, input, options)) ?? {}
|
|
23
|
+
await handleReq.call(this, result.data.id, 'created', options)
|
|
23
24
|
if (noResult) return
|
|
24
25
|
result = result ?? {}
|
|
25
26
|
const { warnings } = getDefaultValues(options)
|
|
26
27
|
if (!warnings) delete result.warnings
|
|
27
28
|
if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
|
|
28
29
|
if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
|
|
29
|
-
await handleReq.call(this, result.data.id, 'created', options)
|
|
30
30
|
await execDynHook.call(this, 'afterCreateRecord', input, result, options)
|
|
31
31
|
await execModelHook.call(this, 'afterCreateRecord', input, result, options)
|
|
32
32
|
await execHook.call(this, 'afterCreateRecord', input, result, options)
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import { getAttachmentPath } from './_util.js'
|
|
2
|
+
import path from 'path'
|
|
2
3
|
const action = 'removeAttachment'
|
|
3
4
|
|
|
4
5
|
async function removeAttachment (...args) {
|
|
5
6
|
if (!this.attachment) return
|
|
6
7
|
if (args.length === 0) return this.action(action, ...args)
|
|
7
8
|
const [id, field, file, opts = {}] = args
|
|
8
|
-
const { fs } = this.app.lib
|
|
9
|
-
const
|
|
9
|
+
const { fs, fastGlob } = this.app.lib
|
|
10
|
+
const fullPath = await getAttachmentPath.call(this, id, field, file)
|
|
10
11
|
const { req } = opts
|
|
11
|
-
await fs.remove(
|
|
12
|
+
await fs.remove(fullPath)
|
|
13
|
+
const dir = path.dirname(fullPath)
|
|
14
|
+
const ext = path.extname(fullPath)
|
|
15
|
+
const base = path.basename(fullPath, ext)
|
|
16
|
+
const pattern = `${dir}/_tn/${base}-*.*`
|
|
17
|
+
const files = await fastGlob(pattern)
|
|
18
|
+
for (const f of files) {
|
|
19
|
+
await fs.remove(f)
|
|
20
|
+
}
|
|
12
21
|
if (!opts.noFlash && req && req.flash) req.flash('notify', req.t('attachmentRemoved'))
|
|
13
22
|
}
|
|
14
23
|
|
|
@@ -45,13 +45,13 @@ async function removeRecord (...args) {
|
|
|
45
45
|
await execModelHook.call(this, 'beforeRemoveRecord', id, options)
|
|
46
46
|
await execDynHook.call(this, 'beforeRemoveRecord', id, options)
|
|
47
47
|
const result = options.record ?? (await this.driver._removeRecord(this, id, options)) ?? {}
|
|
48
|
+
await handleReq.call(this, result.oldData.id, 'removed', options)
|
|
48
49
|
await clearCache.call(this, id)
|
|
49
50
|
if (noResult) return
|
|
50
51
|
const { warnings } = getDefaultValues(options)
|
|
51
52
|
if (!warnings) delete result.warnings
|
|
52
53
|
if (!noResultSanitizer) result.oldData = await this.sanitizeRecord(result.oldData, options)
|
|
53
54
|
if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
|
|
54
|
-
await handleReq.call(this, result.oldData.id, 'removed', options)
|
|
55
55
|
await execDynHook.call(this, 'afterRemoveRecord', id, result, options)
|
|
56
56
|
await execModelHook.call(this, 'afterRemoveRecord', id, result, options)
|
|
57
57
|
await execHook.call(this, 'afterRemoveRecord', id, result, options)
|
|
@@ -67,6 +67,7 @@ async function updateRecord (...args) {
|
|
|
67
67
|
await execDynHook.call(this, 'beforeUpdateRecord', id, input, options)
|
|
68
68
|
if (!noValidation) await execValidation.call(this, input, options)
|
|
69
69
|
const result = options.record ?? (await this.driver._updateRecord(this, id, input, options)) ?? {}
|
|
70
|
+
await handleReq.call(this, result.data.id, 'updated', options)
|
|
70
71
|
await clearCache.call(this, id)
|
|
71
72
|
if (noResult) return
|
|
72
73
|
const { warnings } = getDefaultValues(options)
|
|
@@ -76,7 +77,6 @@ async function updateRecord (...args) {
|
|
|
76
77
|
result.data = await this.sanitizeRecord(result.data, options)
|
|
77
78
|
result.oldData = await this.sanitizeRecord(result.oldData, options)
|
|
78
79
|
}
|
|
79
|
-
await handleReq.call(this, result.data.id, 'updated', options)
|
|
80
80
|
await execDynHook.call(this, 'afterUpdateRecord', id, input, result, options)
|
|
81
81
|
await execModelHook.call(this, 'afterUpdateRecord', id, input, result, options)
|
|
82
82
|
await execHook.call(this, 'afterUpdateRecord', id, input, result, options)
|
|
@@ -16,13 +16,13 @@ async function native (body = {}, opts = {}) {
|
|
|
16
16
|
await execModelHook.call(this, 'beforeUpsertRecord', input, options)
|
|
17
17
|
await execDynHook.call(this, 'beforeUpsertRecord', input, options)
|
|
18
18
|
const result = options.record ?? (await this.driver._upsertRecord(this, input, options)) ?? {}
|
|
19
|
+
await handleReq.call(this, result.data.id, 'upserted', options)
|
|
19
20
|
await clearCache.call(this, body.id)
|
|
20
21
|
if (noResult) return
|
|
21
22
|
const { warnings } = getDefaultValues(options)
|
|
22
23
|
if (!warnings) delete result.warnings
|
|
23
24
|
if (isSet(options.refs)) await getSingleRef.call(this, result.data, options)
|
|
24
25
|
if (!noResultSanitizer) result.data = await this.sanitizeRecord(result.data, options)
|
|
25
|
-
await handleReq.call(this, result.data.id, 'upserted', options)
|
|
26
26
|
await execDynHook.call(this, 'afterUpsertRecord', input, result, options)
|
|
27
27
|
await execModelHook.call(this, 'afterUpsertRecord', input, result, options)
|
|
28
28
|
await execHook.call(this, 'afterUpsertRecord', input, result, options)
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-05-03
|
|
4
|
+
|
|
5
|
+
- [2.22.1] Bug fix in ```dobo:image``` feature
|
|
6
|
+
|
|
7
|
+
## 2026-05-02
|
|
8
|
+
|
|
9
|
+
- [2.22.0] Add auto thumbnail creation when image attachment is uploaded
|
|
10
|
+
- [2.22.0] Add feature to get the thumbnail instead of attachment file in attachment route
|
|
11
|
+
- [2.22.0] Add ```dobo:image``` feature
|
|
12
|
+
- [2.22.0] Bug fix in ```model.createRecord()```
|
|
13
|
+
- [2.22.0] Bug fix in ```model.updateRecord()```
|
|
14
|
+
- [2.22.0] Bug fix in ```model.upsertRecord()```
|
|
15
|
+
- [2.22.0] Bug fix in ```model.removeRecord()```
|
|
16
|
+
- [2.22.0] Remove attachment now also remove corresponding thumbnails
|
|
17
|
+
|
|
3
18
|
## 2026-04-28
|
|
4
19
|
|
|
5
20
|
- [2.21.1] Bug fix in ```collect-models.js```
|