Gospel courtroom page: tabs, plea dropdown, plea email, Acts, accuser/charges, site nav and links; fix service worker for /salvation; courtroom contact type; accept community type; v2.39.0
Publish Container / build (push) Successful in 16m32s
Publish Container / build (push) Successful in 16m32s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ea463b5851
commit
27656b84f2
11 files changed
+875
-22
No files matched your search
@@ -0,0 +1,92 @@
|
||||
import { Resend } from 'resend'
|
||||
import { escapeHtml } from '../helpers.js'
|
||||
import { getResendFromAddress, getResendInboxAddress, sendResendEmailWithRetry } from '../email.js'
|
||||
|
||||
// A visitor who finished the /salvation page can tell us they prayed to receive Christ.
|
||||
// Nothing is stored on the server: the response is emailed once to the site inbox and that is all.
|
||||
const hits = new Map()
|
||||
|
||||
function rateLimit(req, res, next) {
|
||||
const ip = req.ip ?? 'unknown'
|
||||
const now = Date.now()
|
||||
const windowMs = 10 * 60 * 1000
|
||||
const entry = hits.get(ip) ?? { count: 0, start: now }
|
||||
if (now - entry.start > windowMs) { entry.count = 0; entry.start = now }
|
||||
entry.count += 1
|
||||
hits.set(ip, entry)
|
||||
if (hits.size > 2000) {
|
||||
for (const [k, v] of hits) { if (now - v.start > windowMs) hits.delete(k) }
|
||||
}
|
||||
if (entry.count > 3) {
|
||||
res.status(429).json({ message: 'Please wait a few minutes before trying again.' })
|
||||
return
|
||||
}
|
||||
next()
|
||||
}
|
||||
|
||||
function clean(value, max) {
|
||||
return typeof value === 'string' ? value.trim().replace(/\s+/g, ' ').slice(0, max) : ''
|
||||
}
|
||||
|
||||
export function register(app) {
|
||||
app.post('/api/salvation-response', rateLimit, async (req, res) => {
|
||||
const { name, email, message, _honey } = req.body ?? {}
|
||||
if (_honey) { res.json({ ok: true }); return }
|
||||
|
||||
const safeName = clean(name, 100)
|
||||
const safeEmail = clean(email, 200)
|
||||
const safeMessage = typeof message === 'string' ? message.trim().slice(0, 2000) : ''
|
||||
if (safeEmail && !/^[a-zA-Z0-9._%+\-]{1,64}@[a-zA-Z0-9.\-]{1,253}\.[a-zA-Z]{2,}$/.test(safeEmail)) {
|
||||
res.status(400).json({ message: 'That email address does not look right. You can also leave it blank.' })
|
||||
return
|
||||
}
|
||||
|
||||
if (!process.env.RESEND_API_KEY) {
|
||||
console.error('[salvation] RESEND_API_KEY env var not set')
|
||||
res.status(503).json({ message: 'We could not send this right now.' })
|
||||
return
|
||||
}
|
||||
|
||||
const when = new Date().toLocaleString('en-US', { dateStyle: 'medium', timeStyle: 'short' })
|
||||
const nameLine = safeName || 'Name not given'
|
||||
const emailLine = safeEmail || 'Email not given'
|
||||
const text = [
|
||||
'Someone responded to the gospel on the /salvation page (the courtroom presentation) and says they pleaded guilty, accepted the pardon, and prayed to receive Jesus as Savior.',
|
||||
'',
|
||||
`Name: ${nameLine}`,
|
||||
`Email: ${emailLine}`,
|
||||
`When: ${when}`,
|
||||
'',
|
||||
safeMessage ? `Their message:\n${safeMessage}` : 'They did not leave a message.',
|
||||
'',
|
||||
safeEmail ? 'You can reply directly to this email to reach them.' : 'They did not leave an email address, so there is no way to reply.',
|
||||
].join('\n')
|
||||
const html = `<div style="font-family:Georgia,serif;max-width:560px;line-height:1.6;color:#1a1208">
|
||||
<p><strong>Someone responded to the gospel</strong> on the /salvation page and says they prayed to receive Jesus as Savior.</p>
|
||||
<p><strong>Name:</strong> ${escapeHtml(nameLine)}<br><strong>Email:</strong> ${escapeHtml(emailLine)}<br><strong>When:</strong> ${escapeHtml(when)}</p>
|
||||
${safeMessage ? `<p><strong>Their message:</strong></p><blockquote style="margin:0;padding-left:12px;border-left:3px solid #b8860b">${escapeHtml(safeMessage).replace(/\n/g, '<br>')}</blockquote>` : '<p><em>They did not leave a message.</em></p>'}
|
||||
<p style="color:#6b5a30">${safeEmail ? 'You can reply directly to this email to reach them.' : 'They did not leave an email address, so there is no way to reply.'}</p>
|
||||
</div>`
|
||||
|
||||
try {
|
||||
const resend = new Resend(process.env.RESEND_API_KEY)
|
||||
await sendResendEmailWithRetry({
|
||||
resend,
|
||||
context: 'salvation-response',
|
||||
payload: {
|
||||
from: getResendFromAddress(),
|
||||
to: [getResendInboxAddress()],
|
||||
...(safeEmail ? { replyTo: safeEmail } : {}),
|
||||
subject: 'Someone prayed to receive Christ on your salvation page',
|
||||
tags: [{ name: 'flow', value: 'salvation-response' }],
|
||||
text,
|
||||
html,
|
||||
},
|
||||
})
|
||||
res.json({ ok: true })
|
||||
} catch (err) {
|
||||
console.error('[salvation] email failed:', err?.message ?? err)
|
||||
res.status(502).json({ message: 'We could not send this right now.' })
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user