Improve the frontend and repository layer

This commit is contained in:
Urtzi Alfaro
2025-10-23 07:44:54 +02:00
parent 8d30172483
commit 07c33fa578
112 changed files with 14726 additions and 2733 deletions

View File

@@ -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",

View 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

View File

@@ -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)