refactor: complete module-driven architecture migration
This commit completes the migration to a fully module-driven architecture: ## Models Migration - Moved all domain models from models/database/ to their respective modules: - tenancy: User, Admin, Vendor, Company, Platform, VendorDomain, etc. - cms: MediaFile, VendorTheme - messaging: Email, VendorEmailSettings, VendorEmailTemplate - core: AdminMenuConfig - models/database/ now only contains Base and TimestampMixin (infrastructure) ## Schemas Migration - Moved all domain schemas from models/schema/ to their respective modules: - tenancy: company, vendor, admin, team, vendor_domain - cms: media, image, vendor_theme - messaging: email - models/schema/ now only contains base.py and auth.py (infrastructure) ## Routes Migration - Moved admin routes from app/api/v1/admin/ to modules: - menu_config.py -> core module - modules.py -> tenancy module - module_config.py -> tenancy module - app/api/v1/admin/ now only aggregates auto-discovered module routes ## Menu System - Implemented module-driven menu system with MenuDiscoveryService - Extended FrontendType enum: PLATFORM, ADMIN, VENDOR, STOREFRONT - Added MenuItemDefinition and MenuSectionDefinition dataclasses - Each module now defines its own menu items in definition.py - MenuService integrates with MenuDiscoveryService for template rendering ## Documentation - Updated docs/architecture/models-structure.md - Updated docs/architecture/menu-management.md - Updated architecture validation rules for new exceptions ## Architecture Validation - Updated MOD-019 rule to allow base.py in models/schema/ - Created core module exceptions.py and schemas/ directory - All validation errors resolved (only warnings remain) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,24 +1,39 @@
|
||||
# Menu Management Architecture
|
||||
|
||||
The Wizamart platform provides a flexible menu system that supports per-platform and per-user customization. This document explains the menu architecture, configuration options, and how menus integrate with the module system.
|
||||
The Wizamart platform provides a **module-driven menu system** where each module defines its own menu items. The `MenuDiscoveryService` aggregates menus from all enabled modules, applying visibility configuration and permission filtering.
|
||||
|
||||
## Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ MENU REGISTRY (Source of Truth) │
|
||||
│ app/config/menu_registry.py │
|
||||
│ MODULE DEFINITIONS (Source of Truth) │
|
||||
│ app/modules/*/definition.py │
|
||||
│ │
|
||||
│ Each module defines its menu items per FrontendType: │
|
||||
│ ┌─────────────────────────────┐ ┌─────────────────────────────┐ │
|
||||
│ │ ADMIN_MENU_REGISTRY │ │ VENDOR_MENU_REGISTRY │ │
|
||||
│ │ (Admin panel menus) │ │ (Vendor dashboard menus) │ │
|
||||
│ │ catalog.definition.py │ │ orders.definition.py │ │
|
||||
│ │ menus={ADMIN: [...], │ │ menus={ADMIN: [...], │ │
|
||||
│ │ VENDOR: [...]} │ │ VENDOR: [...]} │ │
|
||||
│ └─────────────────────────────┘ └─────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ VISIBILITY CONFIGURATION │
|
||||
│ models/database/admin_menu_config.py │
|
||||
│ MENU DISCOVERY SERVICE │
|
||||
│ app/modules/core/services/menu_discovery_service.py │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 1. Collect menu items from all enabled modules │ │
|
||||
│ │ 2. Filter by user permissions (super_admin_only) │ │
|
||||
│ │ 3. Apply visibility overrides (AdminMenuConfig) │ │
|
||||
│ │ 4. Sort by section/item order │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ VISIBILITY CONFIGURATION │
|
||||
│ app/modules/core/models/admin_menu_config.py │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ AdminMenuConfig Table │ │
|
||||
@@ -32,95 +47,156 @@ The Wizamart platform provides a flexible menu system that supports per-platform
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ FILTERED MENU OUTPUT │
|
||||
│ │
|
||||
│ Registry - Hidden Items - Disabled Modules = Visible Menu Items │
|
||||
│ Module Menus - Disabled Modules - Hidden Items = Visible Menu │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Frontend Types
|
||||
|
||||
The system supports two distinct frontend types:
|
||||
The system supports four distinct frontend types:
|
||||
|
||||
| Frontend | Description | Users |
|
||||
|----------|-------------|-------|
|
||||
| `PLATFORM` | Public marketing pages | Unauthenticated visitors |
|
||||
| `ADMIN` | Admin panel | Super admins, platform admins |
|
||||
| `VENDOR` | Vendor dashboard | Vendors on a platform |
|
||||
| `STOREFRONT` | Customer-facing shop | Shop customers |
|
||||
|
||||
```python
|
||||
from models.database.admin_menu_config import FrontendType
|
||||
from app.modules.enums import FrontendType
|
||||
|
||||
# Use in code
|
||||
FrontendType.ADMIN # "admin"
|
||||
FrontendType.VENDOR # "vendor"
|
||||
FrontendType.PLATFORM # "platform"
|
||||
FrontendType.ADMIN # "admin"
|
||||
FrontendType.VENDOR # "vendor"
|
||||
FrontendType.STOREFRONT # "storefront"
|
||||
```
|
||||
|
||||
## Menu Registry
|
||||
## Module-Driven Menus
|
||||
|
||||
The menu registry (`app/config/menu_registry.py`) is the **single source of truth** for menu structure. The database only stores visibility overrides.
|
||||
Each module defines its menu items in `definition.py` using dataclasses:
|
||||
|
||||
### Admin Menu Structure
|
||||
### Menu Item Definition
|
||||
|
||||
```python
|
||||
ADMIN_MENU_REGISTRY = {
|
||||
"frontend_type": FrontendType.ADMIN,
|
||||
"sections": [
|
||||
{
|
||||
"id": "main",
|
||||
"label": None, # No header
|
||||
"items": [
|
||||
{"id": "dashboard", "label": "Dashboard", "icon": "home", "url": "/admin/dashboard"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "superAdmin",
|
||||
"label": "Super Admin",
|
||||
"super_admin_only": True, # Only visible to super admins
|
||||
"items": [
|
||||
{"id": "admin-users", "label": "Admin Users", "icon": "shield", "url": "/admin/admin-users"},
|
||||
],
|
||||
},
|
||||
# ... more sections
|
||||
],
|
||||
}
|
||||
# app/modules/base.py
|
||||
|
||||
@dataclass
|
||||
class MenuItemDefinition:
|
||||
"""Single menu item definition."""
|
||||
id: str # Unique identifier (e.g., "catalog.products")
|
||||
label_key: str # i18n key for label
|
||||
icon: str # Lucide icon name
|
||||
route: str # URL path
|
||||
order: int = 100 # Sort order within section
|
||||
is_mandatory: bool = False # Cannot be hidden by user
|
||||
is_super_admin_only: bool = False # Only visible to super admins
|
||||
|
||||
@dataclass
|
||||
class MenuSectionDefinition:
|
||||
"""Section containing menu items."""
|
||||
id: str # Section identifier
|
||||
label_key: str # i18n key for section label
|
||||
icon: str # Section icon
|
||||
order: int = 100 # Sort order
|
||||
items: list[MenuItemDefinition] = field(default_factory=list)
|
||||
is_super_admin_only: bool = False
|
||||
```
|
||||
|
||||
### Admin Menu Sections
|
||||
### Example Module Definition
|
||||
|
||||
| Section ID | Label | Description |
|
||||
|------------|-------|-------------|
|
||||
| `main` | (none) | Dashboard - always at top |
|
||||
| `superAdmin` | Super Admin | Super admin only tools |
|
||||
| `platformAdmin` | Platform Administration | Companies, vendors, messages |
|
||||
| `vendorOps` | Vendor Operations | Products, customers, inventory, orders |
|
||||
| `marketplace` | Marketplace | Letzshop integration |
|
||||
| `billing` | Billing & Subscriptions | Tiers, subscriptions, billing history |
|
||||
| `contentMgmt` | Content Management | Platforms, content pages, themes |
|
||||
| `devTools` | Developer Tools | Components, icons |
|
||||
| `platformHealth` | Platform Health | Capacity, testing, code quality |
|
||||
| `monitoring` | Platform Monitoring | Imports, tasks, logs, notifications |
|
||||
| `settingsSection` | Platform Settings | General settings, email templates, my menu |
|
||||
```python
|
||||
# app/modules/catalog/definition.py
|
||||
|
||||
### Vendor Menu Sections
|
||||
from app.modules.base import ModuleDefinition, MenuSectionDefinition, MenuItemDefinition
|
||||
from app.modules.enums import FrontendType
|
||||
|
||||
| Section ID | Label | Description |
|
||||
|------------|-------|-------------|
|
||||
| `main` | (none) | Dashboard, analytics |
|
||||
| `products` | Products & Inventory | Products, inventory, marketplace import |
|
||||
| `sales` | Sales & Orders | Orders, Letzshop orders, invoices |
|
||||
| `customers` | Customers | Customers, messages, notifications |
|
||||
| `shop` | Shop & Content | Content pages, media library |
|
||||
| `account` | Account & Settings | Team, profile, billing, settings |
|
||||
catalog_module = ModuleDefinition(
|
||||
code="catalog",
|
||||
name="Product Catalog",
|
||||
description="Product and category management",
|
||||
version="1.0.0",
|
||||
is_core=True,
|
||||
menus={
|
||||
FrontendType.ADMIN: [
|
||||
MenuSectionDefinition(
|
||||
id="catalog",
|
||||
label_key="menu.catalog",
|
||||
icon="package",
|
||||
order=30,
|
||||
items=[
|
||||
MenuItemDefinition(
|
||||
id="products",
|
||||
label_key="menu.products",
|
||||
icon="box",
|
||||
route="/admin/products",
|
||||
order=10,
|
||||
is_mandatory=True
|
||||
),
|
||||
MenuItemDefinition(
|
||||
id="categories",
|
||||
label_key="menu.categories",
|
||||
icon="folder-tree",
|
||||
route="/admin/categories",
|
||||
order=20
|
||||
),
|
||||
]
|
||||
)
|
||||
],
|
||||
FrontendType.VENDOR: [
|
||||
MenuSectionDefinition(
|
||||
id="products",
|
||||
label_key="menu.my_products",
|
||||
icon="package",
|
||||
order=10,
|
||||
items=[
|
||||
MenuItemDefinition(
|
||||
id="products",
|
||||
label_key="menu.products",
|
||||
icon="box",
|
||||
route="/vendor/{vendor_code}/products",
|
||||
order=10,
|
||||
is_mandatory=True
|
||||
),
|
||||
]
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### Menu Item Properties
|
||||
## Menu Discovery Service
|
||||
|
||||
Each menu item has these properties:
|
||||
The `MenuDiscoveryService` aggregates menus from all enabled modules:
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `id` | string | Unique identifier (used for config and access control) |
|
||||
| `label` | string | Display text |
|
||||
| `icon` | string | Heroicons icon name |
|
||||
| `url` | string | Route URL |
|
||||
| `super_admin_only` | boolean | (Optional) Only visible to super admins |
|
||||
```python
|
||||
from app.modules.core.services.menu_discovery_service import menu_discovery_service
|
||||
|
||||
# Get menu for a frontend type
|
||||
sections = menu_discovery_service.get_menu_for_frontend(
|
||||
db=db,
|
||||
frontend_type=FrontendType.ADMIN,
|
||||
platform_id=1,
|
||||
user_id=current_user.id,
|
||||
is_super_admin=current_user.is_super_admin,
|
||||
)
|
||||
|
||||
# Get all menu items for configuration UI
|
||||
all_items = menu_discovery_service.get_all_menu_items(
|
||||
db=db,
|
||||
frontend_type=FrontendType.ADMIN,
|
||||
platform_id=1,
|
||||
)
|
||||
```
|
||||
|
||||
### Discovery Flow
|
||||
|
||||
1. **Collect**: Get menu definitions from all modules in `MODULES` registry
|
||||
2. **Filter by Module**: Only include menus from enabled modules for the platform
|
||||
3. **Filter by Permissions**: Remove `super_admin_only` items for non-super admins
|
||||
4. **Apply Visibility**: Check `AdminMenuConfig` for hidden items
|
||||
5. **Sort**: Order sections and items by their `order` field
|
||||
6. **Return**: List of `MenuSectionDefinition` with filtered items
|
||||
|
||||
## Visibility Configuration
|
||||
|
||||
@@ -133,23 +209,16 @@ The system uses an **opt-out model**:
|
||||
|
||||
### Mandatory Items
|
||||
|
||||
Certain menu items cannot be hidden:
|
||||
Certain menu items cannot be hidden. These are marked with `is_mandatory=True` in their definition:
|
||||
|
||||
```python
|
||||
MANDATORY_MENU_ITEMS = {
|
||||
FrontendType.ADMIN: frozenset({
|
||||
"dashboard", # Landing page
|
||||
"companies", # Platform admin core
|
||||
"vendors", # Platform admin core
|
||||
"admin-users", # Super admin core
|
||||
"settings", # Configuration access
|
||||
"my-menu", # Must be able to undo changes
|
||||
}),
|
||||
FrontendType.VENDOR: frozenset({
|
||||
"dashboard", # Landing page
|
||||
"settings", # Configuration access
|
||||
}),
|
||||
}
|
||||
MenuItemDefinition(
|
||||
id="dashboard",
|
||||
label_key="menu.dashboard",
|
||||
icon="home",
|
||||
route="/admin/dashboard",
|
||||
is_mandatory=True, # Cannot be hidden
|
||||
)
|
||||
```
|
||||
|
||||
### Scope Types
|
||||
@@ -210,6 +279,9 @@ CREATE TABLE admin_menu_configs (
|
||||
### Examples
|
||||
|
||||
```python
|
||||
from app.modules.core.models import AdminMenuConfig
|
||||
from app.modules.enums import FrontendType
|
||||
|
||||
# Platform "OMS" hides inventory from admin panel
|
||||
AdminMenuConfig(
|
||||
frontend_type=FrontendType.ADMIN,
|
||||
@@ -237,97 +309,65 @@ AdminMenuConfig(
|
||||
|
||||
## Module Integration
|
||||
|
||||
Menu items are associated with modules. When a module is disabled for a platform, its menu items are automatically hidden.
|
||||
### Automatic Menu Discovery
|
||||
|
||||
### Module to Menu Item Mapping
|
||||
|
||||
Each module defines its menu items per frontend:
|
||||
When a module is enabled/disabled for a platform, the menu discovery service automatically includes/excludes its menu items:
|
||||
|
||||
```python
|
||||
# In module definition
|
||||
billing_module = ModuleDefinition(
|
||||
code="billing",
|
||||
menu_items={
|
||||
FrontendType.ADMIN: ["subscription-tiers", "subscriptions", "billing-history"],
|
||||
FrontendType.VENDOR: ["billing", "invoices"],
|
||||
},
|
||||
)
|
||||
```
|
||||
# Module enablement check happens automatically in MenuDiscoveryService
|
||||
enabled_modules = module_service.get_enabled_module_codes(db, platform_id)
|
||||
|
||||
### Menu Filtering Logic
|
||||
|
||||
```python
|
||||
from app.modules.service import module_service
|
||||
|
||||
# Get available menu items (module-aware)
|
||||
available_items = module_service.get_module_menu_items(
|
||||
db, platform_id, FrontendType.ADMIN
|
||||
)
|
||||
|
||||
# Check if specific menu item's module is enabled
|
||||
is_available = module_service.is_menu_item_module_enabled(
|
||||
db, platform_id, "subscription-tiers", FrontendType.ADMIN
|
||||
)
|
||||
# Only modules in enabled_modules have their menus included
|
||||
for module_code, module_def in MODULES.items():
|
||||
if module_code in enabled_modules:
|
||||
# Include this module's menus
|
||||
pass
|
||||
```
|
||||
|
||||
### Three-Layer Filtering
|
||||
|
||||
The final visible menu is computed by three layers:
|
||||
|
||||
1. **Registry**: All possible menu items
|
||||
1. **Module Definitions**: All possible menu items from modules
|
||||
2. **Module Enablement**: Items from disabled modules are hidden
|
||||
3. **Visibility Config**: Explicitly hidden items are removed
|
||||
|
||||
```
|
||||
Final Menu = Registry Items
|
||||
Final Menu = Module Menu Items
|
||||
- Items from Disabled Modules
|
||||
- Items with is_visible=False in config
|
||||
- Items not matching user role (super_admin_only)
|
||||
```
|
||||
|
||||
## Helper Functions
|
||||
## Menu Service Integration
|
||||
|
||||
### Getting Menu Items
|
||||
The `MenuService` provides the interface used by templates:
|
||||
|
||||
```python
|
||||
from app.config.menu_registry import (
|
||||
get_all_menu_item_ids,
|
||||
get_menu_item,
|
||||
is_super_admin_only_item,
|
||||
ADMIN_MENU_REGISTRY,
|
||||
VENDOR_MENU_REGISTRY,
|
||||
from app.modules.core.services import menu_service
|
||||
|
||||
# Get menu for rendering in templates
|
||||
menu_data = menu_service.get_menu_for_rendering(
|
||||
db=db,
|
||||
frontend_type=FrontendType.ADMIN,
|
||||
platform_id=platform_id,
|
||||
user_id=user_id,
|
||||
is_super_admin=is_super_admin,
|
||||
vendor_code=vendor_code, # For vendor frontend
|
||||
)
|
||||
|
||||
# Get all item IDs for a frontend
|
||||
admin_items = get_all_menu_item_ids(FrontendType.ADMIN)
|
||||
# Returns: {'dashboard', 'admin-users', 'companies', ...}
|
||||
|
||||
# Get specific item details
|
||||
item = get_menu_item(FrontendType.ADMIN, "subscription-tiers")
|
||||
# Returns: {
|
||||
# 'id': 'subscription-tiers',
|
||||
# 'label': 'Subscription Tiers',
|
||||
# 'icon': 'tag',
|
||||
# 'url': '/admin/subscription-tiers',
|
||||
# 'section_id': 'billing',
|
||||
# 'section_label': 'Billing & Subscriptions'
|
||||
# Returns legacy format for template compatibility:
|
||||
# {
|
||||
# "frontend_type": "admin",
|
||||
# "sections": [
|
||||
# {
|
||||
# "id": "main",
|
||||
# "label": None,
|
||||
# "items": [{"id": "dashboard", "label": "Dashboard", ...}]
|
||||
# },
|
||||
# ...
|
||||
# ]
|
||||
# }
|
||||
|
||||
# Check if item requires super admin
|
||||
is_restricted = is_super_admin_only_item("admin-users") # True
|
||||
```
|
||||
|
||||
### Menu Enums
|
||||
|
||||
```python
|
||||
from app.config.menu_registry import AdminMenuItem, VendorMenuItem
|
||||
|
||||
# Use enums for type safety
|
||||
AdminMenuItem.DASHBOARD.value # "dashboard"
|
||||
AdminMenuItem.INVENTORY.value # "inventory"
|
||||
|
||||
VendorMenuItem.PRODUCTS.value # "products"
|
||||
VendorMenuItem.ANALYTICS.value # "analytics"
|
||||
```
|
||||
|
||||
## Access Control
|
||||
@@ -380,24 +420,74 @@ Located at `/admin/my-menu`:
|
||||
- Personal preferences that don't affect other users
|
||||
- Useful for hiding rarely-used features
|
||||
|
||||
## Adding Menu Items to a Module
|
||||
|
||||
1. **Define menu sections and items** in your module's `definition.py`:
|
||||
|
||||
```python
|
||||
# app/modules/mymodule/definition.py
|
||||
|
||||
from app.modules.base import ModuleDefinition, MenuSectionDefinition, MenuItemDefinition
|
||||
from app.modules.enums import FrontendType
|
||||
|
||||
mymodule = ModuleDefinition(
|
||||
code="mymodule",
|
||||
name="My Module",
|
||||
description="Description of my module",
|
||||
version="1.0.0",
|
||||
menus={
|
||||
FrontendType.ADMIN: [
|
||||
MenuSectionDefinition(
|
||||
id="mymodule",
|
||||
label_key="mymodule.menu.section",
|
||||
icon="star",
|
||||
order=50, # Position in sidebar
|
||||
items=[
|
||||
MenuItemDefinition(
|
||||
id="mymodule-main",
|
||||
label_key="mymodule.menu.main",
|
||||
icon="star",
|
||||
route="/admin/mymodule",
|
||||
order=10,
|
||||
),
|
||||
]
|
||||
)
|
||||
],
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
2. **Add translation keys** in your module's locales:
|
||||
|
||||
```json
|
||||
// app/modules/mymodule/locales/en.json
|
||||
{
|
||||
"mymodule.menu.section": "My Module",
|
||||
"mymodule.menu.main": "Main Page"
|
||||
}
|
||||
```
|
||||
|
||||
3. **The menu is automatically discovered** - no registration needed.
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do
|
||||
|
||||
- Use menu item enums (`AdminMenuItem`, `VendorMenuItem`) for type safety
|
||||
- Check module enablement before showing module-specific menu items
|
||||
- Respect mandatory items - don't try to hide them
|
||||
- Use `require_menu_access` dependency to protect routes
|
||||
- Define menus in module `definition.py` using dataclasses
|
||||
- Use translation keys (`label_key`) for labels
|
||||
- Set appropriate `order` values for positioning
|
||||
- Mark essential items as `is_mandatory=True`
|
||||
- Use `is_super_admin_only=True` for admin-only features
|
||||
|
||||
### Don't
|
||||
|
||||
- Hardcode menu item IDs as strings (use enums)
|
||||
- Hardcode menu item labels (use i18n keys)
|
||||
- Store `is_visible=True` in database (default state, wastes space)
|
||||
- Allow hiding mandatory items via API
|
||||
- Create menu items without registering them in the registry
|
||||
- Create menu items outside of module definitions
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Module System](module-system.md) - Module architecture and menu item mapping
|
||||
- [Module System](module-system.md) - Module architecture and definitions
|
||||
- [Multi-Tenant System](multi-tenant.md) - Platform isolation
|
||||
- [Feature Gating](../implementation/feature-gating-system.md) - Tier-based access
|
||||
|
||||
@@ -2,34 +2,26 @@
|
||||
|
||||
## Overview
|
||||
|
||||
This project uses a **hybrid architecture** for models and schemas:
|
||||
This project uses a **module-based architecture** for models and schemas:
|
||||
|
||||
1. **CORE models/schemas** in `models/database/` and `models/schema/` - Framework-level entities used across modules
|
||||
2. **Module models/schemas** in `app/modules/<module>/models/` and `app/modules/<module>/schemas/` - Domain-specific entities
|
||||
1. **Infrastructure** in `models/database/` and `models/schema/` - Base classes only
|
||||
2. **Module models/schemas** in `app/modules/<module>/models/` and `app/modules/<module>/schemas/` - All domain entities
|
||||
3. **Inline schemas** defined directly in API route files - Endpoint-specific request/response models
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
models/ # CORE framework models
|
||||
├── database/ # SQLAlchemy models (ORM)
|
||||
│ ├── __init__.py # Exports all models + module discovery
|
||||
│ ├── base.py # Base class, mixins
|
||||
│ ├── user.py # User authentication
|
||||
│ ├── vendor.py # Vendor, VendorUser, Role
|
||||
│ ├── company.py # Company management
|
||||
│ ├── platform.py # Multi-platform support
|
||||
│ ├── media.py # MediaFile (cross-cutting)
|
||||
│ └── ...
|
||||
models/ # INFRASTRUCTURE ONLY
|
||||
├── database/ # SQLAlchemy base classes
|
||||
│ ├── __init__.py # Exports Base, TimestampMixin only
|
||||
│ └── base.py # Base class, mixins
|
||||
│
|
||||
└── schema/ # Pydantic schemas (API validation)
|
||||
├── __init__.py
|
||||
├── auth.py # Login, tokens, password reset
|
||||
├── vendor.py # Vendor CRUD schemas
|
||||
├── company.py # Company schemas
|
||||
└── ...
|
||||
└── schema/ # Pydantic base classes
|
||||
├── __init__.py # Exports base and auth only
|
||||
├── base.py # Base schema classes
|
||||
└── auth.py # Auth schemas (cross-cutting)
|
||||
|
||||
app/modules/<module>/ # Domain modules
|
||||
app/modules/<module>/ # Domain modules (ALL domain entities)
|
||||
├── models/ # Module-specific database models
|
||||
│ ├── __init__.py # Canonical exports
|
||||
│ └── *.py # Model definitions
|
||||
@@ -43,20 +35,28 @@ app/modules/<module>/ # Domain modules
|
||||
|
||||
## Architecture Principles
|
||||
|
||||
### 1. CORE vs Module Models
|
||||
### 1. Infrastructure vs Module Code
|
||||
|
||||
**CORE models** (`models/database/`) are framework-level entities used across multiple modules:
|
||||
- `User` - Authentication, used by all modules
|
||||
- `Vendor` - Multi-tenant anchor, used by all vendor-scoped modules
|
||||
- `Company` - Business entity management
|
||||
- `Platform` - Multi-platform CMS support
|
||||
- `MediaFile` - File storage, used by catalog, CMS, etc.
|
||||
**Infrastructure** (`models/database/`, `models/schema/`) provides base classes only:
|
||||
- `Base` - SQLAlchemy declarative base
|
||||
- `TimestampMixin` - created_at/updated_at columns
|
||||
- `BaseModel` patterns in `models/schema/base.py`
|
||||
- `auth.py` - Authentication schemas (cross-cutting concern)
|
||||
|
||||
**Module models** (`app/modules/<module>/models/`) are domain-specific:
|
||||
- `billing/models/` - Feature, SubscriptionTier, VendorSubscription
|
||||
- `catalog/models/` - Product, ProductTranslation, ProductMedia
|
||||
- `orders/models/` - Order, OrderItem, Invoice
|
||||
- `inventory/models/` - Inventory, InventoryTransaction
|
||||
**Module code** (`app/modules/<module>/`) contains all domain-specific entities:
|
||||
|
||||
| Module | Models | Schemas |
|
||||
|--------|--------|---------|
|
||||
| `tenancy` | User, Admin, Vendor, Company, Platform, VendorDomain | company, vendor, admin, team, vendor_domain |
|
||||
| `billing` | Feature, SubscriptionTier, VendorSubscription | billing, subscription |
|
||||
| `catalog` | Product, ProductTranslation, ProductMedia | catalog, product, vendor_product |
|
||||
| `orders` | Order, OrderItem, Invoice | order, invoice, order_item_exception |
|
||||
| `inventory` | Inventory, InventoryTransaction | inventory |
|
||||
| `cms` | ContentPage, MediaFile, VendorTheme | content_page, media, image, vendor_theme |
|
||||
| `messaging` | Email, VendorEmailSettings, VendorEmailTemplate, Message, Notification | email, message, notification |
|
||||
| `customers` | Customer, PasswordResetToken | customer, context |
|
||||
| `marketplace` | 5 models | 4 schemas |
|
||||
| `core` | AdminMenuConfig | (inline) |
|
||||
|
||||
### 2. Schema Patterns
|
||||
|
||||
@@ -84,7 +84,7 @@ class InventoryResponse(BaseModel):
|
||||
For schemas only used by a single endpoint or closely related endpoints.
|
||||
|
||||
```python
|
||||
# app/api/v1/admin/platforms.py
|
||||
# app/modules/tenancy/routes/api/admin_platforms.py
|
||||
class PlatformResponse(BaseModel):
|
||||
"""Platform response schema - only used in this file."""
|
||||
id: int
|
||||
@@ -122,7 +122,6 @@ class VendorPlatform(Base):
|
||||
| Schema used by single endpoint | Inline | In the route file |
|
||||
| Admin-only endpoint schemas | Inline | In the admin route file |
|
||||
| Model not exposed via API | No schema | N/A |
|
||||
| Cross-module utility schemas | Dedicated file | `models/schema/` |
|
||||
|
||||
---
|
||||
|
||||
@@ -168,74 +167,100 @@ class VendorPlatform(Base):
|
||||
|
||||
## Import Guidelines
|
||||
|
||||
### Canonical Imports (Preferred)
|
||||
### Canonical Imports (Required)
|
||||
|
||||
```python
|
||||
# CORE models - from models.database
|
||||
from models.database import User, Vendor, MediaFile
|
||||
# Infrastructure - base classes only
|
||||
from models.database import Base, TimestampMixin
|
||||
from models.schema.auth import LoginRequest, TokenResponse
|
||||
|
||||
# Module models - from app.modules.<module>.models
|
||||
from app.modules.tenancy.models import User, Vendor, Company
|
||||
from app.modules.billing.models import Feature, SubscriptionTier
|
||||
from app.modules.catalog.models import Product, ProductMedia
|
||||
from app.modules.orders.models import Order, OrderItem
|
||||
|
||||
# CORE schemas - from models.schema
|
||||
from models.schema.auth import LoginRequest, TokenResponse
|
||||
from app.modules.cms.models import MediaFile, VendorTheme
|
||||
from app.modules.messaging.models import Email, VendorEmailTemplate
|
||||
|
||||
# Module schemas - from app.modules.<module>.schemas
|
||||
from app.modules.tenancy.schemas import VendorCreate, CompanyResponse
|
||||
from app.modules.cms.schemas import MediaItemResponse, VendorThemeResponse
|
||||
from app.modules.messaging.schemas import EmailTemplateResponse
|
||||
from app.modules.inventory.schemas import InventoryCreate, InventoryResponse
|
||||
from app.modules.orders.schemas import OrderResponse
|
||||
```
|
||||
|
||||
### Legacy Re-exports (Backwards Compatibility)
|
||||
### Legacy Imports (DEPRECATED)
|
||||
|
||||
`models/database/__init__.py` re-exports module models for backwards compatibility:
|
||||
The following import patterns are deprecated and will cause architecture validation errors:
|
||||
|
||||
```python
|
||||
# These work but prefer canonical imports
|
||||
from models.database import Product # Re-exported from catalog module
|
||||
from models.database import Order # Re-exported from orders module
|
||||
# DEPRECATED - Don't import domain models from models.database
|
||||
from models.database import User, Vendor # WRONG
|
||||
|
||||
# DEPRECATED - Don't import domain schemas from models.schema
|
||||
from models.schema.vendor import VendorCreate # WRONG
|
||||
from models.schema.company import CompanyResponse # WRONG
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Gap Analysis: Models vs Schemas
|
||||
## Module Ownership Reference
|
||||
|
||||
Not every database model needs a dedicated schema file. Here's the current alignment:
|
||||
### Tenancy Module (`app/modules/tenancy/`)
|
||||
|
||||
### CORE Framework
|
||||
**Models:**
|
||||
- `User` - User authentication and profile
|
||||
- `Admin` - Admin user management
|
||||
- `Vendor` - Vendor/merchant entities
|
||||
- `VendorUser` - Vendor team members
|
||||
- `Company` - Company management
|
||||
- `Platform` - Multi-platform support
|
||||
- `AdminPlatform` - Admin-platform association
|
||||
- `VendorPlatform` - Vendor-platform association
|
||||
- `PlatformModule` - Module configuration per platform
|
||||
- `VendorDomain` - Custom domain configuration
|
||||
|
||||
| Database Model | Schema | Notes |
|
||||
|----------------|--------|-------|
|
||||
| `user.py` | `auth.py` | Auth schemas cover user operations |
|
||||
| `vendor.py` | `vendor.py` | Full CRUD schemas |
|
||||
| `company.py` | `company.py` | Full CRUD schemas |
|
||||
| `media.py` | `media.py` | Upload/response schemas |
|
||||
| `platform.py` | Inline | Admin-only, in `platforms.py` route |
|
||||
| `platform_module.py` | Inline | Admin-only, in `modules.py` route |
|
||||
| `admin_menu_config.py` | Inline | Admin-only, in `menu_config.py` route |
|
||||
| `vendor_email_settings.py` | Inline | In `email_settings.py` route |
|
||||
| `vendor_email_template.py` | None | Internal email service use |
|
||||
| `vendor_platform.py` | None | Internal association table |
|
||||
**Schemas:**
|
||||
- `company.py` - Company CRUD schemas
|
||||
- `vendor.py` - Vendor CRUD and Letzshop export schemas
|
||||
- `admin.py` - Admin user and audit log schemas
|
||||
- `team.py` - Team management and invitation schemas
|
||||
- `vendor_domain.py` - Domain configuration schemas
|
||||
|
||||
### Modules
|
||||
### CMS Module (`app/modules/cms/`)
|
||||
|
||||
| Module | Models | Schemas | Alignment |
|
||||
|--------|--------|---------|-----------|
|
||||
| billing | feature, subscription | billing, subscription | ✅ |
|
||||
| cart | cart | cart | ✅ |
|
||||
| catalog | product, product_media, product_translation | catalog, product, vendor_product | ✅ |
|
||||
| checkout | - | checkout | Schema-only (orchestration) |
|
||||
| cms | content_page | content_page, homepage_sections | ✅ |
|
||||
| customers | customer, password_reset_token | customer, context | ✅ (password reset uses auth schemas) |
|
||||
| dev_tools | architecture_scan, test_run | Inline | Admin-only, inline in route files |
|
||||
| inventory | inventory, inventory_transaction | inventory | ✅ (transaction included in inventory.py) |
|
||||
| loyalty | 5 models | 5 schemas | ✅ |
|
||||
| marketplace | 5 models | 4 schemas | ✅ (translation in product schema) |
|
||||
| messaging | admin_notification, message | message, notification | ✅ |
|
||||
| orders | order, order_item, invoice | order, invoice, order_item_exception | ✅ |
|
||||
| payments | - | payment | Schema-only (external APIs) |
|
||||
| analytics | - | stats | Schema-only (aggregation views) |
|
||||
**Models:**
|
||||
- `ContentPage` - CMS content pages
|
||||
- `MediaFile` - File storage and management
|
||||
- `VendorTheme` - Theme customization
|
||||
|
||||
**Schemas:**
|
||||
- `content_page.py` - Content page schemas
|
||||
- `media.py` - Media upload/response schemas
|
||||
- `image.py` - Image handling schemas
|
||||
- `vendor_theme.py` - Theme configuration schemas
|
||||
|
||||
### Messaging Module (`app/modules/messaging/`)
|
||||
|
||||
**Models:**
|
||||
- `Email` - Email records
|
||||
- `VendorEmailSettings` - Email configuration
|
||||
- `VendorEmailTemplate` - Email templates
|
||||
- `Message` - Internal messages
|
||||
- `AdminNotification` - Admin notifications
|
||||
|
||||
**Schemas:**
|
||||
- `email.py` - Email template schemas
|
||||
- `message.py` - Message schemas
|
||||
- `notification.py` - Notification schemas
|
||||
|
||||
### Core Module (`app/modules/core/`)
|
||||
|
||||
**Models:**
|
||||
- `AdminMenuConfig` - Menu visibility configuration
|
||||
|
||||
**Schemas:** (inline in route files)
|
||||
|
||||
---
|
||||
|
||||
@@ -244,16 +269,14 @@ Not every database model needs a dedicated schema file. Here's the current align
|
||||
### Database Model Checklist
|
||||
|
||||
1. **Determine location:**
|
||||
- Cross-module use → `models/database/`
|
||||
- Module-specific → `app/modules/<module>/models/`
|
||||
- All domain models → `app/modules/<module>/models/`
|
||||
|
||||
2. **Create the model file:**
|
||||
```python
|
||||
# app/modules/mymodule/models/my_entity.py
|
||||
from sqlalchemy import Column, Integer, String, ForeignKey
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.core.database import Base
|
||||
from models.database.base import TimestampMixin
|
||||
from models.database import Base, TimestampMixin
|
||||
|
||||
class MyEntity(Base, TimestampMixin):
|
||||
__tablename__ = "my_entities"
|
||||
@@ -299,7 +322,7 @@ Not every database model needs a dedicated schema file. Here's the current align
|
||||
|
||||
3. **Or use inline schema:**
|
||||
```python
|
||||
# app/api/v1/vendor/my_entity.py
|
||||
# app/modules/mymodule/routes/api/vendor.py
|
||||
from pydantic import BaseModel
|
||||
|
||||
class MyEntityResponse(BaseModel):
|
||||
@@ -320,8 +343,8 @@ Not every database model needs a dedicated schema file. Here's the current align
|
||||
|-----------|----------------|
|
||||
| Services access DB directly | `from app.modules.X.models import Model` |
|
||||
| APIs validate with schemas | Request/response Pydantic models |
|
||||
| Reusable schemas | Dedicated files in `schemas/` |
|
||||
| Reusable schemas | Dedicated files in `app/modules/<module>/schemas/` |
|
||||
| Endpoint-specific schemas | Inline in route files |
|
||||
| Internal models | No schema needed |
|
||||
| CORE models | `models/database/` |
|
||||
| Module models | `app/modules/<module>/models/` |
|
||||
| All domain models | `app/modules/<module>/models/` |
|
||||
| Infrastructure only | `models/database/` (Base, TimestampMixin only) |
|
||||
|
||||
Reference in New Issue
Block a user