Skip to content

fix(purchases): expose updated_at on v_usdc_purchases view#819

Merged
rickyrombo merged 1 commit into
mainfrom
mp/fix-v-usdc-purchases-updated-at
May 18, 2026
Merged

fix(purchases): expose updated_at on v_usdc_purchases view#819
rickyrombo merged 1 commit into
mainfrom
mp/fix-v-usdc-purchases-updated-at

Conversation

@rickyrombo
Copy link
Copy Markdown
Contributor

Summary

Hotfix for a regression introduced by #816. `/v1/users/{id}/purchases` crashes with:

```
ERROR: column purchases_with_content.updated_at does not exist (SQLSTATE 42703)
```

`api/v1_users_purchases.go:123` selects `purchases_with_content.updated_at` through a CTE wrapping `v_usdc_purchases`, but the view never exposed `updated_at`.

Fix: alias `sp.created_at` as `updated_at` in the view. The legacy `usdc_purchases` table set both columns to `CURRENT_TIMESTAMP` at insert and never updated rows, so `created_at = updated_at` in practice — the alias keeps the API contract intact.

I had this fix as a follow-up commit on the PR #815 branch ("Expose updated_at on v_usdc_purchases view") but it didn't make it into the squash-merge.

Test plan

  • `/v1/users/{id}/purchases` returns 200 again
  • `/v1/users/{id}/sales` (which also selects `updated_at` in `v1_users_sales.go:94`) returns 200

🤖 Generated with Claude Code

v1_users_purchases.go SELECTs purchases_with_content.updated_at through
a CTE wrapping v_usdc_purchases. The view never exposed updated_at,
which crashes the /v1/users/{id}/purchases page with:

  ERROR: column purchases_with_content.updated_at does not exist
  (SQLSTATE 42703)

Alias sp.created_at as updated_at to keep the legacy API contract
intact. The legacy usdc_purchases table set both columns to
CURRENT_TIMESTAMP at insert and never updated rows, so
created_at = updated_at in practice.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@rickyrombo rickyrombo merged commit e3eb44f into main May 18, 2026
3 of 4 checks passed
@rickyrombo rickyrombo deleted the mp/fix-v-usdc-purchases-updated-at branch May 18, 2026 16:16
rickyrombo added a commit that referenced this pull request May 18, 2026
…ng (#820)

## Summary

Companion to #819. The same PR #815 branch had a third follow-up commit
that didn't make it into the squash-merge — this is it.

The view's \`extra_amount\` computation is \`sp.amount -
COALESCE(<price_history_lookup>, 0)\`. When the price-history lookup
returns NULL (no matching row applicable at purchase time), the fallback
to \`0\` makes the subtraction return \`sp.amount\` itself, i.e. the
entire purchase amount is reported as a "tip."

Cases this hits in practice:
- Content deleted before its price was ever indexed
- Backfilled historical purchases whose content predates price_history
tracking
- Test environments that don't fixture price_history

Production purchases written by the Go indexer always have price_history
coverage (the indexer validates against it before writing), so this only
affects the fallback path — but the fallback path is wrong as-shipped.

Change \`COALESCE(..., 0)\` to \`COALESCE(..., sp.amount)\` so the
subtraction nets to 0 when we don't know the base price. Matches the
legacy semantic of "no tip declared."

## Test plan

- [ ] On a prod replica: \`SELECT signature, amount, extra_amount FROM
v_usdc_purchases WHERE extra_amount = amount LIMIT 10\` — should be
empty (or much smaller) after the fix
- [ ] \`/v1/users/{id}/purchases\` for a historical user shows
\`extra_amount: "0"\` instead of \`extra_amount: <full amount>\` for
content without price history

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant