Features added: - Database schema for pages and media files with content types (Rich Text, HTML, Markdown) - Admin API routes for full page CRUD operations - Image upload functionality with file management - Rich text editor using TinyMCE with image insertion - Admin interface for creating/editing pages with SEO options - Dynamic navigation and footer integration - Public page display routes with proper SEO metadata - Support for featured images and content excerpts Admin features: - Create/edit/delete pages with rich content editor - Upload and manage images through media library - Configure pages to appear in navigation or footer - Set page status (Draft, Published, Archived) - SEO title and description management - Real-time preview of content changes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
141 lines
5.3 KiB
TypeScript
141 lines
5.3 KiB
TypeScript
import { PrismaClient } from '@prisma/client'
|
|
import fs from 'fs'
|
|
import path from 'path'
|
|
|
|
const prisma = new PrismaClient()
|
|
|
|
interface Verse { verseNum: number; text: string }
|
|
interface Chapter { chapterNum: number; verses: Verse[] }
|
|
interface Book { name: string; chapters: Chapter[] }
|
|
interface TestamentFile { testament: string; books: Book[] }
|
|
|
|
function loadJson(file: string): TestamentFile {
|
|
return JSON.parse(fs.readFileSync(file, 'utf-8'))
|
|
}
|
|
|
|
function getBookKeyEn(name: string): string {
|
|
const map: Record<string, string> = {
|
|
'Genesis': 'genesis', 'Exodus': 'exodus', 'Leviticus': 'leviticus', 'Numbers': 'numbers', 'Deuteronomy': 'deuteronomy',
|
|
'Joshua': 'joshua', 'Judges': 'judges', 'Ruth': 'ruth', '1 Samuel': '1_samuel', '2 Samuel': '2_samuel',
|
|
'1 Kings': '1_kings', '2 Kings': '2_kings', '1 Chronicles': '1_chronicles', '2 Chronicles': '2_chronicles',
|
|
'Ezra': 'ezra', 'Nehemiah': 'nehemiah', 'Esther': 'esther', 'Job': 'job', 'Psalms': 'psalms',
|
|
'Proverbs': 'proverbs', 'Ecclesiastes': 'ecclesiastes', 'Song of Songs': 'song_of_songs', 'Isaiah': 'isaiah',
|
|
'Jeremiah': 'jeremiah', 'Lamentations': 'lamentations', 'Ezekiel': 'ezekiel', 'Daniel': 'daniel',
|
|
'Hosea': 'hosea', 'Joel': 'joel', 'Amos': 'amos', 'Obadiah': 'obadiah', 'Jonah': 'jonah', 'Micah': 'micah',
|
|
'Nahum': 'nahum', 'Habakkuk': 'habakkuk', 'Zephaniah': 'zephaniah', 'Haggai': 'haggai', 'Zechariah': 'zechariah', 'Malachi': 'malachi',
|
|
'Matthew': 'matthew', 'Mark': 'mark', 'Luke': 'luke', 'John': 'john', 'Acts': 'acts', 'Romans': 'romans',
|
|
'1 Corinthians': '1_corinthians', '2 Corinthians': '2_corinthians', 'Galatians': 'galatians', 'Ephesians': 'ephesians', 'Philippians': 'philippians', 'Colossians': 'colossians',
|
|
'1 Thessalonians': '1_thessalonians', '2 Thessalonians': '2_thessalonians', '1 Timothy': '1_timothy', '2 Timothy': '2_timothy', 'Titus': 'titus', 'Philemon': 'philemon',
|
|
'Hebrews': 'hebrews', 'James': 'james', '1 Peter': '1_peter', '2 Peter': '2_peter', '1 John': '1_john', '2 John': '2_john', '3 John': '3_john', 'Jude': 'jude', 'Revelation': 'revelation'
|
|
}
|
|
return map[name] || name.toLowerCase().replace(/\s+/g, '_')
|
|
}
|
|
|
|
function getOrderFromList(name: string, list: string[]): number {
|
|
const idx = list.indexOf(name)
|
|
return idx >= 0 ? idx + 1 : 999
|
|
}
|
|
|
|
async function main() {
|
|
try {
|
|
const abbr = (process.env.EN_ABBR || 'BSB').toUpperCase()
|
|
const inputDir = process.env.INPUT_DIR || path.join('data', 'en_bible', abbr)
|
|
const lang = 'en'
|
|
|
|
const otPath = path.join(inputDir, 'old_testament.json')
|
|
const ntPath = path.join(inputDir, 'new_testament.json')
|
|
if (!fs.existsSync(otPath) || !fs.existsSync(ntPath)) {
|
|
throw new Error(`Missing OT/NT JSON at ${inputDir}. Run fetch-english-bible.ts first.`)
|
|
}
|
|
|
|
// Upsert English version
|
|
const englishVersion = await prisma.bibleVersion.upsert({
|
|
where: { abbreviation_language: { abbreviation: abbr, language: lang } },
|
|
update: {},
|
|
create: {
|
|
name: abbr,
|
|
abbreviation: abbr,
|
|
language: lang,
|
|
description: `English Bible (${abbr})`,
|
|
isDefault: true
|
|
}
|
|
})
|
|
|
|
const ot = loadJson(otPath)
|
|
const nt = loadJson(ntPath)
|
|
const canon = [...ot.books.map(b => b.name), ...nt.books.map(b => b.name)]
|
|
|
|
let importedBooks = 0
|
|
let importedChapters = 0
|
|
let importedVerses = 0
|
|
|
|
async function importTestament(test: TestamentFile) {
|
|
for (const book of test.books) {
|
|
const orderNum = getOrderFromList(book.name, canon)
|
|
const testament = test.testament
|
|
const bookKey = getBookKeyEn(book.name)
|
|
|
|
const createdBook = await prisma.bibleBook.upsert({
|
|
where: {
|
|
versionId_orderNum: {
|
|
versionId: englishVersion.id,
|
|
orderNum
|
|
}
|
|
},
|
|
update: {},
|
|
create: {
|
|
versionId: englishVersion.id,
|
|
name: book.name,
|
|
testament,
|
|
orderNum,
|
|
bookKey
|
|
}
|
|
})
|
|
importedBooks++
|
|
|
|
for (const chapter of book.chapters) {
|
|
const createdChapter = await prisma.bibleChapter.upsert({
|
|
where: {
|
|
bookId_chapterNum: {
|
|
bookId: createdBook.id,
|
|
chapterNum: chapter.chapterNum
|
|
}
|
|
},
|
|
update: {},
|
|
create: { bookId: createdBook.id, chapterNum: chapter.chapterNum }
|
|
})
|
|
importedChapters++
|
|
|
|
// Deduplicate verses by verseNum
|
|
const unique = new Map<number, string>()
|
|
for (const v of chapter.verses) {
|
|
if (!unique.has(v.verseNum)) unique.set(v.verseNum, v.text)
|
|
}
|
|
const versesData = Array.from(unique.entries()).map(([num, text]) => ({
|
|
chapterId: createdChapter.id,
|
|
verseNum: num,
|
|
text
|
|
}))
|
|
if (versesData.length > 0) {
|
|
await prisma.bibleVerse.createMany({ data: versesData, skipDuplicates: true })
|
|
importedVerses += versesData.length
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
await importTestament(ot)
|
|
await importTestament(nt)
|
|
|
|
console.log(`Imported ${importedBooks} books, ${importedChapters} chapters, ${importedVerses} verses for ${abbr}.`)
|
|
} catch (e) {
|
|
console.error('English JSON import failed:', e)
|
|
process.exit(1)
|
|
} finally {
|
|
await prisma.$disconnect()
|
|
}
|
|
}
|
|
|
|
main()
|
|
|