- Auto-fixed 4,496 lint issues (import sorting, modern syntax, etc.) - Added ignore rules for patterns intentional in this codebase: E402 (late imports), E712 (SQLAlchemy filters), B904 (raise from), SIM108/SIM105/SIM117 (readability preferences) - Added per-file ignores for tests and scripts - Excluded broken scripts/rename_terminology.py (has curly quotes) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
400 lines
12 KiB
Python
400 lines
12 KiB
Python
# app/modules/tenancy/routes/api/admin_modules.py
|
|
"""
|
|
Admin API endpoints for Platform Module Management.
|
|
|
|
Provides module enablement/disablement for platforms:
|
|
- GET /modules - List all available modules
|
|
- GET /modules/platforms/{platform_id} - Get modules for a platform
|
|
- PUT /modules/platforms/{platform_id} - Update enabled modules
|
|
- POST /modules/platforms/{platform_id}/enable - Enable a module
|
|
- POST /modules/platforms/{platform_id}/disable - Disable a module
|
|
|
|
All endpoints require super admin access.
|
|
"""
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Depends, Path
|
|
from pydantic import BaseModel, Field
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.api.deps import get_current_super_admin, get_db
|
|
from app.modules.registry import MODULES, get_core_module_codes
|
|
from app.modules.service import module_service
|
|
from app.modules.tenancy.services.platform_service import platform_service
|
|
from models.schema.auth import UserContext
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter(prefix="/modules")
|
|
|
|
|
|
# =============================================================================
|
|
# Pydantic Schemas
|
|
# =============================================================================
|
|
|
|
|
|
class ModuleResponse(BaseModel):
|
|
"""Module definition response."""
|
|
|
|
code: str
|
|
name: str
|
|
description: str
|
|
is_core: bool
|
|
is_enabled: bool
|
|
requires: list[str] = Field(default_factory=list)
|
|
features: list[str] = Field(default_factory=list)
|
|
menu_items_admin: list[str] = Field(default_factory=list)
|
|
menu_items_store: list[str] = Field(default_factory=list)
|
|
dependent_modules: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class ModuleListResponse(BaseModel):
|
|
"""Response for module list."""
|
|
|
|
modules: list[ModuleResponse]
|
|
total: int
|
|
enabled: int
|
|
disabled: int
|
|
|
|
|
|
class PlatformModulesResponse(BaseModel):
|
|
"""Response for platform module configuration."""
|
|
|
|
platform_id: int
|
|
platform_code: str
|
|
platform_name: str
|
|
modules: list[ModuleResponse]
|
|
total: int
|
|
enabled: int
|
|
disabled: int
|
|
|
|
|
|
class EnableModulesRequest(BaseModel):
|
|
"""Request to set enabled modules."""
|
|
|
|
module_codes: list[str] = Field(..., description="List of module codes to enable")
|
|
|
|
|
|
class ToggleModuleRequest(BaseModel):
|
|
"""Request to enable/disable a single module."""
|
|
|
|
module_code: str = Field(..., description="Module code to toggle")
|
|
|
|
|
|
# =============================================================================
|
|
# Helper Functions
|
|
# =============================================================================
|
|
|
|
|
|
def _get_dependent_modules(module_code: str) -> list[str]:
|
|
"""Get modules that depend on a given module."""
|
|
dependents = []
|
|
for code, module in MODULES.items():
|
|
if module_code in module.requires:
|
|
dependents.append(code)
|
|
return dependents
|
|
|
|
|
|
def _build_module_response(
|
|
code: str,
|
|
is_enabled: bool,
|
|
) -> ModuleResponse:
|
|
"""Build ModuleResponse from module code."""
|
|
from app.modules.enums import FrontendType # noqa: API-007 - Enum for type safety
|
|
|
|
module = MODULES.get(code)
|
|
if not module:
|
|
raise ValueError(f"Unknown module: {code}")
|
|
|
|
return ModuleResponse(
|
|
code=module.code,
|
|
name=module.name,
|
|
description=module.description,
|
|
is_core=module.is_core,
|
|
is_enabled=is_enabled,
|
|
requires=module.requires,
|
|
features=module.features,
|
|
menu_items_admin=module.get_menu_items(FrontendType.ADMIN),
|
|
menu_items_store=module.get_menu_items(FrontendType.STORE),
|
|
dependent_modules=_get_dependent_modules(module.code),
|
|
)
|
|
|
|
|
|
# =============================================================================
|
|
# API Endpoints
|
|
# =============================================================================
|
|
|
|
|
|
@router.get("", response_model=ModuleListResponse)
|
|
async def list_all_modules(
|
|
current_user: UserContext = Depends(get_current_super_admin),
|
|
):
|
|
"""
|
|
List all available modules.
|
|
|
|
Returns all module definitions with their metadata.
|
|
Super admin only.
|
|
"""
|
|
modules = []
|
|
for code in MODULES:
|
|
# All modules shown as enabled in the global list
|
|
modules.append(_build_module_response(code, is_enabled=True))
|
|
|
|
# Sort: core first, then alphabetically
|
|
modules.sort(key=lambda m: (not m.is_core, m.name))
|
|
|
|
logger.info(f"[MODULES] Super admin {current_user.email} listed all modules")
|
|
|
|
return ModuleListResponse(
|
|
modules=modules,
|
|
total=len(modules),
|
|
enabled=len(modules),
|
|
disabled=0,
|
|
)
|
|
|
|
|
|
@router.get("/platforms/{platform_id}", response_model=PlatformModulesResponse)
|
|
async def get_platform_modules(
|
|
platform_id: int = Path(..., description="Platform ID"),
|
|
db: Session = Depends(get_db),
|
|
current_user: UserContext = Depends(get_current_super_admin),
|
|
):
|
|
"""
|
|
Get module configuration for a platform.
|
|
|
|
Returns all modules with their enablement status for the platform.
|
|
Super admin only.
|
|
"""
|
|
# Verify platform exists
|
|
platform = platform_service.get_platform_by_id(db, platform_id)
|
|
|
|
# Get enabled module codes for this platform
|
|
enabled_codes = module_service.get_enabled_module_codes(db, platform_id)
|
|
|
|
modules = []
|
|
for code in MODULES:
|
|
is_enabled = code in enabled_codes
|
|
modules.append(_build_module_response(code, is_enabled))
|
|
|
|
# Sort: core first, then alphabetically
|
|
modules.sort(key=lambda m: (not m.is_core, m.name))
|
|
|
|
enabled_count = sum(1 for m in modules if m.is_enabled)
|
|
|
|
logger.info(
|
|
f"[MODULES] Super admin {current_user.email} fetched modules "
|
|
f"for platform {platform.code} ({enabled_count}/{len(modules)} enabled)"
|
|
)
|
|
|
|
return PlatformModulesResponse(
|
|
platform_id=platform.id,
|
|
platform_code=platform.code,
|
|
platform_name=platform.name,
|
|
modules=modules,
|
|
total=len(modules),
|
|
enabled=enabled_count,
|
|
disabled=len(modules) - enabled_count,
|
|
)
|
|
|
|
|
|
@router.put("/platforms/{platform_id}", response_model=PlatformModulesResponse)
|
|
async def update_platform_modules(
|
|
update_data: EnableModulesRequest,
|
|
platform_id: int = Path(..., description="Platform ID"),
|
|
db: Session = Depends(get_db),
|
|
current_user: UserContext = Depends(get_current_super_admin),
|
|
):
|
|
"""
|
|
Update enabled modules for a platform.
|
|
|
|
Sets the list of enabled modules. Core modules are automatically included.
|
|
Dependencies are automatically resolved.
|
|
Super admin only.
|
|
"""
|
|
# Verify platform exists
|
|
platform = platform_service.get_platform_by_id(db, platform_id)
|
|
|
|
# Update enabled modules
|
|
module_service.set_enabled_modules(db, platform_id, update_data.module_codes)
|
|
db.commit()
|
|
|
|
# Get updated module list
|
|
enabled_codes = module_service.get_enabled_module_codes(db, platform_id)
|
|
|
|
modules = []
|
|
for code in MODULES:
|
|
is_enabled = code in enabled_codes
|
|
modules.append(_build_module_response(code, is_enabled))
|
|
|
|
modules.sort(key=lambda m: (not m.is_core, m.name))
|
|
enabled_count = sum(1 for m in modules if m.is_enabled)
|
|
|
|
logger.info(
|
|
f"[MODULES] Super admin {current_user.email} updated modules "
|
|
f"for platform {platform.code}: {sorted(update_data.module_codes)}"
|
|
)
|
|
|
|
return PlatformModulesResponse(
|
|
platform_id=platform.id,
|
|
platform_code=platform.code,
|
|
platform_name=platform.name,
|
|
modules=modules,
|
|
total=len(modules),
|
|
enabled=enabled_count,
|
|
disabled=len(modules) - enabled_count,
|
|
)
|
|
|
|
|
|
@router.post("/platforms/{platform_id}/enable")
|
|
async def enable_module(
|
|
request: ToggleModuleRequest,
|
|
platform_id: int = Path(..., description="Platform ID"),
|
|
db: Session = Depends(get_db),
|
|
current_user: UserContext = Depends(get_current_super_admin),
|
|
):
|
|
"""
|
|
Enable a single module for a platform.
|
|
|
|
Also enables required dependencies.
|
|
Super admin only.
|
|
"""
|
|
# Verify platform exists
|
|
platform = platform_service.get_platform_by_id(db, platform_id)
|
|
|
|
# Validate module code
|
|
if request.module_code not in MODULES:
|
|
from app.modules.tenancy.exceptions import BadRequestException
|
|
|
|
raise BadRequestException(f"Unknown module: {request.module_code}")
|
|
|
|
# Enable module
|
|
success = module_service.enable_module(db, platform_id, request.module_code)
|
|
if success:
|
|
db.commit()
|
|
|
|
# Check what dependencies were also enabled
|
|
module = MODULES[request.module_code]
|
|
enabled_deps = module.requires if module.requires else []
|
|
|
|
logger.info(
|
|
f"[MODULES] Super admin {current_user.email} enabled module "
|
|
f"'{request.module_code}' for platform {platform.code}"
|
|
)
|
|
|
|
return {
|
|
"success": success,
|
|
"message": f"Module '{request.module_code}' enabled",
|
|
"also_enabled": enabled_deps,
|
|
}
|
|
|
|
|
|
@router.post("/platforms/{platform_id}/disable")
|
|
async def disable_module(
|
|
request: ToggleModuleRequest,
|
|
platform_id: int = Path(..., description="Platform ID"),
|
|
db: Session = Depends(get_db),
|
|
current_user: UserContext = Depends(get_current_super_admin),
|
|
):
|
|
"""
|
|
Disable a single module for a platform.
|
|
|
|
Core modules cannot be disabled.
|
|
Also disables modules that depend on this one.
|
|
Super admin only.
|
|
"""
|
|
# Verify platform exists
|
|
platform = platform_service.get_platform_by_id(db, platform_id)
|
|
|
|
# Validate module code
|
|
if request.module_code not in MODULES:
|
|
from app.modules.tenancy.exceptions import BadRequestException
|
|
|
|
raise BadRequestException(f"Unknown module: {request.module_code}")
|
|
|
|
# Check if core module
|
|
if request.module_code in get_core_module_codes():
|
|
from app.modules.tenancy.exceptions import BadRequestException
|
|
|
|
raise BadRequestException(f"Cannot disable core module: {request.module_code}")
|
|
|
|
# Get dependent modules before disabling
|
|
dependents = _get_dependent_modules(request.module_code)
|
|
|
|
# Disable module
|
|
success = module_service.disable_module(db, platform_id, request.module_code)
|
|
if success:
|
|
db.commit()
|
|
|
|
logger.info(
|
|
f"[MODULES] Super admin {current_user.email} disabled module "
|
|
f"'{request.module_code}' for platform {platform.code}"
|
|
)
|
|
|
|
return {
|
|
"success": success,
|
|
"message": f"Module '{request.module_code}' disabled",
|
|
"also_disabled": dependents if dependents else [],
|
|
}
|
|
|
|
|
|
@router.post("/platforms/{platform_id}/enable-all")
|
|
async def enable_all_modules(
|
|
platform_id: int = Path(..., description="Platform ID"),
|
|
db: Session = Depends(get_db),
|
|
current_user: UserContext = Depends(get_current_super_admin),
|
|
):
|
|
"""
|
|
Enable all modules for a platform.
|
|
|
|
Super admin only.
|
|
"""
|
|
# Verify platform exists
|
|
platform = platform_service.get_platform_by_id(db, platform_id)
|
|
|
|
# Enable all modules
|
|
all_codes = list(MODULES.keys())
|
|
module_service.set_enabled_modules(db, platform_id, all_codes)
|
|
db.commit()
|
|
|
|
logger.info(
|
|
f"[MODULES] Super admin {current_user.email} enabled all modules "
|
|
f"for platform {platform.code}"
|
|
)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "All modules enabled",
|
|
"enabled_count": len(all_codes),
|
|
}
|
|
|
|
|
|
@router.post("/platforms/{platform_id}/disable-optional")
|
|
async def disable_optional_modules(
|
|
platform_id: int = Path(..., description="Platform ID"),
|
|
db: Session = Depends(get_db),
|
|
current_user: UserContext = Depends(get_current_super_admin),
|
|
):
|
|
"""
|
|
Disable all optional modules for a platform, keeping only core modules.
|
|
|
|
Super admin only.
|
|
"""
|
|
# Verify platform exists
|
|
platform = platform_service.get_platform_by_id(db, platform_id)
|
|
|
|
# Enable only core modules
|
|
core_codes = list(get_core_module_codes())
|
|
module_service.set_enabled_modules(db, platform_id, core_codes)
|
|
db.commit()
|
|
|
|
logger.info(
|
|
f"[MODULES] Super admin {current_user.email} disabled optional modules "
|
|
f"for platform {platform.code} (kept {len(core_codes)} core modules)"
|
|
)
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "Optional modules disabled, core modules kept",
|
|
"core_modules": core_codes,
|
|
}
|