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.
Files changed (58) hide show
  1. package/extend/bajo/hook.js +430 -0
  2. package/extend/dobo/fixture/team-user.json +2 -2
  3. package/extend/dobo/model.js +215 -0
  4. package/extend/dobo/model.json +214 -0
  5. package/index.js +11 -7
  6. package/lib/get-user.js +1 -1
  7. package/package.json +1 -1
  8. package/wiki/CHANGES.md +9 -0
  9. package/extend/bajo/hook/bajo.extend@after-read-config.js +0 -13
  10. package/extend/bajo/hook/dobo.sumba-attrib-guard$dobo.sumba-x-attrib-guard@after-transaction.js +0 -6
  11. package/extend/bajo/hook/dobo.sumba-contact-form@after-create-record.js +0 -17
  12. package/extend/bajo/hook/dobo.sumba-contact-form@before-create-record.js +0 -16
  13. package/extend/bajo/hook/dobo.sumba-model-guard$dobo.sumba-x-model-guard@after-transaction.js +0 -6
  14. package/extend/bajo/hook/dobo.sumba-route-guard$dobo.sumba-x-route-guard@after-transaction.js +0 -6
  15. package/extend/bajo/hook/dobo.sumba-site@after-create-record.js +0 -8
  16. package/extend/bajo/hook/dobo.sumba-site@after-remove-record.js +0 -9
  17. package/extend/bajo/hook/dobo.sumba-site@after-update-record.js +0 -14
  18. package/extend/bajo/hook/dobo.sumba-team$dobo.sumba-site@after-transaction.js +0 -8
  19. package/extend/bajo/hook/dobo.sumba-user@after-create-record.js +0 -15
  20. package/extend/bajo/hook/dobo.sumba-user@after-record-validation.js +0 -8
  21. package/extend/bajo/hook/dobo.sumba-user@after-remove-record.js +0 -7
  22. package/extend/bajo/hook/dobo.sumba-user@after-update-record.js +0 -44
  23. package/extend/bajo/hook/dobo.sumba-user@before-create-record.js +0 -7
  24. package/extend/bajo/hook/dobo.sumba-user@before-record-validation.js +0 -8
  25. package/extend/bajo/hook/dobo.sumba-user@before-update-record.js +0 -9
  26. package/extend/bajo/hook/dobo@before-count-record.js +0 -8
  27. package/extend/bajo/hook/dobo@before-driver-create-record.js +0 -17
  28. package/extend/bajo/hook/dobo@before-driver-find-all-record.js +0 -13
  29. package/extend/bajo/hook/dobo@before-driver-find-record.js +0 -114
  30. package/extend/bajo/hook/dobo@before-driver-get-record.js +0 -22
  31. package/extend/bajo/hook/dobo@before-driver-remove-record.js +0 -10
  32. package/extend/bajo/hook/dobo@before-driver-update-record.js +0 -10
  33. package/extend/bajo/hook/waibu-mpa.sumba@after-build-locals.js +0 -19
  34. package/extend/bajo/hook/waibu-mpa@pre-parsing.js +0 -15
  35. package/extend/bajo/hook/waibu-rest-api@pre-parsing.js +0 -13
  36. package/extend/bajo/hook/waibu-static@pre-parsing.js +0 -13
  37. package/extend/bajo/hook/waibu@after-app-boot.js +0 -7
  38. package/extend/bajo/hook/waibu@after-create-context.js +0 -5
  39. package/extend/bajo/hook/waibu@pre-parsing.js +0 -9
  40. package/extend/dobo/model/attrib-guard.js +0 -32
  41. package/extend/dobo/model/contact-form-cat.json +0 -3
  42. package/extend/dobo/model/contact-form.json +0 -16
  43. package/extend/dobo/model/download.json +0 -19
  44. package/extend/dobo/model/model-guard.js +0 -48
  45. package/extend/dobo/model/route-guard.js +0 -48
  46. package/extend/dobo/model/site-setting.json +0 -16
  47. package/extend/dobo/model/site.json +0 -45
  48. package/extend/dobo/model/team-setting.json +0 -17
  49. package/extend/dobo/model/team-user.json +0 -16
  50. package/extend/dobo/model/team.json +0 -23
  51. package/extend/dobo/model/ticket-cat.json +0 -3
  52. package/extend/dobo/model/ticket-detail.json +0 -7
  53. package/extend/dobo/model/ticket.json +0 -31
  54. package/extend/dobo/model/user-setting.json +0 -17
  55. package/extend/dobo/model/user.js +0 -84
  56. package/extend/dobo/model/x-attrib-guard.js +0 -14
  57. package/extend/dobo/model/x-model-guard.js +0 -13
  58. 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