add build development phases

This commit is contained in:
hamid
2026-06-28 21:59:59 +03:30
parent 1df3cd9f64
commit 53a40dc51d
52 changed files with 12379 additions and 0 deletions
+45
View File
@@ -0,0 +1,45 @@
# Contracts — the shared interface between `client/` and `server/`
The two projects are independent (no shared build). This folder is their **single shared source of
truth** for everything that crosses the wire: API routes, request/response shapes, status codes, enums,
shared flows, and money/format conventions. It lets a frontend agent build against a stable contract
**before, during, and after** the matching backend phase, and it lets the two run in parallel.
## Ownership (this is what makes parallel work safe)
- **Backend owns and writes** `contracts/domains/*` and `contracts/openapi/*`. A backend phase that
ships an API writes/updates the contract in the **same** change.
- **Frontend reads** contracts and derives its TypeScript types from them. The frontend never edits
files here. If a contract is missing or wrong, the frontend appends a request to
[`../shared-working-context/frontend/requests/for-backend.md`](../shared-working-context/frontend/requests/for-backend.md);
the backend delivers the fix in a later change.
## What's here
| Path | What it is |
| --- | --- |
| `conventions/api-conventions.md` | The envelope, routing, pagination, errors, auth, locale — read first. |
| `conventions/money-and-types.md` | How money, dates, enums, gender, IDs are represented on the wire. |
| `domains/_TEMPLATE.md` | The shape every per-domain contract doc follows. |
| `domains/<domain>.md` | One file per domain (identity, catalog, booking, payments, …), added by the backend phase that ships it. |
| `openapi/` | The published `swagger.json` snapshot(s) — the machine-readable contract for type generation. |
## How a contract is produced (backend)
1. Build the endpoints following `server/CONVENTIONS.md`.
2. Write/extend `domains/<domain>.md` from `domains/_TEMPLATE.md`: every route, its method + snake_case
path, auth/policy, request and response JSON (with a real example), the enums it uses, and the error
cases. Reference, don't restate, `conventions/*`.
3. Publish the OpenAPI snapshot per `openapi/README.md`.
4. Note in your handoff (`shared-working-context/backend/handoff/after-backend-phase-N.md`) that the
contract is live.
## How a contract is consumed (frontend)
1. Read `domains/<domain>.md` + `conventions/*`. Derive types in `src/services/{domain}/types.ts`
(or generate from `openapi/swagger.json`) — keep names aligned with the contract.
2. If something is missing/ambiguous, request it (don't guess) and mock behind the `services/{domain}`
seam meanwhile.
> Keep contracts **versioned by being honest**: when a shipped shape changes, update its `domains/*` doc
> and the OpenAPI snapshot in the same change, and call it out in the handoff so the frontend re-syncs.
@@ -0,0 +1,50 @@
# API conventions (read before writing or consuming any contract)
These hold for **every** Balinyaar endpoint. Per-domain contract docs assume them and don't restate them.
## Base & versioning
- Base URL from `NEXT_PUBLIC_API_URL` (client) / `https://localhost:5002` (server default).
- Versioned routes: `api/v{version}/...` (Asp.Versioning). Default `v1`.
- **All URL segments are `snake_case`** (server `SnakeCaseParameterTransformer`). Controllers use
`[controller]`/`[action]` tokens, so `GetNurseProfile``.../get_nurse_profile`.
## Response envelope (`OperationResult` → `ApiResult`)
Every response is the server's standard envelope, not a bare body. Success and failure share the shape;
the frontend's `clientFetch`/`serverFetch` already unwrap it and throw `ApiError` on failure. Document
each endpoint's **payload** (the `data`/result) and its failure cases. The envelope carries at least:
a success flag, an HTTP-aligned status, a user-safe message, and the typed `data` (or validation errors).
The canonical shape is defined by `Baya.Application/Models/ApiResult` + `OperationResult<T>` on the
server — mirror that, don't invent a new envelope.
## Status codes
- `200` success (payload in `data`).
- `400` validation/business-rule failure (field-level errors included).
- `401` unauthenticated (missing/expired token) · `403` unauthorized (lacks permission).
- `404` not found.
- `409` conflict (idempotency / duplicate / state-machine violation) where applicable.
- `5xx` unexpected (generic safe message; details only in server logs).
## Auth
- Bearer JWE in `Authorization: Bearer <token>` (access token, ~15 min). Refresh via the refresh
endpoint; rotation + reuse-detection apply. The client attaches the header automatically in
`clientFetch`. State which policy/role each endpoint needs.
## Localisation
- The client sends the active locale (`Accept-Language` / `x-app-locale`, `fa` default). Server-produced
user-facing messages should honour it. Reference data that has `name_fa`/`name_en` returns both;
the client picks by locale.
## Pagination (mandatory on lists)
- Query params: `page` (1-based) + `page_size` (cap it server-side, e.g. ≤100). Response payload carries
`items` + `total` (+ `page`/`page_size`). Document the default and max `page_size` per endpoint.
## Idempotency (money & side-effecting POSTs)
- Where stated, the client sends an idempotency key (header or body field) and the server dedups. Webhook
endpoints dedup on the provider's `external_event_id`. Document which endpoints require a key.
## Naming
- JSON properties are the server's serialized casing (follow what NSwag/Swagger emits — typically
`snake_case` to match routing, or the project's configured policy; **derive the exact casing from the
published `swagger.json`, don't assume**). The frontend types match the wire exactly.
> When in doubt about an envelope/casing detail, the published `openapi/swagger.json` is authoritative.
@@ -0,0 +1,39 @@
# Money & shared types on the wire
## Money — IRR Rials, integer, no floats
- All monetary values are **IRR Rials as integers** (`BIGINT` server-side). There are **no floats** on
the money path anywhere — not in the DB, not in the API, not in the client.
- Because IRR amounts exceed JS's safe integer range in some aggregates and to avoid float coercion,
represent money on the wire as a **string of digits** (e.g. `"23300000"`) unless the published
`swagger.json` shows otherwise; the client parses with `BigInt`/integer-safe helpers and formats for
display. **Toman is display-only** and is converted to/from Rials **only** inside a provider adapter
at its boundary — never in shared contracts or the client's own math.
- The three booking amounts always satisfy `gross_price_irr = balinyaar_commission_irr + nurse_payout_amount`.
## Dates & times
- Timestamps are **UTC ISO-8601** (`DATETIME2(7)` server-side). Persian-calendar (Shamsi) display is a
**client** concern. The exception is bank-closure scheduling, which the server resolves via the
holiday calendar — the client never computes payout dates.
- `day_of_week` for availability uses the **Shamsi week (0 = Saturday … 6 = Friday)**, not ISO Monday-start.
## Enums (string-valued, stable codes)
Enums cross the wire as their stable string code (e.g. `male`/`female`/`any`, `per_hour`/`per_session`/
`per_half_day`/`per_day`/`per_24h`, booking/verification/payment statuses, `refund_channel` =
`psp_card`/`bnpl_revert`/`manual`). Each domain contract doc lists the exact set it uses. The frontend
mirrors them as string-literal union types and **never** hardcodes a display label off the code — labels
are i18n keys.
## Identifiers
- Entity IDs are integers/`BIGINT` (serialized per the published schema). Human-facing references
(`reference_code` on tickets, `invoice_number`) are strings shown to users — treat as opaque.
## PII & sensitive fields
- Encrypted-at-rest fields (phone, national_id, IBAN, addresses, clinical notes) are returned **only**
to authorized callers and often **masked** (e.g. last 4 of an IBAN). Contracts must state when a field
is masked vs. full, and the two-stage clinical-disclosure rule (full care instructions only after a
booking is confirmed, to the assigned nurse + admin) applies to the relevant payloads.
## Gender (load-bearing)
- `gender` (`male`/`female`) drives **same-gender caregiver matching** — a near-hard requirement. It is
present on users/patients and on the booking request as `required_caregiver_gender`
(`male`/`female`/`any`). Never default or drop it silently.
+36
View File
@@ -0,0 +1,36 @@
# Contract — <Domain> (backend phase bN)
> One-line: what this domain's API covers. Assumes
> [`../conventions/api-conventions.md`](../conventions/api-conventions.md) +
> [`../conventions/money-and-types.md`](../conventions/money-and-types.md). Source of truth for the
> machine schema: [`../openapi/`](../openapi/README.md).
**Status:** live as of backend-phase-bN · **Frontend consumer:** frontend-phase-fM
## Enums used
- `<enum_name>`: `value_a` | `value_b` | … — meaning of each.
## Endpoints
### `<HTTP> api/v1/<controller>/<action>`
- **Purpose:** …
- **Auth:** none | authenticated | policy/role … · **Rate-limited:** yes/no · **Idempotency key:** yes/no
- **Path/query params:** `name` (type) — meaning; pagination `page`/`page_size` (default/max) for lists.
- **Request body:**
```json
{ "field": "example" }
```
- **Success `200` payload (`data`):**
```json
{ "field": "example" }
```
- **Failure cases:** `400` …, `401` …, `403` …, `404` …, `409` … (when/why each).
- **Notes:** masking, two-stage disclosure, tenancy, side effects (notifications/ledger/audit), etc.
_(repeat per endpoint)_
## Shared shapes
- `<DtoName>`: field-by-field (name, type, nullable, masked?, meaning).
## Changelog
- bN — initial contract.
+22
View File
@@ -0,0 +1,22 @@
# OpenAPI snapshots
The server already generates OpenAPI via **NSwag** (Swagger UI at `/swagger`, documents `v1`, `v1.1`).
This folder holds the **published `swagger.json` snapshot(s)** so the frontend can generate/verify types
without running the backend.
## Backend: publish on every API-shipping phase
After adding/changing endpoints and confirming the build, export the OpenAPI document and commit it here
as `swagger.v1.json` (overwrite — git history is the version trail). Typical options:
- Run the API and save `GET /swagger/v1/swagger.json` to `dev/contracts/openapi/swagger.v1.json`, **or**
- Use the NSwag CLI / build target the server already wires to emit the document.
Record in your handoff that the snapshot was refreshed. Keep it in sync with `../domains/*.md` — the
markdown is the human contract, this JSON is the machine contract; they must agree.
## Frontend: consume
Generate types from `swagger.v1.json` (e.g. an `openapi-typescript`-style step) **or** hand-write
`src/services/{domain}/types.ts` to match it. Either way, the wire shapes come from here — not from
guessing. Casing/format questions are resolved by this file.
> Until the first API-shipping backend phase runs, this folder is empty by design.