REFACTOR API gateway
This commit is contained in:
@@ -21,7 +21,7 @@ from app.core.security import SecurityManager
|
||||
from shared.monitoring.decorators import track_execution_time
|
||||
|
||||
logger = structlog.get_logger()
|
||||
router = APIRouter()
|
||||
router = APIRouter(tags=["authentication"])
|
||||
security = HTTPBearer(auto_error=False)
|
||||
|
||||
def get_metrics_collector(request: Request):
|
||||
|
||||
@@ -21,7 +21,7 @@ from shared.auth.decorators import (
|
||||
)
|
||||
|
||||
logger = structlog.get_logger()
|
||||
router = APIRouter()
|
||||
router = APIRouter(tags=["users"])
|
||||
|
||||
@router.get("/me", response_model=UserResponse)
|
||||
async def get_current_user_info(
|
||||
@@ -77,46 +77,4 @@ async def update_current_user(
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to update user"
|
||||
)
|
||||
|
||||
@router.post("/change-password")
|
||||
async def change_password(
|
||||
password_data: PasswordChange,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Change user password"""
|
||||
try:
|
||||
await UserService.change_password(
|
||||
current_user.id,
|
||||
password_data.current_password,
|
||||
password_data.new_password,
|
||||
db
|
||||
)
|
||||
return {"message": "Password changed successfully"}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Password change error: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to change password"
|
||||
)
|
||||
|
||||
@router.delete("/me")
|
||||
async def delete_current_user(
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Delete current user account"""
|
||||
try:
|
||||
await UserService.delete_user(current_user.id, db)
|
||||
return {"message": "User account deleted successfully"}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Delete user error: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to delete user account"
|
||||
)
|
||||
@@ -1,13 +1,13 @@
|
||||
# ================================================================
|
||||
# services/data/app/api/sales.py - UPDATED WITH UNIFIED AUTH
|
||||
# services/data/app/api/sales.py - FIXED FOR NEW TENANT-SCOPED ARCHITECTURE
|
||||
# ================================================================
|
||||
"""Sales data API endpoints with unified authentication"""
|
||||
"""Sales data API endpoints with tenant-scoped URLs"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, Query, Response
|
||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, Query, Response, Path
|
||||
from fastapi.responses import StreamingResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing import List, Optional, Dict, Any
|
||||
import uuid
|
||||
from uuid import UUID
|
||||
from datetime import datetime
|
||||
import base64
|
||||
import structlog
|
||||
@@ -31,22 +31,23 @@ from app.services.messaging import (
|
||||
)
|
||||
|
||||
# Import unified authentication from shared library
|
||||
from shared.auth.decorators import (
|
||||
get_current_user_dep,
|
||||
get_current_tenant_id_dep
|
||||
)
|
||||
from shared.auth.decorators import get_current_user_dep
|
||||
|
||||
router = APIRouter(tags=["sales"])
|
||||
logger = structlog.get_logger()
|
||||
|
||||
@router.post("/", response_model=SalesDataResponse)
|
||||
# ================================================================
|
||||
# TENANT-SCOPED SALES ENDPOINTS
|
||||
# ================================================================
|
||||
|
||||
@router.post("/tenants/{tenant_id}/sales", response_model=SalesDataResponse)
|
||||
async def create_sales_record(
|
||||
sales_data: SalesDataCreate,
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Create a new sales record"""
|
||||
"""Create a new sales record for tenant"""
|
||||
try:
|
||||
logger.debug("Creating sales record",
|
||||
product=sales_data.product_name,
|
||||
@@ -54,7 +55,7 @@ async def create_sales_record(
|
||||
tenant_id=tenant_id,
|
||||
user_id=current_user["user_id"])
|
||||
|
||||
# Override tenant_id from token/header
|
||||
# Override tenant_id from URL path (gateway already verified access)
|
||||
sales_data.tenant_id = tenant_id
|
||||
|
||||
record = await SalesService.create_sales_record(sales_data, db)
|
||||
@@ -85,14 +86,14 @@ async def create_sales_record(
|
||||
tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to create sales record: {str(e)}")
|
||||
|
||||
@router.post("/bulk", response_model=List[SalesDataResponse])
|
||||
@router.post("/tenants/{tenant_id}/sales/bulk", response_model=List[SalesDataResponse])
|
||||
async def create_bulk_sales(
|
||||
sales_data: List[SalesDataCreate],
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Create multiple sales records"""
|
||||
"""Create multiple sales records for tenant"""
|
||||
try:
|
||||
logger.debug("Creating bulk sales records",
|
||||
count=len(sales_data),
|
||||
@@ -127,16 +128,16 @@ async def create_bulk_sales(
|
||||
tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to create bulk sales records: {str(e)}")
|
||||
|
||||
@router.get("/", response_model=List[SalesDataResponse])
|
||||
@router.get("/tenants/{tenant_id}/sales", response_model=List[SalesDataResponse])
|
||||
async def get_sales_data(
|
||||
start_date: Optional[datetime] = Query(None),
|
||||
end_date: Optional[datetime] = Query(None),
|
||||
product_name: Optional[str] = Query(None),
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
start_date: Optional[datetime] = Query(None, description="Start date filter"),
|
||||
end_date: Optional[datetime] = Query(None, description="End date filter"),
|
||||
product_name: Optional[str] = Query(None, description="Product name filter"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get sales data with filters"""
|
||||
"""Get sales data for tenant with filters"""
|
||||
try:
|
||||
logger.debug("Querying sales data",
|
||||
tenant_id=tenant_id,
|
||||
@@ -164,15 +165,15 @@ async def get_sales_data(
|
||||
tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to query sales data: {str(e)}")
|
||||
|
||||
@router.post("/import", response_model=SalesImportResult)
|
||||
@router.post("/tenants/{tenant_id}/sales/import", response_model=SalesImportResult)
|
||||
async def import_sales_data(
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
file: UploadFile = File(...),
|
||||
file_format: str = Form(...),
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Import sales data from file"""
|
||||
"""Import sales data from file for tenant"""
|
||||
try:
|
||||
logger.info("Importing sales data",
|
||||
tenant_id=tenant_id,
|
||||
@@ -220,26 +221,27 @@ async def import_sales_data(
|
||||
tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to import sales data: {str(e)}")
|
||||
|
||||
@router.post("/import/validate", response_model=SalesValidationResult)
|
||||
@router.post("/tenants/{tenant_id}/sales/import/validate", response_model=SalesValidationResult)
|
||||
async def validate_import_data(
|
||||
import_data: SalesDataImport,
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep)
|
||||
):
|
||||
"""Validate import data before processing"""
|
||||
"""Validate import data - Gateway already verified tenant access"""
|
||||
try:
|
||||
logger.debug("Validating import data", tenant_id=tenant_id)
|
||||
logger.debug("Validating import data",
|
||||
tenant_id=tenant_id,
|
||||
user_id=current_user["user_id"])
|
||||
|
||||
# Override tenant_id
|
||||
# Set tenant context from URL path
|
||||
import_data.tenant_id = tenant_id
|
||||
|
||||
validation = await DataImportService.validate_import_data(
|
||||
import_data.model_dump()
|
||||
)
|
||||
validation = await DataImportService.validate_import_data(import_data.model_dump())
|
||||
|
||||
logger.debug("Validation completed",
|
||||
is_valid=validation.get("is_valid", False),
|
||||
tenant_id=tenant_id)
|
||||
|
||||
return validation
|
||||
|
||||
except Exception as e:
|
||||
@@ -248,15 +250,17 @@ async def validate_import_data(
|
||||
tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to validate import data: {str(e)}")
|
||||
|
||||
@router.get("/import/template/{format_type}")
|
||||
@router.get("/tenants/{tenant_id}/sales/import/template/{format_type}")
|
||||
async def get_import_template(
|
||||
format_type: str,
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
format_type: str = Path(..., description="Template format: csv, json, excel"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep)
|
||||
):
|
||||
"""Get import template for specified format"""
|
||||
try:
|
||||
logger.debug("Getting import template",
|
||||
format=format_type,
|
||||
tenant_id=tenant_id,
|
||||
user_id=current_user["user_id"])
|
||||
|
||||
template = await DataImportService.get_import_template(format_type)
|
||||
@@ -265,7 +269,9 @@ async def get_import_template(
|
||||
logger.warning("Template generation error", error=template["error"])
|
||||
raise HTTPException(status_code=400, detail=template["error"])
|
||||
|
||||
logger.debug("Template generated successfully", format=format_type)
|
||||
logger.debug("Template generated successfully",
|
||||
format=format_type,
|
||||
tenant_id=tenant_id)
|
||||
|
||||
if format_type.lower() == "csv":
|
||||
return Response(
|
||||
@@ -291,14 +297,16 @@ async def get_import_template(
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Failed to generate import template", error=str(e))
|
||||
logger.error("Failed to generate import template",
|
||||
error=str(e),
|
||||
tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to generate template: {str(e)}")
|
||||
|
||||
@router.get("/analytics")
|
||||
@router.get("/tenants/{tenant_id}/sales/analytics")
|
||||
async def get_sales_analytics(
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
start_date: Optional[datetime] = Query(None, description="Start date"),
|
||||
end_date: Optional[datetime] = Query(None, description="End date"),
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
@@ -322,17 +330,17 @@ async def get_sales_analytics(
|
||||
tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to generate analytics: {str(e)}")
|
||||
|
||||
@router.post("/export")
|
||||
@router.post("/tenants/{tenant_id}/sales/export")
|
||||
async def export_sales_data(
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
export_format: str = Query("csv", description="Export format: csv, excel, json"),
|
||||
start_date: Optional[datetime] = Query(None, description="Start date"),
|
||||
end_date: Optional[datetime] = Query(None, description="End date"),
|
||||
products: Optional[List[str]] = Query(None, description="Filter by products"),
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Export sales data in specified format"""
|
||||
"""Export sales data in specified format for tenant"""
|
||||
try:
|
||||
logger.info("Exporting sales data",
|
||||
tenant_id=tenant_id,
|
||||
@@ -376,14 +384,14 @@ async def export_sales_data(
|
||||
tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to export sales data: {str(e)}")
|
||||
|
||||
@router.delete("/{record_id}")
|
||||
@router.delete("/tenants/{tenant_id}/sales/{record_id}")
|
||||
async def delete_sales_record(
|
||||
record_id: str,
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
record_id: str = Path(..., description="Sales record ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Delete a sales record"""
|
||||
"""Delete a sales record for tenant"""
|
||||
try:
|
||||
logger.info("Deleting sales record",
|
||||
record_id=record_id,
|
||||
@@ -413,14 +421,14 @@ async def delete_sales_record(
|
||||
tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to delete sales record: {str(e)}")
|
||||
|
||||
@router.get("/summary")
|
||||
@router.get("/tenants/{tenant_id}/sales/summary")
|
||||
async def get_sales_summary(
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
period: str = Query("daily", description="Summary period: daily, weekly, monthly"),
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get sales summary for specified period"""
|
||||
"""Get sales summary for specified period for tenant"""
|
||||
try:
|
||||
logger.debug("Getting sales summary",
|
||||
tenant_id=tenant_id,
|
||||
@@ -437,13 +445,13 @@ async def get_sales_summary(
|
||||
tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to generate summary: {str(e)}")
|
||||
|
||||
@router.get("/products")
|
||||
@router.get("/tenants/{tenant_id}/sales/products")
|
||||
async def get_products_list(
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get list of all products with sales data"""
|
||||
"""Get list of all products with sales data for tenant"""
|
||||
try:
|
||||
logger.debug("Getting products list", tenant_id=tenant_id)
|
||||
|
||||
@@ -458,4 +466,78 @@ async def get_products_list(
|
||||
logger.error("Failed to get products list",
|
||||
error=str(e),
|
||||
tenant_id=tenant_id)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to get products list: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Failed to get products list: {str(e)}")
|
||||
|
||||
@router.get("/tenants/{tenant_id}/sales/{record_id}", response_model=SalesDataResponse)
|
||||
async def get_sales_record(
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
record_id: str = Path(..., description="Sales record ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Get a specific sales record for tenant"""
|
||||
try:
|
||||
logger.debug("Getting sales record",
|
||||
record_id=record_id,
|
||||
tenant_id=tenant_id)
|
||||
|
||||
record = await SalesService.get_sales_record(record_id, db)
|
||||
|
||||
if not record or record.tenant_id != tenant_id:
|
||||
raise HTTPException(status_code=404, detail="Sales record not found")
|
||||
|
||||
logger.debug("Sales record retrieved",
|
||||
record_id=record_id,
|
||||
tenant_id=tenant_id)
|
||||
return record
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Failed to get sales record",
|
||||
error=str(e),
|
||||
tenant_id=tenant_id,
|
||||
record_id=record_id)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to get sales record: {str(e)}")
|
||||
|
||||
@router.put("/tenants/{tenant_id}/sales/{record_id}", response_model=SalesDataResponse)
|
||||
async def update_sales_record(
|
||||
sales_data: SalesDataCreate,
|
||||
record_id: str = Path(..., description="Sales record ID"),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Update a sales record for tenant"""
|
||||
try:
|
||||
logger.info("Updating sales record",
|
||||
record_id=record_id,
|
||||
tenant_id=tenant_id,
|
||||
user_id=current_user["user_id"])
|
||||
|
||||
# Verify record exists and belongs to tenant
|
||||
existing_record = await SalesService.get_sales_record(record_id, db)
|
||||
if not existing_record or existing_record.tenant_id != tenant_id:
|
||||
raise HTTPException(status_code=404, detail="Sales record not found")
|
||||
|
||||
# Override tenant_id from URL path
|
||||
sales_data.tenant_id = tenant_id
|
||||
|
||||
updated_record = await SalesService.update_sales_record(record_id, sales_data, db)
|
||||
|
||||
if not updated_record:
|
||||
raise HTTPException(status_code=404, detail="Sales record not found")
|
||||
|
||||
logger.info("Sales record updated successfully",
|
||||
record_id=record_id,
|
||||
tenant_id=tenant_id)
|
||||
return updated_record
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error("Failed to update sales record",
|
||||
error=str(e),
|
||||
tenant_id=tenant_id,
|
||||
record_id=record_id)
|
||||
raise HTTPException(status_code=500, detail=f"Failed to update sales record: {str(e)}")
|
||||
@@ -3,11 +3,12 @@
|
||||
# ================================================================
|
||||
"""Traffic data API endpoints with improved error handling"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Path
|
||||
from typing import List, Dict, Any
|
||||
from datetime import datetime, timedelta
|
||||
import structlog
|
||||
from uuid import UUID
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.services.traffic_service import TrafficService
|
||||
@@ -23,14 +24,15 @@ from shared.auth.decorators import (
|
||||
get_current_tenant_id_dep
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
router = APIRouter(tags=["traffic"])
|
||||
traffic_service = TrafficService()
|
||||
logger = structlog.get_logger()
|
||||
|
||||
@router.get("/current", response_model=TrafficDataResponse)
|
||||
@router.get("/tenants/{tenant_id}/current", response_model=TrafficDataResponse)
|
||||
async def get_current_traffic(
|
||||
latitude: float = Query(..., description="Latitude"),
|
||||
longitude: float = Query(..., description="Longitude"),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
):
|
||||
"""Get current traffic data for location"""
|
||||
@@ -69,13 +71,14 @@ async def get_current_traffic(
|
||||
logger.error("Traffic API traceback", traceback=traceback.format_exc())
|
||||
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
||||
|
||||
@router.get("/historical", response_model=List[TrafficDataResponse])
|
||||
@router.get("/tenants/{tenant_id}/historical", response_model=List[TrafficDataResponse])
|
||||
async def get_historical_traffic(
|
||||
latitude: float = Query(..., description="Latitude"),
|
||||
longitude: float = Query(..., description="Longitude"),
|
||||
start_date: datetime = Query(..., description="Start date"),
|
||||
end_date: datetime = Query(..., description="End date"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
):
|
||||
"""Get historical traffic data"""
|
||||
@@ -115,11 +118,12 @@ async def get_historical_traffic(
|
||||
logger.error("Unexpected error in historical traffic API", error=str(e))
|
||||
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
||||
|
||||
@router.post("/store")
|
||||
@router.post("/tenants/{tenant_id}/store")
|
||||
async def store_traffic_data(
|
||||
latitude: float = Query(..., description="Latitude"),
|
||||
longitude: float = Query(..., description="Longitude"),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep)
|
||||
):
|
||||
"""Store current traffic data to database"""
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# services/data/app/api/weather.py - UPDATED WITH UNIFIED AUTH
|
||||
"""Weather data API endpoints with unified authentication"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, BackgroundTasks
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, BackgroundTasks, Path
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import datetime, date
|
||||
import structlog
|
||||
from uuid import UUID
|
||||
|
||||
from app.schemas.external import (
|
||||
WeatherDataResponse,
|
||||
@@ -19,14 +20,14 @@ from shared.auth.decorators import (
|
||||
get_current_tenant_id_dep
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/weather", tags=["weather"])
|
||||
router = APIRouter(tags=["weather"])
|
||||
logger = structlog.get_logger()
|
||||
|
||||
@router.get("/current", response_model=WeatherDataResponse)
|
||||
@router.get("/tenants/{tenant_id}/current", response_model=WeatherDataResponse)
|
||||
async def get_current_weather(
|
||||
latitude: float = Query(..., description="Latitude"),
|
||||
longitude: float = Query(..., description="Longitude"),
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
):
|
||||
"""Get current weather data for location"""
|
||||
@@ -64,12 +65,12 @@ async def get_current_weather(
|
||||
logger.error("Failed to get current weather", error=str(e))
|
||||
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
||||
|
||||
@router.get("/forecast", response_model=List[WeatherForecastResponse])
|
||||
@router.get("/tenants/{tenant_id}/forecast", response_model=List[WeatherForecastResponse])
|
||||
async def get_weather_forecast(
|
||||
latitude: float = Query(..., description="Latitude"),
|
||||
longitude: float = Query(..., description="Longitude"),
|
||||
days: int = Query(7, description="Number of forecast days", ge=1, le=14),
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
):
|
||||
"""Get weather forecast for location"""
|
||||
@@ -108,13 +109,13 @@ async def get_weather_forecast(
|
||||
logger.error("Failed to get weather forecast", error=str(e))
|
||||
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
||||
|
||||
@router.get("/history", response_model=List[WeatherDataResponse])
|
||||
@router.get("/tenants/{tenant_id}/history", response_model=List[WeatherDataResponse])
|
||||
async def get_weather_history(
|
||||
start_date: date = Query(..., description="Start date"),
|
||||
end_date: date = Query(..., description="End date"),
|
||||
latitude: float = Query(..., description="Latitude"),
|
||||
longitude: float = Query(..., description="Longitude"),
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep)
|
||||
tenant_id: str = Path(..., description="Tenant ID")
|
||||
):
|
||||
"""Get historical weather data"""
|
||||
try:
|
||||
@@ -134,11 +135,11 @@ async def get_weather_history(
|
||||
logger.error("Failed to get weather history", error=str(e))
|
||||
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
||||
|
||||
@router.post("/sync")
|
||||
@router.post("/tenants/{tenant_id}/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),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
):
|
||||
"""Manually trigger weather data synchronization"""
|
||||
|
||||
@@ -108,9 +108,9 @@ app.add_middleware(
|
||||
)
|
||||
|
||||
# Include routers
|
||||
app.include_router(sales_router, prefix="/api/v1/sales", tags=["sales"])
|
||||
app.include_router(weather_router, prefix="/api/v1/weather", tags=["weather"])
|
||||
app.include_router(traffic_router, prefix="/api/v1/traffic", tags=["traffic"])
|
||||
app.include_router(sales_router, prefix="/api/v1", tags=["sales"])
|
||||
app.include_router(weather_router, prefix="/api/v1", tags=["weather"])
|
||||
app.include_router(traffic_router, prefix="/api/v1", tags=["traffic"])
|
||||
|
||||
# Health check endpoint
|
||||
@app.get("/health")
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
Tenant API endpoints
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Request
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Path
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing import List, Dict, Any
|
||||
import structlog
|
||||
from uuid import UUID
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.schemas.tenants import (
|
||||
@@ -45,8 +46,8 @@ async def register_bakery(
|
||||
|
||||
@router.get("/tenants/{tenant_id}/access/{user_id}", response_model=TenantAccessResponse)
|
||||
async def verify_tenant_access(
|
||||
tenant_id: str,
|
||||
user_id: str,
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""Verify if user has access to tenant - Called by Gateway"""
|
||||
@@ -62,7 +63,7 @@ async def verify_tenant_access(
|
||||
detail="Access verification failed"
|
||||
)
|
||||
|
||||
@router.get("/users/{user_id}/tenants", response_model=List[TenantResponse])
|
||||
@router.get("/tenants/users/{user_id}", response_model=List[TenantResponse])
|
||||
async def get_user_tenants(
|
||||
user_id: str,
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
@@ -89,7 +90,7 @@ async def get_user_tenants(
|
||||
|
||||
@router.get("/tenants/{tenant_id}", response_model=TenantResponse)
|
||||
async def get_tenant(
|
||||
tenant_id: str,
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
@@ -113,8 +114,8 @@ async def get_tenant(
|
||||
|
||||
@router.put("/tenants/{tenant_id}", response_model=TenantResponse)
|
||||
async def update_tenant(
|
||||
tenant_id: str,
|
||||
update_data: TenantUpdate,
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
@@ -134,9 +135,9 @@ async def update_tenant(
|
||||
|
||||
@router.post("/tenants/{tenant_id}/members", response_model=TenantMemberResponse)
|
||||
async def add_team_member(
|
||||
tenant_id: str,
|
||||
user_id: str,
|
||||
role: str,
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
|
||||
@@ -89,7 +89,7 @@ async def health_check():
|
||||
@app.get("/metrics")
|
||||
async def metrics():
|
||||
"""Prometheus metrics endpoint"""
|
||||
return metrics_collector.generate_latest()
|
||||
return metrics_collector.get_metrics()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
# ================================================================
|
||||
"""Training API endpoints with unified authentication"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, Query
|
||||
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, Query, Path
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
import structlog
|
||||
import uuid
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from app.schemas.training import (
|
||||
TrainingJobRequest,
|
||||
@@ -49,7 +49,7 @@ def get_training_service() -> TrainingService:
|
||||
async def start_training_job(
|
||||
request: TrainingJobRequest,
|
||||
background_tasks: BackgroundTasks,
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
training_service: TrainingService = Depends(get_training_service),
|
||||
db: AsyncSession = Depends(get_db_session) # Ensure db is available
|
||||
@@ -57,11 +57,11 @@ async def start_training_job(
|
||||
"""Start a new training job for all products"""
|
||||
try:
|
||||
|
||||
new_job_id = str(uuid.uuid4())
|
||||
new_job_id = str(uuid4())
|
||||
|
||||
logger.info("Starting training job",
|
||||
tenant_id=tenant_id,
|
||||
job_id=uuid.uuid4(),
|
||||
job_id=uuid4(),
|
||||
config=request.dict())
|
||||
|
||||
# Create training job
|
||||
@@ -115,7 +115,7 @@ async def get_training_jobs(
|
||||
status: Optional[TrainingStatus] = Query(None, description="Filter jobs by status"),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
offset: int = Query(0, ge=0),
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
training_service: TrainingService = Depends(get_training_service)
|
||||
):
|
||||
@@ -149,7 +149,7 @@ async def get_training_jobs(
|
||||
@router.get("/jobs/{job_id}", response_model=TrainingJobResponse)
|
||||
async def get_training_job(
|
||||
job_id: str,
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
training_service: TrainingService = Depends(get_training_service)
|
||||
):
|
||||
@@ -182,7 +182,7 @@ async def get_training_job(
|
||||
@router.get("/jobs/{job_id}/progress", response_model=TrainingJobProgress)
|
||||
async def get_training_progress(
|
||||
job_id: str,
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
training_service: TrainingService = Depends(get_training_service)
|
||||
):
|
||||
@@ -212,7 +212,7 @@ async def get_training_progress(
|
||||
@router.post("/jobs/{job_id}/cancel")
|
||||
async def cancel_training_job(
|
||||
job_id: str,
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
training_service: TrainingService = Depends(get_training_service)
|
||||
):
|
||||
@@ -259,7 +259,7 @@ async def train_single_product(
|
||||
product_name: str,
|
||||
request: SingleProductTrainingRequest,
|
||||
background_tasks: BackgroundTasks,
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
training_service: TrainingService = Depends(get_training_service),
|
||||
db: AsyncSession = Depends(get_db_session)
|
||||
@@ -312,7 +312,7 @@ async def train_single_product(
|
||||
@router.post("/validate", response_model=DataValidationResponse)
|
||||
async def validate_training_data(
|
||||
request: DataValidationRequest,
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
training_service: TrainingService = Depends(get_training_service)
|
||||
):
|
||||
@@ -343,7 +343,7 @@ async def validate_training_data(
|
||||
@router.get("/models")
|
||||
async def get_trained_models(
|
||||
product_name: Optional[str] = Query(None),
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
training_service: TrainingService = Depends(get_training_service)
|
||||
):
|
||||
@@ -374,7 +374,7 @@ async def get_trained_models(
|
||||
@require_role("admin") # Only admins can delete models
|
||||
async def delete_model(
|
||||
model_id: str,
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
training_service: TrainingService = Depends(get_training_service)
|
||||
):
|
||||
@@ -411,7 +411,7 @@ async def delete_model(
|
||||
async def get_training_stats(
|
||||
start_date: Optional[datetime] = Query(None),
|
||||
end_date: Optional[datetime] = Query(None),
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
training_service: TrainingService = Depends(get_training_service)
|
||||
):
|
||||
@@ -442,7 +442,7 @@ async def get_training_stats(
|
||||
async def retrain_all_products(
|
||||
request: TrainingJobRequest,
|
||||
background_tasks: BackgroundTasks,
|
||||
tenant_id: str = Depends(get_current_tenant_id_dep),
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
training_service: TrainingService = Depends(get_training_service)
|
||||
):
|
||||
|
||||
@@ -173,18 +173,10 @@ async def global_exception_handler(request: Request, exc: Exception):
|
||||
}
|
||||
)
|
||||
|
||||
# Include API routers - NO AUTH DEPENDENCIES HERE
|
||||
# Authentication is handled by API Gateway
|
||||
app.include_router(
|
||||
training.router,
|
||||
tags=["training"]
|
||||
)
|
||||
# Include API routers
|
||||
app.include_router(training.router, prefix="/api/v1", tags=["training"])
|
||||
app.include_router(models.router, prefix="/api/v1", tags=["models"])
|
||||
|
||||
app.include_router(
|
||||
models.router,
|
||||
prefix="/models",
|
||||
tags=["models"]
|
||||
)
|
||||
|
||||
# Health check endpoints
|
||||
@app.get("/health")
|
||||
|
||||
@@ -488,7 +488,7 @@ class TrainingService:
|
||||
params["end_date"] = request.end_date.isoformat()
|
||||
|
||||
response = await client.get(
|
||||
f"{settings.DATA_SERVICE_URL}/weather/history",
|
||||
f"{settings.DATA_SERVICE_URL}/tenants/{tenant_id}/weather/history",
|
||||
params=params,
|
||||
timeout=30.0
|
||||
)
|
||||
@@ -516,7 +516,7 @@ class TrainingService:
|
||||
params["end_date"] = request.end_date.isoformat()
|
||||
|
||||
response = await client.get(
|
||||
f"{settings.DATA_SERVICE_URL}/traffic/historical",
|
||||
f"{settings.DATA_SERVICE_URL}/tenants/{tenant_id}/traffic/historical",
|
||||
params=params,
|
||||
timeout=30.0
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user