| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- from __future__ import annotations
- import time
- from typing import List
- from fastapi import APIRouter
- from pydantic import BaseModel
- from app.db import get_pool
- START_TIME = time.time()
- router = APIRouter(tags=["stats"])
- # ---------- Pydantic models ----------
- class StatsOut(BaseModel):
- uptime_seconds: float
- total_hits: int
- active_ips: int
- avg_latency_ms: float
- class GeoDistributionItem(BaseModel):
- country: str
- count: int
- percentage: float
- class GeoPoint(BaseModel):
- latitude: float
- longitude: float
- country: str
- city: str
- hit_count: int
- # ---------- Endpoints ----------
- @router.get("/stats", response_model=StatsOut)
- async def get_stats() -> StatsOut:
- pool = get_pool()
- async with pool.acquire() as conn:
- total_hits: int = await conn.fetchval(
- "SELECT COUNT(*) FROM access_logs WHERE path LIKE '/api/public/prices%'"
- ) or 0
- active_ips: int = (
- await conn.fetchval(
- "SELECT COUNT(DISTINCT ip) FROM access_logs "
- "WHERE created_at > NOW() - INTERVAL '5 minutes'"
- )
- or 0
- )
- avg_latency = await conn.fetchval("SELECT AVG(latency_ms) FROM access_logs")
- return StatsOut(
- uptime_seconds=time.time() - START_TIME,
- total_hits=total_hits,
- active_ips=active_ips,
- avg_latency_ms=round(float(avg_latency), 2) if avg_latency is not None else 0.0,
- )
- @router.get("/geo/distribution", response_model=List[GeoDistributionItem])
- async def get_geo_distribution() -> List[GeoDistributionItem]:
- pool = get_pool()
- async with pool.acquire() as conn:
- rows = await conn.fetch(
- "SELECT country, COUNT(*) AS cnt FROM access_logs "
- "GROUP BY country ORDER BY cnt DESC"
- )
- total = sum(r["cnt"] for r in rows)
- return [
- GeoDistributionItem(
- country=row["country"],
- count=row["cnt"],
- percentage=round(row["cnt"] / total * 100, 2) if total else 0.0,
- )
- for row in rows
- ]
- @router.get("/prices/top-ips", response_model=List[dict])
- async def get_top_price_ips() -> List[dict]:
- pool = get_pool()
- async with pool.acquire() as conn:
- rows = await conn.fetch(
- """
- SELECT ip, COUNT(*) AS hit_count
- FROM access_logs
- WHERE path LIKE '/api/public/prices%'
- GROUP BY ip
- ORDER BY hit_count DESC
- LIMIT 20
- """
- )
- total = sum(r["hit_count"] for r in rows) or 1
- return [
- {
- "ip": r["ip"],
- "hit_count": r["hit_count"],
- "percentage": round(r["hit_count"] / total * 100, 2),
- }
- for r in rows
- ]
- @router.get("/geo/points", response_model=List[GeoPoint])
- async def get_geo_points() -> List[GeoPoint]:
- pool = get_pool()
- async with pool.acquire() as conn:
- rows = await conn.fetch(
- "SELECT latitude, longitude, country, city, COUNT(*) AS hit_count "
- "FROM access_logs "
- "WHERE latitude IS NOT NULL AND longitude IS NOT NULL "
- "GROUP BY latitude, longitude, country, city "
- "ORDER BY hit_count DESC "
- "LIMIT 1000"
- )
- return [
- GeoPoint(
- latitude=row["latitude"],
- longitude=row["longitude"],
- country=row["country"],
- city=row["city"],
- hit_count=row["hit_count"],
- )
- for row in rows
- ]
|