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:
378
scripts/old/import-romanian-bible.ts
Normal file
378
scripts/old/import-romanian-bible.ts
Normal file
@@ -0,0 +1,378 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import pdfParse from 'pdf-parse'
|
||||
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
interface BibleVerse {
|
||||
book: string
|
||||
chapter: number
|
||||
verse: number
|
||||
text: string
|
||||
}
|
||||
|
||||
interface BibleBook {
|
||||
name: string
|
||||
testament: string
|
||||
orderNum: number
|
||||
}
|
||||
|
||||
// Romanian Bible book names mapping
|
||||
const romanianBooks: BibleBook[] = [
|
||||
// Old Testament
|
||||
{ name: 'Geneza', testament: 'Vechiul Testament', orderNum: 1 },
|
||||
{ name: 'Exodul', testament: 'Vechiul Testament', orderNum: 2 },
|
||||
{ name: 'Leviticul', testament: 'Vechiul Testament', orderNum: 3 },
|
||||
{ name: 'Numerii', testament: 'Vechiul Testament', orderNum: 4 },
|
||||
{ name: 'Deuteronomul', testament: 'Vechiul Testament', orderNum: 5 },
|
||||
{ name: 'Iosua', testament: 'Vechiul Testament', orderNum: 6 },
|
||||
{ name: 'Judecătorii', testament: 'Vechiul Testament', orderNum: 7 },
|
||||
{ name: 'Rut', testament: 'Vechiul Testament', orderNum: 8 },
|
||||
{ name: '1 Samuel', testament: 'Vechiul Testament', orderNum: 9 },
|
||||
{ name: '2 Samuel', testament: 'Vechiul Testament', orderNum: 10 },
|
||||
{ name: '1 Regi', testament: 'Vechiul Testament', orderNum: 11 },
|
||||
{ name: '2 Regi', testament: 'Vechiul Testament', orderNum: 12 },
|
||||
{ name: '1 Cronici', testament: 'Vechiul Testament', orderNum: 13 },
|
||||
{ name: '2 Cronici', testament: 'Vechiul Testament', orderNum: 14 },
|
||||
{ name: 'Ezra', testament: 'Vechiul Testament', orderNum: 15 },
|
||||
{ name: 'Neemia', testament: 'Vechiul Testament', orderNum: 16 },
|
||||
{ name: 'Estera', testament: 'Vechiul Testament', orderNum: 17 },
|
||||
{ name: 'Iov', testament: 'Vechiul Testament', orderNum: 18 },
|
||||
{ name: 'Psalmii', testament: 'Vechiul Testament', orderNum: 19 },
|
||||
{ name: 'Proverbele', testament: 'Vechiul Testament', orderNum: 20 },
|
||||
{ name: 'Ecleziastul', testament: 'Vechiul Testament', orderNum: 21 },
|
||||
{ name: 'Cântarea Cântărilor', testament: 'Vechiul Testament', orderNum: 22 },
|
||||
{ name: 'Isaia', testament: 'Vechiul Testament', orderNum: 23 },
|
||||
{ name: 'Ieremia', testament: 'Vechiul Testament', orderNum: 24 },
|
||||
{ name: 'Plângerile', testament: 'Vechiul Testament', orderNum: 25 },
|
||||
{ name: 'Ezechiel', testament: 'Vechiul Testament', orderNum: 26 },
|
||||
{ name: 'Daniel', testament: 'Vechiul Testament', orderNum: 27 },
|
||||
{ name: 'Osea', testament: 'Vechiul Testament', orderNum: 28 },
|
||||
{ name: 'Ioel', testament: 'Vechiul Testament', orderNum: 29 },
|
||||
{ name: 'Amos', testament: 'Vechiul Testament', orderNum: 30 },
|
||||
{ name: 'Obadia', testament: 'Vechiul Testament', orderNum: 31 },
|
||||
{ name: 'Iona', testament: 'Vechiul Testament', orderNum: 32 },
|
||||
{ name: 'Mica', testament: 'Vechiul Testament', orderNum: 33 },
|
||||
{ name: 'Naum', testament: 'Vechiul Testament', orderNum: 34 },
|
||||
{ name: 'Habacuc', testament: 'Vechiul Testament', orderNum: 35 },
|
||||
{ name: 'Ţefania', testament: 'Vechiul Testament', orderNum: 36 },
|
||||
{ name: 'Hagai', testament: 'Vechiul Testament', orderNum: 37 },
|
||||
{ name: 'Zaharia', testament: 'Vechiul Testament', orderNum: 38 },
|
||||
{ name: 'Maleahi', testament: 'Vechiul Testament', orderNum: 39 },
|
||||
|
||||
// New Testament
|
||||
{ name: 'Matei', testament: 'Noul Testament', orderNum: 40 },
|
||||
{ name: 'Marcu', testament: 'Noul Testament', orderNum: 41 },
|
||||
{ name: 'Luca', testament: 'Noul Testament', orderNum: 42 },
|
||||
{ name: 'Ioan', testament: 'Noul Testament', orderNum: 43 },
|
||||
{ name: 'Faptele Apostolilor', testament: 'Noul Testament', orderNum: 44 },
|
||||
{ name: 'Romani', testament: 'Noul Testament', orderNum: 45 },
|
||||
{ name: '1 Corinteni', testament: 'Noul Testament', orderNum: 46 },
|
||||
{ name: '2 Corinteni', testament: 'Noul Testament', orderNum: 47 },
|
||||
{ name: 'Galateni', testament: 'Noul Testament', orderNum: 48 },
|
||||
{ name: 'Efeseni', testament: 'Noul Testament', orderNum: 49 },
|
||||
{ name: 'Filipeni', testament: 'Noul Testament', orderNum: 50 },
|
||||
{ name: 'Coloseni', testament: 'Noul Testament', orderNum: 51 },
|
||||
{ name: '1 Tesaloniceni', testament: 'Noul Testament', orderNum: 52 },
|
||||
{ name: '2 Tesaloniceni', testament: 'Noul Testament', orderNum: 53 },
|
||||
{ name: '1 Timotei', testament: 'Noul Testament', orderNum: 54 },
|
||||
{ name: '2 Timotei', testament: 'Noul Testament', orderNum: 55 },
|
||||
{ name: 'Tit', testament: 'Noul Testament', orderNum: 56 },
|
||||
{ name: 'Filimon', testament: 'Noul Testament', orderNum: 57 },
|
||||
{ name: 'Evrei', testament: 'Noul Testament', orderNum: 58 },
|
||||
{ name: 'Iacob', testament: 'Noul Testament', orderNum: 59 },
|
||||
{ name: '1 Petru', testament: 'Noul Testament', orderNum: 60 },
|
||||
{ name: '2 Petru', testament: 'Noul Testament', orderNum: 61 },
|
||||
{ name: '1 Ioan', testament: 'Noul Testament', orderNum: 62 },
|
||||
{ name: '2 Ioan', testament: 'Noul Testament', orderNum: 63 },
|
||||
{ name: '3 Ioan', testament: 'Noul Testament', orderNum: 64 },
|
||||
{ name: 'Iuda', testament: 'Noul Testament', orderNum: 65 },
|
||||
{ name: 'Apocalipsa', testament: 'Noul Testament', orderNum: 66 }
|
||||
]
|
||||
|
||||
function parseRomanianBible(text: string): BibleVerse[] {
|
||||
const verses: BibleVerse[] = []
|
||||
|
||||
// Remove common headers/footers and normalize text
|
||||
const cleanText = text
|
||||
.replace(/BIBLIA\s+FIDELA/gi, '')
|
||||
.replace(/Copyright.*?România/gi, '')
|
||||
.replace(/Cluj-Napoca.*?\d{4}/gi, '')
|
||||
.replace(/\d+\s*$/gm, '') // Remove page numbers at end of lines
|
||||
.replace(/\s+/g, ' ') // Normalize whitespace
|
||||
.trim()
|
||||
|
||||
console.log('Cleaned text preview:', cleanText.substring(0, 2000))
|
||||
|
||||
// Look for patterns like "Geneza 1:1" or "1:1" with verse text
|
||||
// First, split by book names to identify sections
|
||||
const bookSections: { book: string, content: string }[] = []
|
||||
|
||||
let currentContent = cleanText
|
||||
|
||||
for (const book of romanianBooks) {
|
||||
// Look for book name followed by chapter/verse patterns
|
||||
const bookPattern = new RegExp(`\\b${book.name}\\b`, 'gi')
|
||||
const bookMatch = currentContent.search(bookPattern)
|
||||
|
||||
if (bookMatch !== -1) {
|
||||
// Extract content for this book (until next book or end)
|
||||
let nextBookStart = currentContent.length
|
||||
|
||||
for (const nextBook of romanianBooks) {
|
||||
if (nextBook.orderNum > book.orderNum) {
|
||||
const nextPattern = new RegExp(`\\b${nextBook.name}\\b`, 'gi')
|
||||
const nextMatch = currentContent.search(nextPattern)
|
||||
if (nextMatch > bookMatch && nextMatch < nextBookStart) {
|
||||
nextBookStart = nextMatch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bookContent = currentContent.substring(bookMatch, nextBookStart)
|
||||
bookSections.push({ book: book.name, content: bookContent })
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Found ${bookSections.length} book sections`)
|
||||
|
||||
// Parse each book section
|
||||
for (const section of bookSections) {
|
||||
console.log(`Parsing ${section.book}...`)
|
||||
|
||||
// Look for chapter:verse patterns like "1:1", "1:2", etc.
|
||||
const versePattern = /(\d+):(\d+)\s+([^0-9:]+?)(?=\d+:\d+|$)/g
|
||||
let match
|
||||
|
||||
while ((match = versePattern.exec(section.content)) !== null) {
|
||||
const chapter = parseInt(match[1])
|
||||
const verse = parseInt(match[2])
|
||||
const text = match[3].trim()
|
||||
|
||||
// Clean up the verse text
|
||||
const cleanVerseText = text
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/^\W+|\W+$/g, '') // Remove leading/trailing non-word chars
|
||||
.trim()
|
||||
|
||||
if (cleanVerseText.length > 5) { // Only keep substantial text
|
||||
verses.push({
|
||||
book: section.book,
|
||||
chapter: chapter,
|
||||
verse: verse,
|
||||
text: cleanVerseText
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Alternative: look for numbered verses within paragraphs
|
||||
const numberedVersePattern = /(\d+)\s+([^0-9]+?)(?=\d+\s+|$)/g
|
||||
let altMatch
|
||||
let currentChapter = 1
|
||||
|
||||
// Try to find chapter indicators
|
||||
const chapterPattern = /Capitolul\s+(\d+)|^(\d+)$/gm
|
||||
const chapterMatches = [...section.content.matchAll(chapterPattern)]
|
||||
|
||||
if (chapterMatches.length > 0) {
|
||||
for (const chMatch of chapterMatches) {
|
||||
currentChapter = parseInt(chMatch[1] || chMatch[2])
|
||||
|
||||
// Find content after this chapter marker
|
||||
const chapterStart = chMatch.index! + chMatch[0].length
|
||||
let chapterEnd = section.content.length
|
||||
|
||||
// Find next chapter marker
|
||||
for (const nextChMatch of chapterMatches) {
|
||||
if (nextChMatch.index! > chMatch.index! && nextChMatch.index! < chapterEnd) {
|
||||
chapterEnd = nextChMatch.index!
|
||||
}
|
||||
}
|
||||
|
||||
const chapterContent = section.content.substring(chapterStart, chapterEnd)
|
||||
|
||||
// Parse verses in this chapter
|
||||
while ((altMatch = numberedVersePattern.exec(chapterContent)) !== null) {
|
||||
const verseNum = parseInt(altMatch[1])
|
||||
const verseText = altMatch[2].trim()
|
||||
|
||||
const cleanText = verseText
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/^\W+|\W+$/g, '')
|
||||
.trim()
|
||||
|
||||
if (cleanText.length > 10) {
|
||||
verses.push({
|
||||
book: section.book,
|
||||
chapter: currentChapter,
|
||||
verse: verseNum,
|
||||
text: cleanText
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return verses
|
||||
}
|
||||
|
||||
async function importRomanianBible() {
|
||||
console.log('Starting Romanian Bible import...')
|
||||
|
||||
const pdfPath = path.join(process.cwd(), 'bibles', 'Biblia-Fidela-limba-romana.pdf')
|
||||
|
||||
if (!fs.existsSync(pdfPath)) {
|
||||
throw new Error(`PDF file not found at: ${pdfPath}`)
|
||||
}
|
||||
|
||||
console.log('Reading PDF file...')
|
||||
const pdfBuffer = fs.readFileSync(pdfPath)
|
||||
const pdfData = await pdfParse(pdfBuffer)
|
||||
|
||||
console.log(`PDF parsed. Text length: ${pdfData.text.length} characters`)
|
||||
|
||||
console.log('Parsing Bible verses...')
|
||||
const verses = parseRomanianBible(pdfData.text)
|
||||
console.log(`Found ${verses.length} verses`)
|
||||
|
||||
if (verses.length === 0) {
|
||||
console.log('No verses found. PDF content preview:')
|
||||
console.log(pdfData.text.substring(0, 1000))
|
||||
throw new Error('Could not parse any verses from the PDF')
|
||||
}
|
||||
|
||||
try {
|
||||
// First, create all books
|
||||
console.log('Creating Bible books...')
|
||||
for (const bookData of romanianBooks) {
|
||||
await prisma.bibleBook.upsert({
|
||||
where: { id: bookData.orderNum },
|
||||
update: {},
|
||||
create: {
|
||||
id: bookData.orderNum,
|
||||
name: bookData.name,
|
||||
testament: bookData.testament,
|
||||
orderNum: bookData.orderNum
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Group verses by book and chapter
|
||||
const versesByBook = verses.reduce((acc, verse) => {
|
||||
if (!acc[verse.book]) acc[verse.book] = {}
|
||||
if (!acc[verse.book][verse.chapter]) acc[verse.book][verse.chapter] = []
|
||||
acc[verse.book][verse.chapter].push(verse)
|
||||
return acc
|
||||
}, {} as Record<string, Record<number, BibleVerse[]>>)
|
||||
|
||||
console.log('Importing verses by book and chapter...')
|
||||
let totalImported = 0
|
||||
|
||||
for (const [bookName, chapters] of Object.entries(versesByBook)) {
|
||||
const book = romanianBooks.find(b => b.name === bookName)
|
||||
if (!book) {
|
||||
console.warn(`Unknown book: ${bookName}`)
|
||||
continue
|
||||
}
|
||||
|
||||
console.log(`Importing ${bookName}...`)
|
||||
|
||||
for (const [chapterNumStr, chapterVerses] of Object.entries(chapters)) {
|
||||
const chapterNum = parseInt(chapterNumStr)
|
||||
|
||||
// Create chapter
|
||||
const chapter = await prisma.bibleChapter.upsert({
|
||||
where: {
|
||||
bookId_chapterNum: {
|
||||
bookId: book.orderNum,
|
||||
chapterNum: chapterNum
|
||||
}
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
bookId: book.orderNum,
|
||||
chapterNum: chapterNum
|
||||
}
|
||||
})
|
||||
|
||||
// Create verses
|
||||
for (const verse of chapterVerses) {
|
||||
await prisma.bibleVerse.upsert({
|
||||
where: {
|
||||
chapterId_verseNum_version: {
|
||||
chapterId: chapter.id,
|
||||
verseNum: verse.verse,
|
||||
version: 'RO'
|
||||
}
|
||||
},
|
||||
update: {},
|
||||
create: {
|
||||
chapterId: chapter.id,
|
||||
verseNum: verse.verse,
|
||||
text: verse.text,
|
||||
version: 'RO'
|
||||
}
|
||||
})
|
||||
totalImported++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Romanian Bible import completed! Imported ${totalImported} verses.`)
|
||||
|
||||
// Create search function
|
||||
console.log('Creating search function...')
|
||||
await prisma.$executeRaw`
|
||||
CREATE OR REPLACE FUNCTION search_verses(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('romanian', v.text), plainto_tsquery('romanian', 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.text ILIKE '%' || search_query || '%'
|
||||
OR to_tsvector('romanian', v.text) @@ plainto_tsquery('romanian', search_query)
|
||||
ORDER BY rank DESC, b."orderNum", c."chapterNum", v."verseNum"
|
||||
LIMIT limit_count;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
`
|
||||
|
||||
console.log('Search function created successfully!')
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error importing Romanian Bible:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Run the import
|
||||
importRomanianBible()
|
||||
.then(() => {
|
||||
console.log('Romanian 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