Add comprehensive page management system to admin dashboard
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>
This commit is contained in:
281
scripts/old/import-api-bible.ts
Normal file
281
scripts/old/import-api-bible.ts
Normal file
@@ -0,0 +1,281 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
interface ApiBibleBook {
|
||||
id: string
|
||||
bibleId: string
|
||||
abbreviation: string
|
||||
name: string
|
||||
nameLong: string
|
||||
}
|
||||
|
||||
interface ApiBibleChapter {
|
||||
id: string
|
||||
bibleId: string
|
||||
bookId: string
|
||||
number: string
|
||||
reference: string
|
||||
}
|
||||
|
||||
interface ApiBibleVerse {
|
||||
id: string
|
||||
orgId: string
|
||||
bookId: string
|
||||
chapterId: string
|
||||
bibleId: string
|
||||
reference: string
|
||||
content: string
|
||||
verseCount: number
|
||||
}
|
||||
|
||||
interface ApiBibleResponse<T> {
|
||||
data: T
|
||||
}
|
||||
|
||||
const API_KEY = process.env.API_BIBLE_KEY || '7b42606f8f809e155c9b0742c4f1849b'
|
||||
const API_BASE = 'https://api.scripture.api.bible/v1'
|
||||
|
||||
// English Bible for standard structure
|
||||
const BIBLE_ID = 'bba9f40183526463-01' // Berean Standard Bible
|
||||
|
||||
async function apiFetch<T>(endpoint: string): Promise<T> {
|
||||
const response = await fetch(`${API_BASE}${endpoint}`, {
|
||||
headers: {
|
||||
'api-key': API_KEY
|
||||
}
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API request failed: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
return data
|
||||
}
|
||||
|
||||
function cleanHtmlContent(htmlContent: string): string {
|
||||
// Remove HTML tags and extract plain text
|
||||
return htmlContent
|
||||
.replace(/<[^>]*>/g, '') // Remove HTML tags
|
||||
.replace(/\s+/g, ' ') // Normalize whitespace
|
||||
.replace(/^\d+\s*/, '') // Remove verse numbers at start
|
||||
.trim()
|
||||
}
|
||||
|
||||
function parseVerseNumber(verseId: string): number {
|
||||
// Extract verse number from ID like "GEN.1.5"
|
||||
const parts = verseId.split('.')
|
||||
return parseInt(parts[2]) || 1
|
||||
}
|
||||
|
||||
function parseChapterNumber(chapterId: string): number {
|
||||
// Extract chapter number from ID like "GEN.1"
|
||||
const parts = chapterId.split('.')
|
||||
return parseInt(parts[1]) || 1
|
||||
}
|
||||
|
||||
function getTestament(bookId: string): string {
|
||||
// Old Testament books (standard order)
|
||||
const oldTestamentBooks = [
|
||||
'GEN', 'EXO', 'LEV', 'NUM', 'DEU', 'JOS', 'JDG', 'RUT',
|
||||
'1SA', '2SA', '1KI', '2KI', '1CH', '2CH', 'EZR', 'NEH',
|
||||
'EST', 'JOB', 'PSA', 'PRO', 'ECC', 'SNG', 'ISA', 'JER',
|
||||
'LAM', 'EZK', 'DAN', 'HOS', 'JOL', 'AMO', 'OBA', 'JON',
|
||||
'MIC', 'NAM', 'HAB', 'ZEP', 'HAG', 'ZEC', 'MAL'
|
||||
]
|
||||
|
||||
return oldTestamentBooks.includes(bookId) ? 'Old Testament' : 'New Testament'
|
||||
}
|
||||
|
||||
function getBookOrderNumber(bookId: string): number {
|
||||
// Standard Biblical book order
|
||||
const bookOrder: Record<string, number> = {
|
||||
// Old Testament
|
||||
'GEN': 1, 'EXO': 2, 'LEV': 3, 'NUM': 4, 'DEU': 5, 'JOS': 6, 'JDG': 7, 'RUT': 8,
|
||||
'1SA': 9, '2SA': 10, '1KI': 11, '2KI': 12, '1CH': 13, '2CH': 14, 'EZR': 15, 'NEH': 16,
|
||||
'EST': 17, 'JOB': 18, 'PSA': 19, 'PRO': 20, 'ECC': 21, 'SNG': 22, 'ISA': 23, 'JER': 24,
|
||||
'LAM': 25, 'EZK': 26, 'DAN': 27, 'HOS': 28, 'JOL': 29, 'AMO': 30, 'OBA': 31, 'JON': 32,
|
||||
'MIC': 33, 'NAM': 34, 'HAB': 35, 'ZEP': 36, 'HAG': 37, 'ZEC': 38, 'MAL': 39,
|
||||
// New Testament
|
||||
'MAT': 40, 'MRK': 41, 'LUK': 42, 'JHN': 43, 'ACT': 44, 'ROM': 45, '1CO': 46, '2CO': 47,
|
||||
'GAL': 48, 'EPH': 49, 'PHP': 50, 'COL': 51, '1TH': 52, '2TH': 53, '1TI': 54, '2TI': 55,
|
||||
'TIT': 56, 'PHM': 57, 'HEB': 58, 'JAS': 59, '1PE': 60, '2PE': 61, '1JN': 62, '2JN': 63,
|
||||
'3JN': 64, 'JUD': 65, 'REV': 66
|
||||
}
|
||||
|
||||
return bookOrder[bookId] || 999
|
||||
}
|
||||
|
||||
async function importFromApiBible() {
|
||||
console.log('Starting API.Bible import...')
|
||||
|
||||
try {
|
||||
// Get all books for the Bible
|
||||
console.log('Fetching books...')
|
||||
const booksResponse = await apiFetch<ApiBibleResponse<ApiBibleBook[]>>(`/bibles/${BIBLE_ID}/books`)
|
||||
const books = booksResponse.data.filter(book =>
|
||||
book.id !== 'INT' && // Skip introduction
|
||||
!book.id.includes('intro') // Skip intro chapters
|
||||
)
|
||||
|
||||
console.log(`Found ${books.length} books`)
|
||||
|
||||
let totalVersesImported = 0
|
||||
|
||||
for (const book of books.slice(0, 10)) { // Import first 10 books
|
||||
console.log(`Processing ${book.name} (${book.id})...`)
|
||||
|
||||
const orderNum = getBookOrderNumber(book.id)
|
||||
const testament = getTestament(book.id)
|
||||
|
||||
// Create book
|
||||
const createdBook = await prisma.bibleBook.upsert({
|
||||
where: { id: orderNum },
|
||||
update: {},
|
||||
create: {
|
||||
id: orderNum,
|
||||
name: book.name,
|
||||
testament: testament,
|
||||
orderNum: orderNum
|
||||
}
|
||||
})
|
||||
|
||||
// Get chapters for this book
|
||||
const chaptersResponse = await apiFetch<ApiBibleResponse<ApiBibleChapter[]>>(`/bibles/${BIBLE_ID}/books/${book.id}/chapters`)
|
||||
const chapters = chaptersResponse.data.filter(chapter =>
|
||||
chapter.number !== 'intro' && // Skip introduction chapters
|
||||
!isNaN(parseInt(chapter.number)) // Only numeric chapters
|
||||
)
|
||||
|
||||
console.log(` Found ${chapters.length} chapters`)
|
||||
|
||||
for (const chapter of chapters.slice(0, 5)) { // Import first 5 chapters
|
||||
const chapterNum = parseChapterNumber(chapter.id)
|
||||
|
||||
console.log(` Processing chapter ${chapterNum}...`)
|
||||
|
||||
// Create chapter
|
||||
const createdChapter = await prisma.bibleChapter.upsert({
|
||||
where: {
|
||||
bookId_chapterNum: {
|
||||
bookId: orderNum,
|
||||
chapterNum: chapterNum
|
||||
}
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
bookId: orderNum,
|
||||
chapterNum: chapterNum
|
||||
}
|
||||
})
|
||||
|
||||
// Get verses for this chapter
|
||||
const versesResponse = await apiFetch<ApiBibleResponse<ApiBibleVerse[]>>(`/bibles/${BIBLE_ID}/chapters/${chapter.id}/verses`)
|
||||
|
||||
console.log(` Found ${versesResponse.data.length} verses`)
|
||||
|
||||
// Process all verses
|
||||
const allVerses = versesResponse.data
|
||||
for (let i = 0; i < allVerses.length; i += 10) {
|
||||
const verseBatch = allVerses.slice(i, i + 10)
|
||||
|
||||
for (const verseRef of verseBatch) {
|
||||
try {
|
||||
// Get full verse content
|
||||
const verseResponse = await apiFetch<ApiBibleResponse<ApiBibleVerse>>(`/bibles/${BIBLE_ID}/verses/${verseRef.id}`)
|
||||
const verse = verseResponse.data
|
||||
|
||||
const verseNum = parseVerseNumber(verse.id)
|
||||
const cleanText = cleanHtmlContent(verse.content)
|
||||
|
||||
if (cleanText.length > 0) {
|
||||
// Create verse
|
||||
await prisma.bibleVerse.upsert({
|
||||
where: {
|
||||
chapterId_verseNum_version: {
|
||||
chapterId: createdChapter.id,
|
||||
verseNum: verseNum,
|
||||
version: 'EN'
|
||||
}
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
chapterId: createdChapter.id,
|
||||
verseNum: verseNum,
|
||||
text: cleanText,
|
||||
version: 'EN'
|
||||
}
|
||||
})
|
||||
|
||||
totalVersesImported++
|
||||
}
|
||||
|
||||
// Rate limiting - small delay between requests
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
|
||||
} catch (error) {
|
||||
console.warn(` Warning: Failed to fetch verse ${verseRef.id}:`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nAPI.Bible import completed! Imported ${totalVersesImported} verses.`)
|
||||
|
||||
// Create search function for English content
|
||||
console.log('Creating English search function...')
|
||||
await prisma.$executeRaw`
|
||||
CREATE OR REPLACE FUNCTION search_verses_en(search_query TEXT, limit_count INT DEFAULT 10)
|
||||
RETURNS TABLE(
|
||||
verse_id TEXT,
|
||||
book_name TEXT,
|
||||
chapter_num INT,
|
||||
verse_num INT,
|
||||
verse_text TEXT,
|
||||
rank REAL
|
||||
) AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
v.id::TEXT,
|
||||
b.name,
|
||||
c."chapterNum",
|
||||
v."verseNum",
|
||||
v.text,
|
||||
CASE
|
||||
WHEN v.text ILIKE '%' || search_query || '%' THEN 1.0
|
||||
ELSE ts_rank(to_tsvector('english', v.text), plainto_tsquery('english', search_query))
|
||||
END as rank
|
||||
FROM "BibleVerse" v
|
||||
JOIN "BibleChapter" c ON v."chapterId" = c.id
|
||||
JOIN "BibleBook" b ON c."bookId" = b.id
|
||||
WHERE v.version = 'EN'
|
||||
AND (v.text ILIKE '%' || search_query || '%'
|
||||
OR to_tsvector('english', v.text) @@ plainto_tsquery('english', search_query))
|
||||
ORDER BY rank DESC, b."orderNum", c."chapterNum", v."verseNum"
|
||||
LIMIT limit_count;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
`
|
||||
|
||||
console.log('English search function created successfully!')
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error importing from API.Bible:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Run the import
|
||||
importFromApiBible()
|
||||
.then(() => {
|
||||
console.log('API.Bible import completed successfully!')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Import failed:', error)
|
||||
process.exit(1)
|
||||
})
|
||||
.finally(() => prisma.$disconnect())
|
||||
Reference in New Issue
Block a user