feat: add admin menu configuration and sidebar improvements
- 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>
This commit is contained in:
433
docs/proposals/humble-orbiting-otter.md
Normal file
433
docs/proposals/humble-orbiting-otter.md
Normal file
@@ -0,0 +1,433 @@
|
||||
# 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:
|
||||
|
||||
```python
|
||||
# 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
|
||||
|
||||
```python
|
||||
# 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:
|
||||
|
||||
```python
|
||||
# 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:
|
||||
|
||||
```python
|
||||
# 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)
|
||||
|
||||
1. Create `app/modules/` directory structure:
|
||||
```
|
||||
app/modules/
|
||||
├── __init__.py
|
||||
├── base.py # ModuleDefinition class
|
||||
├── registry.py # MODULES dict
|
||||
└── service.py # ModuleService
|
||||
```
|
||||
|
||||
2. Define all modules in registry (data only, no behavior change)
|
||||
|
||||
3. Create `ModuleService`:
|
||||
```python
|
||||
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]
|
||||
```
|
||||
|
||||
4. Initially read from `Platform.settings["enabled_modules"]` (Option A)
|
||||
|
||||
### Phase 2: Integrate with Menu System
|
||||
|
||||
1. Update `MenuService.get_menu_for_rendering()`:
|
||||
- Filter menu items based on enabled modules
|
||||
- Module-disabled items don't appear (not just hidden)
|
||||
|
||||
2. Update `AdminMenuConfig` logic:
|
||||
- Can only configure visibility for module-enabled items
|
||||
- Module-disabled items are completely removed
|
||||
|
||||
### Phase 3: Database Model (Optional)
|
||||
|
||||
1. Create `PlatformModule` model
|
||||
2. Migration to create table
|
||||
3. Migrate data from `Platform.settings["enabled_modules"]`
|
||||
4. Update `ModuleService` to use new table
|
||||
|
||||
### Phase 4: Dynamic Route Registration
|
||||
|
||||
1. Modify `app/api/v1/admin/__init__.py`:
|
||||
```python
|
||||
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)
|
||||
```
|
||||
|
||||
2. Add module check middleware for routes
|
||||
|
||||
### Phase 5: Admin UI for Module Management
|
||||
|
||||
1. Create `/admin/platforms/{code}/modules` page
|
||||
2. Toggle modules on/off per platform
|
||||
3. Show module dependencies
|
||||
4. 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.py` can specify default modules
|
||||
- Database `PlatformModule` or `Platform.settings` overrides defaults
|
||||
|
||||
---
|
||||
|
||||
## Verification Plan
|
||||
|
||||
1. **Module definition only (Phase 1)**
|
||||
- Define all modules in registry
|
||||
- Add `enabled_modules` to Platform.settings
|
||||
- Verify ModuleService returns correct modules
|
||||
|
||||
2. **Menu integration (Phase 2)**
|
||||
- Disable "billing" module for Loyalty platform
|
||||
- Verify billing menu items don't appear in sidebar
|
||||
- Verify `/admin/subscriptions` returns 404 or redirect
|
||||
|
||||
3. **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
|
||||
1. Create `app/modules/` directory with base classes
|
||||
2. Define module registry with all ~12 modules
|
||||
3. Create `ModuleService` reading from `Platform.settings`
|
||||
4. Add `enabled_modules` to OMS and Loyalty platform settings
|
||||
|
||||
### Next (Phase 2): Menu Integration
|
||||
1. Update `MenuService` to filter by enabled modules
|
||||
2. Test: Disable billing module → billing menu items disappear
|
||||
|
||||
### Then (Phase 3): Billing Module Extraction
|
||||
1. Create `app/modules/billing/` structure
|
||||
2. Move billing routes and services into module
|
||||
3. 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 |
|
||||
|
||||
Reference in New Issue
Block a user