Files
tehran_tala/DELIVERY.md
T
s.esmaeiliandClaude Opus 5 53436ec855 chore(release): prepare release/delivery + fix docker env passthrough
DEPLOY-01: docker-compose.yml passed only DATABASE_URL and PORT to the api
service, so JWT_SECRET, JWT_CUSTOMER_SECRET, OTP_MASTER_CODE and every SMS_TREZ_*
variable were silently dropped in Docker. A deployed build would have used the
development JWT fallback and would not have had the delivery master code at all,
so the demo login would have failed with no obvious cause. All are now forwarded,
defaulting to empty (which the code treats as unset); verified with
`docker compose config`.

TEST-01: test-sales-expert-roundtrip.cjs had localhost:3077 hardcoded and could
only connect by accident. Now defaults to the repo's API_PORT. 33/33.

DELIVERY.md documents env vars (names only), run/deploy commands, how migrations
run inside the container, the verification results and the known issues.

release/delivery contains both parity branches with no conflicts (fast-forward).
build 5/5, jest 45/45, admin smoke 43/43, apps smoke 43/43, B2B scan 34/34,
B2B writes 25/25, auth-smoke 32/32, typecheck clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 00:30:09 +03:30

215 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DELIVERY — gold-banking-monorepo
Branch: **`release/delivery`** · Prepared 2026-09-23
Contains everything from `parity/security-hotfix` and `parity/phase-1-admin`.
Both merged with **no conflicts** (`phase-1-admin` already contained
`security-hotfix`, so the merge was a fast-forward). `main` was not touched.
---
## ⚠ Before the real release — do these two things
1. **Remove `OTP_MASTER_CODE` from the environment.** While it is set, **any
active customer can sign in with that one fixed code**. Unset or empty
disables it completely, and the API prints a warning banner at boot for as
long as it is armed. Tracked as **REL-01 (Critical)** in
`docs/parity/ISSUES.md`.
2. **Configure the SMS credentials** (`SMS_TREZ_*`). Until then no customer can
receive a real login code — messages are written to the log instead of sent.
Tracked as **REL-02**.
---
## 1. Environment variables
Names only — no values here, and none are committed. The full annotated list is
in `.env.example`; `docker-compose.yml` passes each one through to the API
container.
### Required
| Variable | Purpose |
|---|---|
| `DATABASE_URL` | PostgreSQL connection string. Inside Docker the host is `db`. |
| `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` | Credentials for the bundled `db` service. |
| `JWT_SECRET` | Signs **admin** tokens. Without it the API falls back to a well-known development string — set it. |
| `JWT_CUSTOMER_SECRET` | Signs **customer** tokens. Deliberately separate from `JWT_SECRET` so an admin token can never validate as a customer token. |
### Optional
| Variable | Default | Purpose |
|---|---|---|
| `PORT` / `API_PORT` | `3000` | API listen port. |
| `JWT_EXPIRES_IN` | `12h` | Admin token lifetime. |
| `JWT_CUSTOMER_EXPIRES_IN` | `30d` | Customer token lifetime. |
| `UPLOAD_DIR` | `<api>/storage/uploads` | Where uploaded files are written. |
| `ADMIN_PANEL_PORT`, `USER_APP_PORT`, `USER_APP_V2_PORT` | 5180–5182 | Dev servers only. |
| `RUN_MIGRATIONS` | `true` | Set `false` to skip the boot-time migration run. |
| `RUN_SEED` | `true` | Set `false` to skip seeding. |
### SMS — all optional; blank means "log, do not send"
`SMS_PROVIDER` (`trez` default, or `none`), `SMS_TREZ_URL`,
`SMS_TREZ_USERNAME`, `SMS_TREZ_PASSWORD`, `SMS_TREZ_SENDER_LINE`.
With these unset the server **still starts normally** and logs a warning; a
failed or skipped SMS never blocks an operation, including customer creation.
### ⚠ `OTP_MASTER_CODE` — delivery only
A fixed OTP accepted for **any active customer**, alongside the real code.
- Read only from the environment; never hardcoded.
- Unset or empty → completely disabled.
- **Must be exactly 4 digits.** Both apps' login box is four single-character
inputs and only submits at four, so a longer code works over the API but
cannot be typed into either app.
- Every login through it is logged with the customer's mobile.
- The real OTP keeps working unchanged while it is armed.
---
## 2. Running it
### Development
```bash
pnpm install
pnpm dev # all apps via turbo
```
Ports come from the repo-root `.env` (single source of truth):
API 3000, admin-panel 5180, user-app 5181, user-app-v2 5182.
### Build
```bash
pnpm build # all workspaces
pnpm build:release # the production/Docker build
```
### Deploy (Docker)
```bash
docker compose up -d --build
```
Services: `db` (Postgres 15), `api` (port 3000), `admin-panel`, `user-app`,
`user-app-v2` (each nginx on port 80). The apps expose no host ports —
Dokploy/Traefik routes to them by domain. Uploads persist in the `api_uploads`
volume; without it every previously uploaded image 404s after a redeploy.
---
## 3. How migrations run
**Automatically, inside the API container, at boot.** No manual step.
`src/prisma/bootstrap.ts` calls `runMigrations()` before Nest starts. That
runner (`src/prisma/migrate.ts`) applies the `.sql` files from
`prisma/migrations` and records each one in its own tracking table,
**`_goldbank_migrations`**, so every file runs exactly once.
It exists because **the deploy image is copy-only and ships no Prisma CLI**, so
`prisma migrate deploy` is not available there. This is why the database has two
migration ledgers — `_prisma_migrations` (written by the Prisma CLI in
development) and `_goldbank_migrations` (written by the runtime runner). Both
are intentional. `_goldbank_migrations` is declared in `schema.prisma` with
`@@ignore` so drift checks do not propose dropping it.
**Never** run `prisma migrate reset`, and never edit either table by hand.
In development, migrations are created with `prisma migrate dev`; in production
the container runner applies them.
Seeding runs once after migrations, gated on a marker row, and is skipped when
no seed script is present.
---
## 4. What was verified on this branch
All green on `release/delivery`:
| Suite | Result |
|---|---|
| `pnpm build` (5 workspaces) | ✅ all pass |
| jest (api unit) | **45/45** |
| `smoke-admin.cjs` — admin flows | **43/43** |
| `smoke-apps.cjs` — customer flows, both apps | **43/43** |
| `scan-admin-b2b.cjs` — every B2B read endpoint | **34 ok, 0 broken** |
| `smoke-admin-b2b-writes.cjs` — order + document writes | **25/25** |
| `auth-smoke.sh` — permissions/401/403 | **32/32** |
| `test-sales-expert-roundtrip.cjs` | **33/33** |
| `test-name-trigger.cjs` | **9/9** |
| typecheck (api, admin-panel, user-app, user-app-v2) | ✅ clean |
Flows walked end to end: admin login, customer creation, sales-expert
create/edit, product and variant create/edit, order list/detail/status change,
wholesale order placement from the admin panel, cash document creation +
confirm + un-confirm; and in **both** customer apps: OTP login with the master
code, product browsing, cart, order placement, order history.
Money paths were checked against the reference's own formulas, not just for a
200: making charge `rash × (1 + (wage − discount)/100)` reproduced exactly, and
the cash-document conversion `price / (price_per_gram / 4.331802)` moved the
customer's gold debt by the expected amount and restored it exactly on
un-confirm.
Every test script deletes only the rows it created; the write test also asserts
the customer's balances end exactly as they started.
---
## 5. Known issues — not done tonight
Nothing here blocks the delivery. Full detail in `docs/parity/ISSUES.md`.
### Missing app pages (not started)
Present in the reference PWA, absent in both apps. Listed in the owner's own
priority order:
| Page | Reference | Notes |
|---|---|---|
| `report` + `report/detail` | `henza_pwa/src/views/report/` | The accounting statement — a store plus two components (`Accounting.vue`, `AccountingDetail.vue`). The largest of the four. |
| `ChildOrders` | `henza_pwa/src/views/user/src/components/ChildOrders.vue` | A referred customer's orders. |
| `customerChoice` | `.../customerChoice.vue` | Acting-as / customer picker. |
| `completeProfile` | `.../completeProfile.vue` | No trace in the target — no page and no route. |
### Behavioural gaps
| ID | What |
|---|---|
| **APP-02** | `user-app-v2` has no `getHomeSlider`, so slides configured in the admin panel appear in **user-app only**; v2 always derives its hero from categories. Not fixed because v2's home is its own design and rewiring it during a delivery risks the page being demonstrated. |
| **PERS-02** | The reference's cities are a province → city **tree** (provinces not selectable); the target flattened this to a `province` string and a flat select on all three Persons forms. The city itself is still captured correctly. |
| **PERS-03** | The reference collects a Jalali **birth date** on the customer forms; the target has no column for it. |
| **PERS-01** | The reference makes a customer's **mobile immutable on edit**; the target allows editing it. Owner ruled this NOT ported — the target keeps its current behaviour. |
| **PERF-01** | `listReagents()` runs one aggregate per row (N+1) for the per-agent sales total. Harmless at the current agent count. |
### Parity work not reached
Phase 1 was not completed: the field-by-field audit stopped after the Persons
section, and products/categories/variants, financial documents, users and
permissions, SMS templates, promotions, cities, branches, workshops and reports
have had only the breakage scan above, not a full parity audit. The
**Phase 1 gate was lifted for tonight only** (D19) and still stands as the next
milestone.
`PERM-01` (the database holds 178 distinct permission ids against the UI's 174)
is still awaiting a decision, because resolving it means deleting rows.
---
## 6. Decisions that shaped this build
Recorded in `docs/parity/DECISIONS.md`:
- **D13** — the retail storefront (`single*`, wallet, comments, `customer.php`,
`site.php`) is **preserved but out of scope**. Nothing there was changed.
- **D19** — delivery mode: implement the main gaps, do not stop to ask, choose
the lowest-risk option, change nothing protected.
- **D20** — SMS can never block a boot or an operation; `OTP_MASTER_CODE` for
the delivery.
- **D11 is cancelled** — superseded by D13.