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>) 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())