church-api/src/handlers/bulletins_shared.rs
Benjamin Slingo 5793e12df9 Add comprehensive hymnal support and enhance bulletin processing
- Add complete hymnal API with search, themes, and responsive readings
- Implement hymn title lookup for bulletins (#319#319 - Hymn Title)
- Add Bible book abbreviation support (Matt → Matthew, etc.)
- Enhance scripture processing to handle verse ranges (Matt 1:21-23)
- Add hymnal database schema with SDA 1985 and 1941 hymnals support
- Implement advanced hymnal search with fuzzy matching and themes
- Update bulletin processing to auto-populate hymn titles from database
2025-08-28 20:34:04 -04:00

149 lines
5.2 KiB
Rust

// SHARED PROCESSING FUNCTIONS for bulletins handler
use crate::{
error::Result,
models::Bulletin,
utils::db_operations::BibleVerseOperations,
services::HymnalService,
};
use regex::Regex;
/// Process multiple bulletins with shared logic
pub async fn process_bulletins_batch(
pool: &sqlx::PgPool,
bulletins: &mut [Bulletin]
) -> Result<()> {
for bulletin in bulletins.iter_mut() {
process_single_bulletin(pool, bulletin).await?;
}
Ok(())
}
/// Process a single bulletin with all required transformations
pub async fn process_single_bulletin(
pool: &sqlx::PgPool,
bulletin: &mut Bulletin
) -> Result<()> {
// Process scripture reading to include full verse text
bulletin.scripture_reading = process_scripture_reading(pool, &bulletin.scripture_reading).await?;
// Process hymn references in worship content
if let Some(ref worship_content) = bulletin.divine_worship {
bulletin.divine_worship = Some(process_hymn_references(pool, worship_content).await?);
}
// Process hymn references in sabbath school content
if let Some(ref ss_content) = bulletin.sabbath_school {
bulletin.sabbath_school = Some(process_hymn_references(pool, ss_content).await?);
}
// Ensure sunset field compatibility
if bulletin.sunset.is_none() {
bulletin.sunset = Some("TBA".to_string());
}
Ok(())
}
/// Process scripture reading field to lookup and include full verse text
async fn process_scripture_reading(
pool: &sqlx::PgPool,
scripture: &Option<String>,
) -> Result<Option<String>> {
let Some(scripture_text) = scripture else {
return Ok(None);
};
// If it already looks like it has full verse text (long), return as-is
if scripture_text.len() > 50 {
return Ok(Some(scripture_text.clone()));
}
// Try to find the verse(s) using existing search functionality
// Allow up to 10 verses for ranges like "Matt 1:21-23"
match BibleVerseOperations::search(pool, scripture_text, 10).await {
Ok(verses) if !verses.is_empty() => {
if verses.len() == 1 {
// Single verse - format as before
let verse = &verses[0];
Ok(Some(format!("{} - {}", verse.text, scripture_text)))
} else {
// Multiple verses - combine them
let combined_text = verses
.iter()
.map(|v| v.text.as_str())
.collect::<Vec<&str>>()
.join(" ");
Ok(Some(format!("{} - {}", combined_text, scripture_text)))
}
},
_ => {
// If no match found, return original text
Ok(Some(scripture_text.clone()))
}
}
}
/// Process hymn references in bulletin content to include titles
/// Looks for patterns like #319, Hymn 319, No. 319 and adds hymn titles
pub async fn process_hymn_references(
pool: &sqlx::PgPool,
content: &str,
) -> Result<String> {
// Create regex patterns to match hymn references
let hymn_patterns = vec![
// #319
Regex::new(r"#(\d{1,3})").unwrap(),
// Hymn 319 (case insensitive)
Regex::new(r"(?i)hymn\s+(\d{1,3})").unwrap(),
// No. 319
Regex::new(r"(?i)no\.\s*(\d{1,3})").unwrap(),
// Number 319
Regex::new(r"(?i)number\s+(\d{1,3})").unwrap(),
];
let mut result = content.to_string();
// Default to 1985 hymnal (most common)
let default_hymnal = "sda-1985";
// Process each pattern
for pattern in hymn_patterns {
let mut matches_to_replace = Vec::new();
// Find all matches for this pattern
for capture in pattern.captures_iter(&result) {
if let Some(number_match) = capture.get(1) {
if let Ok(hymn_number) = number_match.as_str().parse::<i32>() {
// Try to get hymn from 1985 hymnal first, then 1941
let hymn_title = match HymnalService::get_hymn_by_number(pool, default_hymnal, hymn_number).await {
Ok(Some(hymn)) => Some(hymn.title),
_ => {
// Try 1941 hymnal as fallback
match HymnalService::get_hymn_by_number(pool, "sda-1941", hymn_number).await {
Ok(Some(hymn)) => Some(hymn.title),
_ => None,
}
}
};
if let Some(title) = hymn_title {
let full_match = capture.get(0).unwrap();
matches_to_replace.push((
full_match.start(),
full_match.end(),
format!("{} - {}", full_match.as_str(), title)
));
}
}
}
}
// Apply replacements in reverse order to maintain string indices
matches_to_replace.reverse();
for (start, end, replacement) in matches_to_replace {
result.replace_range(start..end, &replacement);
}
}
Ok(result)
}