diff --git a/alembic/versions/z4e5f6a7b8c9_add_multi_platform_support.py b/alembic/versions/z4e5f6a7b8c9_add_multi_platform_support.py
index 46920729..185ed0cf 100644
--- a/alembic/versions/z4e5f6a7b8c9_add_multi_platform_support.py
+++ b/alembic/versions/z4e5f6a7b8c9_add_multi_platform_support.py
@@ -178,9 +178,7 @@ def upgrade() -> None:
""")
)
- # Get the OMS platform ID
- result = conn.execute(sa.text("SELECT id FROM platforms WHERE code = 'oms'"))
- oms_platform_id = result.fetchone()[0]
+I dn
# =========================================================================
# 6. Backfill content_pages with platform_id
diff --git a/alembic/versions/z7h8i9j0k1l2_fix_content_page_nullable_columns.py b/alembic/versions/z7h8i9j0k1l2_fix_content_page_nullable_columns.py
new file mode 100644
index 00000000..12a0fe1f
--- /dev/null
+++ b/alembic/versions/z7h8i9j0k1l2_fix_content_page_nullable_columns.py
@@ -0,0 +1,115 @@
+"""Fix content_page nullable boolean columns
+
+Revision ID: z7h8i9j0k1l2
+Revises: z6g7h8i9j0k1
+Create Date: 2026-01-20
+
+This migration:
+1. Sets NULL values to defaults for boolean and integer columns
+2. Alters columns to be NOT NULL
+"""
+
+from alembic import op
+import sqlalchemy as sa
+
+
+# revision identifiers, used by Alembic.
+revision = "z7h8i9j0k1l2"
+down_revision = "z6g7h8i9j0k1"
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+ # First, update any NULL values to defaults
+ op.execute("""
+ UPDATE content_pages
+ SET display_order = 0
+ WHERE display_order IS NULL
+ """)
+
+ op.execute("""
+ UPDATE content_pages
+ SET show_in_footer = true
+ WHERE show_in_footer IS NULL
+ """)
+
+ op.execute("""
+ UPDATE content_pages
+ SET show_in_header = false
+ WHERE show_in_header IS NULL
+ """)
+
+ op.execute("""
+ UPDATE content_pages
+ SET show_in_legal = false
+ WHERE show_in_legal IS NULL
+ """)
+
+ # Now alter columns to be NOT NULL
+ op.alter_column(
+ "content_pages",
+ "display_order",
+ existing_type=sa.Integer(),
+ nullable=False,
+ server_default="0",
+ )
+
+ op.alter_column(
+ "content_pages",
+ "show_in_footer",
+ existing_type=sa.Boolean(),
+ nullable=False,
+ server_default="true",
+ )
+
+ op.alter_column(
+ "content_pages",
+ "show_in_header",
+ existing_type=sa.Boolean(),
+ nullable=False,
+ server_default="false",
+ )
+
+ op.alter_column(
+ "content_pages",
+ "show_in_legal",
+ existing_type=sa.Boolean(),
+ nullable=False,
+ server_default="false",
+ )
+
+
+def downgrade() -> None:
+ # Revert columns to nullable (no server default)
+ op.alter_column(
+ "content_pages",
+ "display_order",
+ existing_type=sa.Integer(),
+ nullable=True,
+ server_default=None,
+ )
+
+ op.alter_column(
+ "content_pages",
+ "show_in_footer",
+ existing_type=sa.Boolean(),
+ nullable=True,
+ server_default=None,
+ )
+
+ op.alter_column(
+ "content_pages",
+ "show_in_header",
+ existing_type=sa.Boolean(),
+ nullable=True,
+ server_default=None,
+ )
+
+ op.alter_column(
+ "content_pages",
+ "show_in_legal",
+ existing_type=sa.Boolean(),
+ nullable=True,
+ server_default=None,
+ )
diff --git a/app/platforms/shared/base_platform.py b/app/platforms/shared/base_platform.py
index b3d5d1f3..f8d20c2b 100644
--- a/app/platforms/shared/base_platform.py
+++ b/app/platforms/shared/base_platform.py
@@ -69,7 +69,6 @@ class BasePlatformConfig(ABC):
"""
return [
"home",
- "platform_homepage",
"pricing",
"about",
"contact",
diff --git a/app/routes/admin_pages.py b/app/routes/admin_pages.py
index 89b1cd6e..b03d5dda 100644
--- a/app/routes/admin_pages.py
+++ b/app/routes/admin_pages.py
@@ -1116,23 +1116,21 @@ async def admin_platform_edit(
# ============================================================================
-@router.get("/platform-homepage", response_class=HTMLResponse, include_in_schema=False)
+@router.get("/platform-homepage", include_in_schema=False)
async def admin_platform_homepage_manager(
request: Request,
current_user: User = Depends(get_current_admin_from_cookie_or_header),
db: Session = Depends(get_db),
):
"""
- Render platform homepage manager.
- Allows editing the main platform homepage with template selection.
+ Deprecated: Redirects to platforms page.
+
+ Platform homepages are now managed via:
+ - /admin/platforms → Select platform → Homepage button
+ - Or directly: /admin/content-pages?platform_code={code}&slug=home
"""
- return templates.TemplateResponse(
- "admin/platform-homepage.html",
- {
- "request": request,
- "user": current_user,
- },
- )
+ from starlette.responses import RedirectResponse
+ return RedirectResponse(url="/admin/platforms", status_code=302)
@router.get("/content-pages", response_class=HTMLResponse, include_in_schema=False)
diff --git a/app/routes/platform_pages.py b/app/routes/platform_pages.py
index 1b7fea18..1fc045f8 100644
--- a/app/routes/platform_pages.py
+++ b/app/routes/platform_pages.py
@@ -158,18 +158,13 @@ async def homepage(
return RedirectResponse(url="/shop/", status_code=302)
# Scenario 2: Platform marketing site (no vendor)
- # Try to load platform homepage from CMS
+ # Load platform homepage from CMS (slug='home')
platform_id = platform.id if platform else 1
cms_homepage = content_page_service.get_platform_page(
db, platform_id=platform_id, slug="home", include_unpublished=False
)
- if not cms_homepage:
- cms_homepage = content_page_service.get_platform_page(
- db, platform_id=platform_id, slug="platform_homepage", include_unpublished=False
- )
-
if cms_homepage:
# Use CMS-based homepage with template selection
context = get_platform_context(request, db)
diff --git a/app/services/content_page_service.py b/app/services/content_page_service.py
index 816be7b0..c94edbb4 100644
--- a/app/services/content_page_service.py
+++ b/app/services/content_page_service.py
@@ -305,6 +305,66 @@ class ContentPageService:
.all()
)
+ @staticmethod
+ def list_all_platform_pages(
+ db: Session,
+ include_unpublished: bool = False,
+ ) -> list[ContentPage]:
+ """
+ List all platform marketing pages across all platforms (for admin use).
+
+ Args:
+ db: Database session
+ include_unpublished: Include draft pages
+
+ Returns:
+ List of all platform marketing ContentPage objects
+ """
+ filters = [
+ ContentPage.vendor_id.is_(None),
+ ContentPage.is_platform_page.is_(True),
+ ]
+
+ if not include_unpublished:
+ filters.append(ContentPage.is_published.is_(True))
+
+ return (
+ db.query(ContentPage)
+ .filter(and_(*filters))
+ .order_by(ContentPage.platform_id, ContentPage.display_order, ContentPage.title)
+ .all()
+ )
+
+ @staticmethod
+ def list_all_vendor_defaults(
+ db: Session,
+ include_unpublished: bool = False,
+ ) -> list[ContentPage]:
+ """
+ List all vendor default pages across all platforms (for admin use).
+
+ Args:
+ db: Database session
+ include_unpublished: Include draft pages
+
+ Returns:
+ List of all vendor default ContentPage objects
+ """
+ filters = [
+ ContentPage.vendor_id.is_(None),
+ ContentPage.is_platform_page.is_(False),
+ ]
+
+ if not include_unpublished:
+ filters.append(ContentPage.is_published.is_(True))
+
+ return (
+ db.query(ContentPage)
+ .filter(and_(*filters))
+ .order_by(ContentPage.platform_id, ContentPage.display_order, ContentPage.title)
+ .all()
+ )
+
# =========================================================================
# CRUD Methods
# =========================================================================
diff --git a/app/templates/admin/content-pages.html b/app/templates/admin/content-pages.html
index 09e44c5d..a38d1f44 100644
--- a/app/templates/admin/content-pages.html
+++ b/app/templates/admin/content-pages.html
@@ -32,7 +32,8 @@
diff --git a/docs/proposals/TEMP.md b/docs/proposals/TEMP.md
new file mode 100644
index 00000000..5fb22f14
--- /dev/null
+++ b/docs/proposals/TEMP.md
@@ -0,0 +1,557 @@
+● 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