Imporve gateway auth for weather.py

This commit is contained in:
Urtzi Alfaro
2025-07-21 13:09:30 +02:00
parent 2ee5117d48
commit df7c6e1e00

View File

@@ -1,55 +1,68 @@
# ================================================================ # services/data/app/api/weather.py - UPDATED WITH UNIFIED AUTH
# services/data/app/api/weather.py - FIXED VERSION """Weather data API endpoints with unified authentication"""
# ================================================================
"""Weather data API endpoints with improved error handling"""
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query, BackgroundTasks
from sqlalchemy.ext.asyncio import AsyncSession from typing import List, Optional, Dict, Any
from typing import List, Optional from datetime import datetime, date
from datetime import datetime, timedelta
import structlog import structlog
from app.core.database import get_db from app.schemas.weather import (
from app.core.auth import get_current_user, AuthInfo WeatherDataResponse,
WeatherForecastResponse,
WeatherSummaryResponse
)
from app.services.weather_service import WeatherService from app.services.weather_service import WeatherService
from app.services.messaging import publish_weather_updated from app.services.messaging import publish_weather_updated
from app.schemas.external import (
WeatherDataResponse, # Import unified authentication from shared library
WeatherForecastResponse, from shared.auth.decorators import (
LocationRequest, get_current_user_dep,
DateRangeRequest get_current_tenant_id_dep
) )
router = APIRouter() router = APIRouter(prefix="/weather", tags=["weather"])
weather_service = WeatherService()
logger = structlog.get_logger() logger = structlog.get_logger()
@router.get("/current", response_model=WeatherDataResponse) @router.get("/current", response_model=WeatherDataResponse)
async def get_current_weather( async def get_current_weather(
latitude: float = Query(..., description="Latitude"), latitude: float = Query(..., description="Latitude"),
longitude: float = Query(..., description="Longitude"), longitude: float = Query(..., description="Longitude"),
current_user: AuthInfo = Depends(get_current_user) tenant_id: str = Depends(get_current_tenant_id_dep),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
): ):
"""Get current weather for location""" """Get current weather data for location"""
try: try:
logger.debug("API: Getting current weather", lat=latitude, lon=longitude) logger.debug("Getting current weather",
lat=latitude,
lon=longitude,
tenant_id=tenant_id,
user_id=current_user["user_id"])
weather_service = WeatherService()
weather = await weather_service.get_current_weather(latitude, longitude) weather = await weather_service.get_current_weather(latitude, longitude)
if not weather: if not weather:
logger.warning("No weather data available", lat=latitude, lon=longitude)
raise HTTPException(status_code=404, detail="Weather data not available") raise HTTPException(status_code=404, detail="Weather data not available")
logger.debug("Successfully returning weather data", temp=weather.temperature) # Publish event
try:
await publish_weather_updated({
"type": "current_weather_requested",
"tenant_id": tenant_id,
"latitude": latitude,
"longitude": longitude,
"requested_by": current_user["user_id"],
"timestamp": datetime.utcnow().isoformat()
})
except Exception as e:
logger.warning("Failed to publish weather event", error=str(e))
return weather return weather
except HTTPException: except HTTPException:
# Re-raise HTTP exceptions
raise raise
except Exception as e: except Exception as e:
logger.error("Unexpected error in weather API", error=str(e)) logger.error("Failed to get current weather", error=str(e))
import traceback
logger.error("Weather API traceback", traceback=traceback.format_exc())
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}") raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
@router.get("/forecast", response_model=List[WeatherForecastResponse]) @router.get("/forecast", response_model=List[WeatherForecastResponse])
@@ -57,127 +70,139 @@ async def get_weather_forecast(
latitude: float = Query(..., description="Latitude"), latitude: float = Query(..., description="Latitude"),
longitude: float = Query(..., description="Longitude"), longitude: float = Query(..., description="Longitude"),
days: int = Query(7, description="Number of forecast days", ge=1, le=14), days: int = Query(7, description="Number of forecast days", ge=1, le=14),
current_user: AuthInfo = Depends(get_current_user) tenant_id: str = Depends(get_current_tenant_id_dep),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
): ):
"""Get weather forecast for location""" """Get weather forecast for location"""
try: try:
logger.debug("API: Getting weather forecast", lat=latitude, lon=longitude, days=days) logger.debug("Getting weather forecast",
lat=latitude,
lon=longitude,
days=days,
tenant_id=tenant_id)
weather_service = WeatherService()
forecast = await weather_service.get_weather_forecast(latitude, longitude, days) forecast = await weather_service.get_weather_forecast(latitude, longitude, days)
if not forecast: if not forecast:
logger.warning("No forecast data available", lat=latitude, lon=longitude)
raise HTTPException(status_code=404, detail="Weather forecast not available") raise HTTPException(status_code=404, detail="Weather forecast not available")
# Publish event (with error handling) # Publish event
try: try:
await publish_weather_updated({ await publish_weather_updated({
"type": "forecast_requested", "type": "forecast_requested",
"tenant_id": tenant_id,
"latitude": latitude, "latitude": latitude,
"longitude": longitude, "longitude": longitude,
"days": days, "days": days,
"requested_by": current_user["user_id"],
"timestamp": datetime.utcnow().isoformat() "timestamp": datetime.utcnow().isoformat()
}) })
except Exception as pub_error: except Exception as e:
logger.warning("Failed to publish weather forecast event", error=str(pub_error)) logger.warning("Failed to publish forecast event", error=str(e))
# Continue processing - event publishing failure shouldn't break the API
logger.debug("Successfully returning forecast data", count=len(forecast))
return forecast return forecast
except HTTPException: except HTTPException:
# Re-raise HTTP exceptions
raise raise
except Exception as e: except Exception as e:
logger.error("Unexpected error in weather forecast API", error=str(e)) logger.error("Failed to get weather forecast", error=str(e))
import traceback
logger.error("Weather forecast API traceback", traceback=traceback.format_exc())
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}") raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
@router.get("/historical", response_model=List[WeatherDataResponse]) @router.get("/history", response_model=List[WeatherDataResponse])
async def get_historical_weather( async def get_weather_history(
start_date: date = Query(..., description="Start date"),
end_date: date = Query(..., description="End date"),
latitude: float = Query(..., description="Latitude"), latitude: float = Query(..., description="Latitude"),
longitude: float = Query(..., description="Longitude"), longitude: float = Query(..., description="Longitude"),
start_date: datetime = Query(..., description="Start date"), tenant_id: str = Depends(get_current_tenant_id_dep),
end_date: datetime = Query(..., description="End date"), current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db),
current_user: AuthInfo = Depends(get_current_user)
): ):
"""Get historical weather data""" """Get historical weather data"""
try: try:
# Validate date range logger.debug("Getting weather history",
if end_date <= start_date: start_date=start_date,
raise HTTPException(status_code=400, detail="End date must be after start date") end_date=end_date,
tenant_id=tenant_id)
if (end_date - start_date).days > 365: weather_service = WeatherService()
raise HTTPException(status_code=400, detail="Date range cannot exceed 365 days") history = await weather_service.get_weather_history(
latitude, longitude, start_date, end_date
historical_data = await weather_service.get_historical_weather(
latitude, longitude, start_date, end_date, db
) )
# Publish event (with error handling) return history
try:
await publish_weather_updated({
"type": "historical_requested",
"latitude": latitude,
"longitude": longitude,
"start_date": start_date.isoformat(),
"end_date": end_date.isoformat(),
"records_count": len(historical_data),
"timestamp": datetime.utcnow().isoformat()
})
except Exception as pub_error:
logger.warning("Failed to publish historical weather event", error=str(pub_error))
# Continue processing
return historical_data
except HTTPException:
# Re-raise HTTP exceptions
raise
except Exception as e: except Exception as e:
logger.error("Unexpected error in historical weather API", error=str(e)) logger.error("Failed to get weather history", error=str(e))
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}") raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
@router.post("/store") @router.get("/summary", response_model=WeatherSummaryResponse)
async def store_weather_data( async def get_weather_summary(
latitude: float = Query(..., description="Latitude"), location_id: Optional[str] = Query(None, description="Location ID"),
longitude: float = Query(..., description="Longitude"), days: int = Query(30, description="Number of days to summarize"),
db: AsyncSession = Depends(get_db), tenant_id: str = Depends(get_current_tenant_id_dep),
current_user: AuthInfo = Depends(get_current_user) current_user: Dict[str, Any] = Depends(get_current_user_dep),
): ):
"""Store current weather data to database""" """Get weather summary for tenant's location"""
try: try:
# Get current weather data logger.debug("Getting weather summary",
weather = await weather_service.get_current_weather(latitude, longitude) location_id=location_id,
days=days,
tenant_id=tenant_id)
if not weather: weather_service = WeatherService()
raise HTTPException(status_code=404, detail="No weather data to store")
# Convert to dict for storage # If no location_id provided, use tenant's default location
weather_dict = { if not location_id:
"date": weather.date, # This would typically fetch from tenant service
"temperature": weather.temperature, location_id = tenant_id # Simplified for example
"precipitation": weather.precipitation,
"humidity": weather.humidity,
"wind_speed": weather.wind_speed,
"pressure": weather.pressure,
"description": weather.description,
"source": weather.source
}
success = await weather_service.store_weather_data( summary = await weather_service.get_weather_summary(location_id, days)
latitude, longitude, weather_dict, db
return summary
except Exception as e:
logger.error("Failed to get weather summary", error=str(e))
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
@router.post("/sync")
async def sync_weather_data(
background_tasks: BackgroundTasks,
force: bool = Query(False, description="Force sync even if recently synced"),
tenant_id: str = Depends(get_current_tenant_id_dep),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
):
"""Manually trigger weather data synchronization"""
try:
logger.info("Weather sync requested",
tenant_id=tenant_id,
user_id=current_user["user_id"],
force=force)
weather_service = WeatherService()
# Check if user has permission to sync (could be admin only)
if current_user.get("role") not in ["admin", "manager"]:
raise HTTPException(
status_code=403,
detail="Insufficient permissions to sync weather data"
)
# Schedule background sync
background_tasks.add_task(
weather_service.sync_weather_data,
tenant_id=tenant_id,
force=force
) )
if success: return {
return {"status": "success", "message": "Weather data stored successfully"} "message": "Weather sync initiated",
else: "status": "processing",
raise HTTPException(status_code=500, detail="Failed to store weather data") "initiated_by": current_user["user_id"]
}
except HTTPException: except HTTPException:
raise raise
except Exception as e: except Exception as e:
logger.error("Error storing weather data", error=str(e)) logger.error("Failed to initiate weather sync", error=str(e))
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}") raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")