Improve the frontend and repository layer
This commit is contained in:
@@ -26,6 +26,7 @@ from shared.routing.route_builder import RouteBuilder
|
||||
from shared.database.base import create_database_manager
|
||||
from shared.monitoring.metrics import track_endpoint_metrics
|
||||
from shared.security import create_audit_logger, AuditSeverity, AuditAction
|
||||
from shared.config.base import is_internal_service
|
||||
|
||||
logger = structlog.get_logger()
|
||||
router = APIRouter()
|
||||
@@ -64,7 +65,22 @@ def get_subscription_limit_service():
|
||||
try:
|
||||
from app.core.config import settings
|
||||
database_manager = create_database_manager(settings.DATABASE_URL, "tenant-service")
|
||||
redis_client = get_tenant_redis_client()
|
||||
|
||||
# Get Redis client properly (it's an async function)
|
||||
import asyncio
|
||||
try:
|
||||
# Try to get the event loop, if we're in an async context
|
||||
loop = asyncio.get_event_loop()
|
||||
if loop.is_running():
|
||||
# If we're in a running event loop, we can't use await here
|
||||
# So we'll pass None and handle Redis initialization in the service
|
||||
redis_client = None
|
||||
else:
|
||||
redis_client = asyncio.run(get_tenant_redis_client())
|
||||
except RuntimeError:
|
||||
# No event loop running, we can use async/await
|
||||
redis_client = asyncio.run(get_tenant_redis_client())
|
||||
|
||||
return SubscriptionLimitService(database_manager, redis_client)
|
||||
except Exception as e:
|
||||
logger.error("Failed to create subscription limit service", error=str(e))
|
||||
@@ -204,9 +220,10 @@ async def verify_tenant_access(
|
||||
):
|
||||
"""Verify if user has access to tenant - Enhanced version with detailed permissions"""
|
||||
|
||||
# Check if this is a service request
|
||||
if user_id in ["training-service", "data-service", "forecasting-service", "auth-service"]:
|
||||
# Check if this is an internal service request using centralized registry
|
||||
if is_internal_service(user_id):
|
||||
# Services have access to all tenants for their operations
|
||||
logger.info("Service access granted", service=user_id, tenant_id=str(tenant_id))
|
||||
return TenantAccessResponse(
|
||||
has_access=True,
|
||||
role="service",
|
||||
|
||||
186
services/tenant/app/api/tenant_settings.py
Normal file
186
services/tenant/app/api/tenant_settings.py
Normal file
@@ -0,0 +1,186 @@
|
||||
# services/tenant/app/api/tenant_settings.py
|
||||
"""
|
||||
Tenant Settings API Endpoints
|
||||
REST API for managing tenant-specific operational settings
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from uuid import UUID
|
||||
from typing import Dict, Any
|
||||
|
||||
from app.core.database import get_db
|
||||
from shared.routing.route_builder import RouteBuilder
|
||||
from ..services.tenant_settings_service import TenantSettingsService
|
||||
from ..schemas.tenant_settings import (
|
||||
TenantSettingsResponse,
|
||||
TenantSettingsUpdate,
|
||||
CategoryUpdateRequest,
|
||||
CategoryResetResponse
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
route_builder = RouteBuilder("tenants")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{tenant_id}/settings",
|
||||
response_model=TenantSettingsResponse,
|
||||
summary="Get all tenant settings",
|
||||
description="Retrieve all operational settings for a tenant. Creates default settings if none exist."
|
||||
)
|
||||
async def get_tenant_settings(
|
||||
tenant_id: UUID,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Get all settings for a tenant
|
||||
|
||||
- **tenant_id**: UUID of the tenant
|
||||
|
||||
Returns all setting categories with their current values.
|
||||
If settings don't exist, default values are created and returned.
|
||||
"""
|
||||
service = TenantSettingsService(db)
|
||||
settings = await service.get_settings(tenant_id)
|
||||
return settings
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{tenant_id}/settings",
|
||||
response_model=TenantSettingsResponse,
|
||||
summary="Update tenant settings",
|
||||
description="Update one or more setting categories for a tenant. Only provided categories are updated."
|
||||
)
|
||||
async def update_tenant_settings(
|
||||
tenant_id: UUID,
|
||||
updates: TenantSettingsUpdate,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Update tenant settings
|
||||
|
||||
- **tenant_id**: UUID of the tenant
|
||||
- **updates**: Object containing setting categories to update
|
||||
|
||||
Only provided categories will be updated. Omitted categories remain unchanged.
|
||||
All values are validated against min/max constraints.
|
||||
"""
|
||||
service = TenantSettingsService(db)
|
||||
settings = await service.update_settings(tenant_id, updates)
|
||||
return settings
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{tenant_id}/settings/{category}",
|
||||
response_model=Dict[str, Any],
|
||||
summary="Get settings for a specific category",
|
||||
description="Retrieve settings for a single category (procurement, inventory, production, supplier, pos, or order)"
|
||||
)
|
||||
async def get_category_settings(
|
||||
tenant_id: UUID,
|
||||
category: str,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Get settings for a specific category
|
||||
|
||||
- **tenant_id**: UUID of the tenant
|
||||
- **category**: Category name (procurement, inventory, production, supplier, pos, order)
|
||||
|
||||
Returns settings for the specified category only.
|
||||
|
||||
Valid categories:
|
||||
- procurement: Auto-approval and procurement planning settings
|
||||
- inventory: Stock thresholds and temperature monitoring
|
||||
- production: Capacity, quality, and scheduling settings
|
||||
- supplier: Payment terms and performance thresholds
|
||||
- pos: POS integration sync settings
|
||||
- order: Discount and delivery settings
|
||||
"""
|
||||
service = TenantSettingsService(db)
|
||||
category_settings = await service.get_category(tenant_id, category)
|
||||
return {
|
||||
"tenant_id": str(tenant_id),
|
||||
"category": category,
|
||||
"settings": category_settings
|
||||
}
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{tenant_id}/settings/{category}",
|
||||
response_model=TenantSettingsResponse,
|
||||
summary="Update settings for a specific category",
|
||||
description="Update all or some fields within a single category"
|
||||
)
|
||||
async def update_category_settings(
|
||||
tenant_id: UUID,
|
||||
category: str,
|
||||
request: CategoryUpdateRequest,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Update settings for a specific category
|
||||
|
||||
- **tenant_id**: UUID of the tenant
|
||||
- **category**: Category name
|
||||
- **request**: Object containing the settings to update
|
||||
|
||||
Updates only the specified category. All values are validated.
|
||||
"""
|
||||
service = TenantSettingsService(db)
|
||||
settings = await service.update_category(tenant_id, category, request.settings)
|
||||
return settings
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{tenant_id}/settings/{category}/reset",
|
||||
response_model=CategoryResetResponse,
|
||||
summary="Reset category to default values",
|
||||
description="Reset a specific category to its default values"
|
||||
)
|
||||
async def reset_category_settings(
|
||||
tenant_id: UUID,
|
||||
category: str,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Reset a category to default values
|
||||
|
||||
- **tenant_id**: UUID of the tenant
|
||||
- **category**: Category name
|
||||
|
||||
Resets all settings in the specified category to their default values.
|
||||
This operation cannot be undone.
|
||||
"""
|
||||
service = TenantSettingsService(db)
|
||||
reset_settings = await service.reset_category(tenant_id, category)
|
||||
|
||||
return CategoryResetResponse(
|
||||
category=category,
|
||||
settings=reset_settings,
|
||||
message=f"Category '{category}' has been reset to default values"
|
||||
)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{tenant_id}/settings",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
summary="Delete tenant settings",
|
||||
description="Delete all settings for a tenant (used when tenant is deleted)"
|
||||
)
|
||||
async def delete_tenant_settings(
|
||||
tenant_id: UUID,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Delete tenant settings
|
||||
|
||||
- **tenant_id**: UUID of the tenant
|
||||
|
||||
This endpoint is typically called automatically when a tenant is deleted.
|
||||
It removes all setting data for the tenant.
|
||||
"""
|
||||
service = TenantSettingsService(db)
|
||||
await service.delete_settings(tenant_id)
|
||||
return None
|
||||
@@ -37,15 +37,36 @@ async def get_tenant(
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
|
||||
):
|
||||
"""Get tenant by ID - ATOMIC operation"""
|
||||
"""Get tenant by ID - ATOMIC operation - ENHANCED with logging"""
|
||||
|
||||
logger.info(
|
||||
"Tenant GET request received",
|
||||
tenant_id=str(tenant_id),
|
||||
user_id=current_user.get("user_id"),
|
||||
user_type=current_user.get("type", "user"),
|
||||
is_service=current_user.get("type") == "service",
|
||||
role=current_user.get("role"),
|
||||
service_name=current_user.get("service", "none")
|
||||
)
|
||||
|
||||
tenant = await tenant_service.get_tenant_by_id(str(tenant_id))
|
||||
if not tenant:
|
||||
logger.warning(
|
||||
"Tenant not found",
|
||||
tenant_id=str(tenant_id),
|
||||
user_id=current_user.get("user_id")
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Tenant not found"
|
||||
)
|
||||
|
||||
logger.debug(
|
||||
"Tenant GET request successful",
|
||||
tenant_id=str(tenant_id),
|
||||
user_id=current_user.get("user_id")
|
||||
)
|
||||
|
||||
return tenant
|
||||
|
||||
@router.put(route_builder.build_base_route("{tenant_id}", include_tenant_prefix=False), response_model=TenantResponse)
|
||||
|
||||
Reference in New Issue
Block a user