sumba 2.7.3 → 2.8.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.
@@ -115,6 +115,13 @@
115
115
  "statusInactive": "Inactive",
116
116
  "siteSetting": "Site Settings",
117
117
  "validCountryCodeRequired": "Value must be one of valid country codes",
118
+ "aliasRequired": "Alias is required",
119
+ "aliasOrHOstnameExists%s%s": "A site with alias '%s' or hostname '%s' exists already",
120
+ "aliasNotFound%s": "Alias '%s' is nowhere to be found",
121
+ "writingModel%s%s": "Writing model '%s' (%s record(s))",
122
+ "aboutToDeleteSite%s": "You're about to delete a site with alias '%s'. Are you sure?",
123
+ "aboutToCreateSite%s": "You're about to create a site with alias '%s'. Continue?",
124
+ "removedFrom%s%s": "Removed from '%s' (%s record(s))",
118
125
  "field": {
119
126
  "currentPassword": "Current Password",
120
127
  "newPassword": "New Password",
@@ -116,6 +116,13 @@
116
116
  "statusInactive": "Non Aktif",
117
117
  "siteSetting": "Setelan Situs",
118
118
  "validCountryCodeRequired": "Nilai harus salah satu dari kode negara yang berlaku",
119
+ "aliasRequired": "Alias tidak boleh kosong",
120
+ "aliasOrHOstnameExists%s%s": "Situs dengan alias '%s' atau nama host '%s' telah ada",
121
+ "aliasNotFound%s": "Alias '%s' tidak ditemukan",
122
+ "writingModel%s%s": "Menulis model '%s' (%s data)",
123
+ "aboutToDeleteSite%s": "Anda akan menghapus sebuah situs dengan alias '%s'. Anda yakin?",
124
+ "aboutToCreateSite%s": "Anda akan membuat sebuah situs dengan alias '%s'. Lanjutkan?",
125
+ "removedFrom%s%s": "Dihapus dari '%s' (%s data)",
119
126
  "field": {
120
127
  "currentPassword": "Kata Sandi Saat Ini",
121
128
  "newPassword": "Kata Sandi Baru",
@@ -0,0 +1,21 @@
1
+ async function createSite (path, ...args) {
2
+ const { createNewSite } = this.app.sumba
3
+ const { importPkg } = this.app.bajo
4
+ const confirm = await importPkg('bajoCli:@inquirer/confirm')
5
+ const [alias, hostname] = args
6
+ if (!alias) this.print.fatal('aliasRequired')
7
+ const answer = await confirm({ message: this.print.buildText('aboutToCreateSite%s', alias), default: false })
8
+ if (!answer) {
9
+ this.print.fail('aborted')
10
+ this.app.exit()
11
+ }
12
+ await this.app.dobo.start()
13
+ try {
14
+ await createNewSite(alias, hostname, true)
15
+ } catch (err) {
16
+ this.print.fatal(err.message)
17
+ }
18
+ this.app.exit()
19
+ }
20
+
21
+ export default createSite
@@ -0,0 +1,20 @@
1
+ async function removeSite (path, ...args) {
2
+ const { importPkg } = this.app.bajo
3
+ const { removeSite } = this.app.sumba
4
+ const confirm = await importPkg('bajoCli:@inquirer/confirm')
5
+ const [alias] = args
6
+ const answer = await confirm({ message: this.print.buildText('aboutToDeleteSite%s', alias), default: false })
7
+ if (!answer) {
8
+ this.print.fail('aborted')
9
+ this.app.exit()
10
+ }
11
+ await this.app.dobo.start()
12
+ try {
13
+ await removeSite(alias, true)
14
+ } catch (err) {
15
+ this.print.fatal(err.message)
16
+ }
17
+ this.app.exit()
18
+ }
19
+
20
+ export default removeSite
@@ -0,0 +1 @@
1
+ export default 'default'
@@ -13,5 +13,6 @@
13
13
  "website": "http://www.your-domain.com",
14
14
  "phone": "+001-000000001",
15
15
  "waPhone": "+001-111111111",
16
- "status": "ACTIVE"
16
+ "status": "ACTIVE",
17
+ "_immutable": true
17
18
  }]
@@ -1,5 +1,6 @@
1
1
  [{
2
2
  "userId": "?:SumbaUser::username:admin",
3
3
  "teamId": "?:SumbaTeam::alias:administrator",
4
- "siteId": "?:SumbaSite::alias:default"
4
+ "siteId": "?:SumbaSite::alias:default",
5
+ "_immutable": true
5
6
  }]
@@ -12,5 +12,6 @@
12
12
  "country": "US",
13
13
  "provider": "local",
14
14
  "siteId": "?:SumbaSite::alias:default",
15
- "status": "ACTIVE"
15
+ "status": "ACTIVE",
16
+ "_immutable": true
16
17
  }]
@@ -10,7 +10,8 @@
10
10
  "name": "alias",
11
11
  "type": "string",
12
12
  "maxLength": 100,
13
- "index": "unique"
13
+ "index": "unique",
14
+ "immutable": true
14
15
  }, {
15
16
  "name": "title",
16
17
  "type": "string",
@@ -31,5 +32,5 @@
31
32
  "features": ["sumba:personInCharge", "sumba:address", "sumba:social", {
32
33
  "name": "sumba:status",
33
34
  "default": "ACTIVE"
34
- }, "dobo:createdAt", "dobo:updatedAt"]
35
+ }, "dobo:createdAt", "dobo:updatedAt", "dobo:immutable"]
35
36
  }
@@ -17,5 +17,11 @@
17
17
  "fields": ["userId", "siteId", "teamId"],
18
18
  "type": "unique"
19
19
  }],
20
- "features": ["dobo:createdAt", "dobo:updatedAt", "sumba:siteId", "sumba:userId"]
20
+ "features": [
21
+ "dobo:createdAt",
22
+ "dobo:updatedAt",
23
+ "sumba:siteId",
24
+ "sumba:userId",
25
+ "dobo:immutable"
26
+ ]
21
27
  }
@@ -7,9 +7,16 @@
7
7
  "fields": ["alias", "siteId"],
8
8
  "type": "unique"
9
9
  }],
10
- "features": ["dobo:createdAt", "dobo:updatedAt", "dobo:immutable", "sumba:siteId", {
11
- "name": "sumba:status",
12
- "default": "ENABLED",
13
- "values": ["ENABLED", "DISABLED"]
14
- }]
10
+ "features": [
11
+ "dobo:createdAt",
12
+ "dobo:updatedAt",
13
+ "dobo:immutable",
14
+ "sumba:siteId",
15
+ {
16
+ "name": "sumba:status",
17
+ "default": "ENABLED",
18
+ "values": ["ENABLED", "DISABLED"]
19
+ },
20
+ "dobo:immutable"
21
+ ]
15
22
  }
@@ -64,6 +64,7 @@
64
64
  },
65
65
  "sumba:siteId",
66
66
  "dobo:createdAt",
67
- "dobo:updatedAt"
67
+ "dobo:updatedAt",
68
+ "dobo:immutable"
68
69
  ]
69
70
  }
package/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import path from 'path'
2
+ import createNewSite from './lib/create-new-site.js'
3
+ import removeSite from './lib/remove-site.js'
2
4
 
3
5
  /**
4
6
  * Plugin factory
@@ -127,6 +129,7 @@ async function factory (pkgName) {
127
129
  }
128
130
  }
129
131
  this.unsafeUserFields = ['password']
132
+ this.selfBind(['createNewSite', 'removeSite'])
130
133
  }
131
134
 
132
135
  init = async () => {
@@ -574,6 +577,9 @@ async function factory (pkgName) {
574
577
  if (tpl[1]) payload.text = await render(tpl[1], locals, opts)
575
578
  await this.app.masohiMail.send({ payload, source: source ?? this.ns, conn })
576
579
  }
580
+
581
+ createNewSite = createNewSite
582
+ removeSite = removeSite
577
583
  }
578
584
 
579
585
  return Sumba
@@ -0,0 +1,83 @@
1
+ import path from 'path'
2
+
3
+ async function createNewSite (alias, hostname, verbose) {
4
+ const { getPluginDataDir, readConfig } = this.app.bajo
5
+ const { isEmpty, omit, kebabCase, isString } = this.app.lib._
6
+ const { getModel } = this.app.dobo
7
+ const { fastGlob } = this.app.lib
8
+
9
+ function replaceAlias (alias, items) {
10
+ for (const item of items) {
11
+ for (const key in item) {
12
+ const val = item[key]
13
+ if (isString(val)) item[key] = val.replaceAll('{alias}', alias)
14
+ }
15
+ }
16
+ }
17
+
18
+ if (isEmpty(alias)) throw this.error('aliasRequired%s', alias)
19
+ let spin
20
+ if (verbose) spin = this.print.spinner().start('processing...')
21
+ const formats = this.app.configHandlers.map(item => item.ext.slice(1))
22
+ const overrideBase = `${getPluginDataDir('sumba')}/create-new-site-fixtures/${alias}`
23
+ const models = this.app.dobo.models.filter(m => {
24
+ const prop = m.properties.find(p => p.name === 'siteId')
25
+ return !!prop
26
+ }).map(m => m.name)
27
+ models.unshift('SumbaSite')
28
+ const files = (await fastGlob(`${overrideBase}/*.{${formats.join(',')}}`)).map(file => {
29
+ const ext = path.extname(file)
30
+ return file.slice(0, file.length - ext.length)
31
+ })
32
+ const data = {}
33
+ for (const m of models) {
34
+ const model = getModel(m)
35
+ const file = `${overrideBase}/${kebabCase(m)}`
36
+ let fixtures = (await model.loadFixtures({ collectItems: true, noLookup: true })) ?? []
37
+ if (files.includes(file)) {
38
+ const items = await readConfig(file, { ignoreError: true, defValue: [] })
39
+ if (!isEmpty(items)) fixtures = items
40
+ }
41
+ if (!Array.isArray(fixtures)) fixtures = [fixtures]
42
+ if (fixtures.length === 0) continue
43
+ replaceAlias(alias, fixtures)
44
+ if (isEmpty(alias)) throw this.error('aliasRequired%s', alias)
45
+ const omitted = ['_attachments', 'siteId']
46
+ for (const item of ['createdAt', 'updatedAt', 'immutable']) {
47
+ const props = model.properties.filter(prop => prop.feature === ('dobo:' + item)).map(prop => prop.name)
48
+ if (props.length > 0) omitted.push(...props)
49
+ }
50
+ for (const idx in fixtures) {
51
+ fixtures[idx] = omit(fixtures[idx], omitted)
52
+ }
53
+ data[m] = m === 'SumbaSite' ? fixtures[0] : fixtures
54
+ }
55
+ if (verbose) spin.stop()
56
+ // create site first
57
+ const query = { $or: [{ alias }] }
58
+ if (!isEmpty(hostname)) query.$or.push({ hostname })
59
+ const model = getModel('SumbaSite')
60
+ const site = await model.findOneRecord({ query })
61
+ if (!isEmpty(site)) throw this.error('aliasOrHOstnameExists%s%s', alias, hostname ?? '')
62
+ // lets go
63
+ data.SumbaSite.alias = alias
64
+ if (!isEmpty(hostname)) data.SumbaSite.hostname = hostname
65
+ await model.transaction(async (trx) => {
66
+ const options = { trx }
67
+ const newSite = await model.createRecord(data.SumbaSite, options)
68
+ if (verbose) this.print.succeed('writingModel%s%s', model.name, 1)
69
+ for (const m in data) {
70
+ if (m === 'SumbaSite') continue
71
+ const mdl = getModel(m)
72
+ const fixtures = data[m]
73
+ for (const f of fixtures) {
74
+ f.siteId = newSite.id + ''
75
+ await mdl.createRecord(f, options)
76
+ }
77
+ if (verbose) this.print.succeed('writingModel%s%s', mdl.name, fixtures.length)
78
+ }
79
+ })
80
+ if (verbose) this.print.info('done')
81
+ }
82
+
83
+ export default createNewSite
@@ -0,0 +1,35 @@
1
+ async function removeSite (alias, verbose) {
2
+ const { isEmpty, orderBy } = this.app.lib._
3
+ const { getModel } = this.app.dobo
4
+
5
+ if (isEmpty(alias)) throw this.error('aliasRequired%s', alias)
6
+ const model = getModel('SumbaSite')
7
+ const query = { alias }
8
+ const site = await model.findOneRecord({ query })
9
+ if (isEmpty(site)) throw this.error('aliasNotFound%s', alias)
10
+ let spin
11
+ if (verbose) spin = this.print.spinner().start('processing...')
12
+ const models = orderBy(this.app.dobo.models, ['buildLevel'], ['desc']).filter(m => {
13
+ const prop = m.properties.find(p => p.name === 'siteId')
14
+ return !!prop
15
+ }).map(m => m.name)
16
+ if (verbose) spin.stop()
17
+ await model.transaction(async (trx) => {
18
+ const options = { trx }
19
+ for (const m of models) {
20
+ const mdl = getModel(m)
21
+ const ids = (await mdl.findAllRecord({ query: { siteId: site.id } }, options)).map(item => item.id)
22
+ // TODO: backup
23
+ if (ids.length === 0) continue
24
+ for (const id of ids) {
25
+ await mdl.removeRecord(id, { noReturn: true, ...options })
26
+ }
27
+ if (verbose) this.print.succeed('removedFrom%s%s', mdl.name, ids.length)
28
+ }
29
+ await model.removeRecord(site.id, { noReturn: true, ...options })
30
+ if (verbose) this.print.succeed('removedFrom%s%s', model.name, 1)
31
+ })
32
+ if (verbose) this.print.info('done')
33
+ }
34
+
35
+ export default removeSite
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sumba",
3
- "version": "2.7.3",
3
+ "version": "2.8.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,11 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-03-11
4
+
5
+ - [2.8.0] Add ```createNewSite()``` and ```applet.crateNewSite```
6
+ - [2.8.0] Add ```removeSite()``` and ```applet.removeSite```
7
+ - [2.8.0] Set ```site.json```, ```user.json```, ```team.json```, ```team-user.json``` first fixture as immutable row
8
+
3
9
  ## 2026-03-08
4
10
 
5
11
  - [2.7.3] Bug fix on ```mergeTeam()```
@@ -1,8 +0,0 @@
1
- import { handler } from './dobo@before-find-record.js'
2
-
3
- const doboBeforeFindOneRecord = {
4
- level: 1000,
5
- handler
6
- }
7
-
8
- export default doboBeforeFindOneRecord