mirror of
https://github.com/Zproger/Aurum.git
synced 2026-10-12 18:46:40 +07:00
feat: add a Crypto tab with live CoinGecko prices synced into Net Worth
Track individual coins (identity + quantity) in a dedicated tab, backed by CoinGecko's Demo API (AURUM_COINGECKO_API_KEY). A holding is just another Asset under the hood (asset_class=crypto) with its value written as a regular AssetValuation — the existing Net Worth aggregation needs zero changes to pick it up. Prices never refresh in the background (no scheduler in this stack, same reasoning as recurring transactions): a "Refresh prices" button forces a sync, and GET /crypto/holdings lazily re-syncs at most once every 24h. CoinGecko outages leave existing values untouched (error_key: "unreachable") instead of failing the request. Backup/restore now carries CryptoHolding rows too, so a restore doesn't silently strip a holding's coin identity. Backend: 75/75 tests passing (9 new), including a real identity-map staleness bug the tests caught in the raw-Core-upsert + ORM re-read pattern, fixed via populate_existing=True. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01855PiW5Wd8n4aRW4n9kByY
This commit is contained in:
69
DOCS.md
69
DOCS.md
@@ -27,6 +27,7 @@ like Postman/Insomnia.
|
||||
- [Budgets](#budgets)
|
||||
- [Goals](#goals)
|
||||
- [Assets & Net Worth](#assets--net-worth)
|
||||
- [Crypto](#crypto)
|
||||
- [Dashboard, Cash Flow & Reports](#dashboard-cash-flow--reports)
|
||||
- [Insights & Advice](#insights--advice)
|
||||
- [Settings](#settings)
|
||||
@@ -502,6 +503,74 @@ personal vehicle)
|
||||
}
|
||||
```
|
||||
|
||||
## Crypto
|
||||
|
||||
Live-priced crypto holdings — a coin plus a quantity, kept current against
|
||||
[CoinGecko](https://www.coingecko.com/en/api/pricing)'s Demo API. Under the hood each holding **is** an
|
||||
Asset (`asset_class=crypto`, see [Assets & Net Worth](#assets--net-worth)) — deleting one is `DELETE
|
||||
/assets/{asset_id}`, not a separate endpoint, and it shows up in `/net-worth/summary` like any other
|
||||
asset automatically.
|
||||
|
||||
Requires `AURUM_COINGECKO_API_KEY` in `.env` (a free Demo key, no card required). Endpoints that need
|
||||
CoinGecko return `400` with a message telling you so if it's unset.
|
||||
|
||||
Prices are **never** fetched in the background — there's no scheduler in this stack. Two triggers
|
||||
only: `POST /crypto/refresh` (a manual "refresh now"), and a lazy check on every `GET
|
||||
/crypto/holdings` that only actually calls CoinGecko once 24h have passed since the last successful
|
||||
sync. If CoinGecko is unreachable, existing values are left untouched and `error_key` is set instead
|
||||
of the whole request failing.
|
||||
|
||||
| Method | Path | Description |
|
||||
|---|---|---|
|
||||
| `GET` | `/crypto/holdings` | List holdings with their live value. Also runs the lazy once-a-day auto-refresh. |
|
||||
| `POST` | `/crypto/refresh` | Force a price refresh right now, bypassing the 24h window. |
|
||||
| `POST` | `/crypto/holdings` | Add a holding — fetches today's price immediately so it isn't `null` until the next sync. |
|
||||
| `PATCH` | `/crypto/holdings/{asset_id}` | Change `quantity`. Recomputes value from the last known price — does **not** call CoinGecko. |
|
||||
| `GET` | `/crypto/search` | `?q=` — search CoinGecko for a coin to add (name/ticker, returns its `coingecko_id` + logo). |
|
||||
|
||||
**Create body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"coingecko_id": "bitcoin",
|
||||
"symbol": "btc",
|
||||
"name": "Bitcoin",
|
||||
"thumb_url": "https://...",
|
||||
"quantity": "0.05"
|
||||
}
|
||||
```
|
||||
|
||||
`coingecko_id` is CoinGecko's own stable id (not the ticker — tickers collide across unrelated
|
||||
coins) — get it from `/crypto/search` rather than guessing. `quantity` supports up to 18 decimal
|
||||
places (wei-level token amounts).
|
||||
|
||||
**`GET /crypto/holdings` / `POST /crypto/refresh` response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"synced": true,
|
||||
"last_synced_at": "2026-08-30T12:00:00Z",
|
||||
"error_key": null,
|
||||
"holdings": [
|
||||
{
|
||||
"asset_id": 7,
|
||||
"coingecko_id": "bitcoin",
|
||||
"symbol": "BTC",
|
||||
"name": "Bitcoin",
|
||||
"thumb_url": "https://...",
|
||||
"quantity": "0.05",
|
||||
"value": "3000.00",
|
||||
"unit_price": "60000.00",
|
||||
"as_of_date": "2026-08-30"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`value`/`unit_price` are `null` only for a holding whose very first price fetch failed (CoinGecko was
|
||||
down right when it was added) — distinct from a real `0`. `error_key` is `"unreachable"` when this
|
||||
sync attempt couldn't reach CoinGecko (existing values are kept as-is), or `null` otherwise.
|
||||
|
||||
## Dashboard, Cash Flow & Reports
|
||||
|
||||
Read-only aggregation endpoints — the numbers behind Aurum's charts. Useful for pulling summary data
|
||||
|
||||
Reference in New Issue
Block a user