Update to use ESV Bible from GitHub repository with by_chapter structure
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
32
README.md
32
README.md
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
83
src/index.js
83
src/index.js
@@ -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}'` });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user