Skip to content

Add GET /contracts/{id}: fetch a single contract's summary #461

Description

@khaylebfortune

Description

GET /contracts lists every indexed contract, and GET /contracts/{id}/events and GET /contracts/{id}/export both operate on one contract — but there is no way to fetch a single contract's own record. A client that has a contract ID and wants its event count, ledger range and last-activity timestamp has to page through the whole /contracts list and filter client-side, or issue an events query and derive the fields itself.

Add GET /contracts/{id}, returning the same ContractSummary the list endpoint already produces, for one contract.

What "done" looks like

  • GET /contracts/{id} returns the contract's summary — ID, event count, first and last ledger, last activity — in the same shape and field names the list endpoint emits for its elements. A client parsing one should parse the other with no special-casing.
  • A contract ID that is well-formed but has no indexed events returns 404, not an empty-valued 200. "No such contract here" and "a contract with zero events" are not distinguishable in this store, and 404 is the honest answer.
  • A malformed contract ID returns 400 with the structured error envelope, consistent with the other {id} routes.
  • The route respects the same scope/tenancy rules as GET /contracts/{id}/events, so a scoped key cannot read a contract outside its watch list.
  • ETag and cache headers behave consistently with the list endpoint.

Implementation guidance

  • The route goes in internal/api/server.go beside the existing /contracts/{id}/… registrations. Note there are two registration sites — the public tree and the scoped tree around line 412 — and the new route belongs in both, matching how /contracts/{id}/events is registered.
  • The store already has the aggregation behind ListContracts; prefer adding a single-contract query in internal/store/postgres.go over fetching a one-element page, so the index is used and no LIMIT/OFFSET machinery is involved. Add the method to the Store interface and implement it for both Postgres and SQLite — an interface method implemented on only one backend breaks the conformance suite.
  • Return store.ErrNotFound from the store and map it in the handler; do not invent a second not-found error.
  • Update the OpenAPI spec in api/. The route-coverage test walks the live chi router and will fail if a registered route has no spec entry — this is the check most likely to catch you out, so run it early.

Expectations

  • Handler tests: success, unknown contract → 404, malformed ID → 400, scoped-key denial.
  • A store conformance test so both backends are covered by the same assertions.
  • Postgres integration test proving the summary fields match what ListContracts reports for the same contract — these must not drift.
  • README endpoint table updated.
  • go build ./..., make test, make lint pass.

Complexity: Medium (150 points). One endpoint, but it touches the store interface, both backends, the scoped route tree and the OpenAPI spec.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Stellar WaveIssues in the Stellar wave programcomplexity:mediumStandard feature touching multiple areas (150 points)enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions