sumba 2.25.0 → 2.26.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/hook.js +430 -0
- package/extend/dobo/fixture/team-user.json +2 -2
- package/extend/dobo/model.js +215 -0
- package/extend/dobo/model.json +214 -0
- package/index.js +11 -7
- package/lib/get-user.js +1 -1
- package/package.json +1 -1
- package/wiki/CHANGES.md +9 -0
- package/extend/bajo/hook/bajo.extend@after-read-config.js +0 -13
- package/extend/bajo/hook/dobo.sumba-attrib-guard$dobo.sumba-x-attrib-guard@after-transaction.js +0 -6
- package/extend/bajo/hook/dobo.sumba-contact-form@after-create-record.js +0 -17
- package/extend/bajo/hook/dobo.sumba-contact-form@before-create-record.js +0 -16
- package/extend/bajo/hook/dobo.sumba-model-guard$dobo.sumba-x-model-guard@after-transaction.js +0 -6
- package/extend/bajo/hook/dobo.sumba-route-guard$dobo.sumba-x-route-guard@after-transaction.js +0 -6
- package/extend/bajo/hook/dobo.sumba-site@after-create-record.js +0 -8
- package/extend/bajo/hook/dobo.sumba-site@after-remove-record.js +0 -9
- package/extend/bajo/hook/dobo.sumba-site@after-update-record.js +0 -14
- package/extend/bajo/hook/dobo.sumba-team$dobo.sumba-site@after-transaction.js +0 -8
- package/extend/bajo/hook/dobo.sumba-user@after-create-record.js +0 -15
- package/extend/bajo/hook/dobo.sumba-user@after-record-validation.js +0 -8
- package/extend/bajo/hook/dobo.sumba-user@after-remove-record.js +0 -7
- package/extend/bajo/hook/dobo.sumba-user@after-update-record.js +0 -44
- package/extend/bajo/hook/dobo.sumba-user@before-create-record.js +0 -7
- package/extend/bajo/hook/dobo.sumba-user@before-record-validation.js +0 -8
- package/extend/bajo/hook/dobo.sumba-user@before-update-record.js +0 -9
- package/extend/bajo/hook/dobo@before-count-record.js +0 -8
- package/extend/bajo/hook/dobo@before-driver-create-record.js +0 -17
- package/extend/bajo/hook/dobo@before-driver-find-all-record.js +0 -13
- package/extend/bajo/hook/dobo@before-driver-find-record.js +0 -114
- package/extend/bajo/hook/dobo@before-driver-get-record.js +0 -22
- package/extend/bajo/hook/dobo@before-driver-remove-record.js +0 -10
- package/extend/bajo/hook/dobo@before-driver-update-record.js +0 -10
- package/extend/bajo/hook/waibu-mpa.sumba@after-build-locals.js +0 -19
- package/extend/bajo/hook/waibu-mpa@pre-parsing.js +0 -15
- package/extend/bajo/hook/waibu-rest-api@pre-parsing.js +0 -13
- package/extend/bajo/hook/waibu-static@pre-parsing.js +0 -13
- package/extend/bajo/hook/waibu@after-app-boot.js +0 -7
- package/extend/bajo/hook/waibu@after-create-context.js +0 -5
- package/extend/bajo/hook/waibu@pre-parsing.js +0 -9
- package/extend/dobo/model/attrib-guard.js +0 -32
- package/extend/dobo/model/contact-form-cat.json +0 -3
- package/extend/dobo/model/contact-form.json +0 -16
- package/extend/dobo/model/download.json +0 -19
- package/extend/dobo/model/model-guard.js +0 -48
- package/extend/dobo/model/route-guard.js +0 -48
- package/extend/dobo/model/site-setting.json +0 -16
- package/extend/dobo/model/site.json +0 -45
- package/extend/dobo/model/team-setting.json +0 -17
- package/extend/dobo/model/team-user.json +0 -16
- package/extend/dobo/model/team.json +0 -23
- package/extend/dobo/model/ticket-cat.json +0 -3
- package/extend/dobo/model/ticket-detail.json +0 -7
- package/extend/dobo/model/ticket.json +0 -31
- package/extend/dobo/model/user-setting.json +0 -17
- package/extend/dobo/model/user.js +0 -84
- package/extend/dobo/model/x-attrib-guard.js +0 -14
- package/extend/dobo/model/x-model-guard.js +0 -13
- package/extend/dobo/model/x-route-guard.js +0 -13
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { checkUserId, checkTeam, checkXSite, checkTheme, checkIconset } from '../../lib/util.js'
|
|
2
|
+
import { removeRefs } from '../../lib/remove-site.js'
|
|
3
|
+
import { getAllFixtures, createRefs } from '../../lib/create-new-site.js'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
|
|
6
|
+
async function clearCacheSite (id, result) {
|
|
7
|
+
if (!this.app.bajoCache) return
|
|
8
|
+
const { clear } = this.app.bajoCache ?? {}
|
|
9
|
+
const { get } = this.app.lib._
|
|
10
|
+
await clear({ key: 'dobo|SumbaSite|getSite|default' })
|
|
11
|
+
await clear({ key: `dobo|SumbaSite|getSite|multiSite|${id}` })
|
|
12
|
+
await clear({ key: `dobo|SumbaSite|getSite|multiSite|${get(result, 'data.hostname', get(result, 'oldData.hostname'))}` })
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function clearCacheUser (id, result) {
|
|
16
|
+
if (!this.app.bajoCache) return
|
|
17
|
+
const { clear } = this.app.bajoCache ?? {}
|
|
18
|
+
const { get } = this.app.lib._
|
|
19
|
+
const token = get(result, 'data.token', get(result, 'oldData.token', ''))
|
|
20
|
+
await clear({ key: `dobo|SumbaUser|getUserById|${id}` })
|
|
21
|
+
await clear({ key: `dobo|SumbaUser|getUserByToken|${token}` })
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function applyModelGuard ({ model, q, teamIds, options }) {
|
|
25
|
+
const { get, set, orderBy, intersection, without } = this.app.lib._
|
|
26
|
+
const { include } = this.app.lib.aneka
|
|
27
|
+
const { req } = options
|
|
28
|
+
const results = []
|
|
29
|
+
|
|
30
|
+
const guards = await this.getModelGuards()
|
|
31
|
+
const columns = model.getNonVirtualProperties().map(prop => prop.name)
|
|
32
|
+
const filterFn = item => {
|
|
33
|
+
const bySiteId = item.siteIds.includes(req.site.id + '')
|
|
34
|
+
const byModel = item.models.includes(model.name)
|
|
35
|
+
const byTeamId = item.teamIds.length === 0 || include(item.teamIds, teamIds)
|
|
36
|
+
const byColumn = columns.includes(item.column)
|
|
37
|
+
return bySiteId && byModel && byTeamId && byColumn
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
guards.global = orderBy(guards.global.filter(filterFn), ['column'])
|
|
41
|
+
guards.local = orderBy(guards.local.filter(filterFn), ['column'])
|
|
42
|
+
for (const col of columns) {
|
|
43
|
+
let values = []
|
|
44
|
+
let items = guards.global.filter(item => item.column === col && !item.negation)
|
|
45
|
+
for (const item of items) {
|
|
46
|
+
values.push(...item.value)
|
|
47
|
+
}
|
|
48
|
+
items = guards.global.filter(item => item.column === col && item.negation)
|
|
49
|
+
for (const item of items) {
|
|
50
|
+
values = without(values, ...item.value)
|
|
51
|
+
}
|
|
52
|
+
items = guards.local.filter(item => item.column === col && !item.negation)
|
|
53
|
+
const newValues = []
|
|
54
|
+
for (const item of items) {
|
|
55
|
+
newValues.push(...item.value)
|
|
56
|
+
}
|
|
57
|
+
if (newValues.length > 0) values = intersection(values, newValues)
|
|
58
|
+
items = guards.local.filter(item => item.column === col && item.negation)
|
|
59
|
+
for (const item of items) {
|
|
60
|
+
values = without(values, ...item.value)
|
|
61
|
+
}
|
|
62
|
+
if (values.length) results.push(set({}, col, { $in: values }))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const allowEmpty = get(this, `config.dobo.model.${model.name}.allowEmptyQuery`, true)
|
|
66
|
+
if (results.length === 0 && !allowEmpty) throw this.error('_emptyColumnQuery') // signal driver to about with no result immediately
|
|
67
|
+
q.$and.push(...results)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function applyAttribGuard ({ model, teamIds, options }) {
|
|
71
|
+
const { uniq } = this.app.lib._
|
|
72
|
+
const { include } = this.app.lib.aneka
|
|
73
|
+
const { req } = options
|
|
74
|
+
const results = []
|
|
75
|
+
|
|
76
|
+
const guards = await this.getAttribGuards()
|
|
77
|
+
const filterFn = item => {
|
|
78
|
+
const bySiteId = item.siteIds.includes(req.site.id + '')
|
|
79
|
+
const byModel = item.models.includes(model.name)
|
|
80
|
+
const byTeamId = item.teamIds.length === 0 || include(item.teamIds, teamIds)
|
|
81
|
+
return bySiteId && byModel && byTeamId
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let item = guards.global.filter(filterFn)[0]
|
|
85
|
+
if (item) results.push(...item.hiddenCols)
|
|
86
|
+
item = guards.local.filter(filterFn)[0]
|
|
87
|
+
if (item) results.push(...item.hiddenCols)
|
|
88
|
+
options.hidden = options.hidden ?? []
|
|
89
|
+
if (results.length > 0) options.hidden.push(...results)
|
|
90
|
+
options.hidden = uniq(options.hidden)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function rebuildFilter (model, filter = {}, options = {}) {
|
|
94
|
+
const { isEmpty, get } = this.app.lib._
|
|
95
|
+
const { req } = options
|
|
96
|
+
const hasSiteId = model.hasProperty('siteId')
|
|
97
|
+
const hasUserId = model.hasProperty('userId')
|
|
98
|
+
const hasTeamId = model.hasProperty('teamId')
|
|
99
|
+
const teams = get(req, 'user.teams', [])
|
|
100
|
+
const teamIds = teams.map(team => team.id + '')
|
|
101
|
+
const aliases = teams.map(team => team.alias)
|
|
102
|
+
const q = { $and: [] }
|
|
103
|
+
|
|
104
|
+
filter.query = filter.query ?? {}
|
|
105
|
+
if (!isEmpty(filter.query)) {
|
|
106
|
+
if (filter.query.$and) q.$and.push(...filter.query.$and)
|
|
107
|
+
else q.$and.push(filter.query)
|
|
108
|
+
}
|
|
109
|
+
if (req.routeOptions.config.xSite) return
|
|
110
|
+
if (hasSiteId) q.$and.push({ siteId: req.site.id + '' })
|
|
111
|
+
if (aliases.includes('administrator')) {
|
|
112
|
+
filter.query = q
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
if (!req.user) {
|
|
116
|
+
filter.query = q
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const condUserId = {
|
|
121
|
+
$or: [
|
|
122
|
+
{ userId: req.user.id + '' },
|
|
123
|
+
{ userId: { $eq: null } }
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const condTeamId = {
|
|
128
|
+
$or: [
|
|
129
|
+
{ teamId: { $in: teamIds } },
|
|
130
|
+
{ teamId: { $eq: null } }
|
|
131
|
+
]
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (hasTeamId) {
|
|
135
|
+
if (hasUserId) q.$and.push({ $or: [condTeamId, condUserId] })
|
|
136
|
+
else q.$and.push(condTeamId)
|
|
137
|
+
} else if (hasUserId) q.$and.push(condUserId)
|
|
138
|
+
|
|
139
|
+
await applyModelGuard.call(this, { model, q, teamIds, options })
|
|
140
|
+
await applyAttribGuard.call(this, { model, teamIds, options })
|
|
141
|
+
filter.query = q
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function checker (model, id, options = {}) {
|
|
145
|
+
const { isEmpty } = this.app.lib._
|
|
146
|
+
const { req = {} } = options
|
|
147
|
+
if (options.noAutoFilter || isEmpty(req) || isEmpty(req.site)) return
|
|
148
|
+
|
|
149
|
+
const filter = {}
|
|
150
|
+
await rebuildFilter.call(this, model, filter, options)
|
|
151
|
+
if (filter.query.$and) filter.query.$and.push({ id })
|
|
152
|
+
else filter.query.id = id
|
|
153
|
+
const row = await model.findOneRecord(filter, { count: false })
|
|
154
|
+
if (!row) throw this.app.dobo.error('recordNotFound%s%s', id, this.name, { statusCode: 404 })
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function hook () {
|
|
158
|
+
return [{
|
|
159
|
+
name: 'bajo.extend:afterReadConfig',
|
|
160
|
+
handler: async function afterReadConfig (file, result, options) {
|
|
161
|
+
const base = path.basename(file, path.extname(file))
|
|
162
|
+
// rewrite fixtures
|
|
163
|
+
if (!(base === 'route-guard' && Array.isArray(result) && file.includes('/fixture/'))) return
|
|
164
|
+
for (const res of result) {
|
|
165
|
+
if (res.path.slice(0, 2) === ':/') res.path = options.sourceNs + res.path
|
|
166
|
+
res.path = res.path.replaceAll('{ns}', options.sourceNs)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}, {
|
|
170
|
+
name: ['dobo.sumbaAttribGuard:afterTransaction', 'dobo.sumbaXAttribGuard:afterTransaction'],
|
|
171
|
+
handler: async function (action, ...args) {
|
|
172
|
+
if (!['createRecord', 'updateRecord', 'removeRecord'].includes(action)) return
|
|
173
|
+
await this.getAttribGuards(true)
|
|
174
|
+
}
|
|
175
|
+
}, {
|
|
176
|
+
name: 'dobo.sumbaContactForm:afterCreateRecord',
|
|
177
|
+
handler: async function afterCreateRecord (body, rec, options = {}) {
|
|
178
|
+
const { data } = rec
|
|
179
|
+
const { req } = options
|
|
180
|
+
const { get } = this.app.lib._
|
|
181
|
+
const t = get(req, 't', this.t)
|
|
182
|
+
const to = `${data.firstName} ${data.lastName} <${data.email}>`
|
|
183
|
+
let bcc
|
|
184
|
+
if (req.site) bcc = req.site.email
|
|
185
|
+
const subject = t('contactForm')
|
|
186
|
+
const payload = { to, bcc, subject, data }
|
|
187
|
+
await this.sendMail(
|
|
188
|
+
'sumba.template:/_mail/help-contact-form.html',
|
|
189
|
+
{ payload, options, source: this.ns }
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
}, {
|
|
193
|
+
level: 1000,
|
|
194
|
+
name: 'dobo.sumbaContactForm:beforeCreateRecord',
|
|
195
|
+
handler: async function (body, options = {}) {
|
|
196
|
+
const { get, isEmpty } = this.app.lib._
|
|
197
|
+
const user = get(options, 'req.user')
|
|
198
|
+
if (user) {
|
|
199
|
+
if (isEmpty(body.firstName)) body.firstName = user.firstName
|
|
200
|
+
if (isEmpty(body.lastName)) body.lastName = user.firstName
|
|
201
|
+
if (isEmpty(body.email)) body.email = user.email
|
|
202
|
+
}
|
|
203
|
+
if (isEmpty(body.category)) body.category = 'MISC'
|
|
204
|
+
options.checksumId = true
|
|
205
|
+
}
|
|
206
|
+
}, {
|
|
207
|
+
name: ['dobo.sumbaModelGuard:afterTransaction', 'dobo.sumbaXModelGuard:afterTransaction'],
|
|
208
|
+
handler: async function (action, ...args) {
|
|
209
|
+
if (!['createRecord', 'updateRecord', 'removeRecord'].includes(action)) return
|
|
210
|
+
await this.getModelGuards(true)
|
|
211
|
+
}
|
|
212
|
+
}, {
|
|
213
|
+
name: ['dobo.sumbaRouteGuard:afterTransaction', 'dobo.sumbaXRouteGuard:afterTransaction'],
|
|
214
|
+
handler: async function (action, ...args) {
|
|
215
|
+
if (!['createRecord', 'updateRecord', 'removeRecord'].includes(action)) return
|
|
216
|
+
await this.getRouteGuards(true)
|
|
217
|
+
}
|
|
218
|
+
}, {
|
|
219
|
+
name: 'dobo.sumbaSite:afterCreateRecord',
|
|
220
|
+
handler: async function afterCreateRecord (body, result, options) {
|
|
221
|
+
const fixtures = await getAllFixtures.call(this, result.data.alias)
|
|
222
|
+
await createRefs.call(this, result.data, fixtures, options)
|
|
223
|
+
}
|
|
224
|
+
}, {
|
|
225
|
+
name: 'dobo.sumbaSite:afterRemoveRecord',
|
|
226
|
+
handler: async function afterRemoveRecord (id, result, options) {
|
|
227
|
+
await removeRefs.call(this, result.oldData, options)
|
|
228
|
+
await clearCacheSite.call(this, id, result)
|
|
229
|
+
}
|
|
230
|
+
}, {
|
|
231
|
+
name: 'dobo.sumbaSite:afterUpdateRecord',
|
|
232
|
+
handler: async function (id, input, result, opts) {
|
|
233
|
+
await clearCacheSite.call(this, id, result)
|
|
234
|
+
}
|
|
235
|
+
}, {
|
|
236
|
+
name: ['dobo.sumbaTeam:afterTransaction', 'dobo.sumbaSite:afterTransaction'],
|
|
237
|
+
handler: async function (action, ...args) {
|
|
238
|
+
if (!['createRecord', 'updateRecord', 'removeRecord'].includes(action)) return
|
|
239
|
+
await this.getRouteGuards(true)
|
|
240
|
+
await this.getModelGuards(true)
|
|
241
|
+
await this.getAttribGuards(true)
|
|
242
|
+
}
|
|
243
|
+
}, {
|
|
244
|
+
name: 'dobo.sumbaUser:afterCreateRecord',
|
|
245
|
+
handler: async function (body, rec, options = {}) {
|
|
246
|
+
const { data } = rec
|
|
247
|
+
const { req } = options
|
|
248
|
+
const { get } = this.app.lib._
|
|
249
|
+
const t = get(req, 't', this.t)
|
|
250
|
+
const to = `${data.firstName} ${data.lastName} <${data.email}>`
|
|
251
|
+
const subject = t('newUserSignup')
|
|
252
|
+
const payload = { to, subject, data }
|
|
253
|
+
await this.sendMail(
|
|
254
|
+
`sumba.template:/_mail/user-signup-success${data.status === 'ACTIVE' ? '-active' : ''}.html`,
|
|
255
|
+
{ payload, options, source: this.ns }
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
}, {
|
|
259
|
+
name: 'dobo.sumbaUser:afterRecordValidation',
|
|
260
|
+
handler: async function (body, options) {
|
|
261
|
+
const { isBcrypt, hash } = this.app.bajoExtra
|
|
262
|
+
const { has } = this.app.lib._
|
|
263
|
+
|
|
264
|
+
if (has(body, 'password') && !isBcrypt(body.password)) body.password = await hash(body.password, 'bcrypt')
|
|
265
|
+
}
|
|
266
|
+
}, {
|
|
267
|
+
name: 'dobo.sumbaUser:afterRemoveRecord',
|
|
268
|
+
handler: async function (id, rec, options = {}) {
|
|
269
|
+
await clearCacheUser.call(this, id, rec)
|
|
270
|
+
}
|
|
271
|
+
}, {
|
|
272
|
+
name: 'dobo.sumbaUser:afterUpdateRecord',
|
|
273
|
+
handler: async function (id, body, rec, options = {}) {
|
|
274
|
+
const { data, oldData } = rec
|
|
275
|
+
const { req } = options
|
|
276
|
+
const { get } = this.app.lib._
|
|
277
|
+
const t = get(req, 't', this.t)
|
|
278
|
+
const to = `${data.firstName} ${data.lastName} <${data.email}>`
|
|
279
|
+
const source = this.ns
|
|
280
|
+
|
|
281
|
+
await clearCacheUser.call(this, id, rec)
|
|
282
|
+
|
|
283
|
+
let subject
|
|
284
|
+
const payload = { to, subject, data }
|
|
285
|
+
|
|
286
|
+
if (oldData.status === 'UNVERIFIED' && data.status === 'ACTIVE') {
|
|
287
|
+
payload.subject = t('userActivation')
|
|
288
|
+
await this.sendMail(
|
|
289
|
+
'sumba.template:/_mail/user-activation-success.html',
|
|
290
|
+
{ payload, options, source }
|
|
291
|
+
)
|
|
292
|
+
} else if (oldData.token !== data.token) {
|
|
293
|
+
payload.subject = t('resetApiKey')
|
|
294
|
+
await this.sendMail(
|
|
295
|
+
'sumba.template:/_mail/mystuff-reset-api-key.html',
|
|
296
|
+
{ payload, options, source }
|
|
297
|
+
)
|
|
298
|
+
} else if (body.password) {
|
|
299
|
+
payload.subject = t('changePassword')
|
|
300
|
+
await this.sendMail(
|
|
301
|
+
'sumba.template:/_mail/mystuff-change-password.html',
|
|
302
|
+
{ payload, options, source }
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}, {
|
|
307
|
+
name: 'dobo.sumbaUser:beforeCreateRecord',
|
|
308
|
+
handler: async function (body, options = {}) {
|
|
309
|
+
const { token, salt } = await this.resetToken()
|
|
310
|
+
body.token = token
|
|
311
|
+
body.salt = salt
|
|
312
|
+
}
|
|
313
|
+
}, {
|
|
314
|
+
name: 'dobo.sumbaUser:beforeRecordValidation',
|
|
315
|
+
handler: async function (body, options = {}) {
|
|
316
|
+
const { set } = this.app.lib._
|
|
317
|
+
const password = await this.passwordRule(options.req)
|
|
318
|
+
const rule = { password }
|
|
319
|
+
set(options, 'validation.params.rule', rule)
|
|
320
|
+
}
|
|
321
|
+
}, {
|
|
322
|
+
name: 'dobo.sumbaUser:beforeUpdateRecord',
|
|
323
|
+
handler: async function (id, body, options = {}) {
|
|
324
|
+
if (body.salt) {
|
|
325
|
+
const { token, salt } = await this.resetToken(body.salt)
|
|
326
|
+
body.token = token
|
|
327
|
+
body.salt = salt
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}, {
|
|
331
|
+
level: 1000,
|
|
332
|
+
name: 'dobo.driver:beforeCreateRecord',
|
|
333
|
+
handler: async function (model, body, options = {}) {
|
|
334
|
+
const { get } = this.app.lib._
|
|
335
|
+
const { isSet } = this.app.lib.aneka
|
|
336
|
+
const { req } = options
|
|
337
|
+
if (options.noAutoFilter || !req || get(req, 'routeOptions.config.xSite')) return
|
|
338
|
+
const item = { siteId: 'site.id', userId: 'user.id' }
|
|
339
|
+
for (const i in item) {
|
|
340
|
+
const rec = get(req, item[i])
|
|
341
|
+
const field = model.getProperty(i)
|
|
342
|
+
if (rec && field && !isSet(body[i])) body[i] = field.type === 'string' ? (rec + '') : rec
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}, {
|
|
346
|
+
level: 1000,
|
|
347
|
+
name: ['dobo.driver:beforeFindRecord', 'dobo.driver:beforeFindAllRecord', 'dobo.driver:beforeCountRecord'],
|
|
348
|
+
handler: async function handler (model, filter, options = {}) {
|
|
349
|
+
const { isEmpty } = this.app.lib._
|
|
350
|
+
const { req = {} } = options
|
|
351
|
+
if (options.noAutoFilter || isEmpty(req) || isEmpty(req.site)) return
|
|
352
|
+
await rebuildFilter.call(this, model, filter, options)
|
|
353
|
+
}
|
|
354
|
+
}, {
|
|
355
|
+
level: 1000,
|
|
356
|
+
name: ['dobo.driver:beforeGetRecord', 'dobo.driver:beforeRemoveRecord', 'dobo.driver:beforeUpdateRecord'],
|
|
357
|
+
handler: async function (model, id, options) {
|
|
358
|
+
await checker.call(this, model, id, options)
|
|
359
|
+
}
|
|
360
|
+
}, {
|
|
361
|
+
name: 'waibuMpa.sumba:afterBuildLocals',
|
|
362
|
+
handler: async function (locals, req) {
|
|
363
|
+
const { routePath } = this.app.waibu
|
|
364
|
+
const items = []
|
|
365
|
+
if (req.user) {
|
|
366
|
+
items.push({ icon: 'person', 't:tooltip': 'yourProfile', href: routePath('sumba:/your-stuff/profile') })
|
|
367
|
+
items.push({ icon: 'key', 't:tooltip': 'changePassword', href: routePath('sumba:/your-stuff/change-password') })
|
|
368
|
+
items.push({ component: 'navItemSignout', 't:tooltip': 'signout', bottom: true })
|
|
369
|
+
} else {
|
|
370
|
+
items.push({ icon: 'signin', 't:tooltip': 'signin', href: routePath('sumba:/signin') })
|
|
371
|
+
items.push({ icon: 'key', 't:tooltip': 'forgotPassword', href: routePath('sumba:/user/forgot-password') })
|
|
372
|
+
items.push({ icon: 'personAdd', 't:tooltip': 'newUserSignup', href: routePath('sumba:/user/signup') })
|
|
373
|
+
}
|
|
374
|
+
items.push({ divider: true })
|
|
375
|
+
items.push({ icon: 'envelope', 't:tooltip': 'contactForm', href: routePath('sumba:/help/contact-form') })
|
|
376
|
+
items.push({ icon: 'chat', 't:tooltip': 'troubleTickets', href: routePath('sumba:/help/trouble-tickets') })
|
|
377
|
+
locals.sidebar = items
|
|
378
|
+
}
|
|
379
|
+
}, {
|
|
380
|
+
level: 10,
|
|
381
|
+
name: 'waibuMpa:preParsing',
|
|
382
|
+
handler: async function (req, reply) {
|
|
383
|
+
await checkTheme.call(this, req, reply)
|
|
384
|
+
await checkIconset.call(this, req, reply)
|
|
385
|
+
const secure = await checkUserId.call(this, req, reply, 'waibuMpa')
|
|
386
|
+
if (!secure) return
|
|
387
|
+
await checkTeam.call(this, req, reply, secure)
|
|
388
|
+
await checkXSite.call(this, req, reply)
|
|
389
|
+
}
|
|
390
|
+
}, {
|
|
391
|
+
level: 10,
|
|
392
|
+
name: 'waibuRestApi:preParsing',
|
|
393
|
+
handler: async function (req, reply) {
|
|
394
|
+
const secure = await checkUserId.call(this, req, reply, 'waibuRestApi')
|
|
395
|
+
if (!secure) return
|
|
396
|
+
await checkTeam.call(this, req, reply, secure)
|
|
397
|
+
await checkXSite.call(this, req, reply)
|
|
398
|
+
}
|
|
399
|
+
}, {
|
|
400
|
+
level: 10,
|
|
401
|
+
name: 'waibuStatic:preParsing',
|
|
402
|
+
handler: async function (req, reply) {
|
|
403
|
+
const secure = await checkUserId.call(this, req, reply, 'waibuStatic')
|
|
404
|
+
if (!secure) return
|
|
405
|
+
await checkTeam.call(this, req, reply, secure)
|
|
406
|
+
await checkXSite.call(this, req, reply)
|
|
407
|
+
}
|
|
408
|
+
}, {
|
|
409
|
+
name: 'waibu:afterAppBoot',
|
|
410
|
+
handler: async function () {
|
|
411
|
+
await this.getRouteGuards(true)
|
|
412
|
+
await this.getModelGuards(true)
|
|
413
|
+
await this.getAttribGuards(true)
|
|
414
|
+
}
|
|
415
|
+
}, {
|
|
416
|
+
name: 'waibu:afterCreateContext',
|
|
417
|
+
handler: async function (ctx) {
|
|
418
|
+
ctx.decorateRequest('user', null)
|
|
419
|
+
}
|
|
420
|
+
}, {
|
|
421
|
+
level: 10,
|
|
422
|
+
name: 'waibu:preParsing',
|
|
423
|
+
handler: async function (req, reply) {
|
|
424
|
+
const { getHostname } = this.app.waibu
|
|
425
|
+
req.site = await this.getSite(getHostname(req))
|
|
426
|
+
}
|
|
427
|
+
}]
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export default hook
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[{
|
|
2
|
-
"userId": "?:SumbaUser::username:admin",
|
|
3
|
-
"teamId": "?:SumbaTeam::alias:administrator",
|
|
4
2
|
"siteId": "?:SumbaSite::alias:default",
|
|
3
|
+
"userId": "?:SumbaUser::username:admin+siteId={siteId}",
|
|
4
|
+
"teamId": "?:SumbaTeam::alias:administrator+siteId={siteId}",
|
|
5
5
|
"_immutable": true
|
|
6
6
|
}]
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
const buildEnd = async function (model) {
|
|
2
|
+
const prop = model.properties.find(prop => prop.name === 'models')
|
|
3
|
+
if (prop) prop.values = this.getModelNames(true)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const mgProperties = [
|
|
7
|
+
{
|
|
8
|
+
name: 'models',
|
|
9
|
+
type: 'array',
|
|
10
|
+
required: true
|
|
11
|
+
},
|
|
12
|
+
'column,,50,true,true',
|
|
13
|
+
{
|
|
14
|
+
name: 'negation',
|
|
15
|
+
type: 'boolean',
|
|
16
|
+
required: true,
|
|
17
|
+
default: false
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'status',
|
|
21
|
+
type: 'sumba:status',
|
|
22
|
+
values: ['ACTIVE', 'INACTIVE'],
|
|
23
|
+
default: 'ACTIVE'
|
|
24
|
+
},
|
|
25
|
+
'value,array,,,true',
|
|
26
|
+
'siteId,sumba:siteId',
|
|
27
|
+
'teamIds,sumba:teamIds'
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
const options = {
|
|
31
|
+
attachment: false,
|
|
32
|
+
cache: { ttlDur: 0 }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const mgFeatures = [
|
|
36
|
+
'dobo:updatedAt'
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
const agProperties = [
|
|
40
|
+
{
|
|
41
|
+
name: 'models',
|
|
42
|
+
type: 'array',
|
|
43
|
+
required: true
|
|
44
|
+
},
|
|
45
|
+
'hiddenCols,array',
|
|
46
|
+
'siteId,sumba:siteId',
|
|
47
|
+
'teamIds,sumba:teamIds'
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
const agFeatures = [
|
|
51
|
+
{
|
|
52
|
+
name: 'sumba:status',
|
|
53
|
+
values: ['ACTIVE', 'INACTIVE'],
|
|
54
|
+
default: 'ACTIVE'
|
|
55
|
+
},
|
|
56
|
+
'dobo:updatedAt'
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
export const rgProperties = [
|
|
60
|
+
'path,,255,true,true',
|
|
61
|
+
{
|
|
62
|
+
name: 'methods',
|
|
63
|
+
type: 'array',
|
|
64
|
+
required: true,
|
|
65
|
+
default: ['GET', 'POST', 'UPDATE', 'DELETE'],
|
|
66
|
+
values: ['GET', 'POST', 'UPDATE', 'DELETE']
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'weight',
|
|
70
|
+
type: 'smallint',
|
|
71
|
+
default: 0
|
|
72
|
+
}, {
|
|
73
|
+
name: 'negation',
|
|
74
|
+
type: 'boolean',
|
|
75
|
+
required: true,
|
|
76
|
+
default: false
|
|
77
|
+
}, {
|
|
78
|
+
name: 'anonymous',
|
|
79
|
+
type: 'boolean',
|
|
80
|
+
required: true,
|
|
81
|
+
default: false
|
|
82
|
+
},
|
|
83
|
+
'siteId,sumba:siteId',
|
|
84
|
+
'teamIds,sumba:teamIds'
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
export const rgFeatures = [
|
|
88
|
+
{
|
|
89
|
+
name: 'sumba:status',
|
|
90
|
+
values: ['ACTIVE', 'INACTIVE'],
|
|
91
|
+
default: 'ACTIVE'
|
|
92
|
+
},
|
|
93
|
+
'dobo:updatedAt'
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
async function model () {
|
|
97
|
+
const { isString } = this.app.lib._
|
|
98
|
+
return [{
|
|
99
|
+
baseName: 'route-guard',
|
|
100
|
+
properties: rgProperties,
|
|
101
|
+
features: rgFeatures,
|
|
102
|
+
options
|
|
103
|
+
}, {
|
|
104
|
+
baseName: 'x-route-guard',
|
|
105
|
+
properties: rgProperties.filter(prop => !isString(prop) || (isString(prop) && !prop.startsWith('teamIds'))),
|
|
106
|
+
features: rgFeatures.filter(feat => !isString(feat) || (isString(feat) && !['sumba:siteId', 'dobo:updatedAt'].includes(feat))).concat('sumba:siteIds', 'dobo:updatedAt'),
|
|
107
|
+
options
|
|
108
|
+
}, {
|
|
109
|
+
baseName: 'attrib-guard',
|
|
110
|
+
properties: agProperties,
|
|
111
|
+
features: agFeatures,
|
|
112
|
+
options,
|
|
113
|
+
buildEnd
|
|
114
|
+
}, {
|
|
115
|
+
baseName: 'x-attrib-guard',
|
|
116
|
+
properties: agProperties.filter(prop => !isString(prop) || (isString(prop) && !prop.startsWith('teamIds'))),
|
|
117
|
+
features: agFeatures.filter(feat => !isString(feat) || (isString(feat) && !['sumba:siteId', 'dobo:updatedAt'].includes(feat))).concat('sumba:siteIds', 'dobo:updatedAt'),
|
|
118
|
+
options,
|
|
119
|
+
buildEnd
|
|
120
|
+
}, {
|
|
121
|
+
baseName: 'model-guard',
|
|
122
|
+
properties: mgProperties,
|
|
123
|
+
features: mgFeatures,
|
|
124
|
+
options,
|
|
125
|
+
buildEnd
|
|
126
|
+
}, {
|
|
127
|
+
baseName: 'x-model-guard',
|
|
128
|
+
properties: mgProperties.filter(prop => !isString(prop) || (isString(prop) && !prop.startsWith('teamIds'))),
|
|
129
|
+
features: mgFeatures.filter(feat => !isString(feat) || (isString(feat) && !['sumba:siteId', 'dobo:updatedAt'].includes(feat))).concat('sumba:siteIds', 'dobo:updatedAt'),
|
|
130
|
+
options,
|
|
131
|
+
buildEnd
|
|
132
|
+
}, {
|
|
133
|
+
buildLevel: 2,
|
|
134
|
+
baseName: 'user',
|
|
135
|
+
properties: [{
|
|
136
|
+
name: 'username',
|
|
137
|
+
type: 'string',
|
|
138
|
+
minLength: 5,
|
|
139
|
+
maxLength: 50,
|
|
140
|
+
rules: ['alphanum']
|
|
141
|
+
}, {
|
|
142
|
+
name: 'password',
|
|
143
|
+
type: 'string',
|
|
144
|
+
minLength: 8,
|
|
145
|
+
maxLength: 100
|
|
146
|
+
}, {
|
|
147
|
+
name: 'token',
|
|
148
|
+
type: 'string',
|
|
149
|
+
maxLength: 100,
|
|
150
|
+
index: true
|
|
151
|
+
}, {
|
|
152
|
+
name: 'salt',
|
|
153
|
+
type: 'string',
|
|
154
|
+
maxLength: 100,
|
|
155
|
+
required: true
|
|
156
|
+
}, {
|
|
157
|
+
name: 'apiKey',
|
|
158
|
+
type: 'string',
|
|
159
|
+
maxLength: 100,
|
|
160
|
+
virtual: true,
|
|
161
|
+
getValue: async function (val, rec) {
|
|
162
|
+
if (!rec.salt) return
|
|
163
|
+
return await this.plugin.hash(rec.salt)
|
|
164
|
+
}
|
|
165
|
+
}, {
|
|
166
|
+
name: 'provider',
|
|
167
|
+
type: 'string',
|
|
168
|
+
maxLength: 50,
|
|
169
|
+
index: true,
|
|
170
|
+
default: 'local'
|
|
171
|
+
}, {
|
|
172
|
+
name: 'email',
|
|
173
|
+
type: 'string',
|
|
174
|
+
maxLength: 100,
|
|
175
|
+
required: true,
|
|
176
|
+
rules: ['email']
|
|
177
|
+
}, {
|
|
178
|
+
name: 'firstName',
|
|
179
|
+
type: 'string',
|
|
180
|
+
maxLength: 50,
|
|
181
|
+
required: true,
|
|
182
|
+
index: true
|
|
183
|
+
}, {
|
|
184
|
+
name: 'lastName',
|
|
185
|
+
type: 'string',
|
|
186
|
+
maxLength: 50,
|
|
187
|
+
required: true,
|
|
188
|
+
index: true
|
|
189
|
+
}],
|
|
190
|
+
rules: [{ rule: 'trim', fields: ['username', 'firstName', 'lastName'] }],
|
|
191
|
+
indexes: [{
|
|
192
|
+
fields: ['username', 'siteId'],
|
|
193
|
+
type: 'unique'
|
|
194
|
+
}, {
|
|
195
|
+
fields: ['email', 'siteId'],
|
|
196
|
+
type: 'unique'
|
|
197
|
+
}],
|
|
198
|
+
hidden: ['password'],
|
|
199
|
+
features: [
|
|
200
|
+
'sumba:address',
|
|
201
|
+
'sumba:social',
|
|
202
|
+
{
|
|
203
|
+
name: 'sumba:status',
|
|
204
|
+
default: 'UNVERIFIED',
|
|
205
|
+
values: ['UNVERIFIED', 'ACTIVE', 'INACTIVE']
|
|
206
|
+
},
|
|
207
|
+
'sumba:siteId',
|
|
208
|
+
'dobo:createdAt',
|
|
209
|
+
'dobo:updatedAt',
|
|
210
|
+
'dobo:immutable'
|
|
211
|
+
]
|
|
212
|
+
}]
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export default model
|