Some checks failed
- Move platforms menu from CMS to Platform Admin section with create/edit - Add platform create page, API endpoint, and service method - Remove CMS-specific content from platform list and detail pages - Create shared entity_selector + entity_selected_badge Jinja macros - Create entity-selector.js generalizing store-selector.js for any entity - Add Tom Select merchant filter to stores page with localStorage persistence - Migrate store-products page to use shared macros (remove 53 lines of duped CSS) - Fix broken icons: puzzle→puzzle-piece, building-storefront→store, language→translate, server→cube Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
663 lines
20 KiB
Python
663 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()
|
|
|
|
|
|
# ============================================================================
|
|
# 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
|
|
),
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# STORE THEMES ROUTES
|
|
# ============================================================================
|
|
|
|
|
|
@router.get("/store-themes", response_class=HTMLResponse, include_in_schema=False)
|
|
async def admin_store_themes_page(
|
|
request: Request,
|
|
current_user: User = Depends(
|
|
require_menu_access("store-themes", FrontendType.ADMIN)
|
|
),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Render store themes selection page.
|
|
Allows admins to select a store to customize their theme.
|
|
"""
|
|
return templates.TemplateResponse(
|
|
"tenancy/admin/store-themes.html",
|
|
get_admin_context(request, db, current_user),
|
|
)
|
|
|
|
|
|
@router.get(
|
|
"/stores/{store_code}/theme",
|
|
response_class=HTMLResponse,
|
|
include_in_schema=False,
|
|
)
|
|
async def admin_store_theme_page(
|
|
request: Request,
|
|
store_code: str = Path(..., description="Store code"),
|
|
current_user: User = Depends(
|
|
require_menu_access("store-themes", FrontendType.ADMIN)
|
|
),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Render store theme customization page.
|
|
Allows admins to customize colors, fonts, layout, and branding.
|
|
"""
|
|
return templates.TemplateResponse(
|
|
"tenancy/admin/store-theme.html",
|
|
get_admin_context(request, db, current_user, store_code=store_code),
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# 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
|
|
),
|
|
)
|