Update to use ESV Bible from GitHub repository with by_chapter structure

This commit is contained in:
Ryderjj89
2025-09-13 12:03:29 -04:00
parent 921a233c51
commit fab87ca06b
4 changed files with 91 additions and 41 deletions

View File

@@ -2,6 +2,9 @@ FROM node:18-alpine
WORKDIR /app WORKDIR /app
# Install git for cloning the bible repository
RUN apk add --no-cache git
# Copy package files # Copy package files
COPY package*.json ./ COPY package*.json ./
@@ -11,6 +14,12 @@ RUN npm ci --only=production
# Copy application code # Copy application code
COPY . . COPY . .
# Clone ESV Bible markdown repository
RUN git clone https://github.com/lguenth/mdbible.git /tmp/mdbible && \
mkdir -p /app/bible-data && \
cp -r /tmp/mdbible/by_chapter/* /app/bible-data/ && \
rm -rf /tmp/mdbible
# Expose port # Expose port
EXPOSE 3000 EXPOSE 3000

View File

@@ -1,18 +1,20 @@
# ESV Bible Markdown # ESV Bible Markdown
A Docker-based web service for serving the ESV Bible in Markdown format. A Docker-based web service for serving the ESV Bible in Markdown format with chapter-by-chapter organization.
## Features ## Features
- Complete ESV Bible text in Markdown format - Complete ESV Bible text in Markdown format from [lguenth/mdbible](https://github.com/lguenth/mdbible)
- Organized by book and chapter for easy navigation
- Docker containerized for easy deployment - Docker containerized for easy deployment
- RESTful API for accessing Bible content - RESTful API for accessing Bible content
- Persistent volume storage for Bible data
- Optimized for remote hosting - Optimized for remote hosting
## Setup ## Setup
1. Clone this repository 1. Clone this repository
2. Place ESV Bible Markdown files in the `bible-data` directory 2. The ESV Bible data will be automatically downloaded during Docker build from the GitHub repository
3. Run `docker-compose up --build` 3. Run `docker-compose up --build`
## Usage ## Usage
@@ -21,10 +23,24 @@ The service will be available at `http://localhost:3000`
### API Endpoints ### API Endpoints
- `GET /books` - List all books - `GET /health` - Health check endpoint
- `GET /books/:book` - Get specific book - `GET /books` - List all available books
- `GET /books/:book` - Get complete book (all chapters combined)
- `GET /books/:book/:chapter` - Get specific chapter - `GET /books/:book/:chapter` - Get specific chapter
### Example Usage
```bash
# List all books
curl http://localhost:3000/books
# Get the book of Genesis
curl http://localhost:3000/books/Genesis
# Get Genesis chapter 1
curl http://localhost:3000/books/Genesis/1
```
## Development ## Development
For local development: For local development:
@@ -40,6 +56,12 @@ Build and run with Docker Compose:
docker-compose up --build docker-compose up --build
``` ```
The Bible data is stored in a persistent Docker volume named `bible_data` for efficient storage and updates.
## Data Source
Bible content is sourced from [lguenth/mdbible](https://github.com/lguenth/mdbible/tree/main/by_chapter), which provides the ESV Bible organized by book and chapter in Markdown format.
## License ## License
MIT MIT

View File

@@ -1,12 +1,14 @@
version: '3.8'
services: services:
esv-bible: esv-bible:
build: . build: .
ports: ports:
- "3000:3000" - "3000:3000"
volumes: volumes:
- ./bible-data:/app/bible-data:ro - bible_data:/app/bible-data
environment: environment:
- NODE_ENV=production - NODE_ENV=production
restart: unless-stopped restart: unless-stopped
volumes:
bible_data:
driver: local

View File

@@ -28,9 +28,28 @@ async function readMarkdownFile(filePath) {
// Helper function to get all books // Helper function to get all books
async function getBooks() { async function getBooks() {
try { try {
const files = await fs.readdir(BIBLE_DATA_DIR); const items = await fs.readdir(BIBLE_DATA_DIR);
const markdownFiles = files.filter(file => file.endsWith('.md')); const bookDirs = [];
return markdownFiles.map(file => file.replace('.md', ''));
for (const item of items) {
const itemPath = path.join(BIBLE_DATA_DIR, item);
const stat = await fs.stat(itemPath);
if (stat.isDirectory()) {
// Check if directory contains markdown files
try {
const files = await fs.readdir(itemPath);
if (files.some(file => file.endsWith('.md'))) {
bookDirs.push(item);
}
} catch (error) {
// Skip directories we can't read
continue;
}
}
}
return bookDirs;
} catch (error) { } catch (error) {
throw new Error('Failed to read bible data directory'); throw new Error('Failed to read bible data directory');
} }
@@ -53,10 +72,31 @@ app.get('/books', async (req, res) => {
app.get('/books/:book', async (req, res) => { app.get('/books/:book', async (req, res) => {
try { try {
const { book } = req.params; const { book } = req.params;
const filePath = path.join(BIBLE_DATA_DIR, `${book}.md`); const bookDir = path.join(BIBLE_DATA_DIR, book);
const content = await readMarkdownFile(filePath); // Check if book directory exists
res.type('text/markdown').send(content); const stat = await fs.stat(bookDir);
if (!stat.isDirectory()) {
return res.status(404).json({ error: `Book '${book}' not found` });
}
// Get all chapter files
const files = await fs.readdir(bookDir);
const chapterFiles = files.filter(file => file.endsWith('.md')).sort();
if (chapterFiles.length === 0) {
return res.status(404).json({ error: `No chapters found for book '${book}'` });
}
// Combine all chapters
let fullBook = `# ${book}\n\n`;
for (const chapterFile of chapterFiles) {
const chapterPath = path.join(bookDir, chapterFile);
const chapterContent = await readMarkdownFile(chapterPath);
fullBook += chapterContent + '\n\n';
}
res.type('text/markdown').send(fullBook);
} catch (error) { } catch (error) {
res.status(404).json({ error: `Book '${req.params.book}' not found` }); res.status(404).json({ error: `Book '${req.params.book}' not found` });
} }
@@ -65,35 +105,12 @@ app.get('/books/:book', async (req, res) => {
app.get('/books/:book/:chapter', async (req, res) => { app.get('/books/:book/:chapter', async (req, res) => {
try { try {
const { book, chapter } = req.params; const { book, chapter } = req.params;
const filePath = path.join(BIBLE_DATA_DIR, `${book}.md`); const chapterPath = path.join(BIBLE_DATA_DIR, book, `${chapter}.md`);
const content = await readMarkdownFile(filePath); const content = await readMarkdownFile(chapterPath);
res.type('text/markdown').send(content);
// Simple chapter extraction (this could be improved with better parsing)
const lines = content.split('\n');
const chapterPattern = new RegExp(`^# ${chapter}\\s*$`, 'i');
const chapterLines = [];
let inChapter = false;
for (const line of lines) {
if (chapterPattern.test(line)) {
inChapter = true;
chapterLines.push(line);
} else if (inChapter && line.startsWith('# ')) {
// Next chapter starts
break;
} else if (inChapter) {
chapterLines.push(line);
}
}
if (chapterLines.length === 0) {
return res.status(404).json({ error: `Chapter ${chapter} not found in ${book}` });
}
res.type('text/markdown').send(chapterLines.join('\n'));
} catch (error) { } catch (error) {
res.status(500).json({ error: error.message }); res.status(404).json({ error: `Chapter ${chapter} not found in book '${book}'` });
} }
}); });