Files
full_encoach_platform/docs/adr/0003-paginated-response-envelope.md
Yamen Ahmad 93c530eef2
Some checks failed
CI / Frontend — lint + build + e2e (push) Has been cancelled
CI / Backend — Odoo HttpCase (push) Has been cancelled
chore(ci,docs): GitHub Actions, ADRs, README overhaul, §21 Hardening Release
- .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
2026-04-19 14:16:47 +04:00

2.0 KiB

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:

{
  "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.