Add PO Box support and centralize config via API
- Add PO Box field to bulletin contact section from API config - Replace local config files with API-based config fetching - Add cover image upload support to bulletin-input tool - Remove dead config.rs and config.toml files - Unify contact info source to prevent config segregation
This commit is contained in:
parent
76366aaf96
commit
4cfd1bad84
|
@ -4,7 +4,7 @@ mod template_renderer;
|
|||
|
||||
use anyhow::{anyhow, Result};
|
||||
use base64::prelude::*;
|
||||
use bulletin_shared::{Config, NewApiClient};
|
||||
use bulletin_shared::NewApiClient;
|
||||
use chrono::{Datelike, Local, NaiveDate, Weekday};
|
||||
use clap::Parser;
|
||||
use std::path::PathBuf;
|
||||
|
@ -12,9 +12,6 @@ use std::path::PathBuf;
|
|||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
#[arg(short, long, default_value = "shared/config.toml")]
|
||||
config: PathBuf,
|
||||
|
||||
#[arg(short, long)]
|
||||
date: Option<NaiveDate>,
|
||||
|
||||
|
@ -56,8 +53,6 @@ async fn main() -> Result<()> {
|
|||
|
||||
let args = Args::parse();
|
||||
|
||||
let config = Config::from_file(&args.config)?;
|
||||
|
||||
let target_date = args.date.unwrap_or_else(|| get_upcoming_saturday(None));
|
||||
|
||||
println!("--- Starting bulletin generation process for date: {} ---", target_date);
|
||||
|
@ -123,6 +118,18 @@ async fn main() -> Result<()> {
|
|||
eprintln!("Failed to fetch events: {}", e);
|
||||
Vec::new()
|
||||
});
|
||||
|
||||
println!("Fetching config from API...");
|
||||
let api_config = client.get_config().await.unwrap_or_else(|e| {
|
||||
eprintln!("Failed to fetch config from API, using defaults: {}", e);
|
||||
bulletin_shared::models::ApiConfig {
|
||||
church_name: "Rockville-Tolland Seventh-Day Adventist Church".to_string(),
|
||||
church_address: "9 Hartford Tpke Tolland CT 06084".to_string(),
|
||||
po_box: "".to_string(),
|
||||
contact_phone: "860-875-0450".to_string(),
|
||||
contact_email: "admin@rockvilletollandsda.church".to_string(),
|
||||
}
|
||||
});
|
||||
|
||||
println!("Parsing Sabbath School text...");
|
||||
let sabbath_school_items = html_parser::parse_sabbath_school(&bulletin.sabbath_school_section);
|
||||
|
@ -138,16 +145,17 @@ async fn main() -> Result<()> {
|
|||
let context = template_renderer::TemplateContext {
|
||||
bulletin_date: target_date.format("%B %d, %Y").to_string(),
|
||||
bulletin_theme_title: html_parser::strip_html_tags(&bulletin.sermon_title),
|
||||
church_name: config.church_name.clone(),
|
||||
church_name: api_config.church_name.clone(),
|
||||
cover_image_path: cover_image_path.as_ref().map(|p| p.to_string_lossy().to_string()),
|
||||
sabbath_school_items,
|
||||
divine_worship_items,
|
||||
announcements,
|
||||
sunset_times: new_bulletin.sunset.unwrap_or_else(|| "TBA".to_string()),
|
||||
contact_info: template_renderer::ContactInfo {
|
||||
phone: config.contact_phone.clone().unwrap_or_else(|| "860-875-0450".to_string()),
|
||||
website: config.contact_website.clone().unwrap_or_else(|| "rockvilletollandsda.church".to_string()),
|
||||
address: config.contact_address.clone().unwrap_or_else(|| "9 Hartford Tpke Tolland CT 06084".to_string()),
|
||||
phone: api_config.contact_phone.clone(),
|
||||
website: "rockvilletollandsda.church".to_string(), // Could be added to API config if needed
|
||||
address: api_config.church_address.clone(),
|
||||
po_box: if api_config.po_box.is_empty() { None } else { Some(api_config.po_box.clone()) },
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ pub struct ContactInfo {
|
|||
pub phone: String,
|
||||
pub website: String,
|
||||
pub address: String,
|
||||
pub po_box: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
|
|
@ -102,6 +102,7 @@
|
|||
<p>Phone: {{ contact_info.phone | default(value='860-875-0450') }}</p>
|
||||
<p>Website: <a href="https://{{ contact_info.website | default(value='rockvilletollandsda.church') }}">{{ contact_info.website | default(value='rockvilletollandsda.church') }}</a></p>
|
||||
<p>Address: {{ contact_info.address | default(value='9 Hartford Tpke Tolland CT 06084') }}</p>
|
||||
{% if contact_info.po_box %}<p>{{ contact_info.po_box }}</p>{% endif %}
|
||||
</div>
|
||||
<h4>Sunset Times</h4>
|
||||
<p>{{ sunset_times | default(value='Not available') }}</p>
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
mod sermon_parser;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use bulletin_shared::{Config, Bulletin, NewApiClient};
|
||||
use bulletin_shared::{Bulletin, NewApiClient};
|
||||
use chrono::{Datelike, Local, NaiveDate, Weekday};
|
||||
use clap::Parser;
|
||||
use dialoguer::{Input, theme::ColorfulTheme};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
#[arg(short, long, default_value = "shared/config.toml")]
|
||||
config: PathBuf,
|
||||
|
||||
#[arg(short, long)]
|
||||
date: Option<NaiveDate>,
|
||||
|
||||
|
@ -160,8 +156,6 @@ async fn main() -> Result<()> {
|
|||
|
||||
let args = Args::parse();
|
||||
|
||||
let _config = Config::from_file(&args.config)?;
|
||||
|
||||
let target_saturday = get_upcoming_saturday(args.date);
|
||||
println!("--- Creating bulletin for Saturday: {} ---", target_saturday);
|
||||
|
||||
|
@ -341,11 +335,39 @@ async fn main() -> Result<()> {
|
|||
updated: None,
|
||||
};
|
||||
|
||||
println!("\n--- Optional Cover Image ---");
|
||||
println!("Enter cover image file path (press Enter to skip):");
|
||||
let mut image_path = String::new();
|
||||
std::io::stdin().read_line(&mut image_path)?;
|
||||
let image_path = image_path.trim();
|
||||
|
||||
let image_file_path = if !image_path.is_empty() && std::path::Path::new(image_path).exists() {
|
||||
Some(std::path::PathBuf::from(image_path))
|
||||
} else if !image_path.is_empty() {
|
||||
println!("Warning: Image file '{}' not found, continuing without cover image", image_path);
|
||||
None
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
println!("\n--- Creating bulletin in new API ---");
|
||||
|
||||
let created_bulletin = client.create_bulletin_from_bulletin(&bulletin).await?;
|
||||
|
||||
println!("\nSUCCESS! Created bulletin ID: {}", created_bulletin.id);
|
||||
|
||||
// Upload cover image if provided
|
||||
if let Some(image_path) = image_file_path {
|
||||
println!("Uploading cover image...");
|
||||
match client.upload_cover_image(&created_bulletin.id, &image_path).await {
|
||||
Ok(_) => println!("Cover image uploaded successfully!"),
|
||||
Err(e) => {
|
||||
eprintln!("Failed to upload cover image: {}", e);
|
||||
println!("Bulletin created successfully, but cover image upload failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Done! Check your API - bulletin created successfully!");
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use anyhow::Result;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct Config {
|
||||
pub church_name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub contact_phone: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub contact_website: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub contact_youtube: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub contact_address: Option<String>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
let config: Config = toml::from_str(&content)?;
|
||||
Ok(config)
|
||||
}
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
pub mod config;
|
||||
pub mod models;
|
||||
pub mod new_api;
|
||||
|
||||
pub use config::Config;
|
||||
pub use models::{Bulletin, Event, Personnel, NewApiBulletin, NewApiResponse, NewApiEvent, NewApiEventsResponse, ConferenceDataResponse, ConferenceData, ScheduleResponse, ScheduleData, PersonnelData, LoginRequest, LoginResponse, LoginData};
|
||||
pub use new_api::NewApiClient;
|
|
@ -208,4 +208,20 @@ pub struct LoginResponse {
|
|||
#[derive(Debug, Deserialize)]
|
||||
pub struct LoginData {
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct ApiConfig {
|
||||
pub church_name: String,
|
||||
pub church_address: String,
|
||||
pub po_box: String,
|
||||
pub contact_phone: String,
|
||||
pub contact_email: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ApiConfigResponse {
|
||||
pub success: bool,
|
||||
pub data: Option<ApiConfig>,
|
||||
pub message: Option<String>,
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::models::{NewApiResponse, NewApiBulletin, Bulletin, NewApiEventsResponse, NewApiEvent, Event, EventType, ConferenceDataResponse, ConferenceData, ScheduleResponse, PersonnelData, LoginRequest, LoginResponse};
|
||||
use crate::models::{NewApiResponse, NewApiBulletin, Bulletin, NewApiEventsResponse, NewApiEvent, Event, EventType, ConferenceDataResponse, ConferenceData, ScheduleResponse, PersonnelData, LoginRequest, LoginResponse, ApiConfig, ApiConfigResponse};
|
||||
use anyhow::{anyhow, Result};
|
||||
use reqwest::Client;
|
||||
use chrono::{NaiveDate, DateTime, Utc};
|
||||
|
@ -179,6 +179,52 @@ impl NewApiClient {
|
|||
Ok(response_text)
|
||||
}
|
||||
|
||||
pub async fn upload_cover_image(&self, bulletin_id: &str, image_path: &std::path::Path) -> Result<String> {
|
||||
let url = format!("{}/api/upload/bulletins/{}/cover", self.base_url, bulletin_id);
|
||||
|
||||
let file = tokio::fs::read(image_path).await?;
|
||||
let file_name = image_path.file_name()
|
||||
.ok_or_else(|| anyhow!("Invalid file path"))?
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
// Determine MIME type based on file extension
|
||||
let mime_type = match image_path.extension().and_then(|ext| ext.to_str()) {
|
||||
Some("jpg") | Some("jpeg") => "image/jpeg",
|
||||
Some("png") => "image/png",
|
||||
Some("webp") => "image/webp",
|
||||
Some("gif") => "image/gif",
|
||||
_ => "image/jpeg", // default
|
||||
};
|
||||
|
||||
let part = reqwest::multipart::Part::bytes(file)
|
||||
.file_name(file_name)
|
||||
.mime_str(mime_type)?;
|
||||
|
||||
println!("Debug: Uploading cover image to bulletin_id: '{}'", bulletin_id);
|
||||
println!("Debug: Upload URL: {}", url);
|
||||
|
||||
let form = reqwest::multipart::Form::new()
|
||||
.part("file", part);
|
||||
|
||||
let mut request = self.client.post(&url).multipart(form);
|
||||
|
||||
if let Some(ref token) = self.auth_token {
|
||||
request = request.header("Authorization", format!("Bearer {}", token));
|
||||
}
|
||||
|
||||
let response = request.send().await?;
|
||||
|
||||
let status = response.status();
|
||||
let response_text = response.text().await?;
|
||||
|
||||
if !status.is_success() {
|
||||
return Err(anyhow!("Failed to upload cover image (HTTP {}): {}", status, response_text));
|
||||
}
|
||||
|
||||
Ok(response_text)
|
||||
}
|
||||
|
||||
pub async fn get_conference_data(&self, date: NaiveDate) -> Result<ConferenceData> {
|
||||
let date_str = date.format("%Y-%m-%d").to_string();
|
||||
let url = format!("{}/api/conference-data?date={}", self.base_url, date_str);
|
||||
|
@ -224,6 +270,28 @@ impl NewApiClient {
|
|||
|
||||
Ok(api_response.data.personnel)
|
||||
}
|
||||
|
||||
pub async fn get_config(&self) -> Result<ApiConfig> {
|
||||
let url = format!("{}/api/config", self.base_url);
|
||||
|
||||
let response = self.client
|
||||
.get(&url)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
let error_text = response.text().await?;
|
||||
return Err(anyhow!("Failed to get config: {}", error_text));
|
||||
}
|
||||
|
||||
let api_response: ApiConfigResponse = response.json().await?;
|
||||
|
||||
if !api_response.success {
|
||||
return Err(anyhow!("API returned error: {:?}", api_response.message));
|
||||
}
|
||||
|
||||
api_response.data.ok_or_else(|| anyhow!("No config data received"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_to_bulletin(new_bulletin: NewApiBulletin) -> Result<Bulletin> {
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
# Church configuration for bulletin generation
|
||||
church_name = "Rockville-Tolland Seventh-Day Adventist Church"
|
||||
# contact_phone = "555-123-4567"
|
||||
# contact_website = "yourchurchwebsite.org"
|
||||
# contact_youtube = "youtube.com/yourchurchchannel"
|
||||
# contact_address = "123 Church St, Your City, ST 12345"
|
Loading…
Reference in a new issue