Files
the-bible/backend/src/index.js

139 lines
3.8 KiB
JavaScript

const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const path = require('path');
const fs = require('fs').promises;
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(helmet({
contentSecurityPolicy: false,
crossOriginOpenerPolicy: false,
crossOriginEmbedderPolicy: false,
originAgentCluster: false
}));
app.use(cors());
app.use(express.json());
// Serve static files from the React build
app.use(express.static(path.join(__dirname, '../../frontend/build')));
// Bible data directory
const BIBLE_DATA_DIR = path.join(__dirname, '../../bible-data');
// Helper function to read markdown files
async function readMarkdownFile(filePath) {
try {
const content = await fs.readFile(filePath, 'utf-8');
return content;
} catch (error) {
throw new Error(`Failed to read file: ${filePath}`);
}
}
// Helper function to get all books
async function getBooks() {
try {
const items = await fs.readdir(BIBLE_DATA_DIR);
const bookDirs = [];
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) {
throw new Error('Failed to read bible data directory');
}
}
// Routes
app.get('/health', (req, res) => {
res.json({ status: 'OK', message: 'ESV Bible API is running' });
});
app.get('/books', async (req, res) => {
try {
const books = await getBooks();
res.json({ books });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.get('/books/:book', async (req, res) => {
try {
const { book } = req.params;
const bookDir = path.join(BIBLE_DATA_DIR, book);
// Check if book directory exists
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}'` });
}
// Extract chapter numbers from filenames (e.g., "Chapter_01.md" -> "1")
const chapters = chapterFiles.map(file => {
const match = file.match(/Chapter_(\d+)\.md$/);
return match ? parseInt(match[1], 10).toString() : null;
}).filter(Boolean);
res.json({ chapters });
} catch (error) {
res.status(404).json({ error: `Book '${req.params.book}' not found` });
}
});
app.get('/books/:book/:chapter', async (req, res) => {
try {
const { book, chapter } = req.params;
const chapterPath = path.join(BIBLE_DATA_DIR, book, `${chapter}.md`);
const content = await readMarkdownFile(chapterPath);
res.type('text/markdown').send(content);
} catch (error) {
res.status(404).json({ error: `Chapter ${chapter} not found in book '${book}'` });
}
});
// Catch-all handler: send back React's index.html for client-side routing
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../../frontend/build/index.html'));
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
});
// Start server
app.listen(PORT, () => {
console.log(`ESV Bible API server running on port ${PORT}`);
console.log(`Bible data directory: ${BIBLE_DATA_DIR}`);
});