make the domain configuration, not code: two env vars move everything
A domain change is coming (the Zarinpal terminal rejects the current callback domain). ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS, CORS defaults, the Bazaar redirect URI and deploy.sh's public health URL each spelled the domain out. They now derive from PUBLIC_BASE_URL and FRONTEND_URL, which keep today's domain as their default; env values for the three lists are still added on top, so the old domain can stay allowed while DNS moves. ZARINPAL_CALLBACK_URL used os.environ.get(key, default), so an empty key in .env produced an empty callback and every payment would fail closed. It now uses the same `.strip() or` fallback as its neighbours; the new test caught it. config/tests_domains.py loads settings in a subprocess with a new domain and fails if any derived value still mentions the old one. docs/operations.md gains the step-by-step domain move (DNS, .env, Zarinpal/Bazaar panels, frontend config.js, verification). CLAUDE.md records that the frontend is now ours and where the rebuild stands. 624 tests green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
61
CLAUDE.md
61
CLAUDE.md
@@ -119,48 +119,37 @@ than a coding one, and never assume a step was done just because it was describe
|
||||
|
||||
## Frontend ↔ backend (read before wiring the two together)
|
||||
|
||||
The app is two repos and two domains: the React frontend lives in
|
||||
`Tadrisino/MathApp_frontend` (deployed at `math.tadrisino2.ir`, owned by another
|
||||
developer) and this backend serves `api.math.tadrisino2.ir`. Our typed client for them
|
||||
is `src/api/` inside their repo, delivered by pull request — we have read-only Gitea
|
||||
access there, so changes go via a fork + PR, never a direct push.
|
||||
The app is two repos and two domains: the React frontend `Tadrisino/MathApp_frontend`
|
||||
(deployed at `math.tadrisino2.ir`) and this backend (`api.math.tadrisino2.ir`).
|
||||
**Since 2026-09-26 the owner maintains both:** the frontend developer is on leave and
|
||||
`AliReza_Zareian` has `write` on the frontend repo (branch protection on its `main`
|
||||
unknown — not visible without admin). Local clone: `~/Desktop/MathApp_frontend`.
|
||||
|
||||
**The one trap that keeps biting.** Their `App.tsx` calls relative paths
|
||||
(`fetch('/api/auth/login')`). Those resolve against the *frontend* origin, where nginx
|
||||
answers any unknown path with `index.html` — so the response is `200` with an HTML body
|
||||
and the code dies on `res.json()` with `Unexpected token '<'`. Verify with
|
||||
`curl https://math.tadrisino2.ir/api/grades`: HTTP 200, HTML. Two fixes exist, both
|
||||
shipped in PR #3: a `vite` dev proxy (dev works unchanged) and an exported `apiUrl()`
|
||||
for builds. **The better long-term fix is infrastructure, not code** — proxy `/api` on
|
||||
the frontend domain to this backend, which makes everything same-origin and removes CORS
|
||||
entirely. That needs the server admin, not us.
|
||||
**State on 2026-09-26:** the frontend was rebuilt from the single 3,071-line `App.tsx`
|
||||
into a typed, modular app (branch `rebuild/v2`; its README is the map). Every item of
|
||||
the old handoff list (`~/Desktop/کارهای-فرانت-تدریسینو.html`) is implemented, plus
|
||||
screens for profile, streak, support, parents and invite. Verified by a headless-Chrome
|
||||
walkthrough against a local backend; real SMS login and a real Zarinpal payment were not
|
||||
exercised. **The live site still serves the 2026-09-21 build** that calls a relative
|
||||
`/api` and gets HTML back — nothing reaches users until someone builds and deploys the
|
||||
frontend, and who deploys `math.tadrisino2.ir` is not known to us yet.
|
||||
|
||||
**State on 2026-09-23 (checked against upstream `main`, last commit 2026-09-21):** the
|
||||
screens are wired to the API (PR #4/#5 upstream), but `App.tsx` builds the client with
|
||||
`env.VITE_API_URL ?? "/api"` — while their own `.env.example` and `client.ts` name the
|
||||
variable `VITE_API_BASE_URL`. The deployed bundle has neither, so it calls the relative
|
||||
`/api` and gets HTML back: **math.tadrisino2.ir cannot reach the backend** until either
|
||||
that line falls back to `https://api.math.tadrisino2.ir/api` or nginx proxies `/api`.
|
||||
Our PR #3 fixed the same trap and is still open, now likely conflicting with their
|
||||
rewrite. Every path their client calls exists and was driven live against the server.
|
||||
The app also generates a random `deviceId` per browser, so the seeded
|
||||
`frontend-test-device` account only loads after
|
||||
`localStorage.setItem("deviceId", "frontend-test-device")`.
|
||||
|
||||
**Order matters when bringing the stack up:** backend first, then frontend. If the
|
||||
frontend is tested while `api.math.tadrisino2.ir` is down, every screen fails and it
|
||||
looks like the client is broken.
|
||||
**Domains are configuration, not code.** Backend: only `PUBLIC_BASE_URL` and
|
||||
`FRONTEND_URL`; hosts, CSRF, CORS, Zarinpal callback/return, Bazaar redirect and the
|
||||
`deploy.sh` health URL derive from them (`config/tests_domains.py` guards it). Frontend:
|
||||
`dist/config.js` at runtime, else `VITE_API_BASE_URL` from `.env.production`. A domain
|
||||
move is expected (Zarinpal `-14`); the procedure is in `docs/operations.md`
|
||||
(«عوض کردنِ دامنه»).
|
||||
|
||||
**Never hand-write the client from memory.** Three of fifteen calls were silently wrong
|
||||
the first time. Dump the real contract with
|
||||
`python manage.py spectacular --file schema.yaml --format openapi`, then drive the actual
|
||||
client module against a running server (`runserver` on a spare port + `npx tsx`) before
|
||||
sending anything. `content/tests_frontend_contract.py` is the backend-side guard.
|
||||
the first time, and the rebuilt client found two more the old one had (`POST` instead
|
||||
of `PATCH` on notifications, a dropped guest refresh token). Read the contract from
|
||||
`python manage.py spectacular --file schema.yaml --format openapi` and check shapes
|
||||
against a running server. `content/tests_frontend_contract.py` is the backend-side guard.
|
||||
|
||||
**Content the frontend expects but nobody has filled in yet:** `Unit.blurb`,
|
||||
**Content the frontend shows but nobody has filled in yet:** `Unit.blurb`,
|
||||
`Plan.features`/`badge`, `Lesson.example`/`steps`. All optional, all editable from the
|
||||
admin panel. `Node.icon`/`color` fall back to a deterministic palette so cards are never
|
||||
colourless.
|
||||
admin panel.
|
||||
|
||||
## Operations
|
||||
|
||||
|
||||
Reference in New Issue
Block a user