sumba 1.2.7 → 1.2.10
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/bajo/hook/waibu-mpa@pre-parsing.js +3 -1
- package/bajo/intl/en-US.json +3 -1
- package/bajo/intl/id.json +3 -1
- package/bajoTemplate/partial/app-launcher.html +1 -1
- package/dobo/schema/download.json +22 -19
- package/index.js +53 -26
- package/package.json +1 -1
- package/waibuBootstrap/theme/component/factory/nav-dropdown-user.js +61 -0
- package/waibuDb/schema/download.js +27 -0
- package/waibuMpa/route/help/trouble-tickets/details/@id.js +3 -2
- package/waibuMpa/route/your-stuff/download/@action.js +12 -0
- package/waibuMpa/route/your-stuff/download/get.js +12 -0
|
@@ -6,12 +6,13 @@ import checkTeam from '../../lib/check-team.js'
|
|
|
6
6
|
const preParsing = {
|
|
7
7
|
level: 10,
|
|
8
8
|
handler: async function (req, reply) {
|
|
9
|
-
const { routePath } = this.app.waibu
|
|
9
|
+
// const { routePath } = this.app.waibu
|
|
10
10
|
|
|
11
11
|
await checkTheme.call(this, req, reply)
|
|
12
12
|
await checkIconset.call(this, req, reply)
|
|
13
13
|
await checkUserId.call(this, req, reply, 'waibuMpa')
|
|
14
14
|
await checkTeam.call(this, req, reply, 'waibuMpa')
|
|
15
|
+
/*
|
|
15
16
|
req.menu = req.menu ?? {}
|
|
16
17
|
if (req.user) {
|
|
17
18
|
req.menu.user = [
|
|
@@ -28,6 +29,7 @@ const preParsing = {
|
|
|
28
29
|
{ value: routePath('sumba:/user/forgot-password', req), text: req.t('forgotPassword') }
|
|
29
30
|
]
|
|
30
31
|
}
|
|
32
|
+
*/
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
|
package/bajo/intl/en-US.json
CHANGED
|
@@ -105,6 +105,7 @@
|
|
|
105
105
|
"resetPassword": "Reset Password",
|
|
106
106
|
"contactFormSubmitted": "Contact form successfully submitted",
|
|
107
107
|
"currentApiKey": "Current API Key",
|
|
108
|
+
"downloadList": "Download List",
|
|
108
109
|
"field": {
|
|
109
110
|
"currentPassword": "Current Password",
|
|
110
111
|
"newPassword": "New Password",
|
|
@@ -122,7 +123,8 @@
|
|
|
122
123
|
"socX": "X (Twitter)",
|
|
123
124
|
"socInstagram": "Instagram",
|
|
124
125
|
"socFacebook": "Facebook",
|
|
125
|
-
"socLinkedIn": "LinkedIn"
|
|
126
|
+
"socLinkedIn": "LinkedIn",
|
|
127
|
+
"size": "Size"
|
|
126
128
|
},
|
|
127
129
|
"validation": {
|
|
128
130
|
"password": {
|
package/bajo/intl/id.json
CHANGED
|
@@ -106,6 +106,7 @@
|
|
|
106
106
|
"resetPassword": "Reset Kata Sandi",
|
|
107
107
|
"contactFormSubmitted": "Form kontak sukses dikirim",
|
|
108
108
|
"currentApiKey": "Kunci API Saat Ini",
|
|
109
|
+
"downloadList": "Daftar Unduh",
|
|
109
110
|
"field": {
|
|
110
111
|
"currentPassword": "Kata Sandi Saat Ini",
|
|
111
112
|
"newPassword": "Kata Sandi Baru",
|
|
@@ -123,7 +124,8 @@
|
|
|
123
124
|
"socX": "X (Twitter)",
|
|
124
125
|
"socInstagram": "Instagram",
|
|
125
126
|
"socFacebook": "Facebook",
|
|
126
|
-
"socLinkedIn": "LinkedIn"
|
|
127
|
+
"socLinkedIn": "LinkedIn",
|
|
128
|
+
"size": "Besaran"
|
|
127
129
|
},
|
|
128
130
|
"validation": {
|
|
129
131
|
"password": {
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
{
|
|
2
|
-
"properties": [
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
2
|
+
"properties": [
|
|
3
|
+
"description::50:true",
|
|
4
|
+
"file:text:::true",
|
|
5
|
+
"type::20:true",
|
|
6
|
+
"jobQueue:object",
|
|
7
|
+
{
|
|
8
|
+
"name": "size",
|
|
9
|
+
"type": "integer",
|
|
10
|
+
"default": 0,
|
|
11
|
+
"index": true
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"feature": {
|
|
15
|
+
"sumba.siteId": true,
|
|
16
|
+
"sumba.userId": true,
|
|
17
|
+
"createdAt": true,
|
|
18
|
+
"updatedAt": true,
|
|
19
|
+
"sumba.status": {
|
|
20
|
+
"default": "INQUEUE",
|
|
21
|
+
"values": ["INQUEUE", "PROCESSING", "COMPLETE", "FAIL"]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
21
24
|
}
|
package/index.js
CHANGED
|
@@ -1,25 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
const { defaultsDeep } = this.lib.aneka
|
|
3
|
-
const { get } = this.lib._
|
|
4
|
-
|
|
5
|
-
const setting = defaultsDeep(get(this.config, `auth.${source}.${type}`, {}), get(this.config, `auth.common.${type}`, {}))
|
|
6
|
-
if (type === 'basic') setting.type = 'Basic'
|
|
7
|
-
return setting
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
async function getToken (type, req, source) {
|
|
11
|
-
const { isEmpty } = this.lib._
|
|
12
|
-
|
|
13
|
-
const setting = await getSetting.call(this, type, source)
|
|
14
|
-
let token = req.headers[setting.headerKey.toLowerCase()]
|
|
15
|
-
if (!['basic'].includes(type) && isEmpty(token)) token = req.query[setting.qsKey]
|
|
16
|
-
if (isEmpty(token)) {
|
|
17
|
-
const parts = (req.headers.authorization || '').split(' ')
|
|
18
|
-
if (parts[0] === setting.type) token = parts[1]
|
|
19
|
-
}
|
|
20
|
-
if (isEmpty(token)) return false
|
|
21
|
-
return token
|
|
22
|
-
}
|
|
1
|
+
import path from 'path'
|
|
23
2
|
|
|
24
3
|
async function factory (pkgName) {
|
|
25
4
|
const me = this
|
|
@@ -60,6 +39,7 @@ async function factory (pkgName) {
|
|
|
60
39
|
// authenticated only
|
|
61
40
|
{ title: 'yourProfile', href: 'sumba:/your-stuff/profile', visible: 'auth' },
|
|
62
41
|
{ title: 'changePassword', href: 'sumba:/your-stuff/change-password', visible: 'auth' },
|
|
42
|
+
{ title: 'downloadList', href: 'sumba:/your-stuff/download/list', visible: 'auth' },
|
|
63
43
|
{ title: '-', visible: 'auth' },
|
|
64
44
|
{ title: 'signout', href: 'sumba:/signout', visible: 'auth' }
|
|
65
45
|
]
|
|
@@ -138,12 +118,38 @@ async function factory (pkgName) {
|
|
|
138
118
|
}
|
|
139
119
|
|
|
140
120
|
init = async () => {
|
|
121
|
+
const { getPluginDataDir } = this.app.bajo
|
|
122
|
+
this.downloadDir = `${getPluginDataDir(this.name)}/download`
|
|
123
|
+
this.lib.fs.ensureDirSync(this.downloadDir)
|
|
141
124
|
for (const type of ['secure', 'anonymous', 'team']) {
|
|
142
125
|
this[`${type}Routes`] = this[`${type}Routes`] ?? []
|
|
143
126
|
this[`${type}NegRoutes`] = this[`${type}NegRoutes`] ?? []
|
|
144
127
|
}
|
|
145
128
|
}
|
|
146
129
|
|
|
130
|
+
_getSetting = async (type, source) => {
|
|
131
|
+
const { defaultsDeep } = this.lib.aneka
|
|
132
|
+
const { get } = this.lib._
|
|
133
|
+
|
|
134
|
+
const setting = defaultsDeep(get(this.config, `auth.${source}.${type}`, {}), get(this.config, `auth.common.${type}`, {}))
|
|
135
|
+
if (type === 'basic') setting.type = 'Basic'
|
|
136
|
+
return setting
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
_getToken = async (type, req, source) => {
|
|
140
|
+
const { isEmpty } = this.lib._
|
|
141
|
+
|
|
142
|
+
const setting = await this._getSetting(type, source)
|
|
143
|
+
let token = req.headers[setting.headerKey.toLowerCase()]
|
|
144
|
+
if (!['basic'].includes(type) && isEmpty(token)) token = req.query[setting.qsKey]
|
|
145
|
+
if (isEmpty(token)) {
|
|
146
|
+
const parts = (req.headers.authorization || '').split(' ')
|
|
147
|
+
if (parts[0] === setting.type) token = parts[1]
|
|
148
|
+
}
|
|
149
|
+
if (isEmpty(token)) return false
|
|
150
|
+
return token
|
|
151
|
+
}
|
|
152
|
+
|
|
147
153
|
adminMenu = async (locals, req) => {
|
|
148
154
|
if (!this.app.waibuAdmin) return
|
|
149
155
|
const { getPluginPrefix } = this.app.waibu
|
|
@@ -247,7 +253,7 @@ async function factory (pkgName) {
|
|
|
247
253
|
const { getUser } = this
|
|
248
254
|
const { recordFind } = this.app.dobo
|
|
249
255
|
|
|
250
|
-
let token = await
|
|
256
|
+
let token = await this._getToken('apiKey', req, source)
|
|
251
257
|
if (!isMd5(token)) return false
|
|
252
258
|
token = await hash(token)
|
|
253
259
|
const query = { token }
|
|
@@ -275,7 +281,7 @@ async function factory (pkgName) {
|
|
|
275
281
|
reply.code(401)
|
|
276
282
|
}
|
|
277
283
|
|
|
278
|
-
const setting = await
|
|
284
|
+
const setting = await this._getSetting('basic', source)
|
|
279
285
|
let authInfo
|
|
280
286
|
const parts = (req.headers.authorization ?? '').split(' ')
|
|
281
287
|
if (parts[0] === setting.type) authInfo = parts[1]
|
|
@@ -308,8 +314,8 @@ async function factory (pkgName) {
|
|
|
308
314
|
|
|
309
315
|
const fastJwt = await importPkg('bajoExtra:fast-jwt')
|
|
310
316
|
const { createVerifier } = fastJwt
|
|
311
|
-
const setting = await
|
|
312
|
-
const token = await
|
|
317
|
+
const setting = await this._getSetting('jwt', source)
|
|
318
|
+
const token = await this._getToken('jwt', req, source)
|
|
313
319
|
if (isEmpty(token)) return false
|
|
314
320
|
const verifier = createVerifier({
|
|
315
321
|
key: setting.secret,
|
|
@@ -461,6 +467,27 @@ async function factory (pkgName) {
|
|
|
461
467
|
if (cfg.minNumeric) passwd += generateId({ pattern: '0123456789', length: cfg.minNumeric })
|
|
462
468
|
return passwd
|
|
463
469
|
}
|
|
470
|
+
|
|
471
|
+
pushDownload = async ({ description, worker, data, source, req, file, type }) => {
|
|
472
|
+
const { getPlugin } = this.app.bajo
|
|
473
|
+
const { recordCreate } = getPlugin('waibuDb')
|
|
474
|
+
const { push } = getPlugin('bajoQueue')
|
|
475
|
+
description = description ?? file
|
|
476
|
+
const jobQueue = {
|
|
477
|
+
worker,
|
|
478
|
+
source,
|
|
479
|
+
payload: {
|
|
480
|
+
type: 'object',
|
|
481
|
+
data
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (!type) type = path.extname(file)
|
|
485
|
+
if (type[0] === '.') type = type.slice(1)
|
|
486
|
+
const body = { file, description, jobQueue, type }
|
|
487
|
+
const rec = await recordCreate({ model: 'SumbaDownload', body, req, options: { noFlash: true } })
|
|
488
|
+
jobQueue.payload.data.download = { id: rec.data.id, file }
|
|
489
|
+
await push(jobQueue)
|
|
490
|
+
}
|
|
464
491
|
}
|
|
465
492
|
}
|
|
466
493
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
async function navDropdownUser () {
|
|
2
|
+
return class NavDropdownUser extends this.baseFactory {
|
|
3
|
+
build = async () => {
|
|
4
|
+
const { has, omit, find, filter } = this.plugin.lib._
|
|
5
|
+
const { routePath } = this.plugin.app.waibu
|
|
6
|
+
const { req } = this.component
|
|
7
|
+
const icon = this.component.req.iconset ? await this.component.buildTag({ tag: 'icon', attr: { name: 'person' } }) : ''
|
|
8
|
+
let text = ''
|
|
9
|
+
if (has(this.params.attr, 'title')) {
|
|
10
|
+
if (req.user) {
|
|
11
|
+
if (this.params.attr.title === 'short') text = `${req.user.firstName} ${req.user.lastName[0]}.`
|
|
12
|
+
else if (['firstName', 'lastName', 'username'].includes(this.params.attr.title)) text = req.user[this.params.attr.title]
|
|
13
|
+
else text = `${req.user.firstName} ${req.user.lastName}`
|
|
14
|
+
} else text = req.t('guest')
|
|
15
|
+
}
|
|
16
|
+
const html = []
|
|
17
|
+
const attr = omit(this.params.attr, ['text'])
|
|
18
|
+
attr.dropdown = true
|
|
19
|
+
attr.content = `${icon} ${text}`
|
|
20
|
+
if (this.params.attr.noMenu) {
|
|
21
|
+
delete attr.dropdown
|
|
22
|
+
delete attr.dropdownMenu
|
|
23
|
+
attr.href = routePath(this.component.req.user ? 'sumba:/your-stuff/profile' : 'sumba:/signin')
|
|
24
|
+
} else {
|
|
25
|
+
const menu = find(this.plugin.app.sumba.config.waibuMpa.menuHandler, { title: 'account' })
|
|
26
|
+
if (menu) {
|
|
27
|
+
const items = filter(menu.children, c => {
|
|
28
|
+
if (!has(c, 'visible')) return true
|
|
29
|
+
return c.visible === (req.user ? 'auth' : 'anon')
|
|
30
|
+
})
|
|
31
|
+
for (const item of items) {
|
|
32
|
+
if (item.title === 'yourProfile') {
|
|
33
|
+
if (this.params.attr.fancyProfile) {
|
|
34
|
+
const replacer = 'sumba.asset:/user-profile.png'
|
|
35
|
+
const profile = await this.component.buildSentence(`
|
|
36
|
+
<div>
|
|
37
|
+
<c:dropdown-item href="${item.href}">
|
|
38
|
+
<c:img src="dobo:/attachment/SumbaUser/${req.user.id}/profile/main.png?notfound=${replacer}" responsive rounded />
|
|
39
|
+
<c:div margin="top-1" text="align:center">${req.user.firstName} ${req.user.lastName}</c:div>
|
|
40
|
+
</c:dropdown-item>
|
|
41
|
+
</div>
|
|
42
|
+
`)
|
|
43
|
+
html.push(profile)
|
|
44
|
+
} else {
|
|
45
|
+
html.push(await this.component.buildTag({ tag: 'dropdownItem', attr: { href: routePath(item.href) }, html: this.component.req.t(item.title) }))
|
|
46
|
+
}
|
|
47
|
+
} else if (item.title === '-') {
|
|
48
|
+
html.push(await this.component.buildTag({ tag: 'dropdownItem', attr: { divider: true } }))
|
|
49
|
+
} else {
|
|
50
|
+
html.push(await this.component.buildTag({ tag: 'dropdownItem', attr: { href: routePath(item.href) }, html: this.component.req.t(item.title) }))
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
this.params.noTag = true
|
|
56
|
+
this.params.html = await this.component.buildTag({ tag: 'navItem', attr, html: html.join('\n') })
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default navDropdownUser
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
async function download () {
|
|
2
|
+
return {
|
|
3
|
+
common: {
|
|
4
|
+
disabled: ['create', 'update', 'get'],
|
|
5
|
+
hidden: ['id'],
|
|
6
|
+
formatter: {
|
|
7
|
+
description: async function (val, rec) {
|
|
8
|
+
const sentence = `<c:a target="_blank" href="sumba:/your-stuff/download/get/${rec.file}" content="${val}" />`
|
|
9
|
+
return await this.component.buildSentence(sentence)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
view: {
|
|
14
|
+
list: {
|
|
15
|
+
fields: ['description', 'type', 'size', 'status', 'updatedAt'],
|
|
16
|
+
stat: {
|
|
17
|
+
aggregate: [
|
|
18
|
+
{ fields: ['type'], group: 'type', aggregate: ['count'] },
|
|
19
|
+
{ fields: ['status'], group: 'status', aggregate: ['count'] }
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default download
|
|
@@ -27,10 +27,11 @@ const id = {
|
|
|
27
27
|
schema.view.fields = ['createdAt']
|
|
28
28
|
schema.view.label = { createdAt: 'conversation' }
|
|
29
29
|
schema.view.formatter = {
|
|
30
|
-
createdAt: (val, rec)
|
|
30
|
+
createdAt: async function (val, rec) {
|
|
31
31
|
const message = this.app.bajoMarkdown ? this.app.bajoMarkdown.parseContent(rec.message) : rec.message
|
|
32
32
|
const isMe = rec.userId === req.user.id
|
|
33
|
-
|
|
33
|
+
const sentence = `<c:div margin="bottom-3"><c:badge background="color:${isMe ? 'primary' : 'secondary'}" t:content="${isMe ? 'you' : 'us'}" /> <small>${req.format(val, 'datetime')}</small></c:div>`
|
|
34
|
+
return (await this.component.buildSentence(sentence)) + message
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
return await reply.view('sumba.template:/help/trouble-tickets/details.html', { list, schema, master, form, error })
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const download = {
|
|
2
|
+
method: ['GET', 'POST'],
|
|
3
|
+
handler: async function (req, reply) {
|
|
4
|
+
const { importModule } = this.app.bajo
|
|
5
|
+
const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
|
|
6
|
+
const layoutTpl = 'main.layout:/with-addons.html'
|
|
7
|
+
const tpl = 'waibuAdmin.template:/crud/list-notitle.html'
|
|
8
|
+
return await crudSkel.call(this, 'SumbaDownload', req, reply, { tpl, layoutTpl, title: req.t('downloadList') })
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default download
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const get = {
|
|
2
|
+
method: ['GET'],
|
|
3
|
+
url: '/your-stuff/download/get/*',
|
|
4
|
+
handler: async function (req, reply) {
|
|
5
|
+
const { importModule } = this.app.bajo
|
|
6
|
+
const handler = await importModule('waibu:/lib/handle-download.js')
|
|
7
|
+
const file = `${this.downloadDir}/${req.params['*']}`
|
|
8
|
+
return await handler.call(this, file, req, reply)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default get
|