spawn_access_control/
geo_analyzer.rs1use maxminddb::{Reader, geoip2};
2use std::collections::HashMap;
3use chrono::{DateTime, Utc};
4use std::net::IpAddr;
5use serde::{Serialize, Deserialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct LocationInfo {
9 pub country: String,
10 pub city: Option<String>,
11 pub latitude: Option<f64>,
12 pub longitude: Option<f64>,
13 pub last_seen: DateTime<Utc>,
14 pub access_count: u32,
15}
16
17pub struct GeoAnalyzer {
18 db_reader: Reader<Vec<u8>>,
19 location_history: HashMap<IpAddr, LocationInfo>,
20}
21
22impl GeoAnalyzer {
23 pub fn new(db_path: &[u8]) -> Result<Self, maxminddb::MaxMindDBError> {
24 let db_bytes = db_path.to_vec();
25 Ok(Self {
26 db_reader: Reader::from_source(db_bytes)?,
27 location_history: HashMap::new(),
28 })
29 }
30
31 pub fn lookup_location(&self, ip: IpAddr) -> Option<LocationInfo> {
32 if let Ok(city) = self.db_reader.lookup::<geoip2::City>(ip) {
33 let country = city.country
34 .and_then(|c| c.names.clone())
35 .and_then(|n| n.get("en").map(|s| s.to_string()))?;
36
37 let city_name = city.city
38 .and_then(|c| c.names.clone())
39 .and_then(|n| n.get("en").map(|s| s.to_string()));
40
41 let coordinates = if let (Some(lat), Some(lon)) = (
42 city.location.as_ref().and_then(|l| l.latitude),
43 city.location.as_ref().and_then(|l| l.longitude)
44 ) {
45 Some((lat, lon))
46 } else {
47 None
48 };
49
50 Some(LocationInfo {
51 country,
52 city: city_name,
53 latitude: coordinates.map(|(lat, _)| lat),
54 longitude: coordinates.map(|(_, lon)| lon),
55 last_seen: Utc::now(),
56 access_count: 1,
57 })
58 } else {
59 None
60 }
61 }
62
63 pub fn update_location(&mut self, ip: IpAddr) -> Option<LocationInfo> {
64 if let Some(location) = self.lookup_location(ip) {
65 let entry = self.location_history.entry(ip).or_insert_with(|| location.clone());
66 entry.access_count += 1;
67 entry.last_seen = Utc::now();
68 Some(location)
69 } else {
70 None
71 }
72 }
73}