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>
306 lines
9.6 KiB
Python
306 lines
9.6 KiB
Python
# alembic/env.py
|
|
"""
|
|
Alembic migration environment configuration.
|
|
|
|
This file is responsible for:
|
|
1. Importing ALL database models so Alembic can detect schema changes
|
|
2. Configuring the database connection
|
|
3. Running migrations in online/offline mode
|
|
|
|
CRITICAL: Every model in models/database/__init__.py must be imported here!
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
from logging.config import fileConfig
|
|
|
|
from sqlalchemy import engine_from_config, pool
|
|
|
|
from alembic import context
|
|
|
|
# Add your project directory to the Python path
|
|
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
|
|
|
from app.core.config import settings
|
|
from models.database.base import Base
|
|
|
|
# ============================================================================
|
|
# IMPORT ALL DATABASE MODELS
|
|
# ============================================================================
|
|
# CRITICAL: Every model must be imported here so Alembic can detect tables!
|
|
# If a model is not imported, Alembic will not create/update its table.
|
|
#
|
|
# Models list must match: models/database/__init__.py
|
|
# ============================================================================
|
|
|
|
print("[ALEMBIC] Importing database models...")
|
|
print("=" * 70)
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# ADMIN MODELS
|
|
# ----------------------------------------------------------------------------
|
|
try:
|
|
from app.modules.tenancy.models import (
|
|
AdminAuditLog,
|
|
AdminNotification,
|
|
AdminSession,
|
|
AdminSetting,
|
|
PlatformAlert,
|
|
)
|
|
|
|
print(" ✓ Admin models imported (5 models)")
|
|
print(" - AdminAuditLog")
|
|
print(" - AdminNotification")
|
|
print(" - AdminSetting")
|
|
print(" - PlatformAlert")
|
|
print(" - AdminSession")
|
|
except ImportError as e:
|
|
print(f" ✗ Admin models failed: {e}")
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# USER MODEL
|
|
# ----------------------------------------------------------------------------
|
|
try:
|
|
from app.modules.tenancy.models import User
|
|
|
|
print(" ✓ User model imported")
|
|
except ImportError as e:
|
|
print(f" ✗ User model failed: {e}")
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# VENDOR MODELS
|
|
# ----------------------------------------------------------------------------
|
|
try:
|
|
from app.modules.tenancy.models import Role, Vendor, VendorUser
|
|
|
|
print(" ✓ Vendor models imported (3 models)")
|
|
print(" - Vendor")
|
|
print(" - VendorUser")
|
|
print(" - Role")
|
|
except ImportError as e:
|
|
print(f" ✗ Vendor models failed: {e}")
|
|
|
|
try:
|
|
from app.modules.tenancy.models import VendorDomain
|
|
|
|
print(" ✓ VendorDomain model imported")
|
|
except ImportError as e:
|
|
print(f" ✗ VendorDomain model failed: {e}")
|
|
|
|
try:
|
|
from app.modules.cms.models import VendorTheme
|
|
|
|
print(" ✓ VendorTheme model imported")
|
|
except ImportError as e:
|
|
print(f" ✗ VendorTheme model failed: {e}")
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# CONTENT PAGE MODEL (CMS Module)
|
|
# ----------------------------------------------------------------------------
|
|
try:
|
|
from app.modules.cms.models import ContentPage
|
|
|
|
print(" ✓ ContentPage model imported (from CMS module)")
|
|
except ImportError as e:
|
|
print(f" ✗ ContentPage model failed: {e}")
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# PRODUCT MODELS
|
|
# ----------------------------------------------------------------------------
|
|
try:
|
|
from app.modules.catalog.models import Product
|
|
|
|
print(" ✓ Product model imported")
|
|
except ImportError as e:
|
|
print(f" ✗ Product model failed: {e}")
|
|
|
|
try:
|
|
from app.modules.marketplace.models import MarketplaceProduct
|
|
|
|
print(" ✓ MarketplaceProduct model imported")
|
|
except ImportError as e:
|
|
print(f" ✗ MarketplaceProduct model failed: {e}")
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# INVENTORY MODEL
|
|
# ----------------------------------------------------------------------------
|
|
try:
|
|
from app.modules.inventory.models import Inventory
|
|
|
|
print(" ✓ Inventory model imported")
|
|
except ImportError as e:
|
|
print(f" ✗ Inventory model failed: {e}")
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# MARKETPLACE IMPORT
|
|
# ----------------------------------------------------------------------------
|
|
try:
|
|
from app.modules.marketplace.models import MarketplaceImportJob
|
|
|
|
print(" ✓ MarketplaceImportJob model imported")
|
|
except ImportError as e:
|
|
print(f" ✗ MarketplaceImportJob model failed: {e}")
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# CUSTOMER MODELS
|
|
# ----------------------------------------------------------------------------
|
|
try:
|
|
from app.modules.customers.models.customer import Customer, CustomerAddress
|
|
|
|
print(" ✓ Customer models imported (2 models)")
|
|
print(" - Customer")
|
|
print(" - CustomerAddress")
|
|
except ImportError as e:
|
|
print(f" ✗ Customer models failed: {e}")
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# CART MODELS
|
|
# ----------------------------------------------------------------------------
|
|
try:
|
|
from app.modules.cart.models import CartItem
|
|
|
|
print(" ✓ Cart models imported (1 model)")
|
|
print(" - CartItem")
|
|
except ImportError as e:
|
|
print(f" ✗ Cart models failed: {e}")
|
|
|
|
# ----------------------------------------------------------------------------
|
|
# ORDER MODELS
|
|
# ----------------------------------------------------------------------------
|
|
try:
|
|
from app.modules.orders.models import Order, OrderItem
|
|
|
|
print(" ✓ Order models imported (2 models)")
|
|
print(" - Order")
|
|
print(" - OrderItem")
|
|
except ImportError as e:
|
|
print(f" ✗ Order models failed: {e}")
|
|
|
|
# ============================================================================
|
|
# SUMMARY
|
|
# ============================================================================
|
|
print("=" * 70)
|
|
print("[ALEMBIC] Model import completed")
|
|
print("[ALEMBIC] Tables detected in metadata:")
|
|
print("=" * 70)
|
|
|
|
if Base.metadata.tables:
|
|
for i, table_name in enumerate(sorted(Base.metadata.tables.keys()), 1):
|
|
print(f" {i:2d}. {table_name}")
|
|
print("=" * 70)
|
|
print(f"[ALEMBIC] Total tables: {len(Base.metadata.tables)}")
|
|
else:
|
|
print(" ⚠️ WARNING: No tables found in metadata!")
|
|
print(" This usually means models are not being imported correctly.")
|
|
|
|
print("=" * 70)
|
|
print()
|
|
|
|
# ============================================================================
|
|
# ALEMBIC CONFIGURATION
|
|
# ============================================================================
|
|
|
|
# Alembic Config object
|
|
config = context.config
|
|
|
|
# Override sqlalchemy.url with our settings
|
|
config.set_main_option("sqlalchemy.url", settings.database_url)
|
|
|
|
if config.config_file_name is not None:
|
|
fileConfig(config.config_file_name)
|
|
|
|
# Set target metadata from Base
|
|
target_metadata = Base.metadata
|
|
|
|
|
|
# ============================================================================
|
|
# MODULE MIGRATIONS DISCOVERY
|
|
# ============================================================================
|
|
# Discover migration paths from self-contained modules.
|
|
# Each module can have its own migrations/ directory.
|
|
# ============================================================================
|
|
|
|
def get_version_locations() -> list[str]:
|
|
"""
|
|
Get all version locations including module migrations.
|
|
|
|
Returns:
|
|
List of paths to migration version directories
|
|
"""
|
|
try:
|
|
from app.modules.migrations import get_all_migration_paths
|
|
|
|
paths = get_all_migration_paths()
|
|
locations = [str(p) for p in paths if p.exists()]
|
|
|
|
if len(locations) > 1:
|
|
print(f"[ALEMBIC] Found {len(locations)} migration locations:")
|
|
for loc in locations:
|
|
print(f" - {loc}")
|
|
|
|
return locations
|
|
except ImportError:
|
|
# Fallback if module migrations not available
|
|
return ["alembic/versions"]
|
|
|
|
|
|
# Get version locations for multi-directory support
|
|
version_locations = get_version_locations()
|
|
|
|
|
|
def run_migrations_offline() -> None:
|
|
"""
|
|
Run migrations in 'offline' mode.
|
|
|
|
This configures the context with just a URL and not an Engine,
|
|
though an Engine is acceptable here as well. By skipping the Engine
|
|
creation we don't even need a DBAPI to be available.
|
|
|
|
Calls to context.execute() here emit the given string to the script output.
|
|
"""
|
|
url = config.get_main_option("sqlalchemy.url")
|
|
context.configure(
|
|
url=url,
|
|
target_metadata=target_metadata,
|
|
literal_binds=True,
|
|
dialect_opts={"paramstyle": "named"},
|
|
version_locations=version_locations,
|
|
)
|
|
|
|
with context.begin_transaction():
|
|
context.run_migrations()
|
|
|
|
|
|
def run_migrations_online() -> None:
|
|
"""
|
|
Run migrations in 'online' mode.
|
|
|
|
In this scenario we need to create an Engine and associate a connection
|
|
with the context.
|
|
"""
|
|
connectable = engine_from_config(
|
|
config.get_section(config.config_ini_section, {}),
|
|
prefix="sqlalchemy.",
|
|
poolclass=pool.NullPool,
|
|
)
|
|
|
|
with connectable.connect() as connection:
|
|
context.configure(
|
|
connection=connection,
|
|
target_metadata=target_metadata,
|
|
version_locations=version_locations,
|
|
)
|
|
|
|
with context.begin_transaction():
|
|
context.run_migrations()
|
|
|
|
|
|
# ============================================================================
|
|
# MAIN EXECUTION
|
|
# ============================================================================
|
|
|
|
if context.is_offline_mode():
|
|
run_migrations_offline()
|
|
else:
|
|
run_migrations_online()
|