diff --git a/.gitignore b/.gitignore index d9d8587..c56f471 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,35 @@ debug_*.sql # Serena AI assistant files .serena/ + +# Separate projects +church-website-axum/ + +# Shell scripts and utilities (keep only essential ones) +*.fish +*.sh +fix_*.py +*test*.sql +*debug*.sql +temp_*.txt +temp_*.sql +debug_*.fish +debug_*.sh + +# Symlinks and temp files +claude +current + +# HTML test files +*test*.html + +# Binary files that shouldn't be versioned +*.apk +rtsda-android + +# SQL migration helpers (keep actual migrations) +check_*.sql +clean_*.sql +force_*.sql +validate_*.sql +verify_*.sql diff --git a/add_image_path.fish b/add_image_path.fish deleted file mode 100755 index db8cc25..0000000 --- a/add_image_path.fish +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env fish -echo "๐Ÿ”ง FIXING API TO SUPPORT IMAGE_PATH UPDATES" -echo "============================================" - -# Check if we're in the right directory -if not test -f "src/models.rs" - echo "โŒ Error: src/models.rs not found. Are you in the church-api directory?" - exit 1 -end - -echo "1๏ธโƒฃ Backing up original files..." -cp src/models.rs src/models.rs.backup -cp src/db/events.rs src/db/events.rs.backup -echo "โœ… Backups created: .backup files" - -echo "2๏ธโƒฃ Adding image_path to CreateEventRequest struct..." -sed -i 's/pub recurring_type: Option,/pub recurring_type: Option,\n pub image_path: Option,/' src/models.rs - -if grep -q "pub image_path: Option," src/models.rs - echo "โœ… Added image_path field to CreateEventRequest" -else - echo "โŒ Failed to add image_path field" - exit 1 -end - -echo "3๏ธโƒฃ Updating database update function..." -# Replace the UPDATE query to include image_path -sed -i 's/recurring_type = $9, updated_at = NOW()/recurring_type = $9, image_path = $10, updated_at = NOW()/' src/db/events.rs -sed -i 's/WHERE id = $10/WHERE id = $11/' src/db/events.rs -sed -i '/req.recurring_type,/a\ req.image_path,' src/db/events.rs - -if grep -q "image_path = \$10" src/db/events.rs - echo "โœ… Updated database function" -else - echo "โŒ Failed to update database function" - exit 1 -end - -echo "4๏ธโƒฃ Building the project..." -if cargo build - echo "โœ… Build successful!" -else - echo "โŒ Build failed! Restoring backups..." - cp src/models.rs.backup src/models.rs - cp src/db/events.rs.backup src/db/events.rs - exit 1 -end - -echo "5๏ธโƒฃ Showing changes made..." -echo "" -echo "=== Changes to src/models.rs ===" -diff src/models.rs.backup src/models.rs || true -echo "" -echo "=== Changes to src/db/events.rs ===" -diff src/db/events.rs.backup src/db/events.rs || true - -echo "" -echo "๐ŸŽ‰ SUCCESS!" -echo "============" -echo "โœ… Added image_path field to CreateEventRequest struct" -echo "โœ… Updated database update function to handle image_path" -echo "โœ… Project compiled successfully" -echo "" -echo "๐Ÿš€ Next steps:" -echo "1. Restart your API server" -echo "2. Run your image_path update script" -echo "3. Images should now load properly!" -echo "" -echo "๐Ÿ’พ Backup files saved as:" -echo " - src/models.rs.backup" -echo " - src/db/events.rs.backup" diff --git a/bible_verse.sh b/bible_verse.sh deleted file mode 100755 index f095160..0000000 --- a/bible_verse.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash - -# Add BibleVerse model to models.rs -cat >> src/models.rs << 'EOF' - -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct BibleVerse { - pub id: Uuid, - pub reference: String, - pub text: String, - pub is_active: bool, - pub created_at: Option>, - pub updated_at: Option>, -} -EOF - -# Create handlers/bible_verses.rs -cat > src/handlers/bible_verses.rs << 'EOF' -use crate::{db, error::Result, models::{ApiResponse, BibleVerse}, AppState}; -use axum::{extract::State, Json}; - -pub async fn random( - State(state): State, -) -> Result>> { - let verse = db::bible_verses::get_random(&state.pool).await?; - Ok(Json(ApiResponse { - success: true, - data: verse, - message: None, - })) -} - -pub async fn list( - State(state): State, -) -> Result>>> { - let verses = db::bible_verses::list(&state.pool).await?; - Ok(Json(ApiResponse { - success: true, - data: Some(verses), - message: None, - })) -} -EOF - -# Create db/bible_verses.rs -cat > src/db/bible_verses.rs << 'EOF' -use sqlx::PgPool; -use uuid::Uuid; -use crate::{error::Result, models::BibleVerse}; - -pub async fn get_random(pool: &PgPool) -> Result> { - let verse = sqlx::query_as!( - BibleVerse, - "SELECT * FROM bible_verses WHERE is_active = true ORDER BY RANDOM() LIMIT 1" - ) - .fetch_optional(pool) - .await?; - - Ok(verse) -} - -pub async fn list(pool: &PgPool) -> Result> { - let verses = sqlx::query_as!( - BibleVerse, - "SELECT * FROM bible_verses WHERE is_active = true ORDER BY reference" - ) - .fetch_all(pool) - .await?; - - Ok(verses) -} -EOF - -# Add module to handlers/mod.rs -echo "pub mod bible_verses;" >> src/handlers/mod.rs - -# Add module to db/mod.rs -echo "pub mod bible_verses;" >> src/db/mod.rs - -echo "โœ… Bible verses files created!" -echo "Don't forget to add the routes to main.rs:" -echo '.route("/api/bible_verses/random", get(handlers::bible_verses::random))' -echo '.route("/api/bible_verses", get(handlers::bible_verses::list))' diff --git a/check.sh b/check.sh deleted file mode 100755 index e644f1d..0000000 --- a/check.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -echo "=== CLEANING UP REMAINING PLACEHOLDERS ===" - -# Check if these functions are used anywhere -echo "1. Checking if placeholder functions are used in routes..." -ROUTES_USING_CONFIG_LIST=$(grep -r "config::list" src/main.rs | wc -l) -ROUTES_USING_FILES=$(grep -r "files::" src/main.rs | wc -l) - -echo "Routes using config::list: $ROUTES_USING_CONFIG_LIST" -echo "Routes using files handler: $ROUTES_USING_FILES" - -# Remove the unused config list function -echo "2. Removing unused config list function..." -sed -i '/Config list - implement as needed/,/^}/d' src/handlers/config.rs - -# Remove the files handler entirely if it's not used -echo "3. Removing unused files handler..." -rm -f src/handlers/files.rs - -# Remove files from handlers mod.rs if it exists -echo "4. Cleaning up module references..." -sed -i '/mod files;/d' src/handlers/mod.rs 2>/dev/null || true - -# Check our work -echo "5. Checking for remaining placeholders..." -REMAINING_PLACEHOLDERS=$(grep -r "implement as needed\|TODO\|Working\|TBA" src/ 2>/dev/null | wc -l) -echo "Remaining placeholders: $REMAINING_PLACEHOLDERS" - -if [ $REMAINING_PLACEHOLDERS -eq 0 ]; then - echo "โœ… All placeholders removed!" -else - echo "โš ๏ธ Still have placeholders:" - grep -r "implement as needed\|TODO\|Working\|TBA" src/ 2>/dev/null -fi - -# Build to make sure nothing broke -echo "6. Building to verify everything still works..." -cargo build --release - -if [ $? -eq 0 ]; then - echo "โœ… Build successful - API is clean and working!" - - # Restart service - echo "7. Restarting service..." - sudo systemctl restart church-api - - echo "๐ŸŽ‰ YOUR CHURCH API IS NOW 100% COMPLETE WITH NO PLACEHOLDERS!" -else - echo "โŒ Build failed - check for errors" -fi diff --git a/check_models.sh b/check_models.sh deleted file mode 100755 index 87c27f4..0000000 --- a/check_models.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -echo "๐Ÿ” CHECKING ACTUAL MODEL STRUCTURE" -echo "==================================" - -echo "๐Ÿ“‹ SubmitEventRequest fields:" -grep -A 20 "pub struct SubmitEventRequest" src/models.rs || grep -A 20 "struct SubmitEventRequest" src/models.rs - -echo "" -echo "๐Ÿ“‹ ApiError variants:" -grep -A 10 "pub enum ApiError" src/error.rs || grep -A 10 "enum ApiError" src/error.rs - -echo "" -echo "๐Ÿ“‹ Database schema for pending_events:" -find . -name "*.sql" -exec grep -l "pending_events" {} \; | head -1 | xargs cat 2>/dev/null || echo "No migration files found" - -echo "" -echo "๐ŸŽฏ What we need to do:" -echo "1. Use the ACTUAL fields from SubmitEventRequest" -echo "2. Use proper DateTime types" -echo "3. Use correct ApiError variants" -echo "4. Check if image/thumbnail fields exist in DB" diff --git a/check_specific_bulletin.sql b/check_specific_bulletin.sql deleted file mode 100644 index 1bb59a9..0000000 --- a/check_specific_bulletin.sql +++ /dev/null @@ -1,25 +0,0 @@ --- Check the specific bulletin that the API is returning -SELECT id, title, date, - length(scripture_reading) as scripture_length, - substring(scripture_reading, 1, 200) as scripture_sample, - CASE WHEN scripture_reading LIKE '%<%' THEN 'HAS HTML' ELSE 'CLEAN' END as has_html -FROM bulletins -WHERE id = '192730b5-c11c-4513-a37d-2a8b320136a4'; - --- Let's also clean this specific record if it has HTML -UPDATE bulletins -SET scripture_reading = REGEXP_REPLACE(scripture_reading, '<[^>]*>', '', 'g'), - sabbath_school = REGEXP_REPLACE(COALESCE(sabbath_school, ''), '<[^>]*>', '', 'g'), - divine_worship = REGEXP_REPLACE(COALESCE(divine_worship, ''), '<[^>]*>', '', 'g'), - sunset = REGEXP_REPLACE(COALESCE(sunset, ''), '<[^>]*>', '', 'g') -WHERE id = '192730b5-c11c-4513-a37d-2a8b320136a4' - AND (scripture_reading LIKE '%<%' - OR sabbath_school LIKE '%<%' - OR divine_worship LIKE '%<%' - OR sunset LIKE '%<%'); - --- Verify after cleaning -SELECT 'After targeted cleaning:' as status; -SELECT substring(scripture_reading, 1, 200) as cleaned_content -FROM bulletins -WHERE id = '192730b5-c11c-4513-a37d-2a8b320136a4'; \ No newline at end of file diff --git a/chunk_streaming_test.html b/chunk_streaming_test.html deleted file mode 100644 index e40feef..0000000 --- a/chunk_streaming_test.html +++ /dev/null @@ -1,235 +0,0 @@ - - - - - - Netflix-Style Chunk Streaming Test - - - -
-

๐Ÿฟ Netflix-Style Chunk Streaming Test

- -
- Click "Load Video Info" to start -
- -
- - - - -
- -
- -
-
- - - - \ No newline at end of file diff --git a/church-api-script.sh b/church-api-script.sh deleted file mode 100755 index 70a6f36..0000000 --- a/church-api-script.sh +++ /dev/null @@ -1,525 +0,0 @@ -#!/bin/bash -# Church API Deployment Script -# Run this script to deploy the complete Church API - -set -e - -echo "๐Ÿฆ€ Starting Church API Deployment..." - -# Configuration -PROJECT_DIR="/opt/rtsda/church-api" -DB_NAME="church_db" -DB_USER="postgres" -SERVICE_PORT="3002" - -# Create project directory -echo "๐Ÿ“ Creating project directory..." -sudo mkdir -p $PROJECT_DIR -sudo chown $USER:$USER $PROJECT_DIR -cd $PROJECT_DIR - -# Initialize Cargo project -echo "๐Ÿฆ€ Initializing Rust project..." -cargo init --name church-api - -# Create directory structure -mkdir -p src/{handlers,db} templates migrations uploads/{bulletins,events,avatars} - -# Create Cargo.toml -echo "๐Ÿ“ฆ Setting up dependencies..." -cat > Cargo.toml << 'EOF' -[package] -name = "church-api" -version = "0.1.0" -edition = "2021" - -[dependencies] -axum = "0.7" -tokio = { version = "1.0", features = ["full"] } -sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "uuid", "chrono", "json"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -tower = "0.4" -tower-http = { version = "0.5", features = ["cors", "fs"] } -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -dotenv = "0.15" -uuid = { version = "1.0", features = ["v4", "serde"] } -chrono = { version = "0.4", features = ["serde"] } -jsonwebtoken = "9" -bcrypt = "0.15" -multer = "3.0" -mime_guess = "2.0" -lettre = "0.11" -askama = "0.12" -EOF - -# Create .env file -echo "๐Ÿ”ง Creating environment configuration..." -cat > .env << 'EOF' -DATABASE_URL=postgresql://postgres:yourpassword@localhost/church_db -JWT_SECRET=change_this_super_secret_jwt_key_in_production_very_long_and_secure -RUST_LOG=info -UPLOAD_DIR=/opt/rtsda/church-api/uploads -SERVER_PORT=3002 - -# SMTP Configuration - Your Fastmail settings with proper church emails -SMTP_HOST=smtp.fastmail.com -SMTP_PORT=587 -SMTP_USER=ben@slingoapps.dev -SMTP_PASS=9a9g5g7f2c8u233r -SMTP_FROM=noreply@rockvilletollandsda.church -ADMIN_EMAIL=admin@rockvilletollandsda.church -EOF - -chmod 600 .env - -echo "โš ๏ธ IMPORTANT: Update the .env file with your actual SMTP credentials!" -echo "โš ๏ธ Also update the database password in DATABASE_URL" - -# Create main.rs -echo "๐Ÿ“ Creating main application..." -cat > src/main.rs << 'EOF' -use axum::{ - routing::{get, post, put, delete}, - Router, - extract::State, - middleware, -}; -use dotenv::dotenv; -use sqlx::PgPool; -use std::{env, sync::Arc}; -use tower_http::{cors::CorsLayer, services::ServeDir}; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; - -mod error; -mod auth; -mod models; -mod handlers; -mod db; -mod email; - -use error::Result; -use auth::auth_middleware; -use email::{EmailConfig, Mailer}; - -#[derive(Clone)] -pub struct AppState { - pub pool: PgPool, - pub jwt_secret: String, - pub upload_dir: String, - pub mailer: Arc, -} - -#[tokio::main] -async fn main() -> Result<()> { - dotenv().ok(); - - tracing_subscriber::registry() - .with(tracing_subscriber::EnvFilter::new( - env::var("RUST_LOG").unwrap_or_else(|_| "info".into()), - )) - .with(tracing_subscriber::fmt::layer()) - .init(); - - let database_url = env::var("DATABASE_URL").expect("DATABASE_URL not set"); - let jwt_secret = env::var("JWT_SECRET").expect("JWT_SECRET not set"); - let upload_dir = env::var("UPLOAD_DIR").unwrap_or_else(|_| "./uploads".to_string()); - let port = env::var("SERVER_PORT").unwrap_or_else(|_| "3002".to_string()); - - // Create upload directories - tokio::fs::create_dir_all(&format!("{}/bulletins", upload_dir)).await?; - tokio::fs::create_dir_all(&format!("{}/events", upload_dir)).await?; - tokio::fs::create_dir_all(&format!("{}/avatars", upload_dir)).await?; - - let pool = PgPool::connect(&database_url).await?; - - // Set up email - let email_config = EmailConfig::from_env()?; - let mailer = Arc::new(Mailer::new(email_config)?); - - let state = AppState { - pool, - jwt_secret, - upload_dir: upload_dir.clone(), - mailer, - }; - - let app = Router::new() - // Public routes - .route("/api/bulletins", get(handlers::bulletins::list)) - .route("/api/bulletins/current", get(handlers::bulletins::current)) - .route("/api/bulletins/:id", get(handlers::bulletins::get)) - .route("/api/events", get(handlers::events::list)) - .route("/api/events/upcoming", get(handlers::events::upcoming)) - .route("/api/events/featured", get(handlers::events::featured)) - .route("/api/events/:id", get(handlers::events::get)) - .route("/api/events/submit", post(handlers::events::submit)) - .route("/api/church/config", get(handlers::config::get)) - .route("/api/church/schedules", get(handlers::config::get_schedules)) - .route("/api/app/version/:platform", get(handlers::config::get_app_version)) - - // Auth routes - .route("/api/auth/login", post(handlers::auth::login)) - - // Protected admin routes - .route("/api/bulletins", post(handlers::bulletins::create)) - .route("/api/bulletins/:id", put(handlers::bulletins::update).delete(handlers::bulletins::delete)) - .route("/api/events", post(handlers::events::create)) - .route("/api/events/:id", put(handlers::events::update).delete(handlers::events::delete)) - .route("/api/events/pending", get(handlers::events::list_pending)) - .route("/api/events/pending/:id/approve", put(handlers::events::approve)) - .route("/api/events/pending/:id/reject", put(handlers::events::reject)) - .route("/api/church/config", put(handlers::config::update)) - .route("/api/church/schedules", put(handlers::config::update_schedules)) - .route("/api/files/upload", post(handlers::files::upload)) - .route("/api/users", get(handlers::auth::list_users)) - .route_layer(middleware::from_fn_with_state(state.clone(), auth_middleware)) - - // File serving - .nest_service("/uploads", ServeDir::new(&upload_dir)) - - .layer(CorsLayer::permissive()) - .with_state(state); - - let addr = format!("0.0.0.0:{}", port); - tracing::info!("Church API listening on {}", addr); - - let listener = tokio::net::TcpListener::bind(&addr).await?; - axum::serve(listener, app).await?; - - Ok(()) -} -EOF - -# Create ALL the source files... -echo "๐Ÿ“ Creating source files..." - -# error.rs -cat > src/error.rs << 'EOF' -use axum::{http::StatusCode, response::IntoResponse, Json}; -use serde_json::json; - -#[derive(Debug)] -pub enum ApiError { - DatabaseError(sqlx::Error), - AuthError(String), - ValidationError(String), - NotFound(String), - FileError(std::io::Error), - JwtError(jsonwebtoken::errors::Error), - BcryptError(bcrypt::BcryptError), - SerdeError(serde_json::Error), -} - -impl IntoResponse for ApiError { - fn into_response(self) -> axum::response::Response { - let (status, message) = match self { - ApiError::DatabaseError(e) => { - tracing::error!("Database error: {:?}", e); - (StatusCode::INTERNAL_SERVER_ERROR, "Database error".to_string()) - } - ApiError::AuthError(msg) => (StatusCode::UNAUTHORIZED, msg), - ApiError::ValidationError(msg) => (StatusCode::BAD_REQUEST, msg), - ApiError::NotFound(msg) => (StatusCode::NOT_FOUND, msg), - ApiError::FileError(e) => { - tracing::error!("File error: {:?}", e); - (StatusCode::INTERNAL_SERVER_ERROR, "File operation failed".to_string()) - } - ApiError::JwtError(e) => { - tracing::error!("JWT error: {:?}", e); - (StatusCode::UNAUTHORIZED, "Invalid token".to_string()) - } - ApiError::BcryptError(e) => { - tracing::error!("Bcrypt error: {:?}", e); - (StatusCode::INTERNAL_SERVER_ERROR, "Password hashing error".to_string()) - } - ApiError::SerdeError(e) => { - tracing::error!("Serde error: {:?}", e); - (StatusCode::BAD_REQUEST, "Invalid JSON".to_string()) - } - }; - - ( - status, - Json(json!({ - "success": false, - "error": message - })), - ) - .into_response() - } -} - -impl From for ApiError { - fn from(error: sqlx::Error) -> Self { - ApiError::DatabaseError(error) - } -} - -impl From for ApiError { - fn from(error: std::io::Error) -> Self { - ApiError::FileError(error) - } -} - -impl From for ApiError { - fn from(error: jsonwebtoken::errors::Error) -> Self { - ApiError::JwtError(error) - } -} - -impl From for ApiError { - fn from(error: bcrypt::BcryptError) -> Self { - ApiError::BcryptError(error) - } -} - -impl From for ApiError { - fn from(error: serde_json::Error) -> Self { - ApiError::SerdeError(error) - } -} - -pub type Result = std::result::Result; -EOF - -# I'll continue with the essential files to get you started quickly... -# The rest will be created as minimal working versions - -echo "๐Ÿ“ Creating simplified working files..." - -# Create a minimal working version for now -cat > src/models.rs << 'EOF' -use chrono::{DateTime, NaiveDate, Utc}; -use serde::{Deserialize, Serialize}; -use sqlx::FromRow; -use uuid::Uuid; - -#[derive(Debug, Serialize, Deserialize, FromRow)] -pub struct User { - pub id: Uuid, - pub username: String, - pub email: Option, - pub role: String, -} - -#[derive(Debug, Serialize)] -pub struct ApiResponse { - pub success: bool, - pub data: Option, - pub message: Option, -} - -#[derive(Debug, Deserialize)] -pub struct LoginRequest { - pub username: String, - pub password: String, -} - -#[derive(Debug, Serialize)] -pub struct LoginResponse { - pub token: String, - pub user: User, -} -EOF - -# Create minimal handlers -cat > src/handlers/mod.rs << 'EOF' -pub mod auth; -pub mod bulletins; -pub mod events; -pub mod config; -pub mod files; -EOF - -# Basic auth handler -cat > src/handlers/auth.rs << 'EOF' -use axum::{extract::State, Json}; -use crate::{models::{LoginRequest, LoginResponse, ApiResponse}, AppState, error::Result}; - -pub async fn login( - State(_state): State, - Json(_req): Json, -) -> Result>> { - Ok(Json(ApiResponse { - success: true, - data: Some("Login endpoint - implement me!".to_string()), - message: Some("TODO".to_string()), - })) -} - -pub async fn list_users(State(_state): State) -> Result>> { - Ok(Json(ApiResponse { - success: true, - data: Some("Users endpoint - implement me!".to_string()), - message: None, - })) -} -EOF - -# Create stub handlers for the rest -for handler in bulletins events config files; do -cat > src/handlers/${handler}.rs << EOF -use axum::{extract::State, Json}; -use crate::{models::ApiResponse, AppState, error::Result}; - -pub async fn list(State(_state): State) -> Result>> { - Ok(Json(ApiResponse { - success: true, - data: Some("${handler} endpoint - implement me!".to_string()), - message: None, - })) -} - -// Add other stub functions as needed -pub async fn get(State(_state): State) -> Result>> { list(_state).await } -pub async fn create(State(_state): State) -> Result>> { list(_state).await } -pub async fn update(State(_state): State) -> Result>> { list(_state).await } -pub async fn delete(State(_state): State) -> Result>> { list(_state).await } -pub async fn current(State(_state): State) -> Result>> { list(_state).await } -pub async fn upcoming(State(_state): State) -> Result>> { list(_state).await } -pub async fn featured(State(_state): State) -> Result>> { list(_state).await } -pub async fn submit(State(_state): State) -> Result>> { list(_state).await } -pub async fn list_pending(State(_state): State) -> Result>> { list(_state).await } -pub async fn approve(State(_state): State) -> Result>> { list(_state).await } -pub async fn reject(State(_state): State) -> Result>> { list(_state).await } -pub async fn get_schedules(State(_state): State) -> Result>> { list(_state).await } -pub async fn update_schedules(State(_state): State) -> Result>> { list(_state).await } -pub async fn get_app_version(State(_state): State) -> Result>> { list(_state).await } -pub async fn upload(State(_state): State) -> Result>> { list(_state).await } -EOF -done - -# Create stub db modules -cat > src/db/mod.rs << 'EOF' -pub mod users; -pub mod bulletins; -pub mod events; -pub mod config; -EOF - -for db in users bulletins events config; do -cat > src/db/${db}.rs << 'EOF' -// Stub database module - implement me! -EOF -done - -# Create stub auth module -cat > src/auth.rs << 'EOF' -use axum::{extract::{Request, State}, middleware::Next, response::Response}; -use crate::{error::ApiError, AppState}; - -pub async fn auth_middleware( - State(_state): State, - request: Request, - next: Next, -) -> Result { - // Stub auth middleware - implement me! - Ok(next.run(request).await) -} -EOF - -# Create stub email module -cat > src/email.rs << 'EOF' -use std::env; -use crate::error::Result; - -#[derive(Clone)] -pub struct EmailConfig { - pub smtp_host: String, -} - -impl EmailConfig { - pub fn from_env() -> Result { - Ok(EmailConfig { - smtp_host: env::var("SMTP_HOST").unwrap_or_else(|_| "localhost".to_string()), - }) - } -} - -pub struct Mailer; - -impl Mailer { - pub fn new(_config: EmailConfig) -> Result { - Ok(Mailer) - } -} -EOF - -# Create basic database schema -cat > migrations/001_initial_schema.sql << 'EOF' --- Basic users table -CREATE TABLE users ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - username VARCHAR(50) UNIQUE NOT NULL, - email VARCHAR(255) UNIQUE, - password_hash VARCHAR(255) NOT NULL, - role VARCHAR(20) DEFAULT 'user', - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - --- Insert default admin user (password: 'admin123') -INSERT INTO users (username, email, password_hash, role) VALUES -('admin', 'admin@rockvilletollandsda.church', '$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewHhOQY.S1KElH0y', 'admin'); -EOF - -# Create systemd service -echo "๐Ÿ”ง Creating systemd service..." -sudo tee /etc/systemd/system/church-api.service > /dev/null << EOF -[Unit] -Description=Church API Service -After=network.target postgresql.service - -[Service] -Type=simple -User=$USER -Group=$USER -WorkingDirectory=$PROJECT_DIR -Environment=PATH=/usr/local/bin:/usr/bin:/bin -ExecStart=$PROJECT_DIR/target/release/church-api -Restart=always -RestartSec=10 - -[Install] -WantedBy=multi-user.target -EOF - -# Setup database -echo "๐Ÿ—„๏ธ Setting up database..." -sudo -u postgres createdb $DB_NAME 2>/dev/null || echo "Database $DB_NAME already exists" -sudo -u postgres psql -d $DB_NAME -f migrations/001_initial_schema.sql - -# Build the project -echo "๐Ÿฆ€ Building Rust project..." -cargo build --release - -# Enable and start service -echo "๐Ÿš€ Starting service..." -sudo systemctl daemon-reload -sudo systemctl enable church-api -sudo systemctl start church-api - -# Check if it's running -if sudo systemctl is-active --quiet church-api; then - echo "โœ… Church API is running on port $SERVICE_PORT!" -else - echo "โŒ Service failed to start. Check logs with: sudo journalctl -u church-api -f" - exit 1 -fi - -echo "" -echo "๐ŸŽ‰ BASIC CHURCH API DEPLOYED SUCCESSFULLY! ๐ŸŽ‰" -echo "" -echo "Next steps:" -echo "1. Update .env file with your SMTP credentials" -echo "2. Add api.rockvilletollandsda.church to your Caddy config" -echo "3. Implement the full handlers (or let me know if you want the complete code)" -echo "4. Test with: curl http://localhost:$SERVICE_PORT/api/auth/login" -echo "" -echo "Default admin login:" -echo " Username: admin" -echo " Password: admin123" -echo "" -echo "๐Ÿ—‘๏ธ Ready to destroy PocketBase once everything works!" -EOF diff --git a/church-website-axum/Cargo.toml b/church-website-axum/Cargo.toml deleted file mode 100644 index 460ccb7..0000000 --- a/church-website-axum/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "axum-church-website" -version = "0.1.0" -edition = "2021" - -[dependencies] -axum = { version = "0.7", features = ["macros"] } -tokio = { version = "1.0", features = ["full"] } -tower = "0.4" -tower-http = { version = "0.5", features = ["fs", "cors", "compression-gzip"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -reqwest = { version = "0.11", features = ["json"] } -chrono = { version = "0.4", features = ["serde"] } -anyhow = "1.0" -tracing = "0.1" -tracing-subscriber = "0.3" -dotenvy = "0.15" diff --git a/church-website-axum/css/custom.css b/church-website-axum/css/custom.css deleted file mode 100644 index 0681d42..0000000 --- a/church-website-axum/css/custom.css +++ /dev/null @@ -1,688 +0,0 @@ -/* 2025 Modern Church Website Design */ - -@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700;800;900&family=Inter:wght@300;400;500;600;700;800&display=swap'); - -:root { - --midnight: #0a0a0f; - --deep-navy: #1a1a2e; - --royal-blue: #16213e; - --soft-gold: #d4af37; - --warm-gold: #f7d794; - --pearl-white: #fefefe; - --soft-gray: #f5f6fa; - --medium-gray: #57606f; - --charcoal: #2f3542; - - --gradient-primary: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%); - --gradient-gold: linear-gradient(135deg, #d4af37 0%, #f7d794 100%); - --gradient-glass: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%); - - --shadow-soft: 0 4px 20px rgba(26, 26, 46, 0.08); - --shadow-medium: 0 8px 40px rgba(26, 26, 46, 0.12); - --shadow-strong: 0 20px 60px rgba(26, 26, 46, 0.15); - --shadow-glow: 0 0 40px rgba(212, 175, 55, 0.3); -} - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html { - scroll-behavior: smooth; - overflow-x: hidden; -} - -body { - font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; - line-height: 1.6; - color: var(--charcoal); - background: var(--pearl-white); - overflow-x: hidden; - padding-top: 80px; -} - -/* Typography System */ -.serif { font-family: 'Playfair Display', serif; } -.sans { font-family: 'Inter', sans-serif; } - -h1, .h1 { font-size: clamp(2.5rem, 6vw, 5rem); font-weight: 800; } -h2, .h2 { font-size: clamp(2rem, 4vw, 3.5rem); font-weight: 700; } -h3, .h3 { font-size: clamp(1.5rem, 3vw, 2.5rem); font-weight: 600; } -h4, .h4 { font-size: clamp(1.2rem, 2.5vw, 2rem); font-weight: 600; } - -.text-lg { font-size: clamp(1.1rem, 2vw, 1.3rem); } -.text-xl { font-size: clamp(1.3rem, 2.5vw, 1.6rem); } -.text-2xl { font-size: clamp(1.6rem, 3vw, 2rem); } - -/* Advanced Navigation */ -.nav-2025 { - position: fixed; - top: 0; - left: 0; - right: 0; - z-index: 1000; - background: rgba(10, 10, 15, 0.95); - backdrop-filter: blur(20px) saturate(180%); - border-bottom: 1px solid rgba(212, 175, 55, 0.1); - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - height: 80px; -} - -.nav-container { - max-width: 1600px; - margin: 0 auto; - padding: 1rem 1.5rem; - display: flex; - align-items: center; - justify-content: space-between; -} - -.nav-brand { - font-family: 'Playfair Display', serif; - font-size: 1.5rem; - font-weight: 700; - color: var(--soft-gold); - text-decoration: none; - display: flex; - align-items: center; - gap: 0.75rem; -} - -.nav-menu { - display: flex; - list-style: none; - gap: 1.5rem; - align-items: center; - margin: 0; -} - -.nav-link { - color: rgba(255, 255, 255, 0.9); - text-decoration: none; - font-weight: 500; - font-size: 0.95rem; - padding: 0.75rem 1.25rem; - border-radius: 8px; - transition: all 0.3s ease; - position: relative; - overflow: hidden; - display: flex; - align-items: center; - height: 40px; -} - -.nav-link::before { - content: ''; - position: absolute; - top: 0; - left: -100%; - width: 100%; - height: 100%; - background: var(--gradient-gold); - transition: left 0.3s ease; - z-index: -1; -} - -.nav-link:hover::before { - left: 0; -} - -.nav-link:hover { - color: var(--midnight); - transform: translateY(-1px); -} - -/* Hero Section 2025 */ -.hero-2025 { - min-height: 100vh; - background: var(--gradient-primary); - position: relative; - display: flex; - align-items: center; - overflow: hidden; -} - -.hero-2025::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: - radial-gradient(circle at 20% 50%, rgba(212, 175, 55, 0.1) 0%, transparent 50%), - radial-gradient(circle at 80% 20%, rgba(212, 175, 55, 0.15) 0%, transparent 50%), - radial-gradient(circle at 40% 80%, rgba(212, 175, 55, 0.1) 0%, transparent 50%); -} - -.hero-content { - max-width: 1400px; - margin: 0 auto; - padding: 2rem; - display: grid; - grid-template-columns: 1fr 1fr; - gap: 4rem; - align-items: center; - position: relative; - z-index: 2; -} - -.hero-text { - color: white; -} - -.hero-title { - font-family: 'Playfair Display', serif; - font-size: clamp(3rem, 6vw, 5.5rem); - font-weight: 800; - line-height: 1.1; - margin-bottom: 1.5rem; - background: linear-gradient(135deg, #ffffff 0%, var(--warm-gold) 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.hero-subtitle { - font-size: clamp(1.2rem, 2.5vw, 1.5rem); - font-weight: 400; - margin-bottom: 2rem; - opacity: 0.9; - line-height: 1.6; -} - -.hero-cta-group { - display: flex; - gap: 1rem; - flex-wrap: wrap; - margin-bottom: 3rem; -} - -.btn-2025 { - padding: 1rem 2rem; - border-radius: 12px; - border: none; - font-weight: 600; - font-size: 1rem; - text-decoration: none; - display: inline-flex; - align-items: center; - gap: 0.5rem; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - cursor: pointer; - position: relative; - overflow: hidden; -} - -.btn-primary { - background: var(--gradient-gold); - color: var(--midnight); - box-shadow: var(--shadow-medium); -} - -.btn-primary:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-strong), var(--shadow-glow); - color: var(--midnight); -} - -.btn-outline { - background: transparent; - color: white; - border: 2px solid rgba(255, 255, 255, 0.3); - backdrop-filter: blur(10px); -} - -.btn-outline:hover { - background: rgba(255, 255, 255, 0.1); - border-color: var(--soft-gold); - color: white; -} - -.hero-visual { - position: relative; - height: 600px; -} - -.floating-card { - position: absolute; - background: rgba(255, 255, 255, 0.1); - backdrop-filter: blur(20px); - border: 1px solid rgba(255, 255, 255, 0.2); - border-radius: 20px; - padding: 2rem; - box-shadow: var(--shadow-medium); - animation: float 6s ease-in-out infinite; -} - -.floating-card:nth-child(1) { - top: 10%; - right: 10%; - animation-delay: 0s; -} - -.floating-card:nth-child(2) { - top: 50%; - right: 30%; - animation-delay: 2s; -} - -.floating-card:nth-child(3) { - bottom: 20%; - right: 5%; - animation-delay: 4s; -} - -@keyframes float { - 0%, 100% { transform: translateY(0px) rotate(0deg); } - 50% { transform: translateY(-20px) rotate(2deg); } -} - -/* Modern Section Layouts */ -.section-2025 { - padding: 6rem 0; - position: relative; -} - -.container-2025 { - max-width: 1400px; - margin: 0 auto; - padding: 0 2rem; -} - -.section-header { - text-align: center; - margin-bottom: 4rem; -} - -.section-title { - font-family: 'Playfair Display', serif; - font-size: clamp(2.5rem, 5vw, 4rem); - font-weight: 700; - color: var(--deep-navy); - margin-bottom: 1rem; -} - -.section-subtitle { - font-size: clamp(1.1rem, 2vw, 1.3rem); - color: var(--medium-gray); - max-width: 600px; - margin: 0 auto; - font-weight: 400; -} - -/* Premium Cards System */ -.cards-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); - gap: 2rem; - margin-top: 4rem; -} - -.card-2025 { - background: white; - border-radius: 24px; - padding: 3rem; - box-shadow: var(--shadow-soft); - border: 1px solid rgba(26, 26, 46, 0.05); - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); - position: relative; - overflow: hidden; -} - -.card-2025::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 4px; - background: var(--gradient-gold); - transform: scaleX(0); - transform-origin: left; - transition: transform 0.3s ease; -} - -.card-2025:hover::before { - transform: scaleX(1); -} - -.card-2025:hover { - transform: translateY(-8px); - box-shadow: var(--shadow-strong); -} - -.card-icon-2025 { - width: 70px; - height: 70px; - background: var(--gradient-gold); - border-radius: 18px; - display: flex; - align-items: center; - justify-content: center; - margin-bottom: 2rem; - font-size: 1.8rem; - color: var(--midnight); - box-shadow: var(--shadow-soft); -} - -.card-title { - font-family: 'Playfair Display', serif; - font-size: 1.5rem; - font-weight: 600; - color: var(--deep-navy); - margin-bottom: 1rem; -} - -.card-text { - color: var(--medium-gray); - line-height: 1.7; - font-size: 1rem; -} - -/* Three Angels Section */ -.angels-2025 { - background: var(--soft-gray); - position: relative; - overflow: hidden; -} - -.angels-2025::before { - content: ''; - position: absolute; - top: -50px; - left: 0; - right: 0; - height: 100px; - background: var(--pearl-white); - transform: skewY(-2deg); -} - -.angels-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 2rem; - margin-top: 4rem; -} - -/* Service Times Modern Layout */ -.services-2025 { - background: var(--deep-navy); - color: white; - position: relative; -} - -.services-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 2rem; - margin-top: 4rem; -} - -.service-card-2025 { - background: rgba(255, 255, 255, 0.05); - backdrop-filter: blur(20px); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 20px; - padding: 2.5rem; - text-align: center; - transition: all 0.3s ease; -} - -.service-card-2025:hover { - background: rgba(255, 255, 255, 0.1); - transform: translateY(-5px); -} - -.service-icon-2025 { - width: 80px; - height: 80px; - background: var(--gradient-gold); - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - margin: 0 auto 1.5rem; - font-size: 2rem; - color: var(--midnight); -} - -.service-time { - font-family: 'Playfair Display', serif; - font-size: 2.5rem; - font-weight: 700; - color: var(--soft-gold); - margin: 1rem 0; -} - -/* Events Section */ -.events-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); - gap: 2rem; - margin-top: 4rem; -} - -.event-card-2025 { - background: white; - border-radius: 20px; - overflow: hidden; - box-shadow: var(--shadow-soft); - transition: all 0.3s ease; -} - -.event-card-2025:hover { - transform: translateY(-5px); - box-shadow: var(--shadow-medium); -} - -.event-image-2025 { - height: 200px; - background: var(--gradient-primary); - position: relative; - display: flex; - align-items: center; - justify-content: center; - background-image: url('data:image/svg+xml,'); - background-size: cover; - background-position: center; -} - -.event-date-badge { - position: absolute; - top: 1rem; - right: 1rem; - background: var(--soft-gold); - color: var(--midnight); - padding: 0.5rem 1rem; - border-radius: 12px; - font-weight: 600; - font-size: 0.9rem; -} - -.event-content-2025 { - padding: 2rem; -} - -.event-title { - font-family: 'Playfair Display', serif; - font-size: 1.3rem; - font-weight: 600; - color: var(--deep-navy); - margin-bottom: 1rem; -} - -/* Footer 2025 */ -.footer-2025 { - background: var(--midnight); - color: white; - padding: 4rem 0 2rem; - position: relative; -} - -.footer-2025::before { - content: ''; - position: absolute; - top: -50px; - left: 0; - right: 0; - height: 100px; - background: var(--pearl-white); - transform: skewY(-2deg); -} - -/* Mobile Navigation */ -.nav-toggle { - display: none; - flex-direction: column; - background: none; - border: none; - cursor: pointer; - padding: 0.5rem; - border-radius: 4px; -} - -.hamburger { - width: 24px; - height: 2px; - background: var(--soft-gold); - margin: 2px 0; - transition: all 0.3s ease; - border-radius: 2px; -} - -/* Responsive Design */ -@media (max-width: 992px) { - .nav-toggle { - display: flex; - } - - .nav-menu { - position: fixed; - top: 100%; - left: 0; - right: 0; - background: rgba(10, 10, 15, 0.98); - backdrop-filter: blur(20px); - flex-direction: column; - padding: 2rem; - gap: 1.5rem; - transform: translateY(-100vh); - transition: transform 0.3s ease; - border-top: 1px solid rgba(212, 175, 55, 0.2); - } - - .nav-menu.active { - transform: translateY(0); - } - - .nav-toggle.active .hamburger:nth-child(1) { - transform: rotate(45deg) translate(5px, 5px); - } - - .nav-toggle.active .hamburger:nth-child(2) { - opacity: 0; - } - - .nav-toggle.active .hamburger:nth-child(3) { - transform: rotate(-45deg) translate(7px, -6px); - } - - .hero-content { - grid-template-columns: 1fr; - text-align: center; - } - - .hero-visual { - height: 400px; - } - - .cards-grid, - .angels-grid, - .services-grid, - .events-grid { - grid-template-columns: 1fr; - } - - .section-2025 { - padding: 4rem 0; - } -} - -/* Optimized Scroll Animations for Performance */ -.scroll-reveal { - opacity: 0; - transform: translate3d(0, 20px, 0); - will-change: opacity, transform; - transition: opacity 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94), - transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); -} - -.scroll-reveal.revealed { - opacity: 1; - transform: translate3d(0, 0, 0); - will-change: auto; -} - -/* Reduced stagger delays for smoother performance */ -.stagger-1 { transition-delay: 0.05s; } -.stagger-2 { transition-delay: 0.1s; } -.stagger-3 { transition-delay: 0.15s; } -.stagger-4 { transition-delay: 0.2s; } - -/* Performance optimization for animations */ -@media (prefers-reduced-motion: reduce) { - .scroll-reveal { - opacity: 1; - transform: none; - transition: none; - } -} - -/* GPU acceleration for smooth animations */ -.card-2025, .event-card-2025, .floating-card { - will-change: transform; - transform: translateZ(0); - backface-visibility: hidden; - perspective: 1000px; -} - -/* Optimize hover animations */ -.card-2025:hover, .event-card-2025:hover { - transform: translateZ(0) translateY(-5px); - transition: transform 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94); -} - -/* Disable heavy animations on lower-end devices */ -@media (max-width: 992px), (prefers-reduced-motion: reduce) { - .floating-card { - animation: none !important; - } - - .hero-visual::before { - animation: none !important; - } -} - -/* Optimize floating animation */ -@keyframes float { - 0%, 100% { - transform: translateY(0px) rotate(0deg) translateZ(0); - } - 50% { - transform: translateY(-15px) rotate(1deg) translateZ(0); - } -} - -/* Reduce animation complexity on mobile */ -@media (max-width: 992px) { - .btn-2025:hover { - transform: none; - } - - .nav-link:hover { - transform: none; - } -} \ No newline at end of file diff --git a/church-website-axum/deploy.sh b/church-website-axum/deploy.sh deleted file mode 100755 index 26ad27e..0000000 --- a/church-website-axum/deploy.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/bash - -# RTSDA Church Website Deployment Script -set -e - -echo "Setting up RTSDA Church Website..." - -# Create directories -sudo mkdir -p /var/www/rtsda-website -sudo mkdir -p /opt/rtsda-website - -# Copy static assets to web directory -sudo cp -r css/ js/ images/ /var/www/rtsda-website/ -sudo chown -R www-data:www-data /var/www/rtsda-website - -# Copy source code to opt directory -sudo cp -r src/ Cargo.toml Cargo.lock /opt/rtsda-website/ -sudo chown -R rockvilleav:rockvilleav /opt/rtsda-website - -# Build the application -cd /opt/rtsda-website -cargo build --release - -# Create systemd service file -sudo tee /etc/systemd/system/rtsda-website.service > /dev/null < - - Download on the App Store - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/church-website-axum/images/google-play-badge.svg b/church-website-axum/images/google-play-badge.svg deleted file mode 100644 index 12b8e8d..0000000 --- a/church-website-axum/images/google-play-badge.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/church-website-axum/js/main.js b/church-website-axum/js/main.js deleted file mode 100644 index 8948079..0000000 --- a/church-website-axum/js/main.js +++ /dev/null @@ -1,84 +0,0 @@ -// Contact form submission -document.addEventListener('DOMContentLoaded', function() { - const contactForm = document.getElementById('contact-form'); - if (contactForm) { - contactForm.addEventListener('submit', async function(e) { - e.preventDefault(); - - const formData = new FormData(contactForm); - const formMessage = document.getElementById('form-message'); - - try { - const response = await fetch('/contact', { - method: 'POST', - body: formData - }); - - const result = await response.json(); - - formMessage.style.display = 'block'; - if (result.success) { - formMessage.style.background = '#d4edda'; - formMessage.style.color = '#155724'; - formMessage.textContent = result.message; - contactForm.reset(); - } else { - formMessage.style.background = '#f8d7da'; - formMessage.style.color = '#721c24'; - formMessage.textContent = result.message; - } - } catch (error) { - formMessage.style.display = 'block'; - formMessage.style.background = '#f8d7da'; - formMessage.style.color = '#721c24'; - formMessage.textContent = 'An error occurred. Please try again later.'; - } - }); - } -}); - - -// Add smooth scrolling for anchor links -document.addEventListener('DOMContentLoaded', function() { - const links = document.querySelectorAll('a[href^="#"]'); - - links.forEach(link => { - link.addEventListener('click', function(e) { - const href = this.getAttribute('href'); - if (href !== '#') { - e.preventDefault(); - const target = document.querySelector(href); - if (target) { - target.scrollIntoView({ - behavior: 'smooth', - block: 'start' - }); - } - } - }); - }); -}); - -// Load random Bible verse on homepage (if not already loaded) -async function loadRandomBibleVerse() { - const verseContainer = document.getElementById('random-verse'); - if (verseContainer && !verseContainer.dataset.loaded) { - try { - const response = await fetch('https://api.rockvilletollandsda.church/api/bible_verses/random'); - const result = await response.json(); - - if (result.success && result.data) { - verseContainer.innerHTML = ` -
"${result.data.text}"
- - ${result.data.reference} - `; - verseContainer.dataset.loaded = 'true'; - } - } catch (error) { - console.error('Error loading Bible verse:', error); - } - } -} - -// Load verse on page load if container exists -document.addEventListener('DOMContentLoaded', loadRandomBibleVerse); \ No newline at end of file diff --git a/church-website-axum/src/handlers/about.rs b/church-website-axum/src/handlers/about.rs deleted file mode 100644 index 835ff73..0000000 --- a/church-website-axum/src/handlers/about.rs +++ /dev/null @@ -1,160 +0,0 @@ -use axum::response::Html; -use crate::layout::layout; - -pub async fn about_handler() -> Html { - let content = r#" - -
-
-
-

About Our Church

-
- Seventh-day Adventist Church Logo -
-
- -
-
-

- Welcome to the Rockville-Tolland Seventh-day Adventist Church. We are a vibrant community - of believers dedicated to sharing God's love and the hope of His soon return. Our church - family is committed to spiritual growth, fellowship, and service to our local community. -

- -

Our Mission

-

- Our mission is to share the everlasting gospel of Jesus Christ in the context of the Three - Angels' Messages of Revelation 14, leading people to accept Jesus as their personal Savior - and unite with His remnant church in preparation for His soon return. -

- -

What We Believe

-

- As Seventh-day Adventists, we believe in: -

-
-
-
- -

The Bible

-
-

The inspired Word of God and our only rule of faith and practice

-
- -
-
- -

Salvation

-
-

Through faith in Jesus Christ alone, by grace, not by works

-
- -
-
- -

Second Coming

-
-

The blessed hope and grand climax of the gospel

-
- -
-
- -

The Sabbath

-
-

God's holy day of rest and worship from Friday sunset to Saturday sunset

-
- -
-
- -

Wholistic Health

-
-

Caring for body, mind, and spirit as God's temple

-
- -
-
- -

Service

-
-

To God and humanity, following Christ's example

-
-
- -

Our Community

-

- We are blessed to serve the communities of Rockville, Tolland, and surrounding areas. Our - church offers various programs and ministries for all age groups, providing opportunities - for worship, fellowship, and spiritual growth. -

- -

The Three Angels' Messages

-

- Central to our identity as Seventh-day Adventists are the Three Angels' Messages found in Revelation 14:6-12: -

- -
-

- - First Angel's Message -

-

- "Fear God and give glory to Him, for the hour of His judgment has come; and worship Him who made heaven and earth, the sea and springs of water." -

-

- The everlasting gospel calls all people to worship the Creator God. -

-
- -
-

- - Second Angel's Message -

-

- "Babylon is fallen, is fallen, that great city, because she has made all nations drink of the wine of the wrath of her fornication." -

-

- A warning about false religious systems and a call to choose truth over tradition. -

-
- -
-

- - Third Angel's Message -

-

- "Here is the patience of the saints; here are those who keep the commandments of God and the faith of Jesus." -

-

- A call to remain faithful to God's commandments, including the Sabbath, while maintaining faith in Jesus. -

-
- -

Join Us

-

- Whether you're a long-time Adventist, new to the faith, or simply seeking to learn more - about God, we welcome you to join us. Our services are designed to be inclusive and - meaningful for everyone, regardless of where you are in your spiritual journey. -

- - -
-
-
-
- "#; - - Html(layout(content, "About Our Church")) -} \ No newline at end of file diff --git a/church-website-axum/src/handlers/bulletins.rs b/church-website-axum/src/handlers/bulletins.rs deleted file mode 100644 index 39e9451..0000000 --- a/church-website-axum/src/handlers/bulletins.rs +++ /dev/null @@ -1,378 +0,0 @@ -use axum::{extract::Path, response::Html}; -use crate::services::ApiService; -use crate::layout::layout; -use chrono::NaiveDate; - -pub async fn bulletins_handler() -> Html { - let api_service = ApiService::new(); - - match api_service.get_bulletins().await { - Ok(bulletins) => { - let content = format!(r#" - -
-
-
-

Church Bulletins

-

Download our weekly bulletins to stay informed about church activities and worship services

-
-
-
- - -
-
-
- {} -
- - {} -
-
- "#, - // Bulletins grid HTML - bulletins.iter().enumerate().map(|(index, bulletin)| { - let formatted_date = if let Ok(parsed_date) = NaiveDate::parse_from_str(&bulletin.date, "%Y-%m-%d") { - parsed_date.format("%A, %B %d, %Y").to_string() - } else { - bulletin.date.clone() - }; - - format!(r#" - -
-
- -
-

{}

-

- - {} -

- - {} - -
- View Details -
-
-
- "#, - bulletin.id, - (index % 3) + 1, - bulletin.title, - formatted_date, - // Scripture reading preview - if let Some(ref scripture) = bulletin.scripture_reading { - if !scripture.is_empty() { - let preview = if scripture.len() > 150 { - format!("{}...", &scripture[..150]) - } else { - scripture.clone() - }; - format!(r#" -
- Scripture Reading:
- {} -
- "#, preview) - } else { - String::new() - } - } else { - String::new() - } - ) - }).collect::>().join(""), - - // No bulletins message - if bulletins.is_empty() { - r#" -
-
- -
-

No Bulletins Available

-

No bulletins available at this time. Please check back later.

-
- "# - } else { - "" - } - ); - - Html(layout(&content, "Bulletins")) - }, - Err(_) => { - let content = r#" -
-
-
-

Bulletins

-

Unable to load bulletins. Please try again later.

-
-
-
- "#; - Html(layout(content, "Bulletins")) - } - } -} - -pub async fn bulletin_detail_handler(Path(id): Path) -> Html { - let api_service = ApiService::new(); - - match api_service.get_bulletin(&id).await { - Ok(Some(bulletin)) => { - let formatted_date = if let Ok(parsed_date) = NaiveDate::parse_from_str(&bulletin.date, "%Y-%m-%d") { - parsed_date.format("%A, %B %d, %Y").to_string() - } else { - bulletin.date.clone() - }; - - let content = format!(r#" - -
-
- - Back to Bulletins - -

{}

-

- - {} -

-
-
- - {} - - {} - - {} - - {} - "#, - bulletin.title, - formatted_date, - - // Scripture Reading Section - if let Some(ref scripture) = bulletin.scripture_reading { - if !scripture.is_empty() { - format!(r#" -
-
-
-
- -
-

Scripture Reading

-
- {} -
-
-
-
- "#, scripture) - } else { - String::new() - } - } else { - String::new() - }, - - // Service Programs Section - if bulletin.sabbath_school.is_some() || bulletin.divine_worship.is_some() { - let has_both = bulletin.sabbath_school.is_some() && bulletin.divine_worship.is_some(); - format!(r#" -
-
-
-

Service Programs

-

Order of service for worship and fellowship

-
- - {} -
-
- "#, - if has_both { - // Both programs - adaptive grid - format!(r#" -
- {} - {} -
- "#, - // Sabbath School - smaller card - if let Some(ref ss) = bulletin.sabbath_school { - format!(r#" -
-
-
- -
-

Sabbath School

-
- -
-
{}
-
-
- "#, ss) - } else { - String::new() - }, - // Divine Worship - larger card - if let Some(ref dw) = bulletin.divine_worship { - format!(r#" -
-
-
- -
-

Divine Worship

-
- -
-
{}
-
-
- "#, dw) - } else { - String::new() - } - ) - } else { - // Single program or responsive fallback - format!(r#" -
- {} - {} -
- "#, - if let Some(ref ss) = bulletin.sabbath_school { - format!(r#" -
-
-
- -
-

Sabbath School Program

-
- -
-
{}
-
-
- "#, ss) - } else { - String::new() - }, - if let Some(ref dw) = bulletin.divine_worship { - format!(r#" -
-
-
- -
-

Divine Worship Program

-
- -
-
{}
-
-
- "#, dw) - } else { - String::new() - } - ) - }) - } else { - String::new() - }, - - // Sunset Information Section - if let Some(ref sunset) = bulletin.sunset { - if !sunset.is_empty() { - format!(r#" -
-
-
-
- -
-

Sabbath Information

-

{}

-
-
-
- "#, sunset) - } else { - String::new() - } - } else { - String::new() - }, - - // PDF Download Section - if let Some(ref pdf_path) = bulletin.pdf_path { - if !pdf_path.is_empty() { - format!(r#" -
-
-
-
- -
-

Download Full Bulletin

-

Get the complete bulletin with all details and information.

- - - Download PDF - -
-
-
- "#, pdf_path) - } else { - String::new() - } - } else { - String::new() - } - ); - - Html(layout(&content, &bulletin.title)) - }, - Ok(None) => { - let content = r#" -
-
-
-

Bulletin Not Found

-

The requested bulletin could not be found.

- โ† Back to Bulletins -
-
-
- "#; - Html(layout(content, "Bulletin Not Found")) - }, - Err(_) => { - let content = r#" -
-
-
-

Error

-

Unable to load bulletin. Please try again later.

- โ† Back to Bulletins -
-
-
- "#; - Html(layout(content, "Error")) - } - } -} \ No newline at end of file diff --git a/church-website-axum/src/handlers/contact.rs b/church-website-axum/src/handlers/contact.rs deleted file mode 100644 index d58d38f..0000000 --- a/church-website-axum/src/handlers/contact.rs +++ /dev/null @@ -1,319 +0,0 @@ -use axum::{response::Html, http::StatusCode, Json, extract::Host}; -use serde_json::json; -use crate::services::ApiService; -use crate::models::ContactForm; -use crate::layout::layout; - -pub async fn contact_handler(Host(hostname): Host) -> Html { - let api_service = ApiService::new(); - let config = api_service.get_config().await.unwrap_or(None); - - let church_name = config.as_ref() - .and_then(|c| c.church_name.as_ref()) - .map(|s| s.as_str()) - .unwrap_or("Rockville Tolland SDA Church"); - - let church_address = config.as_ref() - .and_then(|c| c.church_address.as_ref()) - .map(|s| s.as_str()) - .unwrap_or(""); - - let po_box = config.as_ref() - .and_then(|c| c.po_box.as_ref()) - .map(|s| s.as_str()) - .unwrap_or(""); - - let contact_phone = config.as_ref() - .and_then(|c| c.contact_phone.as_ref()) - .map(|s| s.as_str()) - .unwrap_or(""); - - let google_maps_url = config.as_ref() - .and_then(|c| c.google_maps_url.as_ref()) - .map(|s| s.as_str()) - .unwrap_or(""); - - // Create dynamic email based on current domain - let contact_email = format!("info@{}", hostname); - - - let content = format!(r#" - -
-
-
-

Contact Us

-

We'd love to hear from you! Whether you have questions, prayer requests, or just want to connect, please reach out

-
-
-
- - -
-
-
- -
-
- -
-

Church Information

-
-

{}

- {} - {} -

- - {} -

- {} -
- {} -
- - -
-
- -
-

Send Us a Message

-
-
-
- - -
-
- - -
-
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- - - - -
-
-
-
-
- - -
-
-
-

Connect With Us

-

Multiple ways to get involved and stay connected

-
- -
-
-
- -
-

Visit Us

-

Join us for worship any Sabbath morning. Visitors are always welcome!

-
- -
-
- -
-

Prayer Requests

-

Submit your prayer requests and our prayer team will lift you up in prayer.

-
- -
-
- -
-

Bible Studies

-

Interested in learning more about the Bible? We offer free Bible studies.

-
-
-
-
- - -
-
-
-
- -
-

Pastor's Welcome

-

Welcome to Rockville Tolland SDA Church! We are a community of believers dedicated to sharing God's love and the Three Angels' Messages with our community. Whether you're a long-time member or a first-time visitor, we're glad you're here.

-

- Pastor Joseph Piresson

-
-
-
- - - - - - "#, - church_name, - - // Church address - if !church_address.is_empty() { - format!(r#" -

- - {} -

- "#, church_address) - } else { - String::new() - }, - - // PO Box - if !po_box.is_empty() { - format!(r#" -

- - {} -

- "#, po_box) - } else { - String::new() - }, - - // Contact email (for mailto link) - contact_email, - contact_email, - - // Contact phone - if !contact_phone.is_empty() { - format!(r#" -

- - {} -

- "#, contact_phone, contact_phone) - } else { - String::new() - }, - - // Google Maps link - if !google_maps_url.is_empty() { - format!(r#" - - - Get Directions - - "#, google_maps_url) - } else { - String::new() - } - ); - - Html(layout(&content, "Contact")) -} - -pub async fn contact_form_handler(Json(form): Json) -> Result, StatusCode> { - let api_service = ApiService::new(); - - match api_service.submit_contact_form(&form).await { - Ok(true) => Ok(Json(json!({ - "success": true, - "message": "Thank you for your message! We will get back to you soon." - }))), - Ok(false) => Err(StatusCode::INTERNAL_SERVER_ERROR), - Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR) - } -} \ No newline at end of file diff --git a/church-website-axum/src/handlers/events.rs b/church-website-axum/src/handlers/events.rs deleted file mode 100644 index 6a1e0de..0000000 --- a/church-website-axum/src/handlers/events.rs +++ /dev/null @@ -1,433 +0,0 @@ -use axum::{extract::Path, response::Html}; -use crate::services::{ApiService, format_event_datetime, strip_html}; -use crate::layout::layout; - -pub async fn events_handler() -> Html { - let api_service = ApiService::new(); - - // Fetch data concurrently for better performance - let (upcoming_res, featured_res) = tokio::join!( - api_service.get_events(None), // Get all upcoming events - api_service.get_events(Some(3)) // Get top 3 for featured - ); - - let upcoming_events = upcoming_res.unwrap_or_default(); - let _featured_events = featured_res.unwrap_or_default(); - - let content = format!(r#" -
-
-
-

Upcoming Events

-

Join us for these special occasions and activities

-
- -
- {} -
- - {} -
-
- -
-
-
-
- -
-

Submit an Event

-

Have an event you'd like to share with the church community?

- - - Submit Event Request - -
-
-
- "#, - // Events grid HTML - upcoming_events.iter().enumerate().map(|(index, event)| { - render_event_card(event, false, index) - }).collect::>().join(""), - - // No events message - if upcoming_events.is_empty() { - r#" -
-
- -
-

No Events Scheduled

-

No upcoming events at this time. Please check back later for new events.

-
- "# - } else { - "" - } - ); - - Html(layout(&content, "Events")) -} - -pub async fn upcoming_events_handler() -> Html { - let api_service = ApiService::new(); - - match api_service.get_events(None).await { - Ok(events) => { - let content = format!(r#" -
-
-
- - Back to Events - -

All Upcoming Events

-

Complete list of all scheduled events and activities

-
- -
- {} -
- - {} -
-
- -
-
-
-
- -
-

Submit an Event

-

Have an event you'd like to share with the church community?

- - - Submit Event Request - -
-
-
- "#, - // Events grid HTML - events.iter().enumerate().map(|(index, event)| { - render_event_card(event, false, index) - }).collect::>().join(""), - - // No events message - if events.is_empty() { - r#" -
-
- -
-

No Events Scheduled

-

No upcoming events at this time. Please check back later for new events.

-
- "# - } else { - "" - } - ); - - Html(layout(&content, "Upcoming Events")) - } - Err(_) => { - let content = r#" -
-
-
-

Unable to Load Events

-

We're having trouble loading the events right now. Please try again later.

- โ† Back to Events -
-
-
- "#; - Html(layout(content, "Upcoming Events")) - } - } -} - -pub async fn event_detail_handler(Path(id): Path) -> Html { - let api_service = ApiService::new(); - - match api_service.get_event(&id).await { - Ok(Some(event)) => { - let content = format!(r#" - -
-
- - Back to Events - -

{}

-
-
- - {} -
-
- - {} -
- {} -
-
-
- - {} - - -
-
-
- -
-
-

Event Description

-
- {} -
-
-
- - -
-
-

Event Details

- -
-
- - Date & Time -
-

{}

-
- -
-
- - Location -
-

- {} -

- {} -
- -
- -
-
- -
-

Need More Info?

-

Contact us for additional details about this event.

- - Contact Us - -
-
-
-
-
- - - - "#, - event.title, - format_event_datetime(&event.start_time, &event.end_time), - // Location with optional URL - if let Some(ref url) = event.location_url { - if !url.is_empty() { - format!(r#" - {} - "#, url, event.location) - } else { - format!("{}", event.location) - } - } else { - format!("{}", event.location) - }, - // Category badge - if let Some(ref category) = event.category { - if !category.is_empty() { - format!(r#" -
- {} -
- "#, category) - } else { - String::new() - } - } else { - String::new() - }, - // Event image section - if let Some(ref image) = event.image { - if !image.is_empty() { - format!(r#" -
-
-
- {} -
-
-
- "#, image, event.title) - } else { - String::new() - } - } else { - String::new() - }, - // Description - event.description.as_ref() - .map(|d| d.as_str()) - .unwrap_or("

No description available for this event.

"), - // Sidebar date/time - format_event_datetime(&event.start_time, &event.end_time), - // Sidebar location - if let Some(ref url) = event.location_url { - if !url.is_empty() { - format!(r#" - {} - "#, url, event.location) - } else { - event.location.clone() - } - } else { - event.location.clone() - }, - // Get directions button - if let Some(ref url) = event.location_url { - if !url.is_empty() { - format!(r#" - - Get Directions - - "#, url) - } else { - String::new() - } - } else { - String::new() - } - ); - - Html(layout(&content, &event.title)) - }, - Ok(None) => { - let content = r#" -
-
-
-

Event Not Found

-

The requested event could not be found.

- โ† Back to Events -
-
-
- "#; - Html(layout(content, "Event Not Found")) - }, - Err(_) => { - let content = r#" -
-
-
-

Error

-

Unable to load event. Please try again later.

- โ† Back to Events -
-
-
- "#; - Html(layout(content, "Error")) - } - } -} - -fn render_event_card(event: &crate::models::Event, is_featured: bool, index: usize) -> String { - let description = event.description.as_ref() - .map(|d| { - let stripped = strip_html(d); - if stripped.len() > 120 { - format!("{}...", &stripped[..120]) - } else { - stripped - } - }) - .unwrap_or_else(|| String::new()); - - let formatted_time = format_event_datetime(&event.start_time, &event.end_time); - - format!(r#" - -
- {} -
-
-

{}

-

- - {} -

-

- - {} -

-
- {} -
-
- View Details -
-
-
-
- "#, - event.id, - (index % 3) + 1, - event.id, - if is_featured { - r#"style="border: 2px solid var(--soft-gold); cursor: pointer;""# - } else { - r#"style="cursor: pointer;""# - }, - if is_featured { - r#"
โญ FEATURED EVENT
"# - } else { - "" - }, - event.image.as_ref() - .filter(|img| !img.is_empty()) - .map(|img| format!(r#"style="background-image: url('{}'); background-size: cover; background-position: center;""#, img)) - .unwrap_or_default(), - event.title, - formatted_time, - if let Some(ref url) = event.location_url { - if !url.is_empty() { - format!(r#" - - {} - - "#, url, event.location) - } else { - event.location.clone() - } - } else { - event.location.clone() - }, - description - ) -} \ No newline at end of file diff --git a/church-website-axum/src/handlers/home.rs b/church-website-axum/src/handlers/home.rs deleted file mode 100644 index 1654d62..0000000 --- a/church-website-axum/src/handlers/home.rs +++ /dev/null @@ -1,460 +0,0 @@ -use axum::response::Html; -use crate::services::{ApiService, format_event_datetime, strip_html}; -use crate::layout::layout; - -pub async fn home_handler() -> Html { - let api_service = ApiService::new(); - - // Fetch data concurrently for better performance - let (config, bulletin, events, verse) = tokio::join!( - api_service.get_config(), - api_service.get_current_bulletin(), - api_service.get_events(Some(3)), - api_service.get_random_verse() - ); - - let config = config.unwrap_or(None); - let current_bulletin = bulletin.unwrap_or(None); - let upcoming_events = events.unwrap_or_default(); - let bible_verse = verse.unwrap_or(None); - - let church_info = config.as_ref(); - let church_name = church_info - .and_then(|c| c.church_name.as_ref()) - .map(|s| s.as_str()) - .unwrap_or("Rockville Tolland SDA Church"); - - let about_text = church_info - .and_then(|c| c.about_text.as_ref()) - .map(|s| s.as_str()) - .unwrap_or("Proclaiming the Three Angels' Messages with Love and Hope. Join our community of faith as we worship together and grow in Christ."); - - let content = format!(r#" - -
-
-
-

- Welcome to
- {} -

-

- {} -

- -
-
-
- Sabbath School 9:30 AM -
-
-
- Divine Worship 11:00 AM -
-
-
- -
-
-
- -

First Angel

-

Fear God & Give Glory

-
-
-
-
- -

Second Angel

-

Babylon is Fallen

-
-
-
-
- -

Third Angel

-

Keep God's Commands

-
-
-
-
-
- - -
-
-
-

The Three Angels' Messages

-

Central to our mission as Seventh-day Adventists, these messages from Revelation 14 guide our purpose and calling.

-
- -
-
-
- -
-

First Angel's Message

-
- "Fear God and give glory to Him, for the hour of His judgment has come; and worship Him who made heaven and earth, the sea and springs of water." -
- (Revelation 14:6-7) -

The everlasting gospel calls all people to worship the Creator God who made heaven and earth, recognizing His authority and giving Him glory.

-
- -
-
- -
-

Second Angel's Message

-
- "Babylon is fallen, is fallen, that great city, because she has made all nations drink of the wine of the wrath of her fornication." -
- (Revelation 14:8) -

A warning about false religious systems and a call to come out of spiritual confusion, choosing truth over tradition.

-
- -
-
- -
-

Third Angel's Message

-
- "Here is the patience of the saints; here are those who keep the commandments of God and the faith of Jesus." -
- (Revelation 14:12) -

A call to remain faithful to God's commandments, including the seventh-day Sabbath, while maintaining faith in Jesus Christ.

-
-
-
-
- - {} - - -
-
-
-

Service Times

-

Join us for worship and fellowship

-
- -
-
-
- -
-

Sabbath School

-
9:30 AM
-

Join us for Bible study and fellowship every Sabbath morning

-
- -
-
- -
-

Divine Worship

-
11:00 AM
-

Worship service with inspiring sermons and uplifting music

-
- -
-
- -
-

Prayer Meeting

-
Wed 7:00 PM
-

Mid-week spiritual refreshment with prayer and Bible study

-
-
-
-
- - {} - - {} - - -
-
-
-

Our Core Beliefs

-

As Seventh-day Adventists, we accept the Bible as our only creed and hold certain fundamental beliefs to be the teaching of the Holy Scriptures.

-
- -
-
-
- -
-

The Holy Scriptures

-

The Holy Scriptures are the infallible revelation of God's will and the authoritative revealer of doctrines.

-
- -
-
- -
-

The Trinity

-

There is one God: Father, Son, and Holy Spirit, a unity of three co-eternal Persons.

-
- -
-
- -
-

The Sabbath

-

The seventh day of the week is the Sabbath of the Lord our God, a day of rest and worship.

-
- -
-
- -
-

The Second Coming

-

The second coming of Christ is the blessed hope of the church and the grand climax of the gospel.

-
-
-
-
- - -
-
-
-

Faith in Your Pocket

-

Access sermons, events, and stay connected with our church family through our mobile app designed for spiritual growth.

-
- -
-
- -
-

Download Our Mobile App

-

- Stay connected with sermons, events, and church activities wherever you go. - Our app makes it easy to access spiritual content and stay engaged with our community. -

- -
- - - Download on the App Store - - - - -
- -
-

- - Available on both iOS and Android platforms. Download today to access sermons, events, and stay connected with our church community. -

-
-
-
-
- - - "#, - church_name, - about_text, - - // Bible verse section - if let Some(verse) = &bible_verse { - format!(r#" -
-
-
-
- -
-

Today's Scripture

-
- "{}" -
- - {} -
-
-
- "#, verse.text, verse.reference) - } else { - String::new() - }, - - // Current bulletin section - if let Some(bulletin) = ¤t_bulletin { - let formatted_date = format!("{}", bulletin.date); // You can add date formatting here - format!(r#" - -
-
-
-

This Week's Bulletin

-

Stay informed about church activities and worship

-
- -
-
- -
-

{}

-

- - {} -

- {} - -
-
-
- "#, - bulletin.title, - formatted_date, - if let Some(ref scripture) = bulletin.scripture_reading { - if !scripture.is_empty() { - format!(r#" -
- Scripture Reading:
- {} -
- "#, scripture) - } else { - String::new() - } - } else { - String::new() - }, - if let Some(ref pdf_path) = bulletin.pdf_path { - if !pdf_path.is_empty() { - format!(r#" - - - Download PDF - - "#, pdf_path) - } else { - String::new() - } - } else { - String::new() - }, - bulletin.id - ) - } else { - String::new() - }, - - // Upcoming events section - if !upcoming_events.is_empty() { - let events_html = upcoming_events.iter().enumerate().map(|(index, event)| { - let description = event.description.as_ref() - .map(|d| { - let stripped = strip_html(d); - if stripped.len() > 120 { - format!("{}...", &stripped[..120]) - } else { - stripped - } - }) - .unwrap_or_else(|| "Join us for this special event.".to_string()); - - let formatted_time = format_event_datetime(&event.start_time, &event.end_time); - - format!(r#" - -
-
-
-

{}

-

- - {} -

-

- - {} -

-
- {} -
-
- View Details -
-
-
-
- "#, - event.id, - (index % 3) + 1, - event.image.as_ref() - .filter(|img| !img.is_empty()) - .map(|img| format!(r#"style="background-image: url('{}'); background-size: cover; background-position: center;""#, img)) - .unwrap_or_default(), - event.title, - formatted_time, - event.location, - description - ) - }).collect::>().join(""); - - format!(r#" - -
-
-
-

Upcoming Events

-

Join us for these special occasions and activities

-
- -
- {} -
- - -
-
- "#, events_html) - } else { - String::new() - } - ); - - Html(layout(&content, "Home")) -} \ No newline at end of file diff --git a/church-website-axum/src/handlers/ministries.rs b/church-website-axum/src/handlers/ministries.rs deleted file mode 100644 index ca8d74e..0000000 --- a/church-website-axum/src/handlers/ministries.rs +++ /dev/null @@ -1,185 +0,0 @@ -use axum::response::Html; -use crate::layout::layout; - -pub async fn ministries_handler() -> Html { - let content = r#" - -
-
-
-

Our Ministries

-

- Discover the various ways you can get involved, grow spiritually, and serve in our church community. - Each ministry is designed to help believers grow in faith and share God's love with others. -

-
- - -
- - -
-
- Prayer Ministry -
-
-
-

Prayer Ministry

-

- Join one of our many prayer groups or submit your prayer requests. We have multiple opportunities for prayer throughout the week: Daily Prayer Group, Wednesday Prayer Group, BiWeekly Prayer Group, and Monthly Prayer Group. -

- -
-
- - Daily, Weekly, BiWeekly, and Monthly Groups -
-
- - Various Times Available -
-
- - In Person & Online -
-
- - - - Contact Prayer Ministry - -
-
- - -
-
- Gardening Ministry -
-
-
-

Gardening Ministry

-

- Learn about sustainable gardening practices and join our community of gardeners. Watch our gardening series to learn practical tips and techniques for growing your own food. -

- - - - Garden Video Series - -
-
- - -
-
- Bible Studies -
-
-
-

Bible Studies

-

- Deepen your understanding of Scripture through our Bible study programs and resources. Access free Bible study guides and tools to enhance your spiritual journey. -

- - -
-
- - -
-
- Adventist Youth -
-
-
-

Adventist Youth

-

- Join our vibrant youth community for spiritual growth, fellowship, and service opportunities. Experience the joy of growing in faith with other young believers. -

- - - - Contact Youth Ministry - -
-
- - -
-
- Health Ministry -
-
-
-

Health Ministry

-

- Discover resources and programs promoting physical, mental, and spiritual well-being through our health ministry. Learn about God's plan for optimal health. -

- - - - Health Resources - -
-
- - -
-
- Training Ministry -
-
-
-

Training & Education

-

- Develop your spiritual gifts and ministry skills through our training programs. Learn to share your faith effectively and serve others with confidence. -

- - - - Learn More - -
-
- -
- - -
-
-

Get Involved Today

-

- Ready to join one of our ministries? Contact us to learn more about how you can get involved and make a difference in our community. -

- - - Contact Us Today - -
-
-
-
- "#; - - Html(layout(content, "Our Ministries")) -} \ No newline at end of file diff --git a/church-website-axum/src/handlers/mod.rs b/church-website-axum/src/handlers/mod.rs deleted file mode 100644 index 7a860cf..0000000 --- a/church-website-axum/src/handlers/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod home; -pub mod about; -pub mod ministries; -pub mod sermons; -pub mod events; -pub mod bulletins; -pub mod contact; \ No newline at end of file diff --git a/church-website-axum/src/handlers/sermons.rs b/church-website-axum/src/handlers/sermons.rs deleted file mode 100644 index eeea1e4..0000000 --- a/church-website-axum/src/handlers/sermons.rs +++ /dev/null @@ -1,757 +0,0 @@ -use axum::{extract::{Path, Query}, response::Html}; -use crate::layout::layout; -use crate::services::{ApiService, parse_sermon_title, format_duration, format_date}; -use serde::Deserialize; -use std::collections::HashMap; -use chrono::Datelike; - -#[derive(Deserialize)] -pub struct ArchiveQuery { - collection: Option, -} - -pub async fn sermons_handler() -> Html { - let api_service = ApiService::new(); - - match api_service.get_jellyfin_libraries().await { - Ok(libraries) => { - if libraries.is_empty() { - return render_no_sermons_page(); - } - - let mut collection_data = std::collections::HashMap::new(); - - for library in &libraries { - match api_service.get_jellyfin_sermons(Some(&library.id), Some(6)).await { - Ok(mut sermons) => { - // Sort sermons by date - sermons.sort_by(|a, b| { - let get_valid_date = |sermon: &crate::models::JellyfinItem| { - let parsed = parse_sermon_title(&sermon.name); - if let Some(ref date_str) = parsed.date_from_title { - if let Ok(date) = chrono::NaiveDate::parse_from_str(date_str, "%Y-%m-%d") { - return date; - } - } - - if let Some(ref premiere_date) = sermon.premiere_date { - if let Ok(date) = chrono::NaiveDate::parse_from_str(&premiere_date.split('T').next().unwrap_or(""), "%Y-%m-%d") { - return date; - } - } - - chrono::Utc::now().naive_utc().date() - }; - - let date_a = get_valid_date(a); - let date_b = get_valid_date(b); - date_b.cmp(&date_a) - }); - - collection_data.insert(library.name.clone(), sermons); - }, - Err(_) => { - collection_data.insert(library.name.clone(), vec![]); - } - } - } - - Html(layout(&render_sermons_content(collection_data), "Latest Sermons & Live Streams")) - }, - Err(_) => render_no_sermons_page() - } -} - -fn render_no_sermons_page() -> Html { - let content = r#" - -
-
-
-

Latest Sermons & Live Streams

-

Listen to our most recent inspiring messages from God's Word

-
-
-
- -
-
-
-
- -
-

Sermons Coming Soon

-

Sermons are currently being prepared for online streaming.

-

Please check back later or contact us for more information.

-

Note: Make sure Jellyfin server credentials are configured properly.

-
-
-
- "#; - - Html(layout(content, "Sermons")) -} - -fn render_sermons_content(collection_data: std::collections::HashMap>) -> String { - format!(r#" - -
-
-
-

Latest Sermons & Live Streams

-

Listen to our most recent inspiring messages from God's Word. These are the latest sermons and live stream recordings for your spiritual growth.

- -
-
-
- - {} - -
-
-
-

About Our Sermons

-

Our sermons focus on the Three Angels' Messages and the teachings of Jesus Christ. Each message is designed to strengthen your faith and deepen your understanding of God's Word.

-
- -
-
-
- -
-

Sabbath Sermons

-

Weekly messages during Divine Worship

-
- -
-
- -
-

Prophecy Studies

-

Deep dives into Biblical prophecy

-
- -
-
- -
-

Practical Christianity

-

Applying Bible principles to daily life

-
- -
-
- -
-

Special Events

-

Revival meetings and guest speakers

-
-
-
-
- "#, - collection_data.iter().enumerate().map(|(collection_index, (collection_name, sermons))| { - format!(r#" -
-
-
-

- - {} -

-

- {} -

- -
- - {} -
-
- "#, - if collection_index > 0 { "background: var(--soft-gray);" } else { "" }, - if collection_name == "LiveStreams" { "broadcast-tower" } else { "church" }, - if collection_name == "LiveStreams" { "Live Stream Recordings" } else { "Sabbath Sermons" }, - if collection_name == "LiveStreams" { - "Recorded live streams from our worship services and special events" - } else { - "Messages from our regular Sabbath worship services" - }, - collection_name, - if collection_name == "LiveStreams" { "Live Streams" } else { "Sermons" }, - if sermons.is_empty() { - format!(r#" -
-

No {} Available

-

Check back later for new content in this collection.

-
- "#, collection_name) - } else { - format!(r#" -
- {} -
- "#, sermons.iter().enumerate().map(|(index, sermon)| { - let parsed = parse_sermon_title(&sermon.name); - format!(r#" -
-
- -
-

{}

- {} - - {} - - {} - -
- {} - - - {} - -
- - - - {} {} - -
- "#, - (index % 3) + 1, - if sermon.media_type.as_ref().unwrap_or(&"Audio".to_string()) == "Audio" { "music" } else { "video" }, - parsed.title, - if let Some(ref speaker) = parsed.speaker { - format!(r#" -

- {} -

- "#, speaker) - } else { - String::new() - }, - if let Some(ref premiere_date) = sermon.premiere_date { - format!(r#" -

- - {} -

- "#, format_date(premiere_date)) - } else if let Some(ref date_from_title) = parsed.date_from_title { - format!(r#" -

- - {} -

- "#, date_from_title) - } else { - String::new() - }, - if let Some(ref overview) = sermon.overview { - let preview = if overview.len() > 150 { - format!("{}...", &overview[..150]) - } else { - overview.clone() - }; - format!(r#" -

- {} -

- "#, preview) - } else { - String::new() - }, - if let Some(ticks) = sermon.run_time_ticks { - format!(r#" - - - {} - - "#, format_duration(ticks)) - } else { - String::new() - }, - if sermon.media_type.as_ref().unwrap_or(&"Audio".to_string()) == "Audio" { "music" } else { "video" }, - if sermon.media_type.as_ref().unwrap_or(&"Audio".to_string()) == "Audio" { "Audio" } else { "Video" }, - sermon.id, - if sermon.media_type.as_ref().unwrap_or(&"Audio".to_string()) == "Audio" { "play" } else { "play-circle" }, - if sermon.media_type.as_ref().unwrap_or(&"Audio".to_string()) == "Audio" { "Listen" } else { "Watch" }, - if collection_name == "LiveStreams" { "Recording" } else { "Sermon" } - ) - }).collect::>().join("")) - }) - }).collect::>().join("")) -} - -pub async fn sermon_detail_handler(Path(id): Path) -> Html { - let api_service = ApiService::new(); - - match api_service.get_jellyfin_sermon(&id).await { - Ok(Some(sermon)) => { - match api_service.authenticate_jellyfin().await { - Ok(Some((token, _))) => { - let parsed = parse_sermon_title(&sermon.name); - let stream_url = api_service.get_jellyfin_stream_url(&sermon.id, &token); - - let content = format!(r#" - -
-
- - Back to Sermons - -

{}

- {} - -
- {} - - {} -
-
-
- - -
-
-
-
- -
-

{}

- - {} -
-
-
- - {} - - -
-
-
-
- -
-

Share This Sermon

-

Invite others to listen to this inspiring message:

- -
-
-
- - - "#, - parsed.title, - if let Some(ref speaker) = parsed.speaker { - format!(r#" -

- Speaker: {} -

- "#, speaker) - } else { - String::new() - }, - if let Some(ref premiere_date) = sermon.premiere_date { - format!(r#" -

- - {} -

- "#, format_date(premiere_date)) - } else if let Some(ref date_from_title) = parsed.date_from_title { - format!(r#" -

- - {} -

- "#, date_from_title) - } else { - String::new() - }, - if let Some(ticks) = sermon.run_time_ticks { - format!(r#" -

- - {} -

- "#, format_duration(ticks)) - } else { - String::new() - }, - if sermon.media_type.as_ref().unwrap_or(&"Audio".to_string()) == "Audio" { "music" } else { "video" }, - if sermon.media_type.as_ref().unwrap_or(&"Audio".to_string()) == "Audio" { "Audio Sermon" } else { "Video Sermon" }, - if sermon.media_type.as_ref().unwrap_or(&"Audio".to_string()) == "Audio" { - format!(r#" - - "#, stream_url) - } else { - format!(r#" - - "#, stream_url) - }, - if let Some(ref overview) = sermon.overview { - format!(r#" -
-
-
-
- -
-

Description

-

{}

-
-
-
- "#, overview) - } else { - String::new() - } - ); - - Html(layout(&content, &parsed.title)) - }, - _ => render_sermon_error("Unable to access sermon content. Please try again later.") - } - }, - Ok(None) => render_sermon_error("The requested sermon could not be found."), - Err(_) => render_sermon_error("Unable to load sermon. Please try again later.") - } -} - -fn render_sermon_error(message: &str) -> Html { - let content = format!(r#" -
-
-
-

Error

-

{}

- โ† Back to Sermons -
-
-
- "#, message); - - Html(layout(&content, "Error")) -} - -pub async fn sermons_archive_handler(Query(params): Query) -> Html { - let api_service = ApiService::new(); - - match api_service.get_jellyfin_libraries().await { - Ok(libraries) => { - // If a specific collection is selected, show only that one - if let Some(selected_collection) = params.collection { - if let Some(library) = libraries.iter().find(|lib| lib.name == selected_collection) { - match api_service.get_jellyfin_sermons(Some(&library.id), None).await { - Ok(sermons) => { - let organized = organize_sermons_by_year_month(&sermons); - let mut years: Vec = organized.keys().cloned().collect(); - years.sort_by(|a, b| b.parse::().unwrap_or(0).cmp(&a.parse::().unwrap_or(0))); - - return render_archive_page(&organized, &years, &selected_collection, &libraries); - } - Err(_) => return render_error_page() - } - } else { - return render_error_page(); - } - } - - // Default to showing Sermons collection (skip multi-collection view) - let default_collection = libraries.iter() - .find(|lib| lib.name == "Sermons") - .or_else(|| libraries.first()); - - if let Some(library) = default_collection { - match api_service.get_jellyfin_sermons(Some(&library.id), None).await { - Ok(sermons) => { - let organized = organize_sermons_by_year_month(&sermons); - let mut years: Vec = organized.keys().cloned().collect(); - years.sort_by(|a, b| b.parse::().unwrap_or(0).cmp(&a.parse::().unwrap_or(0))); - - return render_archive_page(&organized, &years, &library.name, &libraries); - } - Err(_) => return render_error_page() - } - } else { - return render_error_page(); - } - } - Err(_) => return render_error_page() - } -} - -fn organize_sermons_by_year_month(sermons: &[crate::models::JellyfinItem]) -> HashMap>> { - let mut organized: HashMap>> = HashMap::new(); - - for sermon in sermons { - let parsed = parse_sermon_title(&sermon.name); - - let date = if let Some(ref date_str) = parsed.date_from_title { - // Try parsing the date from title using multiple formats - chrono::NaiveDate::parse_from_str(date_str, "%Y-%m-%d") - .or_else(|_| chrono::NaiveDate::parse_from_str(date_str, "%m/%d/%Y")) - .or_else(|_| chrono::NaiveDate::parse_from_str(date_str, "%m-%d-%Y")) - .unwrap_or_else(|_| { - // If date parsing fails, use premiere date or created date as fallback - if let Some(ref premiere_date) = sermon.premiere_date { - chrono::NaiveDate::parse_from_str(&premiere_date[..10], "%Y-%m-%d") - .unwrap_or_else(|_| chrono::Utc::now().naive_utc().date()) - } else if let Some(ref created_date) = sermon.date_created { - chrono::NaiveDate::parse_from_str(&created_date[..10], "%Y-%m-%d") - .unwrap_or_else(|_| chrono::Utc::now().naive_utc().date()) - } else { - chrono::Utc::now().naive_utc().date() - } - }) - } else if let Some(ref premiere_date) = sermon.premiere_date { - chrono::NaiveDate::parse_from_str(&premiere_date[..10], "%Y-%m-%d") - .unwrap_or_else(|_| chrono::Utc::now().naive_utc().date()) - } else if let Some(ref created_date) = sermon.date_created { - chrono::NaiveDate::parse_from_str(&created_date[..10], "%Y-%m-%d") - .unwrap_or_else(|_| chrono::Utc::now().naive_utc().date()) - } else { - chrono::Utc::now().naive_utc().date() - }; - - let year = date.year().to_string(); - let month = date.format("%B").to_string(); - - organized - .entry(year) - .or_insert_with(HashMap::new) - .entry(month) - .or_insert_with(Vec::new) - .push(sermon); - } - - organized -} - - -fn render_archive_page( - organized: &HashMap>>, - years: &[String], - selected_collection: &str, - libraries: &[crate::models::JellyfinLibrary] -) -> Html { - let collection_display_name = if selected_collection == "LiveStreams" { - "Live Stream Recordings" - } else { - "Sabbath Sermons" - }; - - let content = format!(r#" - -
-
-
- - Back to Latest Sermons - -

{} Archive

-

Browse the complete collection organized by year and month.

- - -
- {} -
-
-
-
- -
-
- {} -
-
- - - "#, - collection_display_name, - libraries.iter().map(|lib| { - let display_name = if lib.name == "LiveStreams" { "Live Streams" } else { "Sermons" }; - let icon = if lib.name == "LiveStreams" { "broadcast-tower" } else { "church" }; - let active_class = if lib.name == selected_collection { " active" } else { "" }; - - format!(r#" - {} - "#, lib.name, if active_class.is_empty() { " btn-outline" } else { " btn-primary" }, icon, display_name) - }).collect::>().join(""), - if years.is_empty() { - format!(r#"
-
- -
-

No {} Found

-

This collection doesn't contain any items yet. Please check back later.

-
"#, collection_display_name) - } else { - years.iter().map(|year| { - let year_data = organized.get(year).unwrap(); - let mut months: Vec<&String> = year_data.keys().collect(); - months.sort_by(|a, b| { - let month_a = chrono::NaiveDate::parse_from_str(&format!("{} 1, 2020", a), "%B %d, %Y").unwrap().month(); - let month_b = chrono::NaiveDate::parse_from_str(&format!("{} 1, 2020", b), "%B %d, %Y").unwrap().month(); - month_b.cmp(&month_a) - }); - - let total_items: usize = year_data.values().map(|sermons| sermons.len()).sum(); - - format!(r#" -
-
-

- {} -

-
- {} items - -
-
- - -
- "#, year, year, total_items, year, year, - months.iter().map(|month| { - let month_sermons = year_data.get(*month).unwrap(); - let month_id = format!("{}-{}", year, month.replace(" ", "")); - - format!(r#" -
-
-

- - {} {} -

-
- {} item{} - -
-
- - -
- "#, month_id, month, year, month_sermons.len(), if month_sermons.len() == 1 { "" } else { "s" }, month_id, month_id, - month_sermons.iter().map(|sermon| { - let parsed = parse_sermon_title(&sermon.name); - let premiere_date = sermon.premiere_date.as_ref().map(|d| format_date(d)).unwrap_or_default(); - let default_media_type = "Video".to_string(); - let media_type = sermon.media_type.as_ref().unwrap_or(&default_media_type); - - format!(r#" -
-
-

{}

-
- {} - {} - - {} - -
-
-
- -
-
- "#, sermon.id, parsed.title, - if let Some(speaker) = parsed.speaker { - format!(r#"{}"#, speaker) - } else { String::new() }, - if !premiere_date.is_empty() { - format!(r#"{}"#, premiere_date) - } else { String::new() }, - if media_type == "Audio" { "music" } else { "video" }, - media_type) - }).collect::>().join("")) - }).collect::>().join("")) - }).collect::>().join("") - }); - - Html(layout(&content, &format!("{} Archive", collection_display_name))) -} - -fn render_error_page() -> Html { - let content = r#" -
-
-
-
- -
-

Archive Unavailable

-

Unable to load sermon archive at this time. Please check back later or contact us for assistance.

- โ† Back to Latest Sermons -
-
-
- "#; - - Html(layout(content, "Sermon Archive")) -} \ No newline at end of file diff --git a/church-website-axum/src/layout.rs b/church-website-axum/src/layout.rs deleted file mode 100644 index c45dd0e..0000000 --- a/church-website-axum/src/layout.rs +++ /dev/null @@ -1,167 +0,0 @@ -pub fn layout(children: &str, title: &str) -> String { - format!(r#" - - - - - - {} - Rockville Tolland SDA Church - - - - - - - - - - - - - -
- {} -
- - -
-
-
-

- Rockville Tolland SDA Church -

-

- Proclaiming the Three Angels' Messages with Love and Hope -

- -
-
-
- -
-

Faith

-

Grounded in Scripture

-
- -
-
- -
-

Hope

-

In Christ's Return

-
- -
-
- -
-

Love

-

Through Service

-
-
- -
-

© 2025 Rockville Tolland SDA Church. All rights reserved.

-
-
-
-
- - - - - -"#, title, children) -} \ No newline at end of file diff --git a/church-website-axum/src/main.rs b/church-website-axum/src/main.rs deleted file mode 100644 index 4819d3c..0000000 --- a/church-website-axum/src/main.rs +++ /dev/null @@ -1,69 +0,0 @@ -use axum::{ - routing::{get, post}, - Router, -}; -use std::net::SocketAddr; -use tower_http::{services::ServeDir, cors::CorsLayer}; -use tracing::info; -use tracing_subscriber::fmt::init; - -mod handlers; -mod models; -mod services; -mod layout; - -use handlers::*; - -#[tokio::main] -async fn main() { - dotenvy::dotenv().ok(); - - init(); - - let app = Router::new() - .route("/", get(home::home_handler)) - .route("/about", get(about::about_handler)) - .route("/ministries", get(ministries::ministries_handler)) - .route("/sermons", get(sermons::sermons_handler)) - .route("/sermons/:id", get(sermons::sermon_detail_handler)) - .route("/sermons/archive", get(sermons::sermons_archive_handler)) - .route("/events", get(events::events_handler)) - .route("/events/upcoming", get(events::upcoming_events_handler)) - .route("/events/:id", get(events::event_detail_handler)) - .route("/bulletins", get(bulletins::bulletins_handler)) - .route("/bulletins/:id", get(bulletins::bulletin_detail_handler)) - .route("/contact", get(contact::contact_handler)) - .route("/contact", post(contact::contact_form_handler)) - .route("/debug/api", get(|| async { - use crate::services::ApiService; - let api = ApiService::new(); - let (config, events, bulletins) = tokio::join!( - api.get_config(), - api.get_events(None), // Get all events - api.get_bulletins() - ); - format!("Config: {:?}\nEvents: {:?}\nBulletins: {} items", - config.is_ok(), - match &events { - Ok(events) => format!("OK - {} items: {:?}", events.len(), events.iter().map(|e| &e.title).collect::>()), - Err(e) => format!("ERROR: {}", e) - }, - bulletins.as_ref().map(|b| b.len()).unwrap_or(0) - ) - })) - .nest_service("/css", ServeDir::new("css").precompressed_gzip()) - .nest_service("/js", ServeDir::new("js").precompressed_gzip()) - .nest_service("/images", ServeDir::new("images")) - .layer( - CorsLayer::new() - .allow_origin(tower_http::cors::Any) - .allow_methods([axum::http::Method::GET, axum::http::Method::POST]) - .allow_headers(tower_http::cors::Any) - ); - - let addr = SocketAddr::from(([0, 0, 0, 0], 3001)); - info!("Server running on {}", addr); - - let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); - axum::serve(listener, app).await.unwrap(); -} diff --git a/church-website-axum/src/models/mod.rs b/church-website-axum/src/models/mod.rs deleted file mode 100644 index 4940b17..0000000 --- a/church-website-axum/src/models/mod.rs +++ /dev/null @@ -1,220 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct ChurchConfig { - pub church_name: Option, - pub church_address: Option, - pub po_box: Option, - pub contact_phone: Option, - pub google_maps_url: Option, - pub about_text: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Event { - pub id: String, - pub title: String, - pub description: Option, - pub start_time: String, - pub end_time: String, - pub location: String, - pub location_url: Option, - pub category: Option, - pub image: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Bulletin { - pub id: String, - pub title: String, - pub date: String, - pub scripture_reading: Option, - pub sabbath_school: Option, - pub divine_worship: Option, - pub sunset: Option, - pub pdf_path: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct BibleVerse { - pub text: String, - pub reference: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct Sermon { - pub id: String, - pub title: String, - pub speaker: Option, - pub date: String, - pub description: Option, - pub audio_url: Option, - pub video_url: Option, - pub series: Option, - pub scripture: Option, - pub duration: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ContactForm { - pub first_name: String, - pub last_name: Option, - pub email: String, - #[serde(default)] - pub phone: Option, - pub subject: String, - pub message: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct JellyfinAuth { - #[serde(rename = "Username")] - pub username: String, - #[serde(rename = "Pw")] - pub pw: String, - #[serde(rename = "request")] - pub request: serde_json::Value, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct JellyfinAuthResponse { - #[serde(rename = "AccessToken")] - pub access_token: String, - #[serde(rename = "User")] - pub user: JellyfinUser, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct JellyfinUser { - #[serde(rename = "Id")] - pub id: String, - #[serde(rename = "Name")] - pub name: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct JellyfinItem { - #[serde(rename = "Id")] - pub id: String, - #[serde(rename = "Name")] - pub name: String, - #[serde(rename = "PremiereDate")] - pub premiere_date: Option, - #[serde(rename = "DateCreated")] - pub date_created: Option, - #[serde(rename = "MediaType")] - pub media_type: Option, - #[serde(rename = "RunTimeTicks")] - pub run_time_ticks: Option, - #[serde(rename = "Overview")] - pub overview: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct JellyfinLibrary { - #[serde(rename = "Id")] - pub id: String, - #[serde(rename = "Name")] - pub name: String, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct JellyfinItemsResponse { - #[serde(rename = "Items")] - pub items: Vec, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct JellyfinLibrariesResponse { - #[serde(rename = "Items")] - pub items: Vec, -} - -#[derive(Debug, Clone)] -pub struct ParsedSermon { - pub title: String, - pub speaker: Option, - pub date_from_title: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ApiResponse { - pub success: bool, - pub data: Option, - pub message: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ApiListResponse { - pub success: bool, - pub data: ApiListData, - pub message: Option, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ApiListData { - pub items: Vec, - pub total: u32, - pub page: u32, - pub per_page: u32, - pub has_more: bool, -} - -// Template-specific structs that match what templates expect -#[allow(dead_code)] -#[derive(Debug, Clone)] -pub struct TemplateEvent { - pub id: String, - pub title: String, - pub description: String, - pub start_time: String, - pub end_time: String, - pub location: String, - pub location_url: String, - pub category: String, - pub image: String, -} - -impl From for TemplateEvent { - fn from(event: Event) -> Self { - Self { - id: event.id, - title: event.title, - description: event.description.unwrap_or_default(), - start_time: event.start_time, - end_time: event.end_time, - location: event.location, - location_url: event.location_url.unwrap_or_default(), - category: event.category.unwrap_or_default(), - image: event.image.unwrap_or_default(), - } - } -} - -#[allow(dead_code)] -#[derive(Debug, Clone)] -pub struct TemplateBulletin { - pub id: String, - pub title: String, - pub date: String, - pub scripture_reading: String, - pub sabbath_school: String, - pub divine_worship: String, - pub sunset: String, - pub pdf_path: String, -} - -impl From for TemplateBulletin { - fn from(bulletin: Bulletin) -> Self { - Self { - id: bulletin.id, - title: bulletin.title, - date: bulletin.date, - scripture_reading: bulletin.scripture_reading.unwrap_or_default(), - sabbath_school: bulletin.sabbath_school.unwrap_or_default(), - divine_worship: bulletin.divine_worship.unwrap_or_default(), - sunset: bulletin.sunset.unwrap_or_default(), - pdf_path: bulletin.pdf_path.unwrap_or_default(), - } - } -} \ No newline at end of file diff --git a/church-website-axum/src/services/mod.rs b/church-website-axum/src/services/mod.rs deleted file mode 100644 index d5f4911..0000000 --- a/church-website-axum/src/services/mod.rs +++ /dev/null @@ -1,444 +0,0 @@ -use crate::models::*; -use anyhow::Result; -use reqwest::Client; - -pub struct ApiService { - client: Client, - base_url: String, - jellyfin_url: String, - jellyfin_username: String, - jellyfin_password: String, -} - -impl ApiService { - pub fn new() -> Self { - let client = Client::builder() - .timeout(std::time::Duration::from_secs(10)) - .connect_timeout(std::time::Duration::from_secs(5)) - .pool_idle_timeout(std::time::Duration::from_secs(90)) - .pool_max_idle_per_host(10) - .build() - .unwrap_or_else(|_| Client::new()); - - Self { - client, - base_url: "https://api.rockvilletollandsda.church".to_string(), - jellyfin_url: std::env::var("JELLYFIN_SERVER_URL") - .unwrap_or_else(|_| "https://jellyfin.rockvilletollandsda.church".to_string()), - jellyfin_username: std::env::var("JELLYFIN_USERNAME") - .unwrap_or_else(|_| "RTSDA Mobile".to_string()), - jellyfin_password: std::env::var("JELLYFIN_PASSWORD") - .unwrap_or_else(|_| "KingofMyLife!!".to_string()), - } - } - - pub async fn get_config(&self) -> Result> { - let response = self - .client - .get(&format!("{}/api/config", self.base_url)) - .header("User-Agent", "RTSDA-Website/1.0") - .send() - .await?; - - if response.status().is_success() { - let api_response: ApiResponse = response.json().await?; - Ok(api_response.data) - } else { - Ok(None) - } - } - - pub async fn get_events(&self, limit: Option) -> Result> { - let mut url = format!("{}/api/events/upcoming", self.base_url); - if let Some(limit) = limit { - url.push_str(&format!("?limit={}", limit)); - } - - let response = self - .client - .get(&url) - .header("User-Agent", "RTSDA-Website/1.0") - .send() - .await?; - - if response.status().is_success() { - let api_response: ApiResponse> = response.json().await?; - Ok(api_response.data.unwrap_or_default()) - } else { - Ok(vec![]) - } - } - - pub async fn get_event(&self, id: &str) -> Result> { - let response = self - .client - .get(&format!("{}/api/events/{}", self.base_url, id)) - .header("User-Agent", "RTSDA-Website/1.0") - .send() - .await?; - - if response.status().is_success() { - let api_response: ApiResponse = response.json().await?; - Ok(api_response.data) - } else { - Ok(None) - } - } - - pub async fn get_bulletins(&self) -> Result> { - let response = self - .client - .get(&format!("{}/api/bulletins", self.base_url)) - .header("User-Agent", "RTSDA-Website/1.0") - .send() - .await?; - - if response.status().is_success() { - let api_response: ApiListResponse = response.json().await?; - Ok(api_response.data.items) - } else { - Ok(vec![]) - } - } - - pub async fn get_current_bulletin(&self) -> Result> { - let response = self - .client - .get(&format!("{}/api/bulletins/current", self.base_url)) - .header("User-Agent", "RTSDA-Website/1.0") - .send() - .await?; - - if response.status().is_success() { - let api_response: ApiResponse = response.json().await?; - Ok(api_response.data) - } else { - Ok(None) - } - } - - pub async fn get_bulletin(&self, id: &str) -> Result> { - let response = self - .client - .get(&format!("{}/api/bulletins/{}", self.base_url, id)) - .header("User-Agent", "RTSDA-Website/1.0") - .send() - .await?; - - if response.status().is_success() { - let api_response: ApiResponse = response.json().await?; - Ok(api_response.data) - } else { - Ok(None) - } - } - - pub async fn get_random_verse(&self) -> Result> { - let response = self - .client - .get(&format!("{}/api/bible_verses/random", self.base_url)) - .header("User-Agent", "RTSDA-Website/1.0") - .send() - .await?; - - if response.status().is_success() { - let api_response: ApiResponse = response.json().await?; - Ok(api_response.data) - } else { - Ok(None) - } - } - - pub async fn submit_contact_form(&self, form: &ContactForm) -> Result { - let response = self - .client - .post(&format!("{}/api/contact", self.base_url)) - .header("User-Agent", "RTSDA-Website/1.0") - .json(form) - .send() - .await?; - - Ok(response.status().is_success()) - } - - #[allow(dead_code)] - pub async fn get_sermons(&self, limit: Option) -> Result> { - let mut url = format!("{}/api/sermons", self.base_url); - if let Some(limit) = limit { - url.push_str(&format!("?limit={}", limit)); - } - - let response = self - .client - .get(&url) - .header("User-Agent", "RTSDA-Website/1.0") - .send() - .await?; - - if response.status().is_success() { - let api_response: ApiListResponse = response.json().await?; - Ok(api_response.data.items) - } else { - Ok(vec![]) - } - } - - #[allow(dead_code)] - pub async fn get_sermon(&self, id: &str) -> Result> { - let response = self - .client - .get(&format!("{}/api/sermons/{}", self.base_url, id)) - .header("User-Agent", "RTSDA-Website/1.0") - .send() - .await?; - - if response.status().is_success() { - let api_response: ApiResponse = response.json().await?; - Ok(api_response.data) - } else { - Ok(None) - } - } - - #[allow(dead_code)] - pub async fn get_recent_sermons(&self, limit: Option) -> Result> { - let mut url = format!("{}/api/sermons/recent", self.base_url); - if let Some(limit) = limit { - url.push_str(&format!("?limit={}", limit)); - } - - let response = self - .client - .get(&url) - .header("User-Agent", "RTSDA-Website/1.0") - .send() - .await?; - - if response.status().is_success() { - let api_response: ApiListResponse = response.json().await?; - Ok(api_response.data.items) - } else { - Ok(vec![]) - } - } - - // Jellyfin integration methods - pub async fn authenticate_jellyfin(&self) -> Result> { - let auth_request = JellyfinAuth { - username: self.jellyfin_username.clone(), - pw: self.jellyfin_password.clone(), - request: serde_json::Value::Object(serde_json::Map::new()), - }; - - let response = self - .client - .post(&format!("{}/Users/authenticatebyname", self.jellyfin_url)) - .header("Content-Type", "application/json") - .header("X-Emby-Authorization", r#"MediaBrowser Client="RTSDA Church Website", Device="Web", DeviceId="church-website", Version="1.0.0""#) - .json(&auth_request) - .send() - .await?; - - if response.status().is_success() { - let auth_response: JellyfinAuthResponse = response.json().await?; - Ok(Some((auth_response.access_token, auth_response.user.id))) - } else { - Ok(None) - } - } - - pub async fn get_jellyfin_libraries(&self) -> Result> { - let auth = self.authenticate_jellyfin().await?; - if let Some((token, user_id)) = auth { - let response = self - .client - .get(&format!("{}/Users/{}/Views", self.jellyfin_url, user_id)) - .header("X-Emby-Authorization", format!(r#"MediaBrowser Token="{}""#, token)) - .send() - .await?; - - if response.status().is_success() { - let libraries_response: JellyfinLibrariesResponse = response.json().await?; - let sermon_libraries = libraries_response.items.into_iter() - .filter(|lib| lib.name == "Sermons" || lib.name == "LiveStreams") - .collect(); - Ok(sermon_libraries) - } else { - Ok(vec![]) - } - } else { - Ok(vec![]) - } - } - - pub async fn get_jellyfin_sermons(&self, parent_id: Option<&str>, limit: Option) -> Result> { - let auth = self.authenticate_jellyfin().await?; - if let Some((token, user_id)) = auth { - let mut params = vec![ - ("UserId".to_string(), user_id.clone()), - ("Recursive".to_string(), "true".to_string()), - ("IncludeItemTypes".to_string(), "Movie,Audio,Video".to_string()), - ("SortBy".to_string(), "DateCreated".to_string()), - ("SortOrder".to_string(), "Descending".to_string()), - ]; - - if let Some(l) = limit { - params.push(("Limit".to_string(), l.to_string())); - } - - if let Some(pid) = parent_id { - params.push(("ParentId".to_string(), pid.to_string())); - } - - let url_params: String = params.iter() - .map(|(k, v)| format!("{}={}", k, v)) - .collect::>() - .join("&"); - - let response = self - .client - .get(&format!("{}/Users/{}/Items?{}", self.jellyfin_url, user_id, url_params)) - .header("X-Emby-Authorization", format!(r#"MediaBrowser Token="{}""#, token)) - .send() - .await?; - - if response.status().is_success() { - let items_response: JellyfinItemsResponse = response.json().await?; - Ok(items_response.items) - } else { - Ok(vec![]) - } - } else { - Ok(vec![]) - } - } - - pub async fn get_jellyfin_sermon(&self, sermon_id: &str) -> Result> { - let auth = self.authenticate_jellyfin().await?; - if let Some((token, user_id)) = auth { - let response = self - .client - .get(&format!("{}/Users/{}/Items/{}", self.jellyfin_url, user_id, sermon_id)) - .header("X-Emby-Authorization", format!(r#"MediaBrowser Token="{}""#, token)) - .send() - .await?; - - if response.status().is_success() { - let sermon: JellyfinItem = response.json().await?; - Ok(Some(sermon)) - } else { - Ok(None) - } - } else { - Ok(None) - } - } - - pub fn get_jellyfin_stream_url(&self, sermon_id: &str, token: &str) -> String { - format!("{}/Videos/{}/stream?api_key={}&static=true", self.jellyfin_url, sermon_id, token) - } -} - -pub fn format_event_datetime(start_time: &str, end_time: &str) -> String { - use chrono::DateTime; - - // Parse the datetime strings - if let (Ok(start), Ok(end)) = ( - DateTime::parse_from_rfc3339(start_time).or_else(|_| DateTime::parse_from_str(start_time, "%Y-%m-%dT%H:%M:%S%.fZ")), - DateTime::parse_from_rfc3339(end_time).or_else(|_| DateTime::parse_from_str(end_time, "%Y-%m-%dT%H:%M:%S%.fZ")) - ) { - let start_date = start.format("%a, %b %d").to_string(); - let start_time_str = start.format("%l:%M %p").to_string().trim().to_string(); - let end_time_str = end.format("%l:%M %p").to_string().trim().to_string(); - - // Check if it's an all-day event (starts at midnight) - if start.format("%H:%M:%S").to_string() == "00:00:00" && - end.format("%H:%M:%S").to_string() == "00:00:00" { - return format!("{} - All Day", start_date); - } - - // Check if same day - if start.date_naive() == end.date_naive() { - format!("{} at {} - {}", start_date, start_time_str, end_time_str) - } else { - let end_date = end.format("%a, %b %d").to_string(); - format!("{} {} - {} {}", start_date, start_time_str, end_date, end_time_str) - } - } else { - // Fallback to simple formatting - format!("{} - {}", start_time, end_time) - } -} - -pub fn strip_html(html: &str) -> String { - // Simple HTML stripping and decode basic HTML entities - let mut result = html.to_string(); - - // Simple HTML tag removal (basic implementation) - while let Some(start) = result.find('<') { - if let Some(end) = result.find('>') { - if end > start { - result.replace_range(start..=end, ""); - } else { - break; - } - } else { - break; - } - } - - result - .replace(" ", " ") - .replace("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace(""", "\"") - .trim() - .to_string() -} - -pub fn parse_sermon_title(full_title: &str) -> ParsedSermon { - // Parse format: "Title - Speaker | Date" - let parts: Vec<&str> = full_title.split(" | ").collect(); - let title_and_speaker = parts[0]; - let date_from_title = if parts.len() > 1 { Some(parts[1].to_string()) } else { None }; - - let speaker_parts: Vec<&str> = title_and_speaker.split(" - ").collect(); - let title = speaker_parts[0].trim().to_string(); - let speaker = if speaker_parts.len() > 1 { - Some(speaker_parts[1].trim().to_string()) - } else { - None - }; - - ParsedSermon { - title, - speaker, - date_from_title, - } -} - -pub fn format_duration(ticks: u64) -> String { - let total_seconds = ticks / 10_000_000; - let hours = total_seconds / 3600; - let minutes = (total_seconds % 3600) / 60; - let seconds = total_seconds % 60; - - if hours > 0 { - format!("{}:{:02}:{:02}", hours, minutes, seconds) - } else { - format!("{}:{:02}", minutes, seconds) - } -} - -pub fn format_date(date_string: &str) -> String { - use chrono::NaiveDate; - - // For datetime strings, extract just the date part to avoid timezone conversion - let date_only = date_string.split('T').next().unwrap_or(date_string); - if let Ok(date) = NaiveDate::parse_from_str(date_only, "%Y-%m-%d") { - date.format("%B %d, %Y").to_string() - } else { - date_string.to_string() - } -} \ No newline at end of file diff --git a/church-website-axum/templates/about.html b/church-website-axum/templates/about.html deleted file mode 100644 index 24f4be0..0000000 --- a/church-website-axum/templates/about.html +++ /dev/null @@ -1,290 +0,0 @@ -{% extends "layout.html" %} - -{% block content %} - -
-
-
-

About Our Church

-

- Founded on Biblical principles and committed to proclaiming the Three Angels' Messages, we are a community of believers dedicated to worship, fellowship, and service. -

-
-
-
- - -
-
-
-
-
- -
-

Our Mission

-

- To proclaim the everlasting gospel of Jesus Christ in the context of the Three Angels' Messages of Revelation 14:6-12, - leading people to accept Jesus as their personal Savior and unite with His remnant church, discipling them to serve Him as Lord - and preparing them for His soon return. -

-
- -
-
- -
-

Our Heritage

-

- As part of the Seventh-day Adventist Church, we trace our roots to the great Second Advent awakening of the 1840s. - We are heirs of the Protestant Reformation and hold to the Bible as our only rule of faith and practice. -

-
- -
-
- -
-

Our Community

-

- We believe in fostering a welcoming community where every person can experience God's love, grow in faith, - and discover their unique gifts for ministry. Together, we serve our local community and support global mission. -

-
-
-
-
- - -
-
-
-

Our Core Beliefs

-

- Seventh-day Adventists accept the Bible as their only creed and hold certain fundamental beliefs to be the teaching of the Holy Scriptures. -

-
- -
-
-
- -
-

The Holy Scriptures

-

- The Holy Scriptures, Old and New Testaments, are the written Word of God, given by divine inspiration. - They are the authoritative revealer of doctrines, and the trustworthy record of God's acts in history. -

-
- -
-
- -
-

The Trinity

-

- There is one God: Father, Son, and Holy Spirit, a unity of three co-eternal Persons. - God is immortal, all-powerful, all-knowing, above all, and ever present. -

-
- -
-
- -
-

The Father

-

- God the eternal Father is the Creator, Source, Sustainer, and Sovereign of all creation. - He is just and holy, merciful and gracious, slow to anger, and abounding in steadfast love and faithfulness. -

-
- -
-
- -
-

The Son

-

- God the eternal Son became incarnate in Jesus Christ. Through Him all things were created, - the character of God is revealed, the salvation of humanity is accomplished, and the world is judged. -

-
- -
-
- -
-

The Holy Spirit

-

- God the eternal Spirit was active with the Father and the Son in Creation, incarnation, and redemption. - He inspired the writers of Scripture and filled Christ's life with power. -

-
- -
-
- -
-

Creation

-

- God is Creator of all things, and has revealed in Scripture the authentic account of His creative activity. - In six days the Lord made "the heaven and the earth" and rested on the seventh day. -

-
- -
-
- -
-

Nature of Humanity

-

- Man and woman were made in the image of God with individuality, the power and freedom to think and to do. - Though created free beings, each is an indivisible unity of body, mind, and spirit. -

-
- -
-
- -
-

The Sabbath

-

- The beneficent Creator, after the six days of Creation, rested on the seventh day and instituted the Sabbath - for all people as a memorial of Creation and a sign of sanctification. -

-
- -
-
- -
-

The Great Controversy

-

- All humanity is now involved in a great controversy between Christ and Satan regarding the character of God, - His law, and His sovereignty over the universe. -

-
- -
-
- -
-

Life, Death, and Resurrection of Christ

-

- In Christ's life of perfect obedience to God's will, His suffering, death, and resurrection, - God provided the only means of atonement for human sin. -

-
- -
-
- -
-

The Experience of Salvation

-

- In infinite love and mercy God made Christ, who knew no sin, to be sin for us, - so that in Him we might be made the righteousness of God. -

-
- -
-
- -
-

The Second Coming

-

- The second coming of Christ is the blessed hope of the church, the grand climax of the gospel. - The Savior's coming will be literal, personal, visible, and worldwide. -

-
-
-
-
- - -
-
-
-

The Three Angels' Messages

-

- Central to our identity as Seventh-day Adventists are the messages found in Revelation 14:6-12, - which we believe are particularly relevant for our time. -

-
- -
-
-
- -
-

First Angel's Message

-
- "Then I saw another angel flying in the midst of heaven, having the everlasting gospel to preach to those who dwell on the earthโ€”to every nation, tribe, tongue, and peopleโ€”saying with a loud voice, 'Fear God and give glory to Him, for the hour of His judgment has come; and worship Him who made heaven and earth, the sea and springs of water.'" -
- (Revelation 14:6-7) -

- This message calls all people to worship the Creator God who made heaven and earth. It emphasizes the everlasting gospel - and announces that the hour of God's judgment has come. This is a call to recognize God's authority as Creator and give Him glory. -

-
- -
-
- -
-

Second Angel's Message

-
- "And another angel followed, saying, 'Babylon is fallen, is fallen, that great city, because she has made all nations drink of the wine of the wrath of her fornication.'" -
- (Revelation 14:8) -

- This message warns about spiritual Babylon and announces its fall. It calls people to come out of false religious systems - and spiritual confusion, choosing truth over tradition and Scripture over human authority. -

-
- -
-
- -
-

Third Angel's Message

-
- "Then a third angel followed them, saying with a loud voice, 'If anyone worships the beast and his image, and receives his mark on his forehead or on his hand, he himself shall also drink of the wine of the wrath of God... Here is the patience of the saints; here are those who keep the commandments of God and the faith of Jesus.'" -
- (Revelation 14:9-12) -

- This message identifies God's faithful people as those who keep the commandments of God and have the faith of Jesus. - It emphasizes the importance of remaining faithful to all of God's commandments, including the seventh-day Sabbath, - while maintaining faith in Jesus Christ as our Savior. -

-
-
-
-
- - -
-
-
-

Join Our Church Family

-

- We invite you to join us in worship, fellowship, and service as we grow together in faith and prepare for Christ's return. -

- - -
-
-
-{% endblock %} \ No newline at end of file diff --git a/church-website-axum/templates/bulletin_detail.html b/church-website-axum/templates/bulletin_detail.html deleted file mode 100644 index ccbdd34..0000000 --- a/church-website-axum/templates/bulletin_detail.html +++ /dev/null @@ -1,65 +0,0 @@ -{% extends "layout.html" %} - -{% block content %} -{% if bulletin %} -
-
-
-

{{ bulletin.title }}

-

{{ bulletin.date }}

- - {% if bulletin.scripture_reading %} -
- Scripture Reading:
- {{ bulletin.scripture_reading }} -
- {% endif %} - - {% if bulletin.sabbath_school %} -
-

Sabbath School

- {{ bulletin.sabbath_school }} -
- {% endif %} - - {% if bulletin.divine_worship %} -
-

Divine Worship

- {{ bulletin.divine_worship }} -
- {% endif %} - - {% if bulletin.sunset %} -
-

Sunset

- {{ bulletin.sunset }} -
- {% endif %} - -
- {% if bulletin.pdf_path %} - - - Download PDF - - {% endif %} - - - Back to Bulletins - -
-
-
-
-{% else %} -
-
-
-

Bulletin Not Found

-

The bulletin you're looking for doesn't exist.

- View All Bulletins -
-
-
-{% endif %} -{% endblock %} \ No newline at end of file diff --git a/church-website-axum/templates/bulletins.html b/church-website-axum/templates/bulletins.html deleted file mode 100644 index 8d710ec..0000000 --- a/church-website-axum/templates/bulletins.html +++ /dev/null @@ -1,44 +0,0 @@ -{% extends "layout.html" %} - -{% block content %} -
-
-
-

Church Bulletins

-

Weekly worship programs and announcements

-
-
-
- -
-
- {% if !bulletins.is_empty() %} -
- {% for bulletin in bulletins %} -
-
- -
-

{{ bulletin.title }}

-

{{ bulletin.date }}

- {% if bulletin.scripture_reading %} -

{{ bulletin.scripture_reading }}

- {% endif %} -
- View Details - {% if bulletin.pdf_path %} - Download PDF - {% endif %} -
-
- {% endfor %} -
- {% else %} -
-

No bulletins available

-

Check back soon for new bulletins!

-
- {% endif %} -
-
-{% endblock %} \ No newline at end of file diff --git a/church-website-axum/templates/contact.html b/church-website-axum/templates/contact.html deleted file mode 100644 index 3d393aa..0000000 --- a/church-website-axum/templates/contact.html +++ /dev/null @@ -1,138 +0,0 @@ -{% extends "layout.html" %} - -{% block content %} -
-
-
-

Contact Us

-

We'd love to hear from you and answer any questions

-
-
-
- -{% if success %} -
-
-
-

Message Sent Successfully!

-

Thank you for your message. We'll get back to you soon.

-
-
-
-{% endif %} - -{% if error %} -
-
-
-

Error Sending Message

-

There was an error sending your message. Please try again later.

-
-
-
-{% endif %} - -
-
-
- -
-
- -
-

Church Information

- -

{{ church_name }}

- - {% if church_address %} -

- - {{ church_address }} -

- {% endif %} - - {% if po_box %} -

- - {{ po_box }} -

- {% endif %} - - {% if contact_phone %} -

- - {{ contact_phone }} -

- {% endif %} - - {% if google_maps_url %} - - {% endif %} - -
-

Service Times

-

Sabbath School: 9:30 AM

-

Divine Worship: 11:00 AM

-

Prayer Meeting: Wednesday 7:00 PM

-
-
- - -
-
- -
-

Send Us a Message

- -
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- - -
-
-
-
-
-{% endblock %} \ No newline at end of file diff --git a/church-website-axum/templates/event_detail.html b/church-website-axum/templates/event_detail.html deleted file mode 100644 index 7a58475..0000000 --- a/church-website-axum/templates/event_detail.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends "layout.html" %} - -{% block content %} -{% if event %} -
-
-
-

{{ event.title }}

-

{{ event.start_time }} - {{ event.end_time }}

-

{{ event.location }}

- {% if event.description %} -
{{ event.description }}
- {% endif %} -
-
-
-{% else %} -
-
-
-

Event Not Found

-

The event you're looking for doesn't exist.

- View All Events -
-
-
-{% endif %} -{% endblock %} \ No newline at end of file diff --git a/church-website-axum/templates/events.html b/church-website-axum/templates/events.html deleted file mode 100644 index b192e03..0000000 --- a/church-website-axum/templates/events.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends "layout.html" %} - -{% block content %} -
-
-
-

Upcoming Events

-

Join us for worship, fellowship, and community service

-
-
-
- -
-
- {% if !upcoming_events.is_empty() %} -
- {% for event in upcoming_events %} -
-
-
-

{{ event.title }}

-

{{ event.start_time }} - {{ event.end_time }}

-

{{ event.location }}

- {% if event.description %} -

{{ event.description }}

- {% endif %} -
-
- {% endfor %} -
- {% else %} -
-

No upcoming events at this time

-

Check back soon for new events and activities!

-
- {% endif %} -
-
-{% endblock %} \ No newline at end of file diff --git a/church-website-axum/templates/home.html b/church-website-axum/templates/home.html deleted file mode 100644 index 4e3b747..0000000 --- a/church-website-axum/templates/home.html +++ /dev/null @@ -1,358 +0,0 @@ -{% extends "layout.html" %} - -{% block content %} - -
-
-
-

- Welcome to
- {{ church_name }} -

-

- {{ about_text }} -

- -
-
-
- Sabbath School 9:30 AM -
-
-
- Divine Worship 11:00 AM -
-
-
- -
-
-
- -

First Angel

-

Fear God & Give Glory

-
-
-
-
- -

Second Angel

-

Babylon is Fallen

-
-
-
-
- -

Third Angel

-

Keep God's Commands

-
-
-
-
-
- - -
-
-
-

The Three Angels' Messages

-

Central to our mission as Seventh-day Adventists, these messages from Revelation 14 guide our purpose and calling.

-
- -
-
-
- -
-

First Angel's Message

-
- "Fear God and give glory to Him, for the hour of His judgment has come; and worship Him who made heaven and earth, the sea and springs of water." -
- (Revelation 14:6-7) -

The everlasting gospel calls all people to worship the Creator God who made heaven and earth, recognizing His authority and giving Him glory.

-
- -
-
- -
-

Second Angel's Message

-
- "Babylon is fallen, is fallen, that great city, because she has made all nations drink of the wine of the wrath of her fornication." -
- (Revelation 14:8) -

A warning about false religious systems and a call to come out of spiritual confusion, choosing truth over tradition.

-
- -
-
- -
-

Third Angel's Message

-
- "Here is the patience of the saints; here are those who keep the commandments of God and the faith of Jesus." -
- (Revelation 14:12) -

A call to remain faithful to God's commandments, including the seventh-day Sabbath, while maintaining faith in Jesus Christ.

-
-
-
-
- -{% if bible_verse %} -
-
-
-
- -
-

Today's Scripture

-
- "{{ bible_verse.text }}" -
- - {{ bible_verse.reference }} -
-
-
-{% endif %} - - -
-
-
-

Service Times

-

Join us for worship and fellowship

-
- -
-
-
- -
-

Sabbath School

-
9:30 AM
-

Join us for Bible study and fellowship every Sabbath morning

-
- -
-
- -
-

Divine Worship

-
11:00 AM
-

Worship service with inspiring sermons and uplifting music

-
- -
-
- -
-

Prayer Meeting

-
Wed 7:00 PM
-

Mid-week spiritual refreshment with prayer and Bible study

-
-
-
-
- -{% if current_bulletin %} - -
-
-
-

This Week's Bulletin

-

Stay informed about church activities and worship

-
- -
-
- -
-

{{ current_bulletin.title }}

-

- - {{ current_bulletin.date }} -

- {% if current_bulletin.scripture_reading %} -
- Scripture Reading:
- {{ current_bulletin.scripture_reading }} -
- {% endif %} -
- {% if current_bulletin.pdf_path %} - - - Download PDF - - {% endif %} - - - View Details - - - - View Archive - -
-
-
-
-{% endif %} - -{% if !upcoming_events.is_empty() %} - -
- -
-{% endif %} - - -
-
-
-

Our Core Beliefs

-

As Seventh-day Adventists, we accept the Bible as our only creed and hold certain fundamental beliefs to be the teaching of the Holy Scriptures.

-
- -
-
-
- -
-

The Holy Scriptures

-

The Holy Scriptures are the infallible revelation of God's will and the authoritative revealer of doctrines.

-
- -
-
- -
-

The Trinity

-

There is one God: Father, Son, and Holy Spirit, a unity of three co-eternal Persons.

-
- -
-
- -
-

The Sabbath

-

The seventh day of the week is the Sabbath of the Lord our God, a day of rest and worship.

-
- -
-
- -
-

The Second Coming

-

The second coming of Christ is the blessed hope of the church and the grand climax of the gospel.

-
-
-
-
- - -
-
-
-

Faith in Your Pocket

-

Access sermons, events, and stay connected with our church family through our mobile app designed for spiritual growth.

-
- -
-
- -
-

Download Our Mobile App

-

- Stay connected with sermons, events, and church activities wherever you go. - Our app makes it easy to access spiritual content and stay engaged with our community. -

- -
- - - Download on the App Store - - - - -
- -
-

- - Available on both iOS and Android platforms. Download today to access sermons, events, and stay connected with our church community. -

-
-
-
-
- - -{% endblock %} \ No newline at end of file diff --git a/church-website-axum/templates/home_rich.html b/church-website-axum/templates/home_rich.html deleted file mode 100644 index fe604dd..0000000 --- a/church-website-axum/templates/home_rich.html +++ /dev/null @@ -1,358 +0,0 @@ -{% extends "layout.html" %} - -{% block content %} - -
-
-
-

- Welcome to
- {{ church_name }} -

-

- {{ about_text }} -

- -
-
-
- Sabbath School 9:30 AM -
-
-
- Divine Worship 11:00 AM -
-
-
- -
-
-
- -

First Angel

-

Fear God & Give Glory

-
-
-
-
- -

Second Angel

-

Babylon is Fallen

-
-
-
-
- -

Third Angel

-

Keep God's Commands

-
-
-
-
-
- - -
-
-
-

The Three Angels' Messages

-

Central to our mission as Seventh-day Adventists, these messages from Revelation 14 guide our purpose and calling.

-
- -
-
-
- -
-

First Angel's Message

-
- "Fear God and give glory to Him, for the hour of His judgment has come; and worship Him who made heaven and earth, the sea and springs of water." -
- (Revelation 14:6-7) -

The everlasting gospel calls all people to worship the Creator God who made heaven and earth, recognizing His authority and giving Him glory.

-
- -
-
- -
-

Second Angel's Message

-
- "Babylon is fallen, is fallen, that great city, because she has made all nations drink of the wine of the wrath of her fornication." -
- (Revelation 14:8) -

A warning about false religious systems and a call to come out of spiritual confusion, choosing truth over tradition.

-
- -
-
- -
-

Third Angel's Message

-
- "Here is the patience of the saints; here are those who keep the commandments of God and the faith of Jesus." -
- (Revelation 14:12) -

A call to remain faithful to God's commandments, including the seventh-day Sabbath, while maintaining faith in Jesus Christ.

-
-
-
-
- - {% if bible_verse %} -
-
-
-
- -
-

Today's Scripture

-
- "{{ bible_verse.text }}" -
- - {{ bible_verse.reference }} -
-
-
- {% endif %} - - -
-
-
-

Service Times

-

Join us for worship and fellowship

-
- -
-
-
- -
-

Sabbath School

-
9:30 AM
-

Join us for Bible study and fellowship every Sabbath morning

-
- -
-
- -
-

Divine Worship

-
11:00 AM
-

Worship service with inspiring sermons and uplifting music

-
- -
-
- -
-

Prayer Meeting

-
Wed 7:00 PM
-

Mid-week spiritual refreshment with prayer and Bible study

-
-
-
-
- - {% if current_bulletin %} - -
-
-
-

This Week's Bulletin

-

Stay informed about church activities and worship

-
- -
-
- -
-

{{ current_bulletin.title }}

-

- - {{ current_bulletin.date }} -

- {% if current_bulletin.scripture_reading %} -
- Scripture Reading:
- {{ current_bulletin.scripture_reading }} -
- {% endif %} -
- {% if current_bulletin.pdf_path %} - - - Download PDF - - {% endif %} - - - View Details - - - - View Archive - -
-
-
-
- {% endif %} - - {% if !upcoming_events.is_empty() %} - -
- -
- {% endif %} - - -
-
-
-

Our Core Beliefs

-

As Seventh-day Adventists, we accept the Bible as our only creed and hold certain fundamental beliefs to be the teaching of the Holy Scriptures.

-
- -
-
-
- -
-

The Holy Scriptures

-

The Holy Scriptures are the infallible revelation of God's will and the authoritative revealer of doctrines.

-
- -
-
- -
-

The Trinity

-

There is one God: Father, Son, and Holy Spirit, a unity of three co-eternal Persons.

-
- -
-
- -
-

The Sabbath

-

The seventh day of the week is the Sabbath of the Lord our God, a day of rest and worship.

-
- -
-
- -
-

The Second Coming

-

The second coming of Christ is the blessed hope of the church and the grand climax of the gospel.

-
-
-
-
- - -
-
-
-

Faith in Your Pocket

-

Access sermons, events, and stay connected with our church family through our mobile app designed for spiritual growth.

-
- -
-
- -
-

Download Our Mobile App

-

- Stay connected with sermons, events, and church activities wherever you go. - Our app makes it easy to access spiritual content and stay engaged with our community. -

- -
- - - Download on the App Store - - - - -
- -
-

- - Available on both iOS and Android platforms. Download today to access sermons, events, and stay connected with our church community. -

-
-
-
-
- - -{% endblock %} \ No newline at end of file diff --git a/church-website-axum/templates/home_simple.html b/church-website-axum/templates/home_simple.html deleted file mode 100644 index 6d7adfe..0000000 --- a/church-website-axum/templates/home_simple.html +++ /dev/null @@ -1,51 +0,0 @@ -{% extends "layout.html" %} - -{% block content %} -
-
-
-

Welcome to {{ church_name }}

-

{{ about_text }}

- -
-
-
- -
-
-
-

Our Core Beliefs

-

As Seventh-day Adventists, we accept the Bible as our only creed.

-
- -
-
-
- -
-

The Holy Scriptures

-

The Holy Scriptures are the infallible revelation of God's will.

-
- -
-
- -
-

The Trinity

-

There is one God: Father, Son, and Holy Spirit.

-
- -
-
- -
-

The Sabbath

-

The seventh day is the Sabbath of the Lord our God.

-
-
-
-
-{% endblock %} \ No newline at end of file diff --git a/church-website-axum/templates/layout.html b/church-website-axum/templates/layout.html deleted file mode 100644 index 7346458..0000000 --- a/church-website-axum/templates/layout.html +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - {{ title }} - Rockville Tolland SDA Church - - - - - - - - - - - -
- {% block content %}{% endblock %} -
- - -
-
-
-

- Rockville Tolland SDA Church -

-

- Proclaiming the Three Angels' Messages with Love and Hope -

- -
-
-
- -
-

Faith

-

Grounded in Scripture

-
- -
-
- -
-

Hope

-

In Christ's Return

-
- -
-
- -
-

Love

-

Through Service

-
-
- -
-

© 2025 Rockville Tolland SDA Church. All rights reserved.

-
-
-
-
- - - - - - \ No newline at end of file diff --git a/church-website-axum/templates/ministries.html b/church-website-axum/templates/ministries.html deleted file mode 100644 index ee73774..0000000 --- a/church-website-axum/templates/ministries.html +++ /dev/null @@ -1,254 +0,0 @@ -{% extends "layout.html" %} - -{% block content %} - -
-
-
-

Our Ministries

-

- Discover how God is working through our church community to serve, grow, and share His love with others. -

-
-
-
- - -
-
-
- -
-
- -
-

Prayer Ministry

-

- Our prayer ministry is the spiritual heartbeat of our church. We believe in the power of prayer to transform lives, - heal communities, and advance God's kingdom. Join us for our weekly prayer meetings every Wednesday at 7:00 PM, - where we intercede for our church family, community needs, and global mission. -

-
- Schedule:
- Wednesday Prayer Meeting - 7:00 PM
- Sabbath Morning Prayer - 9:00 AM -
-
- - -
-
- -
-

Gardening Ministry

-

- Our gardening ministry combines our love for God's creation with practical service to our community. - We maintain a church garden that provides fresh produce for local food banks and teaches sustainable, - healthy living practices. Whether you're a seasoned gardener or just starting out, everyone is welcome to dig in! -

- -
- - -
-
- -
-

Bible Studies

-

- Deepen your understanding of God's Word through our various Bible study opportunities. - We offer both in-person and online studies covering topics from prophecy to practical Christian living. - Our studies are designed for all levels of biblical knowledge, from beginners to advanced students. -

- -
- - -
-
- -
-

Adventist Youth (AY)

-

- Our Adventist Youth program is designed to inspire young people to love Jesus, live with purpose, and serve others. - Through dynamic worship experiences, community service projects, and fellowship activities, - our youth develop strong Christian character and leadership skills. -

-
- Activities Include:
- โ€ข Weekly AY meetings
- โ€ข Community service projects
- โ€ข Youth camps and retreats
- โ€ข Leadership development -
-
- - -
-
- -
-

Health Ministry

-

- Following Christ's ministry of healing, we promote physical, mental, and spiritual wellness. - Our health ministry offers educational programs on nutrition, stress management, and natural remedies. - We believe that caring for our bodies is part of honoring God as our Creator. -

-
- Programs Offered:
- โ€ข Cooking classes (plant-based nutrition)
- โ€ข Health screenings
- โ€ข Stress management workshops
- โ€ข Walking groups -
-
- - -
-
- -
-

Training & Education

-

- We are committed to equipping our members for effective ministry and Christian living. - Our training programs cover topics such as evangelism, public speaking, Bible study methods, - and practical ministry skills. We believe every member is called to serve according to their gifts. -

-
- Training Areas:
- โ€ข Evangelism and witnessing
- โ€ข Bible study methods
- โ€ข Public speaking
- โ€ข Children's ministry -
-
-
-
-
- - -
-
-
-

Get Involved

-

- God has given each of us unique gifts and talents to serve His kingdom. - Discover how you can use your abilities to make a difference in our church and community. -

-
- -
-
- -
-

Find Your Ministry

-

- Whether you have a passion for prayer, teaching, music, community service, or something else entirely, - there's a place for you in our ministry team. Contact us to learn more about volunteer opportunities - and how you can use your gifts to serve others. -

- - -
-
-
- - -
-
-
-

Ministry Impact

-

- Through God's grace and your faithful service, our ministries are making a real difference in our community and beyond. -

-
- -
-
-
- -
-

150+

-

People Served Monthly

-

Through our various ministries, we reach over 150 people each month with God's love and practical help.

-
- -
-
- -
-

24/7

-

Prayer Chain

-

Our prayer warriors are available around the clock to lift up your needs and concerns to our loving Father.

-
- -
-
- -
-

500+

-

Pounds of Produce

-

Our garden ministry has donated over 500 pounds of fresh produce to local food banks this year.

-
- -
-
- -
-

12

-

Bible Studies Weekly

-

We conduct 12 Bible studies each week, helping people grow in their understanding of God's Word.

-
-
-
-
- - -
-
-
-

Join Us in Ministry

-

- Every member is a minister. Discover how God wants to use your unique gifts and talents to serve His kingdom. -

- - -
-
-
-{% endblock %} \ No newline at end of file diff --git a/church-website-axum/templates/sermon_detail.html b/church-website-axum/templates/sermon_detail.html deleted file mode 100644 index 4ba9905..0000000 --- a/church-website-axum/templates/sermon_detail.html +++ /dev/null @@ -1,14 +0,0 @@ -{% extends "layout.html" %} - -{% block content %} -
-
-
-

Sermon Player

-

Sermon ID: {{ sermon_id }}

-

Sermon player functionality will be implemented here.

- Back to Sermons -
-
-
-{% endblock %} \ No newline at end of file diff --git a/church-website-axum/templates/sermons.html b/church-website-axum/templates/sermons.html deleted file mode 100644 index df91b30..0000000 --- a/church-website-axum/templates/sermons.html +++ /dev/null @@ -1,34 +0,0 @@ -{% extends "layout.html" %} - -{% block content %} -
-
-
-

Sermons

-

Listen to inspiring messages from God's Word

-
-
-
- -
-
-
-
- -
-

Sermon Player Coming Soon

-

We're working on integrating our sermon player. In the meantime, you can:

- -
-
-
-{% endblock %} \ No newline at end of file diff --git a/church-website-axum/templates/sermons_archive.html b/church-website-axum/templates/sermons_archive.html deleted file mode 100644 index 1774b51..0000000 --- a/church-website-axum/templates/sermons_archive.html +++ /dev/null @@ -1,30 +0,0 @@ -{% extends "layout.html" %} - -{% block content %} -
-
-
-

Sermon Archive

-

Browse our collection of past sermons and messages

-
-
-
- -
-
-
-
- -
-

Archive Integration Coming Soon

-

We're working on integrating our sermon archive with Jellyfin. Check back soon!

- -
-
-
-{% endblock %} \ No newline at end of file diff --git a/claude b/claude deleted file mode 120000 index 29e83fa..0000000 --- a/claude +++ /dev/null @@ -1 +0,0 @@ -/home/rockvilleav/.claude/local/claude \ No newline at end of file diff --git a/clean_existing_html.sql b/clean_existing_html.sql deleted file mode 100644 index 6b1fe37..0000000 --- a/clean_existing_html.sql +++ /dev/null @@ -1,96 +0,0 @@ --- Script to clean existing HTML tags from database content --- Run this script to sanitize existing data in your database - --- Clean bulletins table -UPDATE bulletins SET - title = REGEXP_REPLACE(title, '<[^>]*>', '', 'g'), - sabbath_school = REGEXP_REPLACE(COALESCE(sabbath_school, ''), '<[^>]*>', '', 'g'), - divine_worship = REGEXP_REPLACE(COALESCE(divine_worship, ''), '<[^>]*>', '', 'g'), - scripture_reading = REGEXP_REPLACE(COALESCE(scripture_reading, ''), '<[^>]*>', '', 'g'), - sunset = REGEXP_REPLACE(COALESCE(sunset, ''), '<[^>]*>', '', 'g') -WHERE - title LIKE '%<%' OR - sabbath_school LIKE '%<%' OR - divine_worship LIKE '%<%' OR - scripture_reading LIKE '%<%' OR - sunset LIKE '%<%'; - --- Clean events table -UPDATE events SET - title = REGEXP_REPLACE(title, '<[^>]*>', '', 'g'), - description = REGEXP_REPLACE(description, '<[^>]*>', '', 'g'), - location = REGEXP_REPLACE(location, '<[^>]*>', '', 'g'), - location_url = REGEXP_REPLACE(COALESCE(location_url, ''), '<[^>]*>', '', 'g'), - category = REGEXP_REPLACE(category, '<[^>]*>', '', 'g'), - recurring_type = REGEXP_REPLACE(COALESCE(recurring_type, ''), '<[^>]*>', '', 'g') -WHERE - title LIKE '%<%' OR - description LIKE '%<%' OR - location LIKE '%<%' OR - location_url LIKE '%<%' OR - category LIKE '%<%' OR - recurring_type LIKE '%<%'; - --- Clean pending_events table -UPDATE pending_events SET - title = REGEXP_REPLACE(title, '<[^>]*>', '', 'g'), - description = REGEXP_REPLACE(description, '<[^>]*>', '', 'g'), - location = REGEXP_REPLACE(location, '<[^>]*>', '', 'g'), - location_url = REGEXP_REPLACE(COALESCE(location_url, ''), '<[^>]*>', '', 'g'), - category = REGEXP_REPLACE(category, '<[^>]*>', '', 'g'), - recurring_type = REGEXP_REPLACE(COALESCE(recurring_type, ''), '<[^>]*>', '', 'g'), - bulletin_week = REGEXP_REPLACE(bulletin_week, '<[^>]*>', '', 'g'), - submitter_email = REGEXP_REPLACE(COALESCE(submitter_email, ''), '<[^>]*>', '', 'g'), - admin_notes = REGEXP_REPLACE(COALESCE(admin_notes, ''), '<[^>]*>', '', 'g') -WHERE - title LIKE '%<%' OR - description LIKE '%<%' OR - location LIKE '%<%' OR - location_url LIKE '%<%' OR - category LIKE '%<%' OR - recurring_type LIKE '%<%' OR - bulletin_week LIKE '%<%' OR - submitter_email LIKE '%<%' OR - admin_notes LIKE '%<%'; - --- Clean contact_submissions table -UPDATE contact_submissions SET - first_name = REGEXP_REPLACE(first_name, '<[^>]*>', '', 'g'), - last_name = REGEXP_REPLACE(last_name, '<[^>]*>', '', 'g'), - email = REGEXP_REPLACE(email, '<[^>]*>', '', 'g'), - phone = REGEXP_REPLACE(COALESCE(phone, ''), '<[^>]*>', '', 'g'), - message = REGEXP_REPLACE(message, '<[^>]*>', '', 'g') -WHERE - first_name LIKE '%<%' OR - last_name LIKE '%<%' OR - email LIKE '%<%' OR - phone LIKE '%<%' OR - message LIKE '%<%'; - --- Clean church_config table -UPDATE church_config SET - church_name = REGEXP_REPLACE(church_name, '<[^>]*>', '', 'g'), - contact_email = REGEXP_REPLACE(contact_email, '<[^>]*>', '', 'g'), - contact_phone = REGEXP_REPLACE(COALESCE(contact_phone, ''), '<[^>]*>', '', 'g'), - church_address = REGEXP_REPLACE(church_address, '<[^>]*>', '', 'g'), - po_box = REGEXP_REPLACE(COALESCE(po_box, ''), '<[^>]*>', '', 'g'), - google_maps_url = REGEXP_REPLACE(COALESCE(google_maps_url, ''), '<[^>]*>', '', 'g'), - about_text = REGEXP_REPLACE(about_text, '<[^>]*>', '', 'g') -WHERE - church_name LIKE '%<%' OR - contact_email LIKE '%<%' OR - contact_phone LIKE '%<%' OR - church_address LIKE '%<%' OR - po_box LIKE '%<%' OR - google_maps_url LIKE '%<%' OR - about_text LIKE '%<%'; - --- Also clean HTML entities -UPDATE bulletins SET - title = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(title, '&', '&'), '<', '<'), '>', '>'), '"', '"'), ''', ''''), - sabbath_school = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(sabbath_school, ''), '&', '&'), '<', '<'), '>', '>'), '"', '"'), ''', ''''), - divine_worship = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(divine_worship, ''), '&', '&'), '<', '<'), '>', '>'), '"', '"'), ''', ''''), - scripture_reading = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(scripture_reading, ''), '&', '&'), '<', '<'), '>', '>'), '"', '"'), ''', ''''), - sunset = REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(sunset, ''), '&', '&'), '<', '<'), '>', '>'), '"', '"'), ''', ''''); - -SELECT 'Database cleaning completed. All HTML tags and entities have been removed from existing content.' as result; \ No newline at end of file diff --git a/comprehensive_test.sh b/comprehensive_test.sh deleted file mode 100755 index c0a9623..0000000 --- a/comprehensive_test.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash -echo "=== COMPREHENSIVE API TEST ===" - -# 1. Test Authentication -echo "1. Testing Authentication..." -TOKEN=$(curl -s -X POST https://api.rockvilletollandsda.church/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username": "admin", "password": "Alright8-Reapply-Shrewdly-Platter-Important-Keenness-Banking-Streak-Tactile"}' \ - | jq -r '.success') -echo "Auth: $TOKEN" - -# 2. Test Public Endpoints -echo "2. Testing Public Endpoints..." -curl -s https://api.rockvilletollandsda.church/api/events | jq '.success' -curl -s https://api.rockvilletollandsda.church/api/bulletins | jq '.success' -curl -s https://api.rockvilletollandsda.church/api/config | jq '.success' - -# 3. Test Admin Endpoints -echo "3. Testing Admin Endpoints..." -TOKEN=$(curl -s -X POST https://api.rockvilletollandsda.church/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username": "admin", "password": "Alright8-Reapply-Shrewdly-Platter-Important-Keenness-Banking-Streak-Tactile"}' \ - | jq -r '.data.token') - -curl -s -H "Authorization: Bearer $TOKEN" https://api.rockvilletollandsda.church/api/admin/events/pending | jq '.success' -curl -s -H "Authorization: Bearer $TOKEN" https://api.rockvilletollandsda.church/api/admin/config | jq '.success' - -# 4. Check for any remaining placeholder text -echo "4. Checking for placeholders..." -PLACEHOLDERS=$(grep -r "implement as needed\|TODO\|Working!\|n/a\|TBA" src/ 2>/dev/null | wc -l) -echo "Placeholder count: $PLACEHOLDERS" - -echo "=== TEST COMPLETE ===" diff --git a/current b/current deleted file mode 120000 index baf41d6..0000000 --- a/current +++ /dev/null @@ -1 +0,0 @@ -rtsda-v1.0-beta7.apk \ No newline at end of file diff --git a/debug_images.fish b/debug_images.fish deleted file mode 100755 index 7eeeadc..0000000 --- a/debug_images.fish +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env fish - -echo "๐Ÿ–ผ๏ธ DIRECT FILE COPY + PATH UPDATE (DEBUG)" -echo "==========================================" - -set API_BASE "https://api.rockvilletollandsda.church/api" -set STORAGE_PATH "/media/archive/pocketbase-temp/pocketbase/pb_data/storage/2tz9osuik53a0yh" -set OLD_PB_BASE "https://pocketbase.rockvilletollandsda.church/api" -set UPLOAD_DIR "/opt/rtsda/church-api/uploads/events" - -# Get token -set AUTH_RESPONSE (curl -s -X POST $API_BASE/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username": "admin", "password": "Alright8-Reapply-Shrewdly-Platter-Important-Keenness-Banking-Streak-Tactile"}') - -set JWT_TOKEN (echo $AUTH_RESPONSE | jq -r '.data.token') -echo "โœ… Got token" - -# Get events for matching -set NEW_EVENTS (curl -s -H "Authorization: Bearer $JWT_TOKEN" "$API_BASE/events?perPage=500") -echo $NEW_EVENTS | jq '.data.items | map({id, title})' > new_events.json - -set OLD_EVENTS (curl -s "$OLD_PB_BASE/collections/events/records?perPage=500") -echo $OLD_EVENTS | jq '.items | map({id, title})' > old_events.json - -# Test with just ONE event to see the actual API response -set test_dir (find $STORAGE_PATH -mindepth 1 -maxdepth 1 -type d -name '[a-z0-9]*' | head -1) -set old_id (basename $test_dir) -set image_file (find $test_dir -maxdepth 1 -name "*.webp" -type f | head -1) - -set old_event (cat old_events.json | jq --arg id "$old_id" '.[] | select(.id == $id)') -set title (echo $old_event | jq -r '.title') -set new_event (cat new_events.json | jq --arg title "$title" '.[] | select(.title == $title)') -set new_id (echo $new_event | jq -r '.id') - -set filename (basename $image_file) -set new_filename "$new_id-$filename" -set image_path "uploads/events/$new_filename" - -echo "๐Ÿงช Testing with: $title" -echo "Image path: $image_path" - -# Copy file -cp "$image_file" "$UPLOAD_DIR/$new_filename" -echo "โœ… File copied" - -# Test the API update with debug -echo "๐Ÿ“ค Testing API update..." -set update_response (curl -s -w "\nHTTP_CODE:%{http_code}\nCONTENT_TYPE:%{content_type}\n" \ - -X PUT \ - -H "Authorization: Bearer $JWT_TOKEN" \ - -H "Content-Type: application/json" \ - -d "{\"image\": \"$image_path\"}" \ - "$API_BASE/admin/events/$new_id") - -echo "RAW API RESPONSE:" -echo "$update_response" - -echo "" -echo "๐Ÿ” Checking if file is accessible..." -curl -I "https://api.rockvilletollandsda.church/$image_path" - -rm -f new_events.json old_events.json diff --git a/fix_config.sh b/fix_config.sh deleted file mode 100755 index e77fc56..0000000 --- a/fix_config.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash - -echo "=== FIXING BROKEN CONFIG FILE ===" - -# Let's recreate the config.rs file properly -cat > src/handlers/config.rs << 'EOF' -use axum::{extract::State, response::Json}; -use serde_json::Value; - -use crate::error::{ApiError, Result}; -use crate::models::{ApiResponse, ChurchConfig}; -use crate::AppState; - -pub async fn get_public_config(State(state): State) -> Result>> { - let config = crate::db::config::get_config(&state.pool).await? - .ok_or_else(|| ApiError::NotFound("Church config not found".to_string()))?; - - // Return only public information (no API keys) - let public_config = serde_json::json!({ - "church_name": config.church_name, - "contact_email": config.contact_email, - "contact_phone": config.contact_phone, - "church_address": config.church_address, - "po_box": config.po_box, - "google_maps_url": config.google_maps_url, - "about_text": config.about_text - }); - - Ok(Json(ApiResponse { - success: true, - data: Some(public_config), - message: None, - })) -} - -pub async fn get_admin_config(State(state): State) -> Result>> { - let config = crate::db::config::get_config(&state.pool).await? - .ok_or_else(|| ApiError::NotFound("Church config not found".to_string()))?; - - Ok(Json(ApiResponse { - success: true, - data: Some(config), - message: None, - })) -} -EOF - -# Remove files handler if it exists -rm -f src/handlers/files.rs - -# Remove files from mod.rs if it exists -sed -i '/mod files;/d' src/handlers/mod.rs 2>/dev/null || true - -# Build to verify -echo "=== Building to verify fix ===" -cargo build --release - -if [ $? -eq 0 ]; then - echo "โœ… Build successful!" - - # Check for remaining placeholders - echo "=== Checking for remaining placeholders ===" - REMAINING=$(grep -r "implement as needed\|TODO\|Working\|TBA" src/ 2>/dev/null | wc -l) - echo "Remaining placeholders: $REMAINING" - - if [ $REMAINING -eq 0 ]; then - echo "๐ŸŽ‰ ALL PLACEHOLDERS REMOVED!" - echo "๐ŸŽ‰ YOUR CHURCH API IS 100% COMPLETE!" - - # Restart service - sudo systemctl restart church-api - echo "โœ… Service restarted successfully!" - else - echo "Remaining placeholders:" - grep -r "implement as needed\|TODO\|Working\|TBA" src/ 2>/dev/null - fi -else - echo "โŒ Build still failing - need to debug further" -fi diff --git a/fix_errors.sh b/fix_errors.sh deleted file mode 100755 index 997991e..0000000 --- a/fix_errors.sh +++ /dev/null @@ -1,15 +0,0 @@ -# Add missing imports to db/events.rs -sed -i '1i use crate::models::PaginatedResponse;' src/db/events.rs - -# Add missing import to handlers/events.rs -sed -i '/use crate::models::/s/$/,PaginationParams/' src/handlers/events.rs - -# Fix ApiError::Internal to ValidationError (check what exists) -grep "enum ApiError" -A 10 src/error.rs - -# Fix the admin_notes type issue -sed -i 's/admin_notes: &Option/admin_notes: Option<&String>/' src/db/events.rs -sed -i 's/&req.admin_notes/req.admin_notes.as_ref()/' src/db/events.rs - -# Replace Internal with ValidationError -sed -i 's/ApiError::Internal/ApiError::ValidationError/g' src/db/events.rs diff --git a/fix_handlers.sh b/fix_handlers.sh deleted file mode 100755 index fbf9d5d..0000000 --- a/fix_handlers.sh +++ /dev/null @@ -1,31 +0,0 @@ -# Fix the approve handler -sed -i '/pub async fn approve(/,/^}/c\ -pub async fn approve(\ - Path(id): Path,\ - State(state): State,\ - Json(req): Json,\ -) -> Result>> {\ - let event = crate::db::events::approve_pending(\&state.pool, \&id, req.admin_notes).await?;\ - \ - Ok(Json(ApiResponse {\ - success: true,\ - data: Some(event),\ - message: Some("Event approved successfully".to_string()),\ - }))\ -}' src/handlers/events.rs - -# Fix the reject handler -sed -i '/pub async fn reject(/,/^}/c\ -pub async fn reject(\ - Path(id): Path,\ - State(state): State,\ - Json(req): Json,\ -) -> Result>> {\ - crate::db::events::reject_pending(\&state.pool, \&id, req.admin_notes).await?;\ - \ - Ok(Json(ApiResponse {\ - success: true,\ - data: Some("Event rejected".to_string()),\ - message: Some("Event rejected successfully".to_string()),\ - }))\ -}' src/handlers/events.rs diff --git a/fix_handlers_with_email.sh b/fix_handlers_with_email.sh deleted file mode 100755 index ab933f2..0000000 --- a/fix_handlers_with_email.sh +++ /dev/null @@ -1,45 +0,0 @@ -# Fix the approve handler with email -sed -i '/pub async fn approve(/,/^}/c\ -pub async fn approve(\ - Path(id): Path,\ - State(state): State,\ - Json(req): Json,\ -) -> Result>> {\ - let pending_event = crate::db::events::get_pending_by_id(\&state.pool, \&id).await?\ - .ok_or_else(|| ApiError::NotFound("Pending event not found".to_string()))?;\ - \ - let event = crate::db::events::approve_pending(\&state.pool, \&id, req.admin_notes.clone()).await?;\ - \ - if let Some(_submitter_email) = \&pending_event.submitter_email {\ - let _ = state.mailer.send_event_approval_notification(\&pending_event, req.admin_notes.as_deref()).await;\ - }\ - \ - Ok(Json(ApiResponse {\ - success: true,\ - data: Some(event),\ - message: Some("Event approved successfully".to_string()),\ - }))\ -}' src/handlers/events.rs - -# Fix the reject handler with email -sed -i '/pub async fn reject(/,/^}/c\ -pub async fn reject(\ - Path(id): Path,\ - State(state): State,\ - Json(req): Json,\ -) -> Result>> {\ - let pending_event = crate::db::events::get_pending_by_id(\&state.pool, \&id).await?\ - .ok_or_else(|| ApiError::NotFound("Pending event not found".to_string()))?;\ - \ - crate::db::events::reject_pending(\&state.pool, \&id, req.admin_notes.clone()).await?;\ - \ - if let Some(_submitter_email) = \&pending_event.submitter_email {\ - let _ = state.mailer.send_event_rejection_notification(\&pending_event, req.admin_notes.as_deref()).await;\ - }\ - \ - Ok(Json(ApiResponse {\ - success: true,\ - data: Some("Event rejected".to_string()),\ - message: Some("Event rejected successfully".to_string()),\ - }))\ -}' src/handlers/events.rs diff --git a/fix_image_path.fish b/fix_image_path.fish deleted file mode 100755 index 2f9c49c..0000000 --- a/fix_image_path.fish +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env fish -echo "๐Ÿ”ง COMPLETE IMAGE_PATH FIX (v5 - SUBMISSION + UPDATES)" -echo "=====================================================" - -# Restore from backups first -if test -f "src/models.rs.backup2" - cp src/models.rs.backup2 src/models.rs - cp src/db/events.rs.backup2 src/db/events.rs - cp src/handlers/events.rs.backup2 src/handlers/events.rs - echo "โœ… Restored from backups" -end - -echo "1๏ธโƒฃ Adding image_path to BOTH CreateEventRequest AND SubmitEventRequest..." - -# Add to CreateEventRequest -sed -i '/pub struct CreateEventRequest {/,/^}/ { - /pub recurring_type: Option,/ a\ - pub image_path: Option, -}' src/models.rs - -# Add to SubmitEventRequest -sed -i '/pub struct SubmitEventRequest {/,/^}/ { - /pub submitter_email: Option,/ a\ - pub image_path: Option, -}' src/models.rs - -echo "2๏ธโƒฃ Adding image_path to SubmitEventRequest initialization in handlers..." -sed -i '/let mut req = SubmitEventRequest {/,/};/ { - /submitter_email: None,/ a\ - image_path: None, -}' src/handlers/events.rs - -echo "3๏ธโƒฃ Fixing the submit_for_approval function SQL..." -# Update the INSERT statement to include image_path -sed -i 's|category, is_featured, recurring_type, bulletin_week, submitter_email|category, is_featured, recurring_type, bulletin_week, submitter_email, image_path|' src/db/events.rs -sed -i 's|VALUES (\$1, \$2, \$3, \$4, \$5, \$6, \$7, \$8, \$9, \$10, \$11)|VALUES (\$1, \$2, \$3, \$4, \$5, \$6, \$7, \$8, \$9, \$10, \$11, \$12)|' src/db/events.rs - -echo "4๏ธโƒฃ Fixing the events table update function..." -sed -i 's|recurring_type = \$9, updated_at = NOW()|recurring_type = \$9, image_path = \$10, updated_at = NOW()|' src/db/events.rs -sed -i 's|WHERE id = \$10|WHERE id = \$11|' src/db/events.rs -sed -i '/req\.recurring_type,$/a\ req.image_path,' src/db/events.rs - -echo "5๏ธโƒฃ Building..." -if cargo build - echo "โœ… SUCCESS! Both submission and updates now support image_path." - echo "" - echo "=== What was fixed ===" - echo "โœ… Added image_path to CreateEventRequest struct (for updates)" - echo "โœ… Added image_path to SubmitEventRequest struct (for new submissions)" - echo "โœ… Updated handlers to initialize image_path field" - echo "โœ… Fixed submit_for_approval SQL to include image_path column" - echo "โœ… Fixed update SQL to include image_path column" - echo "" - echo "๐Ÿš€ Next steps:" - echo "1. Restart your API server" - echo "2. Run your image_path update script" - echo "3. Both new submissions AND existing event updates will handle image_path!" -else - echo "โŒ Build failed. Let's debug..." - echo "" - echo "=== Current structs ===" - grep -A 15 "pub struct CreateEventRequest" src/models.rs - echo "" - grep -A 15 "pub struct SubmitEventRequest" src/models.rs - echo "" - echo "=== Current submit_for_approval function ===" - grep -A 15 "submit_for_approval.*SubmitEventRequest" src/db/events.rs - - # Restore - cp src/models.rs.backup2 src/models.rs - cp src/db/events.rs.backup2 src/db/events.rs - cp src/handlers/events.rs.backup2 src/handlers/events.rs - echo "๐Ÿ”„ Restored backups" -end diff --git a/fix_images.fish b/fix_images.fish deleted file mode 100755 index d47ab2f..0000000 --- a/fix_images.fish +++ /dev/null @@ -1,125 +0,0 @@ -#!/usr/bin/env fish - -echo "๐Ÿ–ผ๏ธ MIGRATING ALL IMAGE FORMATS (JPEG, PNG, etc.)" -echo "================================================" - -set API_BASE "https://api.rockvilletollandsda.church/api" -set STORAGE_PATH "/media/archive/pocketbase-temp/pocketbase/pb_data/storage/2tz9osuik53a0yh" -set OLD_PB_BASE "https://pocketbase.rockvilletollandsda.church/api" -set UPLOAD_DIR "/opt/rtsda/church-api/uploads/events" - -# Get token -set AUTH_RESPONSE (curl -s -X POST $API_BASE/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username": "admin", "password": "Alright8-Reapply-Shrewdly-Platter-Important-Keenness-Banking-Streak-Tactile"}') - -set JWT_TOKEN (echo $AUTH_RESPONSE | jq -r '.data.token') -echo "โœ… Got token" - -# Get events for matching -set NEW_EVENTS (curl -s -H "Authorization: Bearer $JWT_TOKEN" "$API_BASE/events?perPage=500") -echo $NEW_EVENTS | jq '.data.items | map({id, title})' > new_events.json - -set OLD_EVENTS (curl -s "$OLD_PB_BASE/collections/events/records?perPage=500") -echo $OLD_EVENTS | jq '.items | map({id, title})' > old_events.json - -set uploaded 0 -set failed 0 - -for event_dir in (find $STORAGE_PATH -mindepth 1 -maxdepth 1 -type d -name '[a-z0-9]*') - set old_id (basename $event_dir) - - # Find ANY image file (jpeg, png, gif, webp) - set image_files (find $event_dir -maxdepth 1 -type f \( -name "*.webp" -o -name "*.jpeg" -o -name "*.jpg" -o -name "*.png" -o -name "*.gif" \) | grep -v "100x100_") - - if test (count $image_files) -eq 0 - continue - end - - set image_file $image_files[1] # Take the first image - - # Get old event and find new match - set old_event (cat old_events.json | jq --arg id "$old_id" '.[] | select(.id == $id)') - if test -z "$old_event" - continue - end - - set title (echo $old_event | jq -r '.title') - set new_event (cat new_events.json | jq --arg title "$title" '.[] | select(.title == $title)') - - if test -z "$new_event" - echo "โŒ No match for: $title" - continue - end - - set new_id (echo $new_event | jq -r '.id') - set original_filename (basename $image_file) - set extension (echo $original_filename | sed 's/.*\.//') - - # Create WebP filename - set base_name (echo $original_filename | sed 's/\.[^.]*$//') - set webp_filename "$new_id-$base_name.webp" - set webp_path "$UPLOAD_DIR/$webp_filename" - - echo "๐Ÿ“ค Processing: $title" - echo " Source: $original_filename ($extension)" - echo " Target: $webp_filename" - - # Convert to WebP (works for any input format) - if convert "$image_file" "$webp_path" - echo "โœ… Converted to WebP" - - # Update database - set current_event (curl -s -H "Authorization: Bearer $JWT_TOKEN" "$API_BASE/events/$new_id") - - set simple_filename (echo $webp_filename | sed "s/^$new_id-//") - set image_path "uploads/events/$webp_filename" - - set event_data (echo $current_event | jq --arg img "$simple_filename" --arg imgpath "$image_path" \ - '.data | { - title: .title, - description: .description, - start_time: .start_time, - end_time: .end_time, - location: .location, - location_url: .location_url, - category: .category, - recurring_type: .recurring_type, - is_featured: .is_featured, - image: $img, - image_path: $imgpath - }') - - set update_response (curl -s -X PUT \ - -H "Authorization: Bearer $JWT_TOKEN" \ - -H "Content-Type: application/json" \ - -d "$event_data" \ - "$API_BASE/admin/events/$new_id") - - set success (echo $update_response | jq -r '.success // false') - - if test "$success" = "true" - echo "โœ… SUCCESS: $title" - set uploaded (math $uploaded + 1) - else - echo "โŒ DB UPDATE FAILED: $title" - set failed (math $failed + 1) - end - else - echo "โŒ CONVERSION FAILED: $title" - set failed (math $failed + 1) - end - - echo "---" - sleep 0.1 -end - -rm -f new_events.json old_events.json - -echo "" -echo "๐ŸŽ‰ FINAL RESULTS!" -echo "=================" -echo "โœ… Successfully converted: $uploaded images" -echo "โŒ Failed: $failed images" -echo "" -echo "All images now converted to WebP format!" diff --git a/fix_migration.py b/fix_migration.py deleted file mode 100644 index 7d058b8..0000000 --- a/fix_migration.py +++ /dev/null @@ -1,119 +0,0 @@ -import json -import psycopg2 -import os -from datetime import datetime -import uuid - -# Connect to database -conn = psycopg2.connect(os.environ['DATABASE_URL']) -cur = conn.cursor() - -def load_json(filename): - try: - with open(f'/tmp/pb_migration/{filename}', 'r') as f: - data = json.load(f) - return data.get('items', []) - except Exception as e: - print(f"Error loading {filename}: {e}") - return [] - -def convert_pb_date(pb_date): - """Convert PocketBase date to PostgreSQL timestamp""" - if not pb_date: - return None - try: - # Remove 'Z' and parse - dt_str = pb_date.replace('Z', '+00:00') - return datetime.fromisoformat(dt_str) - except: - print(f"Failed to parse date: {pb_date}") - return None - -# Clear existing data (except users) -print("๐Ÿงน Clearing existing data...") -cur.execute("DELETE FROM bulletins WHERE id != '00000000-0000-0000-0000-000000000000'") -cur.execute("DELETE FROM events WHERE id != '00000000-0000-0000-0000-000000000000'") - -# Import bulletins -print("๐Ÿ“„ Importing bulletins...") -bulletins = load_json('bulletins.json') -print(f"Found {len(bulletins)} bulletins to import") - -for bulletin in bulletins: - try: - cur.execute(""" - INSERT INTO bulletins (id, title, date, url, pdf_url, is_active, pdf_file, - sabbath_school, divine_worship, scripture_reading, sunset, - cover_image, created_at, updated_at) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - """, ( - str(uuid.uuid4()), - bulletin.get('title', ''), - bulletin.get('date'), # PocketBase dates should work directly - bulletin.get('url'), - bulletin.get('pdf_url'), - bulletin.get('is_active', True), - bulletin.get('pdf'), - bulletin.get('sabbath_school', ''), - bulletin.get('divine_worship', ''), - bulletin.get('scripture_reading'), - bulletin.get('sunset', ''), - bulletin.get('cover_image'), - convert_pb_date(bulletin.get('created')), - convert_pb_date(bulletin.get('updated')) - )) - print(f" โœ… Imported: {bulletin.get('title')}") - except Exception as e: - print(f" โŒ Failed to import bulletin: {e}") - print(f" Data: {bulletin}") - -# Import events -print("๐Ÿ“… Importing events...") -events = load_json('events.json') -print(f"Found {len(events)} events to import") - -for event in events: - try: - cur.execute(""" - INSERT INTO events (id, title, description, start_time, end_time, location, - location_url, image, thumbnail, category, is_featured, - recurring_type, approved_from, created_at, updated_at) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - """, ( - str(uuid.uuid4()), - event.get('title', ''), - event.get('description', ''), - event.get('start_time'), # Let PostgreSQL handle the date conversion - event.get('end_time'), - event.get('location', ''), - event.get('location_url'), - event.get('image'), - event.get('thumbnail'), - event.get('category', 'Other'), - event.get('is_featured', False), - event.get('reoccuring'), # Note: PocketBase spells it 'reoccuring' - event.get('approved_from'), - convert_pb_date(event.get('created')), - convert_pb_date(event.get('updated')) - )) - print(f" โœ… Imported: {event.get('title')}") - except Exception as e: - print(f" โŒ Failed to import event: {e}") - print(f" Data: {event}") - -# Commit changes -conn.commit() -print("โœ… Migration fixed!") - -# Show results -cur.execute("SELECT COUNT(*) FROM bulletins") -bulletin_count = cur.fetchone()[0] -cur.execute("SELECT COUNT(*) FROM events") -event_count = cur.fetchone()[0] - -print(f"๐Ÿ“Š Results:") -print(f" Bulletins: {bulletin_count}") -print(f" Events: {event_count}") - -cur.close() -conn.close() diff --git a/fix_migration_v2.py b/fix_migration_v2.py deleted file mode 100644 index fa4ed3a..0000000 --- a/fix_migration_v2.py +++ /dev/null @@ -1,138 +0,0 @@ -import json -import psycopg2 -import os -from datetime import datetime -import uuid - -# Connect to database -conn = psycopg2.connect(os.environ['DATABASE_URL']) -cur = conn.cursor() - -def load_json(filename): - try: - with open(f'/tmp/pb_migration/{filename}', 'r') as f: - data = json.load(f) - return data.get('items', []) - except Exception as e: - print(f"Error loading {filename}: {e}") - return [] - -def convert_pb_date(pb_date): - """Convert PocketBase date to PostgreSQL timestamp""" - if not pb_date: - return None - try: - dt_str = pb_date.replace('Z', '+00:00') - return datetime.fromisoformat(dt_str) - except: - return None - -def clean_recurring_type(value): - """Clean recurring type field""" - if not value or value == '': - return None - return value - -# Rollback any pending transaction -conn.rollback() - -# Clear existing data -print("๐Ÿงน Clearing existing data...") -cur.execute("DELETE FROM bulletins") -cur.execute("DELETE FROM events") - -# Import bulletins -print("๐Ÿ“„ Importing bulletins...") -bulletins = load_json('bulletins.json') -print(f"Found {len(bulletins)} bulletins to import") - -for i, bulletin in enumerate(bulletins): - try: - cur.execute(""" - INSERT INTO bulletins (id, title, date, url, pdf_url, is_active, pdf_file, - sabbath_school, divine_worship, scripture_reading, sunset, - cover_image, created_at, updated_at) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - """, ( - str(uuid.uuid4()), - bulletin.get('title', ''), - bulletin.get('date'), - bulletin.get('url'), - bulletin.get('pdf_url'), - bulletin.get('is_active', True), - bulletin.get('pdf'), - bulletin.get('sabbath_school', ''), - bulletin.get('divine_worship', ''), - bulletin.get('scripture_reading'), - bulletin.get('sunset', ''), - bulletin.get('cover_image'), - convert_pb_date(bulletin.get('created')), - convert_pb_date(bulletin.get('updated')) - )) - print(f" โœ… Imported bulletin {i+1}: {bulletin.get('title')}") - except Exception as e: - print(f" โŒ Failed to import bulletin {i+1}: {e}") - continue - -# Import events -print("๐Ÿ“… Importing events...") -events = load_json('events.json') -print(f"Found {len(events)} events to import") - -for i, event in enumerate(events): - try: - cur.execute(""" - INSERT INTO events (id, title, description, start_time, end_time, location, - location_url, image, thumbnail, category, is_featured, - recurring_type, approved_from, created_at, updated_at) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - """, ( - str(uuid.uuid4()), - event.get('title', ''), - event.get('description', ''), - event.get('start_time'), - event.get('end_time'), - event.get('location', ''), - event.get('location_url'), - event.get('image'), - event.get('thumbnail'), - event.get('category', 'Other'), - event.get('is_featured', False), - clean_recurring_type(event.get('reoccuring')), # Fix this field - event.get('approved_from') if event.get('approved_from') else None, - convert_pb_date(event.get('created')), - convert_pb_date(event.get('updated')) - )) - print(f" โœ… Imported event {i+1}: {event.get('title')}") - except Exception as e: - print(f" โŒ Failed to import event {i+1}: {e}") - print(f" Title: {event.get('title')}") - continue - -# Commit all changes -conn.commit() -print("โœ… Migration completed!") - -# Show final results -cur.execute("SELECT COUNT(*) FROM bulletins") -bulletin_count = cur.fetchone()[0] -cur.execute("SELECT COUNT(*) FROM events") -event_count = cur.fetchone()[0] - -print(f"๐Ÿ“Š Final Results:") -print(f" Bulletins: {bulletin_count}") -print(f" Events: {event_count}") - -# Show sample data -print(f"\n๐Ÿ“„ Sample bulletins:") -cur.execute("SELECT title, date FROM bulletins ORDER BY date DESC LIMIT 3") -for row in cur.fetchall(): - print(f" - {row[0]} ({row[1]})") - -print(f"\n๐Ÿ“… Sample events:") -cur.execute("SELECT title, start_time FROM events ORDER BY start_time LIMIT 3") -for row in cur.fetchall(): - print(f" - {row[0]} ({row[1]})") - -cur.close() -conn.close() diff --git a/fix_routes.sh b/fix_routes.sh deleted file mode 100755 index f62efe2..0000000 --- a/fix_routes.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env fish - -echo "๐Ÿ”ง ACTUALLY FIXING THE ROUTES (NO BULLSHIT)" -echo "============================================" - -# Backup first -cp src/main.rs src/main.rs.backup - -# Fix admin routes: move pending routes before generic :id routes -sed -i '' ' -/\.route("\/events\/:id", put(handlers::events::update))/i\ - .route("/events/pending", get(handlers::events::list_pending))\ - .route("/events/pending/:id/approve", post(handlers::events::approve))\ - .route("/events/pending/:id/reject", post(handlers::events::reject))\ - .route("/events/pending/:id", delete(handlers::events::delete_pending)) -' src/main.rs - -# Remove the old pending routes that are now duplicated -sed -i '' '/\.route("\/events\/pending", get(handlers::events::list_pending))/d' src/main.rs -sed -i '' '/\.route("\/events\/pending\/:id\/approve", post(handlers::events::approve))/d' src/main.rs -sed -i '' '/\.route("\/events\/pending\/:id\/reject", post(handlers::events::reject))/d' src/main.rs -sed -i '' '/\.route("\/events\/pending\/:id", delete(handlers::events::delete_pending))/d' src/main.rs - -# Fix public routes: move submit before :id -sed -i '' ' -/\.route("\/api\/events\/:id", get(handlers::events::get))/i\ - .route("/api/events/submit", post(handlers::events::submit)) -' src/main.rs - -# Remove the old submit route -sed -i '' '/\.route("\/api\/events\/submit", post(handlers::events::submit))/d' src/main.rs - -echo "โœ… Routes reordered" - -# Build and test -if cargo build - echo "โœ… Build successful!" - - # Restart server - sudo systemctl restart church-api - sleep 3 - - # Test it works - set AUTH_RESPONSE (curl -s -X POST https://api.rockvilletollandsda.church/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username": "admin", "password": "Alright8-Reapply-Shrewdly-Platter-Important-Keenness-Banking-Streak-Tactile"}') - - set JWT_TOKEN (echo $AUTH_RESPONSE | jq -r '.data.token') - - echo "๐Ÿงช Testing pending events endpoint..." - set PENDING_TEST (curl -s -H "Authorization: Bearer $JWT_TOKEN" \ - "https://api.rockvilletollandsda.church/api/admin/events/pending") - - if echo $PENDING_TEST | grep -q success - echo "โœ… PENDING EVENTS WORKING!" - else - echo "โŒ Still broken: $PENDING_TEST" - end - - echo "๐Ÿงช Testing submit endpoint..." - echo "test" > test.txt - set SUBMIT_TEST (curl -s -X POST https://api.rockvilletollandsda.church/api/events/submit \ - -H "Authorization: Bearer $JWT_TOKEN" \ - -F "title=Route Test" \ - -F "description=Testing" \ - -F "start_time=2025-07-01T18:00" \ - -F "end_time=2025-07-01T19:00" \ - -F "location=Test" \ - -F "category=Other" \ - -F "bulletin_week=current" \ - -F "image=@test.txt") - - if echo $SUBMIT_TEST | grep -q success - echo "โœ… SUBMIT WORKING!" - echo "๐ŸŽ‰ ALL ROUTES FIXED!" - else - echo "โŒ Submit still broken: $SUBMIT_TEST" - end - - rm -f test.txt -else - echo "โŒ Build failed, restoring backup" - cp src/main.rs.backup src/main.rs -end diff --git a/fix_timezone_double_conversion.sql b/fix_timezone_double_conversion.sql deleted file mode 100644 index bdffb24..0000000 --- a/fix_timezone_double_conversion.sql +++ /dev/null @@ -1,334 +0,0 @@ --- Fix Timezone Double Conversion --- File: fix_timezone_double_conversion.sql --- --- PROBLEM: The migration script converted EST times to UTC, but the original times --- were already in EST (not UTC as assumed). This resulted in times being converted --- backwards, making events appear 4-5 hours earlier than they should be. --- --- SOLUTION: Restore original times from backup tables. These original times were --- already in the correct EST format that the V1 API expects. --- --- VALIDATION RESULTS SHOWING DOUBLE CONVERSION: --- - Original: 2025-06-01 15:00:00 (3 PM EST - correct) --- - Current: 2025-06-01 11:00:00 (11 AM UTC โ†’ 7 AM EDT display - wrong!) --- - Offset: -4.0 hours (confirms backwards conversion) - --- Enable required extensions -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - --- Start transaction for atomic restoration -BEGIN; - --- ================================ --- VALIDATION BEFORE RESTORATION --- ================================ - -DO $$ -DECLARE - backup_count INTEGER; - current_sample RECORD; -BEGIN - RAISE NOTICE '========================================'; - RAISE NOTICE 'TIMEZONE DOUBLE CONVERSION FIX'; - RAISE NOTICE 'Started at: %', NOW(); - RAISE NOTICE '========================================'; - - -- Check backup tables exist - SELECT COUNT(*) INTO backup_count - FROM information_schema.tables - WHERE table_name LIKE '%timezone_backup'; - - RAISE NOTICE 'Found % backup tables', backup_count; - - IF backup_count < 8 THEN - RAISE EXCEPTION 'Insufficient backup tables found (%). Cannot proceed without backups.', backup_count; - END IF; - - -- Show current problematic times - RAISE NOTICE ''; - RAISE NOTICE 'CURRENT PROBLEMATIC TIMES (Before Fix):'; - FOR current_sample IN - SELECT - e.title, - e.start_time as current_utc, - e.start_time AT TIME ZONE 'America/New_York' as current_display, - eb.start_time as original_est - FROM events e - JOIN events_timezone_backup eb ON e.id = eb.id - WHERE e.start_time IS NOT NULL - ORDER BY e.start_time - LIMIT 3 - LOOP - RAISE NOTICE 'Event: %', current_sample.title; - RAISE NOTICE ' Current UTC: %', current_sample.current_utc; - RAISE NOTICE ' Current Display: %', current_sample.current_display; - RAISE NOTICE ' Original EST: %', current_sample.original_est; - RAISE NOTICE ''; - END LOOP; -END $$; - --- ================================ --- RESTORE ORIGINAL TIMES --- ================================ - -RAISE NOTICE 'RESTORING ORIGINAL TIMES FROM BACKUPS...'; -RAISE NOTICE ''; - --- Restore events table -UPDATE events -SET - start_time = eb.start_time, - end_time = eb.end_time, - created_at = eb.created_at, - updated_at = eb.updated_at -FROM events_timezone_backup eb -WHERE events.id = eb.id; - --- Get count of restored events -DO $$ -DECLARE - events_restored INTEGER; -BEGIN - SELECT COUNT(*) INTO events_restored - FROM events e - JOIN events_timezone_backup eb ON e.id = eb.id - WHERE e.start_time IS NOT NULL; - - RAISE NOTICE 'Events restored: %', events_restored; -END $$; - --- Restore pending_events table -UPDATE pending_events -SET - start_time = peb.start_time, - end_time = peb.end_time, - submitted_at = peb.submitted_at, - created_at = peb.created_at, - updated_at = peb.updated_at -FROM pending_events_timezone_backup peb -WHERE pending_events.id = peb.id; - --- Get count of restored pending events -DO $$ -DECLARE - pending_restored INTEGER; -BEGIN - SELECT COUNT(*) INTO pending_restored - FROM pending_events pe - JOIN pending_events_timezone_backup peb ON pe.id = peb.id - WHERE pe.start_time IS NOT NULL; - - RAISE NOTICE 'Pending events restored: %', pending_restored; -END $$; - --- Restore bulletins table -UPDATE bulletins -SET - created_at = bb.created_at, - updated_at = bb.updated_at -FROM bulletins_timezone_backup bb -WHERE bulletins.id = bb.id; - --- Restore users table -UPDATE users -SET - created_at = ub.created_at, - updated_at = ub.updated_at -FROM users_timezone_backup ub -WHERE users.id = ub.id; - --- Restore church_config table -UPDATE church_config -SET - created_at = ccb.created_at, - updated_at = ccb.updated_at -FROM church_config_timezone_backup ccb -WHERE church_config.id = ccb.id; - --- Restore schedules table (if exists) -DO $$ -BEGIN - IF EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'schedules') THEN - UPDATE schedules - SET - created_at = sb.created_at, - updated_at = sb.updated_at - FROM schedules_timezone_backup sb - WHERE schedules.id = sb.id; - END IF; -END $$; - --- Restore bible_verses table (if exists) -DO $$ -BEGIN - IF EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'bible_verses') THEN - UPDATE bible_verses - SET - created_at = bvb.created_at, - updated_at = bvb.updated_at - FROM bible_verses_timezone_backup bvb - WHERE bible_verses.id = bvb.id; - END IF; -END $$; - --- Restore app_versions table (if exists) -DO $$ -BEGIN - IF EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'app_versions') THEN - UPDATE app_versions - SET - created_at = avb.created_at, - updated_at = avb.updated_at - FROM app_versions_timezone_backup avb - WHERE app_versions.id = avb.id; - END IF; -END $$; - --- ================================ --- POST-RESTORATION VALIDATION --- ================================ - -DO $$ -DECLARE - restored_sample RECORD; - total_events INTEGER; - total_pending INTEGER; -BEGIN - RAISE NOTICE ''; - RAISE NOTICE 'POST-RESTORATION VALIDATION:'; - RAISE NOTICE ''; - - -- Show restored times - FOR restored_sample IN - SELECT - title, - start_time as restored_est, - start_time AT TIME ZONE 'America/New_York' as display_time - FROM events - WHERE start_time IS NOT NULL - ORDER BY start_time - LIMIT 3 - LOOP - RAISE NOTICE 'Event: %', restored_sample.title; - RAISE NOTICE ' Restored EST: %', restored_sample.restored_est; - RAISE NOTICE ' Display Time: %', restored_sample.display_time; - RAISE NOTICE ''; - END LOOP; - - -- Get totals - SELECT COUNT(*) INTO total_events FROM events WHERE start_time IS NOT NULL; - SELECT COUNT(*) INTO total_pending FROM pending_events WHERE start_time IS NOT NULL; - - RAISE NOTICE 'RESTORATION SUMMARY:'; - RAISE NOTICE '- Events with times: %', total_events; - RAISE NOTICE '- Pending with times: %', total_pending; - RAISE NOTICE ''; -END $$; - --- ================================ --- UPDATE MIGRATION LOG --- ================================ - --- Record the fix in migration log -INSERT INTO migration_log (migration_name, description) -VALUES ( - 'fix_timezone_double_conversion', - 'Fixed double timezone conversion by restoring original EST times from backup tables. The original migration incorrectly assumed UTC times when they were already in EST, causing events to display 4-5 hours earlier than intended.' -); - --- ================================ --- FINAL VALIDATION QUERIES --- ================================ - --- Create validation queries for manual verification -CREATE TEMP TABLE post_fix_validation AS -SELECT 1 as query_num, - 'Verify event times now display correctly' as description, - $val1$ -SELECT - title, - start_time as est_time, - start_time AT TIME ZONE 'America/New_York' as ny_display, - EXTRACT(hour FROM start_time) as hour_est -FROM events -WHERE start_time IS NOT NULL -ORDER BY start_time -LIMIT 10; -$val1$ as query_sql - -UNION ALL - -SELECT 2 as query_num, - 'Check that event hours are reasonable (6 AM - 11 PM)' as description, - $val2$ -SELECT - title, - start_time, - EXTRACT(hour FROM start_time) as event_hour, - CASE - WHEN EXTRACT(hour FROM start_time) BETWEEN 6 AND 23 THEN 'REASONABLE' - ELSE 'UNUSUAL' - END as time_assessment -FROM events -WHERE start_time IS NOT NULL -ORDER BY start_time; -$val2$ as query_sql - -UNION ALL - -SELECT 3 as query_num, - 'Verify V1 API will return correct times' as description, - $val3$ --- This simulates what the V1 API timezone conversion will produce -SELECT - title, - start_time as stored_est, - start_time AT TIME ZONE 'America/New_York' as v1_display_equivalent -FROM events -WHERE start_time IS NOT NULL -ORDER BY start_time -LIMIT 5; -$val3$ as query_sql; - --- Display validation queries -DO $$ -DECLARE - val_record RECORD; -BEGIN - RAISE NOTICE '========================================'; - RAISE NOTICE 'VALIDATION QUERIES - RUN THESE TO VERIFY:'; - RAISE NOTICE '========================================'; - - FOR val_record IN SELECT * FROM post_fix_validation ORDER BY query_num LOOP - RAISE NOTICE 'Query %: %', val_record.query_num, val_record.description; - RAISE NOTICE '%', val_record.query_sql; - RAISE NOTICE '----------------------------------------'; - END LOOP; -END $$; - --- ================================ --- COMPLETION MESSAGE --- ================================ - -DO $$ -BEGIN - RAISE NOTICE '========================================'; - RAISE NOTICE 'TIMEZONE DOUBLE CONVERSION FIX COMPLETED'; - RAISE NOTICE 'Completed at: %', NOW(); - RAISE NOTICE '========================================'; - RAISE NOTICE 'WHAT WAS FIXED:'; - RAISE NOTICE '- Restored original EST times from backup tables'; - RAISE NOTICE '- Fixed events showing at midnight/early morning hours'; - RAISE NOTICE '- V1 API will now return correct EST times to frontend'; - RAISE NOTICE '- V2 API logic should be updated to handle EST times properly'; - RAISE NOTICE '========================================'; - RAISE NOTICE 'NEXT STEPS:'; - RAISE NOTICE '1. Run the validation queries above'; - RAISE NOTICE '2. Test the frontend clients to confirm times display correctly'; - RAISE NOTICE '3. Update V2 API to properly convert EST to UTC if needed'; - RAISE NOTICE '4. Consider keeping backup tables until fully verified'; - RAISE NOTICE '========================================'; -END $$; - --- Commit the transaction -COMMIT; \ No newline at end of file diff --git a/force_update_specific.sql b/force_update_specific.sql deleted file mode 100644 index 7d8cbcb..0000000 --- a/force_update_specific.sql +++ /dev/null @@ -1,13 +0,0 @@ --- Force update the specific bulletin with clean content and new timestamp -UPDATE bulletins -SET - scripture_reading = 'For as many of you as have been baptized into Christ have put on Christ. Galatians 3:27 KJV', - updated_at = NOW() -WHERE id = '192730b5-c11c-4513-a37d-2a8b320136a4'; - --- Verify the update -SELECT id, title, - scripture_reading, - updated_at -FROM bulletins -WHERE id = '192730b5-c11c-4513-a37d-2a8b320136a4'; \ No newline at end of file diff --git a/image_path.fish b/image_path.fish deleted file mode 100755 index a7db876..0000000 --- a/image_path.fish +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env fish - -echo "๐Ÿ”ง POPULATING IMAGE_PATH FIELD" -echo "==============================" - -set API_BASE "https://api.rockvilletollandsda.church/api" -set UPLOAD_DIR "/opt/rtsda/church-api/uploads/events" - -# Get token -set AUTH_RESPONSE (curl -s -X POST $API_BASE/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username": "admin", "password": "Alright8-Reapply-Shrewdly-Platter-Important-Keenness-Banking-Streak-Tactile"}') - -set JWT_TOKEN (echo $AUTH_RESPONSE | jq -r '.data.token') -echo "โœ… Got token" - -# Get all events -set EVENTS_RESPONSE (curl -s -H "Authorization: Bearer $JWT_TOKEN" "$API_BASE/events?perPage=500") -echo $EVENTS_RESPONSE | jq '.data.items' > events.json - -set updated 0 -set failed 0 - -echo "๐Ÿ” Updating events with proper image_path..." - -for event in (cat events.json | jq -c '.[]') - set event_id (echo $event | jq -r '.id') - set title (echo $event | jq -r '.title') - set current_image (echo $event | jq -r '.image // empty') - set current_image_path (echo $event | jq -r '.image_path // empty') - - if test -z "$current_image" - continue - end - - # Look for the actual uploaded file - set actual_file (find "$UPLOAD_DIR" -name "$event_id-*" -type f | head -1) - - if test -n "$actual_file" - set correct_path (echo $actual_file | sed "s|$UPLOAD_DIR/|uploads/events/|") - - echo "๐Ÿ“ค $title" - echo " Current image: $current_image" - echo " Setting image_path: $correct_path" - - # Get current event data - set current_event (curl -s -H "Authorization: Bearer $JWT_TOKEN" "$API_BASE/events/$event_id") - - # Update with both image and image_path - set event_data (echo $current_event | jq --arg img "$current_image" --arg imgpath "$correct_path" \ - '.data | { - title: .title, - description: .description, - start_time: .start_time, - end_time: .end_time, - location: .location, - location_url: .location_url, - category: .category, - recurring_type: .recurring_type, - is_featured: .is_featured, - image: $img, - image_path: $imgpath - }') - - set update_response (curl -s -X PUT \ - -H "Authorization: Bearer $JWT_TOKEN" \ - -H "Content-Type: application/json" \ - -d "$event_data" \ - "$API_BASE/admin/events/$event_id") - - set success (echo $update_response | jq -r '.success // false') - - if test "$success" = "true" - echo " โœ… SUCCESS" - set updated (math $updated + 1) - else - echo " โŒ FAILED" - set failed (math $failed + 1) - end - else - echo "โŒ $title - no uploaded file found" - set failed (math $failed + 1) - end - - echo "---" - sleep 0.1 -end - -rm -f events.json - -echo "" -echo "๐ŸŽ‰ RESULTS!" -echo "===========" -echo "โœ… Updated image_path: $updated events" -echo "โŒ Failed: $failed events" -echo "" -echo "Now the admin dashboard should use the proper image_path field!" diff --git a/image_path_removed.fish b/image_path_removed.fish deleted file mode 100755 index 6956f1a..0000000 --- a/image_path_removed.fish +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env fish - -echo "๐Ÿ”ง Consolidating image fields..." - -# Function to run SQL commands -function run_sql - sudo -u postgres psql -d church_db -c "$argv[1]" -end - -# SAFETY: Create backups first -echo "๐Ÿ›ก๏ธ Creating backups..." -run_sql "CREATE TABLE pending_events_backup AS SELECT * FROM pending_events;" -run_sql "CREATE TABLE events_backup AS SELECT * FROM events;" - -echo "๐Ÿ“Š Checking current data before migration..." -run_sql "SELECT COUNT(*) as total_pending FROM pending_events;" -run_sql "SELECT COUNT(*) as total_events FROM events;" - -echo "๐Ÿ” Showing sample data structure..." -run_sql "SELECT id, image, image_path FROM pending_events LIMIT 3;" -run_sql "SELECT id, image, image_path FROM events LIMIT 3;" - -echo "๐Ÿ“‹ Records that will be affected by consolidation..." -run_sql "SELECT COUNT(*) as pending_needs_copy FROM pending_events WHERE image IS NULL AND image_path IS NOT NULL;" -run_sql "SELECT COUNT(*) as events_needs_copy FROM events WHERE image IS NULL AND image_path IS NOT NULL;" - -echo "โš ๏ธ SAFETY CHECK: Review the above data. Press ENTER to continue or Ctrl+C to abort..." -read - -echo "๐Ÿ“ Copying image_path data to image column..." -run_sql "UPDATE pending_events SET image = image_path WHERE image IS NULL AND image_path IS NOT NULL;" -run_sql "UPDATE events SET image = image_path WHERE image IS NULL AND image_path IS NOT NULL;" - -echo "โœ… Verifying consolidation..." -run_sql "SELECT COUNT(*) as pending_with_image FROM pending_events WHERE image IS NOT NULL;" -run_sql "SELECT COUNT(*) as events_with_image FROM events WHERE image IS NOT NULL;" - -echo "๐Ÿ” Sample data after consolidation..." -run_sql "SELECT id, image, image_path FROM pending_events LIMIT 3;" - -echo "โš ๏ธ Ready to drop image_path columns. Press ENTER to continue or Ctrl+C to abort..." -read - -echo "๐Ÿ—‘๏ธ Dropping image_path columns..." -run_sql "ALTER TABLE pending_events DROP COLUMN image_path;" -run_sql "ALTER TABLE events DROP COLUMN image_path;" - -echo "๐ŸŽ‰ Migration complete!" -echo "๐Ÿ“‹ Backup tables created: pending_events_backup, events_backup" -echo "๐Ÿ’ก To rollback: DROP the current tables and rename backups back" diff --git a/pocketbase_data.sh b/pocketbase_data.sh deleted file mode 100755 index e96551d..0000000 --- a/pocketbase_data.sh +++ /dev/null @@ -1,314 +0,0 @@ -#!/bin/bash -# PocketBase to PostgreSQL Migration Script -set -e - -echo "๐Ÿš€ Migrating PocketBase data to PostgreSQL..." - -# Configuration -POCKETBASE_URL="http://localhost:8090" # Adjust if different -POSTGRES_URL="$DATABASE_URL" -MIGRATION_DIR="/tmp/pb_migration" -API_URL="https://api.rockvilletollandsda.church" - -# Create migration directory -mkdir -p "$MIGRATION_DIR" -cd "$MIGRATION_DIR" - -echo "๐Ÿ“ฆ Step 1: Export data from PocketBase..." - -# Function to export PocketBase collection data -export_collection() { - local collection=$1 - echo " Exporting $collection..." - - # Get all records from collection (adjust perPage if you have many records) - curl -s "${POCKETBASE_URL}/api/collections/${collection}/records?perPage=500" \ - -o "${collection}.json" - - if [ $? -eq 0 ]; then - echo " โœ… Exported $(jq '.items | length' ${collection}.json) records from $collection" - else - echo " โŒ Failed to export $collection" - fi -} - -# Export all collections -export_collection "bulletins" -export_collection "events" -export_collection "pending_events" -export_collection "config" -export_collection "bible_verses" -export_collection "Quarterly_Schedule" -export_collection "Offering_and_Sunset_Times_Schedule" -export_collection "rtsda_android" - -echo "๐Ÿ“ฅ Step 2: Transform and import data..." - -# Create Python script for data transformation -cat > transform_data.py << 'EOF' -import json -import sys -import uuid -from datetime import datetime -import psycopg2 -from psycopg2.extras import RealDictCursor -import os - -# Database connection -conn = psycopg2.connect(os.environ['DATABASE_URL']) -cur = conn.cursor(cursor_factory=RealDictCursor) - -def load_json(filename): - try: - with open(filename, 'r') as f: - data = json.load(f) - return data.get('items', []) - except FileNotFoundError: - print(f"โš ๏ธ {filename} not found, skipping...") - return [] - -def convert_date(pb_date): - """Convert PocketBase date to PostgreSQL format""" - if not pb_date: - return None - try: - # PocketBase uses ISO format - dt = datetime.fromisoformat(pb_date.replace('Z', '+00:00')) - return dt - except: - return None - -def generate_uuid(): - """Generate PostgreSQL-compatible UUID""" - return str(uuid.uuid4()) - -print("๐Ÿ”„ Transforming bulletins...") -bulletins = load_json('bulletins.json') -for bulletin in bulletins: - cur.execute(""" - INSERT INTO bulletins (id, title, date, url, pdf_url, is_active, pdf_file, - sabbath_school, divine_worship, scripture_reading, sunset, - cover_image, created_at, updated_at) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - ON CONFLICT (id) DO NOTHING - """, ( - generate_uuid(), - bulletin.get('title'), - convert_date(bulletin.get('date')), - bulletin.get('url'), - bulletin.get('pdf_url'), - bulletin.get('is_active', True), - bulletin.get('pdf'), - bulletin.get('sabbath_school'), - bulletin.get('divine_worship'), - bulletin.get('scripture_reading'), - bulletin.get('sunset'), - bulletin.get('cover_image'), - convert_date(bulletin.get('created')), - convert_date(bulletin.get('updated')) - )) - -print("๐Ÿ”„ Transforming events...") -events = load_json('events.json') -for event in events: - cur.execute(""" - INSERT INTO events (id, title, description, start_time, end_time, location, - location_url, image, thumbnail, category, is_featured, - recurring_type, approved_from, created_at, updated_at) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - ON CONFLICT (id) DO NOTHING - """, ( - generate_uuid(), - event.get('title'), - event.get('description'), - convert_date(event.get('start_time')), - convert_date(event.get('end_time')), - event.get('location'), - event.get('location_url'), - event.get('image'), - event.get('thumbnail'), - event.get('category'), - event.get('is_featured', False), - event.get('reoccuring'), # Note: PB uses 'reoccuring', PG uses 'recurring_type' - event.get('approved_from'), - convert_date(event.get('created')), - convert_date(event.get('updated')) - )) - -print("๐Ÿ”„ Transforming pending events...") -pending_events = load_json('pending_events.json') -for event in pending_events: - cur.execute(""" - INSERT INTO pending_events (id, title, description, start_time, end_time, location, - location_url, image, thumbnail, category, is_featured, - recurring_type, approval_status, submitted_at, bulletin_week, - admin_notes, submitter_email, email_sent, pending_email_sent, - rejection_email_sent, approval_email_sent, created_at, updated_at) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - ON CONFLICT (id) DO NOTHING - """, ( - generate_uuid(), - event.get('title'), - event.get('description'), - convert_date(event.get('start_time')), - convert_date(event.get('end_time')), - event.get('location'), - event.get('location_url'), - event.get('image'), - event.get('thumbnail'), - event.get('category'), - event.get('is_featured', False), - event.get('reoccuring'), - event.get('approval_status', 'pending'), - convert_date(event.get('submitted_at')), - event.get('bulletin_week'), - event.get('admin_notes'), - event.get('submitter_email'), - event.get('email_sent', False), - event.get('pending_email_sent', False), - event.get('rejection_email_sent', False), - event.get('approval_email_sent', False), - convert_date(event.get('created')), - convert_date(event.get('updated')) - )) - -print("๐Ÿ”„ Transforming church config...") -configs = load_json('config.json') -for config in configs: - cur.execute(""" - INSERT INTO church_config (id, church_name, contact_email, contact_phone, - church_address, po_box, google_maps_url, about_text, - api_keys, created_at, updated_at) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - ON CONFLICT (id) DO NOTHING - """, ( - generate_uuid(), - config.get('church_name'), - config.get('contact_email'), - config.get('contact_phone'), - config.get('church_address'), - config.get('po_box'), - config.get('google_maps_url'), - config.get('about_text'), - json.dumps(config.get('api_key', {})), - convert_date(config.get('created')), - convert_date(config.get('updated')) - )) - -print("๐Ÿ”„ Transforming bible verses...") -verses = load_json('bible_verses.json') -for verse_record in verses: - cur.execute(""" - INSERT INTO bible_verses (id, verses, created_at, updated_at) - VALUES (%s, %s, %s, %s) - ON CONFLICT (id) DO NOTHING - """, ( - generate_uuid(), - json.dumps(verse_record.get('verses', {})), - convert_date(verse_record.get('created')), - convert_date(verse_record.get('updated')) - )) - -print("๐Ÿ”„ Transforming schedules...") -# Quarterly schedules -quarterly = load_json('Quarterly_Schedule.json') -for schedule in quarterly: - cur.execute(""" - INSERT INTO schedules (id, schedule_type, year, quarter, schedule_data, created_at, updated_at) - VALUES (%s, %s, %s, %s, %s, %s, %s) - ON CONFLICT (id) DO NOTHING - """, ( - generate_uuid(), - 'quarterly', - schedule.get('year'), - schedule.get('quarter'), - json.dumps(schedule.get('schedule_data', {})), - convert_date(schedule.get('created')), - convert_date(schedule.get('updated')) - )) - -# Offering and sunset schedules -offering = load_json('Offering_and_Sunset_Times_Schedule.json') -for schedule in offering: - cur.execute(""" - INSERT INTO schedules (id, schedule_type, year, quarter, schedule_data, created_at, updated_at) - VALUES (%s, %s, %s, %s, %s, %s, %s) - ON CONFLICT (id) DO NOTHING - """, ( - generate_uuid(), - 'offering_sunset', - schedule.get('year'), - None, - json.dumps(schedule.get('schedule_data', {})), - convert_date(schedule.get('created')), - convert_date(schedule.get('updated')) - )) - -print("๐Ÿ”„ Transforming app versions...") -app_versions = load_json('rtsda_android.json') -for app in app_versions: - cur.execute(""" - INSERT INTO app_versions (id, platform, version_name, version_code, download_url, - update_required, description, created_at, updated_at) - VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s) - ON CONFLICT (id) DO NOTHING - """, ( - generate_uuid(), - 'android', - app.get('version_name'), - app.get('version_code'), - None, # You'll need to set download URLs manually - app.get('update_required', False), - app.get('update_description'), - convert_date(app.get('created')), - convert_date(app.get('updated')) - )) - -# Commit all changes -conn.commit() -cur.close() -conn.close() - -print("โœ… Data transformation complete!") -EOF - -# Install required Python packages -pip3 install psycopg2-binary > /dev/null 2>&1 - -# Run the transformation -python3 transform_data.py - -echo "๐Ÿ“Š Step 3: Verifying migration..." - -# Check what was migrated -psql "$POSTGRES_URL" -c " -SELECT - 'bulletins' as table_name, COUNT(*) as records FROM bulletins -UNION ALL SELECT - 'events', COUNT(*) FROM events -UNION ALL SELECT - 'pending_events', COUNT(*) FROM pending_events -UNION ALL SELECT - 'church_config', COUNT(*) FROM church_config -UNION ALL SELECT - 'bible_verses', COUNT(*) FROM bible_verses -UNION ALL SELECT - 'schedules', COUNT(*) FROM schedules -UNION ALL SELECT - 'app_versions', COUNT(*) FROM app_versions; -" - -echo "๐ŸŽ‰ Migration complete!" -echo "" -echo "๐Ÿ“‹ Next steps:" -echo "1. Verify data looks correct in PostgreSQL" -echo "2. Test API endpoints to ensure data is accessible" -echo "3. Update any file URLs that point to PocketBase" -echo "4. Shut down PocketBase once everything is working" -echo "" -echo "๐Ÿงช Test your migrated data:" -echo " curl $API_URL/api/bulletins" -echo " curl $API_URL/api/events" - -# Cleanup -rm -rf "$MIGRATION_DIR" diff --git a/remove_image_path.fish b/remove_image_path.fish deleted file mode 100755 index 7965e1e..0000000 --- a/remove_image_path.fish +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env fish - -# Script to remove all image_path references from Rust code - -echo "๐Ÿงน Cleaning up image_path references..." - -# Backup original files first -echo "๐Ÿ“ฆ Creating backups..." -set backup_dir "backup_before_image_path_removal_$(date +%Y%m%d_%H%M%S)" -mkdir -p $backup_dir - -for file in src/models.rs src/db/events.rs src/handlers/events.rs src/upload.rs - if test -f $file - cp $file $backup_dir/ - echo " โœ“ Backed up $file" - end -end - -echo "" -echo "๐Ÿ”ง Removing image_path references..." - -# Function to safely remove lines containing image_path -function remove_image_path_lines - set file $argv[1] - if test -f $file - echo " Processing $file..." - - # Remove lines that contain image_path (struct fields, variables, etc.) - sed -i '/image_path/d' $file - - # Also remove any trailing commas that might be left hanging - sed -i '/^[[:space:]]*,$/d' $file - - echo " โœ“ Removed image_path references from $file" - else - echo " โš ๏ธ File $file not found" - end -end - -# Process each file -remove_image_path_lines "src/models.rs" -remove_image_path_lines "src/handlers/events.rs" -remove_image_path_lines "src/upload.rs" - -# For events.rs, we need more careful handling of SQL queries -echo " Processing src/db/events.rs (SQL queries)..." -if test -f "src/db/events.rs" - # Remove image_path from SQL UPDATE/INSERT statements and adjust parameter numbers - sed -i 's/, image_path = \$[0-9][0-9]*//g' src/db/events.rs - sed -i 's/image_path = \$[0-9][0-9]*,//g' src/db/events.rs - sed -i 's/image_path = \$[0-9][0-9]*//g' src/db/events.rs - sed -i 's/, image_path//g' src/db/events.rs - sed -i 's/image_path,//g' src/db/events.rs - sed -i '/image_path/d' src/db/events.rs - - echo " โœ“ Cleaned SQL queries in src/db/events.rs" -else - echo " โš ๏ธ File src/db/events.rs not found" -end - -echo "" -echo "๐Ÿ” Checking for remaining references..." -set remaining (grep -r "image_path" src/ 2>/dev/null | wc -l) - -if test $remaining -eq 0 - echo "โœ… All image_path references removed successfully!" -else - echo "โš ๏ธ Found $remaining remaining references:" - grep -r "image_path" src/ --color=always - echo "" - echo "You may need to manually review these remaining references." -end - -echo "" -echo "๐Ÿงช Running cargo check..." -if cargo check - echo "โœ… Code compiles successfully!" -else - echo "โŒ Compilation errors found. You may need to:" - echo " - Fix parameter indices in SQL queries" - echo " - Remove trailing commas" - echo " - Update function signatures" - echo "" - echo "๐Ÿ’พ Your original files are backed up in: $backup_dir" -end - -echo "" -echo "๐ŸŽ‰ Cleanup complete!" -echo "๐Ÿ’พ Backups saved in: $backup_dir" -echo "๐Ÿ”ง Next steps:" -echo " 1. Review any remaining compilation errors" -echo " 2. Test your application" -echo " 3. Remove backup directory when satisfied" diff --git a/replace-stubs.sh b/replace-stubs.sh deleted file mode 100755 index 7e72a58..0000000 --- a/replace-stubs.sh +++ /dev/null @@ -1,349 +0,0 @@ -#!/bin/bash -# Complete Church API Implementation - ALL FILES AT ONCE! -set -e - -echo "๐Ÿฆ€ Deploying complete Church API functionality..." -cd /opt/rtsda/church-api - -# Complete the pending events database functions that were cut off -cat >> src/db/events.rs << 'EOF' - req.title, - req.description, - req.start_time, - req.end_time, - req.location, - req.location_url, - req.category, - req.is_featured.unwrap_or(false), - req.recurring_type, - req.bulletin_week, - req.submitter_email - ) - .fetch_one(pool) - .await?; - - Ok(pending_event) -} - -pub async fn list_pending(pool: &PgPool, page: i32, per_page: i32) -> Result<(Vec, i64)> { - let offset = (page - 1) * per_page; - - let events = sqlx::query_as!( - PendingEvent, - "SELECT * FROM pending_events WHERE approval_status = 'pending' ORDER BY submitted_at DESC LIMIT $1 OFFSET $2", - per_page, - offset - ) - .fetch_all(pool) - .await?; - - let total = sqlx::query_scalar!("SELECT COUNT(*) FROM pending_events WHERE approval_status = 'pending'") - .fetch_one(pool) - .await? - .unwrap_or(0); - - Ok((events, total)) -} - -pub async fn get_pending_by_id(pool: &PgPool, id: &Uuid) -> Result> { - let event = sqlx::query_as!(PendingEvent, "SELECT * FROM pending_events WHERE id = $1", id) - .fetch_optional(pool) - .await?; - - Ok(event) -} - -pub async fn approve_pending(pool: &PgPool, id: &Uuid, admin_notes: Option) -> Result { - // Start transaction to move from pending to approved - let mut tx = pool.begin().await?; - - // Get the pending event - let pending = sqlx::query_as!( - PendingEvent, - "SELECT * FROM pending_events WHERE id = $1", - id - ) - .fetch_one(&mut *tx) - .await?; - - // Create the approved event - let event = sqlx::query_as!( - Event, - "INSERT INTO events (title, description, start_time, end_time, location, location_url, category, is_featured, recurring_type, approved_from) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) - RETURNING *", - pending.title, - pending.description, - pending.start_time, - pending.end_time, - pending.location, - pending.location_url, - pending.category, - pending.is_featured, - pending.recurring_type, - pending.submitter_email - ) - .fetch_one(&mut *tx) - .await?; - - // Update pending event status - sqlx::query!( - "UPDATE pending_events SET approval_status = 'approved', admin_notes = $1, updated_at = NOW() WHERE id = $2", - admin_notes, - id - ) - .execute(&mut *tx) - .await?; - - tx.commit().await?; - - Ok(event) -} - -pub async fn reject_pending(pool: &PgPool, id: &Uuid, admin_notes: Option) -> Result<()> { - let result = sqlx::query!( - "UPDATE pending_events SET approval_status = 'rejected', admin_notes = $1, updated_at = NOW() WHERE id = $2", - admin_notes, - id - ) - .execute(pool) - .await?; - - if result.rows_affected() == 0 { - return Err(ApiError::NotFound("Pending event not found".to_string())); - } - - Ok(()) -} -EOF - -# Add config database module -cat > src/db/config.rs << 'EOF' -use sqlx::PgPool; -use uuid::Uuid; - -use crate::{error::Result, models::ChurchConfig}; - -pub async fn get_config(pool: &PgPool) -> Result> { - let config = sqlx::query_as!(ChurchConfig, "SELECT * FROM church_config LIMIT 1") - .fetch_optional(pool) - .await?; - - Ok(config) -} - -pub async fn update_config(pool: &PgPool, config: ChurchConfig) -> Result { - let updated = sqlx::query_as!( - ChurchConfig, - "UPDATE church_config SET - church_name = $1, contact_email = $2, contact_phone = $3, - church_address = $4, po_box = $5, google_maps_url = $6, - about_text = $7, api_keys = $8, updated_at = NOW() - WHERE id = $9 - RETURNING *", - config.church_name, - config.contact_email, - config.contact_phone, - config.church_address, - config.po_box, - config.google_maps_url, - config.about_text, - config.api_keys, - config.id - ) - .fetch_one(pool) - .await?; - - Ok(updated) -} -EOF - -# Update main.rs to include email support -cat > src/main.rs << 'EOF' -use anyhow::{Context, Result}; -use axum::{ - middleware, - routing::{delete, get, post, put}, - Router, -}; -use std::{env, sync::Arc}; -use tower::ServiceBuilder; -use tower_http::{ - cors::{Any, CorsLayer}, - trace::TraceLayer, -}; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; - -mod auth; -mod db; -mod email; -mod error; -mod handlers; -mod models; - -use email::{EmailConfig, Mailer}; - -#[derive(Clone)] -pub struct AppState { - pub pool: sqlx::PgPool, - pub jwt_secret: String, - pub mailer: Arc, -} - -#[tokio::main] -async fn main() -> Result<()> { - // Initialize tracing - tracing_subscriber::registry() - .with( - tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| "church_api=debug,tower_http=debug".into()), - ) - .with(tracing_subscriber::fmt::layer()) - .init(); - - // Load environment variables - dotenvy::dotenv().ok(); - - let database_url = env::var("DATABASE_URL").context("DATABASE_URL must be set")?; - let jwt_secret = env::var("JWT_SECRET").context("JWT_SECRET must be set")?; - - // Initialize database - let pool = sqlx::PgPool::connect(&database_url) - .await - .context("Failed to connect to database")?; - - // Run migrations - sqlx::migrate!("./migrations") - .run(&pool) - .await - .context("Failed to run migrations")?; - - // Initialize email - let email_config = EmailConfig::from_env().context("Failed to load email config")?; - let mailer = Arc::new(Mailer::new(email_config).context("Failed to initialize mailer")?); - - let state = AppState { - pool, - jwt_secret, - mailer, - }; - - // Build our application with routes - let app = Router::new() - // Public routes (no auth required) - .route("/api/auth/login", post(handlers::auth::login)) - .route("/api/bulletins", get(handlers::bulletins::list)) - .route("/api/bulletins/current", get(handlers::bulletins::current)) - .route("/api/bulletins/:id", get(handlers::bulletins::get)) - .route("/api/events", get(handlers::events::list)) - .route("/api/events/upcoming", get(handlers::events::upcoming)) - .route("/api/events/featured", get(handlers::events::featured)) - .route("/api/events/:id", get(handlers::events::get)) - .route("/api/events/submit", post(handlers::events::submit)) - // Protected admin routes - .route("/api/admin/users", get(handlers::auth::list_users)) - .route("/api/admin/bulletins", post(handlers::bulletins::create)) - .route("/api/admin/bulletins/:id", put(handlers::bulletins::update)) - .route("/api/admin/bulletins/:id", delete(handlers::bulletins::delete)) - .route("/api/admin/events", post(handlers::events::create)) - .route("/api/admin/events/:id", put(handlers::events::update)) - .route("/api/admin/events/:id", delete(handlers::events::delete)) - .route("/api/admin/events/pending", get(handlers::events::list_pending)) - .route("/api/admin/events/pending/:id/approve", post(handlers::events::approve)) - .route("/api/admin/events/pending/:id/reject", post(handlers::events::reject)) - .layer(middleware::from_fn_with_state(state.clone(), auth::auth_middleware)) - .with_state(state) - .layer( - ServiceBuilder::new() - .layer(TraceLayer::new_for_http()) - .layer( - CorsLayer::new() - .allow_origin(Any) - .allow_methods(Any) - .allow_headers(Any), - ), - ); - - let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?; - tracing::info!("๐Ÿš€ Church API server running on {}", listener.local_addr()?); - - axum::serve(listener, app).await?; - - Ok(()) -} -EOF - -# Update Cargo.toml with all dependencies -cat > Cargo.toml << 'EOF' -[package] -name = "church-api" -version = "0.1.0" -edition = "2021" - -[dependencies] -# Web framework -axum = { version = "0.7", features = ["multipart"] } -tokio = { version = "1.0", features = ["full"] } -tower = "0.4" -tower-http = { version = "0.5", features = ["cors", "trace"] } - -# Database -sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "uuid", "chrono", "json"] } - -# Serialization -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" - -# Authentication & Security -jsonwebtoken = "9.2" -bcrypt = "0.15" - -# Email -lettre = { version = "0.11", features = ["tokio1-rustls-tls", "smtp-transport", "builder"] } - -# Utilities -uuid = { version = "1.6", features = ["v4", "serde"] } -chrono = { version = "0.4", features = ["serde"] } -anyhow = "1.0" -dotenvy = "0.15" -rust_decimal = { version = "1.33", features = ["serde"] } - -# Logging -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -EOF - -# Update .env with email configuration -cat >> .env << 'EOF' - -# Email Configuration (Fastmail SMTP) -SMTP_HOST=smtp.fastmail.com -SMTP_PORT=587 -SMTP_USER=your-email@your-domain.com -SMTP_PASS=your-app-password -SMTP_FROM=noreply@rockvilletollandsda.church -ADMIN_EMAIL=admin@rockvilletollandsda.church -EOF - -# Apply database migrations and restart services -echo "๐Ÿ—„๏ธ Running database migrations..." -cargo sqlx migrate run - -echo "๐Ÿ”„ Rebuilding and restarting services..." -cargo build --release - -# Restart with systemd -sudo systemctl restart church-api -sudo systemctl restart nginx - -echo "โœ… COMPLETE! Your Church API now has:" -echo " โ€ข Real database operations with PostgreSQL" -echo " โ€ข Working email notifications via Fastmail SMTP" -echo " โ€ข JWT authentication system" -echo " โ€ข Event submission & approval workflow with emails" -echo " โ€ข File upload support ready" -echo " โ€ข Production-ready error handling" -echo "" -echo "๐Ÿ”ง Don't forget to update your .env file with real SMTP credentials!" -echo "๐Ÿ“ง Test the email system by submitting an event at /api/events/submit" -echo "๐Ÿš€ API Documentation at: http://your-domain.com/api/docs" diff --git a/rtsda-android b/rtsda-android deleted file mode 100644 index 269f65d..0000000 Binary files a/rtsda-android and /dev/null differ diff --git a/run_html_cleaning_migration.sh b/run_html_cleaning_migration.sh deleted file mode 100755 index d9d1255..0000000 --- a/run_html_cleaning_migration.sh +++ /dev/null @@ -1,177 +0,0 @@ -#!/bin/bash - -# Script to run HTML entity cleaning migration -# This script provides safety checks and backup functionality - -set -e # Exit on any error - -# Configuration -DB_URL="${DATABASE_URL:-postgresql://localhost/church_api}" -MIGRATION_FILE="migrations/20250811000001_clean_html_entities.sql" -TEST_FILE="test_html_cleaning_migration.sql" -BACKUP_DIR="./migration_backups" -TIMESTAMP=$(date +%Y%m%d_%H%M%S) - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -echo -e "${BLUE}Church API - HTML Entity Cleaning Migration${NC}" -echo "=============================================" -echo - -# Check if migration file exists -if [ ! -f "$MIGRATION_FILE" ]; then - echo -e "${RED}Error: Migration file not found: $MIGRATION_FILE${NC}" - exit 1 -fi - -# Check if test file exists -if [ ! -f "$TEST_FILE" ]; then - echo -e "${RED}Error: Test file not found: $TEST_FILE${NC}" - exit 1 -fi - -# Create backup directory -mkdir -p "$BACKUP_DIR" - -echo -e "${YELLOW}Step 1: Testing the cleaning function...${NC}" -echo "Running test script to verify the cleaning logic works correctly..." - -# Run the test script -if psql "$DB_URL" -f "$TEST_FILE" > /dev/null 2>&1; then - echo -e "${GREEN}โœ“ Test passed! The cleaning function works correctly.${NC}" -else - echo -e "${RED}โœ— Test failed! Please check the test output.${NC}" - echo "Test output:" - psql "$DB_URL" -f "$TEST_FILE" - exit 1 -fi - -echo - -echo -e "${YELLOW}Step 2: Creating database backup...${NC}" -BACKUP_FILE="$BACKUP_DIR/backup_before_html_cleaning_$TIMESTAMP.sql" - -# Create backup of affected tables -echo "Creating backup of tables that will be modified..." -pg_dump "$DB_URL" \ - --table=bulletins \ - --table=events \ - --table=pending_events \ - --table=members \ - --table=church_config \ - --table=media_items \ - --table=transcoded_media \ - --table=users \ - --data-only \ - --no-owner \ - --no-privileges > "$BACKUP_FILE" 2>/dev/null || { - echo -e "${YELLOW}Note: Some tables may not exist, continuing with available tables...${NC}" - # Try with just the core tables that definitely exist - pg_dump "$DB_URL" \ - --table=bulletins \ - --table=events \ - --table=pending_events \ - --table=church_config \ - --table=users \ - --data-only \ - --no-owner \ - --no-privileges > "$BACKUP_FILE" 2>/dev/null || { - echo -e "${RED}Failed to create backup. Aborting migration.${NC}" - exit 1 - } -} - -echo -e "${GREEN}โœ“ Backup created: $BACKUP_FILE${NC}" - -echo - -echo -e "${YELLOW}Step 3: Analyzing current data for HTML entities...${NC}" - -# Check for HTML entities in the database -ENTITY_COUNT=$(psql "$DB_URL" -t -c " - SELECT COUNT(*) FROM ( - SELECT id FROM bulletins WHERE - title ~ '<[^>]*>' OR sabbath_school ~ '<[^>]*>' OR divine_worship ~ '<[^>]*>' OR - scripture_reading ~ '<[^>]*>' OR sunset ~ '<[^>]*>' OR - title ~ '&(nbsp|amp|lt|gt|quot|#39);' OR sabbath_school ~ '&(nbsp|amp|lt|gt|quot|#39);' OR - divine_worship ~ '&(nbsp|amp|lt|gt|quot|#39);' OR scripture_reading ~ '&(nbsp|amp|lt|gt|quot|#39);' OR - sunset ~ '&(nbsp|amp|lt|gt|quot|#39);' - UNION ALL - SELECT id FROM events WHERE - title ~ '<[^>]*>' OR description ~ '<[^>]*>' OR location ~ '<[^>]*>' OR - location_url ~ '<[^>]*>' OR approved_from ~ '<[^>]*>' OR - title ~ '&(nbsp|amp|lt|gt|quot|#39);' OR description ~ '&(nbsp|amp|lt|gt|quot|#39);' OR - location ~ '&(nbsp|amp|lt|gt|quot|#39);' OR location_url ~ '&(nbsp|amp|lt|gt|quot|#39);' OR - approved_from ~ '&(nbsp|amp|lt|gt|quot|#39);' - UNION ALL - SELECT id FROM pending_events WHERE - title ~ '<[^>]*>' OR description ~ '<[^>]*>' OR location ~ '<[^>]*>' OR - location_url ~ '<[^>]*>' OR admin_notes ~ '<[^>]*>' OR - title ~ '&(nbsp|amp|lt|gt|quot|#39);' OR description ~ '&(nbsp|amp|lt|gt|quot|#39);' OR - location ~ '&(nbsp|amp|lt|gt|quot|#39);' OR admin_notes ~ '&(nbsp|amp|lt|gt|quot|#39);' - ) AS dirty_records; -" | xargs) - -echo "Found $ENTITY_COUNT records with HTML tags or entities that need cleaning." - -if [ "$ENTITY_COUNT" -eq 0 ]; then - echo -e "${GREEN}โœ“ No HTML entities found! Database is already clean.${NC}" - echo "Migration can still be run to install the cleaning function for future use." -fi - -echo - -echo -e "${YELLOW}Step 4: Ready to run migration${NC}" -echo "This will:" -echo " โ€ข Install the clean_html_entities() function" -echo " โ€ข Clean HTML tags and entities from all text fields" -echo " โ€ข Update the updated_at timestamps for modified records" -echo " โ€ข Provide detailed logging of what was cleaned" -echo -echo "Backup location: $BACKUP_FILE" - -# Ask for confirmation -read -p "Do you want to proceed with the migration? (y/N): " -n 1 -r -echo -if [[ ! $REPLY =~ ^[Yy]$ ]]; then - echo -e "${YELLOW}Migration cancelled by user.${NC}" - exit 0 -fi - -echo - -echo -e "${YELLOW}Step 5: Running migration...${NC}" - -# Run the migration -if psql "$DB_URL" -f "$MIGRATION_FILE"; then - echo - echo -e "${GREEN}โœ“ Migration completed successfully!${NC}" - echo - echo -e "${BLUE}Summary:${NC}" - echo "โ€ข HTML tags and entities have been cleaned from all text fields" - echo "โ€ข Database backup is available at: $BACKUP_FILE" - echo "โ€ข The clean_html_entities() function is now available for future use" - echo "โ€ข All API responses will now return clean data" - - echo - echo -e "${YELLOW}Next steps:${NC}" - echo "1. Test your API endpoints to verify clean data" - echo "2. Monitor for any issues with data formatting" - echo "3. Keep the backup file until you're confident everything works correctly" - - echo - echo -e "${GREEN}Migration completed successfully!${NC}" -else - echo -e "${RED}โœ— Migration failed!${NC}" - echo - echo -e "${YELLOW}Rollback instructions:${NC}" - echo "1. Restore from backup: psql \"$DB_URL\" < \"$BACKUP_FILE\"" - echo "2. Check the migration logs above for error details" - echo "3. Fix any issues and try again" - exit 1 -fi \ No newline at end of file diff --git a/server_debug.sh b/server_debug.sh deleted file mode 100755 index bc7eea0..0000000 --- a/server_debug.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash - -echo "๐Ÿ” SERVER-SIDE DEBUG (Run this on the actual server)" -echo "==================================================" - -# Check if we're on the server -if [ ! -f "/opt/rtsda/church-api/Cargo.toml" ]; then - echo "โŒ This script must be run on the server (rockvilleavdesktop)" - echo " SSH to the server and run this script there" - exit 1 -fi - -echo "โœ… Running on server" - -# Check uploads directory -echo "๐Ÿ“ Checking uploads directory..." -if [ -d "/opt/rtsda/church-api/uploads/events" ]; then - echo "โœ… uploads/events exists" - echo "Files:" - ls -la /opt/rtsda/church-api/uploads/events/ -else - echo "โŒ uploads/events directory not found" - echo "Creating it..." - mkdir -p /opt/rtsda/church-api/uploads/events - chown rockvilleav:rockvilleav /opt/rtsda/church-api/uploads/events - echo "โœ… Created uploads/events directory" -fi - -# Check server logs -echo "" -echo "๐Ÿ“œ Recent server logs..." -journalctl -u church-api --since "5 minutes ago" --no-pager | tail -20 - -# Check the pending events endpoint issue -echo "" -echo "๐Ÿ” Testing pending events endpoint..." -AUTH_TOKEN=$(curl -s -X POST https://api.rockvilletollandsda.church/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username": "admin", "password": "Alright8-Reapply-Shrewdly-Platter-Important-Keenness-Banking-Streak-Tactile"}' \ - | jq -r '.data.token') - -echo "Testing: https://api.rockvilletollandsda.church/api/events/pending" -curl -v -H "Authorization: Bearer $AUTH_TOKEN" \ - "https://api.rockvilletollandsda.church/api/events/pending" 2>&1 - -echo "" -echo "๐ŸŽฏ What to check:" -echo "1. Are WebP files being created in uploads/events/?" -echo "2. What's the UUID parsing error in pending events?" -echo "3. Are there any crash logs in journalctl?" diff --git a/smart_streaming_test.html b/smart_streaming_test.html deleted file mode 100644 index c2ea91f..0000000 --- a/smart_streaming_test.html +++ /dev/null @@ -1,379 +0,0 @@ - - - - - - ๐ŸŽฏ Smart Video Streaming Test - - - -
-

๐ŸŽฏ Smart Video Streaming Test

-

Like Jellyfin but not garbage C# - serves AV1 directly to modern browsers, HLS to legacy clients

- -
-

Your Browser Codec Support

-
-
- -
- -
Click "Start Smart Stream" to begin
-
- -
- - - -
- -
-
-

๐ŸŽฌ Streaming Method

-

Method: Unknown

-

Codec: Unknown

-

Source: Unknown

-
- -
-

โšก Performance

-

Load Time: -

-

File Size: -

-

Bitrate: -

-
- -
-

๐ŸŒ Network

-

Response: -

-

Headers: -

-

Cached: -

-
-
- -
-
- - - - \ No newline at end of file diff --git a/temp_email_method.txt b/temp_email_method.txt deleted file mode 100644 index c7fc74e..0000000 --- a/temp_email_method.txt +++ /dev/null @@ -1,29 +0,0 @@ - - pub async fn send_contact_email(&self, contact: crate::models::ContactEmail) -> Result<()> { - let phone_str = contact.phone.as_deref().unwrap_or("Not provided"); - - let html_body = format!( - "

New Contact Form Submission

\n\ -

Name: {} {}

\n\ -

Email: {}

\n\ -

Phone: {}

\n\ -

Message:

\n\ -

{}

\n", - contact.first_name, - contact.last_name, - contact.email, - phone_str, - contact.message.replace('\n', "
") - ); - - let email = Message::builder() - .from(self.config.from_email.parse()?) - .to(self.config.admin_email.parse()?) - .subject(format!("New Contact Form Submission from {} {}", - contact.first_name, contact.last_name)) - .body(html_body)?; - - self.transport.send(email).await?; - tracing::info!("Contact form email sent successfully"); - Ok(()) - } diff --git a/temp_list_pending.txt b/temp_list_pending.txt deleted file mode 100644 index f12d983..0000000 --- a/temp_list_pending.txt +++ /dev/null @@ -1,12 +0,0 @@ -pub async fn list_pending( - Query(params): Query, - State(state): State, -) -> Result, i64)>>> { - let (events, total) = crate::db::events::list_pending(&state.pool, params.page.unwrap_or(1) as i32, params.per_page.unwrap_or(10)).await?; - - Ok(Json(ApiResponse { - success: true, - data: Some((events, total)), - message: None, - })) -} diff --git a/test.sh b/test.sh deleted file mode 100755 index 48af1fe..0000000 --- a/test.sh +++ /dev/null @@ -1,203 +0,0 @@ -#!/bin/bash - -echo "๐Ÿงช FINAL COMPREHENSIVE API TEST ๐Ÿงช" -echo "==================================" - -PASSED=0 -FAILED=0 - -# Function to test endpoint -test_endpoint() { - local name="$1" - local result="$2" - if [ "$result" = "true" ]; then - echo "โœ… $name" - ((PASSED++)) - else - echo "โŒ $name" - ((FAILED++)) - fi -} - -# Get auth token -echo "๐Ÿ” Getting authentication token..." -TOKEN=$(curl -s -X POST https://api.rockvilletollandsda.church/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username": "admin", "password": "Alright8-Reapply-Shrewdly-Platter-Important-Keenness-Banking-Streak-Tactile"}' \ - | jq -r '.data.token // empty') - -if [ -z "$TOKEN" ]; then - echo "โŒ Failed to get auth token!" - exit 1 -fi -echo "โœ… Auth token obtained" - -echo "" -echo "๐Ÿ“Š TESTING PUBLIC ENDPOINTS..." -echo "===============================" - -# Test public endpoints -test_endpoint "Public Events List" "$(curl -s https://api.rockvilletollandsda.church/api/events | jq -r '.success')" -test_endpoint "Public Bulletins List" "$(curl -s https://api.rockvilletollandsda.church/api/bulletins | jq -r '.success')" -test_endpoint "Public Config" "$(curl -s https://api.rockvilletollandsda.church/api/config | jq -r '.success')" -test_endpoint "Events Upcoming" "$(curl -s https://api.rockvilletollandsda.church/api/events/upcoming | jq -r '.success')" -test_endpoint "Events Featured" "$(curl -s https://api.rockvilletollandsda.church/api/events/featured | jq -r '.success')" -test_endpoint "Current Bulletin" "$(curl -s https://api.rockvilletollandsda.church/api/bulletins/current | jq -r '.success')" - -echo "" -echo "๐Ÿ”’ TESTING ADMIN ENDPOINTS..." -echo "=============================" - -# Test admin endpoints -test_endpoint "Admin Pending Events" "$(curl -s -H "Authorization: Bearer $TOKEN" https://api.rockvilletollandsda.church/api/admin/events/pending | jq -r '.success')" -test_endpoint "Admin Config (with API keys)" "$(curl -s -H "Authorization: Bearer $TOKEN" https://api.rockvilletollandsda.church/api/admin/config | jq -r '.success')" - -echo "" -echo "๐Ÿ“ TESTING CRUD OPERATIONS..." -echo "=============================" - -# Test admin create event -CREATE_RESULT=$(curl -s -X POST -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "title": "Final Test Event", - "description": "Testing complete CRUD", - "start_time": "2025-09-01T18:00:00Z", - "end_time": "2025-09-01T20:00:00Z", - "location": "Test Location", - "category": "Ministry", - "is_featured": false, - "recurring_type": null - }' \ - https://api.rockvilletollandsda.church/api/admin/events | jq -r '.success // false') - -test_endpoint "Admin Create Event" "$CREATE_RESULT" - -if [ "$CREATE_RESULT" = "true" ]; then - # Get the created event ID - EVENT_ID=$(curl -s -X POST -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "title": "Update Test Event", - "description": "Testing update functionality", - "start_time": "2025-09-01T19:00:00Z", - "end_time": "2025-09-01T21:00:00Z", - "location": "Test Location 2", - "category": "Ministry", - "is_featured": false, - "recurring_type": null - }' \ - https://api.rockvilletollandsda.church/api/admin/events | jq -r '.data.id // empty') - - if [ ! -z "$EVENT_ID" ]; then - # Test update - UPDATE_RESULT=$(curl -s -X PUT -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "title": "Updated Test Event", - "description": "Testing update functionality - UPDATED", - "start_time": "2025-09-01T19:30:00Z", - "end_time": "2025-09-01T21:30:00Z", - "location": "Updated Location", - "category": "Ministry", - "is_featured": true, - "recurring_type": null - }' \ - https://api.rockvilletollandsda.church/api/admin/events/$EVENT_ID | jq -r '.success // false') - - test_endpoint "Admin Update Event" "$UPDATE_RESULT" - - # Test delete - DELETE_RESULT=$(curl -s -X DELETE -H "Authorization: Bearer $TOKEN" \ - https://api.rockvilletollandsda.church/api/admin/events/$EVENT_ID | jq -r '.success // false') - - test_endpoint "Admin Delete Event" "$DELETE_RESULT" - else - echo "โŒ Could not get event ID for update/delete tests" - ((FAILED+=2)) - fi -fi - -echo "" -echo "๐Ÿ“ง TESTING EVENT SUBMISSION & WORKFLOW..." -echo "========================================" - -# Test event submission -SUBMIT_RESULT=$(curl -s -X POST https://api.rockvilletollandsda.church/api/events/submit \ - -H "Content-Type: application/json" \ - -d '{ - "title": "Test Submission Workflow", - "description": "Testing the complete submission to approval workflow", - "start_time": "2025-09-15T18:00:00Z", - "end_time": "2025-09-15T20:00:00Z", - "location": "Fellowship Hall", - "category": "Social", - "bulletin_week": "current", - "submitter_email": "admin@rockvilletollandsda.church" - }' | jq -r '.success // false') - -test_endpoint "Public Event Submission" "$SUBMIT_RESULT" - -echo "" -echo "๐Ÿ“ TESTING FILE UPLOAD..." -echo "=========================" - -# Test file upload (create a small test file) -echo "Test file content for API testing" > test_upload.txt -BULLETIN_ID=$(curl -s https://api.rockvilletollandsda.church/api/bulletins | jq -r '.data.items[0].id // empty') - -if [ ! -z "$BULLETIN_ID" ]; then - UPLOAD_RESULT=$(curl -s -X POST -H "Authorization: Bearer $TOKEN" \ - -F "file=@test_upload.txt" \ - https://api.rockvilletollandsda.church/api/upload/bulletins/$BULLETIN_ID/pdf | jq -r '.success // false') - - test_endpoint "File Upload" "$UPLOAD_RESULT" - rm -f test_upload.txt -else - echo "โŒ Could not get bulletin ID for file upload test" - ((FAILED++)) -fi - -echo "" -echo "๐Ÿ”„ TESTING RECURRING EVENTS..." -echo "==============================" - -# Check if recurring events scheduler is running -RECURRING_LOG=$(sudo journalctl -u church-api.service -n 50 | grep -c "recurring events update" || echo "0") -if [ "$RECURRING_LOG" -gt 0 ]; then - echo "โœ… Recurring Events Scheduler Running" - ((PASSED++)) -else - echo "โŒ Recurring Events Scheduler Not Found in Logs" - ((FAILED++)) -fi - -echo "" -echo "๐Ÿ“Š FINAL RESULTS" -echo "================" -echo "โœ… Tests Passed: $PASSED" -echo "โŒ Tests Failed: $FAILED" -echo "๐Ÿ“ˆ Success Rate: $(( PASSED * 100 / (PASSED + FAILED) ))%" - -if [ $FAILED -eq 0 ]; then - echo "" - echo "๐ŸŽ‰๐ŸŽ‰๐ŸŽ‰ ALL TESTS PASSED! ๐ŸŽ‰๐ŸŽ‰๐ŸŽ‰" - echo "๐Ÿš€ YOUR CHURCH API IS 100% FUNCTIONAL! ๐Ÿš€" - echo "๐Ÿ’ช READY FOR PRODUCTION! ๐Ÿ’ช" -else - echo "" - echo "โš ๏ธ Some tests failed. Check the failed endpoints." -fi - -echo "" -echo "๐Ÿ“‹ SUMMARY OF WORKING FEATURES:" -echo "===============================" -echo "๐Ÿ” Authentication & User Management" -echo "๐Ÿ“Š Complete CRUD Operations" -echo "๐Ÿ“ง Email Notifications" -echo "๐Ÿ“ File Upload & Storage" -echo "โšก Event Approval Workflow" -echo "๐Ÿ—‘๏ธ Delete Operations" -echo "๐Ÿ”„ Automatic Recurring Events" -echo "๐Ÿ”ง Secure Configuration Management" -echo "๐ŸŒ Public & Admin API Endpoints" diff --git a/upload_debug.sh b/upload_debug.sh deleted file mode 100755 index 84fb73d..0000000 --- a/upload_debug.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -echo "๐Ÿ” DEBUGGING FILE UPLOAD ISSUE" -echo "===============================" - -# Get token -TOKEN=$(curl -s -X POST https://api.rockvilletollandsda.church/api/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username": "admin", "password": "Alright8-Reapply-Shrewdly-Platter-Important-Keenness-Banking-Streak-Tactile"}' \ - | jq -r '.data.token') - -# Get bulletin ID -BULLETIN_ID=$(curl -s https://api.rockvilletollandsda.church/api/bulletins | jq -r '.data.items[0].id') -echo "Using bulletin ID: $BULLETIN_ID" - -# Create test file -echo "Test PDF content for debugging" > debug_test.pdf - -echo "" -echo "Testing file upload with verbose output..." -curl -v -X POST -H "Authorization: Bearer $TOKEN" \ - -F "file=@debug_test.pdf" \ - https://api.rockvilletollandsda.church/api/upload/bulletins/$BULLETIN_ID/pdf - -echo "" -echo "Checking service logs for upload errors..." -sudo journalctl -u church-api.service -n 10 | tail -5 - -echo "" -echo "Testing file serve endpoint..." -curl -I https://api.rockvilletollandsda.church/api/upload/files/test.txt - -# Cleanup -rm -f debug_test.pdf - -echo "" -echo "Checking upload directory permissions..." -ls -la /opt/rtsda/church-api/uploads/ diff --git a/upload_images.sh b/upload_images.sh deleted file mode 100755 index 2af8cb2..0000000 --- a/upload_images.sh +++ /dev/null @@ -1,117 +0,0 @@ -#!/usr/bin/env fish - -echo "๐Ÿ–ผ๏ธ DIRECT FILE COPY + PROPER UPDATE" -echo "===================================" - -set API_BASE "https://api.rockvilletollandsda.church/api" -set STORAGE_PATH "/media/archive/pocketbase-temp/pocketbase/pb_data/storage/2tz9osuik53a0yh" -set OLD_PB_BASE "https://pocketbase.rockvilletollandsda.church/api" -set UPLOAD_DIR "/opt/rtsda/church-api/uploads/events" - -# Get token -set AUTH_RESPONSE (curl -s -X POST $API_BASE/auth/login \ - -H "Content-Type: application/json" \ - -d '{"username": "admin", "password": "Alright8-Reapply-Shrewdly-Platter-Important-Keenness-Banking-Streak-Tactile"}') - -set JWT_TOKEN (echo $AUTH_RESPONSE | jq -r '.data.token') -echo "โœ… Got token" - -# Get events for matching -set NEW_EVENTS (curl -s -H "Authorization: Bearer $JWT_TOKEN" "$API_BASE/events?perPage=500") -echo $NEW_EVENTS | jq '.data.items | map({id, title})' > new_events.json - -set OLD_EVENTS (curl -s "$OLD_PB_BASE/collections/events/records?perPage=500") -echo $OLD_EVENTS | jq '.items | map({id, title})' > old_events.json - -set uploaded 0 -set failed 0 - -for event_dir in (find $STORAGE_PATH -mindepth 1 -maxdepth 1 -type d -name '[a-z0-9]*') - set old_id (basename $event_dir) - set image_file (find $event_dir -maxdepth 1 -name "*.webp" -type f | head -1) - - if test -z "$image_file" - continue - end - - # Get old event and find new match - set old_event (cat old_events.json | jq --arg id "$old_id" '.[] | select(.id == $id)') - if test -z "$old_event" - continue - end - - set title (echo $old_event | jq -r '.title') - set new_event (cat new_events.json | jq --arg title "$title" '.[] | select(.title == $title)') - - if test -z "$new_event" - echo "โŒ No match for: $title" - continue - end - - set new_id (echo $new_event | jq -r '.id') - set filename (basename $image_file) - set new_filename "$new_id-$filename" - set image_path "uploads/events/$new_filename" - - echo "๐Ÿ“ค Processing: $title" - - # Copy file to upload directory - cp "$image_file" "$UPLOAD_DIR/$new_filename" - - if test $status -eq 0 - echo "โœ… File copied: $new_filename" - - # Get current event data first - set current_event (curl -s -H "Authorization: Bearer $JWT_TOKEN" \ - "$API_BASE/events/$new_id") - - # Extract current event data and add image path - set event_data (echo $current_event | jq --arg img "$image_path" \ - '.data | { - title: .title, - description: .description, - start_time: .start_time, - end_time: .end_time, - location: .location, - location_url: .location_url, - category: .category, - recurring_type: .recurring_type, - is_featured: .is_featured, - image: $img - }') - - # Update event with complete data - set update_response (curl -s -X PUT \ - -H "Authorization: Bearer $JWT_TOKEN" \ - -H "Content-Type: application/json" \ - -d "$event_data" \ - "$API_BASE/admin/events/$new_id") - - set success (echo $update_response | jq -r '.success // false') - - if test "$success" = "true" - echo "โœ… SUCCESS: $title" - set uploaded (math $uploaded + 1) - else - echo "โŒ DB UPDATE FAILED: $title" - echo " Response: "(echo $update_response) - set failed (math $failed + 1) - end - else - echo "โŒ FILE COPY FAILED: $title" - set failed (math $failed + 1) - end - - echo "---" - sleep 0.1 -end - -rm -f new_events.json old_events.json - -echo "" -echo "๐ŸŽ‰ FINAL RESULTS!" -echo "=================" -echo "โœ… Successfully uploaded: $uploaded images" -echo "โŒ Failed uploads: $failed images" -echo "" -echo "๐ŸŒ Images should be accessible at: https://api.rockvilletollandsda.church/uploads/events/" diff --git a/upload_tests.sh b/upload_tests.sh deleted file mode 100755 index a41fa71..0000000 --- a/upload_tests.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/bin/bash - -echo "๐Ÿ” COMPREHENSIVE FILE UPLOAD DEBUG" -echo "==================================" - -# From your previous debug, we know the file was uploaded as: -UPLOADED_FILE="03378cc5-8c62-48b6-818e-643588b253ce.pdf" -UPLOAD_DIR="/opt/rtsda/church-api/uploads/bulletins" -API_BASE="https://api.rockvilletollandsda.church" - -echo "๐Ÿ“ Step 1: Check if uploaded file actually exists" -echo "Looking for: $UPLOAD_DIR/$UPLOADED_FILE" -if [ -f "$UPLOAD_DIR/$UPLOADED_FILE" ]; then - echo "โœ… File exists!" - ls -la "$UPLOAD_DIR/$UPLOADED_FILE" - echo "File size: $(du -h "$UPLOAD_DIR/$UPLOADED_FILE" | cut -f1)" -else - echo "โŒ File NOT found!" - echo "Contents of bulletins directory:" - ls -la "$UPLOAD_DIR/" -fi - -echo "" -echo "๐Ÿ“ก Step 2: Test different file serve URL patterns" -echo "Testing various possible endpoints..." - -# Test common patterns for file serving -ENDPOINTS=( - "/uploads/bulletins/$UPLOADED_FILE" - "/api/uploads/bulletins/$UPLOADED_FILE" - "/api/files/bulletins/$UPLOADED_FILE" - "/static/uploads/bulletins/$UPLOADED_FILE" - "/files/bulletins/$UPLOADED_FILE" - "/bulletins/$UPLOADED_FILE" -) - -for endpoint in "${ENDPOINTS[@]}"; do - echo "Testing: $API_BASE$endpoint" - response=$(curl -s -o /dev/null -w "%{http_code}" "$API_BASE$endpoint") - echo "Response: $response" - if [ "$response" != "404" ]; then - echo "๐ŸŽ‰ FOUND WORKING ENDPOINT: $endpoint" - break - fi -done - -echo "" -echo "๐Ÿ”ง Step 3: Check API server configuration" -echo "Looking for static file serving configuration..." - -# Check if there's a Rust Cargo.toml or main.rs that might show routing -echo "Checking for Rust project files:" -find /opt/rtsda/church-api -name "Cargo.toml" -o -name "main.rs" -o -name "lib.rs" | head -5 - -echo "" -echo "๐ŸŒ Step 4: Check Caddy configuration" -echo "Caddy reverse proxy might need static file rules..." -if [ -f "/etc/caddy/Caddyfile" ]; then - echo "Found Caddyfile, checking for static file rules:" - grep -n -A5 -B5 "file_server\|root\|static" /etc/caddy/Caddyfile || echo "No static file serving rules found" -else - echo "No Caddyfile found at /etc/caddy/Caddyfile" - echo "Checking other common locations:" - find /etc -name "*caddy*" -type f 2>/dev/null | head -5 -fi - -echo "" -echo "๐Ÿ“‹ Step 5: Check API server logs for file access attempts" -echo "Recent logs when accessing files:" -journalctl -u church-api.service --since "10 minutes ago" | tail -20 - -echo "" -echo "๐Ÿ” Step 6: Test with a simple file serve" -echo "Let's see what the API returns when we try to access the file:" -echo "Full response headers and body:" -curl -v "$API_BASE/uploads/bulletins/$UPLOADED_FILE" 2>&1 - -echo "" -echo "๐Ÿ’ก SUMMARY & NEXT STEPS:" -echo "Based on the results above, we need to:" -echo "1. Confirm the file exists and has correct permissions" -echo "2. Find the correct endpoint pattern for serving files" -echo "3. Check if static file serving is configured in your API server" -echo "4. Verify Caddy is properly proxying static file requests" diff --git a/validate_timezone_migration.sql b/validate_timezone_migration.sql deleted file mode 100644 index 88d682f..0000000 --- a/validate_timezone_migration.sql +++ /dev/null @@ -1,422 +0,0 @@ --- Timezone Migration Validation Script --- File: validate_timezone_migration.sql --- --- This script validates that the timezone conversion migration was successful. --- It compares the migrated UTC times with the original EST times from backup tables. --- --- Run this script after the migration to verify correctness. - --- ================================ --- VALIDATION OVERVIEW --- ================================ - -DO $$ -BEGIN - RAISE NOTICE '========================================'; - RAISE NOTICE 'TIMEZONE MIGRATION VALIDATION REPORT'; - RAISE NOTICE 'Generated at: %', NOW(); - RAISE NOTICE '========================================'; -END $$; - --- ================================ --- 1. BACKUP TABLE VERIFICATION --- ================================ - -DO $$ -DECLARE - table_info RECORD; - backup_count INTEGER := 0; -BEGIN - RAISE NOTICE ''; - RAISE NOTICE '1. BACKUP TABLE VERIFICATION'; - RAISE NOTICE '----------------------------'; - - FOR table_info IN - SELECT - schemaname, - tablename, - n_tup_ins as row_count - FROM pg_stat_user_tables - WHERE tablename LIKE '%_timezone_backup' - ORDER BY tablename - LOOP - RAISE NOTICE 'Backup table: % (% rows)', table_info.tablename, table_info.row_count; - backup_count := backup_count + 1; - END LOOP; - - RAISE NOTICE 'Total backup tables found: %', backup_count; - - IF backup_count < 8 THEN - RAISE WARNING 'Expected 8 backup tables, found %. Some backups may be missing.', backup_count; - END IF; -END $$; - --- ================================ --- 2. TIMEZONE OFFSET VALIDATION --- ================================ - --- Check that the migration applied correct timezone offsets -WITH timezone_validation AS ( - SELECT - e.id, - e.title, - e.start_time as current_utc, - eb.original_start_time as original_est, - EXTRACT(EPOCH FROM (e.start_time - eb.original_start_time))/3600 as hour_offset, - CASE - WHEN EXTRACT(EPOCH FROM (e.start_time - eb.original_start_time))/3600 BETWEEN 4 AND 5 THEN 'CORRECT' - ELSE 'INCORRECT' - END as validation_status - FROM events e - JOIN events_timezone_backup eb ON e.id = eb.id - WHERE e.start_time IS NOT NULL - AND eb.original_start_time IS NOT NULL - LIMIT 10 -) -SELECT - '2. TIMEZONE OFFSET VALIDATION' as section, - '' as spacer -UNION ALL -SELECT - '----------------------------' as section, - '' as spacer -UNION ALL -SELECT - 'Sample Event: ' || title as section, - 'Offset: ' || ROUND(hour_offset::numeric, 2) || ' hours (' || validation_status || ')' as spacer -FROM timezone_validation; - --- ================================ --- 3. DISPLAY TIME VALIDATION --- ================================ - --- Verify that UTC times display correctly in NY timezone -DO $$ -DECLARE - event_record RECORD; - sample_count INTEGER := 0; -BEGIN - RAISE NOTICE ''; - RAISE NOTICE '3. DISPLAY TIME VALIDATION'; - RAISE NOTICE '---------------------------'; - RAISE NOTICE 'Verifying UTC times display correctly in America/New_York timezone:'; - RAISE NOTICE ''; - - FOR event_record IN - SELECT - title, - start_time as utc_time, - start_time AT TIME ZONE 'America/New_York' as ny_display_time - FROM events - WHERE start_time IS NOT NULL - ORDER BY start_time - LIMIT 5 - LOOP - sample_count := sample_count + 1; - RAISE NOTICE 'Event: %', event_record.title; - RAISE NOTICE ' UTC Time: %', event_record.utc_time; - RAISE NOTICE ' NY Display: %', event_record.ny_display_time; - RAISE NOTICE ''; - END LOOP; - - IF sample_count = 0 THEN - RAISE WARNING 'No events found for display time validation.'; - END IF; -END $$; - --- ================================ --- 4. MIGRATION STATISTICS --- ================================ - -DO $$ -DECLARE - events_migrated INTEGER; - pending_migrated INTEGER; - bulletins_migrated INTEGER; - users_migrated INTEGER; - total_records INTEGER; -BEGIN - RAISE NOTICE '4. MIGRATION STATISTICS'; - RAISE NOTICE '-----------------------'; - - -- Count migrated records - SELECT COUNT(*) INTO events_migrated - FROM events - WHERE start_time IS NOT NULL OR end_time IS NOT NULL; - - SELECT COUNT(*) INTO pending_migrated - FROM pending_events - WHERE start_time IS NOT NULL OR end_time IS NOT NULL OR submitted_at IS NOT NULL; - - SELECT COUNT(*) INTO bulletins_migrated - FROM bulletins - WHERE created_at IS NOT NULL OR updated_at IS NOT NULL; - - SELECT COUNT(*) INTO users_migrated - FROM users - WHERE created_at IS NOT NULL OR updated_at IS NOT NULL; - - total_records := events_migrated + pending_migrated + bulletins_migrated + users_migrated; - - RAISE NOTICE 'Events with timestamps: %', events_migrated; - RAISE NOTICE 'Pending events with timestamps: %', pending_migrated; - RAISE NOTICE 'Bulletins with timestamps: %', bulletins_migrated; - RAISE NOTICE 'Users with timestamps: %', users_migrated; - RAISE NOTICE 'TOTAL RECORDS MIGRATED: %', total_records; -END $$; - --- ================================ --- 5. CONSISTENCY CHECKS --- ================================ - -DO $$ -DECLARE - inconsistent_count INTEGER; - null_timestamp_count INTEGER; -BEGIN - RAISE NOTICE ''; - RAISE NOTICE '5. CONSISTENCY CHECKS'; - RAISE NOTICE '---------------------'; - - -- Check for events where start_time > end_time (potential migration issue) - SELECT COUNT(*) INTO inconsistent_count - FROM events - WHERE start_time IS NOT NULL - AND end_time IS NOT NULL - AND start_time > end_time; - - RAISE NOTICE 'Events with start_time > end_time: %', inconsistent_count; - - IF inconsistent_count > 0 THEN - RAISE WARNING 'Found % events with inconsistent start/end times!', inconsistent_count; - END IF; - - -- Check for NULL timestamps where they shouldn't be - SELECT COUNT(*) INTO null_timestamp_count - FROM events - WHERE (start_time IS NULL OR end_time IS NULL); - - RAISE NOTICE 'Events with NULL start/end times: %', null_timestamp_count; - - IF null_timestamp_count > 0 THEN - RAISE WARNING 'Found % events with NULL timestamps!', null_timestamp_count; - END IF; -END $$; - --- ================================ --- 6. FUTURE EVENT VALIDATION --- ================================ - --- Check upcoming events to ensure they display correctly -DO $$ -DECLARE - future_event RECORD; - future_count INTEGER := 0; -BEGIN - RAISE NOTICE ''; - RAISE NOTICE '6. FUTURE EVENT VALIDATION'; - RAISE NOTICE '--------------------------'; - RAISE NOTICE 'Upcoming events (next 30 days):'; - RAISE NOTICE ''; - - FOR future_event IN - SELECT - title, - start_time AT TIME ZONE 'America/New_York' as ny_time, - EXTRACT(DOW FROM (start_time AT TIME ZONE 'America/New_York')) as day_of_week - FROM events - WHERE start_time > NOW() - AND start_time < (NOW() + INTERVAL '30 days') - ORDER BY start_time - LIMIT 5 - LOOP - future_count := future_count + 1; - RAISE NOTICE 'Event: %', future_event.title; - RAISE NOTICE ' NY Time: %', future_event.ny_time; - RAISE NOTICE ' Day of Week: %', - CASE future_event.day_of_week::INTEGER - WHEN 0 THEN 'Sunday' - WHEN 1 THEN 'Monday' - WHEN 2 THEN 'Tuesday' - WHEN 3 THEN 'Wednesday' - WHEN 4 THEN 'Thursday' - WHEN 5 THEN 'Friday' - WHEN 6 THEN 'Saturday' - END; - RAISE NOTICE ''; - END LOOP; - - IF future_count = 0 THEN - RAISE NOTICE 'No upcoming events found in the next 30 days.'; - END IF; -END $$; - --- ================================ --- 7. DAYLIGHT SAVING TIME VALIDATION --- ================================ - --- Check that DST transitions are handled correctly -DO $$ -DECLARE - dst_record RECORD; - dst_sample_count INTEGER := 0; -BEGIN - RAISE NOTICE ''; - RAISE NOTICE '7. DAYLIGHT SAVING TIME VALIDATION'; - RAISE NOTICE '-----------------------------------'; - RAISE NOTICE 'Checking DST handling for different times of year:'; - RAISE NOTICE ''; - - -- Sample events from different months to check DST handling - FOR dst_record IN - SELECT - title, - start_time, - start_time AT TIME ZONE 'America/New_York' as ny_time, - EXTRACT(MONTH FROM start_time) as month, - CASE - WHEN EXTRACT(MONTH FROM start_time) IN (11, 12, 1, 2, 3) THEN 'EST (UTC-5)' - ELSE 'EDT (UTC-4)' - END as expected_timezone - FROM events - WHERE start_time IS NOT NULL - ORDER BY EXTRACT(MONTH FROM start_time), start_time - LIMIT 6 - LOOP - dst_sample_count := dst_sample_count + 1; - RAISE NOTICE 'Month %: % (Expected: %)', - dst_record.month, - dst_record.title, - dst_record.expected_timezone; - RAISE NOTICE ' UTC: %', dst_record.start_time; - RAISE NOTICE ' NY Time: %', dst_record.ny_time; - RAISE NOTICE ''; - END LOOP; - - IF dst_sample_count = 0 THEN - RAISE NOTICE 'No events found for DST validation.'; - END IF; -END $$; - --- ================================ --- 8. MIGRATION LOG VERIFICATION --- ================================ - -DO $$ -DECLARE - log_record RECORD; - migration_found BOOLEAN := FALSE; -BEGIN - RAISE NOTICE ''; - RAISE NOTICE '8. MIGRATION LOG VERIFICATION'; - RAISE NOTICE '-----------------------------'; - - FOR log_record IN - SELECT - migration_name, - executed_at, - description, - success - FROM migration_log - WHERE migration_name LIKE '%timezone%' - ORDER BY executed_at DESC - LOOP - migration_found := TRUE; - RAISE NOTICE 'Migration: %', log_record.migration_name; - RAISE NOTICE 'Executed: %', log_record.executed_at; - RAISE NOTICE 'Success: %', log_record.success; - RAISE NOTICE 'Description: %', log_record.description; - RAISE NOTICE ''; - END LOOP; - - IF NOT migration_found THEN - RAISE WARNING 'No timezone migration entries found in migration_log table.'; - END IF; -END $$; - --- ================================ --- VALIDATION SUMMARY --- ================================ - -DO $$ -DECLARE - total_events INTEGER; - total_pending INTEGER; - backup_tables INTEGER; -BEGIN - RAISE NOTICE '========================================'; - RAISE NOTICE 'VALIDATION SUMMARY'; - RAISE NOTICE '========================================'; - - SELECT COUNT(*) INTO total_events FROM events WHERE start_time IS NOT NULL; - SELECT COUNT(*) INTO total_pending FROM pending_events WHERE start_time IS NOT NULL; - SELECT COUNT(*) INTO backup_tables FROM information_schema.tables WHERE table_name LIKE '%_timezone_backup'; - - RAISE NOTICE 'Events validated: %', total_events; - RAISE NOTICE 'Pending events validated: %', total_pending; - RAISE NOTICE 'Backup tables available: %', backup_tables; - RAISE NOTICE ''; - RAISE NOTICE 'VALIDATION COMPLETED at %', NOW(); - RAISE NOTICE '========================================'; - - IF backup_tables >= 8 AND total_events > 0 THEN - RAISE NOTICE 'STATUS: Migration validation PASSED'; - ELSE - RAISE WARNING 'STATUS: Migration validation issues detected - review above'; - END IF; - - RAISE NOTICE '========================================'; -END $$; - --- ================================ --- RECOMMENDED MANUAL CHECKS --- ================================ - --- These queries should be run manually to spot-check results -SELECT - '-- MANUAL CHECK QUERIES' as info, - '-- Run these queries to manually verify migration results:' as instructions -UNION ALL -SELECT - '-- 1. Compare before/after for specific events:' as info, - $manual1$ -SELECT - e.title, - eb.original_start_time as "Before (EST-as-UTC)", - e.start_time as "After (True UTC)", - e.start_time AT TIME ZONE 'America/New_York' as "Display (NY Time)", - EXTRACT(EPOCH FROM (e.start_time - eb.original_start_time))/3600 as "Hour Offset" -FROM events e -JOIN events_timezone_backup eb ON e.id = eb.id -WHERE e.start_time IS NOT NULL -ORDER BY e.start_time -LIMIT 10; -$manual1$ as instructions -UNION ALL -SELECT - '-- 2. Check upcoming events display correctly:' as info, - $manual2$ -SELECT - title, - start_time as utc_time, - start_time AT TIME ZONE 'America/New_York' as ny_display_time, - end_time AT TIME ZONE 'America/New_York' as ny_end_time -FROM events -WHERE start_time > NOW() -ORDER BY start_time -LIMIT 10; -$manual2$ as instructions -UNION ALL -SELECT - '-- 3. Verify pending events submission times:' as info, - $manual3$ -SELECT - title, - submitted_at as utc_submitted, - submitted_at AT TIME ZONE 'America/New_York' as ny_submitted, - start_time AT TIME ZONE 'America/New_York' as ny_event_time -FROM pending_events -WHERE submitted_at IS NOT NULL -ORDER BY submitted_at DESC -LIMIT 5; -$manual3$ as instructions; \ No newline at end of file diff --git a/verify_and_clean.sql b/verify_and_clean.sql deleted file mode 100644 index 9d5ead9..0000000 --- a/verify_and_clean.sql +++ /dev/null @@ -1,102 +0,0 @@ --- First, let's check what data actually contains HTML -SELECT 'Bulletins with HTML tags:' as check_type; -SELECT id, title, - CASE WHEN scripture_reading LIKE '%<%' THEN 'HAS HTML' ELSE 'CLEAN' END as scripture_status, - CASE WHEN sabbath_school LIKE '%<%' THEN 'HAS HTML' ELSE 'CLEAN' END as sabbath_status, - CASE WHEN divine_worship LIKE '%<%' THEN 'HAS HTML' ELSE 'CLEAN' END as worship_status -FROM bulletins -WHERE is_active = true -ORDER BY date DESC -LIMIT 3; - --- Show actual content to see what we're dealing with -SELECT 'Current scripture_reading content:' as content_type; -SELECT substring(scripture_reading, 1, 100) as sample_content -FROM bulletins -WHERE is_active = true -ORDER BY date DESC -LIMIT 1; - --- Now let's clean it more aggressively -UPDATE bulletins -SET scripture_reading = - TRIM( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REGEXP_REPLACE(scripture_reading, '<[^>]*>', '', 'g'), - '&', '&' - ), - '<', '<' - ), - '>', '>' - ), - '"', '"' - ), - ''', '''' - ), - ' ', ' ' - ) - ), - sabbath_school = - CASE WHEN sabbath_school IS NOT NULL THEN - TRIM( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REGEXP_REPLACE(sabbath_school, '<[^>]*>', '', 'g'), - '&', '&' - ), - '<', '<' - ), - '>', '>' - ), - '"', '"' - ), - ''', '''' - ), - ' ', ' ' - ) - ) - ELSE sabbath_school END, - divine_worship = - CASE WHEN divine_worship IS NOT NULL THEN - TRIM( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REPLACE( - REGEXP_REPLACE(divine_worship, '<[^>]*>', '', 'g'), - '&', '&' - ), - '<', '<' - ), - '>', '>' - ), - '"', '"' - ), - ''', '''' - ), - ' ', ' ' - ) - ) - ELSE divine_worship END -WHERE scripture_reading LIKE '%<%' - OR sabbath_school LIKE '%<%' - OR divine_worship LIKE '%<%'; - --- Verify the cleaning worked -SELECT 'After cleaning - scripture_reading content:' as content_type; -SELECT substring(scripture_reading, 1, 100) as sample_content -FROM bulletins -WHERE is_active = true -ORDER BY date DESC -LIMIT 1; \ No newline at end of file diff --git a/verify_migration.sql b/verify_migration.sql deleted file mode 100644 index cb27cf0..0000000 --- a/verify_migration.sql +++ /dev/null @@ -1,90 +0,0 @@ --- Verification script to check HTML entity cleaning migration results --- Run this after the migration to verify it worked correctly - --- Check if the cleaning function exists -SELECT - 'clean_html_entities function: ' || - CASE WHEN EXISTS ( - SELECT 1 FROM pg_proc p - JOIN pg_namespace n ON p.pronamespace = n.oid - WHERE p.proname = 'clean_html_entities' AND n.nspname = 'public' - ) THEN 'โœ“ EXISTS' ELSE 'โœ— MISSING' END as function_status; - --- Count records that still have HTML entities (should be 0 after migration) -SELECT - 'Records with HTML tags in bulletins: ' || COUNT(*) as bulletin_html_tags -FROM bulletins -WHERE - title ~ '<[^>]*>' OR - sabbath_school ~ '<[^>]*>' OR - divine_worship ~ '<[^>]*>' OR - scripture_reading ~ '<[^>]*>' OR - sunset ~ '<[^>]*>'; - -SELECT - 'Records with HTML entities in bulletins: ' || COUNT(*) as bulletin_html_entities -FROM bulletins -WHERE - title ~ '&(nbsp|amp|lt|gt|quot|#39);' OR - sabbath_school ~ '&(nbsp|amp|lt|gt|quot|#39);' OR - divine_worship ~ '&(nbsp|amp|lt|gt|quot|#39);' OR - scripture_reading ~ '&(nbsp|amp|lt|gt|quot|#39);' OR - sunset ~ '&(nbsp|amp|lt|gt|quot|#39);'; - -SELECT - 'Records with HTML tags in events: ' || COUNT(*) as event_html_tags -FROM events -WHERE - title ~ '<[^>]*>' OR - description ~ '<[^>]*>' OR - location ~ '<[^>]*>' OR - location_url ~ '<[^>]*>' OR - approved_from ~ '<[^>]*>'; - -SELECT - 'Records with HTML entities in events: ' || COUNT(*) as event_html_entities -FROM events -WHERE - title ~ '&(nbsp|amp|lt|gt|quot|#39);' OR - description ~ '&(nbsp|amp|lt|gt|quot|#39);' OR - location ~ '&(nbsp|amp|lt|gt|quot|#39);' OR - location_url ~ '&(nbsp|amp|lt|gt|quot|#39);' OR - approved_from ~ '&(nbsp|amp|lt|gt|quot|#39);'; - --- Show some sample cleaned data -SELECT - 'Sample bulletin titles after cleaning:' as sample_data; - -SELECT - SUBSTRING(title, 1, 50) || (CASE WHEN LENGTH(title) > 50 THEN '...' ELSE '' END) as cleaned_titles -FROM bulletins -WHERE title IS NOT NULL -ORDER BY updated_at DESC -LIMIT 5; - -SELECT - 'Sample event descriptions after cleaning:' as sample_data; - -SELECT - SUBSTRING(description, 1, 80) || (CASE WHEN LENGTH(description) > 80 THEN '...' ELSE '' END) as cleaned_descriptions -FROM events -WHERE description IS NOT NULL -ORDER BY updated_at DESC -LIMIT 3; - --- Check when records were last updated (should show recent timestamps if migration ran) -SELECT - 'Recently updated bulletins: ' || COUNT(*) || ' (updated in last hour)' as recent_bulletins -FROM bulletins -WHERE updated_at > NOW() - INTERVAL '1 hour'; - -SELECT - 'Recently updated events: ' || COUNT(*) || ' (updated in last hour)' as recent_events -FROM events -WHERE updated_at > NOW() - INTERVAL '1 hour'; - --- Summary -SELECT '===============================================' as summary; -SELECT 'MIGRATION VERIFICATION COMPLETE' as summary; -SELECT 'If all HTML tag/entity counts above are 0, the migration was successful!' as summary; -SELECT '===============================================' as summary; \ No newline at end of file diff --git a/webp_setup.sh b/webp_setup.sh deleted file mode 100755 index b005994..0000000 --- a/webp_setup.sh +++ /dev/null @@ -1,195 +0,0 @@ -#!/bin/bash - -echo "๐Ÿš€ SETTING UP WEBP CONVERSION" -echo "=============================" - -# Step 1: Add dependencies -echo "๐Ÿ“ฆ Adding WebP dependencies to Cargo.toml..." -if grep -q 'image = ' Cargo.toml; then - echo "โœ… image dependency already exists" -else - echo 'image = "0.24"' >> Cargo.toml - echo "โœ… Added image dependency" -fi - -if grep -q 'webp = ' Cargo.toml; then - echo "โœ… webp dependency already exists" -else - echo 'webp = "0.2"' >> Cargo.toml - echo "โœ… Added webp dependency" -fi - -# Step 2: Create utils directory if it doesn't exist -echo "๐Ÿ“ Creating utils directory..." -mkdir -p src/utils - -# Step 3: Create the WebP conversion module -echo "๐Ÿ”ง Creating WebP conversion module..." -cat > src/utils/images.rs << 'EOF' -use image::ImageFormat; -use std::io::Cursor; - -pub async fn convert_to_webp(image_bytes: &[u8]) -> Result, String> { - let bytes = image_bytes.to_vec(); - - tokio::task::spawn_blocking(move || { - let img = image::load_from_memory(&bytes) - .map_err(|e| format!("Failed to load image: {}", e))?; - - // Resize if too large (optional optimization) - let img = if img.width() > 1920 || img.height() > 1920 { - img.resize(1920, 1920, image::imageops::FilterType::Lanczos3) - } else { - img - }; - - let rgb_img = img.to_rgb8(); - let encoder = webp::Encoder::from_rgb(&rgb_img, img.width(), img.height()); - let webp = encoder.encode(85.0); // 85% quality - - Ok(webp.to_vec()) - }) - .await - .map_err(|e| format!("Task failed: {}", e))? -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_webp_conversion() { - // Create a simple 100x100 red image - let img = image::RgbImage::from_fn(100, 100, |_, _| image::Rgb([255, 0, 0])); - let mut buffer = Vec::new(); - - // Encode as PNG first - { - let mut cursor = Cursor::new(&mut buffer); - image::DynamicImage::ImageRgb8(img) - .write_to(&mut cursor, ImageFormat::Png) - .expect("Failed to write PNG"); - } - - // Convert to WebP - let webp_data = convert_to_webp(&buffer).await.expect("WebP conversion failed"); - - // Verify it's actually WebP data - assert!(webp_data.len() > 0); - assert!(webp_data.starts_with(b"RIFF")); // WebP signature - - println!("โœ… WebP conversion test passed!"); - println!(" Original PNG: {} bytes", buffer.len()); - println!(" WebP result: {} bytes", webp_data.len()); - println!(" Compression: {:.1}%", (1.0 - webp_data.len() as f64 / buffer.len() as f64) * 100.0); - } - - #[tokio::test] - async fn test_invalid_image() { - let fake_data = b"not an image"; - let result = convert_to_webp(fake_data).await; - assert!(result.is_err()); - println!("โœ… Invalid image test passed!"); - } -} -EOF - -echo "โœ… Created src/utils/images.rs" - -# Step 4: Update or create utils mod.rs -if [ -f src/utils/mod.rs ]; then - echo "๐Ÿ“ Updating src/utils/mod.rs..." - if ! grep -q "pub mod images;" src/utils/mod.rs; then - echo "pub mod images;" >> src/utils/mod.rs - fi -else - echo "๐Ÿ“ Creating src/utils/mod.rs..." - echo "pub mod images;" > src/utils/mod.rs -fi - -echo "โœ… Updated utils module" - -# Step 5: Update main.rs or lib.rs to include utils -echo "๐Ÿ“ Checking for utils module inclusion..." -main_file="" -if [ -f src/main.rs ]; then - main_file="src/main.rs" -elif [ -f src/lib.rs ]; then - main_file="src/lib.rs" -fi - -if [ -n "$main_file" ]; then - if ! grep -q "mod utils;" "$main_file"; then - echo "mod utils;" >> "$main_file" - echo "โœ… Added utils module to $main_file" - else - echo "โœ… Utils module already included in $main_file" - fi -else - echo "โš ๏ธ Couldn't find main.rs or lib.rs - you'll need to add 'mod utils;' manually" -fi - -# Step 6: Build to check for errors -echo "๐Ÿ”จ Building project to verify setup..." -if cargo build; then - echo "โœ… Build successful!" -else - echo "โŒ Build failed - check the errors above" - exit 1 -fi - -# Step 7: Run tests -echo "๐Ÿงช Running WebP conversion tests..." -if cargo test images::tests -- --nocapture; then - echo "โœ… All tests passed!" -else - echo "โŒ Tests failed" - exit 1 -fi - -# Step 8: Create example usage -echo "๐Ÿ“„ Creating example usage file..." -cat > webp_example.rs << 'EOF' -// Example usage in your upload handler: - -use crate::utils::images::convert_to_webp; - -pub async fn handle_image_upload(image_data: Vec) -> Result { - // Save original - let original_path = format!("uploads/original_{}.jpg", uuid::Uuid::new_v4()); - tokio::fs::write(&original_path, &image_data).await - .map_err(|e| format!("Failed to save original: {}", e))?; - - // Convert to WebP - let webp_data = convert_to_webp(&image_data).await?; - let webp_path = format!("uploads/webp_{}.webp", uuid::Uuid::new_v4()); - tokio::fs::write(&webp_data, webp_data).await - .map_err(|e| format!("Failed to save WebP: {}", e))?; - - Ok(webp_path) -} - -// Or for immediate conversion (slower but simpler): -pub async fn convert_and_save_webp(image_data: Vec, filename: &str) -> Result { - let webp_data = convert_to_webp(&image_data).await?; - let webp_path = format!("uploads/{}.webp", filename); - tokio::fs::write(&webp_path, webp_data).await - .map_err(|e| format!("Failed to save: {}", e))?; - Ok(webp_path) -} -EOF - -echo "โœ… Created webp_example.rs" - -echo "" -echo "๐ŸŽ‰ WEBP CONVERSION SETUP COMPLETE!" -echo "==================================" -echo "โœ… Dependencies added to Cargo.toml" -echo "โœ… WebP conversion module created" -echo "โœ… Tests written and passing" -echo "โœ… Example usage provided" -echo "" -echo "๐Ÿ”ง Next steps:" -echo "1. Import in your upload handler: use crate::utils::images::convert_to_webp;" -echo "2. Call convert_to_webp(&image_bytes).await in your code" -echo "3. Save the returned Vec as a .webp file" diff --git a/webp_submit.sh b/webp_submit.sh deleted file mode 100755 index 9562126..0000000 --- a/webp_submit.sh +++ /dev/null @@ -1,310 +0,0 @@ -#!/bin/bash - -echo "๐Ÿ”„ UPDATING EVENTS HANDLER (CORRECT VERSION)" -echo "=============================================" - -# Step 1: Restore from backup if needed -if [ -f src/handlers/events.rs.backup.* ]; then - echo "๐Ÿ“ฆ Restoring from backup first..." - cp src/handlers/events.rs.backup.* src/handlers/events.rs - echo "โœ… Restored" -fi - -# Step 2: Add the correct imports -echo "๐Ÿ“ Adding correct imports..." -sed -i '/^use uuid::Uuid;$/a\ -\ -// New imports for WebP and multipart support\ -use axum::extract::Multipart;\ -use crate::utils::images::convert_to_webp;\ -use tokio::fs;\ -use chrono::{DateTime, Utc};' src/handlers/events.rs - -echo "โœ… Imports added" - -# Step 3: Create the CORRECT submit function -echo "๐Ÿ”ง Creating CORRECT submit function..." -cat > /tmp/correct_submit_function.rs << 'EOF' -pub async fn submit( - State(state): State, - mut multipart: Multipart, -) -> Result>> { - // Initialize the request struct with ACTUAL fields - let mut req = SubmitEventRequest { - title: String::new(), - description: String::new(), - start_time: Utc::now(), // Temporary default - end_time: Utc::now(), // Temporary default - location: String::new(), - location_url: None, - category: String::new(), - is_featured: None, - recurring_type: None, - bulletin_week: String::new(), - submitter_email: None, - }; - - // Track image paths (we'll save these separately to DB) - let mut image_path: Option = None; - let mut thumbnail_path: Option = None; - - // Extract form fields and files - while let Some(field) = multipart.next_field().await.map_err(|e| { - ApiError::ValidationError(format!("Failed to read multipart field: {}", e)) - })? { - let name = field.name().unwrap_or("").to_string(); - - match name.as_str() { - "title" => { - req.title = field.text().await.map_err(|e| { - ApiError::ValidationError(format!("Invalid title: {}", e)) - })?; - }, - "description" => { - req.description = field.text().await.map_err(|e| { - ApiError::ValidationError(format!("Invalid description: {}", e)) - })?; - }, - "start_time" => { - let time_str = field.text().await.map_err(|e| { - ApiError::ValidationError(format!("Invalid start_time: {}", e)) - })?; - - // Parse as NaiveDateTime first, then convert to UTC - let naive_dt = chrono::NaiveDateTime::parse_from_str(&time_str, "%Y-%m-%dT%H:%M") - .map_err(|e| ApiError::ValidationError(format!("Invalid start_time format: {}", e)))?; - req.start_time = DateTime::from_utc(naive_dt, Utc); - }, - "end_time" => { - let time_str = field.text().await.map_err(|e| { - ApiError::ValidationError(format!("Invalid end_time: {}", e)) - })?; - - let naive_dt = chrono::NaiveDateTime::parse_from_str(&time_str, "%Y-%m-%dT%H:%M") - .map_err(|e| ApiError::ValidationError(format!("Invalid end_time format: {}", e)))?; - req.end_time = DateTime::from_utc(naive_dt, Utc); - }, - "location" => { - req.location = field.text().await.map_err(|e| { - ApiError::ValidationError(format!("Invalid location: {}", e)) - })?; - }, - "category" => { - req.category = field.text().await.map_err(|e| { - ApiError::ValidationError(format!("Invalid category: {}", e)) - })?; - }, - "location_url" => { - let url = field.text().await.map_err(|e| { - ApiError::ValidationError(format!("Invalid location_url: {}", e)) - })?; - if !url.is_empty() { - req.location_url = Some(url); - } - }, - "reoccuring" => { // Note: form uses "reoccuring" but model uses "recurring_type" - let recurring = field.text().await.map_err(|e| { - ApiError::ValidationError(format!("Invalid recurring: {}", e)) - })?; - if !recurring.is_empty() { - req.recurring_type = Some(recurring); - } - }, - "submitter_email" => { - let email = field.text().await.map_err(|e| { - ApiError::ValidationError(format!("Invalid submitter_email: {}", e)) - })?; - if !email.is_empty() { - req.submitter_email = Some(email); - } - }, - "bulletin_week" => { - req.bulletin_week = field.text().await.map_err(|e| { - ApiError::ValidationError(format!("Invalid bulletin_week: {}", e)) - })?; - }, - "image" => { - let image_data = field.bytes().await.map_err(|e| { - ApiError::ValidationError(format!("Failed to read image: {}", e)) - })?; - - if !image_data.is_empty() { - // Save original immediately - let uuid = Uuid::new_v4(); - let original_path = format!("uploads/events/original_{}.jpg", uuid); - - // Ensure directory exists - fs::create_dir_all("uploads/events").await.map_err(|e| { - ApiError::FileError(e) - })?; - - fs::write(&original_path, &image_data).await.map_err(|e| { - ApiError::FileError(e) - })?; - - // Set original path immediately - image_path = Some(original_path.clone()); - - // Convert to WebP in background (user doesn't wait) - let pool = state.pool.clone(); - tokio::spawn(async move { - if let Ok(webp_data) = convert_to_webp(&image_data).await { - let webp_path = format!("uploads/events/{}.webp", uuid); - if fs::write(&webp_path, webp_data).await.is_ok() { - // Update database with WebP path (using actual column name "image") - let _ = sqlx::query!( - "UPDATE pending_events SET image = $1 WHERE image = $2", - webp_path, - original_path - ).execute(&pool).await; - - // Delete original file - let _ = fs::remove_file(&original_path).await; - } - } - }); - } - }, - "thumbnail" => { - let thumb_data = field.bytes().await.map_err(|e| { - ApiError::ValidationError(format!("Failed to read thumbnail: {}", e)) - })?; - - if !thumb_data.is_empty() { - let uuid = Uuid::new_v4(); - let original_path = format!("uploads/events/thumb_original_{}.jpg", uuid); - - fs::create_dir_all("uploads/events").await.map_err(|e| { - ApiError::FileError(e) - })?; - - fs::write(&original_path, &thumb_data).await.map_err(|e| { - ApiError::FileError(e) - })?; - - thumbnail_path = Some(original_path.clone()); - - // Convert thumbnail to WebP in background - let pool = state.pool.clone(); - tokio::spawn(async move { - if let Ok(webp_data) = convert_to_webp(&thumb_data).await { - let webp_path = format!("uploads/events/thumb_{}.webp", uuid); - if fs::write(&webp_path, webp_data).await.is_ok() { - let _ = sqlx::query!( - "UPDATE pending_events SET thumbnail = $1 WHERE thumbnail = $2", - webp_path, - original_path - ).execute(&pool).await; - - let _ = fs::remove_file(&original_path).await; - } - } - }); - } - }, - _ => { - // Ignore unknown fields - let _ = field.bytes().await; - } - } - } - - // Validate required fields - if req.title.is_empty() { - return Err(ApiError::ValidationError("Title is required".to_string())); - } - if req.description.is_empty() { - return Err(ApiError::ValidationError("Description is required".to_string())); - } - if req.location.is_empty() { - return Err(ApiError::ValidationError("Location is required".to_string())); - } - if req.category.is_empty() { - return Err(ApiError::ValidationError("Category is required".to_string())); - } - if req.bulletin_week.is_empty() { - req.bulletin_week = "current".to_string(); // Default value - } - - // Submit to database first - let mut pending_event = db::events::submit_for_approval(&state.pool, req).await?; - - // Update with image paths if we have them - if let Some(img_path) = image_path { - sqlx::query!( - "UPDATE pending_events SET image = $1 WHERE id = $2", - img_path, - pending_event.id - ).execute(&state.pool).await.map_err(ApiError::DatabaseError)?; - } - - if let Some(thumb_path) = thumbnail_path { - sqlx::query!( - "UPDATE pending_events SET thumbnail = $1 WHERE id = $2", - thumb_path, - pending_event.id - ).execute(&state.pool).await.map_err(ApiError::DatabaseError)?; - } - - // Send email notification to admin (existing logic) - let mailer = state.mailer.clone(); - let event_for_email = pending_event.clone(); - tokio::spawn(async move { - if let Err(e) = mailer.send_event_submission_notification(&event_for_email).await { - tracing::error!("Failed to send email: {:?}", e); - } else { - tracing::info!("Email sent for event: {}", event_for_email.title); - } - }); - - Ok(Json(ApiResponse { - success: true, - data: Some(pending_event), - message: Some("Event submitted successfully! Images are being optimized in the background.".to_string()), - })) -} -EOF - -# Step 4: Replace the old submit function with the CORRECT one -echo "๐Ÿ”„ Replacing submit function..." - -# Find the line numbers of the current submit function -start_line=$(grep -n "^pub async fn submit(" src/handlers/events.rs | cut -d: -f1) -end_line=$(awk "NR>$start_line && /^}/ {print NR; exit}" src/handlers/events.rs) - -if [ -n "$start_line" ] && [ -n "$end_line" ]; then - # Create a temporary file with everything except the old submit function - head -n $((start_line - 1)) src/handlers/events.rs > /tmp/events_before.rs - tail -n +$((end_line + 1)) src/handlers/events.rs > /tmp/events_after.rs - - # Combine: before + new function + after - cat /tmp/events_before.rs /tmp/correct_submit_function.rs /tmp/events_after.rs > src/handlers/events.rs - - echo "โœ… Submit function replaced successfully" -else - echo "โŒ Could not find submit function boundaries" - exit 1 -fi - -# Step 5: Clean up temp files -rm -f /tmp/correct_submit_function.rs /tmp/events_before.rs /tmp/events_after.rs - -# Step 6: Build to check for errors -echo "๐Ÿ”จ Building to verify changes..." -if cargo build; then - echo "โœ… Build successful!" - echo "" - echo "๐ŸŽ‰ EVENTS HANDLER UPDATED SUCCESSFULLY!" - echo "======================================" - echo "โœ… Uses ACTUAL model fields" - echo "โœ… Proper DateTime handling" - echo "โœ… Correct ApiError variants" - echo "โœ… Real database columns" - echo "โœ… Background WebP conversion" - echo "โœ… No hallucinated bullshit" - echo "" - echo "๐Ÿš€ Ready to handle multipart form submissions with WebP conversion!" -else - echo "โŒ Build failed - check errors above" - exit 1 -fi