Files
orion/app/modules/tenancy/routes/pages/admin.py
Samir Boulahtit b9ac252a9f
All checks were successful
CI / ruff (push) Successful in 11s
CI / pytest (push) Successful in 47m48s
CI / validate (push) Successful in 24s
CI / dependency-scanning (push) Successful in 30s
CI / docs (push) Successful in 40s
CI / deploy (push) Successful in 1m18s
feat: add merchant user edit page with editable profile fields
- Add /admin/merchant-users/{id}/edit page route and template
- Replace toggle-status button with edit button on merchant-users list
- Editable fields: username, email, first name, last name
- Quick actions: toggle status, delete (with double confirm)
- Move RBAC two-phase plan to docs/proposals/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 23:30:50 +01:00

617 lines
19 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 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/{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
),
)