Add role-based filtering and imporve code
This commit is contained in:
2
services/external/app/api/city_operations.py
vendored
2
services/external/app/api/city_operations.py
vendored
@@ -15,7 +15,7 @@ from app.schemas.traffic import TrafficDataResponse
|
||||
from app.registry.city_registry import CityRegistry
|
||||
from app.registry.geolocation_mapper import GeolocationMapper
|
||||
from app.repositories.city_data_repository import CityDataRepository
|
||||
from app.cache.redis_cache import ExternalDataCache
|
||||
from app.cache.redis_wrapper import ExternalDataCache
|
||||
from app.services.weather_service import WeatherService
|
||||
from app.services.traffic_service import TrafficService
|
||||
from shared.routing.route_builder import RouteBuilder
|
||||
|
||||
8
services/external/app/api/traffic_data.py
vendored
8
services/external/app/api/traffic_data.py
vendored
@@ -12,6 +12,8 @@ import structlog
|
||||
from app.schemas.traffic import TrafficDataResponse
|
||||
from app.services.traffic_service import TrafficService
|
||||
from shared.routing.route_builder import RouteBuilder
|
||||
from shared.auth.decorators import get_current_user_dep
|
||||
from shared.auth.access_control import analytics_tier_required
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.core.database import get_db
|
||||
|
||||
@@ -29,6 +31,7 @@ def get_traffic_service():
|
||||
route_builder.build_base_route("traffic-data"),
|
||||
response_model=List[TrafficDataResponse]
|
||||
)
|
||||
@analytics_tier_required
|
||||
async def list_traffic_data(
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
start_date: Optional[date] = Query(None),
|
||||
@@ -36,10 +39,11 @@ async def list_traffic_data(
|
||||
latitude: Optional[float] = Query(None),
|
||||
longitude: Optional[float] = Query(None),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
traffic_service: TrafficService = Depends(get_traffic_service)
|
||||
):
|
||||
"""List stored traffic data records"""
|
||||
"""List stored traffic data records (Professional+ tier required)"""
|
||||
try:
|
||||
logger.info("Listing traffic data", tenant_id=tenant_id)
|
||||
|
||||
@@ -64,9 +68,11 @@ async def list_traffic_data(
|
||||
route_builder.build_resource_detail_route("traffic-data", "traffic_id"),
|
||||
response_model=TrafficDataResponse
|
||||
)
|
||||
@analytics_tier_required
|
||||
async def get_traffic_data(
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
traffic_id: UUID = Path(..., description="Traffic data ID"),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
traffic_service: TrafficService = Depends(get_traffic_service)
|
||||
):
|
||||
|
||||
8
services/external/app/api/weather_data.py
vendored
8
services/external/app/api/weather_data.py
vendored
@@ -12,6 +12,8 @@ import structlog
|
||||
from app.schemas.weather import WeatherDataResponse
|
||||
from app.services.weather_service import WeatherService
|
||||
from shared.routing.route_builder import RouteBuilder
|
||||
from shared.auth.decorators import get_current_user_dep
|
||||
from shared.auth.access_control import analytics_tier_required
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.core.database import get_db
|
||||
|
||||
@@ -29,6 +31,7 @@ def get_weather_service():
|
||||
route_builder.build_base_route("weather-data"),
|
||||
response_model=List[WeatherDataResponse]
|
||||
)
|
||||
@analytics_tier_required
|
||||
async def list_weather_data(
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
start_date: Optional[date] = Query(None),
|
||||
@@ -36,10 +39,11 @@ async def list_weather_data(
|
||||
latitude: Optional[float] = Query(None),
|
||||
longitude: Optional[float] = Query(None),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
weather_service: WeatherService = Depends(get_weather_service)
|
||||
):
|
||||
"""List stored weather data records"""
|
||||
"""List stored weather data records (Professional+ tier required)"""
|
||||
try:
|
||||
logger.info("Listing weather data", tenant_id=tenant_id)
|
||||
|
||||
@@ -64,9 +68,11 @@ async def list_weather_data(
|
||||
route_builder.build_resource_detail_route("weather-data", "weather_id"),
|
||||
response_model=WeatherDataResponse
|
||||
)
|
||||
@analytics_tier_required
|
||||
async def get_weather_data(
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
weather_id: UUID = Path(..., description="Weather data ID"),
|
||||
current_user: dict = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
weather_service: WeatherService = Depends(get_weather_service)
|
||||
):
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
# services/external/app/cache/redis_cache.py
|
||||
# services/external/app/cache/redis_wrapper.py
|
||||
"""
|
||||
Redis cache layer for fast training data access
|
||||
Redis cache layer for fast training data access using shared Redis implementation
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Any, Optional
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
import structlog
|
||||
import redis.asyncio as redis
|
||||
|
||||
from app.core.config import settings
|
||||
from shared.redis_utils import get_redis_client
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
@@ -18,12 +16,11 @@ class ExternalDataCache:
|
||||
"""Redis cache for external data service"""
|
||||
|
||||
def __init__(self):
|
||||
self.redis_client = redis.from_url(
|
||||
settings.REDIS_URL,
|
||||
encoding="utf-8",
|
||||
decode_responses=True
|
||||
)
|
||||
self.ttl = 86400 * 7
|
||||
self.ttl = 86400 * 7 # 7 days
|
||||
|
||||
async def _get_client(self):
|
||||
"""Get the shared Redis client"""
|
||||
return await get_redis_client()
|
||||
|
||||
def _weather_cache_key(
|
||||
self,
|
||||
@@ -43,7 +40,8 @@ class ExternalDataCache:
|
||||
"""Get cached weather data"""
|
||||
try:
|
||||
key = self._weather_cache_key(city_id, start_date, end_date)
|
||||
cached = await self.redis_client.get(key)
|
||||
client = await self._get_client()
|
||||
cached = await client.get(key)
|
||||
|
||||
if cached:
|
||||
logger.debug("Weather cache hit", city_id=city_id, key=key)
|
||||
@@ -84,7 +82,8 @@ class ExternalDataCache:
|
||||
|
||||
serializable_data.append(record_dict)
|
||||
|
||||
await self.redis_client.setex(
|
||||
client = await self._get_client()
|
||||
await client.setex(
|
||||
key,
|
||||
self.ttl,
|
||||
json.dumps(serializable_data)
|
||||
@@ -113,7 +112,8 @@ class ExternalDataCache:
|
||||
"""Get cached traffic data"""
|
||||
try:
|
||||
key = self._traffic_cache_key(city_id, start_date, end_date)
|
||||
cached = await self.redis_client.get(key)
|
||||
client = await self._get_client()
|
||||
cached = await client.get(key)
|
||||
|
||||
if cached:
|
||||
logger.debug("Traffic cache hit", city_id=city_id, key=key)
|
||||
@@ -154,7 +154,8 @@ class ExternalDataCache:
|
||||
|
||||
serializable_data.append(record_dict)
|
||||
|
||||
await self.redis_client.setex(
|
||||
client = await self._get_client()
|
||||
await client.setex(
|
||||
key,
|
||||
self.ttl,
|
||||
json.dumps(serializable_data)
|
||||
@@ -168,11 +169,18 @@ class ExternalDataCache:
|
||||
async def invalidate_city_cache(self, city_id: str):
|
||||
"""Invalidate all cache entries for a city"""
|
||||
try:
|
||||
client = await self._get_client()
|
||||
pattern = f"*:{city_id}:*"
|
||||
async for key in self.redis_client.scan_iter(match=pattern):
|
||||
await self.redis_client.delete(key)
|
||||
|
||||
logger.info("City cache invalidated", city_id=city_id)
|
||||
# Use scan_iter for safer key pattern matching
|
||||
keys_to_delete = []
|
||||
async for key in client.scan_iter(match=pattern):
|
||||
keys_to_delete.append(key)
|
||||
|
||||
if keys_to_delete:
|
||||
await client.delete(*keys_to_delete)
|
||||
|
||||
logger.info("City cache invalidated", city_id=city_id, keys_deleted=len(keys_to_delete))
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Error invalidating cache", error=str(e))
|
||||
8
services/external/app/models/__init__.py
vendored
8
services/external/app/models/__init__.py
vendored
@@ -4,6 +4,13 @@ External Service Models Package
|
||||
Import all models to ensure they are registered with SQLAlchemy Base.
|
||||
"""
|
||||
|
||||
# Import AuditLog model for this service
|
||||
from shared.security import create_audit_log_model
|
||||
from shared.database.base import Base
|
||||
|
||||
# Create audit log model for this service
|
||||
AuditLog = create_audit_log_model(Base)
|
||||
|
||||
# Import all models to register them with the Base metadata
|
||||
from .traffic import (
|
||||
TrafficData,
|
||||
@@ -31,4 +38,5 @@ __all__ = [
|
||||
# City-based models (new)
|
||||
"CityWeatherData",
|
||||
"CityTrafficData",
|
||||
"AuditLog",
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user