From 3dd687ba30c350f9aaca8b721925228aaa638aa5 Mon Sep 17 00:00:00 2001 From: RTSDA Date: Fri, 4 Apr 2025 01:36:28 -0400 Subject: [PATCH] moved bible integration to local pocketbase backend and cleaned up unused code --- RTSDA.xcodeproj/project.pbxproj | 4 +- Services/BibleService.swift | 190 +++++++++++++++++++++---------- Services/PocketBaseService.swift | 2 +- Views/ContentView.swift | 2 +- bible_verses.json | 125 ++++++++++++++++++++ test_verses.swift | 107 +++++++++++++++++ 6 files changed, 366 insertions(+), 64 deletions(-) create mode 100644 bible_verses.json create mode 100644 test_verses.swift diff --git a/RTSDA.xcodeproj/project.pbxproj b/RTSDA.xcodeproj/project.pbxproj index 82f4a04..22a1369 100644 --- a/RTSDA.xcodeproj/project.pbxproj +++ b/RTSDA.xcodeproj/project.pbxproj @@ -532,7 +532,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.1; + MARKETING_VERSION = 1.2.2; PRODUCT_BUNDLE_IDENTIFIER = com.rtsda.appr; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -574,7 +574,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.2.1; + MARKETING_VERSION = 1.2.2; PRODUCT_BUNDLE_IDENTIFIER = com.rtsda.appr; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/Services/BibleService.swift b/Services/BibleService.swift index 010e8e6..5631d67 100644 --- a/Services/BibleService.swift +++ b/Services/BibleService.swift @@ -3,86 +3,156 @@ import Foundation @MainActor class BibleService { static let shared = BibleService() - private let configService = ConfigService.shared - private let baseURL = "https://api.scripture.api.bible/v1" + private let pocketBaseService = PocketBaseService.shared private init() {} - // API Response structures - struct BibleAPIResponse: Codable { - let data: VerseData - } - - struct VerseData: Codable { + struct Verse: Identifiable, Codable { let id: String - let orgId: String - let bibleId: String - let bookId: String - let chapterId: String let reference: String - let content: String + let text: String + let isActive: Bool - // The API returns HTML content, so we'll clean it up - var cleanContent: String { - return content - .replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression) - .replacingOccurrences(of: """, with: "\"") - .replacingOccurrences(of: "'", with: "'") - .replacingOccurrences(of: "ΒΆ\\s*", with: "", options: .regularExpression) - .replacingOccurrences(of: "^\\d+\\s*", with: "", options: .regularExpression) // Remove verse numbers at start - .replacingOccurrences(of: "\\((.*?)\\)", with: "$1", options: .regularExpression) // Keep text inside parentheses - .replacingOccurrences(of: "\\s+", with: " ", options: .regularExpression) - .trimmingCharacters(in: .whitespaces) + enum CodingKeys: String, CodingKey { + case id + case reference + case text + case isActive = "is_active" } } + struct VersesRecord: Codable { + let collectionId: String + let collectionName: String + let created: String + let id: String + let updated: String + let verses: VersesData + + struct VersesData: Codable { + let id: String + let verses: [Verse] + } + } + + private var cachedVerses: [Verse]? + func getRandomVerse() async throws -> (verse: String, reference: String) { - // List of popular and uplifting Bible verses - let references = [ - //"JER.29.11", "PRO.3.5", "PHP.4.13", "JOS.1.9", - "PSA.23.1", - //"ISA.40.31", "MAT.11.28", "ROM.8.28", "PSA.27.1", "PSA.46.10", - //"JHN.3.16", "ROM.15.13", - "2CO.5.7", //"DEU.31.6", "ROM.8.31", - //"1JN.4.19", "PHP.4.6", "MAT.6.33", "HEB.11.1", "PSA.37.4" - ] + let verses = try await getVerses() + print("Total verses available: \(verses.count)") - // Randomly select a reference - let randomReference = references.randomElement() ?? "JHN.3.16" - - guard let apiKey = configService.bibleApiKey else { - throw NSError(domain: "BibleService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Bible API key not found"]) + guard !verses.isEmpty else { + print("No verses available") + throw NSError(domain: "BibleService", code: -1, userInfo: [NSLocalizedDescriptionKey: "No verses available"]) } - // Construct the API URL - let urlString = "\(baseURL)/bibles/de4e12af7f28f599-01/verses/\(randomReference)" - var request = URLRequest(url: URL(string: urlString)!) - request.addValue(apiKey, forHTTPHeaderField: "api-key") + let randomVerse = verses.randomElement()! + print("Selected random verse: \(randomVerse.reference)") + return (verse: randomVerse.text, reference: randomVerse.reference) + } + + func getVerse(reference: String) async throws -> (verse: String, reference: String) { + print("Looking up verse with reference: \(reference)") + // Convert API-style reference (e.g., "JER.29.11") to display format ("Jeremiah 29:11") + let displayReference = reference + .replacingOccurrences(of: "\\.", with: " ", options: .regularExpression) + .replacingOccurrences(of: "([A-Z]+)", with: "$1 ", options: .regularExpression) + .trimmingCharacters(in: .whitespaces) + + print("Converted reference to: \(displayReference)") + + let verses = try await getVerses() + print("Found \(verses.count) verses") + + if let verse = verses.first(where: { $0.reference.lowercased() == displayReference.lowercased() }) { + print("Found matching verse: \(verse.reference)") + return (verse: verse.text, reference: verse.reference) + } + + print("No matching verse found for reference: \(displayReference)") + throw NSError(domain: "BibleService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Verse not found"]) + } + + private func getVerses() async throws -> [Verse] { + // Return cached verses if available + if let cached = cachedVerses { + print("Returning cached verses") + return cached + } + + print("Fetching verses from PocketBase") + // Fetch from PocketBase + let endpoint = "\(PocketBaseService.shared.baseURL)/bible_verses/records/nkf01o1q3456flr" + + guard let url = URL(string: endpoint) else { + print("Invalid URL: \(endpoint)") + throw URLError(.badURL) + } + + var request = URLRequest(url: url) + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + print("Making request to: \(endpoint)") let (data, response) = try await URLSession.shared.data(for: request) - // Log raw response - if let rawResponse = String(data: data, encoding: .utf8) { - print("Raw Bible API Response:") - print(rawResponse) - print("\n") + guard let httpResponse = response as? HTTPURLResponse else { + print("Invalid response type") + throw URLError(.badServerResponse) } - // Check for successful response - guard let httpResponse = response as? HTTPURLResponse, - (200...299).contains(httpResponse.statusCode) else { - throw NSError(domain: "BibleService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to fetch verse"]) + print("Response status code: \(httpResponse.statusCode)") + + guard httpResponse.statusCode == 200 else { + if let errorString = String(data: data, encoding: .utf8) { + print("Error response from server: \(errorString)") + } + throw URLError(.badServerResponse) + } + + // Print raw response for debugging + if let rawResponse = String(data: data, encoding: .utf8) { + print("Raw response from PocketBase:") + print(rawResponse) } let decoder = JSONDecoder() - let apiResponse = try decoder.decode(BibleAPIResponse.self, from: data) - - // Log cleaned content - print("Cleaned verse content:") - print(apiResponse.data.cleanContent) - print("\nReference:", apiResponse.data.reference) - print("\n") - - return (verse: apiResponse.data.cleanContent, reference: apiResponse.data.reference) + do { + let versesRecord = try decoder.decode(VersesRecord.self, from: data) + + // Cache the verses + cachedVerses = versesRecord.verses.verses + print("Successfully fetched and cached \(versesRecord.verses.verses.count) verses") + + return versesRecord.verses.verses + } catch { + print("Failed to decode response: \(error)") + if let decodingError = error as? DecodingError { + switch decodingError { + case .keyNotFound(let key, let context): + print("Missing key: \(key.stringValue) in \(context.debugDescription)") + case .typeMismatch(let type, let context): + print("Type mismatch: expected \(type) in \(context.debugDescription)") + case .valueNotFound(let type, let context): + print("Value not found: expected \(type) in \(context.debugDescription)") + case .dataCorrupted(let context): + print("Data corrupted: \(context.debugDescription)") + @unknown default: + print("Unknown decoding error") + } + } + throw error + } + } + + func testAllVerses() async throws { + print("\n=== Testing All Verses ===\n") + let verses = try await getVerses() + for verse in verses { + print("Reference: \(verse.reference)") + print("Verse: \(verse.text)") + print("-------------------\n") + } + print("=== Test Complete ===\n") } } diff --git a/Services/PocketBaseService.swift b/Services/PocketBaseService.swift index 9e086cb..0af9c2f 100644 --- a/Services/PocketBaseService.swift +++ b/Services/PocketBaseService.swift @@ -95,7 +95,7 @@ struct Bulletin: Identifiable, Codable { class PocketBaseService { static let shared = PocketBaseService() - private let baseURL = "https://pocketbase.rockvilletollandsda.church/api/collections" + let baseURL = "https://pocketbase.rockvilletollandsda.church/api/collections" private init() {} diff --git a/Views/ContentView.swift b/Views/ContentView.swift index a483939..772c6b8 100644 --- a/Views/ContentView.swift +++ b/Views/ContentView.swift @@ -495,7 +495,7 @@ struct MoreView: View { fallbackURL: AppAvailabilityService.AppStoreURLs.hymnal ) } label: { - Label("SDA Hymnal", systemImage: "music.note") + Label("Adventist Hymnal", systemImage: "music.note") } } diff --git a/bible_verses.json b/bible_verses.json new file mode 100644 index 0000000..eca2bcb --- /dev/null +++ b/bible_verses.json @@ -0,0 +1,125 @@ +{ + "id": "verses", + "verses": [ + { + "id": "1", + "reference": "Jeremiah 29:11", + "text": "For I know the thoughts that I think toward you, saith the LORD, thoughts of peace, and not of evil, to give you an expected end.", + "is_active": true + }, + { + "id": "2", + "reference": "Proverbs 3:5", + "text": "Trust in the LORD with all thine heart; and lean not unto thine own understanding.", + "is_active": true + }, + { + "id": "3", + "reference": "Philippians 4:13", + "text": "I can do all things through Christ which strengtheneth me.", + "is_active": true + }, + { + "id": "4", + "reference": "John 3:16", + "text": "For God so loved the world, that he gave his only begotten Son, that whosoever believeth in him should not perish, but have everlasting life.", + "is_active": true + }, + { + "id": "5", + "reference": "Romans 8:28", + "text": "And we know that all things work together for good to them that love God, to them who are the called according to his purpose.", + "is_active": true + }, + { + "id": "6", + "reference": "Matthew 6:33", + "text": "But seek ye first the kingdom of God, and his righteousness; and all these things shall be added unto you.", + "is_active": true + }, + { + "id": "7", + "reference": "Isaiah 41:10", + "text": "Fear thou not; for I am with thee: be not dismayed; for I am thy God: I will strengthen thee; yea, I will help thee; yea, I will uphold thee with the right hand of my righteousness.", + "is_active": true + }, + { + "id": "8", + "reference": "Joshua 1:9", + "text": "Have not I commanded thee? Be strong and of a good courage; be not afraid, neither be thou dismayed: for the LORD thy God is with thee whithersoever thou goest.", + "is_active": true + }, + { + "id": "9", + "reference": "Psalm 23:1", + "text": "The LORD is my shepherd; I shall not want.", + "is_active": true + }, + { + "id": "10", + "reference": "Psalm 27:1", + "text": "The LORD is my light and my salvation; whom shall I fear? the LORD is the strength of my life; of whom shall I be afraid?", + "is_active": true + }, + { + "id": "11", + "reference": "Psalm 46:1", + "text": "God is our refuge and strength, a very present help in trouble.", + "is_active": true + }, + { + "id": "12", + "reference": "Psalm 91:1", + "text": "He that dwelleth in the secret place of the most High shall abide under the shadow of the Almighty.", + "is_active": true + }, + { + "id": "13", + "reference": "Psalm 119:105", + "text": "Thy word is a lamp unto my feet, and a light unto my path.", + "is_active": true + }, + { + "id": "14", + "reference": "Psalm 121:1", + "text": "I will lift up mine eyes unto the hills, from whence cometh my help.", + "is_active": true + }, + { + "id": "15", + "reference": "Psalm 139:14", + "text": "I will praise thee; for I am fearfully and wonderfully made: marvellous are thy works; and that my soul knoweth right well.", + "is_active": true + }, + { + "id": "16", + "reference": "Proverbs 16:3", + "text": "Commit thy works unto the LORD, and thy thoughts shall be established.", + "is_active": true + }, + { + "id": "17", + "reference": "Isaiah 40:31", + "text": "But they that wait upon the LORD shall renew their strength; they shall mount up with wings as eagles; they shall run, and not be weary; and they shall walk, and not faint.", + "is_active": true + }, + { + "id": "18", + "reference": "Matthew 11:28", + "text": "Come unto me, all ye that labour and are heavy laden, and I will give you rest.", + "is_active": true + }, + { + "id": "19", + "reference": "John 14:27", + "text": "Peace I leave with you, my peace I give unto you: not as the world giveth, give I unto you. Let not your heart be troubled, neither let it be afraid.", + "is_active": true + }, + { + "id": "20", + "reference": "Romans 12:2", + "text": "And be not conformed to this world: but be ye transformed by the renewing of your mind, that ye may prove what is that good, and acceptable, and perfect, will of God.", + "is_active": true + } + ] +} \ No newline at end of file diff --git a/test_verses.swift b/test_verses.swift new file mode 100644 index 0000000..edb7e71 --- /dev/null +++ b/test_verses.swift @@ -0,0 +1,107 @@ +import Foundation + +class PocketBaseService { + static let shared = PocketBaseService() + let baseURL = "https://pocketbase.rockvilletollandsda.church/api/collections" + private init() {} +} + +@MainActor +class BibleService { + static let shared = BibleService() + private let pocketBaseService = PocketBaseService.shared + + private init() {} + + struct Verse: Identifiable, Codable { + let id: String + let reference: String + let text: String + let isActive: Bool + + enum CodingKeys: String, CodingKey { + case id + case reference + case text + case isActive = "is_active" + } + } + + struct VersesRecord: Codable { + let collectionId: String + let collectionName: String + let created: String + let id: String + let updated: String + let verses: VersesData + + struct VersesData: Codable { + let id: String + let verses: [Verse] + } + } + + private var cachedVerses: [Verse]? + + private func getVerses() async throws -> [Verse] { + print("Fetching verses from PocketBase") + let endpoint = "\(PocketBaseService.shared.baseURL)/bible_verses/records/nkf01o1q3456flr" + + guard let url = URL(string: endpoint) else { + print("Invalid URL: \(endpoint)") + throw URLError(.badURL) + } + + var request = URLRequest(url: url) + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + + print("Making request to: \(endpoint)") + let (data, response) = try await URLSession.shared.data(for: request) + + guard let httpResponse = response as? HTTPURLResponse else { + print("Invalid response type") + throw URLError(.badServerResponse) + } + + print("Response status code: \(httpResponse.statusCode)") + + guard httpResponse.statusCode == 200 else { + if let errorString = String(data: data, encoding: .utf8) { + print("Error response from server: \(errorString)") + } + throw URLError(.badServerResponse) + } + + let decoder = JSONDecoder() + let versesRecord = try decoder.decode(VersesRecord.self, from: data) + return versesRecord.verses.verses + } + + func testAllVerses() async throws { + print("\n=== Testing All Verses ===\n") + let verses = try await getVerses() + for verse in verses { + print("Reference: \(verse.reference)") + print("Text: \(verse.text)") + print("-------------------\n") + } + print("=== Test Complete ===\n") + } +} + +print("Starting Bible Verses Test...") + +// Create a semaphore to signal when the task is complete +let semaphore = DispatchSemaphore(value: 0) + +Task { + do { + try await BibleService.shared.testAllVerses() + } catch { + print("Error testing verses: \(error)") + } + semaphore.signal() +} + +// Wait for the task to complete +semaphore.wait() \ No newline at end of file