Repository navigation
Description
Problem
Three code-quality findings in `internal/api/handler_history.go` identified during systematic review (source: `docs/code-review/01-api-handlers.md`, findings 01-M1, 01-L1, 01-L2-history).
01-M1 (Medium) — `parseHistoryFilters` limit parsing: silent swallow, no 400
File: `handler_history.go:406-413` (parseHistoryFilters limit block)
`fmt.Sscanf` silently ignores parse failures. A caller that sends `limit=abc` or `limit=9999999999` gets no feedback and the limit silently falls back to the default. This violates the project memory rule `feedback_strict_int_parse` which requires `strconv.Atoi` and a 400 on error.
Fix: Replace `fmt.Sscanf(s, "%d", &limit)` with `strconv.Atoi`; return a 400 ClientError on parse failure; clamp `limit < 1` to default before applying the `MaxListLimit` cap.
01-L1 (Low) — `handler_history.go:46` — misleading `//nolint:gocritic` comment on `append` line
File: `handler_history.go:64` (`all := append(completed, extra...)`)
The nolint comment says "intentional new slice" but the `gocritic appendAssign` lint fires on `x = append(x, ...))` when the right-hand side extends the same variable. The alternative is to allocate `make([]T, 0, len(completed)+len(extra))` and then append, which is clearer and avoids a potentially confusing comment. If the nolint comment is correct and the lint rule is a false positive, the comment must explain why: the current wording does not.
Fix: Either (a) replace with `all := make([]config.PurchaseHistoryRecord, 0, len(completed)+len(extra)); all = append(all, completed...); all = append(all, extra...)`, or (b) clarify the nolint comment with the exact reason the lint fires here.
01-L2 (Low, history site) — `filterPurchaseHistoryByAllowedAccounts` uses `purchases[:0]` alias
File: `handler_history.go:929` (filterPurchaseHistoryByAllowedAccounts)
`filtered := purchases[:0]` is the append-in-place pattern: filtered and purchases share the backing array, so appending to filtered overwrites the input slice if the caller holds it. In this handler the input `all` is consumed nowhere else after the call, but the pattern is fragile and the project memory (`feedback_deep_copy_state`) cautions against shared backing arrays in slice operations. The dashboard handler site has the same issue (routed to FOLD-1041).
Fix: Allocate `filtered := make([]config.PurchaseHistoryRecord, 0, len(purchases))` so the filter produces a fresh slice.
Evidence
- 01-M1: `docs/code-review/01-api-handlers.md`, finding M1
- 01-L1: `docs/code-review/01-api-handlers.md`, finding L1
- 01-L2 (history site): `docs/code-review/01-api-handlers.md`, finding L2
Proposed fix location
All three fixes land as additional commits on PR #1043 (`fix/history-get-readonly-scoping`), which already modifies `internal/api/handler_history.go`.
Activity
fix(api): use strconv.Atoi for limit; fresh slice in filter/merge (cl…
fix(api): move expireIfStale async; exempt empty-account rows from sc…