- Add AdminMenuConfig model for per-platform menu customization - Add menu registry for centralized menu configuration - Add my-menu-config and platform-menu-config admin pages - Update sidebar with improved layout and Alpine.js interactions - Add FrontendType enum for admin/vendor menu separation - Document self-contained module patterns in session note Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
13 KiB
# Modular Platform Architecture - Design Plan
Executive Summary
Design a modular architecture where platforms can enable/disable feature modules. This creates a hierarchy:
Global (SaaS Provider)
└── Platform (Business Product - OMS, Loyalty, etc.)
├── Modules (Enabled features - Billing, Marketplace, Inventory, etc.)
│ ├── Routes (API + Page routes)
│ ├── Services (Business logic)
│ ├── Menu Items (Sidebar entries)
│ └── Templates (UI components)
└── Frontends
├── Admin (Platform management)
├── Vendor (Vendor dashboard)
└── Customer (Storefront) - future
Current State Analysis
What Exists
| Component | Status | Location |
|---|---|---|
| Platform Model | ✅ Complete | models/database/platform.py |
| Platform Configs | ⚠️ Partial | app/platforms/{oms,loyalty}/config.py (routes/templates empty) |
| Feature Registry | ✅ Complete | models/database/feature.py (50+ features) |
| Feature Gating | ✅ Complete | app/core/feature_gate.py + app/services/feature_service.py |
| Subscription Tiers | ✅ Complete | models/database/subscription.py (tier→features mapping) |
| Menu System | ✅ Complete | app/config/menu_registry.py + AdminMenuConfig model |
| Platform Context | ✅ Complete | middleware/platform_context.py (domain/path detection) |
Key Insight: Features vs Modules
Current "Features" = granular capabilities (e.g., analytics_dashboard, letzshop_sync)
- Assigned to subscription tiers
- Gated at API route level
- 50+ individual features
Proposed "Modules" = cohesive feature bundles (e.g., billing, marketplace, inventory)
- Enabled/disabled per platform
- Contains multiple features, routes, menu items
- ~10-15 modules total
Proposed Architecture
Module Definition
A Module is a self-contained unit of functionality:
# app/modules/base.py
class ModuleDefinition:
"""Base class for all modules."""
# Identity
code: str # "billing", "marketplace", "inventory"
name: str # "Billing & Subscriptions"
description: str
# Dependencies
requires: list[str] = [] # Other module codes required
# Components
features: list[str] = [] # Feature codes this module provides
menu_items: dict[FrontendType, list[str]] = {} # Menu items per frontend
# Routes (registered dynamically)
admin_router: APIRouter | None = None
vendor_router: APIRouter | None = None
# Status
is_core: bool = False # Core modules cannot be disabled
Module Registry
# app/modules/registry.py
MODULES = {
# Core modules (always enabled)
"core": ModuleDefinition(
code="core",
name="Core Platform",
is_core=True,
features=["dashboard", "settings", "profile"],
menu_items={
FrontendType.ADMIN: ["dashboard", "settings"],
FrontendType.VENDOR: ["dashboard", "settings"],
},
),
# Optional modules
"billing": ModuleDefinition(
code="billing",
name="Billing & Subscriptions",
features=["subscription_management", "billing_history", "stripe_integration"],
menu_items={
FrontendType.ADMIN: ["subscription-tiers", "subscriptions", "billing-history"],
FrontendType.VENDOR: ["billing"],
},
admin_router=billing_admin_router,
vendor_router=billing_vendor_router,
),
"marketplace": ModuleDefinition(
code="marketplace",
name="Marketplace (Letzshop)",
requires=["inventory"], # Depends on inventory module
features=["letzshop_sync", "marketplace_import"],
menu_items={
FrontendType.ADMIN: ["marketplace-letzshop"],
FrontendType.VENDOR: ["letzshop", "marketplace"],
},
),
"inventory": ModuleDefinition(
code="inventory",
name="Inventory Management",
features=["inventory_basic", "inventory_locations", "low_stock_alerts"],
menu_items={
FrontendType.ADMIN: ["inventory"],
FrontendType.VENDOR: ["inventory"],
},
),
# ... more modules
}
Proposed Modules
| Module | Description | Features | Core? |
|---|---|---|---|
core |
Dashboard, Settings, Profile | 3 | Yes |
platform-admin |
Companies, Vendors, Admin Users | 5 | Yes |
billing |
Subscriptions, Tiers, Billing History | 4 | No |
inventory |
Stock management, locations, alerts | 5 | No |
orders |
Order management, fulfillment | 6 | No |
marketplace |
Letzshop integration, import | 3 | No |
customers |
Customer management, CRM | 4 | No |
cms |
Content pages, media library | 6 | No |
analytics |
Dashboard, reports, exports | 4 | No |
messaging |
Internal messages, notifications | 3 | No |
dev-tools |
Components, icons (internal) | 2 | No |
monitoring |
Logs, background tasks, imports | 4 | No |
Database Changes
Option A: JSON Field (Simpler)
Use existing Platform.settings JSON field:
# Platform.settings example
{
"enabled_modules": ["core", "billing", "inventory", "orders"],
"module_config": {
"billing": {"stripe_mode": "live"},
"inventory": {"low_stock_threshold": 10}
}
}
Pros: No migration needed, flexible Cons: No referential integrity, harder to query
Option B: Junction Table (Recommended)
New PlatformModule model:
# models/database/platform_module.py
class PlatformModule(Base, TimestampMixin):
"""Module enablement per platform."""
__tablename__ = "platform_modules"
id = Column(Integer, primary_key=True)
platform_id = Column(Integer, ForeignKey("platforms.id"), nullable=False)
module_code = Column(String(50), nullable=False)
is_enabled = Column(Boolean, default=True)
config = Column(JSON, default={}) # Module-specific config
enabled_at = Column(DateTime)
enabled_by_user_id = Column(Integer, ForeignKey("users.id"))
__table_args__ = (
UniqueConstraint("platform_id", "module_code"),
)
Pros: Proper normalization, audit trail, queryable Cons: Requires migration
Implementation Phases
Phase 1: Module Registry (No DB Changes)
-
Create
app/modules/directory structure:app/modules/ ├── __init__.py ├── base.py # ModuleDefinition class ├── registry.py # MODULES dict └── service.py # ModuleService -
Define all modules in registry (data only, no behavior change)
-
Create
ModuleService:class ModuleService: def get_platform_modules(platform_id: int) -> list[str] def is_module_enabled(platform_id: int, module_code: str) -> bool def get_module_menu_items(platform_id: int, frontend_type: FrontendType) -> list[str] -
Initially read from
Platform.settings["enabled_modules"](Option A)
Phase 2: Integrate with Menu System
-
Update
MenuService.get_menu_for_rendering():- Filter menu items based on enabled modules
- Module-disabled items don't appear (not just hidden)
-
Update
AdminMenuConfiglogic:- Can only configure visibility for module-enabled items
- Module-disabled items are completely removed
Phase 3: Database Model (Optional)
- Create
PlatformModulemodel - Migration to create table
- Migrate data from
Platform.settings["enabled_modules"] - Update
ModuleServiceto use new table
Phase 4: Dynamic Route Registration
-
Modify
app/api/v1/admin/__init__.py:def register_module_routes(app: FastAPI, platform_code: str): enabled_modules = module_service.get_enabled_modules(platform_code) for module in enabled_modules: if module.admin_router: app.include_router(module.admin_router) -
Add module check middleware for routes
Phase 5: Admin UI for Module Management
- Create
/admin/platforms/{code}/modulespage - Toggle modules on/off per platform
- Show module dependencies
- Module-specific configuration
Directory Structure Evolution
Current
app/
├── api/v1/
│ ├── admin/ # All admin routes mixed
│ └── vendor/ # All vendor routes mixed
├── platforms/
│ ├── oms/config.py # Platform config only
│ └── loyalty/config.py
└── services/ # All services mixed
Proposed (Gradual Migration)
app/
├── modules/
│ ├── base.py
│ ├── registry.py
│ ├── service.py
│ ├── core/ # Core module
│ │ ├── __init__.py
│ │ └── definition.py
│ ├── billing/ # Billing module
│ │ ├── __init__.py
│ │ ├── definition.py
│ │ ├── routes/
│ │ │ ├── admin.py
│ │ │ └── vendor.py
│ │ └── services/
│ │ └── subscription_service.py
│ ├── marketplace/ # Marketplace module
│ │ └── ...
│ └── inventory/ # Inventory module
│ └── ...
├── api/v1/ # Legacy routes (gradually migrate)
└── platforms/ # Platform-specific overrides
├── oms/
└── loyalty/
Key Design Decisions Needed
1. Migration Strategy
| Option | Description |
|---|---|
| A: Big Bang | Move all code to modules at once |
| B: Gradual | Keep existing structure, modules are metadata only initially |
| C: Hybrid | New features in modules, migrate existing over time |
Recommendation: Option C (Hybrid) - Start with module definitions as metadata, then gradually move code.
2. Module Granularity
| Option | Example |
|---|---|
| Coarse | 5-8 large modules (billing, operations, content) |
| Medium | 10-15 medium modules (billing, inventory, orders, cms) |
| Fine | 20+ small modules (subscription-tiers, invoices, stock-levels) |
Recommendation: Medium granularity - matches current menu sections.
3. Core vs Optional
Which modules should be mandatory (cannot be disabled)?
Proposed Core:
core(dashboard, settings)platform-admin(companies, vendors, admin-users)
Everything else optional (including billing - some platforms may not charge).
Relationship to Existing Systems
Modules → Features
- Module contains multiple features
- Enabling module makes its features available for tier assignment
- Features still gated by subscription tier
Modules → Menu Items
- Module specifies which menu items it provides
- Menu items only visible if module enabled AND menu visibility allows
Modules → Routes
- Module can provide admin and vendor routers
- Routes only registered if module enabled
- Existing
require_menu_access()still applies
Platform Config → Modules
app/platforms/oms/config.pycan specify default modules- Database
PlatformModuleorPlatform.settingsoverrides defaults
Verification Plan
-
Module definition only (Phase 1)
- Define all modules in registry
- Add
enabled_modulesto Platform.settings - Verify ModuleService returns correct modules
-
Menu integration (Phase 2)
- Disable "billing" module for Loyalty platform
- Verify billing menu items don't appear in sidebar
- Verify
/admin/subscriptionsreturns 404 or redirect
-
Full module isolation (Phase 4)
- Create new platform with minimal modules
- Verify only enabled module routes are accessible
- Verify module dependencies are enforced
Decisions Made
| Decision | Choice | Rationale |
|---|---|---|
| Storage | JSON field (Platform.settings) |
No migration needed, can upgrade to table later |
| Migration | Gradual | Module definitions as metadata first, migrate code over time |
| Billing | Optional module | Some platforms may not charge (e.g., internal loyalty) |
| First module | billing |
Self-contained, clear routes/services, good isolation test |
Implementation Roadmap
Immediate (Phase 1): Module Foundation
- Create
app/modules/directory with base classes - Define module registry with all ~12 modules
- Create
ModuleServicereading fromPlatform.settings - Add
enabled_modulesto OMS and Loyalty platform settings
Next (Phase 2): Menu Integration
- Update
MenuServiceto filter by enabled modules - Test: Disable billing module → billing menu items disappear
Then (Phase 3): Billing Module Extraction
- Create
app/modules/billing/structure - Move billing routes and services into module
- Register billing routes dynamically based on module status
Future: Additional Modules
- Extract marketplace, inventory, orders, etc.
- Consider junction table if audit trail becomes important
Files to Create/Modify
| File | Action | Purpose |
|---|---|---|
app/modules/__init__.py |
CREATE | Module package init |
app/modules/base.py |
CREATE | ModuleDefinition dataclass |
app/modules/registry.py |
CREATE | MODULES dict with all definitions |
app/modules/service.py |
CREATE | ModuleService class |
app/services/menu_service.py |
MODIFY | Filter by enabled modules |
app/platforms/oms/config.py |
MODIFY | Add enabled_modules |
app/platforms/loyalty/config.py |
MODIFY | Add enabled_modules |