List purchase orders with their lines

Go · Go · intermediate · modification

Adds the purchase orders list to the buyer API: newest first, 50 per page with a `before` cursor, every order with its lines in `line_no` order. The page query is keyset-paginated on the existing `(org_id, id)` index, so a deep page costs about the same as the first one, and orders without lines serialise as `[]` rather than `null` so the dashboard does not need a null check. A bad cursor is a 400, database errors are logged and answered with a generic 500.

Go 1.22 procurement service on the standard library (`net/http` method-pattern `ServeMux`, `log/slog`, `database/sql` with the pgx v5 stdlib driver, which passes a Go `[]int64` argument through as a PostgreSQL `bigint[]`). `main` wraps the mux in the auth middleware, so every handler runs for a signed-in org member; `auth.FromContext(ctx)` returns the signed-in `auth.Member` (with `UserID` and `OrgID`) and an `ok` flag. `httpx.WriteJSON(w, status, v)` (existing) sets the JSON content type, writes the status and encodes `v`. Tables (existing): `purchase_orders(id bigserial primary key, org_id bigint not null, number text not null, status text not null, created_at timestamptz not null)` with an index on `(org_id, id)`, and `purchase_order_lines(order_id bigint not null references purchase_orders, line_no int not null, sku text not null, description text not null, quantity bigint not null, unit_price_cents bigint not null, primary key (order_id, line_no))`. An order has 1 to 200 lines once submitted (a draft can have none). The pool's statement timeout is 5 seconds. A page does not need a single snapshot across its queries: the dashboard re-polls every minute.

Requirements

Files touched

--- cmd/api/routes.go
 
 	"example.com/procure/internal/catalog"
+	"example.com/procure/internal/purchaseorders"
 	"example.com/procure/internal/suppliers"
 )
 func registerRoutes(mux *http.ServeMux, db *sql.DB) {
 	catalog.Register(mux, db)
+	purchaseorders.Register(mux, db)
 	suppliers.Register(mux, db)
 }
--- internal/purchaseorders/store.go
+package purchaseorders
+
+import (
+	"context"
+	"database/sql"
+)
+
+type Store struct {
+	DB *sql.DB
+}
+
+// ListPage returns up to limit of the org's orders with an id below before,
+// newest first. Lines are not loaded.
+func (s *Store) ListPage(ctx context.Context, orgID, before int64, limit int) ([]Order, error) {
+	rows, err := s.DB.QueryContext(ctx,
+		`SELECT id, number, status, created_at
+		 FROM purchase_orders
+		 WHERE org_id = $1 AND id < $2

Review this PR

Go practice