feat: Add real-time visitor map with IP geolocation
- Add latitude/longitude columns to visitor_logs model - Update visitor_service to fetch and store coordinates from ip-api.com - Add /admin/map-data API endpoint for map visualization - Create VisitorMap component using Leaflet/OpenStreetMap - Integrate map into visitor-stats admin page - 30-second auto-refresh with animation for new visitors - Color-coded markers (red: active, blue: recent) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,7 @@ from sqlalchemy import func
|
||||
from ..models.visitor import VisitorLog, VisitorDailyStats, VisitorSession
|
||||
|
||||
# IP Geolocation service (free, 45 req/min limit)
|
||||
IP_API_URL = "http://ip-api.com/json/{ip}?fields=status,country,countryCode,regionName,city"
|
||||
IP_API_URL = "http://ip-api.com/json/{ip}?fields=status,country,countryCode,regionName,city,lat,lon"
|
||||
|
||||
# Cache for IP geolocation results (in-memory, simple)
|
||||
_geo_cache: Dict[str, Dict] = {}
|
||||
@@ -77,7 +77,7 @@ async def get_geo_info(ip: str) -> Optional[Dict]:
|
||||
if ip.startswith(('127.', '192.168.', '10.', '172.16.', '172.17.', '172.18.', '172.19.',
|
||||
'172.20.', '172.21.', '172.22.', '172.23.', '172.24.', '172.25.',
|
||||
'172.26.', '172.27.', '172.28.', '172.29.', '172.30.', '172.31.', 'localhost', '::1')):
|
||||
return {"country": "Local", "country_code": "LO", "region": "", "city": ""}
|
||||
return {"country": "Local", "country_code": "LO", "region": "", "city": "", "latitude": None, "longitude": None}
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
@@ -94,6 +94,8 @@ async def get_geo_info(ip: str) -> Optional[Dict]:
|
||||
"country_code": data.get("countryCode", ""),
|
||||
"region": data.get("regionName", ""),
|
||||
"city": data.get("city", ""),
|
||||
"latitude": data.get("lat"),
|
||||
"longitude": data.get("lon"),
|
||||
}
|
||||
# Cache the result
|
||||
_geo_cache[ip] = result
|
||||
@@ -165,6 +167,8 @@ async def log_visit(
|
||||
country_code=geo_info.get("country_code"),
|
||||
city=geo_info.get("city"),
|
||||
region=geo_info.get("region"),
|
||||
latitude=geo_info.get("latitude"),
|
||||
longitude=geo_info.get("longitude"),
|
||||
utm_source=utm_source,
|
||||
utm_medium=utm_medium,
|
||||
utm_campaign=utm_campaign,
|
||||
|
||||
Reference in New Issue
Block a user