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:
300
docs/proposals/SESSION_NOTE_2026-01-26_self-contained-modules.md
Normal file
300
docs/proposals/SESSION_NOTE_2026-01-26_self-contained-modules.md
Normal file
@@ -0,0 +1,300 @@
|
||||
# Session Note: Self-Contained Module Architecture
|
||||
|
||||
**Date:** 2026-01-26
|
||||
**Plan Reference:** `docs/proposals/TEMP.md` (now this file)
|
||||
**Previous Session:** `docs/proposals/SESSION_NOTE_2026-01-25_modular-platform-architecture.md`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Transformed thin module wrappers into fully self-contained modules, using CMS as the pilot. Each self-contained module is an autonomous unit with its own services, models, schemas, templates, exceptions, and locales.
|
||||
|
||||
---
|
||||
|
||||
## Completed Phases
|
||||
|
||||
### Phase 1: Foundation
|
||||
|
||||
Created infrastructure for self-contained modules:
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `app/modules/contracts/` | Protocol definitions for cross-module dependencies |
|
||||
| `app/templates_config.py` | Multi-directory template loader for module templates |
|
||||
| `app/modules/base.py` | Enhanced ModuleDefinition with self-contained flags |
|
||||
|
||||
**Git tag:** `pre-modular-architecture`
|
||||
|
||||
### Phase 2: CMS Pilot (Full Self-Contained Module)
|
||||
|
||||
Migrated CMS to be the first fully self-contained module:
|
||||
|
||||
| Component | Location | Status |
|
||||
|-----------|----------|--------|
|
||||
| Services | `app/modules/cms/services/content_page_service.py` | ✅ |
|
||||
| Models | `app/modules/cms/models/content_page.py` | ✅ |
|
||||
| Schemas | `app/modules/cms/schemas/content_page.py` | ✅ |
|
||||
| Exceptions | `app/modules/cms/exceptions.py` | ✅ |
|
||||
| Locales | `app/modules/cms/locales/{en,fr,de,lb}.json` | ✅ |
|
||||
| Templates | `app/modules/cms/templates/cms/{admin,vendor}/` | ✅ |
|
||||
| Static | `app/modules/cms/static/{admin,vendor}/js/` | ✅ |
|
||||
| Routes | `app/modules/cms/routes/{api,pages}/` | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## CMS Module Structure
|
||||
|
||||
```
|
||||
app/modules/cms/
|
||||
├── __init__.py # Lazy getter to avoid circular imports
|
||||
├── definition.py # ModuleDefinition with self-contained config
|
||||
├── exceptions.py # CMSException, ContentPageNotFoundError
|
||||
├── locales/
|
||||
│ ├── en.json
|
||||
│ ├── fr.json
|
||||
│ ├── de.json
|
||||
│ └── lb.json
|
||||
├── models/
|
||||
│ ├── __init__.py # Exports: ContentPage, MediaFile, ProductMedia
|
||||
│ └── content_page.py # ContentPage model (canonical location)
|
||||
├── routes/
|
||||
│ ├── __init__.py
|
||||
│ ├── admin.py # Admin router wrapper
|
||||
│ ├── vendor.py # Vendor router wrapper
|
||||
│ ├── api/
|
||||
│ │ ├── admin.py # Admin API endpoints
|
||||
│ │ ├── vendor.py # Vendor API endpoints
|
||||
│ │ └── shop.py # Shop/public API endpoints
|
||||
│ └── pages/
|
||||
│ ├── admin.py # Admin page routes
|
||||
│ └── vendor.py # Vendor page routes
|
||||
├── schemas/
|
||||
│ ├── __init__.py
|
||||
│ └── content_page.py # Pydantic schemas
|
||||
├── services/
|
||||
│ ├── __init__.py
|
||||
│ └── content_page_service.py
|
||||
├── static/
|
||||
│ ├── admin/js/
|
||||
│ │ ├── content-pages.js
|
||||
│ │ └── content-page-edit.js
|
||||
│ └── vendor/js/
|
||||
│ ├── content-pages.js
|
||||
│ └── content-page-edit.js
|
||||
└── templates/
|
||||
└── cms/
|
||||
├── admin/
|
||||
│ ├── content-pages.html
|
||||
│ └── content-page-edit.html
|
||||
└── vendor/
|
||||
├── content-pages.html
|
||||
└── content-page-edit.html
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Patterns Established
|
||||
|
||||
### 1. Module-First Models
|
||||
|
||||
Models live in module folders and are dynamically loaded at startup:
|
||||
|
||||
```python
|
||||
# app/modules/cms/models/content_page.py (canonical location)
|
||||
from app.core.database import Base
|
||||
|
||||
class ContentPage(Base):
|
||||
__tablename__ = "content_pages"
|
||||
...
|
||||
|
||||
# models/database/__init__.py (dynamic loader)
|
||||
def _discover_module_models():
|
||||
for module_dir in sorted(modules_dir.iterdir()):
|
||||
models_init = module_dir / "models" / "__init__.py"
|
||||
if models_init.exists():
|
||||
importlib.import_module(f"app.modules.{module_dir.name}.models")
|
||||
|
||||
_discover_module_models()
|
||||
```
|
||||
|
||||
### 2. Shared Templates Instance
|
||||
|
||||
Route files must import from `app.templates_config`:
|
||||
|
||||
```python
|
||||
# CORRECT
|
||||
from app.templates_config import templates
|
||||
|
||||
# WRONG - creates local instance without module loaders
|
||||
templates = Jinja2Templates(directory="app/templates")
|
||||
```
|
||||
|
||||
### 3. Template Namespacing
|
||||
|
||||
Module templates use namespace prefix to avoid collisions:
|
||||
|
||||
```python
|
||||
# Module templates at: app/modules/cms/templates/cms/admin/content-pages.html
|
||||
# Rendered as:
|
||||
templates.TemplateResponse("cms/admin/content-pages.html", ...)
|
||||
```
|
||||
|
||||
### 4. Import Pattern
|
||||
|
||||
All code should import from module:
|
||||
|
||||
```python
|
||||
# Models
|
||||
from app.modules.cms.models import ContentPage
|
||||
|
||||
# Services
|
||||
from app.modules.cms.services import content_page_service
|
||||
|
||||
# Exceptions
|
||||
from app.modules.cms.exceptions import ContentPageNotFoundException
|
||||
```
|
||||
|
||||
### 5. Lazy Imports for Circular Import Prevention
|
||||
|
||||
```python
|
||||
# app/modules/cms/__init__.py
|
||||
def get_cms_module():
|
||||
"""Lazy getter for cms_module to avoid circular imports."""
|
||||
from app.modules.cms.definition import cms_module
|
||||
return cms_module
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Choice | Rationale |
|
||||
|----------|--------|-----------|
|
||||
| Pilot Module | CMS | Simplest, minimal dependencies |
|
||||
| Cross-Module Pattern | Protocol pattern | Type-safe interfaces |
|
||||
| Timeline | Incremental | Alongside feature work |
|
||||
| Backwards Compatibility | No shims | Pre-launch, can delete old files |
|
||||
| Template Namespace | `{module}/admin/`, `{module}/vendor/` | Prevent collisions |
|
||||
|
||||
---
|
||||
|
||||
## Verification Completed
|
||||
|
||||
- [x] `python -c "from main import app"` succeeds
|
||||
- [x] ContentPage model in `app/modules/cms/models/content_page.py`
|
||||
- [x] Dynamic model loader in `models/database/__init__.py`
|
||||
- [x] `content_pages` table in Base.metadata (67 total tables)
|
||||
- [x] Template files in correct locations
|
||||
- [x] Route files use shared templates instance
|
||||
- [x] Admin CMS pages render correctly
|
||||
- [x] Vendor CMS pages render correctly
|
||||
|
||||
---
|
||||
|
||||
## What Stays in Core vs Moves to Modules
|
||||
|
||||
### Core (Stays in Place)
|
||||
|
||||
| Component | Location | Reason |
|
||||
|-----------|----------|--------|
|
||||
| User, Vendor, Company, Platform models | `models/database/` | Foundational entities |
|
||||
| Auth service | `app/services/` | Cross-cutting concern |
|
||||
| Storage, Cache, Email services | `app/services/` | Infrastructure utilities |
|
||||
| Base exceptions | `app/exceptions/` | Shared error types |
|
||||
| Shared macros, partials | `app/templates/shared/` | Reusable UI components |
|
||||
| API dependencies | `app/api/deps.py` | Auth, module access checks |
|
||||
|
||||
### Modules (Move to Self-Contained)
|
||||
|
||||
| Module | Services | Models | Status |
|
||||
|--------|----------|--------|--------|
|
||||
| cms | content_page, media | content_page, media | ✅ Complete |
|
||||
| billing | billing, stripe, invoice, subscription | subscription, invoice, payment | Pending |
|
||||
| inventory | inventory, inventory_transaction | inventory, inventory_transaction | Pending |
|
||||
| orders | order, cart, order_item_exception | order, order_item_exception | Pending |
|
||||
| marketplace | marketplace, marketplace_product, letzshop_export | marketplace_product, import_job | Pending |
|
||||
| customers | customer, customer_address | customer | Pending |
|
||||
| messaging | messaging, notification | message, notification | Pending |
|
||||
| analytics | stats, capacity_forecast | (uses other models) | Pending |
|
||||
| monitoring | background_tasks, test_runner, log | test_run, architecture_scan | Pending |
|
||||
|
||||
---
|
||||
|
||||
## Pending/Next Steps
|
||||
|
||||
### Phase 3: Simple Modules Migration
|
||||
|
||||
- [ ] Migrate analytics module
|
||||
- [ ] Migrate monitoring module
|
||||
- [ ] Migrate messaging module
|
||||
- [ ] Migrate customers module
|
||||
|
||||
### Phase 4: Complex Modules Migration
|
||||
|
||||
- [ ] Migrate billing (with Stripe integration)
|
||||
- [ ] Migrate inventory
|
||||
- [ ] Migrate orders
|
||||
- [ ] Migrate marketplace
|
||||
|
||||
### Phase 5: Cleanup
|
||||
|
||||
- [ ] Remove deprecated shims (if any created)
|
||||
- [ ] Update all imports across codebase
|
||||
- [ ] Delete `app/platforms/` directory
|
||||
- [ ] Update architecture documentation
|
||||
|
||||
### Other Pending Items
|
||||
|
||||
- [ ] Wire up vendor module routers to `app/api/v1/vendor/__init__.py`
|
||||
- [ ] PlatformModule database table (optional - for audit trail)
|
||||
- [ ] Module-specific configuration UI
|
||||
- [ ] Integration tests for `/api/v1/admin/modules/*` endpoints
|
||||
|
||||
---
|
||||
|
||||
## Git Commits
|
||||
|
||||
```
|
||||
ec4ec04 feat: complete CMS as fully autonomous self-contained module
|
||||
0b65864 fix: resolve circular import in CMS module
|
||||
3ffa890 fix: correct static file mount order and update architecture validator
|
||||
3307205 feat: add module info and configuration pages to admin panel
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Files Reference
|
||||
|
||||
### Self-Contained Module Infrastructure
|
||||
|
||||
- `app/modules/base.py` - ModuleDefinition with self-contained flags
|
||||
- `app/modules/contracts/` - Protocol definitions for cross-module deps
|
||||
- `app/templates_config.py` - Multi-directory template loader
|
||||
- `models/database/__init__.py` - Dynamic module model discovery
|
||||
|
||||
### CMS Module (Pilot)
|
||||
|
||||
- `app/modules/cms/definition.py` - Module metadata
|
||||
- `app/modules/cms/models/content_page.py` - ContentPage model
|
||||
- `app/modules/cms/services/content_page_service.py` - Business logic
|
||||
- `app/modules/cms/exceptions.py` - Module-specific exceptions
|
||||
- `app/modules/cms/templates/cms/` - Namespaced templates
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
# Verify app starts
|
||||
python -c "from main import app; print('OK')"
|
||||
|
||||
# Run module service tests
|
||||
python -m pytest tests/unit/services/test_module_service.py -v
|
||||
|
||||
# Verify CMS model is loaded
|
||||
python -c "from app.modules.cms.models import ContentPage; print(ContentPage.__tablename__)"
|
||||
|
||||
# Verify template loading
|
||||
python -c "from app.templates_config import templates; print(templates.env.loader)"
|
||||
```
|
||||
@@ -1,557 +0,0 @@
|
||||
● Platform Homepage Configuration Analysis
|
||||
|
||||
Summary: Current Flexibility
|
||||
┌──────────────────────┬─────────────┬──────────────────────────────────────────────────┐
|
||||
│ Feature │ Flexibility │ Notes │
|
||||
├──────────────────────┼─────────────┼──────────────────────────────────────────────────┤
|
||||
│ Homepage Content │ ✅ High │ Fully CMS-driven via ContentPage model │
|
||||
├──────────────────────┼─────────────┼──────────────────────────────────────────────────┤
|
||||
│ Header/Footer Menus │ ⚠️ Medium │ Driven by ContentPage flags, flat structure only │
|
||||
├──────────────────────┼─────────────┼──────────────────────────────────────────────────┤
|
||||
│ Platform Branding │ ✅ High │ Logo, favicon, colors via Platform model │
|
||||
├──────────────────────┼─────────────┼──────────────────────────────────────────────────┤
|
||||
│ Vendor Themes │ ✅ High │ 7 presets + custom CSS + full color control │
|
||||
├──────────────────────┼─────────────┼──────────────────────────────────────────────────┤
|
||||
│ Pricing/Features │ ❌ Low │ Hardcoded in TIER_LIMITS │
|
||||
├──────────────────────┼─────────────┼──────────────────────────────────────────────────┤
|
||||
│ Navigation Structure │ ❌ Low │ No nested menus, no icons │
|
||||
└──────────────────────┴─────────────┴──────────────────────────────────────────────────┘
|
||||
---
|
||||
1. Homepage Content Configuration
|
||||
|
||||
Model: ContentPage with three-tier hierarchy
|
||||
|
||||
Platform Marketing Pages (is_platform_page=True, vendor_id=NULL)
|
||||
↓ e.g., /pricing, /about, /features for oms.lu
|
||||
|
||||
Vendor Default Pages (is_platform_page=False, vendor_id=NULL)
|
||||
↓ Fallback for all vendors (About, Shipping Policy, etc.)
|
||||
|
||||
Vendor Override Pages (is_platform_page=False, vendor_id=set)
|
||||
↓ Vendor-specific customizations
|
||||
|
||||
Configurable per page:
|
||||
- title, content (HTML/Markdown), slug
|
||||
- template (default, minimal, modern, full)
|
||||
- meta_description, meta_keywords (SEO)
|
||||
- show_in_header, show_in_footer, show_in_legal
|
||||
- display_order, is_published
|
||||
|
||||
---
|
||||
2. Menu Configuration
|
||||
|
||||
Current approach: Content-driven (no separate Menu model)
|
||||
┌───────────────┬─────────────┬─────────────────────┐
|
||||
│ Menu Location │ Source │ Filter │
|
||||
├───────────────┼─────────────┼─────────────────────┤
|
||||
│ Header │ ContentPage │ show_in_header=True │
|
||||
├───────────────┼─────────────┼─────────────────────┤
|
||||
│ Footer │ ContentPage │ show_in_footer=True │
|
||||
├───────────────┼─────────────┼─────────────────────┤
|
||||
│ Legal bar │ ContentPage │ show_in_legal=True │
|
||||
└───────────────┴─────────────┴─────────────────────┘
|
||||
Limitations:
|
||||
- Flat structure only (no dropdowns/submenus)
|
||||
- No custom menu items (only links to content pages)
|
||||
- No menu icons or special styling
|
||||
- No external URLs
|
||||
|
||||
---
|
||||
3. Platform Model
|
||||
|
||||
File: models/database/platform.py
|
||||
|
||||
Platform:
|
||||
code # 'main', 'oms', 'loyalty'
|
||||
name # Display name
|
||||
domain # Production: 'oms.lu'
|
||||
path_prefix # Dev: '/oms/'
|
||||
logo # Light mode logo URL
|
||||
logo_dark # Dark mode logo URL
|
||||
favicon # Favicon URL
|
||||
theme_config # JSON: colors, fonts, etc.
|
||||
default_language # 'fr', 'en', 'de'
|
||||
supported_languages # ['fr', 'de', 'en']
|
||||
settings # JSON: feature flags
|
||||
|
||||
---
|
||||
4. Theme System
|
||||
|
||||
Vendor-level only (not platform-level defaults)
|
||||
┌───────────────┬────────┬─────────────────────────────────────────────────────────────┐
|
||||
│ Property │ Type │ Options │
|
||||
├───────────────┼────────┼─────────────────────────────────────────────────────────────┤
|
||||
│ Colors │ JSON │ primary, secondary, accent, background, text, border │
|
||||
├───────────────┼────────┼─────────────────────────────────────────────────────────────┤
|
||||
│ Fonts │ String │ font_family_heading, font_family_body │
|
||||
├───────────────┼────────┼─────────────────────────────────────────────────────────────┤
|
||||
│ Layout │ String │ grid, list, masonry │
|
||||
├───────────────┼────────┼─────────────────────────────────────────────────────────────┤
|
||||
│ Header │ String │ fixed, static, transparent │
|
||||
├───────────────┼────────┼─────────────────────────────────────────────────────────────┤
|
||||
│ Product cards │ String │ modern, classic, minimal │
|
||||
├───────────────┼────────┼─────────────────────────────────────────────────────────────┤
|
||||
│ Custom CSS │ Text │ Injected into <style> │
|
||||
├───────────────┼────────┼─────────────────────────────────────────────────────────────┤
|
||||
│ Presets │ 7 │ default, modern, classic, minimal, vibrant, elegant, nature │
|
||||
└───────────────┴────────┴─────────────────────────────────────────────────────────────┘
|
||||
---
|
||||
5. What's Hardcoded
|
||||
┌──────────────────────────┬──────────────────────┬───────────────────────────┐
|
||||
│ Item │ Location │ Issue │
|
||||
├──────────────────────────┼──────────────────────┼───────────────────────────┤
|
||||
│ Pricing tiers │ TIER_LIMITS constant │ Cannot edit via admin │
|
||||
├──────────────────────────┼──────────────────────┼───────────────────────────┤
|
||||
│ Add-on options │ Route handlers │ Hardcoded prices/features │
|
||||
├──────────────────────────┼──────────────────────┼───────────────────────────┤
|
||||
│ Platform name "Wizamart" │ Templates │ Not from database │
|
||||
├──────────────────────────┼──────────────────────┼───────────────────────────┤
|
||||
│ Hero section layout │ Tailwind classes │ Fixed structure │
|
||||
├──────────────────────────┼──────────────────────┼───────────────────────────┤
|
||||
│ Menu structure │ Code │ No nesting support │
|
||||
└──────────────────────────┴──────────────────────┴───────────────────────────┘
|
||||
---
|
||||
6. Gaps for Multi-Platform Flexibility
|
||||
|
||||
1. No Platform-Level Theme Defaults
|
||||
- VendorTheme is per-vendor only
|
||||
- Each platform cannot define default vendor theme
|
||||
2. No Menu Model
|
||||
- Cannot create custom navigation structures
|
||||
- No external links, no icons, no dropdowns
|
||||
3. Limited Template Switching
|
||||
- Only 4 homepage templates
|
||||
- No way to create new templates via admin
|
||||
4. Hardcoded Pricing
|
||||
- TIER_LIMITS in code, not database
|
||||
- Cannot customize per platform
|
||||
|
||||
---
|
||||
Would you like me to:
|
||||
|
||||
1. Create a Platform Theme system - Allow platform-level theme defaults that vendors inherit
|
||||
2. Create a Menu model - Support hierarchical menus with custom items, icons, external links
|
||||
3. Move pricing to database - Make tiers configurable per platform
|
||||
4. Add homepage block system - Reorderable sections (hero, features, CTA, testimonials)
|
||||
|
||||
Which enhancement would be most valuable for your use case?
|
||||
|
||||
|
||||
*******************
|
||||
|
||||
Section-Based Homepage Management System
|
||||
|
||||
Problem Statement
|
||||
|
||||
Current homepage implementation has critical issues:
|
||||
1. Hardcoded platform content - Migrations contain OMS/Loyalty/Main-specific HTML
|
||||
2. Monolithic content storage - Entire page stored as HTML blob, can't edit sections individually
|
||||
3. No admin control - Hero, features, pricing sections are hardcoded in templates
|
||||
|
||||
Solution: JSON-Based Section Architecture
|
||||
|
||||
Approach: Add sections JSON field to ContentPage
|
||||
|
||||
Why JSON field vs separate PageSection model:
|
||||
- Simpler - no new tables, no joins, no N+1 queries
|
||||
- Flexible - schema can evolve without migrations
|
||||
- Atomic - save entire homepage in one transaction
|
||||
- Follows existing pattern - VendorTheme already uses JSON for colors
|
||||
|
||||
---
|
||||
Multi-Language Support
|
||||
|
||||
Option A: Language-Keyed Sections (Recommended)
|
||||
|
||||
Store all translations in one JSON structure:
|
||||
|
||||
{
|
||||
"hero": {
|
||||
"enabled": true,
|
||||
"title": {
|
||||
"en": "Welcome to Our Platform",
|
||||
"fr": "Bienvenue sur notre plateforme",
|
||||
"de": "Willkommen auf unserer Plattform"
|
||||
},
|
||||
"subtitle": {
|
||||
"en": "Your success starts here",
|
||||
"fr": "Votre succès commence ici",
|
||||
"de": "Ihr Erfolg beginnt hier"
|
||||
},
|
||||
"buttons": [
|
||||
{
|
||||
"text": {"en": "Get Started", "fr": "Commencer", "de": "Loslegen"},
|
||||
"url": "/signup",
|
||||
"style": "primary"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Pros:
|
||||
- Single page entry per platform (not 3 separate pages)
|
||||
- Easy to see which translations are missing
|
||||
- Atomic save of all language variants
|
||||
- Admin can edit all languages in one form
|
||||
|
||||
Cons:
|
||||
- Larger JSON payload
|
||||
- Need helper function to extract current language
|
||||
|
||||
Option B: Separate Page Per Language
|
||||
|
||||
Create one ContentPage per language with same slug but different content:
|
||||
- slug="home", language="en"
|
||||
- slug="home", language="fr"
|
||||
- slug="home", language="de"
|
||||
|
||||
Pros:
|
||||
- Simpler JSON structure per page
|
||||
- Can have different sections per language
|
||||
|
||||
Cons:
|
||||
- More database entries
|
||||
- Harder to keep in sync
|
||||
- Need to add language column to ContentPage
|
||||
|
||||
Recommendation: Option A (Language-Keyed)
|
||||
|
||||
This keeps all translations together and matches how the platform already handles supported_languages on the Platform model.
|
||||
|
||||
Dynamic Language Support
|
||||
|
||||
Languages are NOT hardcoded. The system uses the platform's supported_languages setting:
|
||||
|
||||
# Platform model already has:
|
||||
supported_languages = Column(JSON) # e.g., ["fr", "de", "en"]
|
||||
default_language = Column(String) # e.g., "fr"
|
||||
|
||||
Schema with Dynamic i18n
|
||||
|
||||
class TranslatableText(BaseModel):
|
||||
"""
|
||||
Text field with translations stored as dict.
|
||||
Keys are language codes from platform.supported_languages.
|
||||
"""
|
||||
translations: dict[str, str] = {} # {"fr": "...", "de": "...", "en": "..."}
|
||||
|
||||
def get(self, lang: str, default_lang: str = "fr") -> str:
|
||||
"""Get translation with fallback to default language."""
|
||||
return self.translations.get(lang) or self.translations.get(default_lang) or ""
|
||||
|
||||
class HeroButton(BaseModel):
|
||||
text: TranslatableText
|
||||
url: str
|
||||
style: str = "primary"
|
||||
|
||||
class HeroSection(BaseModel):
|
||||
enabled: bool = True
|
||||
badge_text: Optional[TranslatableText] = None
|
||||
title: TranslatableText
|
||||
subtitle: TranslatableText
|
||||
background_type: str = "gradient"
|
||||
buttons: list[HeroButton] = []
|
||||
|
||||
Template Usage with Platform Languages
|
||||
|
||||
{# Language comes from platform settings #}
|
||||
{% set lang = request.state.language or platform.default_language %}
|
||||
{% set default_lang = platform.default_language %}
|
||||
|
||||
<h1>{{ hero.title.get(lang, default_lang) }}</h1>
|
||||
<p>{{ hero.subtitle.get(lang, default_lang) }}</p>
|
||||
|
||||
Admin UI Language Tabs
|
||||
|
||||
The admin editor dynamically generates language tabs from platform.supported_languages:
|
||||
|
||||
// Fetch platform languages
|
||||
const platform = await apiClient.get(`/admin/platforms/${platformCode}`);
|
||||
const languages = platform.supported_languages; // ["fr", "de", "en"]
|
||||
|
||||
// Render language tabs dynamically
|
||||
languages.forEach(lang => {
|
||||
addLanguageTab(lang);
|
||||
});
|
||||
|
||||
---
|
||||
Implementation Plan
|
||||
|
||||
Phase 1: Database Changes
|
||||
|
||||
1.1 Add sections column to ContentPage
|
||||
|
||||
File: models/database/content_page.py
|
||||
sections = Column(JSON, nullable=True, default=None)
|
||||
|
||||
1.2 Create migration
|
||||
|
||||
File: alembic/versions/xxx_add_sections_to_content_pages.py
|
||||
- Add sections JSON column (nullable)
|
||||
|
||||
Phase 2: Schema Validation
|
||||
|
||||
2.1 Create Pydantic schemas with dynamic i18n
|
||||
|
||||
File: models/schema/homepage_sections.py (NEW)
|
||||
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
class TranslatableText(BaseModel):
|
||||
"""
|
||||
Stores translations as dict with language codes as keys.
|
||||
Language codes come from platform.supported_languages.
|
||||
"""
|
||||
translations: dict[str, str] = {}
|
||||
|
||||
def get(self, lang: str, default_lang: str = "fr") -> str:
|
||||
"""Get text for language with fallback."""
|
||||
return self.translations.get(lang) or self.translations.get(default_lang) or ""
|
||||
|
||||
class HeroButton(BaseModel):
|
||||
text: TranslatableText
|
||||
url: str
|
||||
style: str = "primary" # primary, secondary, outline
|
||||
|
||||
class HeroSection(BaseModel):
|
||||
enabled: bool = True
|
||||
badge_text: Optional[TranslatableText] = None
|
||||
title: TranslatableText = TranslatableText()
|
||||
subtitle: TranslatableText = TranslatableText()
|
||||
background_type: str = "gradient"
|
||||
buttons: list[HeroButton] = []
|
||||
|
||||
class FeatureCard(BaseModel):
|
||||
icon: str
|
||||
title: TranslatableText
|
||||
description: TranslatableText
|
||||
|
||||
class FeaturesSection(BaseModel):
|
||||
enabled: bool = True
|
||||
title: TranslatableText = TranslatableText()
|
||||
subtitle: Optional[TranslatableText] = None
|
||||
features: list[FeatureCard] = []
|
||||
layout: str = "grid"
|
||||
|
||||
class PricingSection(BaseModel):
|
||||
enabled: bool = True
|
||||
title: TranslatableText = TranslatableText()
|
||||
subtitle: Optional[TranslatableText] = None
|
||||
use_subscription_tiers: bool = True # Pull from DB dynamically
|
||||
|
||||
class CTASection(BaseModel):
|
||||
enabled: bool = True
|
||||
title: TranslatableText = TranslatableText()
|
||||
subtitle: Optional[TranslatableText] = None
|
||||
buttons: list[HeroButton] = []
|
||||
|
||||
class HomepageSections(BaseModel):
|
||||
hero: Optional[HeroSection] = None
|
||||
features: Optional[FeaturesSection] = None
|
||||
pricing: Optional[PricingSection] = None
|
||||
cta: Optional[CTASection] = None
|
||||
|
||||
Phase 3: Template Changes
|
||||
|
||||
3.1 Create section partials
|
||||
|
||||
Directory: app/templates/platform/sections/ (NEW)
|
||||
- _hero.html - Renders hero with language support
|
||||
- _features.html - Renders features grid
|
||||
- _pricing.html - Renders pricing (uses subscription_tiers from DB)
|
||||
- _cta.html - Renders CTA section
|
||||
|
||||
3.2 Update homepage templates
|
||||
|
||||
File: app/templates/platform/homepage-default.html
|
||||
{% set lang = request.state.language or platform.default_language or 'fr' %}
|
||||
|
||||
{% if page and page.sections %}
|
||||
{{ render_hero(page.sections.hero, lang) }}
|
||||
{{ render_features(page.sections.features, lang) }}
|
||||
{{ render_pricing(page.sections.pricing, lang, tiers) }}
|
||||
{{ render_cta(page.sections.cta, lang) }}
|
||||
{% else %}
|
||||
{# Placeholder for unconfigured homepage #}
|
||||
{% endif %}
|
||||
|
||||
Phase 4: Service Layer
|
||||
|
||||
4.1 Add section methods to ContentPageService
|
||||
|
||||
File: app/services/content_page_service.py
|
||||
- update_homepage_sections(db, page_id, sections, updated_by) - Validates and saves
|
||||
- get_default_sections() - Returns empty section structure
|
||||
|
||||
Phase 5: Admin API
|
||||
|
||||
5.1 Add section endpoints
|
||||
|
||||
File: app/api/v1/admin/content_pages.py
|
||||
- GET /{page_id}/sections - Get structured sections
|
||||
- PUT /{page_id}/sections - Update all sections
|
||||
- PUT /{page_id}/sections/{section_name} - Update single section
|
||||
|
||||
Phase 6: Remove Hardcoded Content from Migrations
|
||||
|
||||
6.1 Update OMS migration
|
||||
|
||||
File: alembic/versions/z4e5f6a7b8c9_add_multi_platform_support.py
|
||||
- Remove oms_homepage_content variable
|
||||
- Create homepage with empty sections structure instead
|
||||
- Set is_published=False (admin configures before publishing)
|
||||
|
||||
6.2 Migration creates structure only
|
||||
- Migrations should ONLY create empty structure
|
||||
- Content is entered via admin UI in each language
|
||||
|
||||
Phase 7: Admin UI
|
||||
|
||||
7.1 Add section editor to content-page-edit
|
||||
|
||||
File: app/templates/admin/content-page-edit.html
|
||||
- Add "Sections" tab for homepage pages
|
||||
- Language tabs within each section (EN | FR | DE | LB)
|
||||
- Form fields for each section type
|
||||
- Enable/disable toggle per section
|
||||
|
||||
File: static/admin/js/content-page-edit.js
|
||||
- Section editor logic
|
||||
- Language tab switching
|
||||
- Save sections via API
|
||||
|
||||
---
|
||||
Critical Files to Modify
|
||||
|
||||
1. models/database/content_page.py - Add sections column
|
||||
2. models/schema/homepage_sections.py - NEW: Pydantic schemas with i18n
|
||||
3. app/services/content_page_service.py - Add section methods
|
||||
4. app/api/v1/admin/content_pages.py - Add section endpoints
|
||||
5. app/templates/platform/sections/ - NEW: Section partials
|
||||
6. app/templates/platform/homepage-default.html - Use section partials
|
||||
7. app/routes/platform_pages.py - Pass sections + language to context
|
||||
8. alembic/versions/z4e5f6a7b8c9_*.py - Remove hardcoded content
|
||||
9. app/templates/admin/content-page-edit.html - Section editor UI with language tabs
|
||||
10. static/admin/js/content-page-edit.js - Section editor JS
|
||||
|
||||
---
|
||||
Section JSON Schema Example (with dynamic i18n)
|
||||
|
||||
Languages in translations dict come from platform.supported_languages.
|
||||
|
||||
{
|
||||
"hero": {
|
||||
"enabled": true,
|
||||
"badge_text": {
|
||||
"translations": {
|
||||
"fr": "Essai gratuit de 30 jours",
|
||||
"de": "30 Tage kostenlos testen",
|
||||
"en": "30-Day Free Trial"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"translations": {
|
||||
"fr": "Votre titre de plateforme ici",
|
||||
"de": "Ihr Plattform-Titel hier",
|
||||
"en": "Your Platform Headline Here"
|
||||
}
|
||||
},
|
||||
"subtitle": {
|
||||
"translations": {
|
||||
"fr": "Une description convaincante de votre plateforme.",
|
||||
"de": "Eine überzeugende Beschreibung Ihrer Plattform.",
|
||||
"en": "A compelling description of your platform."
|
||||
}
|
||||
},
|
||||
"background_type": "gradient",
|
||||
"buttons": [
|
||||
{
|
||||
"text": {
|
||||
"translations": {"fr": "Commencer", "de": "Loslegen", "en": "Get Started"}
|
||||
},
|
||||
"url": "/signup",
|
||||
"style": "primary"
|
||||
}
|
||||
]
|
||||
},
|
||||
"features": {
|
||||
"enabled": true,
|
||||
"title": {
|
||||
"translations": {
|
||||
"fr": "Pourquoi nous choisir",
|
||||
"de": "Warum uns wählen",
|
||||
"en": "Why Choose Us"
|
||||
}
|
||||
},
|
||||
"features": [
|
||||
{
|
||||
"icon": "lightning-bolt",
|
||||
"title": {"translations": {"fr": "Rapide", "de": "Schnell", "en": "Fast"}},
|
||||
"description": {"translations": {"fr": "Rapide et efficace.", "de": "Schnell und effizient.", "en": "Quick and efficient."}}
|
||||
}
|
||||
]
|
||||
},
|
||||
"pricing": {
|
||||
"enabled": true,
|
||||
"title": {
|
||||
"translations": {
|
||||
"fr": "Tarification simple",
|
||||
"de": "Einfache Preise",
|
||||
"en": "Simple Pricing"
|
||||
}
|
||||
},
|
||||
"use_subscription_tiers": true
|
||||
},
|
||||
"cta": {
|
||||
"enabled": true,
|
||||
"title": {
|
||||
"translations": {
|
||||
"fr": "Prêt à commencer?",
|
||||
"de": "Bereit anzufangen?",
|
||||
"en": "Ready to Start?"
|
||||
}
|
||||
},
|
||||
"buttons": [
|
||||
{
|
||||
"text": {
|
||||
"translations": {"fr": "S'inscrire gratuitement", "de": "Kostenlos registrieren", "en": "Sign Up Free"}
|
||||
},
|
||||
"url": "/signup",
|
||||
"style": "primary"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
---
|
||||
Migration Strategy (No Hardcoded Content)
|
||||
|
||||
When creating a platform homepage:
|
||||
homepage = ContentPage(
|
||||
platform_id=platform_id,
|
||||
slug="home",
|
||||
title="Homepage", # Generic
|
||||
content="", # Empty - sections used instead
|
||||
sections=get_default_sections(), # Empty structure with all languages
|
||||
is_published=False, # Admin configures first
|
||||
)
|
||||
|
||||
---
|
||||
Verification Steps
|
||||
|
||||
1. Run migration to add sections column
|
||||
2. Create a test homepage with sections via API (all languages)
|
||||
3. Verify homepage renders correct language based on request
|
||||
4. Test admin UI section editor with language tabs
|
||||
5. Verify pricing section pulls from subscription_tiers
|
||||
6. Test enable/disable toggle for each section
|
||||
7. Test language fallback when translation is missing
|
||||
|
||||
---
|
||||
Notes
|
||||
|
||||
- Languages are dynamic from platform.supported_languages (not hardcoded)
|
||||
- Fallback uses platform.default_language
|
||||
- Admin UI should allow partial translations (show warning indicator for missing)
|
||||
- Plan saved for resumption tomorrow
|
||||
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