- .github/workflows/ci.yml: two jobs — frontend (tsc --noEmit, lint, build, Playwright) and backend (Postgres 16 + odoo:19 --test-enable --test-tags encoach_api) — catches regressions before merge. - docs/adr/: start an Architecture Decision Record trail with 0001 canonical directory layout, 0002 JWT refresh flow, 0003 paginated response envelope, 0004 RAG metadata + chunking. - docs/PROJECT_SUMMARY.md §21 Hardening Release: full recap of the AI quality loop, compliance, Paymob, i18n, and CI work shipped in this drop, plus new DB tables, REST routes, frontend routes, verification results, and operator-facing configuration. - README.md refreshed for the v4 split-repo doctrine and the new feature surface. - new_project/DEPRECATED.md: formal retirement notice pointing at backend/ as the canonical tree. Made-with: Cursor
61 lines
2.0 KiB
Markdown
61 lines
2.0 KiB
Markdown
# ADR 0003: Canonical paginated response envelope
|
|
|
|
- **Status:** Accepted
|
|
- **Date:** 2026-04-09
|
|
- **Deciders:** Platform team, Frontend team
|
|
|
|
## Context
|
|
|
|
Different Odoo controllers returned paginated data in at least three shapes:
|
|
|
|
- `{ data: [...], total: N, page, limit }`
|
|
- `{ results: [...], count: N }`
|
|
- `[...]` (bare array, no totals)
|
|
|
|
The frontend grew defensive code paths to handle all three, and every new
|
|
endpoint risked inventing a fourth shape.
|
|
|
|
## Decision
|
|
|
|
Every list endpoint MUST return the canonical envelope produced by
|
|
`encoach_api.controllers.base.paginated_envelope`:
|
|
|
|
```json
|
|
{
|
|
"items": [ … ],
|
|
"data": [ … ],
|
|
"total": 123,
|
|
"page": 1,
|
|
"size": 20
|
|
}
|
|
```
|
|
|
|
- `items` is the canonical field name. New code reads from `items`.
|
|
- `data` mirrors `items` for backwards compatibility with older callers and
|
|
can be removed once every consumer migrates.
|
|
- `total` is the total number of matching records across all pages.
|
|
- `page` is 1-indexed.
|
|
- `size` is the requested page size (capped server-side).
|
|
|
|
On the frontend, `PaginatedResponse<T>` in `frontend/src/types/common.ts`
|
|
exposes both `items` and an optional `data`, and service methods
|
|
(`users.service.ts`, `lms.service.ts`, etc.) construct a clean
|
|
`PaginatedResponse` object from whatever the server returns so that UI code
|
|
never sees the legacy fields.
|
|
|
|
## Consequences
|
|
|
|
- Positive: frontend code is simpler and typesafe — read `items`, done.
|
|
- Positive: OpenAPI spec advertises one consistent shape across endpoints.
|
|
- Negative: one additional key (`data`) is duplicated in responses. Cheap
|
|
(same reference, no JSON bloat) and easy to delete later.
|
|
|
|
## Alternatives considered
|
|
|
|
- **Follow JSON:API's `{ data, meta, links }` convention.** Rejected as
|
|
overkill: we don't use HATEOAS, and the extra indirection would force a
|
|
rewrite of every existing consumer.
|
|
- **Return a bare array + pagination headers.** Rejected because Odoo's
|
|
controller helpers make setting custom headers awkward and because it hides
|
|
totals from curl/Postman users.
|