Add role-based filtering and imporve code
This commit is contained in:
@@ -8,6 +8,7 @@ from datetime import datetime
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Path, Query
|
||||
from typing import List, Dict, Any, Optional
|
||||
from uuid import UUID
|
||||
import shared.redis_utils
|
||||
|
||||
from app.schemas.tenants import (
|
||||
BakeryRegistration, TenantResponse, TenantAccessResponse,
|
||||
@@ -20,14 +21,22 @@ from shared.auth.decorators import (
|
||||
get_current_user_dep,
|
||||
require_admin_role_dep
|
||||
)
|
||||
from shared.auth.access_control import owner_role_required, admin_role_required
|
||||
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
|
||||
|
||||
logger = structlog.get_logger()
|
||||
router = APIRouter()
|
||||
route_builder = RouteBuilder("tenants")
|
||||
|
||||
# Initialize audit logger
|
||||
audit_logger = create_audit_logger("tenant-service")
|
||||
|
||||
# Global Redis client
|
||||
_redis_client = None
|
||||
|
||||
# Dependency injection for enhanced tenant service
|
||||
def get_enhanced_tenant_service():
|
||||
try:
|
||||
@@ -38,11 +47,25 @@ def get_enhanced_tenant_service():
|
||||
logger.error("Failed to create enhanced tenant service", error=str(e))
|
||||
raise HTTPException(status_code=500, detail="Service initialization failed")
|
||||
|
||||
async def get_tenant_redis_client():
|
||||
"""Get or create Redis client"""
|
||||
global _redis_client
|
||||
try:
|
||||
if _redis_client is None:
|
||||
from app.core.config import settings
|
||||
_redis_client = await shared.redis_utils.initialize_redis(settings.REDIS_URL)
|
||||
logger.info("Redis client initialized using shared utilities")
|
||||
return _redis_client
|
||||
except Exception as e:
|
||||
logger.warning("Failed to initialize Redis client, service will work with limited functionality", error=str(e))
|
||||
return None
|
||||
|
||||
def get_subscription_limit_service():
|
||||
try:
|
||||
from app.core.config import settings
|
||||
database_manager = create_database_manager(settings.DATABASE_URL, "tenant-service")
|
||||
return SubscriptionLimitService(database_manager)
|
||||
redis_client = 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))
|
||||
raise HTTPException(status_code=500, detail="Service initialization failed")
|
||||
@@ -325,6 +348,7 @@ async def update_tenant_model_status(
|
||||
|
||||
@router.post(route_builder.build_base_route("{tenant_id}/deactivate", include_tenant_prefix=False))
|
||||
@track_endpoint_metrics("tenant_deactivate")
|
||||
@owner_role_required
|
||||
async def deactivate_tenant(
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
@@ -339,6 +363,25 @@ async def deactivate_tenant(
|
||||
)
|
||||
|
||||
if success:
|
||||
# Log audit event for tenant deactivation
|
||||
try:
|
||||
from app.core.database import get_db_session
|
||||
async with get_db_session() as db:
|
||||
await audit_logger.log_event(
|
||||
db_session=db,
|
||||
tenant_id=str(tenant_id),
|
||||
user_id=current_user["user_id"],
|
||||
action=AuditAction.DEACTIVATE.value,
|
||||
resource_type="tenant",
|
||||
resource_id=str(tenant_id),
|
||||
severity=AuditSeverity.CRITICAL.value,
|
||||
description=f"Owner {current_user.get('email', current_user['user_id'])} deactivated tenant",
|
||||
endpoint="/{tenant_id}/deactivate",
|
||||
method="POST"
|
||||
)
|
||||
except Exception as audit_error:
|
||||
logger.warning("Failed to log audit event", error=str(audit_error))
|
||||
|
||||
return {"success": True, "message": "Tenant deactivated successfully"}
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -359,6 +402,7 @@ async def deactivate_tenant(
|
||||
|
||||
@router.post(route_builder.build_base_route("{tenant_id}/activate", include_tenant_prefix=False))
|
||||
@track_endpoint_metrics("tenant_activate")
|
||||
@owner_role_required
|
||||
async def activate_tenant(
|
||||
tenant_id: UUID = Path(..., description="Tenant ID"),
|
||||
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
||||
@@ -373,6 +417,25 @@ async def activate_tenant(
|
||||
)
|
||||
|
||||
if success:
|
||||
# Log audit event for tenant activation
|
||||
try:
|
||||
from app.core.database import get_db_session
|
||||
async with get_db_session() as db:
|
||||
await audit_logger.log_event(
|
||||
db_session=db,
|
||||
tenant_id=str(tenant_id),
|
||||
user_id=current_user["user_id"],
|
||||
action=AuditAction.ACTIVATE.value,
|
||||
resource_type="tenant",
|
||||
resource_id=str(tenant_id),
|
||||
severity=AuditSeverity.HIGH.value,
|
||||
description=f"Owner {current_user.get('email', current_user['user_id'])} activated tenant",
|
||||
endpoint="/{tenant_id}/activate",
|
||||
method="POST"
|
||||
)
|
||||
except Exception as audit_error:
|
||||
logger.warning("Failed to log audit event", error=str(audit_error))
|
||||
|
||||
return {"success": True, "message": "Tenant activated successfully"}
|
||||
else:
|
||||
raise HTTPException(
|
||||
@@ -644,91 +707,10 @@ async def upgrade_subscription_plan(
|
||||
detail="Failed to upgrade subscription plan"
|
||||
)
|
||||
|
||||
@router.get("/api/v1/plans")
|
||||
async def get_available_plans():
|
||||
"""Get all available subscription plans with features and pricing - Public endpoint"""
|
||||
|
||||
try:
|
||||
# This could be moved to a config service or database
|
||||
plans = {
|
||||
"starter": {
|
||||
"name": "Starter",
|
||||
"description": "Ideal para panaderías pequeñas o nuevas",
|
||||
"monthly_price": 49.0,
|
||||
"max_users": 5,
|
||||
"max_locations": 1,
|
||||
"max_products": 50,
|
||||
"features": {
|
||||
"inventory_management": "basic",
|
||||
"demand_prediction": "basic",
|
||||
"production_reports": "basic",
|
||||
"analytics": "basic",
|
||||
"support": "email",
|
||||
"trial_days": 14,
|
||||
"locations": "1_location",
|
||||
"ai_model_configuration": "basic"
|
||||
},
|
||||
"trial_available": True
|
||||
},
|
||||
"professional": {
|
||||
"name": "Professional",
|
||||
"description": "Ideal para panaderías y cadenas en crecimiento",
|
||||
"monthly_price": 129.0,
|
||||
"max_users": 15,
|
||||
"max_locations": 2,
|
||||
"max_products": -1, # Unlimited
|
||||
"features": {
|
||||
"inventory_management": "advanced",
|
||||
"demand_prediction": "ai_92_percent",
|
||||
"production_management": "complete",
|
||||
"pos_integrated": True,
|
||||
"logistics": "basic",
|
||||
"analytics": "advanced",
|
||||
"support": "priority_24_7",
|
||||
"trial_days": 14,
|
||||
"locations": "1_2_locations",
|
||||
"ai_model_configuration": "advanced"
|
||||
},
|
||||
"trial_available": True,
|
||||
"popular": True
|
||||
},
|
||||
"enterprise": {
|
||||
"name": "Enterprise",
|
||||
"description": "Ideal para cadenas con obradores centrales",
|
||||
"monthly_price": 399.0,
|
||||
"max_users": -1, # Unlimited
|
||||
"max_locations": -1, # Unlimited
|
||||
"max_products": -1, # Unlimited
|
||||
"features": {
|
||||
"inventory_management": "multi_location",
|
||||
"demand_prediction": "ai_personalized",
|
||||
"production_optimization": "capacity",
|
||||
"erp_integration": True,
|
||||
"logistics": "advanced",
|
||||
"analytics": "predictive",
|
||||
"api_access": "personalized",
|
||||
"account_manager": True,
|
||||
"demo": "personalized",
|
||||
"locations": "unlimited_obradores",
|
||||
"ai_model_configuration": "enterprise"
|
||||
},
|
||||
"trial_available": False,
|
||||
"contact_sales": True
|
||||
}
|
||||
}
|
||||
|
||||
return {"plans": plans}
|
||||
|
||||
except Exception as e:
|
||||
logger.error("Failed to get available plans", error=str(e))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Failed to get available plans"
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# PAYMENT OPERATIONS
|
||||
# ============================================================================
|
||||
# Note: /plans endpoint moved to app/api/plans.py for better organization
|
||||
|
||||
@router.post(route_builder.build_base_route("subscriptions/register-with-subscription", include_tenant_prefix=False))
|
||||
async def register_with_subscription(
|
||||
|
||||
Reference in New Issue
Block a user