![]() PROBLEM: - api.rs was duplicating uniffi functionality for NAPI bindings - Every new function had to be written twice (uniffi + api.rs) - Classic DRY violation with identical sync wrappers SOLUTION: - Updated NAPI bindings to use uniffi functions directly - Deleted redundant api.rs file entirely - Fixed uniffi::events::submit_event_json signature to match NAPI needs BENEFITS: ✅ Single source of truth - functions only exist in uniffi modules ✅ Zero duplication - NAPI and UniFFI use identical implementations ✅ Future-proof - new functions only need to be added once ✅ Maintains compatibility for both binding systems This completes our DRY/KISS refactoring by eliminating the last major code duplication in the project. |
||
---|---|---|
bindings/ios/ChurchCore.xcframework | ||
RTSDA | ||
src | ||
tests | ||
.gitignore | ||
build.rs | ||
Cargo.toml | ||
Makefile | ||
README.md |
Church Core
A shared Rust crate providing unified API access and data models for church applications across multiple platforms (iOS, Android, Web, Desktop).
Overview
Church Core centralizes all church application logic, API communication, and data management into a single, well-tested Rust crate. This enables client applications to become "dumb display devices" while ensuring consistency across platforms.
Features
- Unified API Client: Single interface for all church APIs
- Consistent Data Models: Shared types for events, bulletins, sermons, etc.
- Cross-Platform Support: Native bindings for iOS, Android, WASM, and FFI
- Built-in Caching: Automatic response caching with TTL
- Offline Support: Graceful offline operation with cached data
- Authentication: PocketBase and Jellyfin integration
- Error Handling: Comprehensive error types with retry logic
- Type Safety: Rust's type system prevents API mismatches
Architecture
church-core/
├── src/
│ ├── client/ # HTTP client and API modules
│ │ ├── events.rs # Event operations
│ │ ├── bulletins.rs # Bulletin operations
│ │ ├── sermons.rs # Sermon operations
│ │ ├── contact.rs # Contact form handling
│ │ └── config.rs # Configuration management
│ ├── models/ # Data structures
│ │ ├── event.rs # Event models
│ │ ├── bulletin.rs # Bulletin models
│ │ ├── sermon.rs # Sermon models
│ │ ├── contact.rs # Contact models
│ │ └── auth.rs # Authentication models
│ ├── auth/ # Authentication modules
│ ├── cache/ # Caching system
│ ├── utils/ # Utility functions
│ └── error.rs # Error types
└── README.md
Quick Start
Basic Usage
use church_core::{ChurchApiClient, ChurchCoreConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create client with default configuration
let config = ChurchCoreConfig::default();
let client = ChurchApiClient::new(config)?;
// Get upcoming events
let events = client.get_upcoming_events(Some(10)).await?;
for event in events {
println!("{}: {}", event.title, event.start_time);
}
// Get current bulletin
if let Some(bulletin) = client.get_current_bulletin().await? {
println!("Current bulletin: {}", bulletin.title);
}
// Submit contact form
let contact = ContactForm::new(
"John Doe".to_string(),
"john@example.com".to_string(),
"Prayer Request".to_string(),
"Please pray for my family.".to_string()
);
let submission_id = client.submit_contact_form(contact).await?;
println!("Contact form submitted: {}", submission_id);
Ok(())
}
Custom Configuration
use church_core::{ChurchApiClient, ChurchCoreConfig};
use std::time::Duration;
let config = ChurchCoreConfig::new()
.with_base_url("https://api.mychurch.org/api")
.with_cache_ttl(Duration::from_secs(600))
.with_timeout(Duration::from_secs(15))
.with_retry_attempts(5)
.with_offline_mode(true);
let client = ChurchApiClient::new(config)?;
Data Models
Event
use church_core::{Event, EventCategory, NewEvent};
use chrono::{DateTime, Utc};
// Create a new event
let new_event = NewEvent {
title: "Bible Study".to_string(),
description: "Weekly Bible study group".to_string(),
start_time: Utc::now(),
end_time: Utc::now() + chrono::Duration::hours(2),
location: "Fellowship Hall".to_string(),
location_url: Some("https://maps.google.com/...".to_string()),
category: EventCategory::Education,
is_featured: true,
// ... other fields
};
let event_id = client.create_event(new_event).await?;
Bulletin
use church_core::{Bulletin, NewBulletin};
use chrono::NaiveDate;
// Get current bulletin
if let Some(bulletin) = client.get_current_bulletin().await? {
println!("Sabbath School: {}", bulletin.sabbath_school);
println!("Divine Worship: {}", bulletin.divine_worship);
// Check for announcements
for announcement in bulletin.active_announcements() {
println!("📢 {}: {}", announcement.title, announcement.content);
}
}
Contact Form
use church_core::{ContactForm, ContactCategory, VisitorInfo};
let contact = ContactForm::new(
"Jane Smith".to_string(),
"jane@example.com".to_string(),
"New Visitor".to_string(),
"I'm interested in learning more about your church.".to_string()
)
.with_category(ContactCategory::Visitor)
.with_phone("555-123-4567".to_string())
.with_visitor_info(VisitorInfo {
is_first_time: true,
wants_follow_up: true,
wants_newsletter: true,
// ... other fields
});
let submission_id = client.submit_contact_form(contact).await?;
Authentication
PocketBase Authentication
use church_core::auth::{LoginRequest, AuthToken};
// Authenticate with PocketBase
let login = LoginRequest {
identity: "admin@church.org".to_string(),
password: "secure_password".to_string(),
};
// Note: Authentication methods would be implemented in auth module
// let token = client.authenticate_pocketbase(login).await?;
// client.set_auth_token(token).await;
Caching
The client automatically caches responses with configurable TTL:
// Cache is automatically used
let events = client.get_upcoming_events(None).await?; // Fetches from API
let events = client.get_upcoming_events(None).await?; // Returns from cache
// Manual cache management
client.clear_cache().await;
let (cache_size, max_size) = client.get_cache_stats().await;
Error Handling
use church_core::{ChurchApiError, Result};
match client.get_event("invalid-id").await {
Ok(Some(event)) => println!("Found event: {}", event.title),
Ok(None) => println!("Event not found"),
Err(ChurchApiError::NotFound) => println!("Event does not exist"),
Err(ChurchApiError::Network(_)) => println!("Network error - check connection"),
Err(ChurchApiError::Auth(_)) => println!("Authentication required"),
Err(e) => println!("Other error: {}", e),
}
Platform Integration
iOS (Swift)
// FFI bindings would be generated for iOS
import ChurchCore
let client = ChurchApiClient()
client.getUpcomingEvents(limit: 10) { events in
// Handle events
}
Android (Kotlin/Java)
// JNI bindings for Android
import org.church.ChurchCore
val client = ChurchCore()
val events = client.getUpcomingEvents(10)
Web (WASM)
// WASM bindings for web
import { ChurchApiClient } from 'church-core-wasm';
const client = new ChurchApiClient();
const events = await client.getUpcomingEvents(10);
Testing
Run the test binary to verify API connectivity:
cargo run --bin church-core-test
This will test:
- Health check endpoint
- Upcoming events retrieval
- Current bulletin fetching
- Configuration loading
- Cache statistics
API Endpoints
The client interfaces with these standard endpoints:
GET /events/upcoming
- Get upcoming eventsGET /events/{id}
- Get single eventPOST /events
- Create eventGET /bulletins/current
- Get current bulletinGET /bulletins
- List bulletinsGET /config
- Get church configurationPOST /contact
- Submit contact formGET /sermons
- List sermonsGET /sermons/search
- Search sermons
Development
Building for iOS (No Python Required!)
# Quick build using Makefile
make ios
# Or use the build script directly
./build_ios.sh
# Install dependencies first time
make install-deps
# Development build (faster)
make dev
# Clean build artifacts
make clean
Building Steps Explained
- Install iOS targets: Adds Rust toolchains for iOS devices and simulators
- Build static libraries: Creates
.a
files for each iOS architecture - Create universal library: Combines all architectures using
lipo
- Generate Swift bindings: Uses
uniffi-bindgen
to create Swift interfaces - Copy to iOS project: Moves all files to your Xcode project
Generated files:
church_core.swift
- Swift interface to your Rust codechurch_coreFFI.h
- C header filechurch_coreFFI.modulemap
- Module map for Swiftlibchurch_core.a
- Universal static library
Standard Rust Building
# Standard build
cargo build
# Build with WASM support
cargo build --features wasm
# Build with UniFFI support
cargo build --features uniffi
# Run tests
cargo test --features uniffi
Features
default
: Native Rust clientwasm
: WebAssembly bindingsuniffi
: UniFFI bindings for iOS/Android (replaces oldffi
feature)native
: Native platform features
License
MIT License - see LICENSE file for details.
Contributing
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
Support
For issues and questions:
- Check the API documentation
- Review existing GitHub issues
- Create a new issue with detailed information