use axum::{Json, extract::State, http::StatusCode, response::IntoResponse}; use serde::Serialize; use tranquil_db_traits::CommsChannel; use tranquil_pds::BUILD_VERSION; use tranquil_pds::state::AppState; use tranquil_pds::util::{discord_app_id, discord_bot_username, telegram_bot_username}; async fn get_available_comms_channels(state: &AppState) -> Vec { let cfg = tranquil_config::get(); let mut channels = vec![CommsChannel::Email]; if cfg.discord.bot_token.is_some() { channels.push(CommsChannel::Discord); } if cfg.telegram.bot_token.is_some() { channels.push(CommsChannel::Telegram); } if let Some(slot) = &state.signal_sender && slot.is_linked().await { channels.push(CommsChannel::Signal); } channels } pub async fn robots_txt() -> impl IntoResponse { ( StatusCode::OK, [("content-type", "text/plain")], "# Hello!\n\n# Crawling the public API is allowed\nUser-agent: *\nAllow: /\n", ) } pub async fn cow_txt() -> &'static str { include_str!("cow.txt") } pub fn is_self_hosted_did_web_enabled() -> bool { tranquil_config::get().server.enable_pds_hosted_did_web } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct DescribeServerLinks { #[serde(skip_serializing_if = "Option::is_none")] pub privacy_policy: Option, #[serde(skip_serializing_if = "Option::is_none")] pub terms_of_service: Option, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct DescribeServerContact { #[serde(skip_serializing_if = "Option::is_none")] pub email: Option, } #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct DescribeServerOutput { pub available_user_domains: Vec, pub invite_code_required: bool, pub did: String, pub links: DescribeServerLinks, pub contact: DescribeServerContact, pub version: &'static str, pub available_comms_channels: Vec, pub self_hosted_did_web_enabled: bool, #[serde(skip_serializing_if = "Option::is_none")] pub discord_bot_username: Option, #[serde(skip_serializing_if = "Option::is_none")] pub discord_app_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub telegram_bot_username: Option, } pub async fn describe_server(State(state): State) -> Json { let cfg = tranquil_config::get(); let pds_hostname = &cfg.server.hostname; Json(DescribeServerOutput { available_user_domains: cfg.server.user_handle_domain_list(), invite_code_required: cfg.server.invite_code_required, did: format!("did:web:{}", pds_hostname), links: DescribeServerLinks { privacy_policy: cfg.server.privacy_policy_url.clone(), terms_of_service: cfg.server.terms_of_service_url.clone(), }, contact: DescribeServerContact { email: cfg.server.contact_email.clone(), }, version: BUILD_VERSION, available_comms_channels: get_available_comms_channels(&state).await, self_hosted_did_web_enabled: is_self_hosted_did_web_enabled(), discord_bot_username: discord_bot_username().map(String::from), discord_app_id: discord_app_id().map(String::from), telegram_bot_username: telegram_bot_username().map(String::from), }) } #[derive(Serialize)] pub struct HealthOutput { #[serde(skip_serializing_if = "Option::is_none")] pub version: Option, #[serde(skip_serializing_if = "Option::is_none")] pub error: Option<&'static str>, } pub async fn health(State(state): State) -> impl IntoResponse { match state.repos.infra.health_check().await { Ok(true) => ( StatusCode::OK, Json(HealthOutput { version: Some(format!("tranquil {}", BUILD_VERSION)), error: None, }), ), _ => ( StatusCode::SERVICE_UNAVAILABLE, Json(HealthOutput { version: None, error: Some("Service Unavailable"), }), ), } }