webserver_base/
axum_plausible_analytics.rs

1use axum::http::{HeaderMap, StatusCode};
2use plausible_rs::{EventHeaders, EventPayload, PAGEVIEW_EVENT, Plausible};
3use reqwest::Client;
4use serde::{Deserialize, Serialize};
5use std::net::SocketAddr;
6use std::sync::Arc;
7use tracing::{error, info, instrument, warn};
8
9use crate::{
10    base_settings::{BaseSettings, Environment},
11    ip::resolve_true_client_ip_address,
12};
13
14#[derive(Debug, Serialize, Deserialize)]
15pub struct RequestPayload {
16    pub user_agent: String,
17    pub url: String,
18    pub referrer: String,
19    pub screen_width: usize,
20}
21
22pub struct AxumPlausibleAnalyticsHandler {
23    plausible_client: Plausible,
24}
25
26impl AxumPlausibleAnalyticsHandler {
27    #[must_use]
28    #[instrument(skip_all)]
29    pub fn new_with_client(http_client: Client) -> Self {
30        Self {
31            plausible_client: Plausible::new_with_client(http_client),
32        }
33    }
34
35    #[instrument(skip_all)]
36    pub async fn handle(
37        self: Arc<Self>,
38        headers: HeaderMap,
39        settings: BaseSettings,
40        addr: SocketAddr,
41        incoming_payload: RequestPayload,
42    ) -> StatusCode {
43        // generate payload
44        let domain: String = if settings.environment == Environment::Production {
45            settings.analytics_domain.clone()
46        } else {
47            String::from("test.toddgriffin.me")
48        };
49        let outgoing_payload: EventPayload = EventPayload::builder(
50            domain,
51            PAGEVIEW_EVENT.to_string(),
52            incoming_payload.url.clone(),
53        )
54        .referrer(incoming_payload.referrer.clone())
55        .screen_width(incoming_payload.screen_width)
56        .build();
57
58        // generate headers
59        let real_client_ip: String = resolve_true_client_ip_address(addr, headers);
60        let headers: EventHeaders =
61            EventHeaders::new(incoming_payload.user_agent.clone(), real_client_ip);
62
63        info!(
64            "Making Plausible Analytics calls with headers={:?} and body={:?}",
65            headers.clone(),
66            outgoing_payload.clone()
67        );
68        // post 'pageview' event
69        match self.plausible_client.event(headers, outgoing_payload).await {
70            Ok(bytes) => {
71                info!(
72                    "Plausible Analytics call was a success: {}",
73                    String::from_utf8_lossy(&bytes)
74                );
75                StatusCode::OK
76            }
77            Err(e) => {
78                error!("Failed Plausible Analytics call: {}", e);
79                StatusCode::INTERNAL_SERVER_ERROR
80            }
81        }
82    }
83}