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

@@ -1,7 +1,8 @@
# Store Login: JWT Token Gets Wrong Platform
**Status:** Open — needs design review on fallback strategy
**Status:** Resolved
**Date:** 2026-02-24
**Resolved:** 2026-03-10
## Problem
@@ -17,36 +18,37 @@ When a user logs in to a store via `/platforms/loyalty/store/FASHIONHUB/login`,
6. Store auth endpoint calls `get_current_platform(request)` → gets "main" (id=2) instead of "loyalty" (id=3)
7. Token encodes `platform_id=2`, all subsequent menu/API calls use the wrong platform
The Referer-based platform extraction in the middleware (`middleware/platform_context.py` lines 359-374) only handles `/api/v1/storefront/` paths, not `/api/v1/store/` paths.
## Solution (Implemented)
### Why `is_primary` Is Wrong
The login endpoint uses a 3-source priority chain to resolve the platform:
A store can be subscribed to multiple platforms. The platform should be determined by the login URL context (which platform the user navigated from), not by a database default. Using `is_primary` would always pick the same platform regardless of how the user accessed the store.
| Source | How | When it fires |
|--------|-----|---------------|
| **Source 1: Middleware** | `request.state.platform` from domain/subdomain/custom-domain | **Production always** — domain carries platform context in every request |
| **Source 2: Request body** | `platform_code` field in login JSON body | **Dev mode** — JS sends `window.STORE_PLATFORM_CODE \|\| localStorage.store_platform` |
| **Source 3: Fallback** | `get_first_active_platform_id_for_store()` | **Only** on fresh browser in dev mode (no URL context, no localStorage) |
## Key Constraint
### Files Changed
- **Production:** One domain per platform (e.g., `omsflow.lu` for OMS, `loyaltyflow.lu` for loyalty). Store subdomains: `fashionhub.omsflow.lu`. Premium domains: `fashionhub.lu`.
- **Development:** Path-based: `/platforms/{code}/store/{store_code}/login`
- A store can be on multiple platforms and should show different menus depending on which platform URL the user logged in from.
| File | Change |
|------|--------|
| `app/modules/tenancy/routes/api/store_auth.py` | Added `platform_code` to `StoreLoginResponse` and `/me` response |
| `app/modules/tenancy/schemas/auth.py` | Added `platform_code` to `StoreUserResponse` |
| `app/modules/tenancy/static/store/js/login.js` | Save `platform_code` to localStorage on login; use as fallback in login request |
## Current Workaround
### Why Source 3 Fallback Is Safe
`app/modules/tenancy/routes/api/store_auth.py` currently uses `is_primary` to resolve the platform from the store's `store_platforms` table. This works for single-platform stores but breaks for multi-platform stores.
Source 3 only fires when **both** Source 1 and Source 2 have nothing — meaning:
- Not on a platform domain (localhost without `/platforms/` prefix)
- No `platform_code` in request body (no `STORE_PLATFORM_CODE` on page, no localStorage)
## Files Involved
This only happens on a completely fresh browser session in dev mode. In production, Source 1 always resolves because the domain itself identifies the platform.
| File | Role |
|------|------|
| `middleware/platform_context.py` | Platform detection from URL/domain — doesn't cover `/api/v1/store/` paths |
| `middleware/store_context.py` | Store detection from URL/domain |
| `app/modules/tenancy/routes/api/store_auth.py` | Store login endpoint — creates JWT with platform_id |
| `app/modules/tenancy/static/store/js/login.js` | Frontend login — POSTs to `/api/v1/store/auth/login` |
| `static/shared/js/api-client.js` | API client — base URL is `/api/v1` (no platform prefix) |
| `models/schema/auth.py` | `UserLogin` schema — currently has `store_code` but not `platform_code` |
| `app/modules/core/routes/api/store_menu.py` | Menu API — reads `token_platform_id` from JWT |
### Platform Resolution by URL Pattern
## Open Questions
See [middleware.md](../architecture/middleware.md) § "Login Platform Resolution" for the complete matrix.
- What should the fallback strategy be when platform can't be determined from the login context?
- Should the solution also handle storefront customer login (which has the same issue)?
- Should the Referer-based detection in `platform_context.py` be extended to cover `/api/v1/store/` paths as a complementary fix?
## Diagnostic Tools
- **Backend trace**: `/admin/platform-debug` — simulates the full middleware + login resolution pipeline for any host/path combo
- **JS overlay**: `Ctrl+Shift+P` on any store page (localhost only) — shows `window.STORE_PLATFORM_CODE`, `localStorage.store_platform`, JWT decoded platform, `/auth/me` response, and consistency checks