Reubencf commited on
Commit
bf703ad
·
verified ·
1 Parent(s): 15825d3

Upload 10 files

Browse files
app/api/bible/[id]/route.ts CHANGED
@@ -1,18 +1,11 @@
1
  import { NextRequest, NextResponse } from 'next/server'
2
  import { BOOKS, idToBookChapter } from '../../../lib/books'
 
3
 
4
- // In-memory cache with 24 hour TTL (better for Hugging Face Spaces)
5
  const cache = new Map<string, { data: BibleEntry; timestamp: number }>()
6
  const CACHE_TTL = 24 * 60 * 60 * 1000 // 24 hours
7
 
8
- interface BibleEntry {
9
- id: number
10
- chapter_name: string
11
- chapter_no: number
12
- english_chapter: string
13
- konkani_chapter: string
14
- }
15
-
16
  export async function GET(
17
  request: NextRequest,
18
  { params }: { params: Promise<{ id: string }> }
@@ -46,75 +39,9 @@ export async function GET(
46
  return NextResponse.json(cached.data)
47
  }
48
 
49
- // Try to fetch chapter data with backoff
50
- const maxRetries = 3
51
- let retryCount = 0
52
- let chapterData: BibleEntry | null = null
53
 
54
- while (retryCount < maxRetries && !chapterData) {
55
- try {
56
- // Search through dataset to find the matching chapter
57
- const estimatedOffset = Math.max(0, (bookIndex * 30) + (chapter - 1))
58
- const limit = 100
59
-
60
- const response = await fetch(
61
- `https://datasets-server.huggingface.co/rows?dataset=Reubencf/Konkani_bible&config=default&split=train&offset=${estimatedOffset}&limit=${limit}`,
62
- {
63
- headers: {
64
- 'Accept': 'application/json',
65
- },
66
- }
67
- )
68
-
69
- if (response.status === 429) {
70
- // Rate limited - wait and retry
71
- retryCount++
72
- if (retryCount < maxRetries) {
73
- await new Promise(resolve => setTimeout(resolve, 2000 * retryCount))
74
- continue
75
- } else {
76
- throw new Error('Rate limit exceeded. Please try again later.')
77
- }
78
- }
79
-
80
- if (!response.ok) {
81
- throw new Error(`HTTP error! status: ${response.status}`)
82
- }
83
-
84
- const data = await response.json()
85
-
86
- if (!data.rows || data.rows.length === 0) {
87
- break
88
- }
89
-
90
- // Find the matching chapter
91
- const matchingRow = data.rows
92
- .map((row: { row: Record<string, unknown> }) => row.row)
93
- .find((row: Record<string, unknown>) =>
94
- row.chapter_name === book.name &&
95
- parseInt(String(row.chapter_no)) === chapter
96
- )
97
-
98
- if (matchingRow) {
99
- chapterData = {
100
- id: entryId,
101
- chapter_name: matchingRow.chapter_name || book.name,
102
- chapter_no: parseInt(matchingRow.chapter_no) || chapter,
103
- english_chapter: matchingRow.english_chapter || '',
104
- konkani_chapter: matchingRow.konkani_chapter || ''
105
- }
106
- break
107
- }
108
-
109
- } catch (error) {
110
- if (retryCount === maxRetries - 1) {
111
- throw error
112
- }
113
- retryCount++
114
- await new Promise(resolve => setTimeout(resolve, 1000 * retryCount))
115
- }
116
- }
117
-
118
  if (!chapterData) {
119
  return NextResponse.json(
120
  { error: 'Chapter not found' },
@@ -122,10 +49,16 @@ export async function GET(
122
  )
123
  }
124
 
 
 
 
 
 
 
125
  // Cache the chapter data
126
- cache.set(cacheKey, { data: chapterData, timestamp: Date.now() })
127
 
128
- return NextResponse.json(chapterData)
129
 
130
  } catch (error) {
131
  console.error('Error fetching Bible entry:', error)
 
1
  import { NextRequest, NextResponse } from 'next/server'
2
  import { BOOKS, idToBookChapter } from '../../../lib/books'
3
+ import { findChapterByBookAndNumber, BibleEntry } from '../../../lib/csvReader'
4
 
5
+ // In-memory cache with 24 hour TTL
6
  const cache = new Map<string, { data: BibleEntry; timestamp: number }>()
7
  const CACHE_TTL = 24 * 60 * 60 * 1000 // 24 hours
8
 
 
 
 
 
 
 
 
 
9
  export async function GET(
10
  request: NextRequest,
11
  { params }: { params: Promise<{ id: string }> }
 
39
  return NextResponse.json(cached.data)
40
  }
41
 
42
+ // Find chapter data from CSV
43
+ const chapterData = findChapterByBookAndNumber(book.name, chapter)
 
 
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  if (!chapterData) {
46
  return NextResponse.json(
47
  { error: 'Chapter not found' },
 
49
  )
50
  }
51
 
52
+ // Update the ID to match the expected ID from the request
53
+ const responseData: BibleEntry = {
54
+ ...chapterData,
55
+ id: entryId
56
+ }
57
+
58
  // Cache the chapter data
59
+ cache.set(cacheKey, { data: responseData, timestamp: Date.now() })
60
 
61
+ return NextResponse.json(responseData)
62
 
63
  } catch (error) {
64
  console.error('Error fetching Bible entry:', error)
app/lib/csvReader.ts ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ export interface BibleEntry {
5
+ id: number;
6
+ chapter_name: string;
7
+ chapter_no: number;
8
+ english_chapter: string;
9
+ konkani_chapter: string;
10
+ }
11
+
12
+ // Cache for parsed CSV data
13
+ let csvData: BibleEntry[] | null = null;
14
+
15
+ // Parse CSV manually to handle multiline content properly
16
+ function parseCSVLine(line: string): string[] {
17
+ const result: string[] = [];
18
+ let current = '';
19
+ let inQuotes = false;
20
+ let i = 0;
21
+
22
+ while (i < line.length) {
23
+ const char = line[i];
24
+
25
+ if (char === '"') {
26
+ if (inQuotes && line[i + 1] === '"') {
27
+ // Escaped quote
28
+ current += '"';
29
+ i += 2;
30
+ } else {
31
+ // Toggle quote state
32
+ inQuotes = !inQuotes;
33
+ i++;
34
+ }
35
+ } else if (char === ',' && !inQuotes) {
36
+ // Field separator
37
+ result.push(current.trim());
38
+ current = '';
39
+ i++;
40
+ } else {
41
+ current += char;
42
+ i++;
43
+ }
44
+ }
45
+
46
+ // Add the last field
47
+ result.push(current.trim());
48
+ return result;
49
+ }
50
+
51
+ function loadCSVData(): BibleEntry[] {
52
+ if (csvData) {
53
+ return csvData;
54
+ }
55
+
56
+ try {
57
+ const csvPath = path.join(process.cwd(), 'bible_chapters_csv.csv');
58
+ const fileContent = fs.readFileSync(csvPath, 'utf-8');
59
+
60
+ // Remove BOM if present
61
+ const cleanContent = fileContent.replace(/^\uFEFF/, '');
62
+
63
+ csvData = [];
64
+
65
+ // Split by entries - each entry starts with a book name followed by a number
66
+ // We'll look for patterns like "Matthew,1," or "Mark,2," etc
67
+ const entryPattern = /^([^,]+),(\d+),/;
68
+ const lines = cleanContent.split('\n');
69
+
70
+ let currentEntry: Partial<BibleEntry> = {};
71
+ let currentText = '';
72
+ let fieldIndex = 0; // 0 = reading english, 1 = reading konkani
73
+
74
+ for (let i = 1; i < lines.length; i++) { // Skip header
75
+ const line = lines[i];
76
+
77
+ if (!line.trim()) continue;
78
+
79
+ const match = line.match(entryPattern);
80
+
81
+ if (match) {
82
+ // Save previous entry if exists
83
+ if (currentEntry.chapter_name && currentText) {
84
+ if (fieldIndex === 0) {
85
+ currentEntry.english_chapter = currentText.trim();
86
+ } else {
87
+ currentEntry.konkani_chapter = currentText.trim();
88
+ }
89
+
90
+ const entry: BibleEntry = {
91
+ id: csvData.length + 1,
92
+ chapter_name: currentEntry.chapter_name!,
93
+ chapter_no: currentEntry.chapter_no!,
94
+ english_chapter: currentEntry.english_chapter || '',
95
+ konkani_chapter: currentEntry.konkani_chapter || ''
96
+ };
97
+ csvData.push(entry);
98
+ }
99
+
100
+ // Start new entry
101
+ currentEntry = {
102
+ chapter_name: match[1],
103
+ chapter_no: parseInt(match[2])
104
+ };
105
+
106
+ // Get the rest of the line after the book name and chapter number
107
+ const restOfLine = line.substring(match[0].length);
108
+ currentText = restOfLine.startsWith('"') ? restOfLine.substring(1) : restOfLine;
109
+ fieldIndex = 0;
110
+
111
+ // Check if this line contains both english and konkani (separated by quote and comma)
112
+ const splitPoint = currentText.indexOf('","');
113
+ if (splitPoint > -1) {
114
+ currentEntry.english_chapter = currentText.substring(0, splitPoint);
115
+ currentEntry.konkani_chapter = currentText.substring(splitPoint + 3);
116
+
117
+ // Remove trailing quote if present
118
+ if (currentEntry.konkani_chapter.endsWith('"')) {
119
+ currentEntry.konkani_chapter = currentEntry.konkani_chapter.slice(0, -1);
120
+ }
121
+
122
+ const entry: BibleEntry = {
123
+ id: csvData.length + 1,
124
+ chapter_name: currentEntry.chapter_name,
125
+ chapter_no: currentEntry.chapter_no,
126
+ english_chapter: currentEntry.english_chapter,
127
+ konkani_chapter: currentEntry.konkani_chapter
128
+ };
129
+ csvData.push(entry);
130
+
131
+ currentEntry = {};
132
+ currentText = '';
133
+ }
134
+ } else {
135
+ // Continuation of current field
136
+ if (currentText) {
137
+ currentText += '\n' + line;
138
+ } else {
139
+ currentText = line;
140
+ }
141
+
142
+ // Check for transition from english to konkani (marked by ","... pattern)
143
+ const transitionMatch = currentText.match(/^(.*?)","(.*)$/s);
144
+ if (transitionMatch) {
145
+ currentEntry.english_chapter = transitionMatch[1];
146
+ currentText = transitionMatch[2];
147
+ fieldIndex = 1;
148
+
149
+ // Remove trailing quote if present
150
+ if (currentText.endsWith('"')) {
151
+ currentEntry.konkani_chapter = currentText.slice(0, -1);
152
+
153
+ const entry: BibleEntry = {
154
+ id: csvData.length + 1,
155
+ chapter_name: currentEntry.chapter_name!,
156
+ chapter_no: currentEntry.chapter_no!,
157
+ english_chapter: currentEntry.english_chapter,
158
+ konkani_chapter: currentEntry.konkani_chapter
159
+ };
160
+ csvData.push(entry);
161
+
162
+ currentEntry = {};
163
+ currentText = '';
164
+ }
165
+ }
166
+ }
167
+ }
168
+
169
+ // Save last entry if exists
170
+ if (currentEntry.chapter_name && currentText) {
171
+ if (fieldIndex === 0) {
172
+ currentEntry.english_chapter = currentText.trim();
173
+ } else {
174
+ currentEntry.konkani_chapter = currentText.trim().replace(/^"/, '').replace(/"$/, '');
175
+ }
176
+
177
+ const entry: BibleEntry = {
178
+ id: csvData.length + 1,
179
+ chapter_name: currentEntry.chapter_name,
180
+ chapter_no: currentEntry.chapter_no!,
181
+ english_chapter: currentEntry.english_chapter || '',
182
+ konkani_chapter: currentEntry.konkani_chapter || ''
183
+ };
184
+ csvData.push(entry);
185
+ }
186
+
187
+ return csvData;
188
+ } catch (error) {
189
+ console.error('Error reading CSV file:', error);
190
+ return [];
191
+ }
192
+ }
193
+
194
+ export function findChapterByBookAndNumber(bookName: string, chapterNo: number): BibleEntry | null {
195
+ const data = loadCSVData();
196
+
197
+ return data.find(entry =>
198
+ entry.chapter_name === bookName &&
199
+ entry.chapter_no === chapterNo
200
+ ) || null;
201
+ }
202
+
203
+ export function getChapterById(id: number): BibleEntry | null {
204
+ const data = loadCSVData();
205
+
206
+ return data.find(entry => entry.id === id) || null;
207
+ }
208
+
209
+ export function getAllChapters(): BibleEntry[] {
210
+ return loadCSVData();
211
+ }
bible_chapters_csv.csv ADDED
The diff for this file is too large to render. See raw diff