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.
@@ -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
 
@@ -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,6 +1,6 @@
1
1
  <c:navbar border="side:bottom">
2
2
  <c:nav tag="ul" dim="width:100" flex="justify-content:end">
3
- <c:nav-dropdown-user no-menu />
3
+ <c:sumba-nav-dropdown-user no-menu />
4
4
  <c:nav-divider />
5
5
  <c:nav-toggle-fullscreen />
6
6
  <c:nav-dropdown-darkmode dropdown-menu="end" />
@@ -1,21 +1,24 @@
1
1
  {
2
- "properties": [{
3
- "name": "description",
4
- "type": "string",
5
- "maxLength": 50
6
- }, {
7
- "name": "jobId",
8
- "type": "string",
9
- "maxLength": 50
10
- }, {
11
- "name": "jobDetails",
12
- "type": "object"
13
- }, {
14
- "name": "status",
15
- "type": "string",
16
- "maxLength": 20,
17
- "default": "NEW",
18
- "values": ["NEW", "PROCESSING", "COMPLETE", "FAIL"]
19
- }],
20
- "feature": ["sumba.siteId", "sumba.userId", "createdAt", "updatedAt"]
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
- async function getSetting (type, source) {
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 getToken.call(this, 'apiKey', req, source)
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 getSetting.call(this, 'basic', source)
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 getSetting.call(this, 'jwt', source)
312
- const token = await getToken.call(this, 'jwt', req, source)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sumba",
3
- "version": "1.2.7",
3
+ "version": "1.2.10",
4
4
  "description": "Bajo Framework's Biz Suite",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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
- return `<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> ${message}`
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