sumba 2.8.1 → 2.10.0

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.
@@ -1,19 +1,30 @@
1
1
  const useAdmin = ['waibuAdmin']
2
2
 
3
3
  export async function rebuildFilter (modelName, filter, req) {
4
- const { isEmpty, map, find, get } = this.app.lib._
4
+ const { isEmpty, isPlainObject, map, find, get } = this.app.lib._
5
5
  filter.query = filter.query ?? {}
6
+
7
+ const queryByModel = (query) => {
8
+ // by model
9
+ const setting = get(req, `site.setting.dobo.query.${modelName}`)
10
+ if (isPlainObject(setting) && !isEmpty(setting)) query.$and.push(setting)
11
+ return query
12
+ }
13
+
6
14
  const model = this.app.dobo.getModel(modelName)
7
15
  const hasSiteId = model.hasProperty('siteId')
8
16
  const hasUserId = model.hasProperty('userId')
9
17
  const hasTeamId = model.hasProperty('teamId')
10
18
  const isAdmin = find(get(req, 'user.teams', []), { alias: 'administrator' }) && useAdmin.includes(get(req, 'routeOptions.config.ns'))
11
- if (!(hasSiteId || hasUserId || hasTeamId)) return filter
12
19
  const q = { $and: [] }
13
20
  if (!isEmpty(filter.query)) {
14
21
  if (filter.query.$and) q.$and.push(...filter.query.$and)
15
22
  else q.$and.push(filter.query)
16
23
  }
24
+ if (!(hasSiteId || hasUserId || hasTeamId)) {
25
+ filter.query = queryByModel(q)
26
+ return filter
27
+ }
17
28
  if (hasSiteId) q.$and.push({ siteId: req.site.id })
18
29
  if (hasTeamId && !isAdmin) {
19
30
  const teamIds = map(req.user.teams, 'id')
@@ -22,7 +33,7 @@ export async function rebuildFilter (modelName, filter, req) {
22
33
  } else if (!isAdmin) {
23
34
  if (hasUserId) q.$and.push({ userId: req.user.id })
24
35
  }
25
- filter.query = q
36
+ filter.query = queryByModel(q)
26
37
  return filter
27
38
  }
28
39
 
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "properties": [
3
3
  "ns,,50,true,true",
4
- "key,,50,true,true",
4
+ "key,,255,true,true",
5
5
  "value,text"
6
6
  ],
7
7
  "indexes": [{
8
8
  "fields": ["ns", "key", "siteId"],
9
9
  "type": "unique"
10
10
  }],
11
- "features": ["sumba:siteId", "dobo:updatedAt"]
11
+ "features": [
12
+ "sumba:siteId",
13
+ "dobo:immutable",
14
+ "dobo:updatedAt"
15
+ ]
12
16
  }
@@ -4,7 +4,7 @@ const auth = {
4
4
  const { camelCase } = this.app.lib._
5
5
  const { req } = socket
6
6
  const { session } = req
7
- const site = await this.getSite(session.siteId)
7
+ const site = await this.getSite(session.siteId, true)
8
8
  socket.join(camelCase(`site ${site.alias}`))
9
9
  let user
10
10
  if (session.userId) {
@@ -4,6 +4,7 @@ const profile = {
4
4
  if (!this.app.masohiMail) return await reply.view('sumba.template:/user/forgot-password.html')
5
5
  const { defaultsDeep } = this.app.lib.aneka
6
6
  const { dayjs } = this.app.lib
7
+ const { get } = this.app.lib._
7
8
  const model = this.app.dobo.getModel('SumbaUser')
8
9
  const form = defaultsDeep(req.body, {})
9
10
  let error
@@ -16,7 +17,7 @@ const profile = {
16
17
  const to = `${data.firstName} ${data.lastName} <${data.email}>`
17
18
  const subject = req.t('forgotPasswordLink')
18
19
  const options = { req, reply }
19
- const exp = req.site.setting.sumba.forgotPasswordExpDur
20
+ const exp = get(req, 'site.setting.sumba.forgotPasswordExpDur')
20
21
  data.fpToken = Buffer.from(`${data.token}:${dayjs().add(exp, 'ms').unix()}`).toString('base64')
21
22
  data._meta = { hostHeader: req.headers.host }
22
23
  const payload = { to, subject, data }
package/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import path from 'path'
2
2
  import createNewSite from './lib/create-new-site.js'
3
3
  import removeSite from './lib/remove-site.js'
4
+ import getSite from './lib/get-site.js'
4
5
 
5
6
  /**
6
7
  * Plugin factory
@@ -20,7 +21,10 @@ async function factory (pkgName) {
20
21
  constructor () {
21
22
  super(pkgName, me.app)
22
23
  this.config = {
23
- multiSite: false,
24
+ multiSite: {
25
+ enabled: false,
26
+ catchAll: 'default'
27
+ },
24
28
  waibu: {
25
29
  title: 'site',
26
30
  prefix: 'site'
@@ -129,7 +133,7 @@ async function factory (pkgName) {
129
133
  }
130
134
  }
131
135
  this.unsafeUserFields = ['password']
132
- this.selfBind(['createNewSite', 'removeSite'])
136
+ this.selfBind(['createNewSite', 'removeSite', 'getSite'])
133
137
  }
134
138
 
135
139
  init = async () => {
@@ -402,73 +406,6 @@ async function factory (pkgName) {
402
406
  return guarded
403
407
  }
404
408
 
405
- getSite = async (hostname, useId) => {
406
- const { omit } = this.app.lib._
407
- const omitted = ['status']
408
-
409
- const mergeSetting = async (site) => {
410
- const { defaultsDeep } = this.app.lib.aneka
411
- const { parseObject, dayjs } = this.app.lib
412
- const { trim, get, filter } = this.app.lib._
413
- const defSetting = {}
414
- const nsSetting = {}
415
- const names = this.app.getAllNs()
416
- const query = {
417
- ns: { $in: names },
418
- siteId: site.id
419
- }
420
- const all = await this.app.dobo.getModel('SumbaSiteSetting').findAllRecord({ query })
421
- for (const ns of names) {
422
- nsSetting[ns] = {}
423
- defSetting[ns] = get(this, `app.${ns}.config.siteSetting`, {})
424
- const items = filter(all, { ns })
425
- for (const item of items) {
426
- let value = trim([item.value] ?? '')
427
- if (['[', '{'].includes(value[0])) {
428
- try {
429
- value = parseObject(JSON.parse(value))
430
- } catch (err) {}
431
- } else if (Number(value)) value = Number(value)
432
- else if (['true', 'false'].includes(value)) value = value === 'true'
433
- else {
434
- const dt = dayjs(value)
435
- if (dt.isValid()) value = dt.toDate()
436
- }
437
- nsSetting[ns][item.key] = value
438
- }
439
- }
440
- site.setting = defaultsDeep({}, nsSetting, defSetting)
441
- // additional fields
442
- const country = await this.app.dobo.getModel('CdbCountry').getRecord(site.country, { noHook: true })
443
- site.countryName = (country ?? {}).name ?? site.country
444
- }
445
-
446
- let site = {}
447
-
448
- if (!this.config.multiSite) {
449
- const resp = await this.app.dobo.getModel('SumbaSite').findOneRecord({ query: { alias: 'default' } }, { noHook: true })
450
- site = omit(resp, omitted)
451
- await mergeSetting(site)
452
- return site
453
- }
454
- let query
455
- if (useId) query = { id: hostname }
456
- else {
457
- query = {
458
- $or: [
459
- { hostname },
460
- { alias: hostname }
461
- ]
462
- }
463
- }
464
- const row = await this.app.dobo.getModel('SumbaSite').findOneRecord({ query }, { noHook: true })
465
- if (!row) throw this.error('unknownSite')
466
- if (row.status !== 'ACTIVE') throw this.error('siteInactiveInfo')
467
- site = omit(row, omitted)
468
- await mergeSetting(site)
469
- return site
470
- }
471
-
472
409
  signout = async ({ req, reply, reason }) => {
473
410
  const { runHook } = this.app.bajo
474
411
  const { getSessionId } = this.app.waibuMpa
@@ -499,9 +436,10 @@ async function factory (pkgName) {
499
436
  return reply.redirectTo(url, { query, params })
500
437
  }
501
438
 
502
- generatePassword = (req) => {
439
+ generatePassword = (req = {}) => {
440
+ const { get } = this.app.lib._
503
441
  const { generateId } = this.app.lib.aneka
504
- const cfg = req ? req.site.setting.sumba.userPassword : this.config.siteSetting.userPassword
442
+ const cfg = get(req, 'site.setting.sumba.userPassword', this.config.siteSetting.userPassword)
505
443
  let passwd = generateId()
506
444
  if (cfg.minLowercase) passwd += generateId({ pattern: 'abcdefghijklmnopqrstuvwxyz', length: cfg.minLowercase })
507
445
  if (cfg.minUppercase) passwd += generateId({ pattern: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', length: cfg.minUppercase })
@@ -580,6 +518,7 @@ async function factory (pkgName) {
580
518
 
581
519
  createNewSite = createNewSite
582
520
  removeSite = removeSite
521
+ getSite = getSite
583
522
  }
584
523
 
585
524
  return Sumba
@@ -0,0 +1,70 @@
1
+ async function getSite (hostname, useId) {
2
+ const { omit } = this.app.lib._
3
+ const omitted = ['status']
4
+
5
+ const mergeSetting = async (site) => {
6
+ const { defaultsDeep, isSet } = this.app.lib.aneka
7
+ const { parseObject, dayjs } = this.app.lib
8
+ const { isEmpty, trim, get, filter, set, isPlainObject, isArray } = this.app.lib._
9
+ const defSetting = {}
10
+ const nsSetting = {}
11
+ const names = this.app.getAllNs()
12
+ const query = {
13
+ ns: { $in: names },
14
+ siteId: site.id
15
+ }
16
+ const all = await this.app.dobo.getModel('SumbaSiteSetting').findAllRecord({ query })
17
+ for (const ns of names) {
18
+ const item = get(this, `app.${ns}.config.siteSetting`)
19
+ if (isSet(item)) defSetting[ns] = item
20
+ const items = filter(all, { ns })
21
+ for (const item of items) {
22
+ let value = trim([item.value] ?? '')
23
+ if (['[', '{'].includes(value[0])) {
24
+ try {
25
+ value = parseObject(JSON.parse(value))
26
+ } catch (err) {}
27
+ } else if (Number(value)) value = Number(value)
28
+ else if (['true', 'false'].includes(value)) value = value === 'true'
29
+ else if (item.key.endsWith('$in')) value = value.split(',').map(v => v.trim())
30
+ else {
31
+ const dt = dayjs(value)
32
+ if (dt.isValid()) value = dt.toDate()
33
+ }
34
+ if ((isPlainObject(value) || isArray(value)) && isEmpty(value)) continue
35
+ set(nsSetting, `${ns}.${item.key}`, value)
36
+ }
37
+ }
38
+ site.setting = defaultsDeep({}, nsSetting, defSetting)
39
+ // additional fields
40
+ const country = await this.app.dobo.getModel('CdbCountry').getRecord(site.country, { noHook: true })
41
+ site.countryName = (country ?? {}).name ?? site.country
42
+ }
43
+
44
+ let site = {}
45
+
46
+ const multiSite = this.config.multiSite === true ? { enabled: true, catchAll: 'default' } : this.config.multiSite
47
+ if (!multiSite.enabled) {
48
+ const resp = await this.app.dobo.getModel('SumbaSite').findOneRecord({ query: { alias: 'default' } }, { noHook: true })
49
+ site = omit(resp, omitted)
50
+ await mergeSetting(site)
51
+ return site
52
+ }
53
+ let query
54
+ if (useId) query = { id: hostname }
55
+ else query = { hostname }
56
+ let row = await this.app.dobo.getModel('SumbaSite').findOneRecord({ query }, { noHook: true })
57
+ if (!row) {
58
+ if (multiSite.catchAll) {
59
+ query = { alias: multiSite.catchAll === true ? 'default' : multiSite.catchAll }
60
+ row = await this.app.dobo.getModel('SumbaSite').findOneRecord({ query }, { noHook: true })
61
+ }
62
+ if (!row) throw this.error('unknownSite')
63
+ }
64
+ if (row.status !== 'ACTIVE') throw this.error('siteInactiveInfo')
65
+ site = omit(row, omitted)
66
+ await mergeSetting(site)
67
+ return site
68
+ }
69
+
70
+ export default getSite
@@ -1,6 +1,7 @@
1
1
  import { joiPasswordExtendCore } from 'joi-password'
2
2
 
3
- async function passwordRule (req) {
3
+ async function passwordRule (req = {}) {
4
+ const { get } = this.app.lib._
4
5
  const { importPkg } = this.app.bajo
5
6
  const joi = await importPkg('dobo:joi')
6
7
  const joiPassword = joi.extend(joiPasswordExtendCore)
@@ -9,7 +10,7 @@ async function passwordRule (req) {
9
10
  .min(8)
10
11
  .max(100)
11
12
  .required()
12
- const cfg = req ? req.site.setting.sumba.userPassword : this.config.siteSetting.userPassword
13
+ const cfg = get(req, 'site.setting.sumba.userPassword', this.config.siteSetting.userPassword)
13
14
  if (cfg.minUppercase) password = password.minOfUppercase(cfg.minUppercase)
14
15
  if (cfg.minLowercase) password = password.minOfLowercase(cfg.minLowercase)
15
16
  if (cfg.minSpecialChar) password = password.minOfSpecialCharacters(cfg.minSpecialChar)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sumba",
3
- "version": "2.8.1",
3
+ "version": "2.10.0",
4
4
  "description": "Biz Suite for Bajo Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-03-13
4
+
5
+ - [2.10.0] ```getSite()``` now accept object & array based on their keys to
6
+
7
+ ## 2026-03-12
8
+
9
+ - [2.9.0] Add ability to restrict/filter dobo's records through site setting
10
+ - [2.9.0] Multisite config now accept object. If set to ```true``` it defaults to ```catchAll: 'default'```
11
+
3
12
  ## 2026-03-11
4
13
 
5
14
  - [2.8.0] Add ```createNewSite()``` and ```applet.crateNewSite```