Files
Samir Boulahtit 51a2114e02 refactor(cms): migrate store theme UI from tenancy to CMS module
Move store theme admin pages, templates, and JS from tenancy module
to CMS module where the data layer (model, service, API, schemas)
already lives. Eliminates split ownership.

Moved:
- Route handlers: GET /store-themes, GET /stores/{code}/theme
- Templates: store-theme.html, store-themes.html
- JS: store-theme.js, store-themes.js
- Updated static references: tenancy_static → cms_static

Deleted old tenancy files (no remaining references).
Menu item in CMS definition already pointed to correct route.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 10:30:09 +02:00

635 lines
20 KiB
Python

# app/modules/tenancy/routes/pages/admin.py
"""
Tenancy Admin Page Routes (HTML rendering).
Admin pages for multi-tenant management:
- Merchants
- Stores
- Store domains
- Store themes
- Admin users
- Platforms
"""
from fastapi import APIRouter, Depends, Path, Request
from fastapi.responses import HTMLResponse, RedirectResponse
from sqlalchemy.orm import Session
from app.api.deps import get_db, require_menu_access
from app.modules.core.utils.page_context import get_admin_context
from app.modules.enums import FrontendType
from app.modules.tenancy.models import User
from app.templates_config import templates
router = APIRouter()
# ============================================================================
# MY ACCOUNT (Self-Service)
# ============================================================================
@router.get("/my-account", response_class=HTMLResponse, include_in_schema=False)
async def admin_my_account_page(
request: Request,
current_user: User = Depends(require_menu_access("my_account", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""Render the admin user's personal account page."""
return templates.TemplateResponse(
"tenancy/admin/my-account.html",
get_admin_context(request, db, current_user),
)
# ============================================================================
# MERCHANT MANAGEMENT ROUTES
# ============================================================================
@router.get("/merchants", response_class=HTMLResponse, include_in_schema=False)
async def admin_merchants_list_page(
request: Request,
current_user: User = Depends(require_menu_access("merchants", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render merchants management page.
Shows list of all merchants with stats.
"""
return templates.TemplateResponse(
"tenancy/admin/merchants.html",
get_admin_context(request, db, current_user),
)
@router.get("/merchants/create", response_class=HTMLResponse, include_in_schema=False)
async def admin_merchant_create_page(
request: Request,
current_user: User = Depends(require_menu_access("merchants", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render merchant creation form.
"""
return templates.TemplateResponse(
"tenancy/admin/merchant-create.html",
get_admin_context(request, db, current_user),
)
@router.get(
"/merchants/{merchant_id}", response_class=HTMLResponse, include_in_schema=False
)
async def admin_merchant_detail_page(
request: Request,
merchant_id: int = Path(..., description="Merchant ID"),
current_user: User = Depends(require_menu_access("merchants", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render merchant detail view.
"""
return templates.TemplateResponse(
"tenancy/admin/merchant-detail.html",
get_admin_context(request, db, current_user, merchant_id=merchant_id),
)
@router.get(
"/merchants/{merchant_id}/edit",
response_class=HTMLResponse,
include_in_schema=False,
)
async def admin_merchant_edit_page(
request: Request,
merchant_id: int = Path(..., description="Merchant ID"),
current_user: User = Depends(require_menu_access("merchants", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render merchant edit form.
"""
return templates.TemplateResponse(
"tenancy/admin/merchant-edit.html",
get_admin_context(request, db, current_user, merchant_id=merchant_id),
)
# ============================================================================
# STORE MANAGEMENT ROUTES
# ============================================================================
@router.get("/stores", response_class=HTMLResponse, include_in_schema=False)
async def admin_stores_list_page(
request: Request,
current_user: User = Depends(require_menu_access("stores", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render stores management page.
Shows list of all stores with stats.
"""
return templates.TemplateResponse(
"tenancy/admin/stores.html",
get_admin_context(request, db, current_user),
)
@router.get("/stores/create", response_class=HTMLResponse, include_in_schema=False)
async def admin_store_create_page(
request: Request,
current_user: User = Depends(require_menu_access("stores", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render store creation form.
"""
return templates.TemplateResponse(
"tenancy/admin/store-create.html",
get_admin_context(request, db, current_user),
)
@router.get(
"/stores/{store_code}", response_class=HTMLResponse, include_in_schema=False
)
async def admin_store_detail_page(
request: Request,
store_code: str = Path(..., description="Store code"),
current_user: User = Depends(require_menu_access("stores", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render store detail page.
Shows full store information.
"""
return templates.TemplateResponse(
"tenancy/admin/store-detail.html",
get_admin_context(request, db, current_user, store_code=store_code),
)
@router.get(
"/stores/{store_code}/edit", response_class=HTMLResponse, include_in_schema=False
)
async def admin_store_edit_page(
request: Request,
store_code: str = Path(..., description="Store code"),
current_user: User = Depends(require_menu_access("stores", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render store edit form.
"""
return templates.TemplateResponse(
"tenancy/admin/store-edit.html",
get_admin_context(request, db, current_user, store_code=store_code),
)
# ============================================================================
# STORE DOMAINS ROUTES
# ============================================================================
@router.get(
"/stores/{store_code}/domains",
response_class=HTMLResponse,
include_in_schema=False,
)
async def admin_store_domains_page(
request: Request,
store_code: str = Path(..., description="Store code"),
current_user: User = Depends(require_menu_access("stores", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render store domains management page.
Shows custom domains, verification status, and DNS configuration.
"""
return templates.TemplateResponse(
"tenancy/admin/store-domains.html",
get_admin_context(request, db, current_user, store_code=store_code),
)
# ============================================================================
# STORE ROLES ROUTES
# ============================================================================
@router.get("/store-roles", response_class=HTMLResponse, include_in_schema=False)
async def admin_store_roles_page(
request: Request,
current_user: User = Depends(
require_menu_access("store-roles", FrontendType.ADMIN)
),
db: Session = Depends(get_db),
):
"""
Render store roles management page.
Allows admins to select a store and manage its roles and permissions.
Super admins see merchant → store cascading selection.
Platform admins see store selection scoped to their platforms.
"""
is_super_admin = current_user.role == "super_admin"
return templates.TemplateResponse(
"tenancy/admin/store-roles.html",
get_admin_context(
request, db, current_user, is_super_admin=is_super_admin
),
)
# ============================================================================
# MERCHANT USER MANAGEMENT ROUTES (All Admins)
# ============================================================================
@router.get("/merchant-users", response_class=HTMLResponse, include_in_schema=False)
async def admin_merchant_users_list_page(
request: Request,
current_user: User = Depends(
require_menu_access("merchant-users", FrontendType.ADMIN)
),
db: Session = Depends(get_db),
):
"""
Render merchant users management page.
Shows list of all merchant users (owners and store team members).
Visible to all admins.
"""
return templates.TemplateResponse(
"tenancy/admin/merchant-users.html",
get_admin_context(request, db, current_user),
)
@router.get(
"/merchant-users/{user_id}", response_class=HTMLResponse, include_in_schema=False
)
async def admin_merchant_user_detail_page(
request: Request,
user_id: int = Path(..., description="User ID"),
current_user: User = Depends(
require_menu_access("merchant-users", FrontendType.ADMIN)
),
db: Session = Depends(get_db),
):
"""
Render merchant user detail view.
Shows details for a merchant owner or store team member.
"""
return templates.TemplateResponse(
"tenancy/admin/merchant-user-detail.html",
get_admin_context(request, db, current_user, user_id=user_id),
)
@router.get(
"/merchant-users/{user_id}/edit",
response_class=HTMLResponse,
include_in_schema=False,
)
async def admin_merchant_user_edit_page(
request: Request,
user_id: int = Path(..., description="User ID"),
current_user: User = Depends(
require_menu_access("merchant-users", FrontendType.ADMIN)
),
db: Session = Depends(get_db),
):
"""
Render merchant user edit form.
Allows editing merchant owner or store team member details.
"""
return templates.TemplateResponse(
"tenancy/admin/merchant-user-edit.html",
get_admin_context(request, db, current_user, user_id=user_id),
)
# ============================================================================
# ADMIN USER MANAGEMENT ROUTES (Super Admin Only)
# ============================================================================
@router.get("/admin-users", response_class=HTMLResponse, include_in_schema=False)
async def admin_users_list_page(
request: Request,
current_user: User = Depends(
require_menu_access("admin-users", FrontendType.ADMIN)
),
db: Session = Depends(get_db),
):
"""
Render admin users management page.
Shows list of all admin users (super admins and platform admins).
Super admin only (menu is in super_admin_only section).
"""
return templates.TemplateResponse(
"tenancy/admin/admin-users.html",
get_admin_context(request, db, current_user),
)
@router.get(
"/admin-users/create", response_class=HTMLResponse, include_in_schema=False
)
async def admin_user_create_page(
request: Request,
current_user: User = Depends(
require_menu_access("admin-users", FrontendType.ADMIN)
),
db: Session = Depends(get_db),
):
"""
Render admin user creation form.
Super admin only (menu is in super_admin_only section).
"""
return templates.TemplateResponse(
"tenancy/admin/user-create.html",
get_admin_context(request, db, current_user),
)
@router.get(
"/admin-users/{user_id}", response_class=HTMLResponse, include_in_schema=False
)
async def admin_user_detail_page(
request: Request,
user_id: int = Path(..., description="User ID"),
current_user: User = Depends(
require_menu_access("admin-users", FrontendType.ADMIN)
),
db: Session = Depends(get_db),
):
"""
Render admin user detail view.
Super admin only (menu is in super_admin_only section).
"""
return templates.TemplateResponse(
"tenancy/admin/admin-user-detail.html",
get_admin_context(request, db, current_user, user_id=user_id),
)
@router.get(
"/admin-users/{user_id}/edit", response_class=HTMLResponse, include_in_schema=False
)
async def admin_user_edit_page(
request: Request,
user_id: int = Path(..., description="User ID"),
current_user: User = Depends(
require_menu_access("admin-users", FrontendType.ADMIN)
),
db: Session = Depends(get_db),
):
"""
Render admin user edit form.
Super admin only (menu is in super_admin_only section).
"""
return templates.TemplateResponse(
"tenancy/admin/admin-user-edit.html",
get_admin_context(request, db, current_user, user_id=user_id),
)
# ============================================================================
# USER MANAGEMENT ROUTES (Legacy - Redirects)
# ============================================================================
@router.get("/users", response_class=RedirectResponse, include_in_schema=False)
async def admin_users_page_redirect():
"""
Redirect old /admin/users to /admin/admin-users.
"""
return RedirectResponse(url="/admin/admin-users", status_code=302)
@router.get("/users/create", response_class=RedirectResponse, include_in_schema=False)
async def admin_user_create_page_redirect():
"""
Redirect old /admin/users/create to /admin/admin-users/create.
"""
return RedirectResponse(url="/admin/admin-users/create", status_code=302)
@router.get(
"/users/{user_id}", response_class=RedirectResponse, include_in_schema=False
)
async def admin_user_detail_page_redirect(
user_id: int = Path(..., description="User ID"),
):
"""
Redirect old /admin/users/{id} to /admin/admin-users/{id}.
"""
return RedirectResponse(url=f"/admin/admin-users/{user_id}", status_code=302)
@router.get(
"/users/{user_id}/edit", response_class=RedirectResponse, include_in_schema=False
)
async def admin_user_edit_page_redirect(
user_id: int = Path(..., description="User ID"),
):
"""
Redirect old /admin/users/{id}/edit to /admin/admin-users/{id}/edit.
"""
return RedirectResponse(url=f"/admin/admin-users/{user_id}/edit", status_code=302)
# ============================================================================
# PLATFORM MANAGEMENT ROUTES (Multi-Platform Support)
# ============================================================================
@router.get("/platforms", response_class=HTMLResponse, include_in_schema=False)
async def admin_platforms_list(
request: Request,
current_user: User = Depends(require_menu_access("platforms", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render platforms management page.
Shows all platforms (OMS, Loyalty, etc.) with their configuration.
"""
return templates.TemplateResponse(
"tenancy/admin/platforms.html",
get_admin_context(request, db, current_user),
)
@router.get(
"/platforms/create", response_class=HTMLResponse, include_in_schema=False
)
async def admin_platform_create(
request: Request,
current_user: User = Depends(require_menu_access("platforms", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render platform creation form.
Allows creating a new platform with basic settings.
"""
return templates.TemplateResponse(
"tenancy/admin/platform-create.html",
get_admin_context(request, db, current_user),
)
@router.get(
"/platforms/{platform_code}", response_class=HTMLResponse, include_in_schema=False
)
async def admin_platform_detail(
request: Request,
platform_code: str = Path(..., description="Platform code (oms, loyalty, etc.)"),
current_user: User = Depends(require_menu_access("platforms", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render platform detail page.
Shows platform configuration, marketing pages, and store defaults.
"""
return templates.TemplateResponse(
"tenancy/admin/platform-detail.html",
get_admin_context(request, db, current_user, platform_code=platform_code),
)
@router.get(
"/platforms/{platform_code}/edit",
response_class=HTMLResponse,
include_in_schema=False,
)
async def admin_platform_edit(
request: Request,
platform_code: str = Path(..., description="Platform code"),
current_user: User = Depends(require_menu_access("platforms", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render platform edit form.
Allows editing platform settings, branding, and configuration.
"""
return templates.TemplateResponse(
"tenancy/admin/platform-edit.html",
get_admin_context(request, db, current_user, platform_code=platform_code),
)
@router.get(
"/platforms/{platform_code}/menu-config",
response_class=HTMLResponse,
include_in_schema=False,
)
async def admin_platform_menu_config(
request: Request,
platform_code: str = Path(..., description="Platform code"),
current_user: User = Depends(require_menu_access("platforms", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render platform menu configuration page.
Super admin only - allows configuring which menu items are visible
for the platform's admin and store frontends.
"""
if not current_user.is_super_admin:
return RedirectResponse(
url=f"/admin/platforms/{platform_code}", status_code=302
)
return templates.TemplateResponse(
"tenancy/admin/platform-menu-config.html",
get_admin_context(request, db, current_user, platform_code=platform_code),
)
@router.get(
"/platforms/{platform_code}/modules",
response_class=HTMLResponse,
include_in_schema=False,
)
async def admin_platform_modules(
request: Request,
platform_code: str = Path(..., description="Platform code"),
current_user: User = Depends(require_menu_access("platforms", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render platform module configuration page.
Super admin only - allows enabling/disabling feature modules
for the platform.
"""
if not current_user.is_super_admin:
return RedirectResponse(
url=f"/admin/platforms/{platform_code}", status_code=302
)
return templates.TemplateResponse(
"tenancy/admin/platform-modules.html",
get_admin_context(request, db, current_user, platform_code=platform_code),
)
@router.get(
"/platforms/{platform_code}/modules/{module_code}",
response_class=HTMLResponse,
include_in_schema=False,
)
async def admin_module_info(
request: Request,
platform_code: str = Path(..., description="Platform code"),
module_code: str = Path(..., description="Module code"),
current_user: User = Depends(require_menu_access("platforms", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render module info/detail page.
Shows module details including features, menu items, dependencies,
and self-contained module information.
"""
if not current_user.is_super_admin:
return RedirectResponse(
url=f"/admin/platforms/{platform_code}", status_code=302
)
return templates.TemplateResponse(
"tenancy/admin/module-info.html",
get_admin_context(
request, current_user, platform_code=platform_code, module_code=module_code
),
)
@router.get(
"/platforms/{platform_code}/modules/{module_code}/config",
response_class=HTMLResponse,
include_in_schema=False,
)
async def admin_module_config(
request: Request,
platform_code: str = Path(..., description="Platform code"),
module_code: str = Path(..., description="Module code"),
current_user: User = Depends(require_menu_access("platforms", FrontendType.ADMIN)),
db: Session = Depends(get_db),
):
"""
Render module configuration page.
Allows configuring module-specific settings.
"""
if not current_user.is_super_admin:
return RedirectResponse(
url=f"/admin/platforms/{platform_code}", status_code=302
)
return templates.TemplateResponse(
"tenancy/admin/module-config.html",
get_admin_context(
request, current_user, platform_code=platform_code, module_code=module_code
),
)