feat: add SQL query tool, platform debug, loyalty settings, and multi-module improvements
Some checks failed
CI / ruff (push) Successful in 14s
CI / pytest (push) Failing after 50m12s
CI / validate (push) Successful in 25s
CI / dependency-scanning (push) Successful in 32s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped

- Add admin SQL query tool with saved queries, schema explorer presets,
  and collapsible category sections (dev_tools module)
- Add platform debug tool for admin diagnostics
- Add loyalty settings page with owner-only access control
- Fix loyalty settings owner check (use currentUser instead of window.__userData)
- Replace HTTPException with AuthorizationException in loyalty routes
- Expand loyalty module with PIN service, Apple Wallet, program management
- Improve store login with platform detection and multi-platform support
- Update billing feature gates and subscription services
- Add store platform sync improvements and remove is_primary column
- Add unit tests for loyalty (PIN, points, stamps, program services)
- Update i18n translations across dev_tools locales

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 20:08:07 +01:00
parent a77a8a3a98
commit 319900623a
77 changed files with 5341 additions and 401 deletions

View File

@@ -48,6 +48,7 @@ class StoreLoginResponse(BaseModel):
user: dict
store: dict
store_role: str
platform_code: str | None = None
@store_auth_router.post("/login", response_model=StoreLoginResponse)
@@ -116,27 +117,48 @@ def store_login(
f"for store {store.store_code} as {store_role}"
)
# Resolve platform from the store's primary platform link.
# Middleware-detected platform is unreliable for API paths on localhost
# (e.g., /api/v1/store/auth/login defaults to "main" instead of the store's platform).
platform_id = None
platform_code = None
if store:
from app.modules.core.services.menu_service import menu_service
from app.modules.tenancy.services.platform_service import platform_service
# Resolve platform — prefer explicit sources, fall back to store's primary platform
from app.modules.tenancy.services.platform_service import platform_service
primary_pid = menu_service.get_store_primary_platform_id(db, store.id)
platform = None
# Source 1: middleware-detected platform (production domain-based)
mw_platform = get_current_platform(request)
if mw_platform and mw_platform.code != "main":
platform = mw_platform
# Source 2: platform_code from login body (dev mode — JS sends platform from page context)
if platform is None and user_credentials.platform_code:
platform = platform_service.get_platform_by_code_optional(
db, user_credentials.platform_code
)
if not platform:
raise InvalidCredentialsException(
f"Unknown platform: {user_credentials.platform_code}"
)
# Source 3: fall back to store's primary platform
if platform is None:
primary_pid = platform_service.get_first_active_platform_id_for_store(
db, store.id
)
if primary_pid:
plat = platform_service.get_platform_by_id(db, primary_pid)
if plat:
platform_id = plat.id
platform_code = plat.code
platform = platform_service.get_platform_by_id(db, primary_pid)
if platform_id is None:
# Fallback to middleware-detected platform
platform = get_current_platform(request)
platform_id = platform.id if platform else None
platform_code = platform.code if platform else None
# Verify store-platform link if platform was resolved explicitly (source 1 or 2)
if platform is not None and (
mw_platform or user_credentials.platform_code
):
link = platform_service.get_store_platform_entry(
db, store.id, platform.id
)
if not link or not link.is_active:
raise InvalidCredentialsException(
f"Store {store.store_code} is not available on platform {platform.code}"
)
platform_id = platform.id if platform else None
platform_code = platform.code if platform else None
# Create store-scoped access token with store information
token_data = auth_service.auth_manager.create_access_token(
@@ -186,6 +208,7 @@ def store_login(
"is_verified": store.is_verified,
},
store_role=store_role,
platform_code=platform_code,
)
@@ -226,6 +249,7 @@ def get_current_store_user(
email=user.email,
role=user.role,
is_active=user.is_active,
platform_code=user.token_platform_code,
)