Fetch BSB passage text from Bolls.life API in study lesson view; v1.1.35
Adds /api/bible-passage server route that proxies the Berean Standard Bible from Bolls.life per-verse, with in-memory caching. Study lesson scripture block now displays live BSB text instead of static passageText. Adds bibleBook field to StudyProgram so admins can set the book name explicitly when the study slug doesn't match (e.g. a study titled "ephesians-part-2"). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
const passageCache = new Map()
|
||||
|
||||
const BOLLS_BOOK_MAP = {
|
||||
genesis: 1, exodus: 2, leviticus: 3, numbers: 4, deuteronomy: 5,
|
||||
joshua: 6, judges: 7, ruth: 8, '1samuel': 9, '2samuel': 10,
|
||||
'1kings': 11, '2kings': 12, '1chronicles': 13, '2chronicles': 14,
|
||||
ezra: 15, nehemiah: 16, esther: 17, job: 18, psalms: 19, proverbs: 20,
|
||||
ecclesiastes: 21, songofsolomon: 22, isaiah: 23, jeremiah: 24,
|
||||
lamentations: 25, ezekiel: 26, daniel: 27, hosea: 28, joel: 29,
|
||||
amos: 30, obadiah: 31, jonah: 32, micah: 33, nahum: 34, habakkuk: 35,
|
||||
zephaniah: 36, haggai: 37, zechariah: 38, malachi: 39,
|
||||
matthew: 40, mark: 41, luke: 42, john: 43, acts: 44,
|
||||
romans: 45, '1corinthians': 46, '2corinthians': 47, galatians: 48,
|
||||
ephesians: 49, philippians: 50, colossians: 51, '1thessalonians': 52,
|
||||
'2thessalonians': 53, '1timothy': 54, '2timothy': 55, titus: 56,
|
||||
philemon: 57, hebrews: 58, james: 59, '1peter': 60, '2peter': 61,
|
||||
'1john': 62, '2john': 63, '3john': 64, jude: 65, revelation: 66,
|
||||
}
|
||||
|
||||
function parseReference(reference) {
|
||||
const match = reference.match(/^(\d+):(\d+)(?:-(\d+))?$/)
|
||||
if (!match) return null
|
||||
return {
|
||||
chapter: parseInt(match[1], 10),
|
||||
verseStart: parseInt(match[2], 10),
|
||||
verseEnd: match[3] ? parseInt(match[3], 10) : parseInt(match[2], 10),
|
||||
}
|
||||
}
|
||||
|
||||
export function register(app) {
|
||||
app.get('/api/bible-passage', async (req, res) => {
|
||||
const book = String(req.query.book ?? '').trim().toLowerCase().replace(/[\s-]/g, '')
|
||||
const reference = String(req.query.reference ?? '').trim()
|
||||
|
||||
const bookNum = BOLLS_BOOK_MAP[book]
|
||||
if (!bookNum) {
|
||||
res.status(400).json({ message: 'Unknown book.' })
|
||||
return
|
||||
}
|
||||
|
||||
const parsed = parseReference(reference)
|
||||
if (!parsed) {
|
||||
res.status(400).json({ message: 'Invalid reference format. Expected format: chapter:verseStart-verseEnd' })
|
||||
return
|
||||
}
|
||||
|
||||
const cacheKey = `BSB:${bookNum}:${parsed.chapter}:${parsed.verseStart}:${parsed.verseEnd}`
|
||||
if (passageCache.has(cacheKey)) {
|
||||
res.json({ text: passageCache.get(cacheKey) })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const verseNums = []
|
||||
for (let v = parsed.verseStart; v <= parsed.verseEnd; v++) verseNums.push(v)
|
||||
|
||||
const results = await Promise.all(
|
||||
verseNums.map(v =>
|
||||
fetch(`https://bolls.life/get-verse/BSB/${bookNum}/${parsed.chapter}/${v}/`, { signal: AbortSignal.timeout(8000) })
|
||||
.then(r => r.ok ? r.json() : Promise.reject(new Error(`${r.status}`)))
|
||||
)
|
||||
)
|
||||
|
||||
if (results.length === 0) {
|
||||
res.status(404).json({ message: 'Passage not found.' })
|
||||
return
|
||||
}
|
||||
|
||||
const text = results.map(v => `v. ${v.verse} ${String(v.text).trim()}`).join('\n\n')
|
||||
passageCache.set(cacheKey, text)
|
||||
res.json({ text })
|
||||
} catch {
|
||||
res.status(502).json({ message: 'Unable to fetch passage right now.' })
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user