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.
- package/extend/bajo/intl/en-US.json +13 -5
- package/extend/bajo/intl/id.json +13 -5
- package/extend/dobo/feature/country.js +3 -3
- package/extend/dobo/feature/email.js +2 -2
- package/extend/dobo/feature/lat-lng.js +8 -8
- package/extend/dobo/feature/lat.js +2 -2
- package/extend/dobo/feature/lng.js +2 -2
- package/extend/dobo/feature/phone.js +2 -2
- package/extend/dobo/feature/site-id.js +1 -1
- package/extend/dobo/feature/slug.js +14 -14
- package/extend/dobo/feature/status.js +7 -4
- package/extend/dobo/feature/team-id.js +4 -2
- package/extend/dobo/feature/ts.js +2 -2
- package/extend/dobo/feature/url.js +4 -4
- package/extend/dobo/feature/user-id.js +3 -1
- package/extend/dobo/model/ticket.json +14 -1
- package/extend/dobo/model/user-setting.json +17 -0
- package/extend/waibuDb/schema/site.js +1 -6
- package/extend/waibuDb/schema/team-user.js +3 -25
- package/extend/waibuDb/schema/ticket.json +16 -0
- package/extend/waibuDb/schema/user-setting.js +51 -0
- package/extend/waibuDb/schema/user.js +73 -0
- package/extend/waibuMpa/extend/waibuAdmin/route/site.js +1 -1
- package/extend/waibuMpa/extend/waibuAdmin/route/team/@action.js +1 -1
- package/extend/waibuMpa/extend/waibuAdmin/route/team-setting/@action.js +1 -1
- package/extend/waibuMpa/extend/waibuAdmin/route/team-user/@action.js +1 -1
- package/extend/waibuMpa/extend/waibuAdmin/route/user/@action.js +3 -2
- package/extend/waibuMpa/extend/waibuAdmin/route/user-setting/@action.js +11 -0
- package/extend/waibuMpa/route/your-stuff/profile/edit.js +1 -1
- package/extend/waibuMpa/route/your-stuff/profile.js +1 -1
- package/extend/waibuRestApi/route/manage/ticket-cat/model-builder.json +4 -0
- package/index.js +29 -13
- package/lib/util.js +2 -2
- package/package.json +1 -1
- package/wiki/CHANGES.md +10 -0
- 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
|
-
"
|
|
98
|
+
"team": "Team",
|
|
99
99
|
"manageSite": "Manage Site",
|
|
100
|
-
"manageUser": "Manage User",
|
|
101
100
|
"manageTeam": "Manage Team",
|
|
102
|
-
"
|
|
103
|
-
"
|
|
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",
|
package/extend/bajo/intl/id.json
CHANGED
|
@@ -96,12 +96,17 @@
|
|
|
96
96
|
"ticket": "Tiket",
|
|
97
97
|
"ticketCat": "Kategori Tiket",
|
|
98
98
|
"supportSystem": "Sistim Dukungan",
|
|
99
|
-
"
|
|
99
|
+
"team": "Tim",
|
|
100
100
|
"manageSite": "Kelola Situs",
|
|
101
|
-
"manageUser": "Kelola Pengguna",
|
|
102
101
|
"manageTeam": "Kelola Tim",
|
|
103
|
-
"
|
|
104
|
-
"
|
|
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.
|
|
2
|
+
opts.field = opts.field ?? 'country'
|
|
3
3
|
return {
|
|
4
4
|
properties: [{
|
|
5
|
-
name: opts.
|
|
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.
|
|
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.
|
|
2
|
+
opts.field = opts.field ?? 'email'
|
|
3
3
|
return {
|
|
4
4
|
properties: [{
|
|
5
|
-
name: opts.
|
|
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.
|
|
6
|
-
opts.
|
|
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.
|
|
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.
|
|
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.
|
|
28
|
-
await latLngHook.call(this, body, merge({}, opts, { lng: opts.
|
|
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.
|
|
32
|
-
await latLngHook.call(this, body, merge({}, opts, { lng: opts.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
9
|
+
name: opts.field,
|
|
10
10
|
type: 'double',
|
|
11
11
|
required: opts.required ?? true,
|
|
12
12
|
index: opts.required ?? true,
|
|
@@ -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.
|
|
6
|
-
const sort = set({}, opts.
|
|
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.
|
|
10
|
-
const rslugs = resp[opts.
|
|
11
|
-
const slugs = body[opts.
|
|
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.
|
|
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.
|
|
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.
|
|
19
|
+
if (!num) body[opts.field] += '-1'
|
|
20
20
|
else {
|
|
21
21
|
slugs[idx] = num + 1
|
|
22
|
-
body[opts.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
53
|
+
body[opts.field] = slug(source.join(' '))
|
|
54
54
|
}
|
|
55
|
-
if (opts.autoInc) 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.
|
|
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.
|
|
7
|
+
name: opts.field ?? 'status',
|
|
6
8
|
type: 'string',
|
|
7
9
|
maxLength: 50,
|
|
8
10
|
index: true,
|
|
9
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
2
|
+
opts.field = opts.field ?? 'ts'
|
|
3
3
|
return {
|
|
4
4
|
properties: [{
|
|
5
|
-
name: opts.
|
|
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.
|
|
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.
|
|
7
|
+
body[options.field] = val
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
async function url (opts = {}) {
|
|
11
|
-
opts.
|
|
11
|
+
opts.field = opts.field ?? 'url'
|
|
12
12
|
opts.defProto = opts.defProto ?? 'http'
|
|
13
13
|
return {
|
|
14
14
|
properties: [{
|
|
15
|
-
name: opts.
|
|
15
|
+
name: opts.field ?? 'url',
|
|
16
16
|
type: 'string'
|
|
17
17
|
}],
|
|
18
18
|
hook: {
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"properties": [
|
|
3
3
|
"subject,,255,true,true",
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: ['
|
|
23
|
+
fields: ['userId', 'teamId', 'createdAt', 'updatedAt'],
|
|
46
24
|
stat: {
|
|
47
25
|
aggregate: [
|
|
48
26
|
{ fields: ['userId'], group: 'userId', aggregate: ['count'] },
|
|
@@ -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,10 +1,11 @@
|
|
|
1
1
|
const action = {
|
|
2
2
|
method: ['GET', 'POST'],
|
|
3
|
-
title: '
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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)
|
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
|
-
|
|
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: '
|
|
237
|
+
title: 'misc',
|
|
218
238
|
children: [
|
|
219
|
-
{ title: '
|
|
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.
|
|
182
|
-
body[options.
|
|
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
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
|
-
}
|