sumba 2.15.1 → 2.17.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.
Files changed (36) hide show
  1. package/extend/bajo/intl/en-US.json +13 -5
  2. package/extend/bajo/intl/id.json +13 -5
  3. package/extend/dobo/feature/country.js +3 -3
  4. package/extend/dobo/feature/email.js +2 -2
  5. package/extend/dobo/feature/lat-lng.js +8 -8
  6. package/extend/dobo/feature/lat.js +2 -2
  7. package/extend/dobo/feature/lng.js +2 -2
  8. package/extend/dobo/feature/phone.js +2 -2
  9. package/extend/dobo/feature/site-id.js +1 -1
  10. package/extend/dobo/feature/slug.js +14 -14
  11. package/extend/dobo/feature/status.js +7 -4
  12. package/extend/dobo/feature/team-id.js +4 -2
  13. package/extend/dobo/feature/ts.js +2 -2
  14. package/extend/dobo/feature/url.js +4 -4
  15. package/extend/dobo/feature/user-id.js +3 -1
  16. package/extend/dobo/model/ticket.json +14 -1
  17. package/extend/dobo/model/user-setting.json +17 -0
  18. package/extend/waibuDb/schema/site.js +1 -6
  19. package/extend/waibuDb/schema/team-user.js +3 -25
  20. package/extend/waibuDb/schema/ticket.json +16 -0
  21. package/extend/waibuDb/schema/user-setting.js +51 -0
  22. package/extend/waibuDb/schema/user.js +73 -0
  23. package/extend/waibuMpa/extend/waibuAdmin/route/site.js +1 -1
  24. package/extend/waibuMpa/extend/waibuAdmin/route/team/@action.js +1 -1
  25. package/extend/waibuMpa/extend/waibuAdmin/route/team-setting/@action.js +1 -1
  26. package/extend/waibuMpa/extend/waibuAdmin/route/team-user/@action.js +1 -1
  27. package/extend/waibuMpa/extend/waibuAdmin/route/user/@action.js +3 -2
  28. package/extend/waibuMpa/extend/waibuAdmin/route/user-setting/@action.js +11 -0
  29. package/extend/waibuMpa/route/your-stuff/profile/edit.js +1 -1
  30. package/extend/waibuMpa/route/your-stuff/profile.js +1 -1
  31. package/extend/waibuRestApi/route/manage/ticket-cat/model-builder.json +4 -0
  32. package/index.js +29 -13
  33. package/lib/util.js +2 -2
  34. package/package.json +1 -1
  35. package/wiki/CHANGES.md +10 -0
  36. package/extend/waibuDb/schema/user.json +0 -60
@@ -95,12 +95,17 @@
95
95
  "ticket": "Ticket",
96
96
  "ticketCat": "Ticket Category",
97
97
  "supportSystem": "Support System",
98
- "management": "Management",
98
+ "team": "Team",
99
99
  "manageSite": "Manage Site",
100
- "manageUser": "Manage User",
101
100
  "manageTeam": "Manage Team",
102
- "manageTeamUser": "Manage Team Member",
103
- "manageTeamSetting": "Manage Team Setting",
101
+ "manageUser": "Manage User",
102
+ "siteProfile": "Site Profile",
103
+ "siteSetting": "Site Setting",
104
+ "userProfile": "User Profile",
105
+ "userSetting": "User Setting",
106
+ "teamProfile": "Team Profile",
107
+ "teamUser": "Team User",
108
+ "teamSetting": "Team Setting",
104
109
  "resetUserPassword": "Reset User Password",
105
110
  "unknownUser": "User Unknown/Invalid",
106
111
  "socialMedia": "Social Media",
@@ -114,7 +119,6 @@
114
119
  "statusUnverified": "Unverified",
115
120
  "statusActive": "Active",
116
121
  "statusInactive": "Inactive",
117
- "siteSetting": "Site Settings",
118
122
  "validCountryCodeRequired": "Value must be one of valid country codes",
119
123
  "aliasRequired": "Alias is required",
120
124
  "aliasOrHOstnameExists%s%s": "A site with alias '%s' or hostname '%s' exists already",
@@ -127,6 +131,10 @@
127
131
  "protectedArea": "Protected Area",
128
132
  "pleaseAuthenticate": "Please authenticate yourself, thank you!",
129
133
  "manageAllSite": "Manage All Sites",
134
+ "statusEnabled": "Enabled",
135
+ "statusDisabled": "Disabled",
136
+ "statusOpen": "Open",
137
+ "statusClosed": "Closed",
130
138
  "field": {
131
139
  "currentPassword": "Current Password",
132
140
  "newPassword": "New Password",
@@ -96,12 +96,17 @@
96
96
  "ticket": "Tiket",
97
97
  "ticketCat": "Kategori Tiket",
98
98
  "supportSystem": "Sistim Dukungan",
99
- "management": "Pengelolaan",
99
+ "team": "Tim",
100
100
  "manageSite": "Kelola Situs",
101
- "manageUser": "Kelola Pengguna",
102
101
  "manageTeam": "Kelola Tim",
103
- "manageTeamUser": "Kelola Anggota Tim",
104
- "manageTeamSetting": "Kelola Setelan Tim",
102
+ "manageUser": "Kelola Pengguna",
103
+ "siteProfile": "Profil Situs",
104
+ "siteSetting": "Pengaturan Situs",
105
+ "userProfile": "Profil Pengguna",
106
+ "userSetting": "Pengaturan Pengguna",
107
+ "teamProfile": "Profil Tim",
108
+ "teamUser": "Anggota Tim",
109
+ "teamSetting": "Pengaturan Tim",
105
110
  "resetUserPassword": "Reset Kata Sandi Pengguna",
106
111
  "unknownUser": "Pengguna Tidak Dikenal/Tidak Valid",
107
112
  "socialMedia": "Media Sosial",
@@ -115,7 +120,6 @@
115
120
  "statusUnverified": "Belum Terverifikasi",
116
121
  "statusActive": "Aktif",
117
122
  "statusInactive": "Non Aktif",
118
- "siteSetting": "Setelan Situs",
119
123
  "validCountryCodeRequired": "Nilai harus salah satu dari kode negara yang berlaku",
120
124
  "aliasRequired": "Alias tidak boleh kosong",
121
125
  "aliasOrHOstnameExists%s%s": "Situs dengan alias '%s' atau nama host '%s' telah ada",
@@ -128,6 +132,10 @@
128
132
  "protectedArea": "Wilayah Dilindungi",
129
133
  "pleaseAuthenticate": "Silahkan melakukan otentikasi terlebih dahulu, terima kasih!",
130
134
  "manageAllSite": "Kelola Semua Situs",
135
+ "statusEnabled": "Dihidupkan",
136
+ "statusDisabled": "Dimatikan",
137
+ "statusOpen": "Terbuka",
138
+ "statusClosed": "Tertutup",
131
139
  "field": {
132
140
  "currentPassword": "Kata Sandi Saat Ini",
133
141
  "newPassword": "Kata Sandi Baru",
@@ -1,8 +1,8 @@
1
1
  async function country (opts = {}) {
2
- opts.fieldName = opts.fieldName ?? 'country'
2
+ opts.field = opts.field ?? 'country'
3
3
  return {
4
4
  properties: [{
5
- name: opts.fieldName,
5
+ name: opts.field,
6
6
  type: 'string',
7
7
  maxLength: 2,
8
8
  index: opts.index ?? true,
@@ -10,7 +10,7 @@ async function country (opts = {}) {
10
10
  rules: ['uppercase', { rule: 'length', params: 2 }],
11
11
  rulesMsg: { 'any.only': 'validCountryCodeRequired' }
12
12
  }],
13
- rules: [{ rule: 'trim', fields: [opts.fieldName] }]
13
+ rules: [{ rule: 'trim', fields: [opts.field] }]
14
14
  }
15
15
  }
16
16
 
@@ -1,8 +1,8 @@
1
1
  async function email (opts = {}) {
2
- opts.fieldName = opts.fieldName ?? 'email'
2
+ opts.field = opts.field ?? 'email'
3
3
  return {
4
4
  properties: [{
5
- name: opts.fieldName ?? 'email',
5
+ name: opts.field ?? 'email',
6
6
  type: 'string',
7
7
  maxLength: 50,
8
8
  rules: ['email']
@@ -2,20 +2,20 @@ import { latLngHook } from '../../../lib/util.js'
2
2
 
3
3
  async function latLng (opts = {}) {
4
4
  const { merge } = this.app.lib._
5
- opts.fieldNameLat = opts.fieldNameLat ?? 'lat'
6
- opts.fieldNameLng = opts.fieldNameLng ?? 'lng'
5
+ opts.fieldLat = opts.fieldLat ?? 'lat'
6
+ opts.fieldLng = opts.fieldLng ?? 'lng'
7
7
  opts.scale = opts.scale ?? 5
8
8
  opts.precision = opts.precision ?? 8
9
9
  return {
10
10
  properties: [{
11
- name: opts.fieldNameLat,
11
+ name: opts.fieldLat,
12
12
  type: 'double',
13
13
  required: opts.required ?? true,
14
14
  index: opts.required ?? true,
15
15
  precision: opts.precision,
16
16
  scale: opts.scale
17
17
  }, {
18
- name: opts.fieldNameLng,
18
+ name: opts.fieldLng,
19
19
  type: 'double',
20
20
  required: opts.required ?? true,
21
21
  index: opts.required ?? true,
@@ -24,12 +24,12 @@ async function latLng (opts = {}) {
24
24
  }],
25
25
  hook: {
26
26
  beforeCreate: async function (body) {
27
- await latLngHook.call(this, body, merge({}, opts, { lat: opts.fieldNameLat }))
28
- await latLngHook.call(this, body, merge({}, opts, { lng: opts.fieldNameLng }))
27
+ await latLngHook.call(this, body, merge({}, opts, { lat: opts.fieldLat }))
28
+ await latLngHook.call(this, body, merge({}, opts, { lng: opts.fieldLng }))
29
29
  },
30
30
  beforeUpdate: async function (body) {
31
- await latLngHook.call(this, body, merge({}, opts, { lat: opts.fieldNameLat }))
32
- await latLngHook.call(this, body, merge({}, opts, { lng: opts.fieldNameLng }))
31
+ await latLngHook.call(this, body, merge({}, opts, { lat: opts.fieldLat }))
32
+ await latLngHook.call(this, body, merge({}, opts, { lng: opts.fieldLng }))
33
33
  }
34
34
  }
35
35
  }
@@ -1,12 +1,12 @@
1
1
  import { latLngHook } from '../../../lib/util.js'
2
2
 
3
3
  async function lat (opts = {}) {
4
- opts.fieldName = opts.fieldName ?? 'lat'
4
+ opts.field = opts.field ?? 'lat'
5
5
  opts.scale = opts.scale ?? 5
6
6
  opts.precision = opts.precision ?? 8
7
7
  return {
8
8
  properties: [{
9
- name: opts.fieldName,
9
+ name: opts.field,
10
10
  type: 'double',
11
11
  required: opts.required ?? true,
12
12
  index: opts.required ?? true,
@@ -1,12 +1,12 @@
1
1
  import { latLngHook } from '../../../lib/util.js'
2
2
 
3
3
  async function lng (opts = {}) {
4
- opts.fieldName = opts.fieldName ?? 'lng'
4
+ opts.field = opts.field ?? 'lng'
5
5
  opts.scale = opts.scale ?? 5
6
6
  opts.precision = opts.precision ?? 8
7
7
  return {
8
8
  properties: [{
9
- name: opts.fieldName,
9
+ name: opts.field,
10
10
  type: 'double',
11
11
  required: opts.required ?? true,
12
12
  index: opts.required ?? true,
@@ -1,8 +1,8 @@
1
1
  async function phone (opts = {}) {
2
- opts.fieldName = opts.fieldName ?? 'phone'
2
+ opts.field = opts.field ?? 'phone'
3
3
  return {
4
4
  properties: [{
5
- name: opts.fieldName ?? 'phone',
5
+ name: opts.field ?? 'phone',
6
6
  type: 'string',
7
7
  maxLength: 50
8
8
  }]
@@ -7,7 +7,7 @@ async function siteId (opts = {}) {
7
7
  ref: {
8
8
  site: {
9
9
  model: 'SumbaSite',
10
- propName: 'id',
10
+ field: 'id',
11
11
  type: '1:1'
12
12
  }
13
13
  },
@@ -2,36 +2,36 @@ import slug from 'slug'
2
2
 
3
3
  async function autoInc (body, opts) {
4
4
  const { set, last } = this.app.lib._
5
- const query = set({}, opts.fieldName, { $regex: new RegExp('^' + body[opts.fieldName]) })
6
- const sort = set({}, opts.fieldName, -1)
5
+ const query = set({}, opts.field, { $regex: new RegExp('^' + body[opts.field]) })
6
+ const sort = set({}, opts.field, -1)
7
7
  const options = { noHook: true, skipCache: true, thrownNotFound: false }
8
8
  const resp = await this.findOneRecord({ query, sort }, options)
9
- if (resp) return body[opts.fieldName]
10
- const rslugs = resp[opts.fieldName].split('-')
11
- const slugs = body[opts.fieldName].split('-')
9
+ if (resp) return body[opts.field]
10
+ const rslugs = resp[opts.field].split('-')
11
+ const slugs = body[opts.field].split('-')
12
12
  let num
13
- if (Number(last(rslugs)) && body[opts.fieldName] === rslugs.slice(0, rslugs.length - 1).join('-')) {
13
+ if (Number(last(rslugs)) && body[opts.field] === rslugs.slice(0, rslugs.length - 1).join('-')) {
14
14
  num = Number(rslugs.pop()) + 1
15
- body[opts.fieldName] = `${rslugs.join('-')}-${num}`
15
+ body[opts.field] = `${rslugs.join('-')}-${num}`
16
16
  } else {
17
17
  const idx = slugs.length - 1
18
18
  num = Number(slugs[idx])
19
- if (!num) body[opts.fieldName] += '-1'
19
+ if (!num) body[opts.field] += '-1'
20
20
  else {
21
21
  slugs[idx] = num + 1
22
- body[opts.fieldName] = slugs.join('-')
22
+ body[opts.field] = slugs.join('-')
23
23
  }
24
24
  }
25
25
  return await autoInc.call(this, body, opts)
26
26
  }
27
27
 
28
28
  async function mainFn (opts = {}) {
29
- opts.fieldName = opts.fieldName ?? 'slug'
29
+ opts.field = opts.field ?? 'slug'
30
30
  opts.fieldSource = opts.fieldSource ?? ['name']
31
31
  opts.autoInc = true
32
32
  return {
33
33
  properties: [{
34
- name: opts.fieldName ?? 'slug',
34
+ name: opts.field ?? 'slug',
35
35
  type: 'string',
36
36
  maxLength: 255,
37
37
  index: 'unique'
@@ -40,7 +40,7 @@ async function mainFn (opts = {}) {
40
40
  beforeCreate: async function (body) {
41
41
  const { error } = this.app.bajo
42
42
  const { isEmpty, isString } = this.app.lib._
43
- if (isEmpty(body[opts.fieldName])) {
43
+ if (isEmpty(body[opts.field])) {
44
44
  if (isString(opts.fieldSource)) opts.fieldSource = [opts.fieldSource]
45
45
  const source = []
46
46
  opts.fieldSource.forEach(s => {
@@ -50,9 +50,9 @@ async function mainFn (opts = {}) {
50
50
  const details = [{ field: opts.fieldSource.join(', '), error: 'required' }]
51
51
  throw error('\'%s\' is required', opts.fieldSource.join(', '), { details })
52
52
  }
53
- body[opts.fieldName] = slug(source.join(' '))
53
+ body[opts.field] = slug(source.join(' '))
54
54
  }
55
- if (opts.autoInc) body[opts.fieldName] = await autoInc.call(this, body, opts)
55
+ if (opts.autoInc) body[opts.field] = await autoInc.call(this, body, opts)
56
56
  }
57
57
  }
58
58
  }
@@ -1,17 +1,20 @@
1
1
  async function status (opts = {}) {
2
- opts.fieldName = opts.fieldName ?? 'status'
2
+ opts.field = opts.field ?? 'status'
3
+ opts.required = opts.required ?? true
4
+ opts.values = opts.values ?? ['UNVERIFIED', 'ACTIVE', 'INACTIVE']
3
5
  return {
4
6
  properties: [{
5
- name: opts.fieldName ?? 'status',
7
+ name: opts.field ?? 'status',
6
8
  type: 'string',
7
9
  maxLength: 50,
8
10
  index: true,
9
- values: opts.values ?? ['UNVERIFIED', 'ACTIVE', 'INACTIVE']
11
+ required: opts.required,
12
+ values: opts.values
10
13
  }],
11
14
  hook: {
12
15
  beforeCreate: async function (body) {
13
16
  const { isSet } = this.app.lib.aneka
14
- if (!isSet(body[opts.fieldName])) body[opts.fieldName] = opts.default
17
+ if (!isSet(body[opts.field])) body[opts.field] = opts.default
15
18
  }
16
19
  }
17
20
  }
@@ -8,14 +8,16 @@ async function teamId (opts = {}) {
8
8
  ref: {
9
9
  site: {
10
10
  model: 'SumbaSite',
11
- propName: 'id',
11
+ field: 'id',
12
12
  type: '1:1',
13
13
  fields: ['id', 'alias', 'hostname', 'title']
14
14
  },
15
15
  team: {
16
16
  model: 'SumbaTeam',
17
- propName: 'id',
17
+ field: 'id',
18
18
  type: '1:1',
19
+ labelField: 'name',
20
+ searchField: 'name',
19
21
  fields: ['id', 'name']
20
22
  }
21
23
  },
@@ -1,8 +1,8 @@
1
1
  async function ts (opts = {}) {
2
- opts.fieldName = opts.fieldName ?? 'ts'
2
+ opts.field = opts.field ?? 'ts'
3
3
  return {
4
4
  properties: [{
5
- name: opts.fieldName ?? 'ts',
5
+ name: opts.field ?? 'ts',
6
6
  type: 'timestamp',
7
7
  required: opts.required ?? true,
8
8
  index: opts.index ?? true
@@ -1,18 +1,18 @@
1
1
  async function hook (body, options) {
2
2
  const { isSet } = this.app.lib.aneka
3
- let val = body[options.fieldName]
3
+ let val = body[options.field]
4
4
  if (!isSet(val)) return
5
5
  const [, ...params] = val.split('://')
6
6
  if (params.length === 0) val = options.defProto + '://' + val
7
- body[options.fieldName] = val
7
+ body[options.field] = val
8
8
  }
9
9
 
10
10
  async function url (opts = {}) {
11
- opts.fieldName = opts.fieldName ?? 'url'
11
+ opts.field = opts.field ?? 'url'
12
12
  opts.defProto = opts.defProto ?? 'http'
13
13
  return {
14
14
  properties: [{
15
- name: opts.fieldName ?? 'url',
15
+ name: opts.field ?? 'url',
16
16
  type: 'string'
17
17
  }],
18
18
  hook: {
@@ -7,7 +7,9 @@ async function userId (opts = {}) {
7
7
  ref: {
8
8
  user: {
9
9
  model: 'SumbaUser',
10
- propName: 'id',
10
+ field: 'id',
11
+ labelField: 'username',
12
+ searchField: 'username',
11
13
  type: '1:1'
12
14
  }
13
15
  },
@@ -1,7 +1,20 @@
1
1
  {
2
2
  "properties": [
3
3
  "subject,,255,true,true",
4
- "cat,,50,true",
4
+ {
5
+ "name": "cat",
6
+ "type": "string",
7
+ "maxLength": 50,
8
+ "index": true,
9
+ "required": true,
10
+ "ref": {
11
+ "cat": {
12
+ "model": "SumbaTicketCat",
13
+ "searchField": "name",
14
+ "labelField": "name"
15
+ }
16
+ }
17
+ },
5
18
  "message,text,,,true"
6
19
  ],
7
20
  "features": [
@@ -0,0 +1,17 @@
1
+ {
2
+ "buildLevel": 5,
3
+ "properties": [
4
+ "ns,,50,true,true",
5
+ "key,,255,true,true",
6
+ "value,text"
7
+ ],
8
+ "indexes": [{
9
+ "fields": ["ns", "key", "siteId", "userId"],
10
+ "type": "unique"
11
+ }],
12
+ "features": [
13
+ "sumba:siteId",
14
+ "dobo:updatedAt",
15
+ "sumba:userId"
16
+ ]
17
+ }
@@ -28,12 +28,7 @@ async function site (req = {}) {
28
28
  { name: 'personInCharge', fields: ['picName:3-md 6-sm:Name', 'picRole:3-md 6-sm:Role', 'picPhone:3-md 6-sm:Phone', 'picEmail:3-md 6-sm:Email'] },
29
29
  { name: 'address', fields: ['address1:12', 'address2:12', 'city:6-md 8-sm', 'zipCode:2-md 4-sm', 'provinceState:4-md', 'country:6-md', 'phone:6-md', 'website:12'] },
30
30
  { name: 'socialMedia', fields: ['socX:3-md 6-sm', 'socInstagram:3-md 6-sm', 'socFacebook:3-md 6-sm', 'socLinkedIn:3-md 6-sm'] }
31
- ],
32
- widget: {
33
- country: {
34
- component: 'form-select-ext'
35
- }
36
- }
31
+ ]
37
32
  },
38
33
  view: {
39
34
  details,
@@ -5,44 +5,22 @@ async function teamUser () {
5
5
  { name: 'meta', fields: ['id', 'createdAt', 'updatedAt'] },
6
6
  { name: 'general', fields: ['userId:6-md', 'teamId:6-md'] }
7
7
  ],
8
- calcFields: [
9
- { name: 'user', type: 'string' },
10
- { name: 'team', type: 'string' }
11
- ],
12
- valueFormatter: {
13
- user: (val, rec) => {
14
- return rec._ref.user.username
15
- },
16
- team: (val, rec) => {
17
- return rec._ref.team.name
18
- }
19
- },
20
8
  widget: {
21
9
  userId: {
22
- component: 'form-select-ext',
23
10
  attr: {
24
- remoteUrl: 'sumba.restapi:/manage/user',
25
- remoteSearchField: 'username',
26
- remoteLabelField: 'username',
27
- remoteApiKey: true,
28
- ref: 'user:username'
11
+ url: 'sumba.restapi:/manage/user'
29
12
  }
30
13
  },
31
14
  teamId: {
32
- component: 'form-select-ext',
33
15
  attr: {
34
- remoteUrl: 'sumba.restapi:/manage/team',
35
- remoteSearchField: 'name',
36
- remoteLabelField: 'name',
37
- remoteApiKey: true,
38
- ref: 'team:name'
16
+ url: 'sumba.restapi:/manage/team'
39
17
  }
40
18
  }
41
19
  }
42
20
  },
43
21
  view: {
44
22
  list: {
45
- fields: ['user', 'team', 'createdAt', 'updatedAt'],
23
+ fields: ['userId', 'teamId', 'createdAt', 'updatedAt'],
46
24
  stat: {
47
25
  aggregate: [
48
26
  { fields: ['userId'], group: 'userId', aggregate: ['count'] },
@@ -0,0 +1,16 @@
1
+ {
2
+ "common": {
3
+ "widget": {
4
+ "cat": {
5
+ "attr": {
6
+ "url": "sumba.restapi:/manage/ticket-cat"
7
+ }
8
+ },
9
+ "userId": {
10
+ "attr": {
11
+ "url": "sumba.restapi:/manage/user"
12
+ }
13
+ }
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,51 @@
1
+ async function teamUser ({ req } = {}) {
2
+ return {
3
+ common: {
4
+ layout: [
5
+ { name: 'meta', fields: ['id', 'userId', 'createdAt', 'updatedAt'] },
6
+ { name: 'general', fields: ['ns', 'key', 'value'] }
7
+ ],
8
+ calcFields: [
9
+ { name: 'user', type: 'string' }
10
+ ],
11
+ valueFormatter: {
12
+ user: (val, rec) => {
13
+ return rec._ref.user.name
14
+ }
15
+ },
16
+ widget: {
17
+ userId: {
18
+ component: 'form-select-ext',
19
+ attr: {
20
+ remoteUrl: 'sumba.restapi:/manage/user',
21
+ remoteSearchField: 'name',
22
+ remoteLabelField: 'name',
23
+ remoteApiKey: true,
24
+ ref: 'user:name'
25
+ }
26
+ }
27
+ }
28
+ },
29
+ view: {
30
+ list: {
31
+ fields: ['user', 'ns', 'key', 'value', 'createdAt', 'updatedAt'],
32
+ stat: {
33
+ aggregate: [
34
+ { fields: ['userId'], group: 'userId', aggregate: ['count'] },
35
+ { fields: ['teamId'], group: 'teamId', aggregate: ['count'] }
36
+ ]
37
+ }
38
+ },
39
+ details: {
40
+ },
41
+ add: {
42
+ hidden: ['id', 'createdAt', 'updatedAt']
43
+ },
44
+ edit: {
45
+ readonly: ['id', 'createdAt', 'updatedAt']
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ export default teamUser
@@ -0,0 +1,73 @@
1
+ async function user ({ req } = {}) {
2
+ return {
3
+ common: {
4
+ layout: [
5
+ { name: 'meta', fields: ['id:3', 'createdAt:3', 'updatedAt:3', 'status:3'] },
6
+ { name: 'account', fields: ['username:3', 'email:3', 'provider:3', 'password:3', 'firstName:3', 'lastName:3', 'token:6'] },
7
+ { name: 'address', fields: ['address1:12', 'address2:12', 'city:6-md 8-sm', 'zipCode:2-md 4-sm', 'provinceState:4-md', 'country:6-md', 'phone:6-md', 'website:12'] },
8
+ { name: 'socialMedia', fields: ['socX:3-md 6-sm', 'socInstagram:3-md 6-sm', 'socFacebook:3-md 6-sm', 'socLinkedIn:3-md 6-sm'] }
9
+ ],
10
+ valueFormatter: {
11
+ token: async function (val, rec) {
12
+ const { hash } = this.app.bajoExtra
13
+ return await hash(rec.salt)
14
+ }
15
+ },
16
+ widget: {
17
+ country: {
18
+ component: 'form-select-ext'
19
+ },
20
+ token: {
21
+ component: 'form-plaintext'
22
+ }
23
+ }
24
+ },
25
+ view: {
26
+ list: {
27
+ qs: {
28
+ sort: 'username:1',
29
+ limit: 10
30
+ },
31
+ fields: ['createdAt', 'status', 'username', 'provider', 'email', 'firstName', 'lastName', 'city', 'zipCode', 'provinceState', 'country', 'phone'],
32
+ stat: {
33
+ aggregate: [
34
+ { fields: ['status'], group: 'status', aggregate: ['count'] },
35
+ { fields: ['provider'], group: 'provider', aggregate: ['count'] },
36
+ { fields: ['country'], group: 'country', aggregate: ['count'] }
37
+ ]
38
+ }
39
+ },
40
+ details: {
41
+ forceVisible: ['password', 'token'],
42
+ widget: {
43
+ password: {
44
+ component: 'form-plaintext',
45
+ attr: {
46
+ href: 'waibuAdmin:/site/reset-user-password?username={username}',
47
+ value: req.t('resetPassword')
48
+ }
49
+ }
50
+ }
51
+ },
52
+ add: {
53
+ forceVisible: ['password'],
54
+ hidden: ['id', 'createdAt', 'updatedAt', 'provider']
55
+ },
56
+ edit: {
57
+ forceVisible: ['password', 'token'],
58
+ widget: {
59
+ password: {
60
+ component: 'form-plaintext',
61
+ attr: {
62
+ href: 'waibuAdmin:/site/reset-user-password?username={username}',
63
+ value: req.t('resetPassword')
64
+ }
65
+ }
66
+ },
67
+ readonly: ['id', 'createdAt', 'updatedAt', 'username', 'provider']
68
+ }
69
+ }
70
+ }
71
+ }
72
+
73
+ export default user
@@ -1,6 +1,6 @@
1
1
  const manageSite = {
2
2
  method: ['GET', 'POST'],
3
- title: 'manageSite',
3
+ title: 'siteProfile',
4
4
  handler: async function (req, reply) {
5
5
  const { importModule } = this.app.bajo
6
6
  const detailsHandler = await importModule('waibuDb:/lib/crud/details-handler.js')
@@ -1,6 +1,6 @@
1
1
  const action = {
2
2
  method: ['GET', 'POST'],
3
- title: 'manageTeam',
3
+ title: 'teamProfile',
4
4
  handler: async function (req, reply) {
5
5
  const { importModule } = this.app.bajo
6
6
  const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
@@ -1,6 +1,6 @@
1
1
  const action = {
2
2
  method: ['GET', 'POST'],
3
- title: 'manageTeamSetting',
3
+ title: 'teamSetting',
4
4
  handler: async function (req, reply) {
5
5
  const { importModule } = this.app.bajo
6
6
  const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
@@ -1,6 +1,6 @@
1
1
  const action = {
2
2
  method: ['GET', 'POST'],
3
- title: 'manageTeamUser',
3
+ title: 'teamUser',
4
4
  handler: async function (req, reply) {
5
5
  const { importModule } = this.app.bajo
6
6
  const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
@@ -1,10 +1,11 @@
1
1
  const action = {
2
2
  method: ['GET', 'POST'],
3
- title: 'manageUser',
3
+ title: 'userProfile',
4
4
  handler: async function (req, reply) {
5
5
  const { importModule } = this.app.bajo
6
6
  const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
7
- return await crudSkel.call(this, 'SumbaUser', req, reply)
7
+ const options = { modelOpts: { forceNoHidden: ['token'] } }
8
+ return await crudSkel.call(this, 'SumbaUser', req, reply, { options })
8
9
  }
9
10
  }
10
11
 
@@ -0,0 +1,11 @@
1
+ const action = {
2
+ method: ['GET', 'POST'],
3
+ title: 'userSetting',
4
+ handler: async function (req, reply) {
5
+ const { importModule } = this.app.bajo
6
+ const crudSkel = await importModule('waibuAdmin:/lib/crud-skel.js')
7
+ return await crudSkel.call(this, 'SumbaUserSetting', req, reply)
8
+ }
9
+ }
10
+
11
+ export default action
@@ -8,7 +8,7 @@ const profile = {
8
8
  const { updateRecord, getRecord } = this.app.waibuDb
9
9
  const { omit, pick } = this.app.lib._
10
10
  const { hash } = this.app.bajoExtra
11
- const resp = await getRecord({ model: 'SumbaUser', req, id: req.user.id, options: { forceNoHidden: true, noHook: true, noCache: true } })
11
+ const resp = await getRecord({ model: 'SumbaUser', req, id: req.user.id, options: { forceNoHidden: ['token'], noHook: true, noCache: true } })
12
12
  let form = defaultsDeep(req.body, omit(resp.data, ['password', 'salt']))
13
13
  form.token = await hash(form.token)
14
14
  let error
@@ -3,7 +3,7 @@ const profile = {
3
3
  handler: async function (req, reply) {
4
4
  const { hash } = this.app.bajoExtra
5
5
  const { getRecord } = this.app.waibuDb
6
- const options = { forceNoHidden: true, noHook: true, noCache: true, attachment: true, mimeType: true }
6
+ const options = { forceNoHidden: ['token'], noHook: true, noCache: true, attachment: true, mimeType: true }
7
7
  const resp = await getRecord({ model: 'SumbaUser', req, id: req.user.id, options })
8
8
  const form = resp.data
9
9
  form.token = await hash(form.salt)
@@ -0,0 +1,4 @@
1
+ {
2
+ "model": "SumbaTicketCat",
3
+ "disabled": ["create", "update", "remove"]
4
+ }
package/index.js CHANGED
@@ -205,7 +205,27 @@ async function factory (pkgName) {
205
205
  if (!this.app.waibuAdmin) return
206
206
  const { getPluginPrefix } = this.app.waibu
207
207
  const prefix = getPluginPrefix(this.ns)
208
- return [{
208
+ const items = [{
209
+ title: 'manageSite',
210
+ children: [
211
+ { title: 'siteProfile', href: `waibuAdmin:/${prefix}/site` },
212
+ { title: 'siteSetting', href: `waibuAdmin:/${prefix}/site-setting/list` }
213
+ ]
214
+ }, {
215
+ title: 'manageUser',
216
+ children: [
217
+ { title: 'userProfile', href: `waibuAdmin:/${prefix}/user/list` },
218
+ { title: 'userSetting', href: `waibuAdmin:/${prefix}/user-setting/list` },
219
+ { title: 'resetUserPassword', href: `waibuAdmin:/${prefix}/reset-user-password` }
220
+ ]
221
+ }, {
222
+ title: 'manageTeam',
223
+ children: [
224
+ { title: 'teamProfile', href: `waibuAdmin:/${prefix}/team/list` },
225
+ { title: 'teamUser', href: `waibuAdmin:/${prefix}/team-user/list` },
226
+ { title: 'teamSetting', href: `waibuAdmin:/${prefix}/team-setting/list` }
227
+ ]
228
+ }, {
209
229
  title: 'supportSystem',
210
230
  children: [
211
231
  { title: 'contactForm', href: `waibuAdmin:/${prefix}/contact-form/list` },
@@ -214,17 +234,9 @@ async function factory (pkgName) {
214
234
  { title: 'ticketCat', href: `waibuAdmin:/${prefix}/ticket-cat/list` }
215
235
  ]
216
236
  }, {
217
- title: 'management',
237
+ title: 'misc',
218
238
  children: [
219
- { title: 'manageSite', href: `waibuAdmin:/${prefix}/site` },
220
- { title: 'manageUser', href: `waibuAdmin:/${prefix}/user/list` },
221
- { title: 'manageTeam', href: `waibuAdmin:/${prefix}/team/list` },
222
- { title: 'manageTeamUser', href: `waibuAdmin:/${prefix}/team-user/list` },
223
- { title: 'manageTeamSetting', href: `waibuAdmin:/${prefix}/team-setting/list` },
224
- { title: 'manageDownload', href: `waibuAdmin:/${prefix}/download/list` },
225
- { title: '-' },
226
- { title: 'siteSetting', href: `waibuAdmin:/${prefix}/site-setting/list` },
227
- { title: 'resetUserPassword', href: `waibuAdmin:/${prefix}/reset-user-password` }
239
+ { title: 'manageDownload', href: `waibuAdmin:/${prefix}/download/list` }
228
240
  ]
229
241
  }, {
230
242
  title: 'manageAllSite',
@@ -237,10 +249,14 @@ async function factory (pkgName) {
237
249
  title: 'misc',
238
250
  interSite: true,
239
251
  children: [
240
- { title: 'userSession', href: `waibuAdmin:/${prefix}/_is_/session/list` },
241
- { title: 'cacheStorage', href: `waibuAdmin:/${prefix}/_is_/cache/list` }
252
+ { title: 'userSession', href: `waibuAdmin:/${prefix}/_is_/session/list` }
242
253
  ]
243
254
  }]
255
+ if (this.app.bajoCache) {
256
+ const item = items.find(i => i.title === 'misc')
257
+ if (item) item.children.push({ title: 'cacheStorage', href: `waibuAdmin:/${prefix}/_is_/cache/list` })
258
+ }
259
+ return items
244
260
  }
245
261
 
246
262
  createJwtFromUserRecord = async (rec) => {
package/lib/util.js CHANGED
@@ -178,8 +178,8 @@ export async function checkUserId (req, reply, source) {
178
178
  export async function latLngHook (body, options) {
179
179
  const { isSet } = this.app.lib.aneka
180
180
  const { round } = this.app.lib.aneka
181
- if (!isSet(body[options.fieldName])) return
182
- body[options.fieldName] = round(body[options.fieldName], options.scale)
181
+ if (!isSet(body[options.field])) return
182
+ body[options.field] = round(body[options.field], options.scale)
183
183
  }
184
184
 
185
185
  export async function checkinterSite (req, reply) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sumba",
3
- "version": "2.15.1",
3
+ "version": "2.17.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,15 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-04-07
4
+
5
+ - [2.17.0] Change all ```opts.fieldName``` to ```opts.field``` in features
6
+ - [2.17.0] Rewrite necessary changes on model reference to match the new architecture
7
+
8
+ ## 2026-04-02
9
+
10
+ - [2.16.0] Add ```SumbaUserSetting``` model and necessary handlings
11
+ - [2.16.0] Reorganize admin menu
12
+
3
13
  ## 2026-03-30
4
14
 
5
15
  - [2.15.0] Add inter site module mechanism
@@ -1,60 +0,0 @@
1
- {
2
- "common": {
3
- "layout": [
4
- { "name": "meta", "fields": ["id:3", "createdAt:3", "updatedAt:3", "status:3"] },
5
- { "name": "account", "fields": ["username", "email", "provider", "password", "firstName", "lastName"] },
6
- { "name": "address", "fields": ["address1:12", "address2:12", "city:6-md 8-sm", "zipCode:2-md 4-sm", "provinceState:4-md", "country:6-md", "phone:6-md", "website:12"] },
7
- { "name": "socialMedia", "fields": ["socX:3-md 6-sm", "socInstagram:3-md 6-sm", "socFacebook:3-md 6-sm", "socLinkedIn:3-md 6-sm"] }
8
- ],
9
- "widget": {
10
- "country": {
11
- "component": "form-select-ext"
12
- }
13
- }
14
- },
15
- "view": {
16
- "list": {
17
- "qs": {
18
- "sort": "username:1",
19
- "limit": 10
20
- },
21
- "fields": ["createdAt", "status", "username", "provider", "email", "firstName", "lastName", "city", "zipCode", "provinceState", "country", "phone"],
22
- "stat": {
23
- "aggregate": [
24
- { "fields": ["status"], "group": "status", "aggregate": ["count"] },
25
- { "fields": ["provider"], "group": "provider", "aggregate": ["count"] },
26
- { "fields": ["country"], "group": "country", "aggregate": ["count"] }
27
- ]
28
- }
29
- },
30
- "details": {
31
- "forceVisible": ["password"],
32
- "widget": {
33
- "password": {
34
- "component": "form-plaintext",
35
- "attr": {
36
- "href": "waibuAdmin:/site/reset-user-password?username={username}",
37
- "value": "resetPassword"
38
- }
39
- }
40
- }
41
- },
42
- "add": {
43
- "forceVisible": ["password"],
44
- "hidden": ["id", "createdAt", "updatedAt", "provider"]
45
- },
46
- "edit": {
47
- "forceVisible": ["password"],
48
- "widget": {
49
- "password": {
50
- "component": "form-plaintext",
51
- "attr": {
52
- "href": "waibuAdmin:/site/reset-user-password?username={username}",
53
- "value": "resetPassword"
54
- }
55
- }
56
- },
57
- "readonly": ["id", "createdAt", "updatedAt", "username", "provider"]
58
- }
59
- }
60
- }