Initial commit

This commit is contained in:
2026-04-17 23:26:01 +00:00
commit 2ea4ca5d52
409 changed files with 63459 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
/// <reference path="../pb_data/types.d.ts" />
onRecordRequestOTPRequest((e) => {
// create a user with the OTP email if it doesn't exist
if (!e.record) {
const email = e.requestInfo().body['email']
const record = new Record(e.collection)
record.setEmail(email)
record.setPassword($security.randomString(30))
const language = e.requestInfo().headers['language'] || 'en'
record.set('language', language)
e.app.save(record)
e.record = record
}
return e.next()
}, 'users')

View File

@@ -0,0 +1,13 @@
{
"mailSubject": {
"authAlert": "Anmeldung von einem neuen Standort",
"emailChange": "Bestätige deine neue {{.appName}}-E-Mail-Adresse",
"otp": "Einmalpasswort für {{.appName}}",
"passwortReset": "Setze dein {{.appName}}-Passwort zurück",
"verification": "Bestätige deine {{.appName}}-E-Mail-Adresse"
},
"resetCounter": {
"title": "Zähler zurückgesetzt",
"body": "Ihr Zähler wurde auf null zurückgesetzt."
}
}

View File

@@ -0,0 +1,13 @@
{
"mailSubject": {
"authAlert": "Login from a new location",
"emailChange": "Confirm your {{.appName}} new email address",
"otp": "OTP for {{.appName}}",
"passwortReset": "Reset your {{.appName}} password",
"verification": "Verify your {{.appName}} email"
},
"resetCounter": {
"title": "Counter reset",
"body": "Your counter has been reset to zero."
}
}

View File

@@ -0,0 +1,26 @@
/// <reference path="../pb_data/types.d.ts" />
onMailerRecordAuthAlertSend((e) => {
const utils = require(`${__hooks}/utils/renderMailTemplate.js`)
utils.renderMailTemplate(e, 'authAlert')
})
onMailerRecordPasswordResetSend((e) => {
const utils = require(`${__hooks}/utils/renderMailTemplate.js`)
utils.renderMailTemplate(e, 'passwordReset')
})
onMailerRecordVerificationSend((e) => {
const utils = require(`${__hooks}/utils/renderMailTemplate.js`)
utils.renderMailTemplate(e, 'verification')
})
onMailerRecordEmailChangeSend((e) => {
const utils = require(`${__hooks}/utils/renderMailTemplate.js`)
utils.renderMailTemplate(e, 'emailChange')
})
onMailerRecordOTPSend((e) => {
const utils = require(`${__hooks}/utils/renderMailTemplate.js`)
utils.renderMailTemplate(e, 'otp')
})

View File

@@ -0,0 +1,40 @@
/// <reference path="../pb_data/types.d.ts" />
/**
* POST endpoint to reset the user's counter to 0,
* additionally sends a notification to the user.
* Requires authentication.
*/
routerAdd('POST', '/counter/reset', (e) => {
try {
const authRecord = e.auth
// find the user's counter record
const counterRecord = $app.findFirstRecordByData('counters', 'userId', authRecord.id)
if (counterRecord) {
// reset the counter to 0 and save
counterRecord.set('count', 0)
$app.save(counterRecord)
}
// get the user's language and load the locale
const userRecord = $app.findFirstRecordByData('users', 'id', authRecord.id)
const language = userRecord?.getString('language') ?? 'en'
const locale = require(`${__hooks}/locales/${language}.json`)
const t = locale.resetCounter
// add notification
const collection = $app.findCollectionByNameOrId('notifications')
const notificationRecord = new Record(collection)
notificationRecord.set('userId', authRecord.id)
notificationRecord.set('title', t.title)
notificationRecord.set('body', t.body)
$app.save(notificationRecord)
return e.noContent(204)
} catch (error) {
console.error('Error', error)
return e.json(500, { message: error.message })
}
}, $apis.requireAuth())

View File

@@ -0,0 +1,55 @@
/// <reference path="../pb_data/types.d.ts" />
onRecordAfterCreateSuccess((e) => {
const record = e.record
const userId = record.getString('userId')
if (!userId) return e.next()
const tokenRecords = $app.findRecordsByFilter(
'fcm_tokens',
`userId = "${userId}"`,
'-created',
500,
0
)
if (!tokenRecords || tokenRecords.length === 0) return e.next()
const tokens = tokenRecords
.map(r => r.getString('token'))
.filter(t => t.length > 0)
if (tokens.length === 0) return e.next()
const sidecarUrl = $os.getenv('SIDECAR_URL') || 'http://localhost:8091'
const sidecarSecret = $os.getenv('SIDECAR_SECRET') || ''
try {
const res = $http.send({
url: sidecarUrl + '/notify',
method: 'POST',
headers: {
'content-type': 'application/json',
'x-sidecar-secret': sidecarSecret
},
body: JSON.stringify({
tokens: tokens,
title: record.getString('title'),
body: record.getString('body')
}),
timeout: 10
})
if (res.statusCode !== 200) {
e.app.logger().error('FCM relay failed',
'status', res.statusCode,
'response', JSON.stringify(res.json)
)
}
} catch (err) {
e.app.logger().error('FCM relay error', 'error', err)
}
return e.next()
}, 'notifications')

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<body>
<p>Hallo,</p>
<p>Wir haben eine Anmeldung bei deinem {{.appName}}-Konto von einem neuen Standort festgestellt:</p>
<p><em>{{.info}}</em></p>
<p><strong>Wenn du das nicht warst, solltest du sofort dein {{.appName}}-Passwort ändern, um den Zugriff von allen anderen Standorten zu widerrufen.</strong></p>
<p>Wenn du das warst, kannst du diese E-Mail ignorieren.</p>
<p>
Viele Grüße,<br/>
Das {{.appName}}-Team
</p>
</body>
</html>

View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<body>
<p>Hallo,</p>
<p>Klicke auf den Button unten, um deine neue E-Mail-Adresse zu bestätigen.</p>
<p>
<a class="btn" href="{{.appUrl}}/_/#/auth/confirm-email-change/{{.token}}" target="_blank" rel="noopener">Neue E-Mail-Adresse bestätigen</a>
</p>
<p><i>Wenn du keine Änderung deiner E-Mail-Adresse beantragt hast, kannst du diese E-Mail ignorieren.</i></p>
<p>
Viele Grüße,<br/>
Das {{.appName}}-Team
</p>
</body>
</html>

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<body>
<p>Hallo,</p>
<p>Dein Einmalpasswort lautet: <strong>{{.password}}</strong></p>
<p><i>Wenn du kein Einmalpasswort angefordert hast, kannst du diese E-Mail ignorieren.</i></p>
<p>
Viele Grüße,<br/>
Das {{.appName}}-Team
</p>
</body>
</html>

View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<body>
<p>Hallo,</p>
<p>Klicke auf den Button unten, um dein Passwort zurückzusetzen.</p>
<p>
<a class="btn" href="{{.appUrl}}/_/#/auth/confirm-password-reset/{{.token}}" target="_blank" rel="noopener">Passwort zurücksetzen</a>
</p>
<p><i>Wenn du keine Passwortzurücksetzung beantragt hast, kannst du diese E-Mail ignorieren.</i></p>
<p>
Viele Grüße,<br/>
Das {{.appName}}-Team
</p>
</body>
</html>

View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<body>
<p>Hallo,</p>
<p>Willkommen bei {{.appName}}.</p>
<p>Klicke auf den Button unten, um deine E-Mail-Adresse zu bestätigen.</p>
<p>
<a class="btn" href="{{.appUrl}}/_/#/auth/confirm-verification/{{.token}}" target="_blank" rel="noopener">E-Mail-Adresse bestätigen</a>
</p>
<p>
Viele Grüße,<br/>
Das {{.appName}}-Team
</p>
</body>
</html>

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<body>
<p>Hello,</p>
<p>We noticed a login to your {{.appName}} account from a new location:</p>
<p><em>{{.info}}</em></p>
<p><strong>If this wasn't you, you should immediately change your {{.appName}} account password to revoke access from all other locations.</strong></p>
<p>If this was you, you may disregard this email.</p>
<p>
Thanks,<br/>
{{.appName}} team
</p>
</body>
</html>

View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<body>
<p>Hello,</p>
<p>Click on the button below to confirm your new email address.</p>
<p>
<a class="btn" href="{{.appUrl}}/_/#/auth/confirm-email-change/{{.token}}" target="_blank" rel="noopener">Confirm new email</a>
</p>
<p><i>If you didn't ask to change your email address, you can ignore this email.</i></p>
<p>
Thanks,<br/>
{{.appName}} team
</p>
</body>
</html>

View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<body>
<p>Hello,</p>
<p>Your one-time password is: <strong>{{.password}}</strong></p>
<p><i>If you didn't ask for the one-time password, you can ignore this email.</i></p>
<p>
Thanks,<br/>
{{.appName}} team
</p>
</body>
</html>

View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<body>
<p>Hello,</p>
<p>Click on the button below to reset your password.</p>
<p>
<a class="btn" href="{{.appUrl}}/_/#/auth/confirm-password-reset/{{.token}}" target="_blank" rel="noopener">Reset password</a>
</p>
<p><i>If you didn't ask to reset your password, you can ignore this email.</i></p>
<p>
Thanks,<br/>
{{.appName}} team
</p>
</body>
</html>

View File

@@ -0,0 +1,15 @@
<!DOCTYPE html>
<html>
<body>
<p>Hello,</p>
<p>Thank you for joining us at {{.appName}}.</p>
<p>Click on the button below to verify your email address.</p>
<p>
<a class="btn" href="{{.appUrl}}/_/#/auth/confirm-verification/{{.token}}" target="_blank" rel="noopener">Verify</a>
</p>
<p>
Thanks,<br/>
{{.appName}} team
</p>
</body>
</html>

View File

@@ -0,0 +1,29 @@
const renderMailTemplate = (e, type) => {
try {
const language = e.record?.getString('language') ?? 'en'
const locale = require(`${__hooks}/locales/${language}.json`)
const subject = $template.loadString(
locale.mailSubject[type]
).render({
...e.meta,
...e.app.settings().meta
})
const html = $template.loadFiles(
`${__hooks}/templates/${language}/${type}.html`
).render({
...e.meta,
...e.app.settings().meta
})
e.message.subject = subject
e.message.html = html
} catch (error) {
console.log(error)
}
e.next()
}
module.exports = { renderMailTemplate }