Files
orion/alembic/env.py
Samir Boulahtit ec4ec045fc feat: complete CMS as fully autonomous self-contained module
Transform CMS from a thin wrapper into a fully self-contained module with
all code living within app/modules/cms/:

Module Structure:
- models/: ContentPage model (canonical location with dynamic discovery)
- schemas/: Pydantic schemas for API validation
- services/: ContentPageService business logic
- exceptions/: Module-specific exceptions
- routes/api/: REST API endpoints (admin, vendor, shop)
- routes/pages/: HTML page routes (admin, vendor)
- templates/cms/: Jinja2 templates (namespaced)
- static/: JavaScript files (admin/vendor)
- locales/: i18n translations (en, fr, de, lb)

Key Changes:
- Move ContentPage model to module with dynamic model discovery
- Create Pydantic schemas package for request/response validation
- Extract API routes from app/api/v1/*/ to module
- Extract page routes from admin_pages.py/vendor_pages.py to module
- Move static JS files to module with dedicated mount point
- Update templates to use cms_static for module assets
- Add module static file mounting in main.py
- Delete old scattered files (no shims - hard errors on old imports)

This establishes the pattern for migrating other modules to be
fully autonomous and independently deployable.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 22:42:46 +01:00

266 lines
8.4 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 models.database.admin 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 models.database.user import User
print(" ✓ User model imported")
except ImportError as e:
print(f" ✗ User model failed: {e}")
# ----------------------------------------------------------------------------
# VENDOR MODELS
# ----------------------------------------------------------------------------
try:
from models.database.vendor 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 models.database.vendor_domain import VendorDomain
print(" ✓ VendorDomain model imported")
except ImportError as e:
print(f" ✗ VendorDomain model failed: {e}")
try:
from models.database.vendor_theme 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 models.database.product import Product
print(" ✓ Product model imported")
except ImportError as e:
print(f" ✗ Product model failed: {e}")
try:
from models.database.marketplace_product import MarketplaceProduct
print(" ✓ MarketplaceProduct model imported")
except ImportError as e:
print(f" ✗ MarketplaceProduct model failed: {e}")
# ----------------------------------------------------------------------------
# INVENTORY MODEL
# ----------------------------------------------------------------------------
try:
from models.database.inventory import Inventory
print(" ✓ Inventory model imported")
except ImportError as e:
print(f" ✗ Inventory model failed: {e}")
# ----------------------------------------------------------------------------
# MARKETPLACE IMPORT
# ----------------------------------------------------------------------------
try:
from models.database.marketplace_import_job import MarketplaceImportJob
print(" ✓ MarketplaceImportJob model imported")
except ImportError as e:
print(f" ✗ MarketplaceImportJob model failed: {e}")
# ----------------------------------------------------------------------------
# CUSTOMER MODELS
# ----------------------------------------------------------------------------
try:
from models.database.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 models.database.cart import CartItem
print(" ✓ Cart models imported (1 model)")
print(" - CartItem")
except ImportError as e:
print(f" ✗ Cart models failed: {e}")
# ----------------------------------------------------------------------------
# ORDER MODELS
# ----------------------------------------------------------------------------
try:
from models.database.order 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
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"},
)
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)
with context.begin_transaction():
context.run_migrations()
# ============================================================================
# MAIN EXECUTION
# ============================================================================
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()