sumba 2.4.1 → 2.6.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/dobo.sumba-contact-form@after-create-record.js +8 -8
- package/extend/bajo/hook/dobo.sumba-user@after-create-record.js +7 -6
- package/extend/bajo/hook/dobo.sumba-user@after-update-record.js +14 -12
- package/extend/bajoTemplate/layout/email.html +17 -0
- package/extend/bajoTemplate/layout/email.id.html +18 -0
- package/extend/bajoTemplate/partial/_mail/help-contact-form.html +1 -1
- package/extend/bajoTemplate/template/_mail/help-contact-form.html +1 -1
- package/extend/bajoTemplate/template/_mail/mystuff-change-password.html +1 -1
- package/extend/bajoTemplate/template/_mail/mystuff-reset-api-key.html +1 -1
- package/extend/bajoTemplate/template/_mail/user-activation-success.html +1 -1
- package/extend/bajoTemplate/template/_mail/user-forgot-password-changed.html +1 -1
- package/extend/bajoTemplate/template/_mail/user-forgot-password-link.html +1 -1
- package/extend/bajoTemplate/template/_mail/user-signup-success-active.html +1 -1
- package/extend/bajoTemplate/template/_mail/user-signup-success.html +1 -1
- package/extend/dobo/model/contact-form.json +1 -1
- package/extend/waibuMpa/route/help/contact-form.js +1 -1
- package/extend/waibuMpa/route/user/activation.js +3 -1
- package/extend/waibuMpa/route/user/forgot-password/@fpl.js +11 -9
- package/extend/waibuMpa/route/user/forgot-password.js +3 -3
- package/extend/waibuMpa/route/user/signup.js +1 -1
- package/extend/waibuMpa/route/your-stuff/change-password.js +3 -1
- package/extend/waibuMpa/route/your-stuff/profile/edit.js +5 -3
- package/extend/waibuMpa/route/your-stuff/reset-api-key.js +3 -1
- package/index.js +33 -0
- package/package.json +1 -1
- package/wiki/CHANGES.md +10 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
async function afterCreateRecord (body, options = {}
|
|
2
|
-
if (!options.req) return
|
|
3
|
-
if (!this.app.waibu) return
|
|
4
|
-
const { sendMail } = this.app.waibu
|
|
1
|
+
async function afterCreateRecord (body, rec, options = {}) {
|
|
5
2
|
const { data } = rec
|
|
6
3
|
const { req } = options
|
|
4
|
+
const { get } = this.app.lib._
|
|
5
|
+
const t = get(req, 't', this.t)
|
|
7
6
|
const to = `${data.firstName} ${data.lastName} <${data.email}>`
|
|
8
7
|
let bcc
|
|
9
|
-
if (req
|
|
10
|
-
const subject =
|
|
11
|
-
|
|
8
|
+
if (req.site) bcc = req.site.email
|
|
9
|
+
const subject = t('contactForm')
|
|
10
|
+
const payload = { to, bcc, subject, data }
|
|
11
|
+
await this.sendMail(
|
|
12
12
|
'sumba.template:/_mail/help-contact-form.html',
|
|
13
|
-
{
|
|
13
|
+
{ payload, options, source: this.ns }
|
|
14
14
|
)
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
async function afterCreateRecord (body, rec, options = {}) {
|
|
2
|
-
if (!options.req) return
|
|
3
|
-
if (!this.app.waibu) return
|
|
4
|
-
const { sendMail } = this.app.waibu
|
|
5
2
|
const { data } = rec
|
|
3
|
+
const { req } = options
|
|
4
|
+
const { get } = this.app.lib._
|
|
5
|
+
const t = get(req, 't', this.t)
|
|
6
6
|
const to = `${data.firstName} ${data.lastName} <${data.email}>`
|
|
7
|
-
const subject =
|
|
8
|
-
|
|
7
|
+
const subject = t('newUserSignup')
|
|
8
|
+
const payload = { to, subject, data }
|
|
9
|
+
await this.sendMail(
|
|
9
10
|
`sumba.template:/_mail/user-signup-success${data.status === 'ACTIVE' ? '-active' : ''}.html`,
|
|
10
|
-
{
|
|
11
|
+
{ payload, options, source: this.ns }
|
|
11
12
|
)
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
async function afterUpdateRecord (id, body, rec, options = {}) {
|
|
2
|
-
if (!options.req) return
|
|
3
|
-
if (!this.app.waibu) return
|
|
4
2
|
const { data, oldData } = rec
|
|
5
|
-
const {
|
|
3
|
+
const { req } = options
|
|
4
|
+
const { get } = this.app.lib._
|
|
5
|
+
const t = get(req, 't', this.t)
|
|
6
6
|
const to = `${data.firstName} ${data.lastName} <${data.email}>`
|
|
7
|
+
const source = this.ns
|
|
7
8
|
let subject
|
|
9
|
+
const payload = { to, subject, data }
|
|
8
10
|
|
|
9
11
|
if (oldData.status === 'UNVERIFIED' && data.status === 'ACTIVE') {
|
|
10
|
-
subject =
|
|
11
|
-
await sendMail(
|
|
12
|
+
payload.subject = t('userActivation')
|
|
13
|
+
await this.sendMail(
|
|
12
14
|
'sumba.template:/_mail/user-activation-success.html',
|
|
13
|
-
{
|
|
15
|
+
{ payload, options, source }
|
|
14
16
|
)
|
|
15
17
|
} else if (oldData.token !== data.token) {
|
|
16
|
-
subject =
|
|
17
|
-
await sendMail(
|
|
18
|
+
payload.subject = t('resetApiKey')
|
|
19
|
+
await this.sendMail(
|
|
18
20
|
'sumba.template:/_mail/mystuff-reset-api-key.html',
|
|
19
|
-
{
|
|
21
|
+
{ payload, options, source }
|
|
20
22
|
)
|
|
21
23
|
} else if (body.password) {
|
|
22
|
-
subject =
|
|
23
|
-
await sendMail(
|
|
24
|
+
payload.subject = t('changePassword')
|
|
25
|
+
await this.sendMail(
|
|
24
26
|
'sumba.template:/_mail/mystuff-change-password.html',
|
|
25
|
-
{
|
|
27
|
+
{ payload, options, source }
|
|
26
28
|
)
|
|
27
29
|
}
|
|
28
30
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<head lang="<%= _meta.lang %>">
|
|
3
|
+
<meta charset="UTF-8">
|
|
4
|
+
<title><%= page.fullTitle %></title>
|
|
5
|
+
<style type="text/css">
|
|
6
|
+
</style>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<% if (arguments[0].firstName && arguments[0].lastName) { %>
|
|
10
|
+
<p>Dear <%= firstName %> <%= lastName %>,</p>
|
|
11
|
+
<% } %>
|
|
12
|
+
<!-- body -->
|
|
13
|
+
<p>Sincerely,</p>
|
|
14
|
+
|
|
15
|
+
<p><%= _meta.site.title %></p>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="<%= _meta.lang %>">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title><%= page.fullTitle %></title>
|
|
6
|
+
<style type="text/css">
|
|
7
|
+
</style>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<% if (arguments[0].firstName && arguments[0].lastName) { %>
|
|
11
|
+
<p>Yth <%= firstName %> <%= lastName %>,</p>
|
|
12
|
+
<% } %>
|
|
13
|
+
<!-- body -->
|
|
14
|
+
<p>Hormat kami,</p>
|
|
15
|
+
|
|
16
|
+
<p><%= _meta.site.title %></p>
|
|
17
|
+
</body>
|
|
18
|
+
</html>
|
|
@@ -10,7 +10,7 @@ const contactForm = {
|
|
|
10
10
|
let error
|
|
11
11
|
if (req.method === 'POST') {
|
|
12
12
|
try {
|
|
13
|
-
const { data } = await createRecord({ model: 'SumbaContactForm', req, reply, options: { noFlash: true } })
|
|
13
|
+
const { data } = await createRecord({ model: 'SumbaContactForm', req, reply, transaction: true, options: { noFlash: true } })
|
|
14
14
|
req.flash('notify', req.t('contactFormSubmitted'))
|
|
15
15
|
return await reply.view('sumba.template:/help/contact-form/success.html', { form: req.body, data })
|
|
16
16
|
} catch (err) {
|
|
@@ -10,7 +10,9 @@ const userActivation = {
|
|
|
10
10
|
const query = { status: 'UNVERIFIED', token: req.body.key }
|
|
11
11
|
const result = await model.findRecord({ query, limit: 1 })
|
|
12
12
|
if (result.length === 0) throw this.error('validationError', { details: [{ field: 'key', error: 'invalidActivationKey' }] })
|
|
13
|
-
await model.
|
|
13
|
+
await model.transaction(async (trx) => {
|
|
14
|
+
await model.updateRecord(result[0].id, { status: 'ACTIVE' }, { req, noValidation: true, noFlash: true, trx })
|
|
15
|
+
})
|
|
14
16
|
req.flash('notify', req.t('userActivated'))
|
|
15
17
|
return reply.redirectTo(this.config.redirect.signin, req)
|
|
16
18
|
} catch (err) {
|
|
@@ -17,7 +17,6 @@ async function getUser (req, reply) {
|
|
|
17
17
|
const forgotPasswordLink = {
|
|
18
18
|
method: ['GET', 'POST'],
|
|
19
19
|
handler: async function (req, reply) {
|
|
20
|
-
const { sendMail } = this.app.waibu
|
|
21
20
|
const { defaultsDeep } = this.app.lib.aneka
|
|
22
21
|
const { importPkg } = this.app.bajo
|
|
23
22
|
const { isString } = this.app.lib._
|
|
@@ -41,14 +40,17 @@ const forgotPasswordLink = {
|
|
|
41
40
|
} catch (err) {
|
|
42
41
|
throw this.error('validationError', { details: err.details, values: err.values, ns: this.ns, statusCode: 422, code: 'DB_VALIDATION' })
|
|
43
42
|
}
|
|
44
|
-
await model.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
43
|
+
await model.transaction(async trx => {
|
|
44
|
+
await model.updateRecord(user.id, { password: req.body.newPassword }, { noFlash: true, trx })
|
|
45
|
+
const to = `${user.firstName} ${user.lastName} <${user.email}>`
|
|
46
|
+
const subject = req.t('forgotPasswordChanged')
|
|
47
|
+
const options = { req, reply, tpl: '' }
|
|
48
|
+
const payload = { to, subject, data: user }
|
|
49
|
+
await this.sendMail(
|
|
50
|
+
'sumba.template:/_mail/user-forgot-password-changed.html',
|
|
51
|
+
{ payload, options, source: this.ns }
|
|
52
|
+
)
|
|
53
|
+
})
|
|
52
54
|
req.flash('notify', req.t('passwordChangedReSignin'))
|
|
53
55
|
return reply.redirectTo(this.config.redirect.signin)
|
|
54
56
|
} catch (err) {
|
|
@@ -2,7 +2,6 @@ const profile = {
|
|
|
2
2
|
method: ['GET', 'POST'],
|
|
3
3
|
handler: async function (req, reply) {
|
|
4
4
|
if (!this.app.masohiMail) return await reply.view('sumba.template:/user/forgot-password.html')
|
|
5
|
-
const { sendMail } = this.app.waibu
|
|
6
5
|
const { defaultsDeep } = this.app.lib.aneka
|
|
7
6
|
const { dayjs } = this.app.lib
|
|
8
7
|
const model = this.app.dobo.getModel('SumbaUser')
|
|
@@ -20,9 +19,10 @@ const profile = {
|
|
|
20
19
|
const exp = req.site.setting.sumba.forgotPasswordExpDur
|
|
21
20
|
data.fpToken = Buffer.from(`${data.token}:${dayjs().add(exp, 'ms').unix()}`).toString('base64')
|
|
22
21
|
data._meta = { hostHeader: req.headers.host }
|
|
23
|
-
|
|
22
|
+
const payload = { to, subject, data }
|
|
23
|
+
await this.sendMail(
|
|
24
24
|
'sumba.template:/_mail/user-forgot-password-link.html',
|
|
25
|
-
{
|
|
25
|
+
{ payload, options, source: this.ns }
|
|
26
26
|
)
|
|
27
27
|
req.flash('notify', req.t('emailSent'))
|
|
28
28
|
return reply.redirectTo(this.config.redirect.signin)
|
|
@@ -24,7 +24,7 @@ const signup = {
|
|
|
24
24
|
const validation = { ns: ['sumba', 'dobo'], fields, extFields }
|
|
25
25
|
req.body.token = generateId()
|
|
26
26
|
req.body.provider = 'local'
|
|
27
|
-
const { data } = await createRecord({ model: 'SumbaUser', req, reply, options: { validation, noFlash: true, forceNoHidden: true } })
|
|
27
|
+
const { data } = await createRecord({ model: 'SumbaUser', req, reply, transaction: true, options: { validation, noFlash: true, forceNoHidden: true } })
|
|
28
28
|
req.flash('notify', req.t('userCreated'))
|
|
29
29
|
return await reply.view('sumba.template:/user/signup/success.html', { form: req.body, data })
|
|
30
30
|
} catch (err) {
|
|
@@ -26,7 +26,9 @@ const profile = {
|
|
|
26
26
|
const rec = await model.getRecord(req.user.id, { forceNoHidden: true })
|
|
27
27
|
const verified = await bcrypt.compare(req.body.currentPassword, rec.password)
|
|
28
28
|
if (!verified) throw this.error('invalidCurrentPassword', { details: [{ field: 'currentPassword', error: 'invalidPassword' }], statusCode: 400 })
|
|
29
|
-
await model.
|
|
29
|
+
await model.transaction(async (trx) => {
|
|
30
|
+
await model.updateRecord(req.user.id, { password: req.body.newPassword }, { req, reply, noFlash: true, trx })
|
|
31
|
+
})
|
|
30
32
|
// signout and redirect to signin
|
|
31
33
|
req.session.userId = null
|
|
32
34
|
req.flash('notify', req.t('passwordChangedReSignin'))
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const fields = ['email', 'firstName', 'lastName', 'address1', 'address2', 'city', 'zipCode', 'provinceState', 'country', 'phone', 'website']
|
|
2
|
+
|
|
1
3
|
const profile = {
|
|
2
4
|
method: ['GET', 'POST'],
|
|
3
5
|
handler: async function (req, reply) {
|
|
@@ -7,13 +9,13 @@ const profile = {
|
|
|
7
9
|
const { omit, pick } = this.app.lib._
|
|
8
10
|
const { hash } = this.app.bajoExtra
|
|
9
11
|
const resp = await getRecord({ model: 'SumbaUser', req, id: req.user.id, options: { forceNoHidden: true, noHook: true, noCache: true } })
|
|
10
|
-
let form = defaultsDeep(req.body, omit(resp.data, ['password']))
|
|
12
|
+
let form = defaultsDeep(req.body, omit(resp.data, ['password', 'salt']))
|
|
11
13
|
form.token = await hash(form.token)
|
|
12
14
|
let error
|
|
13
15
|
if (req.method === 'POST') {
|
|
14
16
|
try {
|
|
15
|
-
const body = pick(form,
|
|
16
|
-
const options = { noFlash: true, hidden: [], setField: 'profile', setFile: 'main.png' }
|
|
17
|
+
const body = pick(form, fields)
|
|
18
|
+
const options = { noFlash: true, hidden: [], setField: 'profile', setFile: 'main.png', partial: true, fields }
|
|
17
19
|
const resp = await updateRecord({ req, reply, model: 'SumbaUser', id: req.user.id, body, options })
|
|
18
20
|
form = resp.data
|
|
19
21
|
req.flash('notify', req.t('profileUpdated'))
|
|
@@ -24,7 +24,9 @@ const resetApiKey = {
|
|
|
24
24
|
const rec = await model.getRecord(req.user.id, { forceNoHidden: true })
|
|
25
25
|
const verified = await bcrypt.compare(form.password, rec.password)
|
|
26
26
|
if (!verified) throw this.error('validationError', { details: [{ field: 'password', error: 'invalidPassword' }], statusCode: 400 })
|
|
27
|
-
await model.
|
|
27
|
+
await model.transaction(async (trx) => {
|
|
28
|
+
await model.updateRecord(req.user.id, { salt: generateId() }, { req, reply, noFlash: true, forceNoHidden: true, trx })
|
|
29
|
+
})
|
|
28
30
|
await delay(2000) // ensure req.user cache is expired
|
|
29
31
|
req.flash('notify', req.t('resetApiKeySuccessfull'))
|
|
30
32
|
return reply.redirectTo('sumba:/your-stuff/profile')
|
package/index.js
CHANGED
|
@@ -541,6 +541,39 @@ async function factory (pkgName) {
|
|
|
541
541
|
const items = await model.findAllRecord()
|
|
542
542
|
return items.map(item => ({ value: item.id, text: item.name }))
|
|
543
543
|
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Method to send mail through Masohi Messaging System. It is a thin wrapper
|
|
547
|
+
* for {@link https://github.com/ardhi/masohi-mail|masohi-mail} send method.
|
|
548
|
+
*
|
|
549
|
+
* If both masohiMail and waibu are not loaded, nothing is delivered.
|
|
550
|
+
*
|
|
551
|
+
* @method
|
|
552
|
+
* @async
|
|
553
|
+
* @param {(string|Array)} tpl - Mail's template to use. If a string is given, the same template will be used for html & plaintext versions. Otherwise, the first template will be used for html mail, and the second one is for it's plaintext version
|
|
554
|
+
* @param {Object} [params={}] - {@link https://github.com/ardhi/masohi-mail|masohi-mail}'s params object.
|
|
555
|
+
* @returns
|
|
556
|
+
*/
|
|
557
|
+
sendMail = async (tpl, { payload = {}, conn, source, options = {} } = {}) => {
|
|
558
|
+
if (!this.app.masohiMail || !this.app.waibu) return
|
|
559
|
+
conn = conn ?? 'masohiMail:default'
|
|
560
|
+
const { importModule } = this.app.bajo
|
|
561
|
+
const { get, isString } = this.app.lib._
|
|
562
|
+
const { generateId } = this.app.lib.aneka
|
|
563
|
+
const { render } = this.app.bajoTemplate
|
|
564
|
+
const buildLocals = await importModule('waibu:/lib/build-locals.js')
|
|
565
|
+
|
|
566
|
+
if (isString(tpl)) tpl = [tpl]
|
|
567
|
+
const locals = await buildLocals.call(this, { params: payload.data, opts: options })
|
|
568
|
+
payload.from = payload.from ?? get(options, 'req.site.email', payload.from)
|
|
569
|
+
const opts = {
|
|
570
|
+
lang: get(options, 'req.lang'),
|
|
571
|
+
groupId: get(options, 'req.id', generateId())
|
|
572
|
+
}
|
|
573
|
+
payload.html = await render(tpl[0], locals, opts)
|
|
574
|
+
if (tpl[1]) payload.text = await render(tpl[1], locals, opts)
|
|
575
|
+
await this.app.masohiMail.send({ payload, source: source ?? this.ns, conn })
|
|
576
|
+
}
|
|
544
577
|
}
|
|
545
578
|
|
|
546
579
|
return Sumba
|
package/package.json
CHANGED
package/wiki/CHANGES.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changes
|
|
2
2
|
|
|
3
|
+
## 2026-03-05
|
|
4
|
+
|
|
5
|
+
- [2.6.0] Update multiple endpoints to support dobo's transaction
|
|
6
|
+
- [2.6.0] Bug fix on ```profile.edit``` route
|
|
7
|
+
|
|
8
|
+
## 2026-03-02
|
|
9
|
+
|
|
10
|
+
- [2.5.0] Add ```sendMail()``` to send mail using ```masohiMail```
|
|
11
|
+
- [2.5.0] Update all mail templates and hooks to match with the new specs
|
|
12
|
+
|
|
3
13
|
## 2026-02-21
|
|
4
14
|
|
|
5
15
|
- [2.4.1] Bug fix on intl functions. Now moved to ```preParsing``` instead of ```onRequest```
|