File size: 2,958 Bytes
d90101d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
use std::collections::HashMap;
use std::time::Duration;

use chrono::NaiveDateTime;
use http::header::{HeaderName, HeaderValue};
use reqwest::Client;
use serde::Serialize;
use serde_json::Value;

use super::super::Result;
use super::Collect;
use crate::Event;

pub struct Tracker {
    api_secret: &'static str,
    client: Client,
}

impl Tracker {
    pub fn new(api_secret: &'static str) -> Self {
        // Configure HTTP client with connection pooling similar to forge_provider
        let client = Client::builder()
            .connect_timeout(Duration::from_secs(10))
            .read_timeout(Duration::from_secs(30))
            .pool_idle_timeout(Duration::from_secs(90))
            .pool_max_idle_per_host(5)
            .build()
            .expect("Failed to build HTTP client for PostHog tracker");

        Self { api_secret, client }
    }
}

#[derive(Debug, Serialize)]
struct Payload {
    api_key: String,
    event: String,
    distinct_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    properties: Option<HashMap<String, serde_json::Value>>,
    #[serde(rename = "$set", skip_serializing_if = "Option::is_none")]
    set: Option<serde_json::Value>,
    timestamp: Option<NaiveDateTime>,
}

impl Payload {
    fn new(api_key: String, mut input: Event) -> Self {
        let mut properties = HashMap::new();
        let distinct_id = input.client_id.to_string();
        let event = input.event_name.to_string();
        let mut set = None;
        if let Some(identity) = input.identity.take()
            && let Ok(value) = serde_json::to_value(identity)
        {
            set = Some(value);
        }

        if let Ok(Value::Object(map)) = serde_json::to_value(input) {
            for (key, value) in map {
                properties.insert(key, value);
            }
        }

        Self {
            api_key,
            event,
            distinct_id,
            properties: Some(properties),
            set,
            timestamp: Some(chrono::Utc::now().naive_utc()),
        }
    }
}

impl Tracker {
    fn create_request(&self, event: Event) -> Result<reqwest::Request> {
        let url = reqwest::Url::parse("https://us.i.posthog.com/capture/")?;
        let mut request = reqwest::Request::new(reqwest::Method::POST, url);
        request.headers_mut().insert(
            HeaderName::from_static("content-type"),
            HeaderValue::from_static("application/json"),
        );

        let payload = Payload::new(self.api_secret.to_string(), event);

        let _ = request
            .body_mut()
            .insert(reqwest::Body::from(serde_json::to_string(&payload)?));

        Ok(request)
    }
}

#[async_trait::async_trait]
impl Collect for Tracker {
    // TODO: move http request to a dispatch
    async fn collect(&self, event: Event) -> Result<()> {
        let request = self.create_request(event)?;
        self.client.execute(request).await?;

        Ok(())
    }
}