feat: complete modular platform architecture (Phases 1-5)
Phase 1 - Vendor Router Integration: - Wire up vendor module routers in app/api/v1/vendor/__init__.py - Use lazy imports via __getattr__ to avoid circular dependencies Phase 2 - Extract Remaining Modules: - Create 6 new module directories: customers, cms, analytics, messaging, dev_tools, monitoring - Each module has definition.py and route wrappers - Update registry to import from extracted modules Phase 3 - Database Table Migration: - Add PlatformModule junction table for auditable module tracking - Add migration zc2m3n4o5p6q7_add_platform_modules_table.py - Add modules relationship to Platform model - Update ModuleService with JSON-to-junction-table migration Phase 4 - Module-Specific Configuration UI: - Add /api/v1/admin/module-config/* endpoints - Add module-config.html template and JS Phase 5 - Integration Tests: - Add tests/fixtures/module_fixtures.py - Add tests/integration/api/v1/admin/test_modules.py - Add tests/integration/api/v1/modules/test_module_access.py Architecture fixes: - Fix JS-003 errors: use ...data() directly in Alpine components - Fix JS-005 warnings: add init() guards to prevent duplicate init - Fix API-001 errors: add MenuActionResponse Pydantic model - Add FE-008 noqa for dynamic number input in template Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
22
app/modules/analytics/__init__.py
Normal file
22
app/modules/analytics/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# app/modules/analytics/__init__.py
|
||||
"""
|
||||
Analytics Module - Reporting and analytics.
|
||||
|
||||
This module provides:
|
||||
- Dashboard analytics
|
||||
- Custom reports
|
||||
- Data exports
|
||||
- Performance metrics
|
||||
|
||||
Routes:
|
||||
- Vendor: /api/v1/vendor/analytics/*
|
||||
- (Admin uses dashboard for analytics)
|
||||
|
||||
Menu Items:
|
||||
- Admin: (uses dashboard)
|
||||
- Vendor: analytics
|
||||
"""
|
||||
|
||||
from app.modules.analytics.definition import analytics_module
|
||||
|
||||
__all__ = ["analytics_module"]
|
||||
54
app/modules/analytics/definition.py
Normal file
54
app/modules/analytics/definition.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# app/modules/analytics/definition.py
|
||||
"""
|
||||
Analytics module definition.
|
||||
|
||||
Defines the analytics module including its features, menu items,
|
||||
and route configurations.
|
||||
"""
|
||||
|
||||
from app.modules.base import ModuleDefinition
|
||||
from models.database.admin_menu_config import FrontendType
|
||||
|
||||
|
||||
def _get_vendor_router():
|
||||
"""Lazy import of vendor router to avoid circular imports."""
|
||||
from app.modules.analytics.routes.vendor import vendor_router
|
||||
|
||||
return vendor_router
|
||||
|
||||
|
||||
# Analytics module definition
|
||||
analytics_module = ModuleDefinition(
|
||||
code="analytics",
|
||||
name="Analytics & Reporting",
|
||||
description="Dashboard analytics, custom reports, and data exports.",
|
||||
features=[
|
||||
"basic_reports", # Basic reporting
|
||||
"analytics_dashboard", # Analytics dashboard
|
||||
"custom_reports", # Custom report builder
|
||||
"export_reports", # Export to CSV/Excel
|
||||
],
|
||||
menu_items={
|
||||
FrontendType.ADMIN: [
|
||||
# Analytics appears in dashboard for admin
|
||||
],
|
||||
FrontendType.VENDOR: [
|
||||
"analytics", # Vendor analytics page
|
||||
],
|
||||
},
|
||||
is_core=False,
|
||||
)
|
||||
|
||||
|
||||
def get_analytics_module_with_routers() -> ModuleDefinition:
|
||||
"""
|
||||
Get analytics module with routers attached.
|
||||
|
||||
This function attaches the routers lazily to avoid circular imports
|
||||
during module initialization.
|
||||
"""
|
||||
analytics_module.vendor_router = _get_vendor_router()
|
||||
return analytics_module
|
||||
|
||||
|
||||
__all__ = ["analytics_module", "get_analytics_module_with_routers"]
|
||||
26
app/modules/analytics/routes/__init__.py
Normal file
26
app/modules/analytics/routes/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# app/modules/analytics/routes/__init__.py
|
||||
"""
|
||||
Analytics module route registration.
|
||||
|
||||
This module provides functions to register analytics routes
|
||||
with module-based access control.
|
||||
|
||||
NOTE: Routers are NOT auto-imported to avoid circular dependencies.
|
||||
Import directly from vendor.py as needed:
|
||||
from app.modules.analytics.routes.vendor import vendor_router
|
||||
|
||||
Note: Analytics module has no admin routes - admin uses dashboard.
|
||||
"""
|
||||
|
||||
# Routers are imported on-demand to avoid circular dependencies
|
||||
# Do NOT add auto-imports here
|
||||
|
||||
__all__ = ["vendor_router"]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
"""Lazy import routers to avoid circular dependencies."""
|
||||
if name == "vendor_router":
|
||||
from app.modules.analytics.routes.vendor import vendor_router
|
||||
return vendor_router
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
25
app/modules/analytics/routes/vendor.py
Normal file
25
app/modules/analytics/routes/vendor.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# app/modules/analytics/routes/vendor.py
|
||||
"""
|
||||
Analytics module vendor routes.
|
||||
|
||||
This module wraps the existing vendor analytics routes and adds
|
||||
module-based access control. Routes are re-exported from the
|
||||
original location with the module access dependency.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.api.deps import require_module_access
|
||||
|
||||
# Import original router (direct import to avoid circular dependency)
|
||||
from app.api.v1.vendor.analytics import router as original_router
|
||||
|
||||
# Create module-aware router
|
||||
vendor_router = APIRouter(
|
||||
prefix="/analytics",
|
||||
dependencies=[Depends(require_module_access("analytics"))],
|
||||
)
|
||||
|
||||
# Re-export all routes from the original module with module access control
|
||||
for route in original_router.routes:
|
||||
vendor_router.routes.append(route)
|
||||
@@ -4,9 +4,25 @@ Billing module route registration.
|
||||
|
||||
This module provides functions to register billing routes
|
||||
with module-based access control.
|
||||
|
||||
NOTE: Routers are NOT auto-imported to avoid circular dependencies.
|
||||
Import directly from admin.py or vendor.py as needed:
|
||||
from app.modules.billing.routes.admin import admin_router
|
||||
from app.modules.billing.routes.vendor import vendor_router
|
||||
"""
|
||||
|
||||
from app.modules.billing.routes.admin import admin_router
|
||||
from app.modules.billing.routes.vendor import vendor_router
|
||||
# Routers are imported on-demand to avoid circular dependencies
|
||||
# Do NOT add auto-imports here
|
||||
|
||||
__all__ = ["admin_router", "vendor_router"]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
"""Lazy import routers to avoid circular dependencies."""
|
||||
if name == "admin_router":
|
||||
from app.modules.billing.routes.admin import admin_router
|
||||
return admin_router
|
||||
elif name == "vendor_router":
|
||||
from app.modules.billing.routes.vendor import vendor_router
|
||||
return vendor_router
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
22
app/modules/cms/__init__.py
Normal file
22
app/modules/cms/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# app/modules/cms/__init__.py
|
||||
"""
|
||||
CMS Module - Content Management System.
|
||||
|
||||
This module provides:
|
||||
- Content pages management
|
||||
- Media library
|
||||
- Vendor themes
|
||||
- SEO tools
|
||||
|
||||
Routes:
|
||||
- Admin: /api/v1/admin/content-pages/*
|
||||
- Vendor: /api/v1/vendor/content-pages/*, /api/v1/vendor/media/*
|
||||
|
||||
Menu Items:
|
||||
- Admin: content-pages, vendor-themes
|
||||
- Vendor: content-pages, media
|
||||
"""
|
||||
|
||||
from app.modules.cms.definition import cms_module
|
||||
|
||||
__all__ = ["cms_module"]
|
||||
66
app/modules/cms/definition.py
Normal file
66
app/modules/cms/definition.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# app/modules/cms/definition.py
|
||||
"""
|
||||
CMS module definition.
|
||||
|
||||
Defines the CMS module including its features, menu items,
|
||||
and route configurations.
|
||||
"""
|
||||
|
||||
from app.modules.base import ModuleDefinition
|
||||
from models.database.admin_menu_config import FrontendType
|
||||
|
||||
|
||||
def _get_admin_router():
|
||||
"""Lazy import of admin router to avoid circular imports."""
|
||||
from app.modules.cms.routes.admin import admin_router
|
||||
|
||||
return admin_router
|
||||
|
||||
|
||||
def _get_vendor_router():
|
||||
"""Lazy import of vendor router to avoid circular imports."""
|
||||
from app.modules.cms.routes.vendor import vendor_router
|
||||
|
||||
return vendor_router
|
||||
|
||||
|
||||
# CMS module definition
|
||||
cms_module = ModuleDefinition(
|
||||
code="cms",
|
||||
name="Content Management",
|
||||
description="Content pages, media library, and vendor themes.",
|
||||
features=[
|
||||
"cms_basic", # Basic page editing
|
||||
"cms_custom_pages", # Custom page creation
|
||||
"cms_unlimited_pages", # No page limit
|
||||
"cms_templates", # Page templates
|
||||
"cms_seo", # SEO tools
|
||||
"media_library", # Media file management
|
||||
],
|
||||
menu_items={
|
||||
FrontendType.ADMIN: [
|
||||
"content-pages", # Platform content pages
|
||||
"vendor-themes", # Theme management
|
||||
],
|
||||
FrontendType.VENDOR: [
|
||||
"content-pages", # Vendor content pages
|
||||
"media", # Media library
|
||||
],
|
||||
},
|
||||
is_core=False,
|
||||
)
|
||||
|
||||
|
||||
def get_cms_module_with_routers() -> ModuleDefinition:
|
||||
"""
|
||||
Get CMS module with routers attached.
|
||||
|
||||
This function attaches the routers lazily to avoid circular imports
|
||||
during module initialization.
|
||||
"""
|
||||
cms_module.admin_router = _get_admin_router()
|
||||
cms_module.vendor_router = _get_vendor_router()
|
||||
return cms_module
|
||||
|
||||
|
||||
__all__ = ["cms_module", "get_cms_module_with_routers"]
|
||||
31
app/modules/cms/routes/__init__.py
Normal file
31
app/modules/cms/routes/__init__.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# app/modules/cms/routes/__init__.py
|
||||
"""
|
||||
CMS module route registration.
|
||||
|
||||
This module provides functions to register CMS routes
|
||||
with module-based access control.
|
||||
|
||||
NOTE: Routers are NOT auto-imported to avoid circular dependencies.
|
||||
Import directly from admin.py or vendor.py as needed:
|
||||
from app.modules.cms.routes.admin import admin_router
|
||||
from app.modules.cms.routes.vendor import vendor_router, vendor_media_router
|
||||
"""
|
||||
|
||||
# Routers are imported on-demand to avoid circular dependencies
|
||||
# Do NOT add auto-imports here
|
||||
|
||||
__all__ = ["admin_router", "vendor_router", "vendor_media_router"]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
"""Lazy import routers to avoid circular dependencies."""
|
||||
if name == "admin_router":
|
||||
from app.modules.cms.routes.admin import admin_router
|
||||
return admin_router
|
||||
elif name == "vendor_router":
|
||||
from app.modules.cms.routes.vendor import vendor_router
|
||||
return vendor_router
|
||||
elif name == "vendor_media_router":
|
||||
from app.modules.cms.routes.vendor import vendor_media_router
|
||||
return vendor_media_router
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
25
app/modules/cms/routes/admin.py
Normal file
25
app/modules/cms/routes/admin.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# app/modules/cms/routes/admin.py
|
||||
"""
|
||||
CMS module admin routes.
|
||||
|
||||
This module wraps the existing admin content pages routes and adds
|
||||
module-based access control. Routes are re-exported from the
|
||||
original location with the module access dependency.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.api.deps import require_module_access
|
||||
|
||||
# Import original router (direct import to avoid circular dependency)
|
||||
from app.api.v1.admin.content_pages import router as original_router
|
||||
|
||||
# Create module-aware router
|
||||
admin_router = APIRouter(
|
||||
prefix="/content-pages",
|
||||
dependencies=[Depends(require_module_access("cms"))],
|
||||
)
|
||||
|
||||
# Re-export all routes from the original module with module access control
|
||||
for route in original_router.routes:
|
||||
admin_router.routes.append(route)
|
||||
39
app/modules/cms/routes/vendor.py
Normal file
39
app/modules/cms/routes/vendor.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# app/modules/cms/routes/vendor.py
|
||||
"""
|
||||
CMS module vendor routes.
|
||||
|
||||
This module wraps the existing vendor content pages and media routes
|
||||
and adds module-based access control. Routes are re-exported from the
|
||||
original location with the module access dependency.
|
||||
|
||||
Includes:
|
||||
- /content-pages/* - Content page management
|
||||
- /media/* - Media library
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.api.deps import require_module_access
|
||||
|
||||
# Import original routers (direct import to avoid circular dependency)
|
||||
from app.api.v1.vendor.content_pages import router as content_original_router
|
||||
from app.api.v1.vendor.media import router as media_original_router
|
||||
|
||||
# Create module-aware router for content pages
|
||||
vendor_router = APIRouter(
|
||||
prefix="/content-pages",
|
||||
dependencies=[Depends(require_module_access("cms"))],
|
||||
)
|
||||
|
||||
# Re-export all routes from the original content pages module
|
||||
for route in content_original_router.routes:
|
||||
vendor_router.routes.append(route)
|
||||
|
||||
# Create separate router for media library
|
||||
vendor_media_router = APIRouter(
|
||||
prefix="/media",
|
||||
dependencies=[Depends(require_module_access("cms"))],
|
||||
)
|
||||
|
||||
for route in media_original_router.routes:
|
||||
vendor_media_router.routes.append(route)
|
||||
22
app/modules/customers/__init__.py
Normal file
22
app/modules/customers/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# app/modules/customers/__init__.py
|
||||
"""
|
||||
Customers Module - Customer database and management.
|
||||
|
||||
This module provides:
|
||||
- Customer profiles and contact information
|
||||
- Customer segmentation and tags
|
||||
- Purchase history tracking
|
||||
- Customer exports
|
||||
|
||||
Routes:
|
||||
- Admin: /api/v1/admin/customers/*
|
||||
- Vendor: /api/v1/vendor/customers/*
|
||||
|
||||
Menu Items:
|
||||
- Admin: customers
|
||||
- Vendor: customers
|
||||
"""
|
||||
|
||||
from app.modules.customers.definition import customers_module
|
||||
|
||||
__all__ = ["customers_module"]
|
||||
62
app/modules/customers/definition.py
Normal file
62
app/modules/customers/definition.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# app/modules/customers/definition.py
|
||||
"""
|
||||
Customers module definition.
|
||||
|
||||
Defines the customers module including its features, menu items,
|
||||
and route configurations.
|
||||
"""
|
||||
|
||||
from app.modules.base import ModuleDefinition
|
||||
from models.database.admin_menu_config import FrontendType
|
||||
|
||||
|
||||
def _get_admin_router():
|
||||
"""Lazy import of admin router to avoid circular imports."""
|
||||
from app.modules.customers.routes.admin import admin_router
|
||||
|
||||
return admin_router
|
||||
|
||||
|
||||
def _get_vendor_router():
|
||||
"""Lazy import of vendor router to avoid circular imports."""
|
||||
from app.modules.customers.routes.vendor import vendor_router
|
||||
|
||||
return vendor_router
|
||||
|
||||
|
||||
# Customers module definition
|
||||
customers_module = ModuleDefinition(
|
||||
code="customers",
|
||||
name="Customer Management",
|
||||
description="Customer database, profiles, and segmentation.",
|
||||
features=[
|
||||
"customer_view", # View customer profiles
|
||||
"customer_export", # Export customer data
|
||||
"customer_profiles", # Detailed customer profiles
|
||||
"customer_segmentation", # Customer tagging and segments
|
||||
],
|
||||
menu_items={
|
||||
FrontendType.ADMIN: [
|
||||
"customers", # Platform-wide customer view
|
||||
],
|
||||
FrontendType.VENDOR: [
|
||||
"customers", # Vendor customer list
|
||||
],
|
||||
},
|
||||
is_core=False,
|
||||
)
|
||||
|
||||
|
||||
def get_customers_module_with_routers() -> ModuleDefinition:
|
||||
"""
|
||||
Get customers module with routers attached.
|
||||
|
||||
This function attaches the routers lazily to avoid circular imports
|
||||
during module initialization.
|
||||
"""
|
||||
customers_module.admin_router = _get_admin_router()
|
||||
customers_module.vendor_router = _get_vendor_router()
|
||||
return customers_module
|
||||
|
||||
|
||||
__all__ = ["customers_module", "get_customers_module_with_routers"]
|
||||
28
app/modules/customers/routes/__init__.py
Normal file
28
app/modules/customers/routes/__init__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# app/modules/customers/routes/__init__.py
|
||||
"""
|
||||
Customers module route registration.
|
||||
|
||||
This module provides functions to register customers routes
|
||||
with module-based access control.
|
||||
|
||||
NOTE: Routers are NOT auto-imported to avoid circular dependencies.
|
||||
Import directly from admin.py or vendor.py as needed:
|
||||
from app.modules.customers.routes.admin import admin_router
|
||||
from app.modules.customers.routes.vendor import vendor_router
|
||||
"""
|
||||
|
||||
# Routers are imported on-demand to avoid circular dependencies
|
||||
# Do NOT add auto-imports here
|
||||
|
||||
__all__ = ["admin_router", "vendor_router"]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
"""Lazy import routers to avoid circular dependencies."""
|
||||
if name == "admin_router":
|
||||
from app.modules.customers.routes.admin import admin_router
|
||||
return admin_router
|
||||
elif name == "vendor_router":
|
||||
from app.modules.customers.routes.vendor import vendor_router
|
||||
return vendor_router
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
25
app/modules/customers/routes/admin.py
Normal file
25
app/modules/customers/routes/admin.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# app/modules/customers/routes/admin.py
|
||||
"""
|
||||
Customers module admin routes.
|
||||
|
||||
This module wraps the existing admin customers routes and adds
|
||||
module-based access control. Routes are re-exported from the
|
||||
original location with the module access dependency.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.api.deps import require_module_access
|
||||
|
||||
# Import original router (direct import to avoid circular dependency)
|
||||
from app.api.v1.admin.customers import router as original_router
|
||||
|
||||
# Create module-aware router
|
||||
admin_router = APIRouter(
|
||||
prefix="/customers",
|
||||
dependencies=[Depends(require_module_access("customers"))],
|
||||
)
|
||||
|
||||
# Re-export all routes from the original module with module access control
|
||||
for route in original_router.routes:
|
||||
admin_router.routes.append(route)
|
||||
25
app/modules/customers/routes/vendor.py
Normal file
25
app/modules/customers/routes/vendor.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# app/modules/customers/routes/vendor.py
|
||||
"""
|
||||
Customers module vendor routes.
|
||||
|
||||
This module wraps the existing vendor customers routes and adds
|
||||
module-based access control. Routes are re-exported from the
|
||||
original location with the module access dependency.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.api.deps import require_module_access
|
||||
|
||||
# Import original router (direct import to avoid circular dependency)
|
||||
from app.api.v1.vendor.customers import router as original_router
|
||||
|
||||
# Create module-aware router
|
||||
vendor_router = APIRouter(
|
||||
prefix="/customers",
|
||||
dependencies=[Depends(require_module_access("customers"))],
|
||||
)
|
||||
|
||||
# Re-export all routes from the original module with module access control
|
||||
for route in original_router.routes:
|
||||
vendor_router.routes.append(route)
|
||||
21
app/modules/dev_tools/__init__.py
Normal file
21
app/modules/dev_tools/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# app/modules/dev_tools/__init__.py
|
||||
"""
|
||||
Dev-Tools Module - Developer tools and utilities.
|
||||
|
||||
This module provides:
|
||||
- Component library browser
|
||||
- Icon browser
|
||||
- Development utilities
|
||||
|
||||
Routes:
|
||||
- Admin: (page routes only, minimal API)
|
||||
- Vendor: None
|
||||
|
||||
Menu Items:
|
||||
- Admin: components, icons
|
||||
- Vendor: None
|
||||
"""
|
||||
|
||||
from app.modules.dev_tools.definition import dev_tools_module
|
||||
|
||||
__all__ = ["dev_tools_module"]
|
||||
35
app/modules/dev_tools/definition.py
Normal file
35
app/modules/dev_tools/definition.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# app/modules/dev_tools/definition.py
|
||||
"""
|
||||
Dev-Tools module definition.
|
||||
|
||||
Defines the dev-tools module including its features, menu items,
|
||||
and route configurations.
|
||||
|
||||
Note: This module primarily provides page routes, not API routes.
|
||||
"""
|
||||
|
||||
from app.modules.base import ModuleDefinition
|
||||
from models.database.admin_menu_config import FrontendType
|
||||
|
||||
|
||||
# Dev-Tools module definition
|
||||
dev_tools_module = ModuleDefinition(
|
||||
code="dev-tools",
|
||||
name="Developer Tools",
|
||||
description="Component library and icon browser for development.",
|
||||
features=[
|
||||
"component_library", # UI component browser
|
||||
"icon_browser", # Icon library browser
|
||||
],
|
||||
menu_items={
|
||||
FrontendType.ADMIN: [
|
||||
"components", # Component library page
|
||||
"icons", # Icon browser page
|
||||
],
|
||||
FrontendType.VENDOR: [], # No vendor menu items
|
||||
},
|
||||
is_core=False,
|
||||
)
|
||||
|
||||
|
||||
__all__ = ["dev_tools_module"]
|
||||
15
app/modules/dev_tools/routes/__init__.py
Normal file
15
app/modules/dev_tools/routes/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# app/modules/dev_tools/routes/__init__.py
|
||||
"""
|
||||
Dev-Tools module route registration.
|
||||
|
||||
This module provides functions to register dev-tools routes
|
||||
with module-based access control.
|
||||
|
||||
Note: Dev-Tools module has primarily page routes, not API routes.
|
||||
The page routes are defined in admin/vendor page handlers.
|
||||
"""
|
||||
|
||||
# Dev-tools has minimal API routes - primarily page routes
|
||||
# No auto-imports needed
|
||||
|
||||
__all__ = []
|
||||
@@ -4,9 +4,25 @@ Inventory module route registration.
|
||||
|
||||
This module provides functions to register inventory routes
|
||||
with module-based access control.
|
||||
|
||||
NOTE: Routers are NOT auto-imported to avoid circular dependencies.
|
||||
Import directly from admin.py or vendor.py as needed:
|
||||
from app.modules.inventory.routes.admin import admin_router
|
||||
from app.modules.inventory.routes.vendor import vendor_router
|
||||
"""
|
||||
|
||||
from app.modules.inventory.routes.admin import admin_router
|
||||
from app.modules.inventory.routes.vendor import vendor_router
|
||||
# Routers are imported on-demand to avoid circular dependencies
|
||||
# Do NOT add auto-imports here
|
||||
|
||||
__all__ = ["admin_router", "vendor_router"]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
"""Lazy import routers to avoid circular dependencies."""
|
||||
if name == "admin_router":
|
||||
from app.modules.inventory.routes.admin import admin_router
|
||||
return admin_router
|
||||
elif name == "vendor_router":
|
||||
from app.modules.inventory.routes.vendor import vendor_router
|
||||
return vendor_router
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
@@ -11,8 +11,8 @@ from fastapi import APIRouter, Depends
|
||||
|
||||
from app.api.deps import require_module_access
|
||||
|
||||
# Import original router
|
||||
from app.api.v1.admin import inventory as inventory_routes
|
||||
# Import original router (direct import to avoid circular dependency)
|
||||
from app.api.v1.admin.inventory import router as original_router
|
||||
|
||||
# Create module-aware router
|
||||
admin_router = APIRouter(
|
||||
@@ -22,5 +22,5 @@ admin_router = APIRouter(
|
||||
|
||||
# Re-export all routes from the original module with module access control
|
||||
# The routes are copied to maintain the same API structure
|
||||
for route in inventory_routes.router.routes:
|
||||
for route in original_router.routes:
|
||||
admin_router.routes.append(route)
|
||||
|
||||
@@ -11,8 +11,8 @@ from fastapi import APIRouter, Depends
|
||||
|
||||
from app.api.deps import require_module_access
|
||||
|
||||
# Import original router
|
||||
from app.api.v1.vendor import inventory as inventory_routes
|
||||
# Import original router (direct import to avoid circular dependency)
|
||||
from app.api.v1.vendor.inventory import router as original_router
|
||||
|
||||
# Create module-aware router
|
||||
vendor_router = APIRouter(
|
||||
@@ -21,5 +21,5 @@ vendor_router = APIRouter(
|
||||
)
|
||||
|
||||
# Re-export all routes from the original module with module access control
|
||||
for route in inventory_routes.router.routes:
|
||||
for route in original_router.routes:
|
||||
vendor_router.routes.append(route)
|
||||
|
||||
@@ -4,9 +4,31 @@ Marketplace module route registration.
|
||||
|
||||
This module provides functions to register marketplace routes
|
||||
with module-based access control.
|
||||
|
||||
NOTE: Routers are NOT auto-imported to avoid circular dependencies.
|
||||
Import directly from admin.py or vendor.py as needed:
|
||||
from app.modules.marketplace.routes.admin import admin_router, admin_letzshop_router
|
||||
from app.modules.marketplace.routes.vendor import vendor_router, vendor_letzshop_router
|
||||
"""
|
||||
|
||||
from app.modules.marketplace.routes.admin import admin_router, admin_letzshop_router
|
||||
from app.modules.marketplace.routes.vendor import vendor_router, vendor_letzshop_router
|
||||
# Routers are imported on-demand to avoid circular dependencies
|
||||
# Do NOT add auto-imports here
|
||||
|
||||
__all__ = ["admin_router", "admin_letzshop_router", "vendor_router", "vendor_letzshop_router"]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
"""Lazy import routers to avoid circular dependencies."""
|
||||
if name == "admin_router":
|
||||
from app.modules.marketplace.routes.admin import admin_router
|
||||
return admin_router
|
||||
elif name == "admin_letzshop_router":
|
||||
from app.modules.marketplace.routes.admin import admin_letzshop_router
|
||||
return admin_letzshop_router
|
||||
elif name == "vendor_router":
|
||||
from app.modules.marketplace.routes.vendor import vendor_router
|
||||
return vendor_router
|
||||
elif name == "vendor_letzshop_router":
|
||||
from app.modules.marketplace.routes.vendor import vendor_letzshop_router
|
||||
return vendor_letzshop_router
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
@@ -15,9 +15,9 @@ from fastapi import APIRouter, Depends
|
||||
|
||||
from app.api.deps import require_module_access
|
||||
|
||||
# Import original routers
|
||||
from app.api.v1.admin import marketplace as marketplace_routes
|
||||
from app.api.v1.admin import letzshop as letzshop_routes
|
||||
# Import original routers (direct import to avoid circular dependency)
|
||||
from app.api.v1.admin.marketplace import router as marketplace_original_router
|
||||
from app.api.v1.admin.letzshop import router as letzshop_original_router
|
||||
|
||||
# Create module-aware router for marketplace
|
||||
admin_router = APIRouter(
|
||||
@@ -26,7 +26,7 @@ admin_router = APIRouter(
|
||||
)
|
||||
|
||||
# Re-export all routes from the original marketplace module
|
||||
for route in marketplace_routes.router.routes:
|
||||
for route in marketplace_original_router.routes:
|
||||
admin_router.routes.append(route)
|
||||
|
||||
# Create separate router for letzshop integration
|
||||
@@ -35,5 +35,5 @@ admin_letzshop_router = APIRouter(
|
||||
dependencies=[Depends(require_module_access("marketplace"))],
|
||||
)
|
||||
|
||||
for route in letzshop_routes.router.routes:
|
||||
for route in letzshop_original_router.routes:
|
||||
admin_letzshop_router.routes.append(route)
|
||||
|
||||
@@ -15,9 +15,9 @@ from fastapi import APIRouter, Depends
|
||||
|
||||
from app.api.deps import require_module_access
|
||||
|
||||
# Import original routers
|
||||
from app.api.v1.vendor import marketplace as marketplace_routes
|
||||
from app.api.v1.vendor import letzshop as letzshop_routes
|
||||
# Import original routers (direct import to avoid circular dependency)
|
||||
from app.api.v1.vendor.marketplace import router as marketplace_original_router
|
||||
from app.api.v1.vendor.letzshop import router as letzshop_original_router
|
||||
|
||||
# Create module-aware router for marketplace
|
||||
vendor_router = APIRouter(
|
||||
@@ -26,7 +26,7 @@ vendor_router = APIRouter(
|
||||
)
|
||||
|
||||
# Re-export all routes from the original marketplace module
|
||||
for route in marketplace_routes.router.routes:
|
||||
for route in marketplace_original_router.routes:
|
||||
vendor_router.routes.append(route)
|
||||
|
||||
# Create separate router for letzshop integration
|
||||
@@ -35,5 +35,5 @@ vendor_letzshop_router = APIRouter(
|
||||
dependencies=[Depends(require_module_access("marketplace"))],
|
||||
)
|
||||
|
||||
for route in letzshop_routes.router.routes:
|
||||
for route in letzshop_original_router.routes:
|
||||
vendor_letzshop_router.routes.append(route)
|
||||
|
||||
22
app/modules/messaging/__init__.py
Normal file
22
app/modules/messaging/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# app/modules/messaging/__init__.py
|
||||
"""
|
||||
Messaging Module - Internal messaging and notifications.
|
||||
|
||||
This module provides:
|
||||
- Internal messages between users
|
||||
- Customer communication
|
||||
- Notification center
|
||||
- Email notifications
|
||||
|
||||
Routes:
|
||||
- Admin: /api/v1/admin/messages/*, /api/v1/admin/notifications/*
|
||||
- Vendor: /api/v1/vendor/messages/*, /api/v1/vendor/notifications/*
|
||||
|
||||
Menu Items:
|
||||
- Admin: messages, notifications
|
||||
- Vendor: messages, notifications
|
||||
"""
|
||||
|
||||
from app.modules.messaging.definition import messaging_module
|
||||
|
||||
__all__ = ["messaging_module"]
|
||||
77
app/modules/messaging/definition.py
Normal file
77
app/modules/messaging/definition.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# app/modules/messaging/definition.py
|
||||
"""
|
||||
Messaging module definition.
|
||||
|
||||
Defines the messaging module including its features, menu items,
|
||||
and route configurations.
|
||||
"""
|
||||
|
||||
from app.modules.base import ModuleDefinition
|
||||
from models.database.admin_menu_config import FrontendType
|
||||
|
||||
|
||||
def _get_admin_router():
|
||||
"""Lazy import of admin router to avoid circular imports."""
|
||||
from app.modules.messaging.routes.admin import admin_router
|
||||
|
||||
return admin_router
|
||||
|
||||
|
||||
def _get_admin_notifications_router():
|
||||
"""Lazy import of admin notifications router to avoid circular imports."""
|
||||
from app.modules.messaging.routes.admin import admin_notifications_router
|
||||
|
||||
return admin_notifications_router
|
||||
|
||||
|
||||
def _get_vendor_router():
|
||||
"""Lazy import of vendor router to avoid circular imports."""
|
||||
from app.modules.messaging.routes.vendor import vendor_router
|
||||
|
||||
return vendor_router
|
||||
|
||||
|
||||
def _get_vendor_notifications_router():
|
||||
"""Lazy import of vendor notifications router to avoid circular imports."""
|
||||
from app.modules.messaging.routes.vendor import vendor_notifications_router
|
||||
|
||||
return vendor_notifications_router
|
||||
|
||||
|
||||
# Messaging module definition
|
||||
messaging_module = ModuleDefinition(
|
||||
code="messaging",
|
||||
name="Messaging & Notifications",
|
||||
description="Internal messages, customer communication, and notifications.",
|
||||
features=[
|
||||
"customer_messaging", # Customer communication
|
||||
"internal_messages", # Internal team messages
|
||||
"notification_center", # Notification management
|
||||
],
|
||||
menu_items={
|
||||
FrontendType.ADMIN: [
|
||||
"messages", # Admin messages
|
||||
"notifications", # Admin notifications
|
||||
],
|
||||
FrontendType.VENDOR: [
|
||||
"messages", # Vendor messages
|
||||
"notifications", # Vendor notifications
|
||||
],
|
||||
},
|
||||
is_core=False,
|
||||
)
|
||||
|
||||
|
||||
def get_messaging_module_with_routers() -> ModuleDefinition:
|
||||
"""
|
||||
Get messaging module with routers attached.
|
||||
|
||||
This function attaches the routers lazily to avoid circular imports
|
||||
during module initialization.
|
||||
"""
|
||||
messaging_module.admin_router = _get_admin_router()
|
||||
messaging_module.vendor_router = _get_vendor_router()
|
||||
return messaging_module
|
||||
|
||||
|
||||
__all__ = ["messaging_module", "get_messaging_module_with_routers"]
|
||||
34
app/modules/messaging/routes/__init__.py
Normal file
34
app/modules/messaging/routes/__init__.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# app/modules/messaging/routes/__init__.py
|
||||
"""
|
||||
Messaging module route registration.
|
||||
|
||||
This module provides functions to register messaging routes
|
||||
with module-based access control.
|
||||
|
||||
NOTE: Routers are NOT auto-imported to avoid circular dependencies.
|
||||
Import directly from admin.py or vendor.py as needed:
|
||||
from app.modules.messaging.routes.admin import admin_router, admin_notifications_router
|
||||
from app.modules.messaging.routes.vendor import vendor_router, vendor_notifications_router
|
||||
"""
|
||||
|
||||
# Routers are imported on-demand to avoid circular dependencies
|
||||
# Do NOT add auto-imports here
|
||||
|
||||
__all__ = ["admin_router", "admin_notifications_router", "vendor_router", "vendor_notifications_router"]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
"""Lazy import routers to avoid circular dependencies."""
|
||||
if name == "admin_router":
|
||||
from app.modules.messaging.routes.admin import admin_router
|
||||
return admin_router
|
||||
elif name == "admin_notifications_router":
|
||||
from app.modules.messaging.routes.admin import admin_notifications_router
|
||||
return admin_notifications_router
|
||||
elif name == "vendor_router":
|
||||
from app.modules.messaging.routes.vendor import vendor_router
|
||||
return vendor_router
|
||||
elif name == "vendor_notifications_router":
|
||||
from app.modules.messaging.routes.vendor import vendor_notifications_router
|
||||
return vendor_notifications_router
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
39
app/modules/messaging/routes/admin.py
Normal file
39
app/modules/messaging/routes/admin.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# app/modules/messaging/routes/admin.py
|
||||
"""
|
||||
Messaging module admin routes.
|
||||
|
||||
This module wraps the existing admin messages and notifications routes
|
||||
and adds module-based access control. Routes are re-exported from the
|
||||
original location with the module access dependency.
|
||||
|
||||
Includes:
|
||||
- /messages/* - Message management
|
||||
- /notifications/* - Notification management
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.api.deps import require_module_access
|
||||
|
||||
# Import original routers (direct import to avoid circular dependency)
|
||||
from app.api.v1.admin.messages import router as messages_original_router
|
||||
from app.api.v1.admin.notifications import router as notifications_original_router
|
||||
|
||||
# Create module-aware router for messages
|
||||
admin_router = APIRouter(
|
||||
prefix="/messages",
|
||||
dependencies=[Depends(require_module_access("messaging"))],
|
||||
)
|
||||
|
||||
# Re-export all routes from the original messages module
|
||||
for route in messages_original_router.routes:
|
||||
admin_router.routes.append(route)
|
||||
|
||||
# Create separate router for notifications
|
||||
admin_notifications_router = APIRouter(
|
||||
prefix="/notifications",
|
||||
dependencies=[Depends(require_module_access("messaging"))],
|
||||
)
|
||||
|
||||
for route in notifications_original_router.routes:
|
||||
admin_notifications_router.routes.append(route)
|
||||
39
app/modules/messaging/routes/vendor.py
Normal file
39
app/modules/messaging/routes/vendor.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# app/modules/messaging/routes/vendor.py
|
||||
"""
|
||||
Messaging module vendor routes.
|
||||
|
||||
This module wraps the existing vendor messages and notifications routes
|
||||
and adds module-based access control. Routes are re-exported from the
|
||||
original location with the module access dependency.
|
||||
|
||||
Includes:
|
||||
- /messages/* - Message management
|
||||
- /notifications/* - Notification management
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.api.deps import require_module_access
|
||||
|
||||
# Import original routers (direct import to avoid circular dependency)
|
||||
from app.api.v1.vendor.messages import router as messages_original_router
|
||||
from app.api.v1.vendor.notifications import router as notifications_original_router
|
||||
|
||||
# Create module-aware router for messages
|
||||
vendor_router = APIRouter(
|
||||
prefix="/messages",
|
||||
dependencies=[Depends(require_module_access("messaging"))],
|
||||
)
|
||||
|
||||
# Re-export all routes from the original messages module
|
||||
for route in messages_original_router.routes:
|
||||
vendor_router.routes.append(route)
|
||||
|
||||
# Create separate router for notifications
|
||||
vendor_notifications_router = APIRouter(
|
||||
prefix="/notifications",
|
||||
dependencies=[Depends(require_module_access("messaging"))],
|
||||
)
|
||||
|
||||
for route in notifications_original_router.routes:
|
||||
vendor_notifications_router.routes.append(route)
|
||||
25
app/modules/monitoring/__init__.py
Normal file
25
app/modules/monitoring/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# app/modules/monitoring/__init__.py
|
||||
"""
|
||||
Monitoring Module - Platform monitoring and system health.
|
||||
|
||||
This module provides:
|
||||
- Application logs
|
||||
- Background tasks monitoring
|
||||
- Import job tracking
|
||||
- Platform health metrics
|
||||
- Testing hub
|
||||
- Code quality tools
|
||||
|
||||
Routes:
|
||||
- Admin: /api/v1/admin/logs/*, /api/v1/admin/background-tasks/*,
|
||||
/api/v1/admin/tests/*, /api/v1/admin/code-quality/*
|
||||
- Vendor: None
|
||||
|
||||
Menu Items:
|
||||
- Admin: imports, background-tasks, logs, platform-health, testing, code-quality
|
||||
- Vendor: None
|
||||
"""
|
||||
|
||||
from app.modules.monitoring.definition import monitoring_module
|
||||
|
||||
__all__ = ["monitoring_module"]
|
||||
59
app/modules/monitoring/definition.py
Normal file
59
app/modules/monitoring/definition.py
Normal file
@@ -0,0 +1,59 @@
|
||||
# app/modules/monitoring/definition.py
|
||||
"""
|
||||
Monitoring module definition.
|
||||
|
||||
Defines the monitoring module including its features, menu items,
|
||||
and route configurations.
|
||||
"""
|
||||
|
||||
from app.modules.base import ModuleDefinition
|
||||
from models.database.admin_menu_config import FrontendType
|
||||
|
||||
|
||||
def _get_admin_router():
|
||||
"""Lazy import of admin router to avoid circular imports."""
|
||||
from app.modules.monitoring.routes.admin import admin_router
|
||||
|
||||
return admin_router
|
||||
|
||||
|
||||
# Monitoring module definition
|
||||
monitoring_module = ModuleDefinition(
|
||||
code="monitoring",
|
||||
name="Platform Monitoring",
|
||||
description="Logs, background tasks, imports, and system health.",
|
||||
features=[
|
||||
"application_logs", # Log viewing
|
||||
"background_tasks", # Task monitoring
|
||||
"import_jobs", # Import job tracking
|
||||
"capacity_monitoring", # System capacity
|
||||
"testing_hub", # Test runner
|
||||
"code_quality", # Code quality tools
|
||||
],
|
||||
menu_items={
|
||||
FrontendType.ADMIN: [
|
||||
"imports", # Import jobs
|
||||
"background-tasks", # Background tasks
|
||||
"logs", # Application logs
|
||||
"platform-health", # Platform health
|
||||
"testing", # Testing hub
|
||||
"code-quality", # Code quality
|
||||
],
|
||||
FrontendType.VENDOR: [], # No vendor menu items
|
||||
},
|
||||
is_core=False,
|
||||
)
|
||||
|
||||
|
||||
def get_monitoring_module_with_routers() -> ModuleDefinition:
|
||||
"""
|
||||
Get monitoring module with routers attached.
|
||||
|
||||
This function attaches the routers lazily to avoid circular imports
|
||||
during module initialization.
|
||||
"""
|
||||
monitoring_module.admin_router = _get_admin_router()
|
||||
return monitoring_module
|
||||
|
||||
|
||||
__all__ = ["monitoring_module", "get_monitoring_module_with_routers"]
|
||||
26
app/modules/monitoring/routes/__init__.py
Normal file
26
app/modules/monitoring/routes/__init__.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# app/modules/monitoring/routes/__init__.py
|
||||
"""
|
||||
Monitoring module route registration.
|
||||
|
||||
This module provides functions to register monitoring routes
|
||||
with module-based access control.
|
||||
|
||||
NOTE: Routers are NOT auto-imported to avoid circular dependencies.
|
||||
Import directly from admin.py as needed:
|
||||
from app.modules.monitoring.routes.admin import admin_router
|
||||
|
||||
Note: Monitoring module has no vendor routes.
|
||||
"""
|
||||
|
||||
# Routers are imported on-demand to avoid circular dependencies
|
||||
# Do NOT add auto-imports here
|
||||
|
||||
__all__ = ["admin_router"]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
"""Lazy import routers to avoid circular dependencies."""
|
||||
if name == "admin_router":
|
||||
from app.modules.monitoring.routes.admin import admin_router
|
||||
return admin_router
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
53
app/modules/monitoring/routes/admin.py
Normal file
53
app/modules/monitoring/routes/admin.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# app/modules/monitoring/routes/admin.py
|
||||
"""
|
||||
Monitoring module admin routes.
|
||||
|
||||
This module wraps the existing admin monitoring routes and adds
|
||||
module-based access control. Routes are re-exported from the
|
||||
original location with the module access dependency.
|
||||
|
||||
Includes:
|
||||
- /logs/* - Application logs
|
||||
- /background-tasks/* - Background task monitoring
|
||||
- /tests/* - Test runner
|
||||
- /code-quality/* - Code quality tools
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from app.api.deps import require_module_access
|
||||
|
||||
# Import original routers (direct import to avoid circular dependency)
|
||||
from app.api.v1.admin.logs import router as logs_original_router
|
||||
from app.api.v1.admin.background_tasks import router as tasks_original_router
|
||||
from app.api.v1.admin.tests import router as tests_original_router
|
||||
from app.api.v1.admin.code_quality import router as code_quality_original_router
|
||||
|
||||
# Create module-aware router for logs
|
||||
admin_router = APIRouter(
|
||||
prefix="/monitoring",
|
||||
dependencies=[Depends(require_module_access("monitoring"))],
|
||||
)
|
||||
|
||||
# Create sub-routers for each component
|
||||
logs_router = APIRouter(prefix="/logs")
|
||||
for route in logs_original_router.routes:
|
||||
logs_router.routes.append(route)
|
||||
|
||||
tasks_router = APIRouter(prefix="/background-tasks")
|
||||
for route in tasks_original_router.routes:
|
||||
tasks_router.routes.append(route)
|
||||
|
||||
tests_router = APIRouter(prefix="/tests")
|
||||
for route in tests_original_router.routes:
|
||||
tests_router.routes.append(route)
|
||||
|
||||
code_quality_router = APIRouter(prefix="/code-quality")
|
||||
for route in code_quality_original_router.routes:
|
||||
code_quality_router.routes.append(route)
|
||||
|
||||
# Include all sub-routers
|
||||
admin_router.include_router(logs_router)
|
||||
admin_router.include_router(tasks_router)
|
||||
admin_router.include_router(tests_router)
|
||||
admin_router.include_router(code_quality_router)
|
||||
@@ -4,9 +4,25 @@ Orders module route registration.
|
||||
|
||||
This module provides functions to register orders routes
|
||||
with module-based access control.
|
||||
|
||||
NOTE: Routers are NOT auto-imported to avoid circular dependencies.
|
||||
Import directly from admin.py or vendor.py as needed:
|
||||
from app.modules.orders.routes.admin import admin_router
|
||||
from app.modules.orders.routes.vendor import vendor_router
|
||||
"""
|
||||
|
||||
from app.modules.orders.routes.admin import admin_router
|
||||
from app.modules.orders.routes.vendor import vendor_router
|
||||
# Routers are imported on-demand to avoid circular dependencies
|
||||
# Do NOT add auto-imports here
|
||||
|
||||
__all__ = ["admin_router", "vendor_router"]
|
||||
|
||||
|
||||
def __getattr__(name: str):
|
||||
"""Lazy import routers to avoid circular dependencies."""
|
||||
if name == "admin_router":
|
||||
from app.modules.orders.routes.admin import admin_router
|
||||
return admin_router
|
||||
elif name == "vendor_router":
|
||||
from app.modules.orders.routes.vendor import vendor_router
|
||||
return vendor_router
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
@@ -15,9 +15,9 @@ from fastapi import APIRouter, Depends
|
||||
|
||||
from app.api.deps import require_module_access
|
||||
|
||||
# Import original routers
|
||||
from app.api.v1.admin import orders as orders_routes
|
||||
from app.api.v1.admin import order_item_exceptions as exceptions_routes
|
||||
# Import original routers (direct import to avoid circular dependency)
|
||||
from app.api.v1.admin.orders import router as orders_original_router
|
||||
from app.api.v1.admin.order_item_exceptions import router as exceptions_original_router
|
||||
|
||||
# Create module-aware router for orders
|
||||
admin_router = APIRouter(
|
||||
@@ -26,7 +26,7 @@ admin_router = APIRouter(
|
||||
)
|
||||
|
||||
# Re-export all routes from the original orders module
|
||||
for route in orders_routes.router.routes:
|
||||
for route in orders_original_router.routes:
|
||||
admin_router.routes.append(route)
|
||||
|
||||
# Create separate router for order item exceptions
|
||||
@@ -36,5 +36,5 @@ admin_exceptions_router = APIRouter(
|
||||
dependencies=[Depends(require_module_access("orders"))],
|
||||
)
|
||||
|
||||
for route in exceptions_routes.router.routes:
|
||||
for route in exceptions_original_router.routes:
|
||||
admin_exceptions_router.routes.append(route)
|
||||
|
||||
@@ -15,9 +15,9 @@ from fastapi import APIRouter, Depends
|
||||
|
||||
from app.api.deps import require_module_access
|
||||
|
||||
# Import original routers
|
||||
from app.api.v1.vendor import orders as orders_routes
|
||||
from app.api.v1.vendor import order_item_exceptions as exceptions_routes
|
||||
# Import original routers (direct import to avoid circular dependency)
|
||||
from app.api.v1.vendor.orders import router as orders_original_router
|
||||
from app.api.v1.vendor.order_item_exceptions import router as exceptions_original_router
|
||||
|
||||
# Create module-aware router for orders
|
||||
vendor_router = APIRouter(
|
||||
@@ -26,7 +26,7 @@ vendor_router = APIRouter(
|
||||
)
|
||||
|
||||
# Re-export all routes from the original orders module
|
||||
for route in orders_routes.router.routes:
|
||||
for route in orders_original_router.routes:
|
||||
vendor_router.routes.append(route)
|
||||
|
||||
# Create separate router for order item exceptions
|
||||
@@ -35,5 +35,5 @@ vendor_exceptions_router = APIRouter(
|
||||
dependencies=[Depends(require_module_access("orders"))],
|
||||
)
|
||||
|
||||
for route in exceptions_routes.router.routes:
|
||||
for route in exceptions_original_router.routes:
|
||||
vendor_exceptions_router.routes.append(route)
|
||||
|
||||
@@ -24,6 +24,12 @@ from app.modules.billing.definition import billing_module
|
||||
from app.modules.inventory.definition import inventory_module
|
||||
from app.modules.marketplace.definition import marketplace_module
|
||||
from app.modules.orders.definition import orders_module
|
||||
from app.modules.customers.definition import customers_module
|
||||
from app.modules.cms.definition import cms_module
|
||||
from app.modules.analytics.definition import analytics_module
|
||||
from app.modules.messaging.definition import messaging_module
|
||||
from app.modules.dev_tools.definition import dev_tools_module
|
||||
from app.modules.monitoring.definition import monitoring_module
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -93,127 +99,18 @@ MODULES: dict[str, ModuleDefinition] = {
|
||||
"orders": orders_module,
|
||||
# Marketplace module - imported from app/modules/marketplace/
|
||||
"marketplace": marketplace_module,
|
||||
"customers": ModuleDefinition(
|
||||
code="customers",
|
||||
name="Customer Management",
|
||||
description="Customer database, profiles, and segmentation.",
|
||||
features=[
|
||||
"customer_view",
|
||||
"customer_export",
|
||||
"customer_profiles",
|
||||
"customer_segmentation",
|
||||
],
|
||||
menu_items={
|
||||
FrontendType.ADMIN: [
|
||||
"customers",
|
||||
],
|
||||
FrontendType.VENDOR: [
|
||||
"customers",
|
||||
],
|
||||
},
|
||||
),
|
||||
"cms": ModuleDefinition(
|
||||
code="cms",
|
||||
name="Content Management",
|
||||
description="Content pages, media library, and vendor themes.",
|
||||
features=[
|
||||
"cms_basic",
|
||||
"cms_custom_pages",
|
||||
"cms_unlimited_pages",
|
||||
"cms_templates",
|
||||
"cms_seo",
|
||||
"media_library",
|
||||
],
|
||||
menu_items={
|
||||
FrontendType.ADMIN: [
|
||||
"content-pages",
|
||||
"vendor-themes",
|
||||
],
|
||||
FrontendType.VENDOR: [
|
||||
"content-pages",
|
||||
"media",
|
||||
],
|
||||
},
|
||||
),
|
||||
"analytics": ModuleDefinition(
|
||||
code="analytics",
|
||||
name="Analytics & Reporting",
|
||||
description="Dashboard analytics, custom reports, and data exports.",
|
||||
features=[
|
||||
"basic_reports",
|
||||
"analytics_dashboard",
|
||||
"custom_reports",
|
||||
"export_reports",
|
||||
],
|
||||
menu_items={
|
||||
FrontendType.ADMIN: [
|
||||
# Analytics appears in dashboard for admin
|
||||
],
|
||||
FrontendType.VENDOR: [
|
||||
"analytics",
|
||||
],
|
||||
},
|
||||
),
|
||||
"messaging": ModuleDefinition(
|
||||
code="messaging",
|
||||
name="Messaging & Notifications",
|
||||
description="Internal messages, customer communication, and notifications.",
|
||||
features=[
|
||||
"customer_messaging",
|
||||
"internal_messages",
|
||||
"notification_center",
|
||||
],
|
||||
menu_items={
|
||||
FrontendType.ADMIN: [
|
||||
"messages",
|
||||
"notifications",
|
||||
],
|
||||
FrontendType.VENDOR: [
|
||||
"messages",
|
||||
"notifications",
|
||||
],
|
||||
},
|
||||
),
|
||||
"dev-tools": ModuleDefinition(
|
||||
code="dev-tools",
|
||||
name="Developer Tools",
|
||||
description="Component library and icon browser for development.",
|
||||
features=[
|
||||
"component_library",
|
||||
"icon_browser",
|
||||
],
|
||||
menu_items={
|
||||
FrontendType.ADMIN: [
|
||||
"components",
|
||||
"icons",
|
||||
],
|
||||
FrontendType.VENDOR: [],
|
||||
},
|
||||
),
|
||||
"monitoring": ModuleDefinition(
|
||||
code="monitoring",
|
||||
name="Platform Monitoring",
|
||||
description="Logs, background tasks, imports, and system health.",
|
||||
features=[
|
||||
"application_logs",
|
||||
"background_tasks",
|
||||
"import_jobs",
|
||||
"capacity_monitoring",
|
||||
"testing_hub",
|
||||
"code_quality",
|
||||
],
|
||||
menu_items={
|
||||
FrontendType.ADMIN: [
|
||||
"imports",
|
||||
"background-tasks",
|
||||
"logs",
|
||||
"platform-health",
|
||||
"testing",
|
||||
"code-quality",
|
||||
],
|
||||
FrontendType.VENDOR: [],
|
||||
},
|
||||
),
|
||||
# Customers module - imported from app/modules/customers/
|
||||
"customers": customers_module,
|
||||
# CMS module - imported from app/modules/cms/
|
||||
"cms": cms_module,
|
||||
# Analytics module - imported from app/modules/analytics/
|
||||
"analytics": analytics_module,
|
||||
# Messaging module - imported from app/modules/messaging/
|
||||
"messaging": messaging_module,
|
||||
# Dev-Tools module - imported from app/modules/dev_tools/
|
||||
"dev-tools": dev_tools_module,
|
||||
# Monitoring module - imported from app/modules/monitoring/
|
||||
"monitoring": monitoring_module,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,11 +5,15 @@ Module service for platform module operations.
|
||||
Provides methods to check module enablement, get enabled modules,
|
||||
and filter menu items based on module configuration.
|
||||
|
||||
Module configuration is stored in Platform.settings["enabled_modules"].
|
||||
If not configured, all modules are enabled (backwards compatibility).
|
||||
Module configuration can be stored in two places:
|
||||
1. PlatformModule junction table (preferred, auditable)
|
||||
2. Platform.settings["enabled_modules"] (fallback, legacy)
|
||||
|
||||
If neither is configured, all modules are enabled (backwards compatibility).
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from functools import lru_cache
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -23,6 +27,7 @@ from app.modules.registry import (
|
||||
)
|
||||
from models.database.admin_menu_config import FrontendType
|
||||
from models.database.platform import Platform
|
||||
from models.database.platform_module import PlatformModule
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -34,11 +39,20 @@ class ModuleService:
|
||||
Handles module enablement checking, module listing, and menu item filtering
|
||||
based on enabled modules.
|
||||
|
||||
Module configuration is stored in Platform.settings["enabled_modules"]:
|
||||
- If key exists: Only listed modules (plus core) are enabled
|
||||
- If key missing: All modules are enabled (backwards compatibility)
|
||||
Module configuration is stored in two places (with fallback):
|
||||
1. PlatformModule junction table (preferred, auditable)
|
||||
2. Platform.settings["enabled_modules"] (legacy fallback)
|
||||
|
||||
Example Platform.settings:
|
||||
The service checks the junction table first. If no records exist,
|
||||
it falls back to the JSON settings for backwards compatibility.
|
||||
|
||||
If neither is configured, all modules are enabled (backwards compatibility).
|
||||
|
||||
Example PlatformModule records:
|
||||
PlatformModule(platform_id=1, module_code="billing", is_enabled=True, config={"stripe_mode": "live"})
|
||||
PlatformModule(platform_id=1, module_code="inventory", is_enabled=True, config={"low_stock_threshold": 10})
|
||||
|
||||
Legacy Platform.settings (fallback):
|
||||
{
|
||||
"enabled_modules": ["core", "billing", "inventory", "orders"],
|
||||
"module_config": {
|
||||
@@ -119,10 +133,13 @@ class ModuleService:
|
||||
platform_id: int,
|
||||
) -> set[str]:
|
||||
"""
|
||||
Get enabled module codes from platform settings.
|
||||
Get enabled module codes for a platform.
|
||||
|
||||
Internal method that reads Platform.settings["enabled_modules"].
|
||||
If not configured, returns all module codes (backwards compatibility).
|
||||
Checks two sources with fallback:
|
||||
1. PlatformModule junction table (preferred, auditable)
|
||||
2. Platform.settings["enabled_modules"] (legacy fallback)
|
||||
|
||||
If neither is configured, returns all module codes (backwards compatibility).
|
||||
Always includes core modules.
|
||||
|
||||
Args:
|
||||
@@ -137,22 +154,102 @@ class ModuleService:
|
||||
logger.warning(f"Platform {platform_id} not found, returning all modules")
|
||||
return set(MODULES.keys())
|
||||
|
||||
settings = platform.settings or {}
|
||||
enabled_modules = settings.get("enabled_modules")
|
||||
# Try junction table first (preferred)
|
||||
platform_modules = (
|
||||
db.query(PlatformModule)
|
||||
.filter(PlatformModule.platform_id == platform_id)
|
||||
.all()
|
||||
)
|
||||
|
||||
# If not configured, enable all modules (backwards compatibility)
|
||||
if enabled_modules is None:
|
||||
return set(MODULES.keys())
|
||||
if platform_modules:
|
||||
# Use junction table data
|
||||
enabled_set = {pm.module_code for pm in platform_modules if pm.is_enabled}
|
||||
else:
|
||||
# Fallback to JSON settings (legacy)
|
||||
settings = platform.settings or {}
|
||||
enabled_modules = settings.get("enabled_modules")
|
||||
|
||||
# If not configured, enable all modules (backwards compatibility)
|
||||
if enabled_modules is None:
|
||||
return set(MODULES.keys())
|
||||
|
||||
enabled_set = set(enabled_modules)
|
||||
|
||||
# Always include core modules
|
||||
core_codes = get_core_module_codes()
|
||||
enabled_set = set(enabled_modules) | core_codes
|
||||
enabled_set = enabled_set | core_codes
|
||||
|
||||
# Resolve dependencies - add required modules
|
||||
enabled_set = self._resolve_dependencies(enabled_set)
|
||||
|
||||
return enabled_set
|
||||
|
||||
def _migrate_json_to_junction_table(
|
||||
self,
|
||||
db: Session,
|
||||
platform_id: int,
|
||||
user_id: int | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Migrate JSON settings to junction table records.
|
||||
|
||||
Called when first creating a junction table record for a platform
|
||||
that previously used JSON settings. This ensures consistency when
|
||||
mixing junction table and JSON approaches.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
platform_id: Platform ID
|
||||
user_id: ID of user performing the migration (for audit)
|
||||
"""
|
||||
# Check if any junction table records exist
|
||||
existing_count = (
|
||||
db.query(PlatformModule)
|
||||
.filter(PlatformModule.platform_id == platform_id)
|
||||
.count()
|
||||
)
|
||||
|
||||
if existing_count > 0:
|
||||
# Already using junction table
|
||||
return
|
||||
|
||||
platform = db.query(Platform).filter(Platform.id == platform_id).first()
|
||||
if not platform:
|
||||
return
|
||||
|
||||
settings = platform.settings or {}
|
||||
enabled_modules = settings.get("enabled_modules")
|
||||
|
||||
if enabled_modules is None:
|
||||
# No JSON settings, start fresh with all modules enabled
|
||||
enabled_codes = set(MODULES.keys())
|
||||
else:
|
||||
enabled_codes = set(enabled_modules) | get_core_module_codes()
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
# Create junction table records for all known modules
|
||||
for code in MODULES.keys():
|
||||
is_enabled = code in enabled_codes
|
||||
pm = PlatformModule(
|
||||
platform_id=platform_id,
|
||||
module_code=code,
|
||||
is_enabled=is_enabled,
|
||||
enabled_at=now if is_enabled else None,
|
||||
enabled_by_user_id=user_id if is_enabled else None,
|
||||
disabled_at=None if is_enabled else now,
|
||||
disabled_by_user_id=None if is_enabled else user_id,
|
||||
config={},
|
||||
)
|
||||
db.add(pm)
|
||||
|
||||
# Flush to ensure records are visible to subsequent queries
|
||||
db.flush()
|
||||
|
||||
logger.info(
|
||||
f"Migrated platform {platform_id} from JSON settings to junction table"
|
||||
)
|
||||
|
||||
def _resolve_dependencies(self, enabled_codes: set[str]) -> set[str]:
|
||||
"""
|
||||
Resolve module dependencies by adding required modules.
|
||||
@@ -283,6 +380,10 @@ class ModuleService:
|
||||
"""
|
||||
Get module-specific configuration for a platform.
|
||||
|
||||
Checks two sources with fallback:
|
||||
1. PlatformModule.config (preferred, auditable)
|
||||
2. Platform.settings["module_config"] (legacy fallback)
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
platform_id: Platform ID
|
||||
@@ -291,6 +392,20 @@ class ModuleService:
|
||||
Returns:
|
||||
Module configuration dict (empty if not configured)
|
||||
"""
|
||||
# Try junction table first (preferred)
|
||||
platform_module = (
|
||||
db.query(PlatformModule)
|
||||
.filter(
|
||||
PlatformModule.platform_id == platform_id,
|
||||
PlatformModule.module_code == module_code,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if platform_module:
|
||||
return platform_module.config or {}
|
||||
|
||||
# Fallback to JSON settings (legacy)
|
||||
platform = db.query(Platform).filter(Platform.id == platform_id).first()
|
||||
if not platform:
|
||||
return {}
|
||||
@@ -299,22 +414,80 @@ class ModuleService:
|
||||
module_configs = settings.get("module_config", {})
|
||||
return module_configs.get(module_code, {})
|
||||
|
||||
def set_module_config(
|
||||
self,
|
||||
db: Session,
|
||||
platform_id: int,
|
||||
module_code: str,
|
||||
config: dict,
|
||||
) -> bool:
|
||||
"""
|
||||
Set module-specific configuration for a platform.
|
||||
|
||||
Uses junction table for persistence. Creates record if doesn't exist.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
platform_id: Platform ID
|
||||
module_code: Module code
|
||||
config: Configuration dict to set
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
"""
|
||||
if module_code not in MODULES:
|
||||
logger.error(f"Unknown module: {module_code}")
|
||||
return False
|
||||
|
||||
platform = db.query(Platform).filter(Platform.id == platform_id).first()
|
||||
if not platform:
|
||||
logger.error(f"Platform {platform_id} not found")
|
||||
return False
|
||||
|
||||
# Get or create junction table record
|
||||
platform_module = (
|
||||
db.query(PlatformModule)
|
||||
.filter(
|
||||
PlatformModule.platform_id == platform_id,
|
||||
PlatformModule.module_code == module_code,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if platform_module:
|
||||
platform_module.config = config
|
||||
else:
|
||||
# Create new record with config
|
||||
platform_module = PlatformModule(
|
||||
platform_id=platform_id,
|
||||
module_code=module_code,
|
||||
is_enabled=True, # Default to enabled
|
||||
config=config,
|
||||
)
|
||||
db.add(platform_module)
|
||||
|
||||
logger.info(f"Updated config for module '{module_code}' on platform {platform_id}")
|
||||
return True
|
||||
|
||||
def set_enabled_modules(
|
||||
self,
|
||||
db: Session,
|
||||
platform_id: int,
|
||||
module_codes: list[str],
|
||||
user_id: int | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Set the enabled modules for a platform.
|
||||
|
||||
Core modules are automatically included.
|
||||
Dependencies are automatically resolved.
|
||||
Uses junction table for auditability.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
platform_id: Platform ID
|
||||
module_codes: List of module codes to enable
|
||||
user_id: ID of user making the change (for audit)
|
||||
|
||||
Returns:
|
||||
True if successful, False if platform not found
|
||||
@@ -338,10 +511,44 @@ class ModuleService:
|
||||
# Resolve dependencies
|
||||
enabled_set = self._resolve_dependencies(enabled_set)
|
||||
|
||||
# Update platform settings
|
||||
settings = platform.settings or {}
|
||||
settings["enabled_modules"] = list(enabled_set)
|
||||
platform.settings = settings
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
# Update junction table for all modules
|
||||
for code in MODULES.keys():
|
||||
platform_module = (
|
||||
db.query(PlatformModule)
|
||||
.filter(
|
||||
PlatformModule.platform_id == platform_id,
|
||||
PlatformModule.module_code == code,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
should_enable = code in enabled_set
|
||||
|
||||
if platform_module:
|
||||
# Update existing record
|
||||
if should_enable and not platform_module.is_enabled:
|
||||
platform_module.is_enabled = True
|
||||
platform_module.enabled_at = now
|
||||
platform_module.enabled_by_user_id = user_id
|
||||
elif not should_enable and platform_module.is_enabled:
|
||||
platform_module.is_enabled = False
|
||||
platform_module.disabled_at = now
|
||||
platform_module.disabled_by_user_id = user_id
|
||||
else:
|
||||
# Create new record
|
||||
platform_module = PlatformModule(
|
||||
platform_id=platform_id,
|
||||
module_code=code,
|
||||
is_enabled=should_enable,
|
||||
enabled_at=now if should_enable else None,
|
||||
enabled_by_user_id=user_id if should_enable else None,
|
||||
disabled_at=None if should_enable else now,
|
||||
disabled_by_user_id=None if should_enable else user_id,
|
||||
config={},
|
||||
)
|
||||
db.add(platform_module)
|
||||
|
||||
logger.info(
|
||||
f"Updated enabled modules for platform {platform_id}: {sorted(enabled_set)}"
|
||||
@@ -353,16 +560,19 @@ class ModuleService:
|
||||
db: Session,
|
||||
platform_id: int,
|
||||
module_code: str,
|
||||
user_id: int | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Enable a single module for a platform.
|
||||
|
||||
Also enables required dependencies.
|
||||
Uses junction table for auditability when available.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
platform_id: Platform ID
|
||||
module_code: Module code to enable
|
||||
user_id: ID of user enabling the module (for audit)
|
||||
|
||||
Returns:
|
||||
True if successful
|
||||
@@ -376,15 +586,45 @@ class ModuleService:
|
||||
logger.error(f"Platform {platform_id} not found")
|
||||
return False
|
||||
|
||||
settings = platform.settings or {}
|
||||
enabled = set(settings.get("enabled_modules", list(MODULES.keys())))
|
||||
enabled.add(module_code)
|
||||
# Migrate JSON settings to junction table if needed
|
||||
self._migrate_json_to_junction_table(db, platform_id, user_id)
|
||||
|
||||
# Resolve dependencies
|
||||
enabled = self._resolve_dependencies(enabled)
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
settings["enabled_modules"] = list(enabled)
|
||||
platform.settings = settings
|
||||
# Enable this module and its dependencies
|
||||
modules_to_enable = {module_code}
|
||||
module = get_module(module_code)
|
||||
if module:
|
||||
for required in module.requires:
|
||||
modules_to_enable.add(required)
|
||||
|
||||
for code in modules_to_enable:
|
||||
# Check if junction table record exists
|
||||
platform_module = (
|
||||
db.query(PlatformModule)
|
||||
.filter(
|
||||
PlatformModule.platform_id == platform_id,
|
||||
PlatformModule.module_code == code,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if platform_module:
|
||||
# Update existing record
|
||||
platform_module.is_enabled = True
|
||||
platform_module.enabled_at = now
|
||||
platform_module.enabled_by_user_id = user_id
|
||||
else:
|
||||
# Create new record
|
||||
platform_module = PlatformModule(
|
||||
platform_id=platform_id,
|
||||
module_code=code,
|
||||
is_enabled=True,
|
||||
enabled_at=now,
|
||||
enabled_by_user_id=user_id,
|
||||
config={},
|
||||
)
|
||||
db.add(platform_module)
|
||||
|
||||
logger.info(f"Enabled module '{module_code}' for platform {platform_id}")
|
||||
return True
|
||||
@@ -394,17 +634,20 @@ class ModuleService:
|
||||
db: Session,
|
||||
platform_id: int,
|
||||
module_code: str,
|
||||
user_id: int | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Disable a single module for a platform.
|
||||
|
||||
Core modules cannot be disabled.
|
||||
Also disables modules that depend on this one.
|
||||
Uses junction table for auditability when available.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
platform_id: Platform ID
|
||||
module_code: Module code to disable
|
||||
user_id: ID of user disabling the module (for audit)
|
||||
|
||||
Returns:
|
||||
True if successful, False if core or not found
|
||||
@@ -423,23 +666,48 @@ class ModuleService:
|
||||
logger.error(f"Platform {platform_id} not found")
|
||||
return False
|
||||
|
||||
settings = platform.settings or {}
|
||||
enabled = set(settings.get("enabled_modules", list(MODULES.keys())))
|
||||
# Migrate JSON settings to junction table if needed
|
||||
self._migrate_json_to_junction_table(db, platform_id, user_id)
|
||||
|
||||
# Remove this module
|
||||
enabled.discard(module_code)
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
# Remove modules that depend on this one
|
||||
# Get modules to disable (this one + dependents)
|
||||
modules_to_disable = {module_code}
|
||||
dependents = self._get_dependent_modules(module_code)
|
||||
for dependent in dependents:
|
||||
if dependent in enabled:
|
||||
enabled.discard(dependent)
|
||||
logger.info(
|
||||
f"Also disabled '{dependent}' (depends on '{module_code}')"
|
||||
)
|
||||
modules_to_disable.update(dependents)
|
||||
|
||||
settings["enabled_modules"] = list(enabled)
|
||||
platform.settings = settings
|
||||
for code in modules_to_disable:
|
||||
# Check if junction table record exists
|
||||
platform_module = (
|
||||
db.query(PlatformModule)
|
||||
.filter(
|
||||
PlatformModule.platform_id == platform_id,
|
||||
PlatformModule.module_code == code,
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
if platform_module:
|
||||
# Update existing record
|
||||
platform_module.is_enabled = False
|
||||
platform_module.disabled_at = now
|
||||
platform_module.disabled_by_user_id = user_id
|
||||
else:
|
||||
# Create disabled record for tracking
|
||||
platform_module = PlatformModule(
|
||||
platform_id=platform_id,
|
||||
module_code=code,
|
||||
is_enabled=False,
|
||||
disabled_at=now,
|
||||
disabled_by_user_id=user_id,
|
||||
config={},
|
||||
)
|
||||
db.add(platform_module)
|
||||
|
||||
if code != module_code:
|
||||
logger.info(
|
||||
f"Also disabled '{code}' (depends on '{module_code}')"
|
||||
)
|
||||
|
||||
logger.info(f"Disabled module '{module_code}' for platform {platform_id}")
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user