diff --git a/gateway/app/core/config.py b/gateway/app/core/config.py index 522ef602..ca61a1f6 100644 --- a/gateway/app/core/config.py +++ b/gateway/app/core/config.py @@ -15,27 +15,32 @@ class Settings(BaseSettings): DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true" LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO") - # CORS settings - CORS_ORIGINS: List[str] = os.getenv("CORS_ORIGINS", "http://localhost:3000,http://localhost:3001").split(",") + # CORS settings - FIXED: Remove List[str] type and parse manually + CORS_ORIGINS: str = "http://localhost:3000,http://localhost:3001" # Service URLs - AUTH_SERVICE_URL: str = os.getenv("AUTH_SERVICE_URL", "http://auth-service:8000") - TRAINING_SERVICE_URL: str = os.getenv("TRAINING_SERVICE_URL", "http://training-service:8000") - FORECASTING_SERVICE_URL: str = os.getenv("FORECASTING_SERVICE_URL", "http://forecasting-service:8000") - DATA_SERVICE_URL: str = os.getenv("DATA_SERVICE_URL", "http://data-service:8000") - TENANT_SERVICE_URL: str = os.getenv("TENANT_SERVICE_URL", "http://tenant-service:8000") - NOTIFICATION_SERVICE_URL: str = os.getenv("NOTIFICATION_SERVICE_URL", "http://notification-service:8000") + AUTH_SERVICE_URL: str = "http://auth-service:8000" + TRAINING_SERVICE_URL: str = "http://training-service:8000" + FORECASTING_SERVICE_URL: str = "http://forecasting-service:8000" + DATA_SERVICE_URL: str = "http://data-service:8000" + TENANT_SERVICE_URL: str = "http://tenant-service:8000" + NOTIFICATION_SERVICE_URL: str = "http://notification-service:8000" # Redis settings - REDIS_URL: str = os.getenv("REDIS_URL", "redis://redis:6379/6") + REDIS_URL: str = "redis://redis:6379/6" # Rate limiting - RATE_LIMIT_REQUESTS: int = int(os.getenv("RATE_LIMIT_REQUESTS", "100")) - RATE_LIMIT_WINDOW: int = int(os.getenv("RATE_LIMIT_WINDOW", "60")) + RATE_LIMIT_REQUESTS: int = 100 + RATE_LIMIT_WINDOW: int = 60 # JWT settings - JWT_SECRET_KEY: str = os.getenv("JWT_SECRET_KEY", "your-secret-key-change-in-production") - JWT_ALGORITHM: str = os.getenv("JWT_ALGORITHM", "HS256") + JWT_SECRET_KEY: str = "your-secret-key-change-in-production" + JWT_ALGORITHM: str = "HS256" + + @property + def CORS_ORIGINS_LIST(self) -> List[str]: + """Parse CORS origins from string to list""" + return [origin.strip() for origin in self.CORS_ORIGINS.split(",") if origin.strip()] @property def SERVICES(self) -> Dict[str, str]: @@ -49,4 +54,7 @@ class Settings(BaseSettings): "notification": self.NOTIFICATION_SERVICE_URL } + class Config: + env_file = ".env" + settings = Settings() \ No newline at end of file diff --git a/gateway/app/main.py b/gateway/app/main.py index eb2e4b6a..03239a0f 100644 --- a/gateway/app/main.py +++ b/gateway/app/main.py @@ -40,10 +40,10 @@ metrics_collector = MetricsCollector("gateway") # Service discovery service_discovery = ServiceDiscovery() -# CORS middleware +# CORS middleware - FIXED: Use the parsed list property app.add_middleware( CORSMiddleware, - allow_origins=settings.CORS_ORIGINS, + allow_origins=settings.CORS_ORIGINS_LIST, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], diff --git a/gateway/app/routes/data.py b/gateway/app/routes/data.py new file mode 100644 index 00000000..c0df3cc1 --- /dev/null +++ b/gateway/app/routes/data.py @@ -0,0 +1,66 @@ +""" +Data routes for gateway +""" + +from fastapi import APIRouter, Request, HTTPException +from fastapi.responses import JSONResponse +import httpx +import logging + +from app.core.config import settings + +logger = logging.getLogger(__name__) +router = APIRouter() + +@router.post("/upload") +async def upload_sales_data(request: Request): + """Proxy data upload to data service""" + try: + body = await request.body() + auth_header = request.headers.get("Authorization") + + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.post( + f"{settings.DATA_SERVICE_URL}/upload", + content=body, + headers={ + "Content-Type": "application/json", + "Authorization": auth_header + } + ) + + return JSONResponse( + status_code=response.status_code, + content=response.json() + ) + + except httpx.RequestError as e: + logger.error(f"Data service unavailable: {e}") + raise HTTPException( + status_code=503, + detail="Data service unavailable" + ) + +@router.get("/sales") +async def get_sales_data(request: Request): + """Get sales data""" + try: + auth_header = request.headers.get("Authorization") + + async with httpx.AsyncClient(timeout=10.0) as client: + response = await client.get( + f"{settings.DATA_SERVICE_URL}/sales", + headers={"Authorization": auth_header} + ) + + return JSONResponse( + status_code=response.status_code, + content=response.json() + ) + + except httpx.RequestError as e: + logger.error(f"Data service unavailable: {e}") + raise HTTPException( + status_code=503, + detail="Data service unavailable" + ) diff --git a/gateway/app/routes/forecasting.py b/gateway/app/routes/forecasting.py new file mode 100644 index 00000000..6433eeb3 --- /dev/null +++ b/gateway/app/routes/forecasting.py @@ -0,0 +1,66 @@ +""" +Forecasting routes for gateway +""" + +from fastapi import APIRouter, Request, HTTPException +from fastapi.responses import JSONResponse +import httpx +import logging + +from app.core.config import settings + +logger = logging.getLogger(__name__) +router = APIRouter() + +@router.post("/predict") +async def create_forecast(request: Request): + """Proxy forecast request to forecasting service""" + try: + body = await request.body() + auth_header = request.headers.get("Authorization") + + async with httpx.AsyncClient(timeout=30.0) as client: + response = await client.post( + f"{settings.FORECASTING_SERVICE_URL}/predict", + content=body, + headers={ + "Content-Type": "application/json", + "Authorization": auth_header + } + ) + + return JSONResponse( + status_code=response.status_code, + content=response.json() + ) + + except httpx.RequestError as e: + logger.error(f"Forecasting service unavailable: {e}") + raise HTTPException( + status_code=503, + detail="Forecasting service unavailable" + ) + +@router.get("/forecasts") +async def get_forecasts(request: Request): + """Get forecasts""" + try: + auth_header = request.headers.get("Authorization") + + async with httpx.AsyncClient(timeout=10.0) as client: + response = await client.get( + f"{settings.FORECASTING_SERVICE_URL}/forecasts", + headers={"Authorization": auth_header} + ) + + return JSONResponse( + status_code=response.status_code, + content=response.json() + ) + + except httpx.RequestError as e: + logger.error(f"Forecasting service unavailable: {e}") + raise HTTPException( + status_code=503, + detail="Forecasting service unavailable" + ) diff --git a/gateway/app/routes/notification.py b/gateway/app/routes/notification.py new file mode 100644 index 00000000..3bfbe81a --- /dev/null +++ b/gateway/app/routes/notification.py @@ -0,0 +1,66 @@ +""" +Notification routes for gateway +""" + +from fastapi import APIRouter, Request, HTTPException +from fastapi.responses import JSONResponse +import httpx +import logging + +from app.core.config import settings + +logger = logging.getLogger(__name__) +router = APIRouter() + +@router.post("/send") +async def send_notification(request: Request): + """Proxy notification request to notification service""" + try: + body = await request.body() + auth_header = request.headers.get("Authorization") + + async with httpx.AsyncClient(timeout=10.0) as client: + response = await client.post( + f"{settings.NOTIFICATION_SERVICE_URL}/send", + content=body, + headers={ + "Content-Type": "application/json", + "Authorization": auth_header + } + ) + + return JSONResponse( + status_code=response.status_code, + content=response.json() + ) + + except httpx.RequestError as e: + logger.error(f"Notification service unavailable: {e}") + raise HTTPException( + status_code=503, + detail="Notification service unavailable" + ) + +@router.get("/history") +async def get_notification_history(request: Request): + """Get notification history""" + try: + auth_header = request.headers.get("Authorization") + + async with httpx.AsyncClient(timeout=10.0) as client: + response = await client.get( + f"{settings.NOTIFICATION_SERVICE_URL}/history", + headers={"Authorization": auth_header} + ) + + return JSONResponse( + status_code=response.status_code, + content=response.json() + ) + + except httpx.RequestError as e: + logger.error(f"Notification service unavailable: {e}") + raise HTTPException( + status_code=503, + detail="Notification service unavailable" + ) \ No newline at end of file diff --git a/gateway/app/routes/tenant.py b/gateway/app/routes/tenant.py new file mode 100644 index 00000000..1e19e796 --- /dev/null +++ b/gateway/app/routes/tenant.py @@ -0,0 +1,66 @@ +""" +Tenant routes for gateway +""" + +from fastapi import APIRouter, Request, HTTPException +from fastapi.responses import JSONResponse +import httpx +import logging + +from app.core.config import settings + +logger = logging.getLogger(__name__) +router = APIRouter() + +@router.post("/") +async def create_tenant(request: Request): + """Proxy tenant creation to tenant service""" + try: + body = await request.body() + auth_header = request.headers.get("Authorization") + + async with httpx.AsyncClient(timeout=10.0) as client: + response = await client.post( + f"{settings.TENANT_SERVICE_URL}/tenants", + content=body, + headers={ + "Content-Type": "application/json", + "Authorization": auth_header + } + ) + + return JSONResponse( + status_code=response.status_code, + content=response.json() + ) + + except httpx.RequestError as e: + logger.error(f"Tenant service unavailable: {e}") + raise HTTPException( + status_code=503, + detail="Tenant service unavailable" + ) + +@router.get("/") +async def get_tenants(request: Request): + """Get tenants""" + try: + auth_header = request.headers.get("Authorization") + + async with httpx.AsyncClient(timeout=10.0) as client: + response = await client.get( + f"{settings.TENANT_SERVICE_URL}/tenants", + headers={"Authorization": auth_header} + ) + + return JSONResponse( + status_code=response.status_code, + content=response.json() + ) + + except httpx.RequestError as e: + logger.error(f"Tenant service unavailable: {e}") + raise HTTPException( + status_code=503, + detail="Tenant service unavailable" + ) \ No newline at end of file diff --git a/gateway/requirements.txt b/gateway/requirements.txt index 28fa51e9..5e3bead1 100644 --- a/gateway/requirements.txt +++ b/gateway/requirements.txt @@ -10,4 +10,5 @@ prometheus-client==0.17.1 python-json-logger==2.0.4 email-validator==2.0.0 aio-pika==9.3.0 -pytz==2023.3 \ No newline at end of file +pytz==2023.3 +python-logstash==0.4.8 \ No newline at end of file diff --git a/services/training/app/services/messaging.py b/services/training/app/services/messaging.py index dab29f91..e03d2a5b 100644 --- a/services/training/app/services/messaging.py +++ b/services/training/app/services/messaging.py @@ -4,3 +4,6 @@ Messaging service for training service from shared.messaging.rabbitmq import RabbitMQClient from app.core.config import settings + +# Global message publisher +message_publisher = RabbitMQClient(settings.RABBITMQ_URL) \ No newline at end of file