webserver_base/
axum_plausible_analytics.rsuse crate::{BaseSettings, Environment};
use axum::http::{HeaderMap, StatusCode};
use plausible_rs::{EventHeaders, EventPayload, Plausible, PAGEVIEW_EVENT};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::Arc;
use tracing::{error, info, instrument, warn};
#[derive(Debug, Serialize, Deserialize)]
pub struct RequestPayload {
    pub user_agent: String,
    pub url: String,
    pub referrer: String,
    pub screen_width: usize,
}
pub struct AxumPlausibleAnalyticsHandler {
    plausible_client: Plausible,
}
impl AxumPlausibleAnalyticsHandler {
    #[must_use]
    pub fn new_with_client(http_client: Client) -> Self {
        Self {
            plausible_client: Plausible::new_with_client(http_client),
        }
    }
    #[instrument(skip_all)]
    pub async fn handle(
        self: Arc<Self>,
        headers: HeaderMap,
        settings: BaseSettings,
        addr: SocketAddr,
        incoming_payload: RequestPayload,
    ) -> StatusCode {
        let domain: String = if settings.environment == Environment::Production {
            settings.analytics_domain.clone()
        } else {
            String::from("test.toddgriffin.me")
        };
        let outgoing_payload: EventPayload = EventPayload::builder(
            domain,
            PAGEVIEW_EVENT.to_string(),
            incoming_payload.url.clone(),
        )
        .referrer(incoming_payload.referrer.clone())
        .screen_width(incoming_payload.screen_width)
        .build();
        let real_client_ip: String = Self::resolve_true_client_ip_address(addr, headers);
        let headers: EventHeaders =
            EventHeaders::new(incoming_payload.user_agent.clone(), real_client_ip);
        info!(
            "Making Plausible Analytics calls with headers={:?} and body={:?}",
            headers.clone(),
            outgoing_payload.clone()
        );
        match self.plausible_client.event(headers, outgoing_payload).await {
            Ok(bytes) => {
                info!(
                    "Plausible Analytics call was a success: {}",
                    String::from_utf8_lossy(&bytes)
                );
                StatusCode::OK
            }
            Err(e) => {
                error!("Failed Plausible Analytics call: {}", e);
                StatusCode::INTERNAL_SERVER_ERROR
            }
        }
    }
    #[instrument(skip_all)]
    fn resolve_true_client_ip_address(socket_addr: SocketAddr, header_map: HeaderMap) -> String {
        let prioritized_headers: Vec<&str> = vec![
            "True-Client-IP",
            "CF-Connecting-IP",
            "X-Forwarded-For",
            "X-Real-IP",
            "Forwarded",
            "socket_addr",
        ];
        let prioritized_headers_values: HashMap<&str, Option<String>> = prioritized_headers.iter().map(|prioritized_header| {
            if *prioritized_header == "socket_addr" {
                return (*prioritized_header, Some(socket_addr.ip().to_string()));
            }
            let header_value: Option<String> = match header_map.get(*prioritized_header) {
                Some(header_value) => {
                    match header_value.to_str() {
                        Ok(header_value) => {
                            if *prioritized_header == "X-Forwarded-For" {
                                info!("full HTTP 'X-Forwarded-For' header IP list: {header_value}");
                                let parts: Vec<&str> = header_value.split(',').collect();
                                let x_forwarded_for_client_ip: Option<&&str> = parts.first();
                                if let Some(x_forwarded_for_client_ip) = x_forwarded_for_client_ip {
                                    let x_forwarded_for: String = (*x_forwarded_for_client_ip).to_string();
                                    Some(x_forwarded_for)
                                } else {
                                    None
                                }
                            } else {
                                Some(header_value.to_string())
                            }
                        }
                        Err(to_str_error) => {
                            error!("failed to parse HTTP '{prioritized_header}' header value: {to_str_error}");
                            None
                        }
                    }
                }
                None => {
                    None
                },
            };
            (*prioritized_header, header_value)
        }).collect();
        info!(
            "Client IP headers: {:?}",
            prioritized_headers_values.clone()
        );
        for prioritized_header in prioritized_headers {
            if let Some(Some(header_value)) = prioritized_headers_values.get(prioritized_header) {
                info!(
                    "chose HTTP '{prioritized_header}' header for true client IP: '{header_value}'"
                );
                return header_value.to_string();
            }
        }
        error!("failed to find any prioritized HTTP headers for true client IP address");
        prioritized_headers_values
            .get("socket_addr")
            .unwrap()
            .as_ref()
            .unwrap()
            .to_string()
    }
}