spawn_access_control/
geo_analyzer.rs

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
use maxminddb::{Reader, geoip2};
use std::collections::HashMap;
use chrono::{DateTime, Utc};
use std::net::IpAddr;
use serde::{Serialize, Deserialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LocationInfo {
    pub country: String,
    pub city: Option<String>,
    pub latitude: Option<f64>,
    pub longitude: Option<f64>,
    pub last_seen: DateTime<Utc>,
    pub access_count: u32,
}

pub struct GeoAnalyzer {
    db_reader: Reader<Vec<u8>>,
    location_history: HashMap<IpAddr, LocationInfo>,
}

impl GeoAnalyzer {
    pub fn new(db_path: &[u8]) -> Result<Self, maxminddb::MaxMindDBError> {
        let db_bytes = db_path.to_vec();
        Ok(Self {
            db_reader: Reader::from_source(db_bytes)?,
            location_history: HashMap::new(),
        })
    }

    pub fn lookup_location(&self, ip: IpAddr) -> Option<LocationInfo> {
        if let Ok(city) = self.db_reader.lookup::<geoip2::City>(ip) {
            let country = city.country
                .and_then(|c| c.names.clone())
                .and_then(|n| n.get("en").map(|s| s.to_string()))?;

            let city_name = city.city
                .and_then(|c| c.names.clone())
                .and_then(|n| n.get("en").map(|s| s.to_string()));

            let coordinates = if let (Some(lat), Some(lon)) = (
                city.location.as_ref().and_then(|l| l.latitude),
                city.location.as_ref().and_then(|l| l.longitude)
            ) {
                Some((lat, lon))
            } else {
                None
            };

            Some(LocationInfo {
                country,
                city: city_name,
                latitude: coordinates.map(|(lat, _)| lat),
                longitude: coordinates.map(|(_, lon)| lon),
                last_seen: Utc::now(),
                access_count: 1,
            })
        } else {
            None
        }
    }

    pub fn update_location(&mut self, ip: IpAddr) -> Option<LocationInfo> {
        if let Some(location) = self.lookup_location(ip) {
            let entry = self.location_history.entry(ip).or_insert_with(|| location.clone());
            entry.access_count += 1;
            entry.last_seen = Utc::now();
            Some(location)
        } else {
            None
        }
    }
}