- Frontend: Next.js 14 with TypeScript - Backend: FastAPI with SQLAlchemy - Agent: Carmodoo sync agent - Deployment: Docker Compose based staging/production setup - Scripts: Automated deployment with rollback support 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
112 lines
3.9 KiB
Python
112 lines
3.9 KiB
Python
"""
|
|
Visitor tracking models for analytics
|
|
"""
|
|
from sqlalchemy import Column, Integer, String, DateTime, Text, Index
|
|
from sqlalchemy.sql import func
|
|
from ..database import Base
|
|
|
|
|
|
class VisitorLog(Base):
|
|
"""
|
|
Raw visitor log - tracks every page visit
|
|
IP addresses are hashed for privacy
|
|
"""
|
|
__tablename__ = "visitor_logs"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
|
|
# Visitor identification (hashed for privacy)
|
|
visitor_hash = Column(String(64), nullable=False, index=True) # SHA256 hash of IP + User-Agent
|
|
ip_hash = Column(String(64), nullable=False) # SHA256 hash of IP only
|
|
|
|
# Session tracking
|
|
session_id = Column(String(64), nullable=True, index=True) # Cookie-based session ID
|
|
user_id = Column(Integer, nullable=True, index=True) # If logged in
|
|
|
|
# Page information
|
|
page_path = Column(String(500), nullable=False, index=True)
|
|
page_title = Column(String(200), nullable=True)
|
|
referrer = Column(String(1000), nullable=True)
|
|
referrer_domain = Column(String(200), nullable=True, index=True)
|
|
|
|
# Device information
|
|
device_type = Column(String(20), nullable=True, index=True) # mobile, desktop, tablet
|
|
browser = Column(String(50), nullable=True, index=True)
|
|
browser_version = Column(String(20), nullable=True)
|
|
os = Column(String(50), nullable=True)
|
|
os_version = Column(String(20), nullable=True)
|
|
|
|
# Geographic information (from IP geolocation)
|
|
country = Column(String(50), nullable=True, index=True)
|
|
country_code = Column(String(5), nullable=True)
|
|
city = Column(String(100), nullable=True)
|
|
region = Column(String(100), nullable=True)
|
|
|
|
# UTM parameters
|
|
utm_source = Column(String(100), nullable=True)
|
|
utm_medium = Column(String(100), nullable=True)
|
|
utm_campaign = Column(String(100), nullable=True)
|
|
|
|
# Timestamp
|
|
visited_at = Column(DateTime(timezone=True), server_default=func.now(), index=True)
|
|
|
|
|
|
class VisitorDailyStats(Base):
|
|
"""
|
|
Aggregated daily statistics for faster queries
|
|
Pre-computed by a scheduled task
|
|
"""
|
|
__tablename__ = "visitor_daily_stats"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
stat_date = Column(String(10), nullable=False, unique=True, index=True) # YYYY-MM-DD
|
|
|
|
# Visitor counts
|
|
total_visits = Column(Integer, default=0)
|
|
unique_visitors = Column(Integer, default=0)
|
|
|
|
# Device breakdown (JSON string)
|
|
device_breakdown = Column(Text) # {"mobile": 100, "desktop": 200, "tablet": 20}
|
|
|
|
# Browser breakdown (JSON string)
|
|
browser_breakdown = Column(Text) # {"Chrome": 150, "Safari": 100, ...}
|
|
|
|
# Country breakdown (JSON string)
|
|
country_breakdown = Column(Text) # {"MN": 200, "RU": 50, "KR": 30}
|
|
|
|
# Top pages (JSON string)
|
|
top_pages = Column(Text) # [{"path": "/", "views": 500}, ...]
|
|
|
|
# Top referrers (JSON string)
|
|
top_referrers = Column(Text) # [{"domain": "google.com", "visits": 100}, ...]
|
|
|
|
# Timestamps
|
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
|
|
class VisitorSession(Base):
|
|
"""
|
|
Track visitor sessions for better analytics
|
|
"""
|
|
__tablename__ = "visitor_sessions"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
session_id = Column(String(64), unique=True, nullable=False, index=True)
|
|
visitor_hash = Column(String(64), nullable=False, index=True)
|
|
user_id = Column(Integer, nullable=True)
|
|
|
|
# Session info
|
|
first_page = Column(String(500))
|
|
last_page = Column(String(500))
|
|
page_count = Column(Integer, default=1)
|
|
|
|
# Device/geo info (copied from first visit)
|
|
device_type = Column(String(20))
|
|
browser = Column(String(50))
|
|
country = Column(String(50))
|
|
|
|
# Timestamps
|
|
started_at = Column(DateTime(timezone=True), server_default=func.now(), index=True)
|
|
last_activity_at = Column(DateTime(timezone=True), server_default=func.now())
|