mod admin; mod i18n; mod migrations; pub mod models; mod public; mod telegram; mod turnstile; mod tz; mod uploads; mod web_push; use tracing_subscriber; use cot::cli::CliMetadata; use cot::config::{ DatabaseConfig, MiddlewareConfig, ProjectConfig, SameSite, SessionMiddlewareConfig, SessionStoreConfig, SessionStoreTypeConfig, }; use cot::db::migrations::SyncDynMigration; use cot::middleware::SessionMiddleware; use cot::project::{MiddlewareContext, ProjectContext, RegisterAppsContext, RootHandler}; use cot::router::Router; use cot::session::db::SessionApp; use cot::{App, AppBuilder, Project}; struct PettingApp; impl App for PettingApp { fn name(&self) -> &'static str { "web-petting" } fn migrations(&self) -> Vec> { cot::db::migrations::wrap_migrations(migrations::MIGRATIONS) } fn router(&self) -> Router { admin::admin_router() } } struct PublicApp; #[async_trait::async_trait] impl App for PublicApp { fn name(&self) -> &'static str { "public" } async fn init(&self, context: &mut ProjectContext) -> cot::Result<()> { web_push::initialize(context.database()).await; Ok(()) } fn router(&self) -> Router { public::public_router() } } struct PettingProject; fn parse_bool_env(name: &str) -> Option { let value = std::env::var(name).ok()?; match value.trim().to_ascii_lowercase().as_str() { "1" | "true" | "yes" | "on" => Some(true), "0" | "false" | "no" | "off" => Some(false), _ => None, } } fn debug_enabled(config_name: &str) -> bool { parse_bool_env("WEB_PETTING_DEBUG").unwrap_or_else(|| { matches!( config_name, "dev" | "development" | "debug" | "local" | "test" ) }) } fn database_url() -> String { std::env::var("WEB_PETTING_DATABASE_URL") .or_else(|_| std::env::var("DATABASE_URL")) .unwrap_or_else(|_| { eprintln!( "WEB_PETTING_DATABASE_URL and DATABASE_URL are not set; using the local default \ postgresql://postgres:postgres@localhost:5432/web_petting" ); "postgresql://postgres:postgres@localhost:5432/web_petting".to_string() }) } impl Project for PettingProject { fn cli_metadata(&self) -> CliMetadata { cot::cli::metadata!() } fn config(&self, config_name: &str) -> cot::Result { Ok(ProjectConfig::builder() .debug(debug_enabled(config_name)) .database(DatabaseConfig::builder().url(database_url()).build()) .middlewares( MiddlewareConfig::builder() .session( SessionMiddlewareConfig::builder() .secure(false) .same_site(SameSite::Lax) .store( SessionStoreConfig::builder() .store_type(SessionStoreTypeConfig::Database) .build(), ) .build(), ) .build(), ) .build()) } fn register_apps(&self, apps: &mut AppBuilder, _context: &RegisterAppsContext) { apps.register(SessionApp::new()); apps.register_with_views(PublicApp, ""); apps.register_with_views(PettingApp, "/admin"); } fn middlewares( &self, handler: cot::project::RootHandlerBuilder, context: &MiddlewareContext, ) -> RootHandler { handler .middleware(SessionMiddleware::from_context(context)) .build() } } fn main() { let filter = tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")); let _ = tracing_subscriber::fmt().with_env_filter(filter).try_init(); let runtime = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .expect("failed to build Tokio runtime"); if let Err(error) = runtime.block_on(cot::run_cli(PettingProject)) { let message = error.to_string(); let details = format!("{error:?}"); eprintln!("Failed to start web-petting: {message}\nDetails: {details}"); if details.contains("28P01") || details.contains("password authentication failed") { eprintln!( "\nPostgreSQL rejected the configured username or password.\n\ Set the connection string before starting the application, for example:\n\n \ WEB_PETTING_DATABASE_URL='postgresql://postgres:YOUR_PASSWORD@localhost:5432/web_petting' cargo run\n\n\ WEB_PETTING_DATABASE_URL takes priority over DATABASE_URL.\n\ Check the current value with: printenv WEB_PETTING_DATABASE_URL" ); } else if message.to_ascii_lowercase().contains("database") { eprintln!( "\nConfigure PostgreSQL with WEB_PETTING_DATABASE_URL or DATABASE_URL.\n\ Example:\n\n WEB_PETTING_DATABASE_URL='postgresql://postgres:YOUR_PASSWORD@localhost:5432/web_petting' cargo run" ); } std::process::exit(1); } }