Consolidate web functionality into main church-core crate
Some checks failed
iOS UniFFI Build / build-ios (push) Has been cancelled

- Add api.rs with all web-specific API functions
- Add admin_login and validate_admin_token methods to ChurchApiClient
- Add image fields to EventSubmission model
- Add submit_event_with_image and submit_event_multipart functions
- Update test files for new EventSubmission fields

This consolidates the web church-core into the main crate to achieve single source of truth.
This commit is contained in:
Benjamin Slingo 2025-08-30 08:49:49 -04:00
parent 0afe80ca8d
commit 9daf11c5af
8 changed files with 682 additions and 0 deletions

527
src/api.rs Normal file
View file

@ -0,0 +1,527 @@
use crate::{
ChurchApiClient, ChurchCoreConfig,
models::{NewSchedule, ScheduleUpdate, NewBulletin, BulletinUpdate, NewEvent, EventUpdate},
};
use tokio::runtime::Runtime;
use std::sync::OnceLock;
static CLIENT: OnceLock<ChurchApiClient> = OnceLock::new();
static RT: OnceLock<Runtime> = OnceLock::new();
fn get_client() -> &'static ChurchApiClient {
CLIENT.get_or_init(|| {
let config = ChurchCoreConfig::default();
ChurchApiClient::new(config).expect("Failed to create church client")
})
}
fn get_runtime() -> &'static Runtime {
RT.get_or_init(|| {
Runtime::new().expect("Failed to create async runtime")
})
}
// Configuration functions
pub fn get_church_name() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_config()) {
Ok(config) => config.church_name.unwrap_or_else(|| "".to_string()),
Err(e) => format!("Error: {}", e),
}
}
pub fn get_contact_phone() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_config()) {
Ok(config) => config.contact_phone.unwrap_or_else(|| "".to_string()),
Err(e) => format!("Error: {}", e),
}
}
pub fn get_contact_email() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_config()) {
Ok(config) => config.contact_email.unwrap_or_else(|| "".to_string()),
Err(e) => format!("Error: {}", e),
}
}
pub fn get_church_address() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_config()) {
Ok(config) => config.church_address.unwrap_or_else(|| "".to_string()),
Err(e) => format!("Error: {}", e),
}
}
pub fn get_church_physical_address() -> String {
get_church_address()
}
pub fn get_church_po_box() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_config()) {
Ok(config) => config.po_box.unwrap_or_else(|| "".to_string()),
Err(e) => format!("Error: {}", e),
}
}
pub fn get_mission_statement() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_config()) {
Ok(config) => config.mission_statement.unwrap_or_else(|| "".to_string()),
Err(e) => format!("Error: {}", e),
}
}
pub fn get_facebook_url() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_config()) {
Ok(config) => config.facebook_url.unwrap_or_else(|| "".to_string()),
Err(e) => format!("Error: {}", e),
}
}
pub fn get_youtube_url() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_config()) {
Ok(config) => config.youtube_url.unwrap_or_else(|| "".to_string()),
Err(e) => format!("Error: {}", e),
}
}
pub fn get_instagram_url() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_config()) {
Ok(config) => config.instagram_url.unwrap_or_else(|| "".to_string()),
Err(e) => format!("Error: {}", e),
}
}
pub fn get_stream_live_status() -> bool {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_stream_status()) {
Ok(status) => status.is_live,
Err(_) => false,
}
}
pub fn get_livestream_url() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_live_stream()) {
Ok(stream) => stream.stream_title.unwrap_or_else(|| "".to_string()),
Err(e) => format!("Error: {}", e),
}
}
// JSON API functions
pub fn fetch_events_json() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_upcoming_events(Some(50))) {
Ok(events) => {
// Format events with display formatting using existing Event methods
let formatted_events: Vec<_> = events.iter().map(|event| {
let mut event_json = serde_json::to_value(event).unwrap_or_default();
// Add formatted fields using Event's built-in methods
if let Some(obj) = event_json.as_object_mut() {
obj.insert("formatted_date".to_string(), serde_json::Value::String(event.formatted_date_range()));
obj.insert("formatted_time".to_string(), serde_json::Value::String(event.formatted_start_time()));
}
event_json
}).collect();
serde_json::to_string(&formatted_events).unwrap_or_else(|_| "[]".to_string())
},
Err(_) => "[]".to_string(),
}
}
pub fn fetch_featured_events_json() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_featured_events_v2(Some(10))) {
Ok(events) => serde_json::to_string(&events).unwrap_or_else(|_| "[]".to_string()),
Err(_) => "[]".to_string(),
}
}
pub fn fetch_sermons_json() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_recent_sermons(Some(20))) {
Ok(sermons) => serde_json::to_string(&sermons).unwrap_or_else(|_| "[]".to_string()),
Err(_) => "[]".to_string(),
}
}
pub fn fetch_config_json() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_config()) {
Ok(config) => serde_json::to_string(&config).unwrap_or_else(|_| "{}".to_string()),
Err(_) => "{}".to_string(),
}
}
pub fn fetch_random_bible_verse_json() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_random_verse()) {
Ok(verse) => serde_json::to_string(&verse).unwrap_or_else(|_| "{}".to_string()),
Err(_) => "{}".to_string(),
}
}
pub fn fetch_bulletins_json() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_bulletins(true)) {
Ok(bulletins) => serde_json::to_string(&bulletins).unwrap_or_else(|_| "[]".to_string()),
Err(_) => "[]".to_string(),
}
}
pub fn fetch_current_bulletin_json() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_current_bulletin()) {
Ok(Some(bulletin)) => serde_json::to_string(&bulletin).unwrap_or_else(|_| "{}".to_string()),
Ok(None) => "{}".to_string(),
Err(_) => "{}".to_string(),
}
}
pub fn fetch_bible_verse_json(query: String) -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_verse_by_reference(&query)) {
Ok(Some(verse)) => serde_json::to_string(&verse).unwrap_or_else(|_| "{}".to_string()),
Ok(None) => "{}".to_string(),
Err(_) => "{}".to_string(),
}
}
pub fn fetch_livestream_archive_json() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_livestreams()) {
Ok(streams) => serde_json::to_string(&streams).unwrap_or_else(|_| "[]".to_string()),
Err(_) => "[]".to_string(),
}
}
pub fn submit_contact_v2_json(name: String, email: String, subject: String, message: String, phone: String) -> String {
let client = get_client();
let rt = get_runtime();
let contact = crate::models::ContactForm::new(name, email, subject, message)
.with_phone(phone);
match rt.block_on(client.submit_contact_form_v2(contact)) {
Ok(id) => serde_json::to_string(&serde_json::json!({"success": true, "id": id})).unwrap_or_else(|_| "{}".to_string()),
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": e.to_string()})).unwrap_or_else(|_| "{}".to_string()),
}
}
pub fn validate_contact_form_json(form_json: String) -> String {
match serde_json::from_str::<crate::models::ContactForm>(&form_json) {
Ok(_) => serde_json::to_string(&crate::utils::ValidationResult::valid()).unwrap_or_else(|_| "{}".to_string()),
Err(_) => serde_json::to_string(&crate::utils::ValidationResult::invalid(vec!["Invalid JSON format".to_string()])).unwrap_or_else(|_| "{}".to_string()),
}
}
pub fn submit_event_json(
title: String,
description: String,
start_time: String,
end_time: String,
location: String,
location_url: Option<String>,
category: String,
recurring_type: Option<String>,
submitter_email: Option<String>
) -> String {
let client = get_client();
let rt = get_runtime();
let submission = crate::models::EventSubmission {
title,
description,
start_time,
end_time,
location,
location_url,
category,
recurring_type,
submitter_email: submitter_email.unwrap_or_else(|| "".to_string()),
is_featured: false,
bulletin_week: None,
image_data: None,
image_filename: None,
image_mime_type: None,
};
match rt.block_on(client.submit_event(submission)) {
Ok(id) => serde_json::to_string(&serde_json::json!({"success": true, "id": id})).unwrap_or_else(|_| "{}".to_string()),
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": e.to_string()})).unwrap_or_else(|_| "{}".to_string()),
}
}
pub fn submit_event_with_image_json(
title: String,
description: String,
start_time: String,
end_time: String,
location: String,
location_url: Option<String>,
category: String,
recurring_type: Option<String>,
submitter_email: Option<String>,
image_data: Option<Vec<u8>>,
image_filename: Option<String>
) -> String {
let client = get_client();
let rt = get_runtime();
let submission = crate::models::EventSubmission {
title,
description,
start_time,
end_time,
location,
location_url,
category,
recurring_type,
submitter_email: submitter_email.unwrap_or_else(|| "".to_string()),
is_featured: false,
bulletin_week: None,
image_data: image_data.clone(),
image_filename: image_filename.clone(),
image_mime_type: None, // Could be improved to detect from filename or data
};
// Convert image data to format expected by multipart method
let image_multipart = if let (Some(data), Some(filename)) = (image_data, image_filename) {
Some((data, filename))
} else {
None
};
match rt.block_on(crate::client::events::submit_event_with_image(client, submission, image_multipart)) {
Ok(id) => serde_json::to_string(&serde_json::json!({"success": true, "id": id})).unwrap_or_else(|_| "{}".to_string()),
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": e.to_string()})).unwrap_or_else(|_| "{}".to_string()),
}
}
// Admin functions
pub fn fetch_all_schedules_json() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.get_all_admin_schedules()) {
Ok(schedules) => serde_json::to_string(&schedules).unwrap_or_else(|_| "[]".to_string()),
Err(_) => "[]".to_string(),
}
}
pub fn create_schedule_json(schedule_json: String) -> String {
let client = get_client();
let rt = get_runtime();
match serde_json::from_str::<NewSchedule>(&schedule_json) {
Ok(schedule) => {
match rt.block_on(client.create_admin_schedule(schedule)) {
Ok(id) => serde_json::to_string(&serde_json::json!({"success": true, "id": id})).unwrap_or_else(|_| "{}".to_string()),
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": e.to_string()})).unwrap_or_else(|_| "{}".to_string()),
}
},
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": format!("Invalid JSON: {}", e)})).unwrap_or_else(|_| "{}".to_string()),
}
}
pub fn update_schedule_json(date: String, update_json: String) -> String {
let client = get_client();
let rt = get_runtime();
match serde_json::from_str::<ScheduleUpdate>(&update_json) {
Ok(update) => {
match rt.block_on(client.update_admin_schedule(&date, update)) {
Ok(_) => serde_json::to_string(&serde_json::json!({"success": true})).unwrap_or_else(|_| "{}".to_string()),
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": e.to_string()})).unwrap_or_else(|_| "{}".to_string()),
}
},
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": format!("Invalid JSON: {}", e)})).unwrap_or_else(|_| "{}".to_string()),
}
}
pub fn delete_schedule_json(date: String) -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.delete_admin_schedule(&date)) {
Ok(_) => serde_json::to_string(&serde_json::json!({"success": true})).unwrap_or_else(|_| "{}".to_string()),
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": e.to_string()})).unwrap_or_else(|_| "{}".to_string()),
}
}
// Admin Auth Functions
pub fn admin_login_json(email: String, password: String) -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.admin_login(&email, &password)) {
Ok(token) => serde_json::to_string(&serde_json::json!({"success": true, "token": token})).unwrap_or_else(|_| "{}".to_string()),
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": e.to_string()})).unwrap_or_else(|_| "{}".to_string()),
}
}
pub fn validate_admin_token_json(token: String) -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(client.validate_admin_token(&token)) {
Ok(valid) => serde_json::to_string(&serde_json::json!({"success": true, "valid": valid})).unwrap_or_else(|_| "{}".to_string()),
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": e.to_string()})).unwrap_or_else(|_| "{}".to_string()),
}
}
// Admin Events Functions
pub fn fetch_pending_events_json() -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(crate::client::admin::get_pending_events(client)) {
Ok(events) => serde_json::to_string(&events).unwrap_or_else(|_| "[]".to_string()),
Err(_) => "[]".to_string(),
}
}
pub fn approve_pending_event_json(event_id: String) -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(crate::client::admin::approve_pending_event(client, &event_id)) {
Ok(_) => serde_json::to_string(&serde_json::json!({"success": true})).unwrap_or_else(|_| "{}".to_string()),
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": e.to_string()})).unwrap_or_else(|_| "{}".to_string()),
}
}
pub fn reject_pending_event_json(event_id: String) -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(crate::client::admin::reject_pending_event(client, &event_id)) {
Ok(_) => serde_json::to_string(&serde_json::json!({"success": true})).unwrap_or_else(|_| "{}".to_string()),
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": e.to_string()})).unwrap_or_else(|_| "{}".to_string()),
}
}
pub fn delete_pending_event_json(event_id: String) -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(crate::client::admin::delete_pending_event(client, &event_id)) {
Ok(_) => serde_json::to_string(&serde_json::json!({"success": true})).unwrap_or_else(|_| "{}".to_string()),
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": e.to_string()})).unwrap_or_else(|_| "{}".to_string()),
}
}
pub fn update_admin_event_json(event_id: String, update_json: String) -> String {
let client = get_client();
let rt = get_runtime();
match serde_json::from_str::<EventUpdate>(&update_json) {
Ok(update) => {
match rt.block_on(crate::client::admin::update_admin_event(client, &event_id, update)) {
Ok(_) => serde_json::to_string(&serde_json::json!({"success": true})).unwrap_or_else(|_| "{}".to_string()),
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": e.to_string()})).unwrap_or_else(|_| "{}".to_string()),
}
},
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": format!("Invalid JSON: {}", e)})).unwrap_or_else(|_| "{}".to_string()),
}
}
pub fn delete_admin_event_json(event_id: String) -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(crate::client::admin::delete_admin_event(client, &event_id)) {
Ok(_) => serde_json::to_string(&serde_json::json!({"success": true})).unwrap_or_else(|_| "{}".to_string()),
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": e.to_string()})).unwrap_or_else(|_| "{}".to_string()),
}
}
// Admin Bulletins Functions
pub fn create_bulletin_json(bulletin_json: String) -> String {
let client = get_client();
let rt = get_runtime();
match serde_json::from_str::<NewBulletin>(&bulletin_json) {
Ok(bulletin) => {
match rt.block_on(crate::client::admin::create_bulletin(client, bulletin)) {
Ok(id) => serde_json::to_string(&serde_json::json!({"success": true, "id": id})).unwrap_or_else(|_| "{}".to_string()),
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": e.to_string()})).unwrap_or_else(|_| "{}".to_string()),
}
},
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": format!("Invalid JSON: {}", e)})).unwrap_or_else(|_| "{}".to_string()),
}
}
pub fn update_bulletin_json(bulletin_id: String, update_json: String) -> String {
let client = get_client();
let rt = get_runtime();
match serde_json::from_str::<BulletinUpdate>(&update_json) {
Ok(update) => {
match rt.block_on(crate::client::admin::update_bulletin(client, &bulletin_id, update)) {
Ok(_) => serde_json::to_string(&serde_json::json!({"success": true})).unwrap_or_else(|_| "{}".to_string()),
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": e.to_string()})).unwrap_or_else(|_| "{}".to_string()),
}
},
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": format!("Invalid JSON: {}", e)})).unwrap_or_else(|_| "{}".to_string()),
}
}
pub fn delete_bulletin_json(bulletin_id: String) -> String {
let client = get_client();
let rt = get_runtime();
match rt.block_on(crate::client::admin::delete_bulletin(client, &bulletin_id)) {
Ok(_) => serde_json::to_string(&serde_json::json!({"success": true})).unwrap_or_else(|_| "{}".to_string()),
Err(e) => serde_json::to_string(&serde_json::json!({"success": false, "error": e.to_string()})).unwrap_or_else(|_| "{}".to_string()),
}
}

View file

@ -21,6 +21,9 @@ async fn main() {
recurring_type: None, recurring_type: None,
bulletin_week: None, bulletin_week: None,
submitter_email: "test@example.com".to_string(), submitter_email: "test@example.com".to_string(),
image_data: None,
image_filename: None,
image_mime_type: None,
}; };
println!("Testing date validation:"); println!("Testing date validation:");

View file

@ -0,0 +1,46 @@
// Test to verify all consolidated functions are available
use church_core::{
ChurchApiClient, ChurchCoreConfig,
// Test that API functions are exported
fetch_events_json, fetch_sermons_json, submit_event_json,
admin_login_json, validate_admin_token_json,
// Test that models have new fields
models::EventSubmission,
};
fn main() {
println!("✅ All imports successful!");
// Test EventSubmission has new image fields
let submission = EventSubmission {
title: "Test".to_string(),
description: "Test".to_string(),
start_time: "2025-01-01T10:00:00Z".to_string(),
end_time: "2025-01-01T11:00:00Z".to_string(),
location: "Test".to_string(),
location_url: None,
category: "Other".to_string(),
is_featured: false,
recurring_type: None,
bulletin_week: None,
submitter_email: "test@test.com".to_string(),
image_data: Some(vec![1, 2, 3, 4]), // NEW FIELD
image_filename: Some("test.jpg".to_string()), // NEW FIELD
image_mime_type: Some("image/jpeg".to_string()), // NEW FIELD
};
println!("✅ EventSubmission with image fields created successfully!");
// Test that we can create a client and that admin methods exist
let config = ChurchCoreConfig::default();
let client = ChurchApiClient::new(config).expect("Failed to create client");
println!("✅ ChurchApiClient created successfully!");
// Verify admin methods exist (just check they compile, don't actually call them)
let _has_admin_login = client.admin_login("test", "test");
let _has_validate_token = client.validate_admin_token("test");
let _has_submit_multipart = church_core::client::events::submit_event_with_image(&client, submission, None);
println!("✅ All admin and multipart functions compile successfully!");
println!("🎉 Consolidation test PASSED - all functions available!");
}

View file

@ -189,4 +189,8 @@ pub async fn get_event_v2(client: &ChurchApiClient, id: &str) -> Result<Option<E
pub async fn submit_event(client: &ChurchApiClient, submission: EventSubmission) -> Result<String> { pub async fn submit_event(client: &ChurchApiClient, submission: EventSubmission) -> Result<String> {
client.post_api("/events/submit", &submission).await client.post_api("/events/submit", &submission).await
}
pub async fn submit_event_with_image(client: &ChurchApiClient, submission: EventSubmission, image_data: Option<(Vec<u8>, String)>) -> Result<String> {
client.submit_event_multipart(&submission, image_data).await
} }

View file

@ -399,4 +399,60 @@ impl ChurchApiClient {
Ok(cached_response) Ok(cached_response)
} }
pub(crate) async fn submit_event_multipart(&self, submission: &crate::models::EventSubmission, image_data: Option<(Vec<u8>, String)>) -> Result<String> {
let url = self.build_url("/events/submit");
let mut form = reqwest::multipart::Form::new()
.text("title", submission.title.clone())
.text("description", submission.description.clone())
.text("start_time", submission.start_time.clone())
.text("end_time", submission.end_time.clone())
.text("location", submission.location.clone())
.text("category", submission.category.clone())
.text("submitter_email", submission.submitter_email.clone());
if let Some(ref location_url) = submission.location_url {
form = form.text("location_url", location_url.clone());
}
if let Some(ref recurring_type) = submission.recurring_type {
form = form.text("recurring_type", recurring_type.clone());
}
if let Some(ref bulletin_week) = submission.bulletin_week {
form = form.text("bulletin_week", bulletin_week.clone());
}
if let Some((image_bytes, filename)) = image_data {
let part = reqwest::multipart::Part::bytes(image_bytes)
.file_name(filename)
.mime_str("image/jpeg") // Default to JPEG, could be improved to detect actual type
.map_err(|e| crate::error::ChurchApiError::Internal(format!("Failed to create image part: {}", e)))?;
form = form.part("image", part);
}
let request = self.client.post(&url).multipart(form);
let request = self.add_auth_header(request).await;
let response = self.send_with_retry(request).await?;
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await?;
return Err(crate::error::ChurchApiError::Api(format!("HTTP {}: {}", status, error_text)));
}
let result: crate::models::ApiResponse<String> = response.json().await?;
if result.success {
result.data.ok_or_else(|| {
crate::error::ChurchApiError::Api("Event submission succeeded but no ID returned".to_string())
})
} else {
Err(crate::error::ChurchApiError::Api(
result.error.unwrap_or_else(|| "Unknown error".to_string())
))
}
}
} }

View file

@ -409,4 +409,42 @@ pub async fn get_livestreams(&self) -> Result<Vec<Sermon>> {
pub async fn get_cache_stats(&self) -> (usize, usize) { pub async fn get_cache_stats(&self) -> (usize, usize) {
(self.cache.len().await, self.config.max_cache_size) (self.cache.len().await, self.config.max_cache_size)
} }
// Admin Auth operations
pub async fn admin_login(&self, email: &str, password: &str) -> Result<String> {
let url = self.build_url("/auth/login");
let request_body = serde_json::json!({
"email": email,
"password": password
});
let response = self.client
.post(&url)
.json(&request_body)
.send()
.await?;
if response.status().is_success() {
let auth_response: serde_json::Value = response.json().await?;
if let Some(token) = auth_response.get("token").and_then(|t| t.as_str()) {
Ok(token.to_string())
} else {
Err(crate::error::ChurchApiError::Api("No token in response".to_string()))
}
} else {
let error_text = response.text().await.unwrap_or_else(|_| "Unknown error".to_string());
Err(crate::error::ChurchApiError::Api(error_text))
}
}
pub async fn validate_admin_token(&self, token: &str) -> Result<bool> {
let url = self.build_url("/admin/events/pending");
let response = self.client
.get(&url)
.header("Authorization", format!("Bearer {}", token))
.send()
.await?;
Ok(response.status().is_success())
}
} }

View file

@ -5,11 +5,13 @@ pub mod cache;
pub mod utils; pub mod utils;
pub mod error; pub mod error;
pub mod config; pub mod config;
pub mod api;
pub use client::ChurchApiClient; pub use client::ChurchApiClient;
pub use config::ChurchCoreConfig; pub use config::ChurchCoreConfig;
pub use error::{ChurchApiError, Result}; pub use error::{ChurchApiError, Result};
pub use models::*; pub use models::*;
pub use cache::*; pub use cache::*;
pub use api::*;
#[cfg(feature = "wasm")] #[cfg(feature = "wasm")]
pub mod wasm; pub mod wasm;

View file

@ -300,6 +300,12 @@ pub struct EventSubmission {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub bulletin_week: Option<String>, // Date string in YYYY-MM-DD format pub bulletin_week: Option<String>, // Date string in YYYY-MM-DD format
pub submitter_email: String, pub submitter_email: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub image_data: Option<Vec<u8>>, // Binary image data
#[serde(skip_serializing_if = "Option::is_none")]
pub image_filename: Option<String>, // Original filename
#[serde(skip_serializing_if = "Option::is_none")]
pub image_mime_type: Option<String>, // MIME type (e.g., "image/jpeg")
} }
impl EventSubmission { impl EventSubmission {